1. Problem
You need to play music, such as MP3 and WMA files, and videos by using the same Silverlight control.
2. Solution
You can use the MediaElement control within the System.Windows.Controls namespace.
3. How It Works
Within the Silverlight for Windows Phone 7 controls library is the MediaElement
control, which enables us to reproduce music and video available from
remote sites and local resources. Obviously, in the latter case, the
final distribution XAP package will be greater than the XAP package
provided by the remote solution.
This path can be specified in the Source property that accepts the URI path of the file, or by using the SetSource method if the media file is stored locally in isolated storage.
Other than the classic methods to start, stop, and pause the media playing, MediaElement provides some very interesting events. The CurrentStateChanged
event is raised when the state of the media playing is changed. This
event is used when you need to manage buttons to play, stop, and pause
the media. For example, in this recipe, the event is used to change the
Play icon of the ApplicationBarIcon to a Pause icon when the CurrentState property of MediaElement is equal to the MediaElementState.Playing value.
The BufferingProgressChanged
event is raised during the buffering of the media file. Usually when
the file is on a remote site, this event is used to inform the user that
a buffering process is in progress. You can use the event to show the
buffering percentage. The buffering progress can be retrieved from the BufferingProgress property, which returns a double value (from 0.0 to 1.0) that you can multiply by 100 to obtain the progress percentage.
The MediaOpened event is raised when the media is ready to be played (which means the header of the file has been read and the MediaElement properties are set). MediaEnded is raised when the media playing ends.
The AutoPlay property enables you to automatically start playing the media when its value is set to true.
However, the Marketplace certification requirements (Section 6.5)
indicate that you can't interrupt music that is already playing, so it
is better to start with this value set to false and then check whether music is already playing on your device. This can be accomplished by calling the GameHasControl method provided by the MediaPlayer class.
The NaturalDuration, NaturalWidth, and NaturalHeight properties represent the media duration, width, and height (if the media is a video), respectively.
Finally, the Position property represents the media-playing position that you can read and set with a TimeSpan value. In this recipe, we used this property to update a Slider control position so to have visual feedback about the progression of the media playing.
4. The Code
To demonstrate the MediaElement usage, we have created a Silverlight for Windows Phone 7 application that uses a MediaElement, a slider, to represent the progression of the media playing, and a TextBlock control to write the buffering percentage.
In the MainPage.xaml file, you add those three controls within the Content grid. Moreover, you define two ApplicationBarIconButton
controls that enable us to play, pause, and stop the media playing (the
Play button becomes the Pause button after being pressed).
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="tbBuffering" Grid.Row="0" />
<MediaElement AutoPlay="False" x:Name="meVideo" Source="http://mschannel9.vo.msecnd.net/o9/mix/09/wmv/key01.wmv" Grid.Row="1"/>
<Slider x:Name="sPosition" Grid.Row="2"/>
</Grid>
</Grid>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.transport.play.rest.jpg"
Text="Play" x:Name="btnPlay" Click="btnPlay_Click"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.stop.rest.jpg"
Text="Stop" Click="btnStop_Click"/>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
In the MainPage.xaml.cs code file, you start defining two class-level variables representing the media file duration and a DispatcherTimer timer to update the slider control position. In the MainPage
constructor, you add event handlers and other initialization code. As
you already learned in Recipe 3-2, even if you define a name for an
application's icon button, you need to assign its own control's
reference by picking the related index from the Buttons collection of the ApplicationBar class.
public partial class MainPage : PhoneApplicationPage
{
private TimeSpan duration;
private DispatcherTimer timer;
// Constructor
public MainPage()
{
InitializeComponent();
meVideo.BufferingProgressChanged +=
new RoutedEventHandler(meVideo_BufferingProgressChanged);
meVideo.CurrentStateChanged +=
new RoutedEventHandler(meVideo_CurrentStateChanged);
meVideo.MediaOpened += new RoutedEventHandler(meVideo_MediaOpened);
meVideo.MediaEnded += new RoutedEventHandler(meVideo_MediaEnded);
btnPlay = ApplicationBar.Buttons[0] as ApplicationBarIconButton;
timer = new DispatcherTimer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = TimeSpan.FromMilliseconds(500);
sPosition.Value = 0;
}
. . .
The DispatcherTimer object is used to update the progress value of the slider control. Every half second, the Tick event is raised and the slider value property is set with the new Position value provided by the MediaElement class.
void timer_Tick(object sender, EventArgs e)
{
if (meVideo.CurrentState == MediaElementState.Playing)
{
double currentPostition = meVideo.Position.TotalMilliseconds;
double progressPosition = (currentPostition * 100) /
duration.TotalMilliseconds;
sPosition.Value = progressPosition;
}
}
The duration variable is set in the MediaOpened event handler, where you are sure that the media header has been read and media properties are set.
void meVideo_MediaOpened(object sender, RoutedEventArgs e)
{
duration = meVideo.NaturalDuration.TimeSpan;
}
In the BufferingProgressChanged event handler, you are going to update the text block Text property with the media buffering percentage that is retrieved by the BufferingProgress property of MediaElement and formatted with the Format method.
void meVideo_BufferingProgressChanged(object sender, RoutedEventArgs e)
{
tbBuffering.Text = string.Format("Buffering...{0:P}",
meVideo.BufferingProgress);
}
The CurrentStateChanged event is managed by the related event handler in order to change the Play icon button into the Pause icon button when CurrentState is equal to Playing.
void meVideo_CurrentStateChanged(object sender, RoutedEventArgs e)
{
if (meVideo.CurrentState == MediaElementState.Playing)
btnPlay.IconUri = new Uri("/Images/appbar.transport.pause.rest.jpg",
UriKind.Relative);
else
btnPlay.IconUri = new Uri("/Images/appbar.transport.play.rest.jpg",
UriKind.Relative);
}
In the Play and Stop event handlers, in response to the Play and Stop button clicks on the application bar, you call the Play, Pause, and Stop methods provided by the MediaElement class.
private void btnPlay_Click(object sender, EventArgs e)
{
if (meVideo.CurrentState == MediaElementState.Playing)
{
meVideo.Pause();
timer.Stop();
}
else
{
timer.Start();
meVideo.Play();
}
}
private void btnStop_Click(object sender, EventArgs e)
{
meVideo.Stop();
}
Finally, you add some code to reset the media control when the media playing is over:
void meVideo_MediaEnded(object sender, RoutedEventArgs e)
{
meVideo.Position = TimeSpan.Zero;
sPosition.Value = 0;
btnPlay.IconUri = new Uri("/Images/appbar.transport.play.rest.jpg",
UriKind.Relative);
}
5. Usage
From Visual Studio 2010,
select the Windows Phone 7 Emulator as the target output and press
Ctrl+F5 to start the application shown in Figure 1.
Now you can press the Play
button on the application bar and see the buffering information on the
screen. When the buffering is complete, the movie will start and the
Play button will change into the Pause button.