tags:

views:

420

answers:

3

It is obvious what I am trying to do below, but I get the following exception:

Unable to return a TimeSpan property value for a Duration value of 'Automatic'.

I was discouraged to read that

NaturalDuration cannot be determined until after MediaOpened has occurred. (link)

Does this mean that I have to come up with a contrived method to open the file, wait for the media opened event in a separate thread, then return the duration only after the event has fired?

    public static int PlayAudio(string fileName)
    {
        try
        {
            myMediaPlayer.Stop();
            myMediaPlayer.Close();
            myMediaPlayer.Open(new Uri(filename));
            myMediaPlayer.Play();
            return myMediaPlayer.NaturalDuration.TimeSpan.Milliseconds;
        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message);
            return -1;
        }
    }
A: 

It is possible to get the duration info from the ID3 tags in MP3s and similar tags in WMA using one of the libraries mentioned in this question.

You could possibly get this info before loading the file in the MediaPlayer control.

Rhys
Unfortunately I am playing a variety of audio files including wav files as well. ID3 tags are not a solution for me.
Ben McIntosh
+1  A: 

I have to come up with a contrived method to open the file, wait for the media opened event in a separate thread, then return the duration only after the event has fired

That's exactly what it means, unfortunately. The below is what I just came up with for this:

using System;
using System.Threading;
using System.Windows.Media;
using System.Windows.Threading;

class Program
{
    static void Main(string[] args)
    {
        var mediaFile = @"c:\path\to\mediafile.mp3";
        var duration = GetMediaDuration(mediaFile, TimeSpan.FromMilliseconds(500));
        Console.WriteLine(duration.ToString());
        Console.ReadKey();
    }

    static TimeSpan GetMediaDuration(string mediaFile)
    {
        return GetMediaDuration(mediaFile, TimeSpan.Zero);
    }

    static TimeSpan GetMediaDuration(string mediaFile, TimeSpan maxTimeToWait)
    {
        var mediaData = new MediaData() {MediaUri = new Uri(mediaFile)};

        var thread = new Thread(GetMediaDurationThreadStart);
        DateTime deadline = DateTime.Now.Add(maxTimeToWait);
        thread.Start(mediaData);

        while (!mediaData.Done && ((TimeSpan.Zero == maxTimeToWait) || (DateTime.Now < deadline)))
            Thread.Sleep(100);

        Dispatcher.FromThread(thread).InvokeShutdown();

        if (!mediaData.Done)
            throw new Exception(string.Format("GetMediaDuration timed out after {0}", maxTimeToWait));
        if (mediaData.Failure)
            throw new Exception(string.Format("MediaFailed {0}", mediaFile));

        return mediaData.Duration;
    }

    static void GetMediaDurationThreadStart(object context)
    {
        var mediaData = (MediaData) context;
        var mediaPlayer = new MediaPlayer();

        mediaPlayer.MediaOpened += 
            delegate 
                {
                    if (mediaPlayer.NaturalDuration.HasTimeSpan)
                        mediaData.Duration = mediaPlayer.NaturalDuration.TimeSpan; 
                    mediaData.Success = true; 
                    mediaPlayer.Close();
                };

        mediaPlayer.MediaFailed += 
            delegate
                {
                    mediaData.Failure = true;
                    mediaPlayer.Close();
                };

        mediaPlayer.Open(mediaData.MediaUri);

        Dispatcher.Run();
    }
}

class MediaData
{
    public Uri MediaUri;
    public TimeSpan Duration = TimeSpan.Zero;
    public bool Success;
    public bool Failure;
    public bool Done { get { return (Success || Failure); } }
}
Tim Erickson
+1  A: 

Yes, there is no straightforward way to open a media file and get the media duration, the reason is that the file is opened in the background (to support files that take a long time to open, for example files on remote servers), so right after you call Open the file hasn't been opened yet.

Your best option is to restructure your code so you open the file without returning the duration and then wait for the MediaOpened/MediaFailed events.

If you absolutely need to open the file and return the duration you are in for some trouble, just try to avoid Thread.Sleep since it will lock out your UI and remember the user can keep interacting with the GUI while you wait for the file to open, potentially opening another file while you wait.

Nir