detecting end of audio clip

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.

1 Like

it’s all in the scripting reference.

OR :

Get the AudioClip length, and make an Invoke (or whatever you want).

Spent my time looking at audio clip and audio listener - never checked audio source - duh!

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:

9 Likes

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

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.

gameObject.renderer.material.mainTexture = www.movie;
gameObject.audio.clip = gameObject.renderer.material.mainTexture.audioClip;
Debug.Log("Length: " + gameObject.audio.clip.length);

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:

timerBar.transform.localScale = Vector3((0.5/gameObject.audio.clip.length)*gameObject.audio.time, 1, 0.08);

It also seems that gameObject.audio.time resets if you pause then resume, but that’s another issue with a simple solution.

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 :slight_smile:
eg from

File1=actionMood\DST-Assembly.ogg
Title1=Deceased Superior Technician - Assembly
Length1=143

to

songAction.Push("actionMood\DST-Assembly");
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:

<?php
if ($handle = opendir('.')) {
while (false !== ($entry = readdir($handle))) {
	if (substr($entry, -3) == "pls") {
		rFile($entry);
	}
}
}
function rFile($f){
	$lines = file($f);
	foreach ($lines as $line_num => $line) {
		if (substr($line, 0, 1) == 'F') {
			echo 'songShort.Push("';
			$x = strpos($line,'=');
			echo str_replace("\\", "/", substr($line, $x+1, -7));
			echo '");
';
		}
		if (substr($line, 0, 1) == 'L') {
			echo 'songShortTime.Push("';
			$x = strpos($line,'=');
			echo substr($line, $x+1, -3);
			echo '");
';
		}
	}
}
?>

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

var currentMood : String;

var songAction = new Array ();
var songActionTime = new Array ();
var songBoss = new Array ();
var songBossTime = new Array ();
var songGood = new Array ();
var songGoodTime = new Array ();
var songMoment = new Array ();
var songMomentTime = new Array ();
var songNormal = new Array ();
var songNormalTime = new Array ();
var songTitle = new Array ();
var songTitleTime = new Array ();
var songShort = new Array ();
var songShortTime = new Array ();

var urlPrefix : String = "http://www.sitename.com/music/";

var initialized : boolean = false;

var testSound : AudioClip;

var fExt : String = ".ogg";

var www : WWW;
var url : String = "";

var loading : boolean = false;

var realAudioLength : int;

function Start () {
}

function init(){
	if (initialized) return;
	initialized = true;
	
	
	//ridiculous amount of ogg files (9000+) because im a fuking pro like that
	
	songAction.Push("actionMood/someSongWithoutExtension");
	songActionTime.Push("143"); //songDurationInSeconds
	songBoss.Push("bossMood/MarioBrossUnite");
	songBossTime.Push("92");
	songNormal.Push("normalMood/Whatever");
	songNormalTime.Push("377");
	

}

function Update () {
	
	songManager();
	
}

function songManager(){

// stuff I tried and failed misserably		

/*
	if (!loading  audio.time > audio.clip.length-3) {
		Debug.Log("got to song end -3");
		setSameMood();
	}

	if (!loading  audio.time > 0  lastaudiotime == audio.time) {
		//setSameMood();
	}
*/
	
						
// also stuff I tried to get audio started and failed
			
/*
	if(loading  !audio.isPlaying  audio.clip.isReadyToPlay){
		loading = false;
		audio.Play();
	}
*/	

	//this works
	if (url != "") {
	
		if (!loading  audio.time > realAudioLength-1) {
			Debug.Log("got to song end -1");
			setSameMood();
		}
	
		if (www.isDone  loading){	
			loading = false;
			audio.Play();
		}
	}	
}

function setSameMood(){
	setMood(currentMood);
}

function setNewMood(mood:String){
    currentMood = mood;
    setMood(currentMood);
}

function setTempMood(mood:String){
    setMood(mood);
}

