: C# 2008 Programmer

Creating Your Own Media Player

Creating Your Own Media Player

The MediaElement element is a bare-bones control that simply plays back a media file it does not have visual controls for you to pause or advance the media (although you can programmatically do that). In this section, you build a Silverlight application that resembles the YouTube player, allowing you to visually control the playback of the media as well as customize its look and feel. Figure 19-57 shows the end product.


Figure 19-57

Creating the Project

Using Expression Blend 2, create a new Silverlight project and name it MediaPlayer.

Add a Windows Media file (.wmv) file to the project by right-clicking on the project name and selecting Add Existing Item. For this project, use the same file as in the previous example, WindowsMedia.wmv.

Designing the User Interface

The first step is to design the user interface of the media player. Figure 19-58 shows the various controls that you will add to the page. The outline is used to identify the major parts of the player.


Figure 19-58

Figure 19-59 shows the organization and hierarchy of the various controls. Those controls correspond to the controls listed in Figure 19-58.


Figure 19-59

The most delicate part of the media player is the slider used to indicate the progress of the media playback. As shown in Figure 19-60, the slider (canvasProgress) consists of two Rectangle elements and an Ellipse element. The first Rectangle element (rectProgressWell) represents the entire duration of the movie. This control also forms the path that the marker (ellMarker, an Ellipse element) slides on. The second Rectangle control (rectDownloadProgress) is used to indicate the percentage of the media downloaded from the remote server. The lower part of Figure 19-60 shows this control in action (partially filled).


Figure 19-60

Here's the complete XAML code for the media player:

<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="472" Height="376"
Background="#FFD6D4C8">
<MediaElement
x:Name="MediaElement1"
Width="466" Height="340"
Stretch="Fill"
Canvas.Left="3" Canvas.Top="3"
AutoPlay="false" Source="WindowsMedia.wmv"/>
<Canvas
Width="24" Height="24"
Canvas.Left="5" Canvas.Top="348"
x:Name="btnPlayPause">
<Canvas
Width="24" Height="24"
x:Name="canvasPlay">
<Rectangle
Width="24" Height="24" Fill="#FFFFFFFF"
Stroke="#FF000000"
RadiusX="3" RadiusY="3"
StrokeThickness="2"/>
<Polygon
Points="8,5 8,19 18,13"
StrokeThickness="5" Fill="Red"
Width="24" Height="24"/>
</Canvas>
<Canvas
Width="24" Height="24"
x:Name="canvasPause"
MouseEnter="PauseButtonMouseEnter"
MouseLeave="PauseButtonMouseLeave"
Opacity="0">
<Rectangle
Width="24" Height="24"
Fill="#FFFFFFFF"
Stroke="#FF000000"
RadiusX="3" RadiusY="3"
StrokeThickness="2"/>
<Rectangle
Width="6" Height="14"
Fill="#FF141414"
Stroke="#FF000000"
Canvas.Left="13" Canvas.Top="5"
/>
<Rectangle
Width="6" Height="14"
Fill="#FF141414"
Stroke="#FF000000"
Canvas.Left="5" Canvas.Top="5"
x:Name="rectPauseBar2"/>
</Canvas>
</Canvas>
<Canvas
Width="255" Height="27"
Canvas.Left="36" Canvas.Top="346"
x:Name="canvasProgress">
<Rectangle
Width="244" Height="8"
Fill="#FF414141" Stroke="#FF000000"
Canvas.Top="10"
x:Name="rectProgressWell"
Canvas.Left="8.5"/>
<Rectangle
Width="3" Height="8"
Fill="#FF9D0808" Stroke="#FF000000"
Canvas.Top="10"
x:Name="rectDownloadProgress"
StrokeThickness="0" Canvas.Left="8.5"/>
<Ellipse
Width="16" Height="16"
Stroke="#FF000000"
Canvas.Top="6" Canvas.Left="0"
x:Name="ellMarker">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="#FF000000" Offset="0"/>
<GradientStop Color="#FFF6F6EC" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Canvas>
<TextBlock
Width="148" Height="21"
Text="TextBlock" TextWrapping="Wrap"
Canvas.Left="321" Canvas.Top="348"
x:Name="TextBlock"/>
</Canvas>

Wiring All the Controls

With the UI created and ready for coding, you're ready to wire up all the controls so that they will function as one. You'll define the event handlers in the following table.

