2015-04-27 298 views
1

我有一个使用C#编写的应用程序,可以播放小的.wav文件。它使用System.Media命名空间中的SoundPlayer类播放声音,使用调用SoundPlayer.PlaySync方法播放.wav文件的线程。在一个类中,看起来像这样这一切都包裹起来:SoundPlayer.Stop不会停止声音播放

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(); 
     } 
    } 
} 

当我的程序调用PlaySound方法和声音正在播放时,Stop方法被调用,但声音的播放实际上并没有停止。我在呼叫Stop时添加了一些跟踪点,并且在我之后添加了一条线,以便我可以看到何时进行呼叫以及何时返回,同时用耳机收听。很明显,声音一直播放到最后。

如何让声音停止可靠播放?

+0

声音[]定义在哪里? – Pseudonym

+0

这是在我发布的代码 –

+0

oh derp,对不起,一定是错过了 – Pseudonym

回答

2

不幸的是,这是一个混乱的过程,但这个工程:

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); 
     } 
    } 

那么,为什么它的工作“正常”?它是一个众所周知的问题。 SoundPlayer是一段“忘却遗忘”的代码,如果你没有在开始它的同一个线程上取消它,它将不会执行任何操作。很多人都抱怨它,因为我相信你已经看到使用原始wave_out调用或移动到DirectX(或使用WPF,使用MediaPlayer控件)的解决方案非常少。

SoundPlayerEx这个SoundPlayerEx类有一些属性,让你知道什么时候声音结束或取消播放异步启动的声音。没有必要创建一个新的线程来处理,使它更容易使用。

随意扩展代码,这是一个快速和肮脏的解决方案,您的问题。你需要的两个类是SoundInfo类和SoundPlayerEx类,其余的代码是一个演示(用你自己的一个替换wav文件)。

注意这不是一个通用的解决方案,因为它依赖于WINMM.DLL,所以这次到单声道不会端口(不知道,如果单有SoundPlayer类或不)。此外,由于它是一个肮脏的解决方案,如果您不使用PlayAsync呼叫,您将无法获得Finished活动或财产。

+0

声音必须停止在播放它的线程?你能指出我的任何文件吗? –

+0

我没有任何文档,但我已经使用SoundPlayer很多,并从其他人遇到的问题(以及问题的解决方案)得出的结论。 SoundPlayer是为了在UI中用作组件,所以它假设所有的调用都发生在UI线程上。如果你想弄清楚为什么你可以通读代码:http://referencesource.microsoft.com/#System/sys/system/Media/SoundPlayer.cs –