Kamalesh Wankhede
Kamalesh Wankhede

Reputation: 1505

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.)

Upvotes: 2

Views: 3145

Answers (3)

Ivan Stoev
Ivan Stoev

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

Berkay Yaylacı
Berkay Yaylacı

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

Skintkingle
Skintkingle

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

Related Questions