SoundPlayer.Stop does not stop sound playback

前端 未结 1 825
小鲜肉
小鲜肉 2021-01-24 21:04

I have an application written in C# which plays little .wav files. It uses the SoundPlayer class in the System.Media namepace to play the sounds, using a thread th

相关标签:
1条回答
  • 2021-01-24 21:26

    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. 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).

    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. 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).

    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). Also since its a dirty solution, you won't get the Finished event or property if you don't use the PlayAsync call.

    0 讨论(0)
提交回复
热议问题