Mute/UnMute a track via scripting

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.

1 Like

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!

Any ideas on that?

Still trying to figure this out. Any ideas anyone? i.e. How do I add another trackassets playable to an existing graph?

I notice in the new docs it talks about the director as being a timeline instance, so how can I add something to that instance maybe?

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.

1 Like

hey @seant_unity thanks again for your help.

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 :frowning:

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?

Found this here: Unity - Scripting API: Timeline.TrackAsset.muted

Pretty straight forward actually, I’m using it like this:

        PlayableDirector director = gameObject.GetComponent<PlayableDirector>();
        TimelineAsset asset = director.playableAsset as TimelineAsset;
        foreach (var track in asset.GetOutputTracks())
        {
            if (itsTheRightTrack)
            {
                track.muted = true;
            }
        }

(might need a director.RebuildGraph() at the end…?)

2 Likes

@SuppleTeets unless things have changed post 2017.1 I think you will find that does not work at runtime…

Yes, you need to rebuild the graph if you mute tracks.

2 Likes

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?

When I posted that I was 2017.3, now I’m in 2018.1 and it works in both.

I believe RebuildGraph will just continue playing from where it was. If I am wrong and that’s not the case, you can always do

var t = playableDirector.time;
playableDirector.RebuildGraph();
playableDirector.time = t;

You can just disable the animator component

1 Like

Hey!

When I call RebuildGraph() the Unity editor crashes. Using Unity 2019.1.4f1.

The error seems to be "
Unity.exe caused an Access Violation (0xc0000005)

in module Unity.exe at 0033:7d254df8."