PlaySound does not play if method called from a FileWatcher Event

Hi all,

I wrapped my issue in a single method called Testplay(), that plays an audioclip:

When TestPlay() is called from the Start() method, the audioclip plays → Work as expected.
When TestPlay() is called from a FileWatcher callback method, audioclip does NOT play.

Can’t figure out why it is so (I thought it was due to a call to TestPlay() before the json file was closed but it does not seem to be the case).

Your help or advice would be much appreciated. Thank you!

(code and console output below).

        void Start()
        {
            TestPlay();//Music is played OK with this call
        }
    
    
                
        private void OnQueueFileChange(object sender, FileSystemEventArgs e)
        {
            if (e.ChangeType != WatcherChangeTypes.Changed)
            {
                return;
            }
    
            Debug.Log("[Karajan / OnQueueFileChange()] File candidateQueues.json has changed...");
    
            //Wait for the external file to close before calling TestPlay()
            DirectoryInfo prefetch = new DirectoryInfo(queueFolder);
            FileInfo[] log = prefetch.GetFiles("*.json");
    
            if (IsFileLocked(log[0]))
            {
                Debug.Log("[Karajan / OnQueueFileChange()] =====> File not locked, call TestPlay()");
                TestPlay();//Music is NOT PLAYED OK with this call
            }
            else
            {
                Debug.Log("[Karajan / OnQueueFileChange()] <===== Wait for file to unlock before calling TestPlay()");
            }
        }
    
    
    
        private void TestPlay()
        {
            Debug.Log("[Karajan / TestPlay()] ...In TestPlay() method");
    
            //Deserialize names of candidate queues
            string jsonString = File.ReadAllText(queueFolder + candidateQueueFile);
            List<string> qList = JsonConvert.DeserializeObject<List<string>>(jsonString);
    
            //Get first name without file extension
            string audioClipName = qList[0].Split('.')[0];
    
            //Load AudioClip of that name
            AudioClip clip = Resources.Load<AudioClip>(audioFolder + audioClipName);// audioFolder + audioClipName);
            Debug.Log("[Karajan / TestPlay()] Loaded AudioClip name: " + clip.name);
    
            audioSource.clip = clip;
            audioSource.Play();
    
            /* ***ISSUE *** */
            //audioS.Play() above plays OK if TestPlay() called from Start() Method
            //but does not play if TestPlay() called from OnQueueFileChange() method
        }

Now the console output:

First line → “…In TestPlay()” → TestPlay() called from the Start() method → Sound plays OK.

Last line → “…In TestPlay()” → TestPlay() called from the callback OnQueueFileChanged() → Sound DOES NOT play.


[Karajan / TestPlay()] ...In TestPlay() method
UnityEngine.Debug:Log (object)
Karajan:TestPlay () (at Assets/StarterAssets/ContextEvents/Karajan.cs:72)
Karajan:Start () (at Assets/StarterAssets/ContextEvents/Karajan.cs:39)

[Karajan / TestPlay()] Loaded AudioClip name: Music_Loop
UnityEngine.Debug:Log (object)
Karajan:TestPlay () (at Assets/StarterAssets/ContextEvents/Karajan.cs:83)
Karajan:Start () (at Assets/StarterAssets/ContextEvents/Karajan.cs:39)

[Karajan / OnQueueFileChange()] File candidateQueues.json has changed...
UnityEngine.Debug:Log (object)
Karajan:OnQueueFileChange (object,System.IO.FileSystemEventArgs) (at Assets/StarterAssets/ContextEvents/Karajan.cs:51)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

[Karajan / OnQueueFileChange()] File candidateQueues.json has changed...
UnityEngine.Debug:Log (object)
Karajan:OnQueueFileChange (object,System.IO.FileSystemEventArgs) (at Assets/StarterAssets/ContextEvents/Karajan.cs:51)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

[Karajan / OnQueueFileChange()] File candidateQueues.json has changed...
UnityEngine.Debug:Log (object)
Karajan:OnQueueFileChange (object,System.IO.FileSystemEventArgs) (at Assets/StarterAssets/ContextEvents/Karajan.cs:51)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

