Silverlight, WCF, and Streaming My Personal Music Repository from IIS

Updates

I've recently posted a new article continuing this topic:  Silverlight, WCF REST and Streaming My Personal Music Repository from a Standalone EXE , which as an updated client with regex search and random play functionality.

image

A buddy of mine recently had mentioned he wanted a web based program he could use to listen to his music from home.  As a contractor, we move around a lot. At some locations, we can’t install any software; others, we have firewall issues. Our mobile devices couldn’t hold every song we stored at home, and copying our music around everywhere just didn’t seem like a great idea.  So we thought… wouldn’t it be cool if we could have just a simple website that could stream our music from anywhere? It’s an easy install…

So I started digesting the idea and thought yeah, we could do that.  At first we’ll have to just use something simple like streaming the files directly from IIS in a silverlight music player of sorts.  But eventually, we can really take advantage of some cool WCF hosting features in windows and not require IIS at all, which would be great for most of us since everyone doesn’t have a windows server sitting around at home. After a tiny bit of research, I realize we could also take advantage of the Media Services component built into windows server and IIS, which from the sound of it are pretty comprehensive. But with our end goal in mind, we’ll want to find a way to stream it right out of a simple windows app available to any edition of Windows.

Having said all that, I decided to start writing about this little toy project.  To begin, we have a simple Silverlight application with a music player, list manager, and an IIS server hosting a virtual directly to our mp3 repository.

Getting Started

Of course you’ll need all the links and references to some handy Silverlight information.

http://silverlight.net/GetStarted/ is an excellent starting point.

You may need the Silverlight Tools for Visual Studio 2008 SP1.

I’m using Expression Blend 2, and for my project templates to be correct, I needed Expression Blend 2 SP1.

Download the Sample

So here we have our simple player. It has a stop and play button and a list box.  It’s very, very, very simple.  The idea is to load the music list on startup, then as we choose an item in the list, it plays.  It also will auto-play the next song in the list when the current song ends, and it will recycle the whole list when the last song in the list ends. There are a TON of cool things we can do with this, but for now, we’re starting simple.

I started out by creating a new Silverlight Application in Expression Blend.  If you don’t have Expression Blend, you can create a new Silverlight Application using Visual Studio with Silverlight Tools. This creates a class library containing the Silverlight controls along with a website project containing sample run pages to load and test your Silverlight application. In this case, our project name is MusicPlayer and the auto-generated web project name is MusicPlayer.Web.

The Player

Add a new Silveright User Control to the MusicPlayer project called Player. Here’s the XAML for our new control.

<UserControl x:Class="MusicPlayer.Player"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="513" Height="613" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Name="ContentPanel">
    <Grid x:Name="LayoutRoot" Background="#343434">
        <MediaElement x:Name="MediaElement1" Margin="0,0,103,0" Height="26" VerticalAlignment="Top" Width="109" HorizontalAlignment="Right" AutoPlay="True" />
        <Button HorizontalAlignment="Left" Width="95" Content="Stop" x:Name="uxStop" VerticalAlignment="Top" Height="26" />
        <Button Content="Play" x:Name="uxPlay"  Height="26" VerticalAlignment="Top" Width="99" HorizontalAlignment="Right" MinHeight="0" UseLayoutRounding="True"/>
        <ListBox Margin="0,30,0,0" x:Name="uxPlayList" Background="#FF000000" BorderBrush="#FF345CA4" Foreground="#FF8FC0FF" Opacity="0.41"/>
        <TextBlock Margin="99,0,103,0" VerticalAlignment="Top" Text="" TextWrapping="Wrap" Foreground="#FFAEAEAE" x:Name="uxStatus" Height="30"/>
    </Grid>
</UserControl>

This should give you something similar to the control shown above. Start wiring up events for the simple things like the buttons and such. For their click event, you can control the MediaElement by asking it to Play(), Stop(), Pause(), etc.    I included a text block that just shows the current state of the media element.  Use the CurrentStateChanged event on the MediaElement to update its value.

image.png

Getting the Playlist

Before we get into loading the list control, we’ll need to define its WCF services. I designed a very simple structure to handle the information we need for our Music in this exercise. For now, it contains only a list of filenames and their relative path to the root of the mp3 repository. It also contains the public web path configured on the server.

MusicManagerService will have one function for now: GetFiles(), which will return a FileBrowser filled with the information from the MP3 repository. Simple enough…

