【问题标题】:SoundPlayer.Stop does not stop sound playbackSoundPlayer.Stop 不会停止声音播放
【发布时间】:2015-04-27 20:20:04
【问题描述】:

我有一个用 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();
        }
    }
}

当我的程序调用PlaySound 方法并且当前正在播放声音时,会调用Stop 方法,但正在播放的声音实际上并没有停止。我在对Stop 的调用上放置了跟踪点,并在其后添加了一行,这样我就可以在使用耳机收听时查看调用的时间和返回时间。很明显,声音一直播放到最后。

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

【问题讨论】:

  • Sounds[] 定义在哪里?
  • 就在我发布的代码中
  • 哎呀,对不起,一定错过了

标签: c# audio


【解决方案1】:

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

 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 类)。此外,由于它是一个肮脏的解决方案,如果您不使用 PlayAsync 调用,您将不会获得 Finished 事件或属性。

【讨论】:

  • 声音必须在播放它的线程上停止?你能给我指出任何这样说的文件吗?
  • 我没有任何文档,但我已经使用了很多 SoundPlayer,从其他人遇到的问题(以及问题的解决方案)来看,我得出的结论是到。 SoundPlayer 旨在用作 UI 中的组件,因此它假定所有调用都发生在 UI 线程上。如果您想弄清楚原因,可以通读代码:referencesource.microsoft.com/#System/sys/system/Media/…
猜你喜欢
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多