Event Handler Description
DownloadProgressChanged() Continuously invoked when the MediaElement control downloads the media from the remote server. It is used to update the red progress bar indicating the progress of the download.
EllMarkerMouseDown() Invoked when the user clicks on the marker using the left mouse button.
EllMarkerMouseUp() Invoked when the user releases the left mouse button.
MediaPlayerMouseMove() Invoked when the mouse moves across the Silverlight page.
MediaPlayerMouseLeave() Invoked when the mouse leaves the Silverlight page.
MediaEnded() Invoked when the media has finished playing. The media will be reset to its starting position (so is the marker).
PlayPauseButtonUp() Invoked when the user clicks on the Play/Pause button.

First, assign the various event handlers to the elements as shown in the following highlighted code:

<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="472" Height="376"
Background="#FFD6D4C8"
x:Name="Page"
MouseMove="MediaPlayerMouseMove"
MouseLeave="MediaPlayerMouseLeave"
MouseLeftButtonUp="EllMarkerMouseUp">
<MediaElement
x:Name="MediaElement1"
Width="466" Height="340"
Stretch="Fill" Canvas.Left="3" Canvas.Top="3"
AutoPlay="false" Source="WindowsMedia.wmv"
MediaEnded="MediaEnded"
DownloadProgressChanged="DownloadProgressChanged"/>
<Canvas
Width="24" Height="24"
Canvas.Left="5" Canvas.Top="348"

MouseLeftButtonUp="PlayPauseButtonUp">
<CanvasWidth="24" Height="24">
<Rectangle
Width="24" Height="24"
Fill = "#FFFFFFFF" Stroke="#FF000000"
RadiusX="3" RadiusY="3"
x:Name="RectPlay" StrokeThickness="2"/>
<Polygon
Points="8,5 8,19 18,13" StrokeThickness="5"
Fill="Red" Width="24" Height="24"/>
</Canvas>
<Canvas
Width="24" Height="24"
x:Name="canvasPause"
MouseEnter="PauseButtonMouseEnter"
MouseLeave="PauseButtonMouseLeave"
Opacity="0">
<Rectangle
Width="24" Height="24" Fill="#FFFFFFFF"
Stroke="#FF000000" RadiusX="3" RadiusY="3"
StrokeThickness="2"/>
<Rectangle
Width="6" Height="14"
Fill="#FF141414" Stroke="#FF000000"
Canvas.Left="13" Canvas.Top="5"
x:Name="rectPauseBar1"/>
<Rectangle
Width="6" Height="14"
Fill="#FF141414" Stroke="#FF000000"
Canvas.Left="5" Canvas.Top="5"
x:Name="rectPauseBar2"/>
</Canvas>
</Canvas>
<Canvas
Width="255" Height="27"
Canvas.Left="36" Canvas.Top="346"
x:Name="canvasProgress">
<Rectangle
Width="244" Height="8"
Fill="#FF414141" Stroke="#FF000000"
Canvas.Top="10"
x:Name="rectProgressWell" Canvas.Left="8.5"/>
<Rectangle
Width="3" Height="8"
Fill="#FF9D0808" Stroke="#FF000000"
Canvas.Top="10"
x:Name="rectDownloadProgress"
StrokeThickness="0" Canvas.Left="8.5"/>
<Ellipse
Width="16" Height="16"
Stroke="#FF000000"
Canvas.Top="6" Canvas.Left="0"
x:Name="ellMarker"
MouseLeftButtonDown="EllMarkerMouseDown"
MouseLeftButtonUp="EllMarkerMouseUp">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="#FF000000" Offset="0"/>
<GradientStop Color="#FFF6F6EC" Offset="1"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Canvas>
<TextBlock
Width="148" Height="21"
Text="TextBlock" TextWrapping="Wrap"
Canvas.Left="321" Canvas.Top="348"
x:Name="TextBlock"/>
</Canvas>

Now double-click on the Page.xaml.js file in the Project window to open it. Declare the following global variables at the top of the file:

//---global variables---
var playing = false;
var markerClicked = false;
var duration=0;
var intervalID;
//---all the major elements on the page---
var ellMarker;
var MediaElement1;
var textblock;
var rectProgressWell;
var rectDownloadProgress;
//----------------------------------------

When the page is loaded, get a reference to all the major controls on the page:

