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

This is way cool. From time to time, I tend to screw around with stuff I enjoy that really has little productive value.  A year ago this month, I recall wanting to continue writing about sharing your personal music without using IIS and hosting the music directly from a WCF standalone executable. Well it turns out this is a whole lot simpler than I originally thought.

Disclaimer: This probably isn’t best practice, and it’s ripe for enhancements. My hope is that it gets your imagination fired up and motivates you to continue where I left off.

Challenges:

  • How the heck do I stream an MP3 file to a media element via WCF (without having to download the whole file before playing it)?
  • Cross-site scripting policies are blocking my download requests. How can I serve these up via my self hosted WCF REST service?
  • Silverlight3 doesn’t support WCF REST yet. How do I deserialize the WCF REST contract messages?

 

Changes Since Last Release

player_small My original design was very simple.  I hosted the Silverlight files and used WCF with an IIS server at home. The player pulled a list of files and locations. The player’s media element source was set directly to a music file hosted in an IIS virtual directory. All that works great until you are on a machine without a web server. Ultimately, I wanted to figure out a way for the user to host their music repository from their desktop with a simple executable and port forwarding. Which means it would have to serve up all the resources necessary to run the Silverlight player. It would also have to serve up the WCF services and actual music files themselves.

Since the last post a year ago, I’ve made quite a few little tweaks to the player like: regular expression search, random songs, random play order, and a volume control. I also integrated a compact SQLite database for searching so the server-side doesn’t have to do a file system search for each request.

 

WCF REST

So first, how to we host the resources and music files?   This is where WCF REST comes into play. WCF REST allows us to host a service or respond to an HTTP GET request very easily. And since we can host any WCF service in an executable, it makes for a pretty easy standalone option.  The only real caveats here are security and firewall access, which in this demo are non-existent. You will also need to enable port-forwarding in your firewall for whichever port you decide to host the WCF service app. For this sample, I’m using TCP port 8000.

I found in this article by Peter Vucetin on how to serve up any request with a custom MIME type. I took what he had and tweaked it a little so I could serve up content like the silverlight package, html files and a javascript resources.

 

Playing Media

Okay, so the first problem with playing media is… how do we stream audio files from a WCF service to a media element in Silverlight?  Well, it’s as simple as setting the MediaElement source to a method endpoint in the WCF service, which returns a FileStream to the audio file.

For example, our service endpoint lives at: http://localhost:8000/Music for the MusicStreamerService. Our function endpoint is Files(string filename) using the route: /Files/{filename}.  So our source for the media element becomes http://localhost:8000/Music/Files/{filename}. On the server side, I simply set the content type and returned a direct FileStream to the file. This allows the player to behave like a normal web server and stream the file, which allows the Media Element to immediately start reading the data as it comes down. I also tweaked the request path a little bit for the filename parameter, which contains full relative path.  If using the WCF service to host the file, then I enable a base64 encode option that encodes the entire relative path so as not to confuse the WCF request routing.  If I choose to serve it up with a normal web servr, then I can disable the base64 encode and use UrlEncode instead so it treats the request as a normal file system request against a virtual directory.

 

Hosting Policy Files

Cross site scripting… That’s our next problem.  Say your website with silverlight resources are hosted on one server and your music file server is hosted on another. If you host a silverlight app and access a web url across domains, then you’re going to need to serve up the cross site policy files. I wrote a simple file serve function that kicks out a local file when you request it. However I setup a separate service contract and endpoint since the policy files have to reside at the root of the host.

 

Deserializing DataContracts Manually

Silverlight3 doesn’t like consuming WCF REST services yet. So for the functionality of searching and listing the files available, I’m manually retrieving the XML response over asynchronous HTTP calls and then use System.Runtime.Serialization.DataContractSerializer to do handle the data.  On the client side, I copied data contract proxy classes from my service so their serialized definitions match. 

 

Configure the Projects

There are a few settings to be aware of. First, the Silverlight initializes with the location of the WCF endpoints. This is why we have a separate html file for the console app and the web app in the sample. The url is specified using the InitParams parameter on the html object. The standalone sample uses console.html whose initParams is set to: baseuri=http://localhost:8000/Music shown here.

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="814px" height="580px">  
  <param name="source" value="ClientBin/MusicPlayerCustom.xap"/>
  <param name="onError" value="onSilverlightError" />
  <param name="background" value="white" />
  <param name="minRuntimeVersion" value="3.0.40624.0" />
  <param name="autoUpgrade" value="true" />
  <param name="initParams" value="baseuri=http://localhost:8000/Music"; />
  <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0"; style="text-decoration:none">
       <img src="http://go.microsoft.com/fwlink/?LinkId=108181"; alt="Get Microsoft Silverlight" style="border-style:none"/>
  </a>
</object>

So the html file tells Silverlight how to get to the WCF service, now the app.config tells the server side how to get to the music files using these settings. The server side returns a list of files with the configured web root so the Silverlight client knows where to make a request to for the actual file.  Note that this is also where you change the base64 encode flag (false for virtual directory, true for WCF).

<MusicService.Properties.Settings>  
    <setting name="LocalRootPath" serializeAs="String">
        <value>C:\music</value>
    </setting>
    <setting name="WebRootUrl" serializeAs="String">
        <value>http://localhost:8000/Music/Files/</value>;
    </setting>
    <setting name="Base64EncodedFilePath" serializeAs="String">
        <value>True</value>
    </setting>
</MusicService.Properties.Settings>

 

Next, for the database to work properly, I added the provider configuration for System.Data.SQLite. This is mostly just an FYI that it’s there and that I clear all existing ones during runtime. (In case you already have it installed to your system). This will allow SubSonic (the data framework I chose for this project) to load the provider for SQLite when the server performs searches.

<system.data>  
    <DbProviderFactories>
        <add name="SQLite Data Provider" 
              invariant="System.Data.SQLite" 
              description=".Net Framework Data Provider for SQLite" 
              type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite, Version=1.0.65.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139"/>
    </DbProviderFactories>
</system.data>

 

Run the Standalone

To make the standalone version run, follow these steps:

  1. Get the files here  (Released Open Source via Creative Commons. See below for more details)
  2. Open the app.config in the MusicHost project. Check the LocalRootPath setting and set it to the root of your music folder (for example: C:\Music or \\Server\Share.
  3. Also set the WebRootUrl setting to the public location where your music files are available. For example: http://myhost.com/Music  ; or for the standalone: http://localhost:8000/Music/Files/
  4. Run the MusicHost application.
  5. Browse to http://localhost:8000/Music/console.html
  6. For the first run, wait about 20 seconds for the indexer to index your music repository. If you click Random or search for files with no results, it may still be empty.
  7. Select an item in the list to start playing it.

 

Hosting with IIS

I left the functionality in there to host music files from a web server directly.  To see it, edit your Web.Config in the Web project and set WebRootUrl to the IIS virtual directory of your music files. (For example: http://some.web.server/Music/).  Set Base64EncodedFilePath to False so it doesn’t encode the file request. Then run the Web project and browse to Default.html. During the first run, click Reindex and wait for the confirmation dialog, which make take awhile depending on the size and location of your local repository.

 

Enjoy!

 

Creative Commons License

MusicCenter by Nathan Bridgewater is licensed under a Creative Commons Attribution-Noncommercial 3.0 Unported License.