Stopping SoundPlayer from a different thread while it's playing - c#

For the sake of making a (shuffled) playlist, I've made a separate thread in which I load and play each song in the playlist. The background stuff (wav files, file paths, playlists and shuffling) all work without a hitch.
The issue is that I have 2 windows, each of which can close and open the other. Each window has a different playlist, and when I switch to the other window, I want my static SoundPlayer to stop playing, then start playing the new playlist.
This currently isn't working: currently, the application waits until the current track is finished before displaying the next window and starting the other playlist. Yes, the entire application waits on this.
I'm new to thread coding, so I'm not really sure what to do. The two methods of stopping this I've tried so far have been SoundPlayer.Stop() and Thread.Abort(). Neither changes the situation at all.
In each window:
Thread playlistThread;
public Window()
{
InitializeComponent();
MusicPlayer.music.Stop();
playlistThread = new Thread(() => MusicPlayer.PlayPlaylist(MusicPlayer.ShufflePlaylist(MusicPlayer.PlaylistFromType("[insert track type]"), random)));
playlistThread.Start();
PlayPlaylist which I will show next takes a List of strings, so don't worry about the Thread line, it's just a few sections put into one. The properties after that simply generate that list, and again, that all works, but I can show it if anyone thinks it's necessary. Here is the PlayPlaylist method:
public static void PlayPlaylist(List<string> tracks)
{
for (int i = 0; i < tracks.Count; i++)
{
music.SoundLocation = tracks[i];
music.PlaySync();
}
}

Here's the answer I worked out:
public static void PlayTrack(List<string> tracks, int i)
{
while (true)
{
if (i == tracks.Count)
{
tracks = MusicPlayer.ShufflePlaylist(tracks, MusicPlayer.random);
i = 0;
}
music.SoundLocation = tracks[i];
int l = SoundInfo.GetSoundLength(tracks[i]);
music.Play();
while (l > 0)
{
Thread.Sleep(1000);
l -= 1000;
}
i++;
}
}
The SoundInfo class with its GetSoundLength method I found here.
The reason this method works while others do not is because of how Play() and PlaySync() work. PlaySync() plays the entire .wav file in the current thread, with nothing else running until it finishes. Thus, even SoundPlayer.Stop() and Thread.Abort() do not work, as they insert a new line after the current one.
By running this method in a new thread, you avoid PlaySync() giving you that issue. However, it will still be impossible to stop the track ahead of time using PlaySync(). This is why you use Play() instead.
Therein lies a second issue, however: Play() plays the track in its own thread, meaning the rest of the code will continue. This is a big risk if you're wanting to do anything only after the current track finishes.
The answer is to calculate the length of the track you're going to play. Then simply create a while loop, running until l (given by GetSoundLength()) reaches 0. In each pass through the loop the thread (separate from your window's main thread) sleeps for 1 second. This is fine on the CPU and means that every second extra code, such as SoundPlayer.Stop(), can be injected into the thread.

Related

Progress report : Need help understanding how it works