MediaPlayer.Page.prototype = {
handleLoad: function(control, userContext, rootElement) {
this.control = control;
//---get a reference to all the major controls on the page---
MediaElement1 = rootElement.findName("MediaElement1");
ellMarker = rootElement.findName("ellMarker");
textblock = rootElement.findName("TextBlock");
rectProgressWell = rootElement.findName("rectProgressWell");
rectDownloadProgress =
rootElement.findName("rectDownloadProgress");
textblock = rootElement.findName("TextBlock");
//-----------------------------------------------------------
// Sample event hookup:
rootElement.addEventListener("MouseLeftButtonDown",
Silverlight.createDelegate(this, this.handleMouseDown));
},
// Sample event handler
handleMouseDown: function(sender, eventArgs) {
// The following line of code shows how to find an element by
// name and call a method on it.
// this.control.content.findName("Timeline1").Begin();
}
}

Creating the Helper Functions

Two helper functions ConvertToTimeSpan() and DisplayCurrentPlayBack() need to be defined.

The ConvertToTimeSpan() function converts value in seconds to the TimeSpan format of hh:mm:ss. For example, 61 seconds converts to 00:01:01. You need this function because the Position property of the MediaElement control accepts only values of the TimeSpan type. The ConvertToTimeSpan() function is defined as follows:

//---convert time in seconds to "hh:mm:ss"---
function ConvertToTimeSpan(timeinseconds) {
if (timeinseconds<0) {
return ("00:00:00");
} else if (timeinseconds>60) {
return ("00:00:" + Math.floor(timeinseconds));
} else if (timeinseconds<3600) {
var mins = Math.floor(timeinseconds / 60);
var seconds = Math.floor(timeinseconds - (mins * 60));
return ("00:" + mins + ":" + seconds);
} else {
var hrs = Math.floor(timeinseconds / 3600);
var mins = timeinseconds - (hrs * 3600)
var seconds = Math.floor(timeinseconds - (hrs * 3600) - (mins * 60));
return (hrs + mins + ":" + seconds);
}
}

The DisplayCurrentPlayBack() function is used to display the current status of the media playback. It displays the elapsed time versus the total time of the media. For example, if the media (total duration two minutes) is into its 30th second, the DisplayCurrentPlayBack() function displays 00:00:30 / 00:02:00. In addition, the function is also responsible for synchronizing the marker as the media is played. To ensure that the status of the playback is updated constantly, you call DisplayCurrentPlayBack() repeatedly, using the setInterval() JavaScript function (more on this later). The DisplayCurrentPlayBack() function is defined as follows:

//---shows the current playback -- marker and position---
function DisplayCurrentPlayBack() {
//---find duration of movie---
if (duration==0)
duration =
Math.round(MediaElement1.NaturalDuration.Seconds * 100)
/ 100;
//---find current position---
var position = MediaElement1.Position.Seconds;
//---move the marker---
ellMarker["Canvas.Left"] =
Math.round((position / duration) *
rectProgressWell.width);
//---format - elapsed time/total time---
var str = ConvertToTimeSpan(position) + "/" +
ConvertToTimeSpan(duration);
textblock.Text = str;
}

Defining the Event Handlers

Finally you define the various event handlers.

The DownloadProgressChanged event handler is continuously fired when the MediaElement control is downloading the media from the remote server. In this event handler, you first obtain the progress value (from 0 to 1) and then display the downloaded percentage on the TextBlock control. In addition, you adjust the width of the rectProgressWell control so that as the media is downloaded, its width expands (see Figure 19-61). Here's the code:

//---fired while the movie is being downloaded---
function DownloadProgressChanged(sender, eventArgs) {
//---get the progress value from 0 to 1---
var progress = MediaElement1.DownloadProgress;
//---display the download in percentage---
textblock.Text = Math.round(progress*100).toString() + "%";
//---adjust the width of theprogress bar---
var progressWidth = progress* rectProgressWell.width;
rectDownloadProgress.width =Math.round(progressWidth);
}


Figure 19-61

The EllMarkerDown event handler is fired when the user clicks on the marker (the Ellipse element). Here, you set the markerClicked variable to true to indicate that the marker has been clicked:

//---marker is clicked---
function EllMarkerMouseDown(sender, eventArgs) {
markerClicked = true;
}

When the user releases the mouse button, the EllMarkerMouseUp event handler is fired. You first need to check if the user releases the button on the main canvas itself or on the marker. If the marker was previously clicked, you need to move the marker to the current location of the mouse and set the media to the new position. The new position of the movie is determined by multiplying the duration of the media and the ratio of the position of the marker with respect to the width of the progress well. Here's the code:

