Unexplained time delay when playing audio [SOLVED kind of]

Hey there,

I’m working on a basic random generative music player script.

The problem I am having is that I keep getting a very small time delay after the very first section of music has finished playing. All subsequent sections play without this delay.

It’s really annoying me and I have had to make a work around for it for the time being.

Coding is not my strong point. Could it be quick fix I have overlooked?

I have included my script below. I think it’s important to mention that the time delay between music sections persists even when the script starts with playIntro = false:

Thank you in advance for any input that helps point me in the right direction.

Fabrice

using System.Collections.Generic;
using UnityEngine;


[RequireComponent(typeof(AudioSource))]

public class MusicPlayer : MonoBehaviour                                
{
    public AudioClip[] MusicSections;                                   
                                                                        
    private AudioSource audioSource;

    private int lastPlayed;                                             
    public bool playIntro = true;                                       

    void Awake()
    {
        audioSource = GetComponent<AudioSource>();

        if (MusicSections.Length == 0)
        {
            Debug.Log("Please add music segments!");                     
        }

        else
        {
            StartCoroutine(PlayAudio());
        }
    }


        IEnumerator PlayAudio()                                        

    {

        if (playIntro)                                                   
   
        {
            audioSource.clip = MusicSections[0];
            audioSource.Play();

            Debug.Log("Playing clip: " + MusicSections[0]);

            yield return new WaitForSeconds(MusicSections[0].length);    

            playIntro = false;                                           
         
        }

        int section = Random.Range(1, MusicSections.Length);             

        if (section != lastPlayed)                                       
        {
            audioSource.clip = MusicSections[section];
            audioSource.Play();

            Debug.Log("Playing clip: " + MusicSections[section]);        

            yield return new WaitForSeconds(MusicSections[section].length);

            lastPlayed = section;

            StartCoroutine(PlayAudio());                                 
        }


        else
        {

            StartCoroutine(PlayAudio());                                
        }
    }
}

Well, I’m not sure if you are also hit by this, but there seems to be a known issue with playing an audio ( at least since 2011 ) source starting after a noticeable (200-500ms - that’s up to half a second!) delay. There is currently no known remedy. From what I gather, Unity is unsuited for any application that requires precise (within 10ms) audio timing.

You can’t decide to audioSource.Play() when you need it
By that time you’re already too late. That is frame rate dependent, and frames are too slow.

Use PlayScheduled to schedule the clip slightly in advance. The Audio runs on a different thread, and it can start the audio at the correct time if it’s told in advance.

Use doubles for times, not floats.

public class SchedulePlaylist : MonoBehaviour
{

    public List<AudioClip> audioClipPlaylist;
    private List<AudioSource> audioSourcePool;

    //The time at which we need to schedule a new clip
    public double scheduleNewClipTime;
    //The buffer time we have in which to schedule the clip
    public double bufferSeconds = 0.5d;
    //Current audio engine time, just something to look at in the inspector
    public double DSPTime;

    private int currentClipIndex = -1;

    // Use this for initialization
    void Start()
    {

        audioSourcePool = new List<AudioSource>();
        //If we are only playing one piece of audio at a time, we only need two audio sources for accurate scheduling
        audioSourcePool.Add(gameObject.AddComponent<AudioSource>());
        audioSourcePool.Add(gameObject.AddComponent<AudioSource>());

        //Schedule the first audioclip
        scheduleNewClipTime = AudioSettings.dspTime;
    }

