detecting end of audio clip

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:

IEnumerator AfterPlayed(AudioSource audioSource, System.Action action) {
        yield return new WaitWhile(()=>audioSource.isPlaying);
        action();
    }

Then you call it like so:

        //...
        audioSource.Play();

        StartCoroutine(AfterPlayed(audioSource, ()=>{
            //Whatever you want to run after the sound's done playing goes here
            subject2Animator.SetTrigger("Appear");
        }));

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.

1 Like

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.

1 Like

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

2 Likes

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?

//Of course AudioSource is not in loop mode
//audioSource.loop = false;

if (!audioSource.isPlaying && (audioSource.time == 0f)) {
    // The track ended
}

As long as you control when to start playing, you can check for that and it should work?

7 Likes
 IEnumerator WaitForSongEnd()
        {
            yield return new WaitUntil(() => !musicSource.isPlaying);
            Debug.Log("Music Ended");
        }
1 Like

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.

3 Likes

Having events in the AudioSource like the VideoPlayer.loopPointReached would be very helpful but for now, I would go with davidtabernerom’s solution

3 Likes

Here’s my system. It works pretty well if you don’t want a pause inbetween playing songs:

public AudioSource playerAudioSourceMusic;
    public AudioClip[] backgroundMusic;
    private int i = 0;


    void Update()
    {

        if(!playerAudioSourceMusic.isPlaying)
        {
            playClip();
            i++;
        }
    }

 

    public void playClip()
    {
        playerAudioSourceMusic.PlayOneShot(backgroundMusic[i]);
    }

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 }

1 Like

Maybe something like that. It wouldn’t be affected by pausing the music or anything.

        void PlayMusic()
        {
            Audio.clip = selectedClip;
            Audio.time = .01f;

            if (isMusicOn)
            {
                if (Audio.isPlaying == false)
                {
                    Audio.Play();
                    StopAllCoroutines();
                    StartCoroutine(PlayControl());
                }
            }
            else
            {
                if (Audio.isPlaying)
                {
                    StopAllCoroutines();
                    Audio.Stop();
                }
            }
        }

        IEnumerator PlayControl()
        {
            yield return new WaitWhile(() => (Audio.time > 0));

            if (isMusicPlayRandom)
            {
                LoadMusicClip(null, true);
            }
            else
            {
                PlayMusic();
            }
        }
if (your_sound_clip.clip.length >= your_sound_clip.clip.length){
       your_sound_clip.stop();
}

This will work

I am not sure if this will solve any problem.
As far as I know, when the audio is over, time is set to zero, I mean EXACTLY zero, and any decimal is by definition the audio playing again.