I'm discovering this and can't get to understand how it works.
I have a progress bar in my main form and a menuStrip with a few maintenance functions on a mysql DB. I'd like to monitor progress for each of them.
So I have a class report and a Progress progress.
My declarations are as follow :
ProgressReportModel report = new ProgressReportModel();
Progress<ProgressReportModel> progress = new Progress<ProgressReportModel>();
progress.ProgressChanged += ReportProgress;
Now for each of the functions, I usually start with :
report.max = count;
report.visibility = true;
report.PercentageComplete = 0;
showtheProgress(progress);
with ShowtheProgress because I didn't find a way to convert from Progress to IProgress:
private void showtheProgress(IProgress<ProgressReportModel> progress)
{
progress.Report(report);
}
My issue is that this works for some functions but not all.
One difference that i can see if that it works with functions which are async but not with functions which are not async. To void flooding with code, just gonna put one function which is not working (ie the progressbar is not even showing up):
private void getMp3Files()
{
Globals.counttot = Directory.GetFiles(Globals.pathdir, "*.mp3", SearchOption.AllDirectories).Length;
report.max = Globals.counttot;
report.visibility = true;
report.PercentageComplete = x;
showtheProgress(progress);
DirectoryCopy(Globals.pathdir);
report.visibility = false;
report.PercentageComplete = 0;
showtheProgress(progress);
}
private void DirectoryCopy(string sourceDirName)
{
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
FileInfo[] files = dir.GetFiles("*.mp3");
foreach (FileInfo file in files)
{
string temppath = Path.Combine(sourceDirName, file.Name);
Mp3 tmp_mp3 = Globals.getTags(temppath);
Globals.AddRowDs(tmp_mp3, false);
report.PercentageComplete = x; //not part of this code but it's a counter
showtheProgress(progress);
}
foreach (DirectoryInfo subdir in dirs) DirectoryCopy(subdir.FullName);
}
Thanks in advance!
It's not completely clear from the code that you have posted but my guess is that you have this problem because of the way how winforms works with threads and one of the reasons why the async was 'invented'.
A windows form is based on something like a message queue. Everything that you do on that form like moving your mouse, clicking a button, moving/resizing the form, typing and many more is converted to events and placed in this queue. In the background there is something constantly checking this queue for new events and executing them, one of these events is to draw the screen (paint), basically showing your form on the screen. You can only see the changes that are made (like showing a progress bar) during these paint events. If the time between these paint events is too long you see the message "Not responding".
Some of these events are also clicks on a button and these will execute all the code that you made. If the process of this event takes too long, then you will hold up the message queue and create the "Not responding" message. In order to avoid this, it's suggested to make these events as quick as possible, if you want to execute something that takes a long time, make it on another thread (see BackgroundWorker for an example). One downside of this is that it's not easy to communicate with the form (typically checking InvokeRequired and calling Invoke)
Here is where the async/await comes to help you. If you have a button click, and your code encounters an await that is not done yet, then it will add some code that, once the awaited method is done, it will add an event to the message queue to continue from this point in your code and end the event. This means that the rest of the events in the message queue (like showing that progress bar, displaying some text or handling other button clicks) are possible.
Just to be complete, some can suggest that you can sprinkle your code with Application.DoEvents(), these will force the message queue to be processed while you are still inside the method. This is a quick and dirty way that some people take without knowing the full implications of this. Know that it exists but avoid it.

A thread for updating a RichTextBox.Text twice a second is not working

First of all - I'm very low skilled programmer. I am building the foundation of a simple music app for my bachelor degree project. My question is regarding a internal clock method that is meant to increase an int value by 1 BPM times a minute.
I've created an internalClock class:
public class internalClock
{
// THIS METHOD WILL BE CALLED WHEN THE THREAD IS STARTED
public static void clockMethod()
{
int BPM = 135;
int clockTick = 1;
Form1 clockForm = new Form1();
// infinite loop
while (true)
{
if (clockTick == 8)
{
clockTick = 1;
}
else
{
clockTick++;
}
clockForm.metrobox.Text = clockTick.ToString();
Thread.Sleep(60 * 1000 / BPM);
}
}
}
This is how I managed to get an access to the RichTextBox itself:
public RichTextBox metrobox
{
get { return metroBox; }
set { metroBox = value; }
}
In the main 'Program.cs' I've written what's meant to start a separate thread to run the clockMethod until the program is closed:
// THREADING
// Create a thread
internalClock oClock = new internalClock();
Thread oClockThread = new Thread(new ThreadStart(internalClock.clockMethod));
// Start the internalClock thread
oClockThread.Start();
It's not updating the text in the RichTextBox. Also, if I call the clockMethod() without creating a separate thread for it - the application freezes. Sorry for my amateur question, I'm just getting started with C# (yeah.. my uni is useless). What am I doing wrong in my code?
So the above code has several problems, however I would encourage you to check out the Timer control that you can add to the form you want to do processing at a certain interval or in certain "ticks". MSDN Form Timer
With the timer you can remove that class you have and invoking a new thread, etc etc. I would read up on the Timer class in the given link and think about how you can re-work your application structure to fit that. The concepts for why that thread isn't working, etc, is frankly not that important for where you're at. I think you just need to focus for now on a tool that already does what you want it to do, which I believe is the Timer.
As a general note, you usually don't need to create a raw thread in .NET. As of .NET 4.0 you can access types called Tasks to perform multi-threaded logic and processing. If you find the need to do that later on, check that out. Task Type MSDN