    // Update is called once per frame
    void Update()
    {

        DSPTime = AudioSettings.dspTime;


        if (AudioSettings.dspTime > scheduleNewClipTime)
        {
            foreach (var source in audioSourcePool)
            {
                //Find which audio source isn't currently playing, and recycle it
                if (!source.isPlaying)
                {
                    //Set the clip
                    currentClipIndex++;
                    if (currentClipIndex.Equals(audioClipPlaylist.Count)) { currentClipIndex = 0; }
                    source.clip = audioClipPlaylist[currentClipIndex];

                    //Schedule the audio
                    source.PlayScheduled(scheduleNewClipTime + bufferSeconds);

                    double clipLength = (((double)source.clip.samples) / ((double)source.clip.frequency));
                    //Debug.Log("Clip length: " + clipLength);

                    //Calculate the next time we will need to schedule a clip
                    scheduleNewClipTime += clipLength;
                    //Debug.Log("Schedule time: " + scheduleNewClipTime);
                    break;
                }
            }
        }

    }
}

Not sure exactly what’s causing such a significant delay in your code for the second clip played. It happened for me when I ran your code too. I recommend rewriting it using playscheduled, doubles, and without the coroutine. Just get it working first at the most basic level, then you can experiment.

2 Likes

Hey there and thanks for your input!

I have read through the included links. It sounds like you are referring to a different problem, but I’m not sure. Could you explain it again please. Thanks.

Fabrice

Hi Hikiko, thanks for your post and thanks for taking the time to run my script!

I think re-writing the script properly is something I will look into for sure. I did some reading yesterday/today and your code is more in line with the correct approach.

However, I have included my updated workaround script for reference below.

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[RequireComponent(typeof(AudioSource))]

public class MusicPlayer : MonoBehaviour                                   

{
    public AudioClip[] MusicSections;
    // Music files which share the identifier 'Section' go in here. Example track: 'Adventure Inn Section 2.wav'.
    // In the editor, changing the 'Size' of the array increases/decreases the available slots. This is useful for removing unwanted music sections.
    public bool playIntro = true;
    // Disabling this in the Unity Inspector will skip Element 1 in MusicSections Array (by default this should be reserved for files with the 'Intro' identifier. Example track: 'Adventure Inn Section 1 Intro.wav').

    private AudioSource audioSource;
    // The audiosource is responsible for playing all of our 'MusicSections'.
    private int lastPlayed;
    // This keeps a log of the last played music section. Leave this alone unless you know what you are doing!
    private bool preloadBufferActive = true;
    // Necessary Preload buffer, leave this alone unless you know what you are doing!

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

        if (MusicSections.Length == 0)
        {
            Debug.Log("Please add music segments!");                        
           // If you see this message, please check to see if you have music sections loaded!
        }
        else
        {
            StartCoroutine(PlayAudio());
        }
    }

        IEnumerator PlayAudio()                                           
    {
        if (preloadBufferActive)
        {
            audioSource.clip = MusicSections[0];
            audioSource.Play();
            yield return new WaitForSeconds(MusicSections[0].length);
            preloadBufferActive = false;
        }
        if (playIntro)
        // This is always 'Element 1' and should be assigned to audio files with the 'Intro' identifier. Example track: 'Adventure Inn Section 1 Intro.wav').
        {
            audioSource.clip = MusicSections[1];
            audioSource.Play();

            Debug.Log("Playing clip: " + MusicSections[1]);
            // Displays the currently playing clip in the editor console.
            yield return new WaitForSeconds(MusicSections[1].length);       
            // This tells us to wait for the duration of the audio clip before proceeding any further.
            playIntro = false;                                              
            // Ensures the Intro only plays once!
        }

        int section = Random.Range(2, MusicSections.Length);                
        // Random number generator used to determine which music section from the array to play next.

        if (section != lastPlayed)                                          
        // Ensures we don't play the same section twice!
        {
            audioSource.clip = MusicSections[section];
            audioSource.Play();

            Debug.Log("Playing clip: " + MusicSections[section]);
            // Displays the currently playing clip in the editor console.
            yield return new WaitForSeconds(MusicSections[section].length); 
            // This tells us to wait for the duration of the audio clip before proceeding any further.
            lastPlayed = section;
          
            StartCoroutine(PlayAudio());                                    
            // This keeps us in our loop.
        }
        else
        {
            StartCoroutine(PlayAudio());                                    
            // This keeps the us in our loop.
        }
    }
}