Nerd Paradise
Username:   Password:   (Register)

How to Make Your Own Media Player Replacement: Part 1

Post by Blake
Tired of Windows Media Player? WinAmp getting too clunky these days? Has the inconvenience of the VLC UI failed your easy listening scenarios? It's time to make your own media player.

If you don't have Visual Studio already, download the express edition for C#. If you're using a version of Windows prior to Windows 7, you'll want to also download the .NET Framework 3.5 SP1 if you don't already have it. I think Visual Studio Express will prompt you to download it, but I can't test that theory because I already have it installed on this machine.

Okay. Got everything installed? Good. Launch Visual Studio and create a new project. (File --> New Project...)
superduper01
Choose WPF Application. I'm calling it SuperDuperPlayer. You can call it whatever you want. No funny punctuation or spaces, please.

Alright. Now you have this...
superduper02
Chances are you are presented with a UI designer. We won't be using that because we are big kids. Click the tab at the bottom that says "XAML" so you see scary XML code instead. This is where 30% of the magic will take place. Paste the following code in.

<Window x:Class="SuperDuperPlayer.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SuperDuperPlayer"
    Title="Super Duper Player" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="40"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <!-- teehee, I'm invisibule! -->
        <MediaElement x:Name="player" Width="0" Height="0" LoadedBehavior="Manual" />
        
        <Grid Grid.Row="0">
            <TextBlock HorizontalAlignment="Left" x:Name="Time" Text="00:00 / 00:00" />
            <TextBlock HorizontalAlignment="Left" x:Name="Title" Margin="100,0,0,0" Text="Current Song" />
        </Grid>
        
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Button x:Name="button_back" Grid.Column="0">Back</Button>
            <Button x:Name="button_stop" Grid.Column="1">Stop</Button>
            <Button x:Name="button_play" Grid.Column="2">Play/Pause</Button>
            <Button x:Name="button_forward" Grid.Column="3">Forward</Button>
            <Button x:Name="button_add" Grid.Column="4">Add File</Button>
            <Button x:Name="button_repeat" Grid.Column="5">Repeat [On]</Button>
            <Button x:Name="button_shuffle" Grid.Column="6">Shuffle [Off]</Button>
        </Grid>
        
        <ScrollViewer Grid.Row="2">
            <ListBox>
                <!-- magic playlist -->
            </ListBox>
        </ScrollViewer>
    </Grid>
</Window>


This is just UI code. No brains to it yet. The magical piece is the tag called MediaElement. This little piece of UI encapsulates all possible means of playback you will need to make a sufficient media player. It's built in to the .NET Framework. It has methods such as Play, Pause, Stop, etc that will magically work for any supported media file. This is why you can make your own media player without knowing an ounce about codecs or compression formats. You may be curious if Media Player runs off the same control and if so won't our media player be just as slow? I don't know if Media Player uses the same native control, however, it is most definitely not slow. It's minimalistic and so if you have no other frills in your media player, it should load lightning fast. Media Player is slow because it loads millions of other features that you probably don't need if you just want to load a simple playlist of mp3's and listen.

If you hit F5, it will actually launch Super Duper Player and you'll see something like this...

superduper04

Alright. C# programs can't exist without C# code, so let's get cracking. This is where the other 70% of the magic comes in. Right click on SuperDuperPlayer.csproj (the little green icon that says "C#" in it) and then click Add --> Class...

Let's name this class Song...
superduper03

Do the same thing again and call the next class Playlist. Now you should have two additional files in your solution explorer called Song.cs and Playlist.cs. Double click on them.

Let's look at Song.cs

This class will represent the actual song entity, both logically, and the thingy that will appear in the playlist. All we need to include in this class is everything we need to know about a snog. Basically just the title and the filename. Here's the spectacularly unspectacular code I wrote up...

using System;

namespace SuperDuperPlayer
{
    public class Song
    {
        public Song(string title, string path)
        {
            this.Path = path;
            this.Title = title;
        }

        public string Title { getprivate set; }
        public string Path { getprivate set; }

        public override string ToString()
        {
            return this.Title;
        }
    }
}