Why can't I run this separate window

Using c# winforms, I'm trying to make a sort of overlay. Here's what I'm testing with:
Main window has this code:
OverlayThread = new Thread(DisplayOrderOverlay);
OverlayThread.Start();
private void DisplayOrderOverlay(object obj)
{
ActiveOrderOverlay AOA = new ActiveOrderOverlay();
AOA.StartLoop();
AOA.ShowDialog();
}
And the overlay is just a list box on a form with this code:
public void StartLoop()
{
while (true)
{
Thread.Sleep(500);
Random r = new Random();
listBox1.Items.Add(r.Next().ToString());
this.Refresh();
}
}
I never even see the overlay, but if I pause, the loop is running.
AOA.StartLoop();
AOA.ShowDialog();
You're starting your loop and trying to show the dialog on the same thread, so until the loop finishes (it never will), AOA.ShowDialog() isn't called. Make the loop exit and you should see your dialog open. Or, you can also test by putting a breakpoint on the second line to see if it ever gets hit (you should have done this already).
while (true) loop will run forever and hence freeze you app. Normally you'd break of such loop on some condition to terminate the loop.
Possibly you are looking for Timer setup for every 500ms with handler that adds random number to a list.
Side note: check out Random number generator only generating one random number for proper creation of Random.

Threading doesn't work correctly

