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

Pocket Streamer

0.00/5 (No votes)
22 May 2004 9  
Stream audio to your Pocket PC with Pocket Streamer.

Sample Image - display_1.jpg

Sample Image - display_2.jpg

Introduction

This application allows you to browse the music library held on your desktop PC from your Pocket PC, select an artist, album, track or radio station, stream it over a network connection, and listen to it on your Pocket PC. Using this application, you can carry your entire music collection around with you without the hassle of downloading selected tracks to an expansion card. You can also listen to many Internet radio stations without being tied to your PC. As long as you have a network connection to your desktop, you can listen to your music library wherever you are, including theoretically from the other side of the world by using a WiFi hotspot.

System Requirements

Desktop:

  • Windows Media Player 9. You can download this from here.
  • Windows Media Encoder 9. You can download this from here.
  • .NET Framework. You can download this from here.

Pocket PC

  • Pocket PC 2000, 2002 or 2003.
  • .NET Compact Framework. You can download this from here.

Installation

Pocket Streamer includes a client application that runs on the Pocket PC, and a server application that runs on the desktop PC.

Setup.exe

A Setup.exe is provided that installs the client and server applications. Before you run Setup.exe, make sure Windows Media Player and Windows Media Encoder are installed on the desktop PC.

Source Code

You can also download a Visual Studio .NET 2003 solution that contains the source code for the client and server applications. Before you can compile and run the server application, you will need to install the Windows Media Player SDK & the Windows Media Encoder SDK. These SDKs provide interfaces that allow you to automate Windows Media Player and Windows Media Encoder.

You can download the Windows Media Player SDK from here. You can download the Windows Media Encoder SDK from here.

Once you have downloaded and installed these SDKs, you must register the "Primary Interop Assemblies" which provide a COM interop layer. These assemblies come as part of the Windows Media Player SDK. Follow these instructions for registering the assemblies:

  • Register the Windows Media Player interop assembly by using the command line tool regasm.
    regasm C:\WMSDK\WMPSDK9\redist\wmppia.dll
  • Install the Windows Media Player interop assembly into the Global Assembly Cache using the command line tool gacutil.
    Gacutil /i C:\WMSDK\WMPSDK9\redist\wmppia.dll

Note: The Setup.exe performs these registration steps for you.

Once you have installed the SDKs, you should be able to open the solution and build the projects.

Note: if you need to update the web references in the client application, be sure to edit the URL to point to your desktop PC. You will also have to have the server application running while you are updating the references.

Running the application

To run the application:

  1. Start the server on the desktop PC.
  2. Connect your Pocket PC to your network in whichever way you would normally do this (you must have IP connectivity between the Pocket PC and the desktop PC).
  3. Start the client application and click on the Tools-Settings menu item. Enter the IP address of the desktop PC and close the form.
  4. Click on the Library menu item and click on the arrow toolbar button. This will download your media library to your Pocket PC.
  5. Select a media item from the tree by clicking and holding. In the pop-up menu, select Play. This should start streaming the selected item to your Pocket PC.

Background

Pocket Streamer uses the Windows Media suite of technologies to allow you to browse your media library and listen to selected tracks on your Pocket PC. Three separate Windows Media applications are used to support Pocket Streamer.

  1. Windows Media Player 9 is used to provide access to the media library stored on your desktop PC and to play selected media items. Windows Media Player not only plays media items such as MP3s, it also allows you to manage those media items by organizing them into playlists and albums. Pocket Streamer retrieves the details of the playlists etc. from Media Player and displays them on your Pocket PC. Once you have selected a media item to play, Pocket Streamer then instructs Windows Media Player to play the item through the desktop PC's sound card.
  2. Windows Media Encoder provides basic facilities to encode an audio or video stream. Various options are provided by Media Encoder, one of these is to stream the output of the sound card attached to the PC. Clients can then connect using Windows Media Player and listen to the stream. Pocket Streamer automates Media Encoder and instructs it to stream the output from the sound card over HTTP.
  3. Windows Media Player Pocket PC Edition is used to connect to the stream provided by Media Encoder. The Pocket Streamer client does not play the selected media item itself, instead it starts the pocket Windows Media Player with a command line parameter consisting of the URL to connect to. Windows Media Player then plays the stream at the selected URL.

Pocket Streamer makes this whole process transparent to the user, all the user has to do is select their tracks and listen.

Using the code

Server

The Pocket Streamer server runs in the system tray and is responsible for:

  • Starting and stopping an encoding session. Provided by the Encoder class.
  • Querying the Windows Media Library. Provided by the Library class.
  • Playing a media item. Provided by the Player class.
  • Providing an interface for Pocket PC clients to call.

Encoder class

The Encoder class provides two methods to automate the Windows Media Encoder application: Start() and Stop().