Within the FileBrowser we have our implementation of the LoadFiles() function where it actually seeks the local repository and collects its information.

//FileBrowser.cs

public void LoadFiles()  
{
    if (!string.IsNullOrEmpty(LocalMusicRoot) && Directory.Exists(LocalMusicRoot))
    {
        if (this.Files == null)
            this.Files = new List<string>();

        this.Files.Clear();

        this.Files.AddRange(Directory.GetFiles(this.LocalMusicRoot, "*.mp3", SearchOption.AllDirectories));
        this.Files.AddRange(Directory.GetFiles(this.LocalMusicRoot, "*.wma", SearchOption.AllDirectories));

        //trim off the full path; leave only the relative one.
        List<string> newFiles = new List<string>();
        foreach (string file in this.Files)
        {
            newFiles.Add(file.Replace(LocalMusicRoot, string.Empty).Replace('\\','/'));
        }
        this.Files = newFiles;
    }
    else
        throw new ApplicationException("Cannot continue. No directory specified or the directory does not exist.");
}

WCF Setup

The easiest way to do this is to add a new Silverlight-enabled WCF Service to your web project.  Change its service contract to resemble the following snippet.  This will also automatically configure your web.config as well. The funny thing about the Silverlight enabled web service is that it mixes the contract interface with the class implementation. You can optionally separate this out into a WCF library in a more traditional approach using the interface and class implementation separated. We’ve done this in our downloadable sample code.

[ServiceContract()]  
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MusicManager  
{
    [OperationContract]
    public FileBrowser GetFiles()
    {
        FileBrowser fb = new FileBrowser();
        fb.LocalMusicRoot = Properties.Settings.Default.LocalRootPath;
        fb.WebMusicRoot = Properties.Settings.Default.WebRootUrl;
        fb.LoadFiles();

        fb.LocalMusicRoot = null; //clearing unnecessary local path from return value
        return fb;
    }
}

You should be able to browse MusicManager.svc and see the service page. You can also reference the service in the MusicPlayer silverlight project. In the ContentPanel Loaded event, we will call out to the service.

private void ContentPanel_Loaded(object sender, RoutedEventArgs e)  
{
    //load up the music files.
    MusicManagerService.MusicManagerServiceClient client = new MusicManagerService.MusicManagerServiceClient();
    client.GetFilesCompleted += new EventHandler<MusicPlayer.MusicManagerService.GetFilesCompletedEventArgs>(client_GetFilesCompleted);
    client.GetFilesAsync();
}

Our async callback function for the service call will populate the list control.

void client_GetFilesCompleted(object sender, MusicPlayer.MusicManagerService.GetFilesCompletedEventArgs e)  
{
    this.uxPlayList.Items.Clear();
    _Browser = e.Result;

    foreach (string file in _Browser.Filesk__BackingField)
    {
        this.uxPlayList.Items.Add(file);
    }
}

To finish up, add implementation for the selected index changed on the list control to change the MediaElement’s Source to the selected file with its web path. For convenience, we AutoPlay to true on our MediaElement to autoplay its media. Anytime the Source property changes values, it will begin streaming and playing the audio.

private void uxPlayList_SelectionChanged(object sender, SelectionChangedEventArgs e)  
{
    //start streaming the file.
    MediaElement1.Source = new Uri(_Browser.WebMusicRootk__BackingField + uxPlayList.SelectedItem.ToString());
}

When the media ends, we need to increment the playlist selection.

private void MediaElement1_MediaEnded(object sender, RoutedEventArgs e)  
{
    //move to the next item
    int ix = uxPlayList.SelectedIndex;
    ix++;

    if (ix >= uxPlayList.Items.Count)
        ix = 0;

    uxPlayList.SelectedIndex = ix;
}

Wrap Up

So the end result will be our player initializing by pre-loading a playlist. Once the first song is played, the rest will auto-play as they increment through the list. With my MP3’s on a cable connection I’ve noticed it’s uploading around around 60KB/s.  So as long as the location you’re at can tolerate a little bit of bandwidth, it’s a pretty good start to a little remote music player. You might even be able to throttle IIS down so it doesn’t saturate the wire.

Next Time

The next round will be using a restful http request via WCF 3.5 to stream audio files rather than straight up IIS.  You can read more about that here:

Part 2 – Silverlight, WCF REST and Streaming My Personal Music Repository from a Standalone EXE