Reputation: 1505
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.)
Upvotes: 2
Views: 3145
Reputation: 205939
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();
}
Upvotes: 4
Reputation: 4513
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,
Upvotes: 1
Reputation: 1579
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.
Upvotes: 1