SoundPlayer.Stop不会停止声音播放

时间:2015-04-27 20:20:04

标签: c# audio

我有一个用C#编写的应用程序,它可以播放很少的.wav文件。它使用System.Media名称空间中的SoundPlayer类来播放声音,使用调用SoundPlayer.PlaySync方法的线程来播放.wav文件。它全部包含在一个看起来像这样的类中:

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

当我的程序调用{​​{1}}方法并且当前正在播放声音时,会调用PlaySound方法,但播放的声音实际上并未停止。我已经在Stop的电话号码上放置了跟踪点,之后我添加了一条线路,这样我就可以看到通话时间以及何时返回,同时使用耳机收听。很明显,声音一直播放到最后。

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

1 个答案:

答案 0 :(得分:3)

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

 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类有几个属性可让您知道声音何时结束或取消播放您异步启动的声音。无需创建新线程即可使用,使其更易于使用。

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

注意这不是一个通用的解决方案,因为它依赖于winmm.dll,所以这不会移植到Mono(不确定Mono是否有SoundPlayer类) 。此外,由于它是一个肮脏的解决方案,如果您不使用Finished调用,则不会获得PlayAsync事件或属性。