How-to: Creating a complete Audio/Video player

As a conclusion to the last chapters on playing audio and video, I decided to create a more complete sample, where we take advantage of the fact that the MediaPlayer/MediaElement classes can handle both audio and video.

I will take the concepts used in the articles about playing audio and video and combine them with several controls which we have already discussed previously in this article, and turn it all into a WPF Media Player. The result will look something like this:

Our WPF Media Player, playing an MP3 file

But that's just when it plays audio/MP3 files. Once a video is loaded, the interface automatically expands to show the video content inside the window:

Our WPF Media Player, playing a video file

Let me tell you a bit about how this thing was built. In the end, you can of course see the entire source code, ready for you to play with.

The interface

The interface has been split into three vertical areas: The top, where the toolbar is located, the middle, where the video (if a video file is loaded) is shown, and the bottom, where we find a status bar, complete with a time status, a Slider for seeing and controlling progress and a ProgressBar for showing the volume. All of the controls used here have been explained previously in the tutorial, so we won't focus too much on that.

Notice the use of WPF commands, instead of click events for the buttons. This allows us to easily re-use the functionality in case we want to add e.g. a main menu or a context menu with some of the same functionality. It also makes it easier for us to toggle the functionality on and off, depending on the current state of the player.

Also notice that we have set the MediaElement Stretch property to None, and the Window SizeToContentMode to WidthAndHeight. This is what keeps the window to the minimum size needed to show the interface as well as the video, if one is playing.

For showing the Volume, we've used a ProgressBar control in the lower, right corner. This doesn't currently let the user control the volume, but merely reflects the Volume property on the MediaElement control, through a classic data binding. We've implemented a small but neat trick for letting the user control the volume anyway though - more on that below.

The code

In Code-behind, we re-use several techniques already used in our previous examples. For instance, we initiate a DispatcherTimer and let it tick every second, to show the current playback progress in the interface. In the Tick event of the timer, we update Slider control, by settingMinimum, Maximum and current Value according to the file being played, and by hooking up to the ValueChanged event on the slider, we use that to update the label showing the current playback progress in hours, minutes and seconds.

The Slider control also allows the user to skip to another part of the file, simply by dragging the "thumb" to another location. We handle this by implementing events for DragStarted and DragCompleted - the first one to set a variable ( userIsDraggingSlider) that tells the timer not to update the Slider while we drag, and the second one to skip to the designated part when the user releases the mouse button.

There are CanExecute and Executed handlers for the four commands we use and especially the ones for Pause and Stop are interesting. Since we can't get a current state from the MediaElement control, we have to keep track of the current state ourselves. This is done with a local variable called mediaPlayerIsPlaying, which we regularly check to see if the Pause and Stop buttons should be enabled.

The last little detail you should notice is the Grid_MouseWheel event. The main Grid covers the entire window, so by subscribing to this event, we get notified when the user scrolls the wheel. When that happens, as a little gimmick, we turn the volume up or down, depending on the direction (we get that by looking at the Delta property, which is negative when scrolling down and positive when scrolling up). This is immediately reflected in the user interface, where a ProgressBar control is bound to the Volume property of the MediaElement.

The complete source code

With all the theory behind the example described, here's the complete source code:

