Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Code Example : How To Detect The End Of The Playable Clip

Discussion in 'Timeline' started by drihpee, Apr 11, 2019.

  1. drihpee

    drihpee

    Joined:
    Apr 15, 2015
    Posts:
    16
    Hi,

    This is my first post here, so, sorry if something is weird.

    Anyway, I'm about to share my little discovery to detect if the clip is ended. But if there's a solution out there already that same or even better than me, let me know.

    Straight to the code:

    Code (CSharp):
    1.     public override void OnBehaviourPause (Playable playable , FrameData info)
    2.     {
    3.         var duration = playable.GetDuration ();
    4.         //var delay = playable.GetDelay (); // probably used in some cases, but for now, just let it be
    5.         var time = playable.GetTime ();
    6.         var delta = info.deltaTime;
    7.  
    8.         if (info.evaluationType == FrameData.EvaluationType.Playback)
    9.         {
    10.             var count = time + delta;
    11.  
    12.             if (count >= duration)
    13.             {
    14.                 Debug.Log ($@"OnClipEnd");
    15.             }
    16.         }
    17.     }
    So the Idea here is we know if the Clip is stop playing for getting function call on
    OnBehaviourPause()


    And for polishing the check, we use the
    info.evaluationType
    to check if it's ended while the timeline getting played (it's work, but when you pause in the middle of the clip, it'll got caught too)

    and if you dig into both parameter, you actually get the required data

    First, is the
    playable.GetDuration()
    , the crucial one, it'll give you the, well, duration of your clip
    Second, the
    playable.GetTime()
    , this is the local, clip played time, between 0 - your duration
    And lastly,
    info.deltaTime
    , the amount of time passes from the previous frame, with this we can make the logic here more promising

    The first thing here if you just printed out the
    playable.GetTime()
    after the check of
    info.evaluationType
    , it'll print out a number that close to your clip's duration, but even that, we can't sure if its because pausing in the middle of the clip or not

    On the other hand tho, the
    info.deltaTime
    variable give us the answer, it'll return 0 if it's paused and from seek, but when played it correctly, it actually give us the delta time,

    So when you add the
    playable.GetTime()
    and the
    info.deltaTime
    you'll get the value >= the
    playable.GetDuration()


    So from there we can check if it's actually ended or not.

    And also there's one function that may be useful, its the
    playable.GetPreviousTime()
    it gives us the time before the current frame time, but if you use that instead, the result will be less than the
    playable.GetDuration()
     
    INeatFreak and zander_m like this.
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Great post! Thank you!

    We identified this as an issue a while back. The method gets called when the playable behaviour becomes inactive due to the time OR when the graph itself gets stopped (i.e. paused). The latter was a mistake, but one that removing would break a lot of code.

    So instead we added https://docs.unity3d.com/ScriptReference/Playables.FrameData-effectivePlayState.html

    If that is set to playing, it means the graph was paused and you can ignore it. If it's paused it means the clip became inactive (or a parent did, but that doesn't happen on custom tracks).

    But your solution looks great...and backwards compatible! :)
     
  3. f0ff886f

    f0ff886f

    Joined:
    Nov 1, 2015
    Posts:
    201
    I have one question here: neither the original method with checking deltaTime+GetTime >= Duration nor just checking if the effectivePlayState is Paused work when the clip is the last clip on the timeline.

    OnBehaviourPause is indeed called, but both of those conditionals will fail. DeltaTime will be 0, and the effectivePlayState will return "Playing".

    The only way I found to reliably handle both cases (a clip in the middle of the timeline, and a clip at the very end), was to check for Mathf.Approximately((float)time, (float)duration). If I checked the values they were always equal right at the end, but for some reason the conditional time == duration would never be true, but Approximately worked out well.

    Edit: actually, info.effectivePlayState == PlayState.Paused will trigger to true at the very beginning of the PlayMode! So you can use either the original approach + Approximately, or a hybrid of seant's and Approximately, as you will still need to check count > duration if effectivePlayState == Paused.

    Final code with Hybrid approach:
    Code (CSharp):
    1.  
    2.     public override void OnBehaviourPause(Playable playable, FrameData info)
    3.     {
    4.         if (!Application.isPlaying)
    5.         {
    6.             return;
    7.         }
    8.  
    9.         var duration = playable.GetDuration();
    10.         var time = playable.GetTime();
    11.         var count = time + info.deltaTime;
    12.  
    13.         if ((info.effectivePlayState == PlayState.Paused && count > duration) || Mathf.Approximately((float)time, (float)duration))
    14.         {
    15.             // Execute your finishing logic here:
    16.             Debug.Log("Clip done!");
    17.         }
    18.     }
     
    Last edited: Apr 14, 2019
    drihpee likes this.
  4. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    You are right, that's a special case. The graph is stopped because the timeline is completed, so the clip is never actually paused on its own, or has it's time updated accurately.

    I think you can more accurately check for that condition by playable.GetGraph().GetRootPlayable(0).IsDone();

    It is basically checking if the timeline playable has been flagged as past it's duration. That's only valid on the timeline playable, and only happens if the wrap mode isn't loop or hold (which is when the graph gets stopped automatically).
     
    io-games likes this.
  5. drihpee

    drihpee

    Joined:
    Apr 15, 2015
    Posts:
    16
    Glad you guys provide the more cleaner look or even shortest way to check the state.
     
  6. drihpee

    drihpee

    Joined:
    Apr 15, 2015
    Posts:
    16
    Yeah, but it is somehow more often happen if the Playable Director Wrap Mode is on Hold.

    Nice workaround, tho.
     
  7. rendermouse

    rendermouse

    Joined:
    Dec 9, 2015
    Posts:
    8
    This is EXACTLY what I needed, because I built a custom clip that jumps the timeline backwards to a loop point while it waits for a button press. I was checking in ProcessFrame to see if we were at the end of the clip, but if the device has a lower frame rate, that check was failing. This "end of clip" check lets me catch this situation and rewind the timeline if you have not clicked the button.
     
  8. dimmduh1

    dimmduh1

    Joined:
    Feb 5, 2021
    Posts:
    24
    Code (CSharp):
    1.     public override void OnBehaviourPause(Playable playable, FrameData info)
    2.     {
    3.         var duration = playable.GetDuration();
    4.         var count = playable.GetTime() + info.deltaTime;
    5.      
    6.         if ((info.effectivePlayState == PlayState.Paused && count > duration) || playable.GetGraph().GetRootPlayable(0).IsDone())
    7.         {
    8.             // Execute your finishing logic here:
    9.             Debug.Log("Clip done!");
    10.         }
    11.     }
     
  9. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    106
    Awesome post. I recently encountered a problem with spine's timeline integration and I'm pretty sure this solves it.

    taking 10 minutes instead of days to solve a bug like that is very much appreciated.
     
  10. Pixelcloud-Games

    Pixelcloud-Games

    Joined:
    Apr 29, 2011
    Posts:
    160
    > taking 10 minutes instead of days to solve a bug like that is very much appreciated.

    Updating to the latest Spine Timeline UPM package which already resolved the problem would also have helped ;).