[英]SoundPlayer.Stop does not stop sound playback
I have an application written in C# which plays little .wav files. 我有一个用C#编写的应用程序,它可以播放很少的.wav文件。 It uses the
SoundPlayer
class in the System.Media namepace to play the sounds, using a thread that calls the SoundPlayer.PlaySync
method to play the .wav file. 它使用System.Media名称空间中的
SoundPlayer
类来播放声音,使用调用SoundPlayer.PlaySync
方法的线程来播放.wav文件。 It's all wrapped up in a class that looks like this: 这一切都包含在一个看起来像这样的类中:
public class SoundController {
private object soundLocker = new object();
protected Thread SoundThread { get; set; }
protected string NextSound { get; set; }
protected AutoResetEvent PlayASoundPlag { get; set; }
protected Dictionary<string, SoundPlayer> Sounds { get; set; }
protected bool Stopping { get; set; }
public string SoundPlaying { get; private set; }
public SoundController() {
PendingCount = 0;
PlayASoundFlag = new AutoResetEvent( false );
Sounds = new Dictionary<string, SoundPlayer>();
soundLocker = new object();
Stopping = false;
SoundThread = new Thread( new ThreadStart( SoundPlayer ) ) { Name = "SoundThread", IsBackground = true };
SoundThread.Start();
}
private void SoundPlayer() {
do {
PlayASoundFlag.WaitOne();
bool soundWasPlayed = false;
while ( !Stopping && NextSound != null ) {
lock ( soundLocker ) {
SoundPlaying = NextSound;
NextSound = null;
}
Sounds[ SoundPlaying ].PlaySync();
lock ( soundLocker ) {
SoundPlaying = null;
soundWasPlayed = true;
}
}
} while ( !Stopping );
}
public bool HasSound( string key ) {
return Sounds.ContainsKey( key );
}
public void PlayAlarmSound( string key, bool stopCurrentSound ) {
if ( !Sounds.ContainsKey( key ) )
throw new ArgumentException( "Sound unknown", "key" );
lock ( soundLocker ) {
NextSound = key;
if ( SoundPlaying != null && stopCurrentSound )
Sounds[ SoundPlaying ].Stop();
PlayASoundFlag.Set();
}
}
}
When my program calls the PlaySound
method, and a sound is currently playing, the Stop
method is called, but the sound that's playing doesn't actually stop. 当我的程序调用
PlaySound
方法并且当前正在播放声音时,将调用Stop
方法,但正在播放的声音实际上并未停止。 I've placed trace points on the call to Stop
and a line I added after it just so I could see when the call was made and when it returned, while listening with headphones. 我已经在
Stop
的呼叫上放置了跟踪点,并且我在它之后添加了一条线路,这样我就可以看到呼叫何时以及何时返回,同时使用耳机收听。 It's obvious that the sound plays all the way through to the end. 很明显,声音一直播放到最后。
How do I get the sounds to stop playing reliably? 如何让声音可靠地停止播放?
Unfortunately this is a messy process, but this works: 不幸的是,这是一个混乱的过程,但这有效:
class Program
{
static void Main(string[] args)
{
SoundPlayerEx player = new SoundPlayerEx(@"c:\temp\sorry_dave.wav");
player.SoundFinished += player_SoundFinished;
Console.WriteLine("Press any key to play the sound");
Console.ReadKey(true);
player.PlayAsync();
Console.WriteLine("Press a key to stop the sound.");
Console.ReadKey(true);
player.Stop();
Console.WriteLine("Press any key to continue");
}
static void player_SoundFinished(object sender, EventArgs e)
{
Console.WriteLine("The sound finished playing");
}
}
public static class SoundInfo
{
[DllImport("winmm.dll")]
private static extern uint mciSendString(
string command,
StringBuilder returnValue,
int returnLength,
IntPtr winHandle);
public static int GetSoundLength(string fileName)
{
StringBuilder lengthBuf = new StringBuilder(32);
mciSendString(string.Format("open \"{0}\" type waveaudio alias wave", fileName), null, 0, IntPtr.Zero);
mciSendString("status wave length", lengthBuf, lengthBuf.Capacity, IntPtr.Zero);
mciSendString("close wave", null, 0, IntPtr.Zero);
int length = 0;
int.TryParse(lengthBuf.ToString(), out length);
return length;
}
}
public class SoundPlayerEx : SoundPlayer
{
public bool Finished { get; private set; }
private Task _playTask;
private CancellationTokenSource _tokenSource = new CancellationTokenSource();
private CancellationToken _ct;
private string _fileName;
private bool _playingAsync = false;
public event EventHandler SoundFinished;
public SoundPlayerEx(string soundLocation)
: base(soundLocation)
{
_fileName = soundLocation;
_ct = _tokenSource.Token;
}
public void PlayAsync()
{
Finished = false;
_playingAsync = true;
Task.Run(() =>
{
try
{
double lenMs = SoundInfo.GetSoundLength(_fileName);
DateTime stopAt = DateTime.Now.AddMilliseconds(lenMs);
this.Play();
while (DateTime.Now < stopAt)
{
_ct.ThrowIfCancellationRequested();
//The delay helps reduce processor usage while "spinning"
Task.Delay(10).Wait();
}
}
catch (OperationCanceledException)
{
base.Stop();
}
finally
{
OnSoundFinished();
}
}, _ct);
}
public new void Stop()
{
if (_playingAsync)
_tokenSource.Cancel();
else
base.Stop(); //To stop the SoundPlayer Wave file
}
protected virtual void OnSoundFinished()
{
Finished = true;
_playingAsync = false;
EventHandler handler = SoundFinished;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
So why doesn't it work "normally"? 那么为什么它不能“正常”工作呢? Its a well known problem.
这是一个众所周知的问题。 The
SoundPlayer
is a "fire and forget" piece of code, and if you don't cancel it on the same thread that you started it on, it will not do anything. SoundPlayer
是一个“即SoundPlayer
即忘”的代码段,如果你没有在你启动它的同一个线程上取消它,它就不会做任何事情。 A lot of people complain about it and as I'm sure you've seen there are very few solutions out side of using raw wave_out
calls or moving to DirectX (or with WPF, using the MediaPlayer
control). 很多人抱怨它,因为我相信你已经看到使用原始
wave_out
调用或移动到DirectX(或使用WPF,使用MediaPlayer
控件)的解决方案很少。
This SoundPlayerEx
class has a couple properties that let you know when the sound is finished or to cancel playing a sound that you started asynchronously. 此
SoundPlayerEx
类有几个属性可以让您知道声音何时结束或取消播放您异步启动的声音。 There is no need to create a new thread to work on, making it a lot easier to use. 无需创建新线程即可使用,使其更易于使用。
Feel free to expand on the code, it was a quick and dirty solution to your problem. 随意扩展代码,这是一个快速和肮脏的解决方案来解决您的问题。 The two classes you need are the SoundInfo class and the SoundPlayerEx class, the rest of the code above is a demo (replace the wav file with one of your own).
您需要的两个类是SoundInfo类和SoundPlayerEx类,上面的其余代码是一个演示(用您自己的一个替换wav文件)。
Note this is not a universal solution as it relies on the winmm.dll, so this will not port over to Mono (not sure if Mono has a SoundPlayer
class or not). 请注意,这不是一个通用的解决方案,因为它依赖于winmm.dll,所以这不会移植到Mono(不确定Mono是否有
SoundPlayer
类)。 Also since its a dirty solution, you won't get the Finished
event or property if you don't use the PlayAsync
call. 此外,由于它是一个脏的解决方案,如果您不使用
PlayAsync
调用,您将无法获得Finished
事件或属性。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.