/// <SUMMARY>
/// Starts the media encoder.
/// </SUMMARY>
public void Start()
{
  //Check whether the encoder is currently running
  if (WMP.Encoder.RunState == WMENC_ENCODER_STATE.WMENC_ENCODER_STOPPED)
  {
    try
    {
      // Specify the source for the input stream.
      IWMEncSourceGroupCollection SrcGrpColl = 
        WMP.Encoder.SourceGroupCollection;
      IWMEncSourceGroup SrcGrp = SrcGrpColl.Add("SG_1");
      IWMEncSource SrcAud = SrcGrp.AddSource(WMENC_SOURCE_TYPE.WMENC_AUDIO);
      SrcAud.SetInput("Default_Audio_Device", "Device", "");
      SrcGrp.set_Profile(System.Windows.Forms.Application.StartupPath 
                                             + @"\pocketprofile.prx");
      // Create a broadcast.
      IWMEncBroadcast BrdCst = WMP.Encoder.Broadcast;
      BrdCst.set_PortNumber (WMENC_BROADCAST_PROTOCOL.WMENC_PROTOCOL_HTTP, 8080);
      //Start the encoder
      WMP.Encoder.Start();
    }
    catch (Exception ex)
    {
      throw new ApplicationException("Failed to start the encoder.", ex);
    }
  }
}

/// <SUMMARY>
/// Stops the media encoder.
/// </SUMMARY>
public void Stop()
{
  try
  {
    WMP.Encoder.Stop();
  }
  catch (Exception ex)
  {
    throw new ApplicationException("Failed to stop the encoder.", ex);
  }
}

This code is pretty much taken straight from an example in the SDK. One point to note is the use of the pocketprofile.prx file. This is a Windows Media Encoder file that contains all of the properties and values for an encoding session. Windows Media Encoder comes with a tool that allows you to create and edit these files. This was the quickest way of creating a Media Encoder profile that allowed me to stream to a Pocket PC. I tried and failed to set the encoder session in code, so this was an easy way out. One of the problems with this approach is that the application is limited to the bitrate specified in the file. If you want to provide the ability to vary the bitrate for different circumstances, you would have to set up the encoding session parameters in code rather than from a file.

Library class

The Library class has various methods for retrieving details about the media items held in the Windows Media Player Library. Methods are provided for retrieving all of the audio items held by the library, all of the playlists, and all the radio stations. Each of these methods return the library data as a string of XML. The Windows Media Player SDK provides various methods for querying the library. The following code fragment demonstrates how a list of radio stations is obtained:

IWMPPlaylist stations = 
  WMP.Player.mediaCollection.getByAttribute("MediaType", "RADIO");

Player class

The Player class provides the standard Media Player controls such as start, stop, next & previous track, and pause. It also provides methods to play an album, a single track, all tracks by an artist, a playlist, and a radio station. The following code fragment demonstrates how to set Media Player to play all tracks by an artist:

IWMPPlaylist playlist = 
  WMP.Player.mediaCollection.getByAuthor("artist_name");
WMP.Player.currentPlaylist = playlist;
WMP.Player.Ctlcontrols.play();

It is not a requirement of Windows Media Encoder to play the selected media items through Windows Media Player. Windows Media Encoder can stream files as well as the output from audio devices. However, I chose to use Media Player because it was easier to tell Media Player to play a whole album and not worry about which audio files are required, where they are located, what order to play them in, when to load up the next track, what the current playlist is, or what the current track is. By letting Media Player handle all this, it becomes a simple task to play an album. The disadvantage of this approach is that you will hear the output through your desktop speakers as well as your Pocket PC.

Main Form

The main form is hidden when the application runs. A system tray icon is provided with a single "Exit" menu item. The form's main responsibility is to set up the .NET Remoting configuration. Remoting is used to provide an interface that can be called across a network. Each of the three classes mentioned above derive off MarshalByRefObject and can therefore be called across process boundaries. The main form configures .NET Remoting to expose these classes as SingleCall classes with a SOAP formatter using an HTTP channel. Selecting HTTP as the channel and SOAP as the formatter is crucial as it allows the Pocket PC client to treat the remote object as a Web Service. The .NET Compact Framework does not support Remoting. However, Web Services are supported, so this is an easy way of exposing remote objects to .NET Compact Framework clients.

Finally, the main form selects port 9000 as the HTTP port.

Client

The client application is responsible for;

  • Displaying the media library in a selectable tree.
  • Providing controls to start playing, stop playing, pause, and navigate to the next & previous tracks.
  • Displaying the current playlist and current album cover.
  • Starting the Pocket Media Player which in turn connects to the stream.