[Karajan / OnQueueFileChange()] <===== Wait for file to unlock before calling TestPlay()
UnityEngine.Debug:Log (object)
Karajan:OnQueueFileChange (object,System.IO.FileSystemEventArgs) (at Assets/StarterAssets/ContextEvents/Karajan.cs:64)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

[Karajan / OnQueueFileChange()] <===== Wait for file to unlock before calling TestPlay()
UnityEngine.Debug:Log (object)
Karajan:OnQueueFileChange (object,System.IO.FileSystemEventArgs) (at Assets/StarterAssets/ContextEvents/Karajan.cs:64)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

[Karajan / OnQueueFileChange()] =====> File not locked, call TestPlay()
UnityEngine.Debug:Log (object)
Karajan:OnQueueFileChange (object,System.IO.FileSystemEventArgs) (at Assets/StarterAssets/ContextEvents/Karajan.cs:59)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

[Karajan / TestPlay()] ...In TestPlay() method
UnityEngine.Debug:Log (object)
Karajan:TestPlay () (at Assets/StarterAssets/ContextEvents/Karajan.cs:72)
Karajan:OnQueueFileChange (object,System.IO.FileSystemEventArgs) (at Assets/StarterAssets/ContextEvents/Karajan.cs:60)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

Really banging my head over this one… :slight_smile:

When SynchronizingObject is null, methods handling the Changed, Created, Deleted, and Renamed events are called on a thread from the system thread pool

Which confirms what I suspected. The events are not fired on the main thread, resulting in the Play method not being handled properly.

FileSystemWatcher watcher;
private object _lock;
private bool callTestPlay;

void Awake()
{
    _lock = new object();

     audioSource = sourceA.GetComponent<AudioSource>();

    watcher = new FileSystemWatcher(queueFolder)
    {
        NotifyFilter = NotifyFilters.LastAccess
    };

    watcher.Changed += OnChangeDetected;

    watcher.Filter = candidateQueueFile;
    watcher.IncludeSubdirectories = false;
    watcher.EnableRaisingEvents = true;
}

private void OnQueueFileChange(object sender, FileSystemEventArgs e)
{
    if (e.ChangeType != WatcherChangeTypes.Changed)
    {
        return;
    }

    Debug.Log("[Karajan / OnQueueFileChange()] File candidateQueues.json has changed...");
 
    //Wait for the external file to close before calling TestPlay()
    DirectoryInfo prefetch = new DirectoryInfo(queueFolder);
    FileInfo[] log = prefetch.GetFiles("*.json");
 
    if (IsFileLocked(log[0]))
    {
        Debug.Log("[Karajan / OnQueueFileChange()] =====> File not locked, call TestPlay()");

         lock(_lock)
        {
            callTestPlay= true;
        }
    }
    else
    {
        Debug.Log("[Karajan / OnQueueFileChange()] <===== Wait for file to unlock before calling TestPlay()");
    }
}

private void Update()
{
    lock(_lock)
    {
        if(callTestPlay)
        {
            TestPlay();
            callTestPlay = false;
        }
    }
}

private void OnDestroy()
{
    watcher.Dispose();
}

@Hellium Moving forward but still not there :
Did inherit the interface:

public class Karajan : MonoBehaviour, ISynchronizeInvoke

Did implement the interface (all members):

    IAsyncResult ISynchronizeInvoke.BeginInvoke(Delegate method, object[] args)
    {
        throw new NotImplementedException();
    }

    object ISynchronizeInvoke.EndInvoke(IAsyncResult result)
    {
        throw new NotImplementedException();
    }

    object ISynchronizeInvoke.Invoke(Delegate method, object[] args)
    {
        throw new NotImplementedException();
    }

Did create a Delegate:

    public delegate void playDelegate();
    playDelegate pl;

But don’t know where to assign and call it…
A bit of more help would be wonderful.
Thank you !

@Hellium

This works like a charm!
I understand that the lock is manual, but I don’t understand how the unity thread can handle the FileSystemWatcher event since it does use a Thread pool as you mentioned.

(I’ll dig into this).

Thank you so much for your help. I’ve learned something important about thread pool and locking system!