//---marker is released---
function EllMarkerMouseUp(sender, eventArgs) {
//---only execute this function if the user is moving the marker---
if (markerClicked) {
markerClicked=false;
//---find duration of movie---
duration =
Math.round(MediaElement1.NaturalDuration.Seconds * 100) / 100;
//---get the position of the marker w.r.t. to the Well---
position =
((ellMarker["Canvas.Left"]) / rectProgressWell.width)* duration;
//---get integer part---
position = Math.floor(position);
//---end of the media---
if (ellMarker["Canvas.Left"]==rectProgressWell.width) {
//---move the movie to the last frame---
MediaElement1.Position = ConvertToTimeSpan(duration);
} else {
//---move the movie to the new position---
MediaElement1.Position = ConvertToTimeSpan(position);
}
}
}

The MediaPlayerMouseMove event handler is continuously fired when the mouse moves over the page. You need to determine if the marker is clicked when the mouse is moving. If it is, that means that the user is moving the marker, and you need to reposition the marker. Here's the code:

//---mouse moves inside the Silverlight media player control---
function MediaPlayerMouseMove(sender, eventArgs) {
//---user clicks marker and drags it---
if (markerClicked) {
//---find duration of movie---
if (duration==0)
duration =
Math.round(MediaElement1.NaturalDuration.Seconds * 100) /
100;
clearlnterval(intervallD);
//---get the position of the mouse with respect to the progress Well---
var pt = eventArgs.getPosition(rectProgressWell);
//---marker not allowed to stray outside the well---
if (pt.x > 0 && pt.x < rectProgressWell.width) {
//---moves the marker---
ellMarker["Canvas.Left"] = pt.x;
//---display the new time---
textblock.Text =
ConvertToTimeSpan((pt.x / rectProgressWell.width) * duration).toString();
} else if (pt.x <= 0) //---move to the beginning---
{
//---moves the marker---
ellMarker["Canvas.Left"] = 0;
//---display the new time---
textblock.Text = "00:00:00";
} else if (pt.x >= rectProgressWell.width) //---move to the end---
{
//---moves the marker---
ellMarker["Canvas.Left"] = rectProgressWell.width;
//---display the new time---
textblock.Text = ConvertToTimeSpan(duration);
}
if (playing)
intervalID = window.setInterval("DisplayCurrentPlayBack()", 500)
}
}

The MediaPlayerMouseLeave event handler is fired when the mouse leaves the Silverlight page. In this case, you set the markerClicked variable to false:

//---mouse leaves the entire Silverlight media player control
function MediaPlayerMouseLeave(sender, eventArgs) {
markerClicked=false;
}

The MediaEnded event handler is fired when the media has finished playing. You have to make the Play button visible again and hide the Pause button. In addition, you have to move the marker to the beginning and reset the media to the beginning. Here's the code:

//---movie has finished playing---
function MediaEnded(sender, eventArgs) {
var btnPlay = sender.findName("canvasPlay");
var btnPause = sender.findName("canvasPause");
playing = false;
clearlnterval(intervallD); //---clear the progress updating---
btnPlay.opacity = 1;//---show the Play button---
btnPause.opacity = 0;//---hide the Pause button---
//---move the marker to the beginning---
ellMarker["Canvas.Left"] = -2;
MediaElement1.Position="00:00:00"; //---reset the movie position---
}

The PlayPauseButtonUp button is fired when the user clicks on the Play/Pause button and releases the mouse. When the media has started playing, you use the setlnterval() JavaScript function to display the media progress every half second:

function PlayPauseButtonUp(sender, eventArgs) {
var btnPlay = sender.findName("canvasPlay");
var btnPause = sender.findName("canvasPause");
//---if currently playing and now going to pause---
if (playing==true) {
MediaElement1.pause();//---pause the movie---
clearlnterval(intervalID); //---stop updating the marker---
playing = false;
btnPlay.opacity = 1;//---show the Play button---
btnPause.opacity = 0;//---hide the Pause button---
} else {
MediaElement1.play();//---play the movie---
playing = true;
btnPlay.opacity = 0;//---hide the Play button---
btnPause.opacity = 1;//---show the Pause button---
//---update the progress of the movie---
intervalID =
window.setInterval("DisplayCurrentPlayBack()", 500);
}
}

That's it! Press F5 in Expression Blend 2, and you should be able to use the new media player (see Figure 19-62)!


Figure 19-62


: 1.063. /Cache: 3 / 1