Search Unity

  1. Get the latest news, tutorials and offers directly to your inbox with our newsletters. Sign up now.
    Dismiss Notice

Mute/UnMute a track via scripting

Discussion in 'Timeline' started by davidosullivan, Dec 21, 2017.

  1. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    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...
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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.
     
    utophobia likes this.
  3. goxy1996

    goxy1996

    Joined:
    Jan 8, 2017
    Posts:
    1
    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??
     
  4. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    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?
     
  5. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    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?
     
  6. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    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)
     
  7. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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.
     
    ContractorNation likes this.
  8. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    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?
     
  9. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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:

    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Runtime.InteropServices;
    6. using UnityEngine;
    7. using Object=UnityEngine.Object;
    8. using UnityEngine.Playables;
    9. using UnityEngine.Animations;
    10. using UnityEngine.Audio;
    11.  
    12. [RequireComponent(typeof(PlayableDirector))]
    13. public class RuntimeMuter : MonoBehaviour
    14. {
    15.     public string trackName;
    16.  
    17.     private int trackIndex = -1;
    18.     private Object trackToMute;
    19.  
    20.     void Awake()
    21.     {
    22.         var playableDirector = GetComponent<PlayableDirector>();
    23.         if (playableDirector.playOnAwake)
    24.             OnPlay(playableDirector);
    25.     }
    26.  
    27.     void OnPlay(PlayableDirector director)
    28.     {
    29.         Mute();
    30.         StartCoroutine(WaitAndUnmute());
    31.     }
    32.  
    33.     IEnumerator WaitAndUnmute()
    34.     {
    35.         yield return new WaitForSeconds(2);
    36.         UnMute();
    37.     }
    38.  
    39.     void FindTrack()
    40.     {
    41.         var playableDirector = GetComponent<PlayableDirector>();
    42.         if (playableDirector != null && playableDirector.playableAsset != null)
    43.         {
    44.             trackIndex = -1;
    45.             var bindings = playableDirector.playableAsset.outputs.ToArray();
    46.             for (int i = 0; i < bindings.Length; i++)
    47.             {
    48.                 if (bindings[i].streamName == trackName)
    49.                 {
    50.                     trackToMute = bindings[i].sourceObject;
    51.                     trackIndex = i;
    52.                     break;
    53.                 }
    54.             }
    55.         }
    56.     }
    57.  
    58.     public void Mute()
    59.     {
    60.         FindTrack();
    61.         if (trackIndex == -1)
    62.             return;
    63.  
    64.         var graph = GetComponent<PlayableDirector>().playableGraph;
    65.         if (!graph.IsValid())
    66.             return;
    67.  
    68.         PlayableOutput output = graph.GetOutput(trackIndex);
    69.         if (output.IsPlayableOutputOfType<AnimationPlayableOutput>())
    70.         {
    71.             ((AnimationPlayableOutput) output).SetTarget(null);
    72.         }
    73.         else if (output.IsPlayableOutputOfType<AudioPlayableOutput>())
    74.         {
    75.             ((AudioPlayableOutput) output).SetTarget(null);
    76.         }
    77.         else
    78.         {
    79.             output.SetUserData(null);
    80.         }
    81.  
    82.     }
    83.  
    84.     public void UnMute()
    85.     {
    86.         if (trackIndex == -1)
    87.             return;
    88.  
    89.         var graph = GetComponent<PlayableDirector>().playableGraph;
    90.         if (!graph.IsValid())
    91.             return;
    92.  
    93.         PlayableOutput output = graph.GetOutput(trackIndex);
    94.         var binding = GetComponent<PlayableDirector>().GetGenericBinding(trackToMute);
    95.         if (output.IsPlayableOutputOfType<AnimationPlayableOutput>())
    96.         {
    97.             var animator = binding as Animator;
    98.             if (animator == null && binding is GameObject)
    99.                 animator = (binding as GameObject).GetComponent<Animator>();
    100.             ((AnimationPlayableOutput) output).SetTarget(animator);
    101.         }
    102.         else if (output.IsPlayableOutputOfType<AudioPlayableOutput>())
    103.         {
    104.             var audioSource = binding as AudioSource;
    105.             if (audioSource == null && binding is GameObject)
    106.                 audioSource = (binding as GameObject).GetComponent<AudioSource>();
    107.             ((AudioPlayableOutput) output).SetTarget(audioSource);
    108.         }
    109.         else
    110.         {
    111.             output.SetUserData(binding);
    112.         }
    113.     }
    114. }
    115.  
     
  10. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    @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 :(
     
  11. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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.
     
  12. SpiralConDave

    SpiralConDave

    Joined:
    Dec 29, 2014
    Posts:
    37
    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?
     
    Last edited: Jan 27, 2018
  13. SuppleTeets

    SuppleTeets

    Joined:
    Aug 22, 2014
    Posts:
    25
    Found this here: https://docs.unity3d.com/2017.3/Documentation/ScriptReference/Timeline.TrackAsset-muted.html

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

    Code (CSharp):
    1.  
    2.         PlayableDirector director = gameObject.GetComponent<PlayableDirector>();
    3.         TimelineAsset asset = director.playableAsset as TimelineAsset;
    4.         foreach (var track in asset.GetOutputTracks())
    5.         {
    6.             if (itsTheRightTrack)
    7.             {
    8.                 track.muted = true;
    9.             }
    10.         }
    11.  
    (might need a director.RebuildGraph() at the end...?)
     
    Last edited: May 2, 2018
    JoRangers likes this.
  14. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    @SuppleTeets unless things have changed post 2017.1 I think you will find that does not work at runtime...
     
  15. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Yes, you need to rebuild the graph if you mute tracks.
     
    ContractorNation likes this.
  16. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    385
    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?
     
  17. SuppleTeets

    SuppleTeets

    Joined:
    Aug 22, 2014
    Posts:
    25
    When I posted that I was 2017.3, now I'm in 2018.1 and it works in both.
     
  18. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    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;
     
  19. slake_it

    slake_it

    Joined:
    Aug 2, 2015
    Posts:
    89
    You can just disable the animator component
     
  20. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    315
    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."
     
  21. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    It definitely should not do that. Is it possible to file a bug? With repro steps, we should be able to to fix it fairly quickly.
     
  22. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    315
    Where should I file it?
     
  23. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Under the help menu, go to Report a Bug....it will bring up a dialog and attach the necessary files.
     
  24. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    315
    I would if it didn't take that long to pack and upload the report and then fail the upload with an error message. Perhaps I could share the repository and describe the steps? If so then where should I send the repo link and description?
     
  25. Skjalg

    Skjalg

    Joined:
    May 25, 2009
    Posts:
    194
  26. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    @Skjalg - Thanks for the report. Just to avoid the need for others to click the link the link, it's been fixed in 2020.1.
     
  27. zaozhuangwang

    zaozhuangwang

    Joined:
    Jul 20, 2018
    Posts:
    2
    I was confused by the timeline recently.Finally I have found a tried-and-true method. Hope it can help you! Here is the code:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Timeline;
    4. public class timeLineMuteUnMute : MonoBehaviour
    5. {
    6.     PlayableDirector m_PlayableDirector;
    7.     TimelineAsset m_TimelineAsset;
    8.     // Start is called before the first frame update
    9.     void Start()
    10.     {
    11.         m_PlayableDirector = FindObjectOfType<PlayableDirector>();
    12.         m_TimelineAsset = (TimelineAsset)m_PlayableDirector.playableAsset;
    13.     }
    14.     public void MuteTrack(int inNum)
    15.     {
    16.         TrackAsset theTrack = m_TimelineAsset.GetOutputTrack(inNum);
    17.         theTrack.muted = true;
    18.         double t0 = m_PlayableDirector.time;
    19.         m_PlayableDirector.RebuildGraph();
    20.         m_PlayableDirector.time = t0;
    21.         m_PlayableDirector.Play();
    22.     }
    23.     public void UnMuteTrack(int inNum)
    24.     {
    25.         TrackAsset theTrack = m_TimelineAsset.GetOutputTrack(inNum);
    26.         theTrack.muted = false;
    27.         double t0 = m_PlayableDirector.time;
    28.         m_PlayableDirector.Stop();
    29.         m_PlayableDirector.time = t0;
    30.         m_PlayableDirector.Play();
    31.         //You can also use this function ,but It will easily make your program crash especially when your timelineasset is huge.
    32.         //TrackAsset theTrack = m_TimelineAsset.GetOutputTrack(inNum);
    33.         //theTrack.muted = false;
    34.         //double t0 = m_PlayableDirector.time;
    35.         //m_PlayableDirector.RebuildGraph();
    36.         //m_PlayableDirector.time = t0;
    37.         //m_PlayableDirector.Play();
    38.     }
    39.  
    40.     private void Update()
    41.     {
    42.         if (Input.GetKeyDown(KeyCode.M))
    43.             MuteTrack(2);
    44.         if (Input.GetKeyDown(KeyCode.U))
    45.             UnMuteTrack(2);
    46.     }
    47. }
    BTW,the crash when trying to mute a track from a signal really has been fixed.( the version I used is 2020.3)
     
  28. Jamez0r

    Jamez0r

    Joined:
    Jul 29, 2019
    Posts:
    174
    I need to be able to disable tracks at runtime, based on whether the game is in Singleplayer mode or Multiplayer mode.

    For tracks with bindings, I'm able to do what has been suggested in this tread - simply removing the Binding.

    But, as mentioned above by @seant_unity , tracks that don't have Bindings (or work even with null binding, like audio) are a problem. Specifically for me right now, audio tracks and Control tracks.

    All I need is the ability to somehow disable the track in any way whatsoever, at runtime. I don't need to add a new track, or "unmute" a track. Can this be done without having to RebuildGraph()? Surely there must be a way to simply disable/not process a track that exists in the current graph?

    I tried muting the track, but it mutes the track on the actual timeline asset permanently. Maybe I am doing something wrong there?

    Would super appreciate any help!! :)
     
unityunity