Problem: The wav files plays, but GUI does not respond at all. The lbl_PhonoString.Text is not updated every loop. User cannot pause or cancel until all the files are played completely.
I know I have to use a seaparate thread to play the Wav file. I am not at all familiar with threading. Can someone suggest what I should do here?
My Code:
//====================================================
bool bPause = false, bCancel = false;
private void btn_DicStart_Click(object sender,EventArgs e)
{
PlayWave("StartProgram.WAV");
for(int i=0;i<10;i++)
{
lbl_PhonoString.Text=i.ToString();
PlayWave("NextSound.WAV");
PlayWave("Clue"+i.ToString()+".WAV");
//Check if user pressed pause/cancel
while(bPause);
if(bCancel)
break;
}
}
private void btn_Pause_Click(object sender,EventArgs e)
{
bPause=!bPause;
if(!bPause)
btn_Pause.Text ="PAUSE";
else
btn_Pause.Text ="CONTINUE";
}
private void btn_Cancel_Click(object sender,EventArgs e)
{
bCancel=true;
}
//Play the Wave File
private void PlayWave(string WaveFile)
{
System.Media.SoundPlayer myPlayer=new System.Media.SoundPlayer();
myPlayer.SoundLocation=WaveFile;
myPlayer.PlaySync();
}
//====================================================
Look here essentially the same question and the accepted answer is your answer. In a nutshell, yes, you should be doing all long running processes in a seperate thread or they will block the UI, or use BackgroundWorker of course.
Related
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.
I am having some trouble playing a 4second long wave... What I am currently doing is running a timer....
So the timer is set to a second intervals... So every second, I run off and check something... If this check fails.. I play a wav file saying "Get back to work!"...
Currently, it pauses the timer.... So I hear "Get back to work" but while it is playing, I have lost 4 seconds of count time, because it is still finishing playing the sound.... Here is my call and my function...
playSimpleSound();
private void playSimpleSound()
{
SoundPlayer simpleSound = new SoundPlayer(#"c:\Windows\Media\shortwav.wav");
simpleSound.PlaySync();
}
If I switch them out, so that it actually plays everytime it hits.... I only hear the beginning of the wav file....
playSimpleSound();
private void playSimpleSound()
{
SoundPlayer simpleSound = new SoundPlayer(#"c:\Windows\Media\shortwav.wav");
simpleSound.Play();
}
So my question is...
How can I continue counting, and play the whole wav file?
Should I figure out how long the wav file is and then go ahead and do some kind of count with a mod on it?
So that I basically only play the file every x amount of seconds or something?
So basically just call the playsound function everytime, but inside that function count how many times it has been visited and only play it on the 4th second?
You could do something like this...play the sound on a different thread and toggle a flag:
public partial class Form1 : Form
{
private SoundPlayer simpleSound;
private bool SoundPlaying = false;
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
}
void Form1_Load(object sender, EventArgs e)
{
simpleSound = new SoundPlayer(#"c:\Windows\Media\shortwav.wav");
simpleSound.LoadAsync();
}
private void timer1_Tick(object sender, EventArgs e)
{
Console.WriteLine("Tick");
if (true) // check your condition
{
this.PlaySound();
}
}
private void PlaySound()
{
if (!this.SoundPlaying)
{
Console.WriteLine("Starting Sound");
this.SoundPlaying = true;
Task.Factory.StartNew(() => {
simpleSound.PlaySync();
this.SoundPlaying = false;
});
}
}
}
I need some help setting up a BackgroundWorker process for a Windows Media Player audio. I need to run the audio (WMP) directly from the BackgroundWorker, not from the main Thread, and that background process needs to remain opened until the end of the audio file, but even though the audio starts playing normal on PLAY, the BackgroundWorker stops, and therefore I don't think the audio is actually playing on that second Thread or backgroundWorker as is already closed.
The question I have is, how I can play this audio file using Windows Media Player (WMPLib) from a backgroundWorker that will remain opened until the end of the song?
using WMPLib;
namespace mediaplayer
{
public partial class MainWindow : Window
{
BackgroundWorker m_audioProcessingWorker;
public MainWindow()
{
InitializeComponent();
}
string filename = #"C:\audio\song1.mp3"
private void button_play_Click(object sender, EventArgs e)
{
// Create the Audio Processing Worker (Thread)
m_audioProcessingWorker = new BackgroundWorker();
m_audioProcessingWorker.DoWork += new DoWorkEventHandler(audioProcessingWorker_DoWork);
m_audioProcessingWorker.RunWorkerAsync();
m_audioProcessingWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(audioProcessingWorker_Completed);
}
private void audioProcessingWorker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
axWindowsMediaPlayer1.URL = filename;
axWindowsMediaPlayer1.Ctlcontrols.play();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void audioProcessingWorker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Audio is finished");
}
private void button_stop_Click(object sender, EventArgs e)
{
axWindowsMediaPlayer1.Ctlcontrols.stop();
m_audioProcessingWorker.CancelAsync();
}
private void button_pause_Click(object sender, EventArgs e)
{
axWindowsMediaPlayer1.Ctlcontrols.pause();
}
}
}
Thanks.
The UI I have it's causing the audio playback to freeze up or stalls
when loading big chunks of data inside the same Thread UI, and I would
prefer to have that audio process running on a separate thread, that
why I though of BackgroundWorker.
You should rather use BackgroundWorker (or better yet, Task.Run or naturally async IO APIs) to load big chunks of data in the background and keep the main UI thread lag-free. Then create and use the WMP control on the UI thread.
If you can't refactor the code this way and want to use a background thread for WPM, keep in mind such thread has to pump Windows messages, otherwise the WPM control may not function properly. For this purpose, you could use my ThreadWithAffinityContext from here.
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);
}
}
}
}
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();
}
}