How can I get the currently playing clip?

I need to set a specific value before every clip on a track in a timeline starts playing. So I need to somehow figure out when a new clip on the track is about to begin. Simplified the code looks like this, and is based on a tutorial posed on the Unity blog one or two years ago.

public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
        LightControllerAdv trackBinding = playerData as LightControllerAdv;

        float endVal = 0f;

        int inputCount = playable.GetInputCount(); //get number of clips on track

        for (int i = 0; i < inputCount; i++)
        {
            float inputWeight = playable.GetInputWeight(i);
            ScriptPlayable<LightControlBehaviour> inputPlayable = (ScriptPlayable<LightControlBehaviour>)playable.GetInput(i);
            LightControlBehaviour input = inputPlayable.GetBehaviour();

            endVal += input.endVal * inputWeight;
        }
}

So if I understand this correctly each frame it loops through all the clips in the track in order to figure out the weight of each clip if they are supposed to blend into eachother. But is there a way to get the current clip that is active? When clip0 starts I need to set a specific value, and then when clip1 is about to start I need to change that specific value, and then again when clip2 starts, and so on. But I can’t change this specific value every frame, I only want to change it once per clip.

@seant_unity Any ideas?

When you say about to start, do you mean ‘is starting this frame’ or will start within a given time period?

If it’s this frame, you can override OnBehaviourPlay in your clips PlayableBehaviour (LightControlBehaviour) set a variable to true. Then in the ProcessFrame above (i.e the PlayableBehaviour for the track) you can check the value on each input, make a change and then set it to false. something like:

class LightControlBehaviour : PlayableBehaviour
{
    public bool becameActiveThisFrame;
    public override void OnBehaviourPlay(Playable playable)
    {
        becameActiveThisFrame = true;
    }
}

class LightControlMixerBehaviour : PlayableBehaviour
{
    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        int inputCount = playable.GetInputCount(); //get number of clips on track
        for (int i = 0; i < inputCount; i++)
        {
            ScriptPlayable<LightControlBehaviour> inputPlayable = (ScriptPlayable<LightControlBehaviour>)playable.GetInput(i);
            LightControlBehaviour input = inputPlayable.GetBehaviour();
            if (input.becameActiveThisFrame)
            {
                input.SetSomeValueHere();
                input.becameActiveThisFrame = false;
            }
        }
    }
}

Otherwise if you want to set a value in advance of it becoming active, it’s a bit trickier, but one formula that works is

  1. Add a startTime property or non-serialized field to your PlayableAsset and PlayableBehaviour for the clip. In CreatePlayable, make sure to copy it from the PlayableAsset to the PlayableBehaviour.
  2. In your tracks CreateTrackMixer method, set the start time of the playable asset. CreateTrackMixer gets called before CreatePlayable.
      public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
        {
            foreach (var clip in GetClips())
            {
                var asset = clip.asset as MyPlayableAsset;
                if (asset != null)
                {
                    asset.startTime = clip.start;
                }
            }

            return ScriptPlayable<MyMixerPlayableBehaviour>.Create(graph, inputCount);
        }
  1. In your mixer PlayableBehaviour you can use the timeline time to see if you are within a given range of the clip.
public override void PrepareFrame(Playable playable, FrameData info)
{
    var timelineTime = playable.GetGraph().GetRootPlayable(0).GetTime();
    for (int i = 0; i < playable.GetInputCount(); i++)
    {
        ScriptPlayable<MyPlayableBehaviour> scriptPlayable = (ScriptPlayable<MyPlayableBehaviour>) playable.GetInput(i);
        MyPlayableBehaviour myPlayableBehaviour = scriptPlayable.GetBehaviour();
        double clipStart = myPlayableBehaviour.startTime;

        if (timelineTime > clipStart - 0.5f && timelineTime <= clipStart)
            myPlayableBehaviour.DoSomething();
    }
}
1 Like

Thanks @seant_unity . I tried your first suggestion and it seems to be working so far, great - thank you!

Instead of writing all these scripts, it would be very helpful if Unity would provide officially a method like:
public virtual Clip GetCurrentlyPlayingClip(); in TrackAsset
and/ or
public virtual bool IsClipPlaying(); in PlayableAsset:face_with_spiral_eyes:

2 Likes

Would second these methods! Would be also great to have clear callbacks for 'OnClipStart / OnClipEnd / or a global ‘OnClipChanged’. And an easy getter/setter to the Clip name would be great too!