Channels ▼
RSS

Mobile

Your Own MP3 Duration Calculator


The TrackDurations Class

The TrackDurations class is the workhorse that does the heavy lifting of accurately measuring the duration of each track. It utilizes the useful MediaElement class, which wraps the Windows Media Player engine (At least the Microsoft Windows Media Player 10 OCX for media playback).

Before diving into the TrackDurations class, let me introduce the TrackInfo class. This is basically a pair of properties: Name (a string) and Duration. A collection of TrackInfo object is the main data structure that travels from the TrackDurations class to the MainWindow class. The fact that Name and Duration are properties allows the UI to bind to them directly. More about it later:


  class TrackInfo
  {
    public string Name { get; set; }
    public TimeSpan Duration { get; set; }
  }

The TrackDurations class uses a delegate to notify the UI whenever it discovers the duration of another track. After processing all the tracks it sends a null object to let the UI know it's done. Here is the signature:


  delegate void TrackDurationsDelegate(TrackInfo ti);

A delegate is really a callback function. In this case a TrackInfo object is sent with each call.

TrackDurations implements the IDisposable interface, which allows explicitly disposing of resources by calling its Dispose() method:


class TrackDurations : IDisposable
{
  ...
}

The class keeps references to a media element and the delegate and stores a list of files in a stack:


    MediaElement _me;
    TrackDurationsDelegate _delegate;
    Stack<string> _files;

The media element is not managed by TrackDurations (it doesn't create or own it). It is passed in the constructor. The reason for this design is that a TrackDurations object is instantiated every time a new folder needs to be scanned, but a single media element can be used because it is relatively expansive to instantiate one. Also, there is never more than one scan going on at the same time, so there is no need to worry about conflicts.

The constructor accepts a media element, a delegate and a list of file names. It stores them (the list of files in a stack that can be popped). It sets the LoadedBehavior of the media element to "Manual", which means programmatic control on play/pause/stop, attaches two event handlers for MediaOpened and MediaFailed, sets the Source property to the first file in the list, and tells the media element to play it:


    public TrackDurations(
      MediaElement me,
      IEnumerable<string> files, 
      TrackDurationsDelegate d)
    {   
      Debug.Assert(me != null);
      Debug.Assert(d != null);
      _delegate = d;
      _files = new Stack<string>(files);
      Debug.Assert(_files.Count >  0);
      _me = me;
      _me.LoadedBehavior = MediaState.Manual;
      _me.MediaOpened += _onMediaOpened;
      _me.MediaFailed += _onMediaFailed;
      _me.Source = new System.Uri(_files.Peek());
      _me.Play();
    }

The result of all these operations is that the media element will open the first file in order to play it. If the open operation succeeds the _onMediaOpened() event handler will be called; otherwise _onMediaFailed() will be called.

The Dispose() method simply detaches the two event handlers:


    public void Dispose()
    {
      _me.MediaOpened -= _onMediaOpened;
      _me.MediaFailed -= _onMediaFailed;
    }

The _getNextTrack() method is used by both event handlers to move on after the current track has been handled. It reads the next file from the stack (processed files are removed by the event handlers) and tries to play it. If there are no more files the delegate is called with a null object to signal end of processing:


    void _getNextTrack()
    {
      if (_files.Count == 0)
      {
        _delegate(null);
      }
      else
      {
        // Get the next file
        _me.Source = new System.Uri(_files.Peek());
        _me.Play();
      }
    }

When the media element opens successfully the current file as a result of a Play() call the MediaOpened event is fired. That causes the _onMediaOpened() event handler to be called. At this point the correct duration is available as the NaturalDuration property of the media element. The event handler pauses the media element to avoid accidental playing, creates a new TrackInfo object, stops the media element completely, invokes the delegate and finally gets the next track.


    private void _onMediaOpened(object sender, RoutedEventArgs e)
    {
      _me.Pause();
      Debug.Assert(_me.NaturalDuration.HasTimeSpan);
      TimeSpan duration = _me.NaturalDuration.TimeSpan;
      var ti = new TrackInfo 
      {
        Name = System.IO.Path.GetFileName(_files.Pop()), 
        Duration = duration
      };

      _me.Stop();
      _delegate(ti);

      _getNextTrack();
    }

If for some reason, the media element failed to open the track the MediaFailed event is fired and the _onMediaFailed() event handler is called. All it does, is pop the bad file from the files list and get the next track:


    private void _onMediaFailed(object sender, RoutedEventArgs e)
    {
      // Get rid of the bad file
      _files.Pop();
      _getNextTrack();
    }


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video