function setMood(mood:String){
	var randomSong : int;
	var audioL : String;
	init();
	

	if (mood == "title") {
		randomSong = Random.Range(0, songTitle.length-1);
		url = songTitle[randomSong];
		audioL = songTitleTime[randomSong];
	} else
	if (mood == "normal") {
		randomSong = Random.Range(0, songNormal.length-1);
		url = songNormal[randomSong];
		audioL = songNormalTime[randomSong];
	} else
	if (mood == "action") {
		randomSong = Random.Range(0, songAction.length-1);
		url = songAction[randomSong];
		audioL = songActionTime[randomSong];
	} else
	if (mood == "boss") {
		randomSong = Random.Range(0, songBoss.length-1);
		url = songBoss[randomSong];
		audioL = songBossTime[randomSong];
	} else
	if (mood == "good") {
		randomSong = Random.Range(0, songGood.length-1);
		url = songGood[randomSong];
		audioL = songGoodTime[randomSong];
	} else
	if (mood == "moment") {
		randomSong = Random.Range(0, songMoment.length-1);
		url = songMoment[randomSong];
		audioL = songMoment[randomSong];
	}
	if (mood == "short") {
		randomSong = Random.Range(0, songShort.length-1);
		url = songShort[randomSong];
		audioL = songShortTime[randomSong];
	}
	
	realAudioLength = parseInt(audioL);
	
	if (realAudioLength == 0) {
		return;
	}
	
	loading = true;
	
	Debug.Log(urlPrefix+url+fExt);

	if (url != "") {
		www = new WWW (urlPrefix+url+fExt); // start a download of the given URL
		audio.clip = www.GetAudioClip(false, true); // 2D, streaming
	}
}

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)

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:

timePlaying >= audio.clip.length
1 Like

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:

    AudioSource audioSource
    public AudioClip sound;
    float wait;
    bool check;
  
    void Start(){
        audioSource=this.GetComponent<AudioSource>();
    }

    void Update(){

        if(Input.GetMouseButtonDown(0)){
            audioSource.clip=sound;
            audioSource.pitch=1f;
            audioSource.audio.Play();
            wait=sound.length;//set wait to be clip's length
            check=true;
        }

        if(check){
            wait-=Time.deltaTime; //reverse count
        }

        if((wait<0f)  (check)){ //here you can check if clip is not playing
            Debug.Log("sound is end");
            check=false;
        }
    }

or out of Update

   AudioSource audioSource;

   void Start()
   {
        audioSource=this.GetComponent<AudioSource>();
   }

   void PlayClip(AudioClip clip)
   {
       audioSource.Stop(); //stop previous clip
       audioSource.clip = clip; //assign new clip
       audioSource.Play();

       //CancelInvoke("EventOnEnd"); //in case previously invoked
       Invoke(nameof(EventOnEnd), clip.length); //execute on clip finished
   }

    void EventOnEnd()
    {
        //execute your code here
        
        if(Application.isEditor) Debug.LogWarning("audio finished!");
    }
6 Likes

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.

    void Update()
    {
        if(!myMusicSource.isPlaying)
        {
            finishedCount++;
            if (finishedCount > 1)
            {
                SongFinished();
            }
        }
        else
        {
            finishedCount = 0;
        }
    }
2 Likes

@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.

bool introMusicIsNoMore;
public AudioSource introMusic;
public AudioSource actionMusicLoop;
float musicTimer;
float progress;

void Awake()
{
        progress = 0f;
        musicTimer = 0f;
        introMusicIsNoMore = false;
}


void Update()
{
        musicTimer = musicTimer + Time.deltaTime;

        progress =  Mathf.Clamp01(introMusic.time / introMusic.clip.length);
        if(progress == 1f)
        {
            if(!actionMusicLoop.isPlaying)
            {
                introMusicIsNoMore = true;
                PlayThatFunkyMusic();
                Destroy(introMusic.gameObject);
            }
        }
}

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.

1 Like

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: Audio Mixing - Unity Learn

1 Like

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();
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.

4 Likes

Im doing this:
AudioSource nameClip;

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

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

7 Likes

This is working for me! Thanks for posting.

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!

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;

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.

    private AudioSource _source;
    private bool _didStart;

    public bool IsDone {
        get { return !_source.loop && (_didStart && _source.time <= 0.0f); }
    }

    private void Update() {
        _didStart |= _source.time > 0.0f;

        if (IsDone) {
            Destroy(gameObject);
        }
    }
2 Likes

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.

1 Like