I went ahead and overrode the .ToString method so that when it appears in the playlist without a DataTemplate, it'll actually be somewhat useful. Smile and nod.

Now the playlist is a bit more complicated and I now somewhat regret the name I chose for it. "SongManager" might be a better name for it. But too late! This will serve as the brain of our app, mostly. it'll send messages to our MediaElement to stop and start playing which songs based on the buttons you press and the settings you have set (shuffle/repeat). There's significantly more code, but it's still pretty simple stuff...

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;

namespace SuperDuperPlayer
{
    public class Playlist
    {
        private bool shuffle = false;
        private bool repeat = false;
        private bool paused = false;
        private bool isPlaying = false;

        private ObservableCollection<Song> songs;
        private MediaElement player;

        public Playlist(MediaElement mediaElement)
        {
            this.songs = new ObservableCollection<Song>();
            this.player = mediaElement;
        }

        public int ActiveIndex { getset; }

        public ObservableCollection<Song> Songs
        {
            get { return this.songs; }
        }

        public void AddSong(string path)
        {
            if (System.IO.File.Exists(path))
            {
                this.songs.Add(new Song(
                    System.IO.Path.GetFileNameWithoutExtension(path),
                    path));
            }
        }

        public void DeleteSong()
        {
            if (this.songs.Count > this.ActiveIndex)
            {
                this.songs.RemoveAt(this.ActiveIndex);
            }
        }

        public void Play()
        {
            if (!this.paused && this.isPlaying)
            {
                this.Pause();
            }
            else
            {
                if (!this.paused && this.Songs.Count > this.ActiveIndex)
                {
                    Song songToPlay = this.Songs[this.ActiveIndex];
                    this.player.Source = new Uri(songToPlay.Path);
                }
                this.player.Play();
                this.paused = false;
                this.isPlaying = true;
            }
        }

        public void Stop()
        {
            this.player.Stop();
            this.player.Position = new TimeSpan(0);
            this.isPlaying = false;
            this.paused = false;
        }

        public void Pause()
        {
            this.paused = true;
            this.isPlaying = false;
            this.player.Pause();
        }

        public void Next()
        {
            this.Stop();
            this.ActiveIndex++;
            if (this.ActiveIndex >= this.songs.Count)
            {
                this.ActiveIndex = 0;
            }

            if (this.isPlaying)
            {
                this.Play();
            }
        }

        public void Prev()
        {
            this.Stop();
            this.ActiveIndex--;
            if (this.ActiveIndex < 0)
            {
                this.ActiveIndex = this.songs.Count - 1;
            }
            if (this.isPlaying)
            {
                this.Play();
            }
        }

        public void ToggleRepeat()
        {
            this.repeat = !this.repeat;
        }

        public void ToggleShuffle()
        {
            this.shuffle = !this.shuffle;
        }
    }
}


The only data this class maintains is a list of songs, a few boolean flags for some useful stuff, and an integer to indicate which file in the list is currently playing. The methods basically map to actions a user can perform by hitting buttons we have provided. This methods will set the song MediaElement is currently playing (via its Source property) and start or stop it. The most complicated aspect so far is the code to ensure that the ActiveIndex does not go out of bounds of the number of item in the song list. I use an ObservableCollection instead of just a List because I'm going to bind this collection to the ListBox you may have seen at the bottom of the first bit of Xaml code.

One last thing. We need to tie this playlist object to the actual UI. Next to Window1.xaml, you'll notice a little + icon indicating more things below this file. Expand it. You'll see a Window1.xaml.cs.

Open it up and make it look like this...

using System;
using System.Windows;

