Do something after a music ends with SoundPlayer - c#

I need to do something after my Async Loaded music file finishes. Let's say I want the program to exit or something. Now how do I make it do is after the music finishes?
private void button1_Click(object sender, EventArgs e)
{
player.SoundLocation = #"Music\" + FileList[0];
player.LoadAsync();
}
private void Player_LoadCompleted(object sender, AsyncCompletedEventArgs e)
{
if (player.IsLoadCompleted)
{
player.PlaySync();
}
}

Since the PlaySync method is synchronous then it will not return until the file has been played to the end. So, you can simply do it like this:
if (player.IsLoadCompleted)
{
player.PlaySync();
DoSomethingAfterMusicIsDone();
}
UPDATE:
LoadAsync seems to run synchronously if the SoundLocation points to a file on the file system. This means that you should invoke LoadAsync on another thread if you don't want to freeze the UI thread. Here is an example:
Task.Run(() => player.LoadAsync());

Use MediaPlayer instead:
mediaPlayer = new MediaPlayer();
mediaPlayer.MediaEnded += delegate { MessageBox.Show("Media Ended"); };
mediaPlayer.Open(new Uri(#"C:\myfile.mp3"));
mediaPlayer.Play();
Sounds good in theory. However, in truth, I couldn't get the MediaEnded Event to fire. Thus, I had to poll for the end of MediaEvent as follows:
while(true)
{
System.Threading.Thread.Sleep(1000);
string pos = "unknown";
string dur = "unknown";
try
{
pos = mediaplayer1.Position.Ticks.ToString();
dur = mediaplayer1.NaturalDurataion.TimeSpan.Ticks.ToString()
if (pos == dur)
{
// MediaEnded!!!!!
pos = "0";
dur = "0";
}
catch {}
}
}
On the positive side, you can update your Audio player Slider by polling...
On the downside, it makes the pause button a little bit unresponsive if you notice 1000 milliseconds lag...
If you use this workaround, I would recommend placing the Mediaplayer in a background thread so that the polling loop doesn't lock up the UI thread.

Related

How to have program wait X seconds and allow UI to execute?

I am making a small game with an auto play feature, but the program runs too fast so the user can't see the outcome at each stage. I am using VS 2017, so I can't use async (at least from what I have read). How can I have the program wait and allow the UI to update?
I am working in a do while loop. The main chunk of the game executes, updates the UI, and then waits for the player to click a button (assuming auto play is not running), with auto play running the do while loop repeats, but after the UI updates it would wait X seconds.
Use a Timer component instead of a loop, and put the loop body in the timer's Elapsed event.
And VS2017 definitely supports async, but it wouldn't help in this case... things would still move too fast for the user.
You can use async/await to slow down the execution of event handler without having to split the logic. This is pretty simple:
async void Button_Click(object sender, RoutedEventArgs e) // wpf event handler
{
...
await Task.Delay(1000); // pause 1 second
...
while (someCondition)
{
...
await Task.Delay(1000);
...
}
}
You can read about async/await at msdn.
If you are using WPF, then you have to look into animations. They are much simpler to use to ensure smooth changes than manually changing something (position, sizes).
Usage: DelayFactory.DelayAction(500, new Action(() => { this.RunAction(); }));`
//Note Forms.Timer and Timer() have similar implementations.
//Assumes you will have a DelayFactory Static Class
public static void DelayAction(int millisecond, Action action)
{
var timer = new DispatcherTimer();
timer.Tick += delegate
{
action.Invoke();
timer.Stop();
};
timer.Interval = TimeSpan.FromMilliseconds(millisecond);
timer.Start();
}
Wait function using timers, no UI locks.
public void wait(int milliseconds)
{
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
if (milliseconds == 0 || milliseconds < 0) return;
//Console.WriteLine("start wait timer");
timer1.Interval = milliseconds;
timer1.Enabled = true;
timer1.Start();
timer1.Tick += (s, e) =>
{
timer1.Enabled = false;
timer1.Stop();
//Console.WriteLine("stop wait timer");
};
while (timer1.Enabled)
{
Application.DoEvents();
}
}
Usage:
wait(1000); //wait one second
It looks like you have a couple of options
1.You can try Sleep -(but it may hang the UI)
int Seconds = 1;
Threading.Thread.Sleep(Seconds * 1000);
2.You can try this code:
int Seconds = 1;
Private void WaitNSeconds(int seconds)
{
if (seconds < 1) return;
DateTime _desired = DateTime.Now.AddSeconds(seconds);
while (DateTime.Now < _desired) {
System.Windows.Forms.Application.DoEvents();
}
}
3.Try to use Async and see what happens
async Task MakeDelay() {
await Task.Delay(5000);
}
private async void btnTaskDelay_Click(object sender, EventArgs e) {
await MakeDelay();
}

MediaPlayer is not looping/repeating the audio provided

I am using System.Windows.Media.MediaPlayer for playing .wma audio file of 5 seconds length .
I was not able to find any direct option for repeatedly playing this media file so I have implemented looping logic as follows. But it seems to be not working.
For the following code, MediaEnded event is not getting fired even after playback is ended. What am I doing wrong here?
public void PlayAudio(string audioFilePath, TimeSpan duration)
{
var thread = new Thread(() => { PlayAudioThreadProc(audioFilePath, duration); });
thread.Start();
}
private void PlayAudioThreadProc(string audioFilePath, TimeSpan duration)
{
MediaPlayer mediaPlayer = CreateMediaPlayer(audioFilePath);
mediaPlayer.Play();
_stopSignal.WaitOne(duration);
Stop(mediaPlayer);
}
private static MediaPlayer CreateMediaPlayer(string audioFilePath)
{
var mediaPlayer = new MediaPlayer();
mediaPlayer.MediaEnded += MediaPlayer_MediaEnded; //This event is not getting fired.
mediaPlayer.Open(new Uri(audioFilePath));
return mediaPlayer;
}
private static void MediaPlayer_MediaEnded(object sender, EventArgs e)
{
//This part of code is supposed to start the media again after it is ended playing.
var player = (MediaPlayer)sender;
player.Position = TimeSpan.Zero;
player.Play();
}
private static void Stop(MediaPlayer mediaPlayer)
{
mediaPlayer.Stop();
mediaPlayer.MediaEnded -= MediaPlayer_MediaEnded;
mediaPlayer.Close();
}
Looping logic in above code is not working.
If above approach is not possible, please recommend me another audio player which supports Volume adjustments and Repeat media option. (I tried System.Media.SoundPlayer but it does not support Volume adjustments and .wma files are also not supported in it.)
First, you seem to be using the MediaPlayer class incorrectly. It inherits DispatcherObject and also is not blocking, so it should really be used on the UI thread.
Now on the main subject.
I was not able to find any direct option for repeatedly playing this media file
Well, actually it supports everything you need except the total play duration time. But you are right - it's not so direct (as most of the WPF stuff) - the repeating is achieved by using MediaTimeline through RepeatBehavior property. You can find sample usage in How to: Play Media using a VideoDrawing. So the basic playing code with repeating is like this:
var timeLine = new MediaTimeline(new Uri(audioFilePath));
timeLine.RepeatBehavior = RepeatBehavior.Forever;
var mediaPlayer = new MediaPlayer();
mediaPlayer.Clock = timeLine.CreateClock();
mediaPlayer.Clock.Controller.Begin();
You create MediaTimeline object, set properties (use RepeatBehavior.Forever to get indefinite repeating, but you can also use the constructor and specify concrete count), then create MediaClock from it and assign it to the MediaPlayer.Clock property. Make sure to read the documentation, because in this mode you should not use Position property and Play, Pause and Stop methods of the MediaPlayer class, but the Clock.Controller methods.
The MediaTimeline also has a property Duration, but it allows you (along with the BeginTime property) to select a portion of the audio file to be played, hence cannot be used to set up the total play duration. So the play time duration problem should be solved in a separate way.
The easiest way I see to support what you need, along with stop function, is to use async method:
public async Task PlayAudioAsync(string audioFilePath, TimeSpan duration, CancellationToken cancellationToken)
{
var timeLine = new MediaTimeline(new Uri(audioFilePath));
timeLine.RepeatBehavior = RepeatBehavior.Forever;
var mediaPlayer = new MediaPlayer();
mediaPlayer.Clock = timeLine.CreateClock();
mediaPlayer.Clock.Controller.Begin();
try
{
await Task.Delay(duration, cancellationToken);
}
finally
{
mediaPlayer.Clock.Controller.Stop();
}
}
Task.Delay will give you the desired play duration, and CancellationToken - the stop functionality.
Jus make sure to call it from UI thread. Here is a sample usage:
XAML:
<Canvas>
<Button x:Name="playButton" Content="Play" Width="75" RenderTransformOrigin="1.2,5.24" Canvas.Left="98" Canvas.Top="135" Click="playButton_Click"/>
<Button x:Name="stopButton" Content="Stop" IsEnabled="False" Width="75" Canvas.Left="208" Canvas.Top="135" Click="stopButton_Click"/>
</Canvas>
Code behind:
private CancellationTokenSource ctsPlay;
private async void playButton_Click(object sender, RoutedEventArgs e)
{
string audioFile = ...;
TimeSpan duration = ...;
ctsPlay = new CancellationTokenSource();
playButton.IsEnabled = false;
stopButton.IsEnabled = true;
try
{
await PlayAudioAsync(audioFile, duration, ctsPlay.Token);
}
catch
{
}
ctsPlay.Dispose();
ctsPlay = null;
stopButton.IsEnabled = false;
playButton.IsEnabled = true;
}
private void stopButton_Click(object sender, RoutedEventArgs e)
{
ctsPlay.Cancel();
}
MediaPlayers "Play" is not thread locking. the thread ends execution as soon as the sound starts playing. I've setup a local class testing this out and I get the event to fire (On a background thread) like this (I've changed it to be OOP, not a statically used class, you have to call Stop from somewhere else):
public class MediaStuff
{
private bool _closing = false;
public void PlayAudio(string audioFilePath)
{
var thread = new Thread(() => { PlayAudioThreadProc(audioFilePath); });
thread.Start();
}
private void PlayAudioThreadProc(string audioFilePath)
{
MediaPlayer mediaPlayer = CreateMediaPlayer(audioFilePath);
mediaPlayer.Play();
while (!_closing)
{
System.Threading.Thread.Sleep(10);
Dispatcher.Run();
}
mediaPlayer.Stop();
mediaPlayer.MediaEnded -= MediaPlayer_MediaEnded;
mediaPlayer.Close();
}
private MediaPlayer CreateMediaPlayer(string audioFilePath)
{
var mediaPlayer = new MediaPlayer();
mediaPlayer.MediaEnded += MediaPlayer_MediaEnded; //This event is not getting fired.
mediaPlayer.Open(new Uri(Path.GetFullPath(audioFilePath)));
return mediaPlayer;
}
private void MediaPlayer_MediaEnded(object sender, EventArgs e)
{
//This part of code is supposed to start the media again after it is ended playing.
var player = (MediaPlayer)sender;
player.Position = TimeSpan.Zero;
player.Play();
}
public void Stop()
{
_closing = true;
}
}
I too had your problem when testing your code. After changing and testing my code, the event now fires, and the sound loops.
Alternatively, you can use mciSendString(). Tried to make an example and it succeed, try this;
winmm.dll import,
[DllImport("winmm.dll")]
private static extern long mciSendString( string command, string returnValue,
int returnLength, IntPtr winHandle);
Need to catch operations done by mciSendString(), so we need WndProc;
private const int MM_MCINOTIFY = 0x03b9; // notify mci completed operation
private const int MCI_NOTIFY_SUCCESS = 0x01; // mci successfully executed command
protected override void WndProc(ref Message m)
{
if (m.Msg == MM_MCINOTIFY)
{
switch (m.WParam.ToInt32())
{
case MCI_NOTIFY_SUCCESS: // if successfull
if (IsLoop) // check if we gave parameter true
PlayMediaFile(IsLoop); // so it should run forever, call again function
break;
default:
break;
}
}
base.WndProc(ref m);
}
Method that executes, media file by using mciSendString() that we imported at the beginning
private bool IsLoop = false; // need this to check inside WndProc
private void PlayMediaFile(bool RepeatForever)
{
IsLoop = RepeatForever;
mciSendString("close voice1", null, 0, this.Handle); // first close, after first run, the previous opened file should be terminated
mciSendString("stop voice1", null, 0, this.Handle); // close file
string playCommand = "open " + "yourFilePath" + " type waveaudio alias voice1"; // open command string
mciSendString(playCommand, null, 0, this.Handle); // open file
mciSendString("play voice1 notify", null, 0, this.Handle); // play file
}
Then call the method by giving parameter anywhere.
Hope helps,

Constant running process on a sperate thread blocking a UI thread

i am trying to use a third party telnet library "active expert" for a basic telnet session.
in my UI code behind i have something like
private async void Button_Click(object sender, RoutedEventArgs e)
{
var ts = new TelnetService();
await ts.DoConnect(node);
}
and my TelnetService looks like this
public class TelnetService
{
private Tcp objSocket = new Tcp();
private NwConstants objConstants = new NwConstants();
public string Responses { get; set; }
private Timer timer1 = new Timer();
public TelnetService()
{
timer1.Elapsed += timer1_Elapsed;
timer1.Interval = 100;
timer1.Start();
}
void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (objSocket.ConnectionState == objConstants.nwSOCKET_CONNSTATE_CONNECTED)
{
if (objSocket.HasData())
{
Responses += objSocket.ReceiveString() + "\r\n";
}
}
}
public Task DoConnect(Node node)
{
return Task.Factory.StartNew(() =>
{
objSocket.Protocol = objConstants.nwSOCKET_PROTOCOL_TELNET;
objSocket.Connect(node.IP, 23);
while (true)
{
if ((Responses == null) || (!Responses.Contains(node.WaitString))) continue;
//do something
Responses = "";
break;
}
});
}
}
there are two important pieces of functionalities.
First in the timer1_Elapsed function which is process that will keeps on ruining and checks if there is data on socket, and if there is, it will append it to a string "Response". and i am using "timer" for it.
Second in the DoConnect function which will check the"Response" string for a certain input. for this i am using async await and Task.
in a nutshell first one accumulating the Response and Second one checking the Response.
Problem is that it looks like the timer code in general and
objSocket.ReceiveString()
line specifically is causing the UI thread to halt for several seconds. which means after clicking the button i cannot move my main form on the screen however the code is running in a separate thread.
i have tried using pure Thread for this but it didn't helped either.
update
instead of timer i am using a method AccumulateResponse
public static void AccumulateResponse()
{
while (true)
{
if (objSocket.ConnectionState == objConstants.nwSOCKET_CONNSTATE_CONNECTED)
{
if (objSocket.HasData())
{
Responses += objSocket.ReceiveString() + "\r\n";
}
}
}
}
and calling it like
var t = new Task(TelnetService.AccumulateResponse);
t.Start();
await TelnetService.DoConnect(node);
still no luck
The DoConnect isn't your problem. It is your Timer Elapsed Event handler.
The timer elapsed event is NOT asynchronous. Only the DoConnect is.
If there is no asynchronous version of ReceiveString() from your third party lib, then use Task.Run there as well inside of an async timer1_elapsed method.

How to play sounds asynchronuously, but themselves in a queue?

I just want to play 4 sounds after each other (sounds1->sound2->sound3), but without stopping the flow in my code during each play or without waiting for each sound to finish.
I have searched for this about everywhere, but every direction I read, gets stuck in some other problem.
My best bet so far was: using my already used SoundPlayer from System.Media and make my own queue function, but Soundplayer doesn't have a "finished playing" event so I have no idea of knowing when to start the next sound. (Really, Microsoft?)
Other solution and problems:
DirectSound seems complicated to get working in .NET (c#).
Win Playsound doesn't really help because it can't queue either.
You can try to use PlaySync on a thread outside UI, eg: Background thread, as some people have commented.
Here is a sample (untested) using a thread-safe* BlockingCollection for the queue
* which you can use in and outside the thread
You may want to make your own class or methods that rises an event every time the sounds ends. Or you can just loop the queue in the thread since PlaySync will just wait by itself.
using System.Threading;
using System.Collections.Concurrent;
namespace PlaySound
{
public partial class Form1 : Form
{
private Thread soundPlayThread;
private BlockingCollection<string> speakQueue = new BlockingCollection<string>();
private CancellationTokenSource cancelSoundPlay;
private int soundPlayCount = 0;
public Form1()
{
InitializeComponent();
cancelSoundPlay = new CancellationTokenSource();
}
private void btnStartSoundPlay_Click(object sender, EventArgs e)
{
StartSoundPlay();
}
private void btnStopSoundPlay_Click(object sender, EventArgs e)
{
cancelSoundPlay.Cancel();
Console.WriteLine("Sound play cancelled.");
}
private void btnAddToQueue_Click(object sender, EventArgs e)
{
speakQueue.Add("MyFile.wav");
}
private void queueAndPlay(string loc)
{
if (!File.Exists(loc=loc+".wav"))
loc=configPath+"soundnotfound.wav";
speakQueue.Add(loc);
StartSoundPlay();
}
private void StartSoundPlay()
{
//Sound Player Loop Thread
if (this.soundPlayThread == null || !this.soundPlayThread.IsAlive)
{
this.soundPlayThread = new Thread(SoundPlayerLoop);
this.soundPlayThread.Name = "SoundPlayerLoop";
this.soundPlayThread.IsBackground = true;
this.soundPlayThread.Start();
Console.WriteLine("Sound play started");
}
}
//Method that the outside thread will use outside the thread of this class
private void SoundPlayerLoop()
{
var sound = new SoundPlayer();
foreach (String soundToPlay in this.speakQueue.GetConsumingEnumerable(cancelSoundPlay.Token))
{
//http://msdn.microsoft.com/en-us/library/system.media.soundplayer.playsync.aspx
speaker.SoundLocation=soundToPlay;
//Here the outside thread waits for the following play to end before continuing.
sound.PlaySync();
soundPlayCount++;
Console.WriteLine("Sound play end. Count: " + soundPlayCount);
}
}
}
}

c# Starting a stopwatch after wav file is played

I am working on a stopwatch, that i want to use in some sort of competition. I would like to start my stopwatch by clicking Button 1 in order that first wav file is played and after that stopwatch starts. But Stopwatch doesn't start. This is what I came up to till now.
Stopwatch sw = new Stopwatch();
private void button1_Click(object sender, EventArgs e)
{
new System.Threading.Thread(testMethod).Start();
}
private void testMethod(object obj)
{
System.Media.SoundPlayer sp = new System.Media.SoundPlayer(#"D:\...\something.wav");
sp.Play();
}
void OnSoundPlayOver(object sender, EventArgs e)
{
timer1.Start();
timer2.Start();
sw.Start();
}
If your requirements are:
Start button that plays a sound, then starts a timer that displays the elapsed time on the screen.
Stop button that stops any current timers, leaving the last value on the screen.
Implemented in Windows Forms.
The following code is a BASIC example of how to get the above requirements working. It leverages the PlaySync method of SoundPlayer, a BackgroundWorker (to update the value on the label to be the elapsed seconds) and a Stopwatch for actually recording the elapsed time. It is definitely not the BEST way to accomplish this, but it should provide a starting point for you.
An important thing to note is that you cannot update a Label from a thread that is different from the thread that created the label (typically the UI thread). So if you're trying to update the Text of a label from another thread you need to use the labels .Invoke method (see the ThreadSafeUpdateLabel method in the code below).
This code does not take into account the situation where someone spam clicks the Start button (it just plays the sound as many times as you click) and the UI freezes when you click the Start button for as long as it takes the sound to play. I'll leave fixing those issues to you as a natural extension of the code.
Anyway, onto the code:
private Stopwatch _timer = new Stopwatch();
private BackgroundWorker _worker;
private void btnStop_Click(object sender, EventArgs e)
{
CancelExistingBackgroundWorker();
_timer.Stop();
}
private void btnStart_Click(object sender, EventArgs e)
{
CancelExistingBackgroundWorker();
_timer.Reset();
UpdateLabel(0);
_worker = new BackgroundWorker() { WorkerSupportsCancellation = true };
_worker.DoWork += (a, b) =>
{
while (true)
{
if ((a as BackgroundWorker).CancellationPending) return;
ThreadSafeUpdateLabel();
Thread.Sleep(100);
}
};
var soundPlayer = new SoundPlayer("wavfile.wav");
soundPlayer.PlaySync();
_timer.Start();
_worker.RunWorkerAsync();
}
private void ThreadSafeUpdateLabel()
{
if (lblElapsed.InvokeRequired)
{
lblElapsed.Invoke(new Action(() => ThreadSafeUpdateLabel()));
}
else
{
UpdateLabel(_timer.Elapsed.TotalSeconds);
}
}
private void UpdateLabel(double seconds)
{
lblElapsed.Text = seconds.ToString();
}
private void CancelExistingBackgroundWorker()
{
if (_worker != null)
{
_worker.CancelAsync();
_worker.Dispose();
}
}

Categories