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... :)
Is OnQueueFileChange
called on the main thread? How is it called?
OnQueueFileChanged is the callback of a FileSystemWatcher event (All methods in the same class, one instance):
void Awake()
{
audioSource = sourceA.GetComponent<AudioSource>();
watcher = new FileSystemWatcher(queueFolder)
{
NotifyFilter = NotifyFilters.LastAccess
};
watcher.Changed += OnQueueFileChange;
watcher.Filter = candidateQueueFile;
watcher.IncludeSubdirectories = false;
watcher.EnableRaisingEvents = true;
}
Thank for helping!
Answer by Hellium · Jan 04 at 03:19 PM
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();
}
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!
Your answer
Follow this Question
Related Questions
Sound on trigger script only plays once. 0 Answers
audio trigger not working 0 Answers
Play sound when looking at an object for a certain amount of time 0 Answers
Audio Trigger 2D in C#? 1 Answer
Stop AudioSource on Trigger!!! 1 Answer