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;
});
}
}
}
Related
I am playing audio files from device library using CrossMediaManager plugin. It plays files correctly, however, in MediaitemFinisedinstead of stopped status I am getting playing status. What I am trying to achieve is to play another file or stop playing based on some settings. e.g If user setting is configured to play next, I am picking another file and playing next item, but I need to do that after playing the first item is fully finished.
void AddCrossMediaHandlers()
{
CrossMediaManager.Current.StateChanged += Current_StateChanged;
CrossMediaManager.Current.MediaItemFinished += Current_MediaItemFinished;
CrossMediaManager.Current.MediaPlayer.BeforePlaying += MediaPlayer_BeforePlaying;
}
void RemoveCrossMediaHandlers()
{
CrossMediaManager.Current.StateChanged -= Current_StateChanged;
CrossMediaManager.Current.MediaItemFinished -= Current_MediaItemFinished;
CrossMediaManager.Current.MediaPlayer.BeforePlaying -= MediaPlayer_BeforePlaying;
}
private void Button_Clicked(object sender, EventArgs e)
{
StartPlaying(someFilePath);
}
async void StartPlaying(filePath)
{
var player = CrossMediaManager.Current;
if (player.State != MediaManager.Player.MediaPlayerState.Playing)
{
if (!string.IsNullOrEmpty(filePath))
await player.Play(new FileInfo(filePath));
}
}
private void Current_MediaItemFinished(object sender, MediaManager.Media.MediaItemEventArgs e)
{
Debug.WriteLine("item Finished event: " + CrossMediaManager.Current.State); //this return playing status therefore it doesen't play the next file.
//When media is finished look for user setting and play next or stop playing.
if (playNext) //I also checked !CrossMediaManager.Current.IsPlaying(), but didn't help.
StartPlaying(NextFilePath); //This fire, but because the status is playing, it doesn't play next.
}
Solved it. In case someone else may need it. I added Task.Delay(milliseconds) to wait for the player to stop.
private void Current_MediaItemFinished(object sender, MediaManager.Media.MediaItemEventArgs e)
{
Debug.WriteLine("item Finished event: " + CrossMediaManager.Current.State); //this return playing status therefore it doesen't play the next file.
var player = CrossMediaManager.Current.MediaPlayer;
await player.Stop(); //stop playing explicitly
await Task.Delay(TimeSpan.FromSeconds(1)); // add a delay (you can add milliseconds)
}
I have a little problem. There is something like chess timer. When i press button, current timer stops and second starts, but after 1 second. How can i start second one immediately?
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1 {
public partial class Form1 : Form {
byte sec1;
byte sec2;
public Form1() {
InitializeComponent();
sec1 = 0;
sec2 = 0;
}
private void button1_Click(object sender , EventArgs e) {
timer1.Start();
timer2.Stop();
}
private void button2_Click(object sender , EventArgs e) {
timer2.Start();
timer1.Stop();
}
private void timer1_Tick(object sender , EventArgs e) {
label1.Text = sec1.ToString();
sec1++;
}
private void timer2_Tick(object sender , EventArgs e) {
label2.Text = sec2.ToString();
sec2++;
}
}
}
Edit
I know your question is "how to start the timers immediately", but in your code they are starting immediately. When you call start the timer starts. I believe the effect you are seeing is related to the delay associated with the tick event, which from the description I am assuming is set to a 1 second interval. Since you have said that you are trying to simulate something similar to a chess timer (although in your case counting up as opposed to down), then using something like a stop watch which can start, stop and show elapsed time would be a closer model. Since there is a Stopwatch class that provides exactly this behavior, I think it would be easier to implement it using two of those and just have a single background thread that updates the UI as frequently as needed. You could even add an update call into each button push to ensure the text boxes are up to date.
===============================
Maybe instead of the timers you should use two instances of the Stopwatch class. This will remove the need for your two variables that you are using to keep track of the seconds as the Stopwatch class will be holding the elapsed time for each counter.
Then in your button methods you could just do this:
private Stopwatch sw1 = new Stopwatch();
private Stopwatch sw2 = new Stopwatch();
private void button1_Click(object sender , EventArgs e) {
sw1.Start();
sw2.Stop();
}
private void button2_Click(object sender , EventArgs e) {
sw2.Start();
sw1.Stop();
}
And then you can use a Background worker or some other background thread that runs and updates your text boxes with the elapsed time from the timers you just need to grab the elapsed time.
// This will give you the total number of seconds elapsed.
var timer1Seconds = Math.Floor(sw1.Elapsed.TotalSeconds);
Here is an example of how you can make this update the UI:
private bool _stop = false;
public Form1()
{
InitializeComponent();
Task.Run(() =>
{
while(!_stop)
{
UpdateElapsedTimes();
Thread.Sleep(1000);
}
}
}
private void UpdateElapsedTimes()
{
if (InvokeRequired)
{
Invoke(UpdateElapsedTimes());
return;
}
label1.Text = Math.Floor(sw1.Elapsed.TotalSeconds).ToString();
label2.Text = Math.Floor(sw2.Elapsed.TotalSeconds).ToString();
}
Note - in a production program I would not use a boolean as my loop checker, you would use an event handle, and probably a couple of event handles if you wanted to allow pausing the updates, this is just to show an idea of how to do it. You could invoke directly from the thread method and drop the InvokeRequired check, but I added that for additional safety and since it was there I skipped it in the loop.
The timer does start immediately. The problem is that you are not reporting fractions of seconds, so the display will show 0 until a full second has elapsed, which is accurate, technically.
If you want to show 1 immediately, just initialize your variables that way.
public Form1() {
InitializeComponent();
sec1 = 1;
sec2 = 1;
}
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();
}
}
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.