When first thinking about this application, I was desperate to host Windows Media Player in my application and control the player directly. Unfortunately, I could not find an appropriate method for doing this. The Pocket Media Player does not provide an ActiveX style control that can be embedded in a Pocket PC application, and in any case, the .NET Compact Framework does not support COM Interop. I did play with the Windows Media Player Control for Pocket Internet Explorer for a while. My idea was to embed the control in an HTML page which I would host in my application. However, I had a similar problem of finding a suitable HTML viewer control. I had a look at the OpenNetCF.org HTMLViewer control which showed promise, but I then discovered that the Media Player Control for PIE doesn't work with Windows Mobile 2003, and in fact has been pulled from the Microsoft site. What I was left with was a crude method of starting pocket Media Player from a call to CreateProcess and passing in a parameter of the URL to stream from.

This approach works but has a few annoying side effects. The first is that when I start Windows Media Player, it comes to the foreground covering my application. I can bring my application back to the foreground but the effect is annoying. Second and more important is the way Windows Media Player buffers the incoming stream. This buffering causes a delay when pausing, stopping and navigating to the next or previous track. For example, if you press Pause in my application, the desktop Media Player is immediately paused, however, because the Pocket Media Player will play out its buffer, the music does not actually pause for a few seconds. I can see no way of getting round this problem short of killing the pocket player process every time someone pauses etc. This would work but it is pretty brutal.

The client application consists of three forms, the main form, the library, and a settings form.

Main Form

The main form provides the standard controls you would expect in a media player, play, pause etc. These buttons simply call their respective remote methods available from the server. As well as controlling the audio playback, the main form also displays the current playlist along with the album cover for the current album if available.

The client application is largely single threaded. This isn't ideal but I did not have time to make the application fully multithreaded. A thread is used when retrieving the current playlist and album cover. Retrieving the album cover can take a few seconds, so it was essential to run this in the context of its own thread. The thread is started by using the standard ThreadStart delegate. Because the .NET Compact Framework does not support passing parameters to the Contol.Invoke method, the ControlInvoker class described in the framework quickstarts was used. See this quickstart tutorial for more details.

Library

The library form provides a tabbed dialog with three tabs for audio, playlists and radio. Each tab hosts a tree control which displays the media items retrieved from the library. A toolbar button is provided that downloads the media library from the server as an XML string. The XML is then parsed and the trees populated. Once this has been done, the XML document is saved to a local file. Each time the library form loads, it checks to see if the local file exists, and if it does, it populates the trees from this file rather than from the server.

The code that populates the trees is fairly straightforward. The XML document is examined a node at a time. Each node is given a name by the server which is one of artist, album, track, playlist or radiostation. The artist, album and track nodes are added to the audio tree, whilst the playlist and radiostation nodes are added to the playlist and radiostation trees.

private void AddNode(XmlTextReader reader)
{
    switch (reader.Name)
    {
        case "artist":
            artistNode = audioTreeView.Nodes.Add(reader.GetAttribute("name"));
            if (artistNode.Text.Length == 0)
                artistNode.Text = "Unknown";
            artistNode.Tag = "artist";
            break;

        case "album":
            albumNode = artistNode.Nodes.Add(reader.GetAttribute("title"));
            if (albumNode.Text.Length == 0)
                albumNode.Text = "Unknown";
            albumNode.Tag = "album";
            break;

        case "track":
            TreeNode track = albumNode.Nodes.Add(reader.GetAttribute("title"));
            if (track.Text.Length == 0)
                track.Text = "Unknown";
            track.Tag = reader.GetAttribute("ID");
            break;

        case "playlist":
            playlistNode = playlistsTreeView.Nodes.Add(reader.GetAttribute("name"));
            if (playlistNode.Text.Length == 0)
                playlistNode.Text = "Unknown";
            playlistNode.Tag = "playlist";
            break;

        case "radiostation":
            radioStationNode = radioTreeView.Nodes.Add(reader.GetAttribute("name"));
            if (radioStationNode.Text.Length == 0)
                radioStationNode.Text = "Unknown";
            radioStationNode.Tag = "radiostation";
            break;
    }
}

Settings

The settings form allows the user to enter the IP address of the server. The IP address is saved to a local configuration file by using the example in the CodeProject article by Page Brooks which describes an implementation of the standard .NET app settings framework, which is not part of the .NET Compact Framework. See "AppSettings implementation for Compact Framework" for more info.

Improvements

I'm sure there are a whole load of improvements that could be made, but here is a list of some of the major ones.

  • A "Queue it up" option. This would allow you to select a media item and add it to the end of the current playlist rather than replacing the current playlist with the new selection.
  • A more responsive UI with better use of threads.
  • Streaming video. This should be fairly straightforward, the major change to the application would be to stream from files rather than from devices. Pocket Streamer currently works by streaming the output of the sound card. To stream video, you could stream the output of the monitor, however, you would get the whole desktop streamed to the Pocket PC rather than just the video.

References

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