namespace SuperDuperPlayer
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private Playlist brain;

        public Window1()
        {
            InitializeComponent();
            this.DataContext = this;
            this.brain = new Playlist(this.player);

            this.button_add.Click += new RoutedEventHandler(button_add_Click);
            this.button_back.Click += new RoutedEventHandler(button_back_Click);
            this.button_forward.Click += new RoutedEventHandler(button_forward_Click);
            this.button_play.Click += new RoutedEventHandler(button_play_Click);
            this.button_repeat.Click += new RoutedEventHandler(button_repeat_Click);
            this.button_shuffle.Click += new RoutedEventHandler(button_shuffle_Click);
            this.button_stop.Click += new RoutedEventHandler(button_stop_Click);
        }

        void button_stop_Click(object sender, RoutedEventArgs e)
        {
            this.brain.Stop();
        }

        void button_shuffle_Click(object sender, RoutedEventArgs e)
        {
            this.brain.ToggleShuffle();
        }

        void button_repeat_Click(object sender, RoutedEventArgs e)
        {
            this.brain.ToggleRepeat();
        }

        void button_play_Click(object sender, RoutedEventArgs e)
        {
            this.brain.Play();
        }

        void button_forward_Click(object sender, RoutedEventArgs e)
        {
            this.brain.Next();
        }

        void button_back_Click(object sender, RoutedEventArgs e)
        {
            this.brain.Prev();
        }

        void button_add_Click(object sender, RoutedEventArgs e)
        {
            // yeah, I know. We'll hook up the open file dialog later.
            this.brain.AddSong(@"C:\users\Blake\desktop\PurplePeopleEater.mp3");
        }

        public ObservableCollection<Song> Songs
        {
            get { return this.brain.Songs; }
        }
    }
}


There shouldn't be any logic in this code. This class should only take user input and convert them into commands that are directly called into the Playlist class we have. I'm going to create a file-open dialog later to browse for mp3's, so for the time being, I've hardcoded a path to a file on my harddrive.

So now we have almost everything hooked up in the most basic way. One last thing...

I've exposed the ObservableCollection of Songs from Window1.xaml.cs so that we can bind the ListBox's ItemSource to it so we can actually see a list of songs. So go back to Window1.xaml and fix up the ListBox in the ScrollViewer to look like this...

        <ScrollViewer Grid.Row="2">
            <ListBox ItemsSource="{Binding Path=Songs}">
                <!-- magic playlist -->
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Title}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </ScrollViewer>


Go ahead and hit F5 and run it. Press the Add File button and then hit play. The song you hardcoded should appear in the playlist and start playing.

superduper06

But listening to the same file over and over again is no fun. So let's add a file browse dialog.

Go back to Window1.xaml.cs to your button_add_Click method...
void button_add_Click(object sender, RoutedEventArgs e)
{
        // yeah, I know. We'll hook up the open file dialog later.
        this.brain.AddSong(@"C:\users\Blake\desktop\PurplePeopleEater.mp3");
}


The file open dialog exists in WinForms. Unfortunately, we're in a WPF project, so we'll have to manually add a reference to the WinForms assembly. But no biggie. Right click on the References item located 2 entries below SuperDuperPlayer.csproj in the Solution Explorer and click "Add Reference...".

superduper07

Find System.Windows.Forms in the .NET tab and add it. This allows us to use any of the legacy classes in the System.Windows.Forms namespace. Such as the OpenFileDialog class.

So now change that method to this:

        void button_add_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.OpenFileDialog dialog = new System.Windows.Forms.OpenFileDialog();
            dialog.Filter = "MP3's (*.mp3)|*.mp3|All files (*.*)|*.*";
            dialog.Multiselect = true;
            System.Windows.Forms.DialogResult result = dialog.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                foreach (string filename in dialog.FileNames)
                {
                    if (System.IO.File.Exists(filename))
                    {
                        this.brain.AddSong(filename);
                    }
                }
            }
        }


And that's about it. See? Nothing magical at all. Next post, I'll show you how to make a slidey bar that updates in real time and make the time show the correct amount and song title. Then we'll create a playlist file format so that we can save playlists and open them later.
Share on Facebook Tweet Submit to Stumble Upon Reddit
Tags: .NET, C#, media
No Comments yet. You can be the first!
You must be logged in to post comments.
List all posts by Blake
Most recent posts by Blake
 
Users online: Blake, Deckmaster, Falun, Tempus
Your IP: 38.107.191.82
Current Time: 9 Abeo 0:0 - 14.60.43 ?
Blake O'Hare | asdfjkl; | Two Cans & String | Game Requests