首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SoundPlayer.Stop不停止声音播放

SoundPlayer.Stop不停止声音播放
EN

Stack Overflow用户
提问于 2015-04-27 20:20:04
回答 1查看 2.3K关注 0票数 1

我有一个用C#编写的应用程序,它可以播放小的.wav文件。它使用SoundPlayer名称空间中的System.Media类来播放声音,使用调用SoundPlayer.PlaySync方法来播放.wav文件的线程。所有这些都被包装在一个类似于这样的类中:

代码语言:javascript
复制
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的调用中放置了跟踪点,并在它之后添加了一行,这样我就可以看到呼叫何时发出,何时返回,同时使用耳机进行监听。很明显,声音会一直播放到最后。

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

EN

回答 1

Stack Overflow用户

发布于 2015-04-27 21:43:53

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

代码语言:javascript
复制
 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文件替换为您自己的一个)。

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

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29905192

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档