<Window x:Class="WpfTutorialSamples.Audio_and_Video.AudioVideoPlayerCompleteSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF Media Player" Height="300" Width="300"
        MinWidth="300" SizeToContent="WidthAndHeight">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Open" CanExecute="Open_CanExecute" Executed="Open_Executed" />
        <CommandBinding Command="MediaCommands.Play" CanExecute="Play_CanExecute" Executed="Play_Executed" />
        <CommandBinding Command="MediaCommands.Pause" CanExecute="Pause_CanExecute" Executed="Pause_Executed" />
        <CommandBinding Command="MediaCommands.Stop" CanExecute="Stop_CanExecute" Executed="Stop_Executed" />
    </Window.CommandBindings>
    <Grid MouseWheel="Grid_MouseWheel">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ToolBar>
            <Button Command="ApplicationCommands.Open">
                <Image Source="/WpfTutorialSamples;component/Images/folder.png" />
            </Button>
            <Separator />
            <Button Command="MediaCommands.Play">
                <Image Source="/WpfTutorialSamples;component/Images/control_play_blue.png" />
            </Button>
            <Button Command="MediaCommands.Pause">
                <Image Source="/WpfTutorialSamples;component/Images/control_pause_blue.png" />
            </Button>
            <Button Command="MediaCommands.Stop">
                <Image Source="/WpfTutorialSamples;component/Images/control_stop_blue.png" />
            </Button>
        </ToolBar>

        <MediaElement Name="mePlayer" Grid.Row="1" LoadedBehavior="Manual" Stretch="None" />

        <StatusBar Grid.Row="2">
            <StatusBar.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                    </Grid>
                </ItemsPanelTemplate>
            </StatusBar.ItemsPanel>
            <StatusBarItem>
                <TextBlock Name="lblProgressStatus">00:00:00</TextBlock>
            </StatusBarItem>
            <StatusBarItem Grid.Column="1" HorizontalContentAlignment="Stretch">
                <Slider Name="sliProgress" Thumb.DragStarted="sliProgress_DragStarted"  Thumb.DragCompleted="sliProgress_DragCompleted" ValueChanged="sliProgress_ValueChanged" />
            </StatusBarItem>
            <StatusBarItem Grid.Column="2">
                <ProgressBar Name="pbVolume" Width="50" Height="12" Maximum="1" Value="{Binding ElementName=mePlayer, Path=Volume}" />
            </StatusBarItem>
        </StatusBar>
    </Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Threading;
using Microsoft.Win32;

namespace WpfTutorialSamples.Audio_and_Video
{
	public partial class AudioVideoPlayerCompleteSample : Window
	{
		private bool mediaPlayerIsPlaying = false;
		private bool userIsDraggingSlider = false;

		public AudioVideoPlayerCompleteSample()
		{
			InitializeComponent();

			DispatcherTimer timer = new DispatcherTimer();
			timer.Interval = TimeSpan.FromSeconds(1);
			timer.Tick += timer_Tick;
			timer.Start();
		}

		private void timer_Tick(object sender, EventArgs e)
		{
			if((mePlayer.Source != null) && (mePlayer.NaturalDuration.HasTimeSpan) && (!userIsDraggingSlider))
			{
				sliProgress.Minimum = 0;
				sliProgress.Maximum = mePlayer.NaturalDuration.TimeSpan.TotalSeconds;
				sliProgress.Value = mePlayer.Position.TotalSeconds;
			}
		}

		private void Open_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = true;
		}

		private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			OpenFileDialog openFileDialog = new OpenFileDialog();
			openFileDialog.Filter = "Media files (*.mp3;*.mpg;*.mpeg)|*.mp3;*.mpg;*.mpeg|All files (*.*)|*.*";
			if(openFileDialog.ShowDialog() == true)
				mePlayer.Source = new Uri(openFileDialog.FileName);
		}

		private void Play_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = (mePlayer != null) && (mePlayer.Source != null);
		}

		private void Play_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			mePlayer.Play();
			mediaPlayerIsPlaying = true;
		}

		private void Pause_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = mediaPlayerIsPlaying;
		}

		private void Pause_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			mePlayer.Pause();
		}

		private void Stop_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = mediaPlayerIsPlaying;
		}

		private void Stop_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			mePlayer.Stop();
			mediaPlayerIsPlaying = false;
		}

		private void sliProgress_DragStarted(object sender, DragStartedEventArgs e)
		{
			userIsDraggingSlider = true;
		}

		private void sliProgress_DragCompleted(object sender, DragCompletedEventArgs e)
		{
			userIsDraggingSlider = false;
			mePlayer.Position = TimeSpan.FromSeconds(sliProgress.Value);
		}

		private void sliProgress_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
		{
			lblProgressStatus.Text = TimeSpan.FromSeconds(sliProgress.Value).ToString(@"hh\:mm\:ss");
		}

		private void Grid_MouseWheel(object sender, MouseWheelEventArgs e)
		{
			mePlayer.Volume += (e.Delta > 0) ? 0.1 : -0.1;
		}

	}
}

Summary

The code listing might look a bit overwhelming, but as you can see, there's a lot of repetition in it. If you take that out of the picture, you will soon realize that creating a pretty capable media player in WPF is really not that hard! Feel free to expand on this example for your own projects - how about implementing a playlist feature?

<PreviousNext>
^ Back to Top