英文:
AVAudioPlayer : waiting for a sound to finish playing
问题
我正在尝试播放声音文件并等待其完成后再退出调用*AVAudioPlayer.Play()*的函数。
我理解有一个FinishedPlaying事件处理程序,可以初始化并相应地调用。
但该方法必须等待,而不会阻塞主线程...
这应该如何实现?
编辑 1:
我尝试使用ManualResetEvent,但 AVAudioPlayer 实例上的 FinishedPlaying 委托在播放结束时从未被调用...
NSError err;
ManualResetEvent playSoundFinished = new ManualResetEvent(false);
var currentPlayer = new AVAudioPlayer(soundResource, "mp3", out err);
currentPlayer.FinishedPlaying += delegate { playSoundFinished.Set();};
currentPlayer.Play();
// 此调用应在事件被设置之前阻塞
DispatchQueue.DefaultGlobalQueue.DispatchSync(() => playSoundFinished.WaitOne(-1));
英文:
I'm trying to play a sound file and wait for it to finish before exiting the function that calls AVAudioPlayer.Play().
I understand that there is a FinishedPlaying EventHandler that could be initialized and will be called accordingly.
But the method must wait without bloking the main thread...
How could this be achived?
EDIT 1:
I've tried using a ManualResetEvent but the delegate FinishedPlaying on the AVAudioPlayer instance is never called at the end of the playback...
NSError err;
ManualResetEvent playSoundFinished = new ManualResetEvent(false);
var currentPlayer = new AVAudioPlayer(soundResource, "mp3", out err);
currentPlayer.FinishedPlaying += delegate { playSoundFinished.Set();};
currentPlayer.Play();
// This call should block until the event is set
DispatchQueue.DefaultGlobalQueue.DispatchSync(() => playSoundFinished.WaitOne(-1));
答案1
得分: 1
以下是翻译好的部分:
"The delegate is never called because the call to DispatchQueue.DefaultGlobalQueue.DispatchSync blocks the main thread."
- 代理从未被调用,因为对 DispatchQueue.DefaultGlobalQueue.DispatchSync 的调用阻塞了主线程。
"The solution is to use a CancellationToken (or ManualResetEvent for that matter) that is canceled in the delegate and waited outside the method that plays the sound."
- 解决方案是使用 CancellationToken(或者也可以使用 ManualResetEvent),在代理中取消它,并在播放声音的方法之外等待它。
"Pseudo Code:"
- 伪代码:
"Playing the sound"
- 播放声音
"NSError err;"
- NSError err;
"this.currentPlayer = new AVAudioPlayer(soundResource, "mp3", out err);"
- this.currentPlayer = new AVAudioPlayer(soundResource, "mp3", out err);
"this.waitFinishedPlaying = new CancellationTokenSource();"
- this.waitFinishedPlaying = new CancellationTokenSource();
"this.currentPlayer.FinishedPlaying = delegate {waitFinishedPlaying.Cancel();};"
- this.currentPlayer.FinishedPlaying = delegate {waitFinishedPlaying.Cancel();};
"this.currentPlayer.NumberOfLoops = looping ? -1 : 0;"
- this.currentPlayer.NumberOfLoops = looping ? -1 : 0;
"this.currentPlayer.PrepareToPlay();"
- this.currentPlayer.PrepareToPlay();
"if (delay != 0)"
- if (delay != 0)
"this.currentPlayer.PlayAtTime(this.currentPlayer.DeviceCurrentTime + delay);"
- this.currentPlayer.PlayAtTime(this.currentPlayer.DeviceCurrentTime + delay);
"else"
- 否则
"this.currentPlayer.Play();"
- this.currentPlayer.Play();
"Outside the method that contains the code above:"
- 在包含上述代码的方法之外:
"try"
- 尝试
"The TimeSpan duration must be greater than the duration of the longest sound to be played."
- 时间跨度必须大于要播放的最长声音的持续时间。
"await Task.Delay(TimeSpan.FromSeconds(20), this.audioService.GetFinishedPlayingToken()).ConfigureAwait(true);"
- 等待 Task.Delay(TimeSpan.FromSeconds(20), this.audioService.GetFinishedPlayingToken()).ConfigureAwait(true);
"catch (OperationCanceledException)"
- 捕获 (catch) OperationCanceledException 异常
"GetFinishedToken() returns the waitFinishedPlaying instance initiated in the first block of code above."
- GetFinishedToken() 返回在上面的第一个代码块中初始化的 waitFinishedPlaying 实例。
"The only limitation to this solution is that the caller method must be async to await the Task."
- 此解决方案的唯一限制是调用方法必须是异步的,以等待任务。
英文:
The delegate is never called because the call to DispatchQueue.DefaultGlobalQueue.DispatchSync blocks the main thread.
The solution is to use a CancellationToken (or ManualResetEvent for that matter
) that is canceled in the delegate and waited outside the method that plays the sound.
Pseudo Code:
Playing the sound
NSError err;
this.currentPlayer = new AVAudioPlayer(soundResource, "mp3", out err);
this.waitFinishedPlaying = new CancellationTokenSource();
this.currentPlayer.FinishedPlaying = delegate {waitFinishedPlaying.Cancel();};
this.currentPlayer.NumberOfLoops = looping ? -1 : 0;
this.currentPlayer.PrepareToPlay();
if (delay != 0)
{
this.currentPlayer.PlayAtTime(this.currentPlayer.DeviceCurrentTime + delay);
}
else
{
this.currentPlayer.Play();
}
Outside the method that contains the code above:
try
{
// The TimeSpan duration must be greater than the duration of the longuest sound to be played.
await Task.Delay(TimeSpan.FromSeconds(20), this.audioService.GetFinishedPlayingToken()).ConfigureAwait(true);
}
catch (OperationCanceledException)
{
}
GetFinishedToken() returns the waitFinishedPlaying instance initiated in the first block of code above.
The only limitation to this solution is that the caller method must be async to await the Task.
答案2
得分: 0
ManualResetEvent 在内存中维护一个布尔变量。当布尔变量为 false
时,它会阻塞所有线程,当布尔变量为 true
时,它会解除阻塞所有线程。您可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态:如果初始状态被标记为 true
,则传递 true
,否则传递 false
。
您可以通过以下方式更改来解决这个问题:
ManualResetEvent playSoundFinished = new ManualResetEvent(true);
英文:
ManualResetEvent maintains a boolean variable in memory. When the boolean variable is false
then it blocks all threads and when the boolean variable is true
it unblocks all threads. You can control the initial state of a ManualResetEvent by passing a Boolean value to the constructor: true
if the initial state is signaled, and false
otherwise.
You can solve the problem by changing it as below:
ManualResetEvent playSoundFinished = new ManualResetEvent(true);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论