[Source: http://geekswithblogs.net/EltonStoneman]

For a forthcoming celebration, I’ve been working on a jukebox web application:

The client controls the music being played on the server, so its intended for local networks where you want shared control of a central music player. If you can find a use for it, help yourself – it’s on CodePlex here: The People’s Jukebox. If nothing else it’s a straightforward example of a Silverlight 2.0 client talking to WCF REST services, in what’s probably a familiar domain.

The user experience is meant to be zero-guidance. You can try it out on a version where the UI functions but doesn’t play any music: The People’s Jukebox on sixeyed.com.

The initial screen shows what’s currently playing and has a big button for you to select a track. In the next screen you choose either to search for a specific track, browse through the collection, or have the jukebox randomly choose some options for you. From any of those routes, you select a track, confirm your choice and it gets queued:

The queuing is what gives The People’s Jukebox its democratic flavour – if you select a track which is already queued, you increment its vote count. When the current track finishes, the jukebox picks the next track with the most votes. When a track is selected the UI forces you to wait for a few seconds before you can continue, to discourage The People from abusing their power and making the same selection over and over again.

If no one’s choosing tracks, the jukebox will randomly pick one which it hasn’t already played, so it should keep playing until all the songs are played, or the web server is stopped.

Usage

Run the MSI and the jukebox Web site and services will be installed with default configuration options, using the installing user’s “My Music” as the root folder to look for tracks. It will populate the track catalogue from the file system, expecting to find a conventional root/artist/album/track hierarchy:

  • My Music
    • The Beatles
      • Abbey Road
        • 01. Come Together
        • etc.

Open http://localhost/ThePeoplesJukebox/ and hit Start – the music should start playing and you should be able to navigate around your music collection. The jukebox services log everything they do, by default to C:\TEMP\PeoplesJukeboxService.log, so if nothing happens that’s the place to look for clues.

Configuration

The jukebox is configurable through Web.config on the services. Options are documented in comments – the installer sets up the root music directory, and the types of music files in the fileSystemConfiguration element:

<!–Specify the root path for music files, and

the types of files to load. Directory can be

a network path, extensions are comma-separated:–>

<fileSystemCatalogueConfiguration

rootDirectory=x:\My Music

fileExtensions=*.wav,*.mp3

/>

rootDirectory can be local or a UNC path, e.g. \\nas\music.

Track Lists

Track lists are what drives the song selection behaviour; this is also configurable. Three types of tracklist are provided. When the jukebox finishes playing a track, it will query the tracklists in priority order until one returns a track, which will be loaded and played next:

<!– Tracklists to use, and the order in which they are queried.

Specify “chosenByText” to display which tracklist chose the

Now Playing track on the client –>

<trackLists>

<trackList type=PeoplesJukebox.Core.TrackLists.ConfiguredTrackList, PeoplesJukebox.Core priority=1/>

<trackList type=PeoplesJukebox.Core.TrackLists.SelectedTrackList, PeoplesJukebox.Core priority=2 chosenByText=The People/>

<trackList type=PeoplesJukebox.Core.TrackLists.RandomTrackList, PeoplesJukebox.Core priority=3/>

</trackLists>

In default form, the jukebox lets you start off with some specified songs, moving onto the selections of The People, with the jukebox picking a random track if none are queued, so tracklists are queried in the following order:

1. ConfiguredTrackList – reads a list of specific track names from configuration, and returns them in priority order. Will not play a track that’s already been played. Configuration looks like:

<trackConfiguration>

<tracks>

<track trackName=Blank Slate position=1/>

<track trackName=Love Is Noise position=2/>

</tracks>

</trackConfiguration>

Intended to start the evening off with a known selection, when the configured tracks have been played, the list returns null so the jukebox moves on to the next tracklist.

2. SelectedTrackList – hopefully the main provider, queues up track requests as users make a selection. Operates the voting mechanism, so tracks which are selected multiple times move up the queue. If all queued tracks have the same number of votes, they’re played in the order they were first selected. Will allow tracks to be played multiple times, but not sequentially – if you choose a track that’s already queued, you increase its vote count; if you choose the same track again whilst its playing, or after it has been played, it gets queued again. Returns null if no tracks are queued.

3. RandomTrackList – failsafe list, to ensure music is always playing, picks tracks at random from the catalogue. Will not play a track that’s already been played. Allows the administrator to sneak in some favourites by configuring “must-have” tracks. Must-haves will be inserted between random selections, at a configured frequency:

<randomTrackListConfiguration mustHaveTrackFrequency=5>

<mustHaveTracks>

<track trackName=Black Eyed Dog position=1/>

<track trackName=The Magic Position position=2/>

</mustHaveTracks>

</randomTrackListConfiguration>

– meaning every fifth track will be picked from the configured list, then the next four will be random. If no must-haves are specified, or they have all been played, the selection will be random.

Technical Details

Much of the implementation is straightforward. WCF has in-built support for REST, so you can expose a WCF service with a RESTful endpoint like http://localhost/PeoplesJukebox.Services/CatalogueService.svc/artist/2 (to get the list of tracks from artist with ID=2 from the catalogue service), just by flagging the service implementation with the WebGet attribute:

[WebGet(UriTemplate = “artist/{artistId}”, ResponseFormat = WebMessageFormat.Xml)]

public Artist GetArtist(string artistId)

{…

Task-driven workflow implementation in Silverlight is also straightforward, by hosting multiple pages on the application, all of which are invisible except the current page. When the current page completes, it hides itself and loads the next page – handled through events in Page.xaml.cs:

public Page()

{

InitializeComponent();

foreach (PageBase page in Pages)

{

page.Complete += new PageBase.OnCompleteEventHandler(PageComplete);

page.Cancelled += new PageBase.OnCancelledEventHandler(PageCancelled);

}

this.CurrentPage = this.ucStartPage;

}

Silverlight 2.0 doesn’t work so well with non-GET web requests, which is why all the services are GETs where some should really be POSTs.

The only difficult part was playing music files through IIS in a background thread, while continuing to respond to web service and web site requests. If you’re really interested in looking into this, you’ll see the music playing component is also specified in config:

<!– Core configuration – which implementations to use for

media player and catalogue components –>

<peoplesJukeboxConfiguration

mediaPlayer=PeoplesJukebox.Core.Players.FMODPlayer, PeoplesJukebox.Core

mediaCatalogue=PeoplesJukebox.Core.Catalogues.FileSystemCatalogue, PeoplesJukebox.Core

>

There are several (failed) implementations in the source which are not built in the solution (under ..\PeoplesJukebox\PeoplesJukebox.Core\Players). Of the five attempts, all of them had showstopper issues except the excellent FMOD library:

  • DirectX – using the Audio class from the managed wrapper in the DirectX SDK. Redistributable install is massive, which is offputting, but more importantly the Audio.Ending event didn’t fire reliably, so playback would intermittently stop and never resume;
  • Windows Media Player – using the managed WMPLib wrapper in the Windows Media Player SDK, to devolve playing responsibility to Media Player. Didn’t like the extra dependency, or the alarming memory profile. Worse, the player would randomly change state to Stopping in the middle of playback and not resume;
  • Sound – using .NET’s native sound player in System.Media. Fine for playing WAVs, at the cost of having to manage your own threads. Main issue that WAVs are not normalized so tracks will be played at their native volume, which is likely to mean volume changes between songs. Unable to play other media types;
  • Alvas Audio – looked good, but highly unfriendly API. Couldn’t get the thing to play at all;
  • FMOD – highly-featured C++ audio processor which does its own thread management, copes with various media types, plays reliably for extended periods and has an efficient memory model. The library is free for non-commercial use, so The People’s Jukebox has the same restriction. The library is complex, but I’ve used it through FMOD.NET – Rafael Nicoletti’s very nice C# wrapper.

The mediaCatalogue element lets you choose how the catalogue is built. Currently, only a file system catalogue is provided, but extending it (e.g. to use SqueezeCenter – now SqueezeBox Server), is an option.

If you download the jukebox and find any issues using it, let me know through a comment or a CodePlex bug.