![]() |
Platforms, Frameworks & Libraries »
Mobile Development »
Applications
Intermediate
Pocket StreamerBy David EvansStream audio to your Pocket PC with Pocket Streamer. |
C#, Windows, .NET CF, Mobile, .NET 1.1VS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||


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.
Pocket Streamer includes a client application that runs on the Pocket PC, and a server application that runs on the desktop PC.
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.
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:
regasm C:\WMSDK\WMPSDK9\redist\wmppia.dll
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.
To run the application:
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.
Pocket Streamer makes this whole process transparent to the user, all the user has to do is select their tracks and listen.
The Pocket Streamer server runs in the system tray and is responsible for:
Encoder class. Library class. Player 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.
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");
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.
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.
The client application is responsible for;
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.
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.
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;
}
}
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.
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.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 22 May 2004 Editor: Sean Ewington |
Copyright 2004 by David Evans Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |