Hey there, I am trying to mute/unmute tracks in my timeline at runtime. I have had a good look through the code using ILSpy and I know what the problem is, I just don’t know how to solve it.
Muting a previously UN-muted track by simply setting the tracks mute property seems to work fine.
But what is tricky is UN-muting a track that was muted when we started playing. I can see that this is because muted tracks dont get added to the graph. So want I want to do is trigger whatever voodoo is required to create the tracks runtime representation and add that to the directors currently playing graph.
Hopefully there is a way to do this without stopping the timeline and thus resetting everything thats already happened. I just want the new track to ‘join in’ and start playing its clips from wherever the playhead currently is…
Depending on the type of track you are using, you can always add/remove the binding on the playable graph itself while it’s playing. Each track has a corresponding PlayableOutput in the playableGraph.
So, if it’s an animation track you are trying to selectively mute, you could find the correct output on the playable graph and remove (or re-set) the animator.
For audio, it may not work since audio tracks can play without a binding. For all other outputs generated from a timeline in Timeline, they are ScriptPlayableOutputs and Set/GetUserData will set the binding. If there is no binding, it gets a bit more complicated.
I want to mute audio tracks via C# scripts. So I find the playbinding of this audio track, but I can not find any API to mute this Audio. Should I use the playableGraph?? Or how can I achieve that??
Hey @seant_unity Thanks for the tip. That might work for me in some cases, but setting the binding to nothing is not always the same as muting. in a Control Playable for example, the target is actually an exposedRef rather than a binding.
Do you know if there is any way to inject a new ‘track’ into a current graph? Like I say, removing one, thats already there doesn’t seem to be necessary, since once the muted bool is true, it doesn’t seem to perform any operations anyways- but I guess that could change- in that case I need to know how to remove one too!
Also I am a bit confused about ‘wrap modes’ there doesn’t seem to be one that leaves things how they are after the timeline has changed them. You’d think ‘hold’ but like the docs say hold keeps things as they are at the end until the timeline is interrupted (then it reverts)
The reason the docs mention the timeline instance, is the timeline asset isn’t what’s executed. It’s compiled into a playable graph. So if you want to modify a timeline while it’s playing, you are left with two options. The first is to modify the timeline asset, then stop, and restart the playable director. This will recompile the playable graph.
The second is to use the PlayableAPI to modify the playable graph on the fly. The visualizer can be useful to learn how timeline structures it’s graphs if you choose to go this route.
As for the wrap and hold modes - leaving things in the state the timeline left them is dependent on what type of track you are using. Hold should work in most cases - but can be dependent on having a clip on the last frame.
yep the second option (use the PlayableAPI to modify the playable graph on the fly) is what I am after help with.
Do you have any sample code you could provide as an example for how I could inject a previously muted track in to a Playable Graph when it becomes unmuted, so that it is in the graph exactly as it would have been if it had not been muted when the graph was first created?
Muting a track prevents it from generating playables at all. You can try modifying an output once the graph has started playing to mute, then unmute later. This works pretty well for AnimationTracks, but your mileage will vary for other tracks. Audio still plays with no binding (plays in 2D), and other tracks (like activation) will cache the binding so changing it has no effect.
Here’s some example code for changing the binding to simulate muting:
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using Object=UnityEngine.Object;
using UnityEngine.Playables;
using UnityEngine.Animations;
using UnityEngine.Audio;
[RequireComponent(typeof(PlayableDirector))]
public class RuntimeMuter : MonoBehaviour
{
public string trackName;
private int trackIndex = -1;
private Object trackToMute;
void Awake()
{
var playableDirector = GetComponent<PlayableDirector>();
if (playableDirector.playOnAwake)
OnPlay(playableDirector);
}
void OnPlay(PlayableDirector director)
{
Mute();
StartCoroutine(WaitAndUnmute());
}
IEnumerator WaitAndUnmute()
{
yield return new WaitForSeconds(2);
UnMute();
}
void FindTrack()
{
var playableDirector = GetComponent<PlayableDirector>();
if (playableDirector != null && playableDirector.playableAsset != null)
{
trackIndex = -1;
var bindings = playableDirector.playableAsset.outputs.ToArray();
for (int i = 0; i < bindings.Length; i++)
{
if (bindings[i].streamName == trackName)
{
trackToMute = bindings[i].sourceObject;
trackIndex = i;
break;
}
}
}
}
public void Mute()
{
FindTrack();
if (trackIndex == -1)
return;
var graph = GetComponent<PlayableDirector>().playableGraph;
if (!graph.IsValid())
return;
PlayableOutput output = graph.GetOutput(trackIndex);
if (output.IsPlayableOutputOfType<AnimationPlayableOutput>())
{
((AnimationPlayableOutput) output).SetTarget(null);
}
else if (output.IsPlayableOutputOfType<AudioPlayableOutput>())
{
((AudioPlayableOutput) output).SetTarget(null);
}
else
{
output.SetUserData(null);
}
}
public void UnMute()
{
if (trackIndex == -1)
return;
var graph = GetComponent<PlayableDirector>().playableGraph;
if (!graph.IsValid())
return;
PlayableOutput output = graph.GetOutput(trackIndex);
var binding = GetComponent<PlayableDirector>().GetGenericBinding(trackToMute);
if (output.IsPlayableOutputOfType<AnimationPlayableOutput>())
{
var animator = binding as Animator;
if (animator == null && binding is GameObject)
animator = (binding as GameObject).GetComponent<Animator>();
((AnimationPlayableOutput) output).SetTarget(animator);
}
else if (output.IsPlayableOutputOfType<AudioPlayableOutput>())
{
var audioSource = binding as AudioSource;
if (audioSource == null && binding is GameObject)
audioSource = (binding as GameObject).GetComponent<AudioSource>();
((AudioPlayableOutput) output).SetTarget(audioSource);
}
else
{
output.SetUserData(binding);
}
}
}
@seant_unity ah man, thankyou for that, but its still not doing what i want to do, maybe i havent explained clearly enough…
what you describe there works if I want to mute tracks that were unmuted when we start and then mute/unmute them but thats not what I am after.
I want to take a track that was muted at start and add it to the playing directors timelineinstance. Like you say ‘Muting a track prevents it from generating playables at all’ so what I want to do is make it do whatever it would have done if it was unmuted when we started and add that to the timelineInstance…
when I get the directors timelinePlayable by doing something like var thisPlayable = director.playableGraph.GetRootPlayable(0); I can cast that to TimelinePlayable and then I can do things like ‘AddPlayableInput’ but there are no docs for that that I can find and I just cant make it do anything useful
Ok, you can add new playables and new outputs to the graph. For example, you can add another character animating or a new script that always should be run.
The timeline playable activates, and sets the weight and time on all playables that represent clips. If you need any of that behaviour there is unfortunately no API to add that after the graph is created.
Looking for something similar. I have two similar audio tracks. I want to mute one, and unmute the other at Start or Awake (or similar). The above code appears like it may help, seems rather complex though.
For my purposes, I would be fine with an Editor script that would set the tracks muted or not depending on my Build options (my custom build script would call this). Shoudn’t that be easy?
So director.rebuildgraph is what I have been looking for all along?
I’m guessing it will need some post processing if I want to unmute a track while a timeline is actually playing… I.e if the timeline is half way through playing I assume rebuild graph won’t just add the unmuted track and just continue playing will it?