Search Unity

Custom Playable Behaviour with Notification timing in relation to full timeline

Discussion in 'Timeline' started by Sangemdoko, Sep 28, 2019.

  1. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    51
    Hi,

    I'm new to Timeline so this might be basic but I can't find a way to get the notification timings to be relative to the Timeline when I fire them from a playableBehaviour

    PlayableAsset
    Code (CSharp):
    1. public class RhythmBeatAsset :  PlayableAsset
    2. {
    3.     protected RhythmBeatBehaviour m_RhythmBeatBehaviour;
    4.  
    5.     public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
    6.     {
    7.         var playable = ScriptPlayable<RhythmBeatBehaviour>.Create(graph);
    8.  
    9.         m_RhythmBeatBehaviour = playable.GetBehaviour();
    10.  
    11.         return playable;  
    12.     }
    13. }
    PlayableBehaviour
    Code (CSharp):
    1. public class RhythmBeatBehaviour : TimeNotificationBehaviour
    2. {
    3.     protected double m_PreviousTime;
    4.     protected float m_Bpm;
    5.     protected RhythmBeatReceiver m_RhythmBeatReceiver;
    6.    
    7.     public override void OnGraphStart(Playable playable)
    8.     {
    9.         m_PreviousTime = 0;
    10.     }
    11.  
    12.     public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    13.     {
    14.         m_RhythmBeatReceiver = playerData as RhythmBeatReceiver;
    15.         if (m_RhythmBeatReceiver != null) {
    16.             m_Bpm = m_RhythmBeatReceiver.Bpm;
    17.         }
    18.  
    19.         if (m_PreviousTime < playable.GetTime())
    20.         {
    21.             info.output.PushNotification(playable, new MyNotification());
    22.             m_PreviousTime += 60f / m_Bpm;
    23.         }
    24.     }
    25. }
    Notification Receiver
    Code (CSharp):
    1. public class RhythmBeatReceiver : MonoBehaviour, INotificationReceiver
    2. {
    3.     [SerializeField] protected float m_Bpm = 120;
    4.     [SerializeField] protected bool m_DebugLogTimings;
    5.  
    6.     public float Bpm => m_Bpm;
    7.  
    8.     public void OnNotify(Playable origin, INotification notification, object context)
    9.     {
    10.         if (notification != null && m_DebugLogTimings)
    11.         {
    12.             double time = origin.IsValid() ? origin.GetTime() : 0.0;
    13.             Debug.LogFormat("Rhythm Beat Received notification of type {0} at time {1}", notification.GetType(), time);
    14.         }
    15.     }
    16. }
    When I place Notification markers on the track the receiver logs the timings in relation to the full timeline they. When I place a clip the timings of the notification are relative to that clip. That's when I call "origin.GetTime()". From what I understand the Playbale "origin" is a clip within the timeline, and markers have a clip of their own for all markers in a track.

    So I'm wondering how I'm supposed to get the relative timings? Something like clipStartTime+origin.GetTime() would do the trick but I'm not sure where I can get the clipStartTime from the receiver.
    The other thing I wanted to know is how do I push a notification on start and on stop of a clip?

    I have a ton of other questions but I'll ask them at another time
     
  2. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    51
    I forgot to ask Is there a way to pass the timings of markers to the notification. There is always a tiny delay in "GetTime()" because that's when the receiver receives the notification. I want to know if I can somehow get the exact time where the notification was supposed to be push. This way I could get the difference (receivedTime-exactTime) and then I can do adjustment using that difference
     
  3. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,010
    If you want the timeline time, you can use origin.GetGraph().GetRootPlayable(0).GetTime(); The time of the origin is relative to the clip. In the case of signals - their playable is synced to the timeline time, but there is no corresponding clip, hence why they get the timeline's time.

    The notification is received on the same frame it is fired, so their should be no effective difference in time from that perspective.

    For signals, which are IMarkers and INotifications, you can cast the INotification to a signal (or marker) in OnNotify, and get the time it was placed on the timeline/track. You can do something similar and store values in your Notification class. That could store the difference between when it was marked to fire and when it actually did. (e.g. it's 0.03 seconds ahead of the beat because the last frame was 0.05 seconds ago).

    Pushing notifications on the start and the end of a clip can be done via OnBehaviourPlay/OnBehaviourPause in the same manner as you are doing in ProcessFrame.
     
  4. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    51
    That's great Thank you!

    Last question can I get the clip from "origin"?
    Currently my work around is to save to clip to the playableAsset when the graph is built and then I pass the clip through the INotification parameter. It causes some slight problems in the editor preview, but for my purpose I made it play only by checking Application.IsPlaying.

    The other thing is that OnBehaviorPause is called when I build the graph and when I destroy the graph. I get an error because one of the instances I reference in the OnBehaviourPause is destroyed with the graph. Do you have any advice on how I could check if the graph is destroyed or is being destroyed?

    I greatly appreciate the help
     
  5. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,010
    No, that's really the only way right now.

    Yes, OnBehaviourPause is called under a few scenarios related to the graph starting/stopping/destroying. If you only want to handle when the clip is deactivated you can check if frameInfo.effectivePlayState == PlayState.Paused.

    You can then use OnPlayableDestroy() when the graph is actually destroyed.