I have an application, which is to repeat the numbers what I hear then record the digits what I read. The numbers are 0,1,2,3,4,5,6,7,8,9. I use a for loop to play these numbers with a text to speech skill, which is from a third party.
For the recording part, I have to put it in a separate thread by the third party requirement. To record the voice, the method is likely:
recordVoiceResource.Record(fileName);
To stop it, use:
recordVoiceResource.Stop();
Now I find sometimes my recording is 0 second, which means the code perhaps doesn't reach Record line. Sometimes it only has 2 seconds. I believe the thread schedule is wrong.
private ManualResetEvent terminate = new ManualResetEvent(false);
PlayTTS("Please repeat the following numbers as you hear them.");
Thread t = new Thread(() => RecordNumbers());
t.Start();
Thread.Sleep(2000);
terminate.Set();
terminate.WaitOne();
PlayNumbers();
recordVoiceResource.Stop();
The thread method is:
private void RecordNumbers()
{
recordVoiceResource = TelephonyServer.GetVoiceResource();
recordVoiceResource.MaximumSilence = 1;
recordVoiceResource.MaximumTime = 30;
// Start recording what I read from I heard
recordVoiceResource.Record(fileName);
}
To playNumbers,
private void PlayNumbers()
{
foreach (var item in numbers)
{
try
{
vr.PlayTTS(item.ToString()); // will be 0,1,2,...9
Thread.Sleep(2000);
}
According to your comment, the property MaximumSilence gets or sets the maximum silence in seconds that will be allowed until termination of the next voice function. You are setting it to one second, starting the recording, and then sleeping for two seconds before beginning playback that prompts the user to say something. Do you see the problem here? Assuming the mic doesn't pick up some unrelated speech during that period, the recording will stop before the playback even begins.
Since there is a 2-second gap between number playback, you probably need to set MaximumSilence to several seconds.
That is, of course, assuming your intention was to capture a single recording of the user speaking all the numbers (which is how your code is written). If you want to capture the spoken numbers individually, then you may need to schedule and synchronize separate recordings as each number is played back. You may want to double-check the API to make sure your solution is what you intended.
It is very likely your problem is causing due to Thread.Sleep(). Use a timer instead:
System.Timers.Timer Record = new System.Timers.Timer();
Record.Interval = 2000;
Record.Elapsed += new System.Timers.ElapsedEventHandler(Record_Elapsed);
void Record_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Record.Enabled=false;
PlayNumbers();
recordVoiceResource.Stop();
}
And set:
Thread t = new Thread(() => RecordNumbers());
t.Start();
Record.Enabled=true;

XNA game collected by GC after running update

I'm working on writing a splash screen that returns a game mode (int) and an IP address (string). The idea is the splash screen runs, takes user input and then runs the main game with these options. I'm using a thread to achieve this - the thread polls for an exit request from the splash screen, then pulls the values out to program.cs and calls exit() on splash.
The main game runs on it's own with no issues but with the splash screen enabled the game runs just 1 frame and seems to be disposed of by garbage collection after running the update method. (returns a DisposedObjectException or something of the sort if trying to reference it) After a bit of debugging I've found the issue is with the exit command. Code is as follows:
using System;
using System.Threading;
namespace SplashScreen
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{
int choice = 0;
string ip = "";
bool runSplash = true;
bool useThreading = true;
bool ignoreThreadResponse = false;
// Debug option, toggle running splash screen
if (runSplash == true)
{
bool splashrunning = true;
using (Splash1 splash = new Splash1())
{
if (useThreading)
{
// Run a thread to poll whether the splash screen has requested an exit every 0.5 seconds
Thread t = new Thread(() =>
{
while (splashrunning)
{
// If splash requests exit pull gameMode choice and IP Address before killing it, then quit this thread
if (splash.requestingExit)
{
choice = splash.choice;
ip = splash.ip;
// The offending piece of code, without this you can simply select an option, force close and second part runs fine
//splash.Exit();
splashrunning = false;
}
Thread.Sleep(500);
}
});
t.Start();
}
splash.Run();
}
}
// If splash screen is not running, assign default values
if(!useThreading || !runSplash || ignoreThreadResponse)
{
choice = 2;
ip = "127.0.0.1";
}
if (choice != 0)
{
// This game is picked up by garbage collection after running Update once
using (Game1 game = new Game1(choice, ip))
{
game.Run();
}
}
}
}
}
When splash.Exit() is called, it causes game1 to be collected after the first update. If I disable threading it works fine. If I exit using the X at the top right it works fine. Whether or not I ignore the thread response the game fails to run if threading is enabled and I call splash.Exit().
What I'm looking for is any of the following:
A reason why the second game is being collected.
An alternative way to exit a game or call the 'close window' (big red x) function.
A better way of implementing this.
I've used console input to do this in the past, but I want to move on to using a graphical interface instead of an ugly command prompt for the user.
Edit:
Turns out I was almost there. While GSM is probably the correct way of doing things, for anyone who just wants to grab the code from the question and throw caution to the wind you simply need to add a thread to run the second game.
I'm pretty sure this isn't ideal, but it's a lot less tweaking in my case.
Thread gt = new Thread(() =>
{
using (Game1 game = new Game1(choice, ip))
{
game.Run();
}
});
gt.Start();
So while I'd recommend anyone starting from scratch to use GSM, this might be a quick solution for someone else just trying to get it running.
What does your Splash class look like? Exit is a method of the Game class, and exits the game. Is your Splash class inheriting Game? If so, it shouldn't.
Edit:
After reading the bottom half of your post a bit more clearly - you should only have a single class that inherits from Game, and it should run your game. If you want to display a splash screen it should be a custom class, or look into the Game State Management sample.

Categories