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.
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:
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 ![]()
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 ![]()
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
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!");
}
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;
}
}
@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.
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.
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.
Im doing this:
AudioSource nameClip;
nameClip.Play ();
StartCoroutine (waitAudio ());
private IEnumerator waitAudio ()
{
yield return new WaitForSeconds (nameClip.clip.length);
print (“end of sound”);
}
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);
}
}
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.