Search Unity

detecting end of audio clip

Discussion in 'Editor & General Support' started by JRL, Sep 20, 2010.

  1. JRL

    JRL

    Joined:
    Sep 2, 2010
    Posts:
    8
    Is there any way to detect when an audio clip ends? (I know, I know you don't hear anything! lol) I mean by code. I'd like to start an event but only at the end of the audio clip.
     
    peony likes this.
  2. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
  3. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
  4. JRL

    JRL

    Joined:
    Sep 2, 2010
    Posts:
    8
    Spent my time looking at audio clip and audio listener - never checked audio source - duh!
     
  5. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Problem is, this kind of thing doesn't work, with some Audio Filters applied. (Reverb, Delay, and Even Chorus will extended the effective length of a clip.) Unity provides no solution for it, and no one responded to me when I questioned this on the beta list. :evil:
     
    neonblitzer, awsapps, ecv80 and 4 others like this.
  6. AkilaeTribe

    AkilaeTribe

    Joined:
    Jul 4, 2010
    Posts:
    1,149
    Those filters modify the length, but there is a way to determine the final length, isn't there ?

    Of course, not everything works for everything, it depends of the context ;)
     
  7. Dover8

    Dover8

    Joined:
    Aug 20, 2010
    Posts:
    94
    Has anyone anywhere found the solution to why audioClip.length always returns -1? I've searched and found lots of people asking, but no answers.

    Code (csharp):
    1.            
    2. gameObject.renderer.material.mainTexture = www.movie;
    3. gameObject.audio.clip = gameObject.renderer.material.mainTexture.audioClip;
    4. Debug.Log("Length: " + gameObject.audio.clip.length);
    5.  
    This is always returning -1 and I am hoping to be able to use it to detect the length of the movie so that I can do some like this:
    Code (csharp):
    1.  
    2. timerBar.transform.localScale = Vector3((0.5/gameObject.audio.clip.length)*gameObject.audio.time, 1, 0.08);
    3.  
    It also seems that gameObject.audio.time resets if you pause then resume, but that's another issue with a simple solution.
     
  8. jaxx0rr

    jaxx0rr

    Joined:
    Aug 25, 2013
    Posts:
    23
    detecting end of streaming ogg audio (only format that streams is vorbis) is not currently supported
    I had to include the audio time.. problem was I had ALOT of ogg files..

    so I made a php script to convert a winamp "pls" playlist (not m3u) into unity code :)
    eg from
    Code (csharp):
    1. File1=actionMood\DST-Assembly.ogg
    2. Title1=Deceased Superior Technician - Assembly
    3. Length1=143
    to
    Code (csharp):
    1. songAction.Push("actionMood\DST-Assembly");
    2. songActionTime.Push("143 ");

    heres the script very easy to use.. just install wamp.. create a folder in the "www" folder called whatever and make a file in it called index.php and put this in:
    Code (csharp):
    1. <?php
    2. if ($handle = opendir('.')) {
    3. while (false !== ($entry = readdir($handle))) {
    4.     if (substr($entry, -3) == "pls") {
    5.         rFile($entry);
    6.     }
    7. }
    8. }
    9. function rFile($f){
    10.     $lines = file($f);
    11.     foreach ($lines as $line_num => $line) {
    12.         if (substr($line, 0, 1) == 'F') {
    13.             echo 'songShort.Push("';
    14.             $x = strpos($line,'=');
    15.             echo str_replace("\\", "/", substr($line, $x+1, -7));
    16.             echo '");<br>';
    17.         }
    18.         if (substr($line, 0, 1) == 'L') {
    19.             echo 'songShortTime.Push("';
    20.             $x = strpos($line,'=');
    21.             echo substr($line, $x+1, -3);
    22.             echo '");<br>';
    23.         }
    24.     }
    25. }
    26. ?>
    then run it using firefox just type "localhost/whatevernameuputasafolder"
    also make sure u have at least one .pls file in the folder (that u generate with winamp -> save playlist -> pls)


    usage

    Code (csharp):
    1. var currentMood : String;
    2.  
    3. var songAction = new Array ();
    4. var songActionTime = new Array ();
    5. var songBoss = new Array ();
    6. var songBossTime = new Array ();
    7. var songGood = new Array ();
    8. var songGoodTime = new Array ();
    9. var songMoment = new Array ();
    10. var songMomentTime = new Array ();
    11. var songNormal = new Array ();
    12. var songNormalTime = new Array ();
    13. var songTitle = new Array ();
    14. var songTitleTime = new Array ();
    15. var songShort = new Array ();
    16. var songShortTime = new Array ();
    17.  
    18. var urlPrefix : String = "http://www.sitename.com/music/";
    19.  
    20. var initialized : boolean = false;
    21.  
    22. var testSound : AudioClip;
    23.  
    24. var fExt : String = ".ogg";
    25.  
    26. var www : WWW;
    27. var url : String = "";
    28.  
    29. var loading : boolean = false;
    30.  
    31. var realAudioLength : int;
    32.  
    33. function Start () {
    34. }
    35.  
    36. function init(){
    37.     if (initialized) return;
    38.     initialized = true;
    39.    
    40.    
    41.     //ridiculous amount of ogg files (9000+) because im a fuking pro like that
    42.    
    43.     songAction.Push("actionMood/someSongWithoutExtension");
    44.     songActionTime.Push("143"); //songDurationInSeconds
    45.     songBoss.Push("bossMood/MarioBrossUnite");
    46.     songBossTime.Push("92");
    47.     songNormal.Push("normalMood/Whatever");
    48.     songNormalTime.Push("377");
    49.    
    50.  
    51. }
    52.  
    53. function Update () {
    54.    
    55.     songManager();
    56.    
    57. }
    58.  
    59. function songManager(){
    60.  
    61. // stuff I tried and failed misserably     
    62.  
    63. /*
    64.     if (!loading  audio.time > audio.clip.length-3) {
    65.         Debug.Log("got to song end -3");
    66.         setSameMood();
    67.     }
    68.  
    69.     if (!loading  audio.time > 0  lastaudiotime == audio.time) {
    70.         //setSameMood();
    71.     }
    72. */
    73.    
    74.                        
    75. // also stuff I tried to get audio started and failed
    76.            
    77. /*
    78.     if(loading  !audio.isPlaying  audio.clip.isReadyToPlay){
    79.         loading = false;
    80.         audio.Play();
    81.     }
    82. */ 
    83.  
    84.     //this works
    85.     if (url != "") {
    86.    
    87.         if (!loading  audio.time > realAudioLength-1) {
    88.             Debug.Log("got to song end -1");
    89.             setSameMood();
    90.         }
    91.    
    92.         if (www.isDone  loading){  
    93.             loading = false;
    94.             audio.Play();
    95.         }
    96.     }  
    97. }
    98.  
    99. function setSameMood(){
    100.     setMood(currentMood);
    101. }
    102.  
    103. function setNewMood(mood:String){
    104.     currentMood = mood;
    105.     setMood(currentMood);
    106. }
    107.  
    108. function setTempMood(mood:String){
    109.     setMood(mood);
    110. }
    111.  
    112. function setMood(mood:String){
    113.     var randomSong : int;
    114.     var audioL : String;
    115.     init();
    116.    
    117.  
    118.     if (mood == "title") {
    119.         randomSong = Random.Range(0, songTitle.length-1);
    120.         url = songTitle[randomSong];
    121.         audioL = songTitleTime[randomSong];
    122.     } else
    123.     if (mood == "normal") {
    124.         randomSong = Random.Range(0, songNormal.length-1);
    125.         url = songNormal[randomSong];
    126.         audioL = songNormalTime[randomSong];
    127.     } else
    128.     if (mood == "action") {
    129.         randomSong = Random.Range(0, songAction.length-1);
    130.         url = songAction[randomSong];
    131.         audioL = songActionTime[randomSong];
    132.     } else
    133.     if (mood == "boss") {
    134.         randomSong = Random.Range(0, songBoss.length-1);
    135.         url = songBoss[randomSong];
    136.         audioL = songBossTime[randomSong];
    137.     } else
    138.     if (mood == "good") {
    139.         randomSong = Random.Range(0, songGood.length-1);
    140.         url = songGood[randomSong];
    141.         audioL = songGoodTime[randomSong];
    142.     } else
    143.     if (mood == "moment") {
    144.         randomSong = Random.Range(0, songMoment.length-1);
    145.         url = songMoment[randomSong];
    146.         audioL = songMoment[randomSong];
    147.     }
    148.     if (mood == "short") {
    149.         randomSong = Random.Range(0, songShort.length-1);
    150.         url = songShort[randomSong];
    151.         audioL = songShortTime[randomSong];
    152.     }
    153.    
    154.     realAudioLength = parseInt(audioL);
    155.    
    156.     if (realAudioLength == 0) {
    157.         return;
    158.     }
    159.    
    160.     loading = true;
    161.    
    162.     Debug.Log(urlPrefix+url+fExt);
    163.  
    164.     if (url != "") {
    165.         www = new WWW (urlPrefix+url+fExt); // start a download of the given URL
    166.         audio.clip = www.GetAudioClip(false, true); // 2D, streaming
    167.     }
    168. }
    169.  
    ps: a great resource for free to use music tracks for games
    http://www.nosoapradio.us/ (not a radio station at all)
    just u have to convert them to ogg (I used MediaCoder)
     
  9. bernardfrancois

    bernardfrancois

    Joined:
    Oct 29, 2009
    Posts:
    373
    Note that using isPlaying doesn't work, as it becomes false when you unfocus your window or tab. The same most likely happens when bringing an iOS or Android app to the background.

    I used mp3 files in my project and kept track of a float variable named 'timePlaying' to test if the current clip finished:
    Code (csharp):
    1. timePlaying >= audio.clip.length
     
    neonblitzer likes this.
  10. sath

    sath

    Joined:
    Mar 25, 2013
    Posts:
    13
    make a float variable("wait") then at the time you call the audio.clip to Play set this variable to be clip.length ,
    something like this:

    Code (csharp):
    1.     AudioSource audioSource
    2.     public AudioClip sound;
    3.     float wait;
    4.     bool check;
    5.  
    6.     void Start(){
    7.         audioSource=this.GetComponent<AudioSource>();
    8.     }
    9.  
    10.     void Update(){
    11.  
    12.         if(Input.GetMouseButtonDown(0)){
    13.             audioSource.clip=sound;
    14.             audioSource.pitch=1f;
    15.             audioSource.audio.Play();
    16.             wait=sound.length;//set wait to be clip's length
    17.             check=true;
    18.         }
    19.  
    20.         if(check){
    21.             wait-=Time.deltaTime; //reverse count
    22.         }
    23.  
    24.         if((wait<0f)  (check)){ //here you can check if clip is not playing
    25.             Debug.Log("sound is end");
    26.             check=false;
    27.         }
    28.     }
    or out of Update

    Code (csharp):
    1.  
    2.    AudioSource audioSource;
    3.  
    4.    void Start()
    5.    {
    6.         audioSource=this.GetComponent<AudioSource>();
    7.    }
    8.  
    9.    void PlayClip(AudioClip clip)
    10.    {
    11.        audioSource.Stop(); //stop previous clip
    12.        audioSource.clip = clip; //assign new clip
    13.        audioSource.Play();
    14.  
    15.        //CancelInvoke("EventOnEnd"); //in case previously invoked
    16.        Invoke(nameof(EventOnEnd), clip.length); //execute on clip finished
    17.    }
    18.  
    19.     void EventOnEnd()
    20.     {
    21.         //execute your code here
    22.        
    23.         if(Application.isEditor) Debug.LogWarning("audio finished!");
    24.     }
     
    Last edited: Oct 30, 2022
  11. Precache

    Precache

    Joined:
    Jul 30, 2015
    Posts:
    47
    If you have to run through an update anyway just check for !isPlaying two frames in a row, it gets the job done without having to keep track of time or resort to an invoke. Unity should add an event for this, as that would be the best way to handle it.

    Code (csharp):
    1.  
    2.     void Update()
    3.     {
    4.         if(!myMusicSource.isPlaying)
    5.         {
    6.             finishedCount++;
    7.             if (finishedCount > 1)
    8.             {
    9.                 SongFinished();
    10.             }
    11.         }
    12.         else
    13.         {
    14.             finishedCount = 0;
    15.         }
    16.     }
    17.  
     
    forestrf and lucassivolella like this.
  12. Negede

    Negede

    Joined:
    Dec 9, 2015
    Posts:
    1
    @SharkoFR i tried using your little code, but the next audiosource didn't know when to play. PlayThatFunkyMusic() is just an AudioSource.Play();

    I had something that worked fine, but with a constant delay that varied too much to just fix it with a frameskip.

    I have embedded just the relevant code in this thread as none of the other codes i have written does anything to the code in question.

    Code (CSharp):
    1.  
    2. bool introMusicIsNoMore;
    3. public AudioSource introMusic;
    4. public AudioSource actionMusicLoop;
    5. float musicTimer;
    6. float progress;
    7.  
    8. void Awake()
    9. {
    10.         progress = 0f;
    11.         musicTimer = 0f;
    12.         introMusicIsNoMore = false;
    13. }
    14.  
    15.  
    16. void Update()
    17. {
    18.         musicTimer = musicTimer + Time.deltaTime;
    19.  
    20.         progress =  Mathf.Clamp01(introMusic.time / introMusic.clip.length);
    21.         if(progress == 1f)
    22.         {
    23.             if(!actionMusicLoop.isPlaying)
    24.             {
    25.                 introMusicIsNoMore = true;
    26.                 PlayThatFunkyMusic();
    27.                 Destroy(introMusic.gameObject);
    28.             }
    29.         }
    30. }
    On a last note. If someone figures out how to make seamless audio playlists in Unity; please make a guide. I will do the same.
     
    Last edited: Dec 21, 2017
    citromfa likes this.
  13. Uncle_Skippy

    Uncle_Skippy

    Joined:
    Oct 24, 2013
    Posts:
    2
    I've written a music manager that uses two Audio Sources and I just cross fade between the 2 Audio Sources and it seems (feels) rather seamless. Not sure if that is what you are looking for but that's an alternate solution. Based on the code above I've been able to detect end of one track playing and can then queue up the next random track to play. Then I just do a cross fade and it works well. This idea can also be used for a contextual system. Going into combat and back out. Fighting a Boss and back out and so forth.

    Unity also has their Audio Mixer and Audio Mixer Groups, this was another solution I was going to explore.

    Link: https://unity3d.com/learn/tutorials...e-order-beta/audiomixer-and-audiomixer-groups
     
  14. donzaemon

    donzaemon

    Joined:
    Mar 24, 2017
    Posts:
    5
    Not sure if you guys had the same resources because some of these posts are old and the versions have changed a lot but recently I have had success with putting it all into a IEnumerator function then first playing the sound

    AudioSource source = gameObject.GetComponent<AudioSource>();
    source.Play();

    then instantiate the pyro and everything else we need to do then move the object so that it doesn't linger and look like a lag.

    gameObject.transform.position = new Vector3(9999, 9999, 9999);

    after that let the function get out of the way until the sound is done

    yield return new WaitWhile(() => source.isPlaying);

    Then after waiting for it

    Destroy(gameObject);

    This seems to be working well so far, they are popping and blowing up and disappearing all at the same time.
     
  15. ANIMAPRO

    ANIMAPRO

    Joined:
    Nov 11, 2017
    Posts:
    5
    Im doing this:
    AudioSource nameClip;

    nameClip.Play ();
    StartCoroutine (waitAudio ());

    private IEnumerator waitAudio ()
    {
    yield return new WaitForSeconds (nameClip.clip.length);
    print ("end of sound");
    }
     
  16. pamelacook

    pamelacook

    Joined:
    Dec 13, 2017
    Posts:
    10
    This is working for me! Thanks for posting.
     
  17. WeltallZero

    WeltallZero

    Joined:
    Oct 23, 2016
    Posts:
    19
    Ancient question, but this seems to be the best solution I've found, with no downsides that I can think of yet:

    bool IsDone (AudioSource audioSource) {
    return !audioSource.loop
    && audioSource.time >= audioSource.clip.length;
    }

    Seems to be robust against changing pitch, pausing the sound, etc. Please feel free to poke holes through it, though!
     
  18. Deleted User

    Deleted User

    Guest

    This is a solid way of doing it however, if you're looking for a conditional that lasts multiple frames, this won't work.
    This is because as soon as the audioSource sees that the audioClip is finished, audioSource.time will reset to 0;
     
  19. Pawl

    Pawl

    Joined:
    Jun 23, 2013
    Posts:
    113
    I found this didn't work in all cases (Unity 2019.1.8f1). I was checking IsDone() within Update() to determine when to clean up each active AudioSource, but on occasion (5-10%) audioSource.time would get set to 0 before ever reaching clip.length.

    I'm not exactly sure why, but perhaps the audio engine updates internally at a different interval which makes it possible for time to skip past the clip length. In any case, I ended up using an internal variable to track if the clip had ever started which seems to be working better so far.

    Code (CSharp):
    1.     private AudioSource _source;
    2.     private bool _didStart;
    3.  
    4.     public bool IsDone {
    5.         get { return !_source.loop && (_didStart && _source.time <= 0.0f); }
    6.     }
    7.  
    8.     private void Update() {
    9.         _didStart |= _source.time > 0.0f;
    10.  
    11.         if (IsDone) {
    12.             Destroy(gameObject);
    13.         }
    14.     }
     
    neonblitzer and LilGames like this.
  20. AShenawy

    AShenawy

    Joined:
    Apr 3, 2018
    Posts:
    4
    This is because Update() is frame rate dependent, while audio playback isn't. So at some point before the next frame is started, the audio clip has already finished playing and the time is reset to 0.
     
    Joe-Censored likes this.
  21. ecv80

    ecv80

    Joined:
    Oct 1, 2017
    Posts:
    28
    I liked this WaitWhile thing so I put in this coroutine that will take a lambda expression and I think it would provide a versatile way of running any stuff after any audio source has stopped playing:

    Code (CSharp):
    1. IEnumerator AfterPlayed(AudioSource audioSource, System.Action action) {
    2.         yield return new WaitWhile(()=>audioSource.isPlaying);
    3.         action();
    4.     }
    Then you call it like so:
    Code (CSharp):
    1.         //...
    2.         audioSource.Play();
    3.  
    4.         StartCoroutine(AfterPlayed(audioSource, ()=>{
    5.             //Whatever you want to run after the sound's done playing goes here
    6.             subject2Animator.SetTrigger("Appear");
    7.         }));
     
  22. biruktes1

    biruktes1

    Joined:
    Nov 15, 2020
    Posts:
    2


    isplaying is the same whether paused or stopped, so you do not know exactly.

    Unity should have had: onPause, onPlay, onStop listenable events. Vain that inconsistent techniques have to be written.
     
  23. Ne0mega

    Ne0mega

    Joined:
    Feb 18, 2018
    Posts:
    755
    saving the previous audioSource.time then subtracting it from the new audioSource.time and checking if it is less than 0 is working for me, though I have my clips on loop:

    if (audioSource.time - previousAudioSourceTime < 0)
    {
    Debug.Log("audio clip finished");
    repeatCounter++;
    }


    what I was going to originally post:
    Yup. I am missing out detections where ( audioSource.time >= audioSourceClipLength ) and also (!audioSource.isPlaying) because it is not frame dependent. I tried it in LateUpdate, with the same luck... none.
    I tried ( audioSource.time >= audioSourceClipLength * 0.999 ) which kind of worked, but I am trying to count the number of repeats, and this would often return 4 or 5 counts, knocking my system out of whack.

    I guess my easiest option from here is to subtract the previous audioSource.time from the current audioSource.time, and if it is negative, that means the clip restarted.

    Somehow, the audioSource must know when to restart the clip, so we should be able to get that too, somehow.
     
    Last edited: Jul 27, 2021
    angelonit likes this.
  24. Antonius007

    Antonius007

    Joined:
    Aug 26, 2020
    Posts:
    10
    Why can't Unity provide a callback on Audio Source when it finished playing? What I don't like about using audio length as a solution is because if it already started playing then the length of audio file is going to overshoot it.

    @ecv80 thanks for the code snippet, it's probably the cleanest solution for this
     
    Last edited: Oct 10, 2021
    davidtabernerom likes this.
  25. davidtabernerom

    davidtabernerom

    Joined:
    Jul 13, 2020
    Posts:
    2
    I know it's a kinda old thread but, if I understood this whole problem properly, why to not use both audioSource.isPlaying and audioSource.time?

    Code (CSharp):
    1. //Of course AudioSource is not in loop mode
    2. //audioSource.loop = false;
    3.  
    4. if (!audioSource.isPlaying && (audioSource.time == 0f)) {
    5.     // The track ended
    6. }
    As long as you control when to start playing, you can check for that and it should work?
     
  26. prakyath_unity

    prakyath_unity

    Joined:
    Dec 11, 2020
    Posts:
    9
    Code (CSharp):
    1.  IEnumerator WaitForSongEnd()
    2.         {
    3.             yield return new WaitUntil(() => !musicSource.isPlaying);
    4.             Debug.Log("Music Ended");
    5.         }
     
    Ne0mega likes this.
  27. sonolil

    sonolil

    Joined:
    Mar 15, 2015
    Posts:
    14
    While I fixed my issue, it would still really be nice to have a way to know if the AudioSource has ended. Even without Loop turned on, the source's time will revert to 0 once it reached the end. This is good enough if we already know if the source has started playing, but that may not always the case. As of now, there's no way of knowing whether the source has ended or has never even started... unless we use coroutines or have a HasPlayed boolean or something.

    Anyway, my issue was about "!source.isPlaying" returning true for a frame or two when the window loses focus. This is because !isPlaying detects pauses as well as stops. I was overlapping two AudioSources to make a seamless loop and this got in the way.

    The quickest and easiest fix which seems to cover all base is to check if the source time is above zero. So "!source.isPlaying && source.time == 0.0f" returns false even when the window loses focus. Losing focus only pauses the audio which keeps the time.

    Hope this helps someone, it wasn't too hard to fix but I still feel like this kind of thing shouldn't have consumed my time in the first place.
     
    Last edited: Aug 11, 2022
    Wilhelm_LAS, neonblitzer and rockin like this.
  28. RegularEarth

    RegularEarth

    Joined:
    Feb 21, 2019
    Posts:
    1
    Having events in the AudioSource like the VideoPlayer.loopPointReached would be very helpful but for now, I would go with davidtabernerom's solution
     
    Pitou22 likes this.
  29. Maxi5The1st

    Maxi5The1st

    Joined:
    Dec 7, 2022
    Posts:
    1
    Here's my system. It works pretty well if you don't want a pause inbetween playing songs:

    Code (CSharp):
    1. public AudioSource playerAudioSourceMusic;
    2.     public AudioClip[] backgroundMusic;
    3.     private int i = 0;
    4.  
    5.  
    6.     void Update()
    7.     {
    8.  
    9.         if(!playerAudioSourceMusic.isPlaying)
    10.         {
    11.             playClip();
    12.             i++;
    13.         }
    14.     }
    15.  
    16.  
    17.  
    18.     public void playClip()
    19.     {
    20.         playerAudioSourceMusic.PlayOneShot(backgroundMusic[i]);
    21.     }
     
  30. MikeOnSoftware

    MikeOnSoftware

    Joined:
    Jan 27, 2020
    Posts:
    5
    Works perfect, but if your audio clip is smaller than 1 sec, for ex.: if using one step sound for walking, it's better to use:
    if (audioSource.time == Mathf.Epsilon) { //then do your staff }