Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Audiobook Player Using an Embedded Microsoft Mediaplayer

0.00/5 (No votes)
7 Mar 2010 1  
An audiobook player using an embedded Microsoft mediaplayer

Introduction

This project is born out of pure frustration with Microsoft Mediaplayer and Itunes. The thing I needed was a simple player for my audio books that remembers the last position (file index and position).

Some of my audio books are a composed of several MP3 files (not all containing full meta information). So it would be nice to get a cover image if none is provided and to guess the author and title. The application is a hobby project, not a commercial grade application. So don't expect any unit tests or any other guaranties, it works on my machine (A Windows 7 64-bit laptop). It’s placed on CodeProject to give something back to everybody posting nice articles on the site. May you find the application interesting or the code useful. It contains the full source code and a setup project for the executable.

Application Requirements

Remember Position

There is only 1 playlist (the current). It will be written to the application data directory when the application closes and read at application startup. The playlist (class AudiobookList.cs) contains the names of the files, the cover image and the current playing position.

Easy to Create New Playlist

Support for drag-and-drop. All files dropped on the application will form a new playlist and start playing. The files can also be added to the commandline startup parameters.

Read Meta-Info

The taglib is used to read the meta information from a media file.

Guess meta-info Based on Filename

Use the Amazon webstore to check artist and title and get the cover image. You will need to supply your own account key. This key can be obtained without cost from Amazon (http://aws.amazon.com/).

Implementation Details

UI

The application is built using WPF and is a border-less application. The border can be hidden by adding the following to the declaration of the window:

<window ResizeMode="NoResize" WindowStyle="None" AllowsTransparency="True"
Background="Transparent">

Adding a leftmouse down eventhandler adds support for moving the window around on the screen when the left mouse button is held, while moving the mouse.

private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (sender == this)
    {
        DragMove();
    }
}

The main window is still called Window1 because I am a bit lazy. ;-) The code is straight-forward without any tricks, other than some opacity changes on bitmaps.

Playlist

The playlist is stored as an instance of AudiobookList. The class itself contains all the code needed to create, load and store the list. Based on a list of files or a directory, a new instance can be created. The directories will be scanned recursively and each file will be added if it’s an audio file. This is done by getting the mime-type from the registry based on the file extension. If it starts with “audio”, we assume it’s an audio file.
The filenames are sorted based on the numbers in the file and the containing directory. Every number is added, multiplying the total with 1000 for each number found.

public static FileType DetermineFileType( string filename )
{
    string mime = MimeType(filename);
    if (mime.StartsWith("image"))
    {
        return FileType.Image;
    }
    else if (mime.StartsWith("audio"))
    {
        return FileType.Audio;
    }
    return FileType.Other;
}

private static string MimeType(string filename)
{
    string mime = "application/octetstream";
    string ext = System.IO.Path.GetExtension(filename).ToLower();
    Microsoft.Win32.RegistryKey rk = 
	Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
    if (rk != null && rk.GetValue("Content Type") != null)
    {
        mime = rk.GetValue("Content Type").ToString();
    }
    return mime;
} 

Meta-info

The class DetectMediaInfo will retrieve the meta info from a file. It will try the MP3 tags first using the open-source TagLib library. If no information is provided in the meta-info, we try to extract the author and title from the filename. We use the Amazon web store to check them. The parameters for Amazon are added to a URL and hashed using the access and secret key (not provided).The access and secret key can be entered using the config page and will be stored in the application config file. The parameters for the Amazon WS are hashed and added to the URL. The response is plain XML. The code itself was provided by Amazon. The only thing I added was the processing of the resulting XML with LINQ.

public void FindBookByTitle(string searchTitle, string searchAuthor, 
	out string bookTitle, out string bookArtist, out string imageUrl) 
{ 
    bookTitle = string.Empty; 
    bookArtist = string.Empty; 
    imageUrl = string.Empty; 
    string url = BuildRequestSearchBookTitle(searchTitle, searchAuthor); 
    string xml = CallAmazonWS(url); 
    ProcessXmlBookResult(xml, ref bookTitle, ref bookArtist, ref imageUrl); 
} 

private string BuildRequestSearchBookTitle(string searchTitle, 
					string searchAuthor) 
{ 
    IDictionary parameters = new Dictionary(); 
    parameters["Service"] = "AWSECommerceService"; 
    parameters["Version"] = "2009-03-31"; 
    parameters["Operation"] = "ItemSearch"; 
    parameters["SearchIndex"] = "Books"; 
    parameters["Title"] = searchTitle; 
    if (string.IsNullOrEmpty(searchAuthor) == false) 
    { 
        parameters["AuthorName"] = searchAuthor; 
    } 
    parameters["ResponseGroup"] = "ItemAttributes,Images"; 
    string requestUrl = Sign(parameters); 
    return requestUrl; 
} 

private static string CallAmazonWS(string url) 
{ 
    WebRequest request = HttpWebRequest.Create(url); 
    WebResponse response = request.GetResponse(); 
    StreamReader reader = new StreamReader( response.GetResponseStream( ) ); 
    return reader.ReadToEnd( ); 
}

private static void ProcessXmlBookResult(string xml, ref string bookTitle, 
			ref string bookArtist, ref string imageUrl) 
{ 
    XNamespace ns = "http://webservices.amazon.com/AWSECommerceService/2009-03-31"; 
    XElement root = XElement.Parse( xml ); 
    bool isValid = RequestIsValid(root, ns); 
    int resultCount = GetResultCount(root, ns); 
    if ( isValid && (resultCount > 0)) 
    { 
        XElement item = GetFirstItem(root, ns); 
        GetAuthorAndTitle(item, ns, ref bookTitle, ref bookArtist); 
        imageUrl = GetItemImage( item, ns ); 
    } 
} 

The Player

The player is a COM interface of Microsoft Mediaplayer, hence the WMPLIB interop library. The MyPlayer class is an abstraction of the COM instance. We cast the interface to a WindowsMediaPlayerClass instance. This allows us to receive PlayStateChange event and set the Autostart property to false. There is no need to do anything fancy. The UI has a background timer that is run every second. It checks if the player is playing and then prints the current duration as a string. A duration string is provided if the Mediaplayer is not yet playing, otherwise we get an empty string.

Ideas to Improve the Application

  • The application assumes there is only one author and title for all the files. If you add different titles to the list, only the first will be shown.
  • The XML result from Amazon only processes the first item. Maybe the other items may also contain a valid result.
  • Add some more checks and add some robustness. Currently only the real bare minimum of the exceptions are trapped with a try/catch.
  • Add support for formats other than MP3. It may work, but none is tested.
  • Use the cloud as a storage for audio files and AudiobookList. So play may continue on every computer used.

Have fun!!

History

  • 7th March, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here