Introduction
I always thought that developing Extensions for Visual Studio is very hard task. And I'm sure it was, but today it's really easy to create your own Extensions and even share them with everyone worldwide.
This was my first experience in developing extensions for Visual Studio and I hope - not the last.
I like listening to Internet radio or music from my PC while working with my code. Sometimes switching between different radio stations (for example when some station goes down) it is difficult because I also have to switch my "context" to find another station.
Then I thought that it would be really cool if I could do this from Visual Studio. It could even increase my productivity! That's how the idea of player extension for Visual Studio came to me.
Prerequisite for Extension Development
For developing Visual Studio 2010 Extensions you will need the following:
Using Extension
Playlists for Player Extension are text files in some strange format (that's because I created it).
Playlist may look like this:
16bit radio >>> http://www.16bit.fm/play/16bit.fm_192.m3u
dance radio >>> http://danceradio.ru/playlist160.m3u
Dinamyte Radio >>> http://80.254.15.12:8000/listen.pls
song 1 >>> D:/0/11.mp3
song 2 >>> D:/0/12.mp3
Where '>>>' is a delimiter. Everything to the left from delimiter is a name of the radio station (song/playlist or anything that can be played by Windows Media Player except video). You can print whatever you like here.
Actual path to the media is placed to the right from the delimiter.
First of all you have to create your playlist (in your preferred text editor) then load it with the Player Extension (button 'Load'). Next select media which you want to listen to from the list.
After that you can you standart controls (Play/Pause, Stop, Volume slider).
Developing Media Player Extension for Visual Studio 2010
While developing Player Extension I had to face two problems:
- How to play media from different sources?
- How to make player common for all pages (I mean code editors in Visual Studio)?
Ways to solve those problems are discussed in the next chapter
Using the Code
Creating Project
First of all we need create project.
Player should be placed to the text editor so we choose Editor Margin from Extensibility menu. I named the project "InternetRadioPlayer." Right after creating the project you'll get a few files that are already written for you.
Most interesting for us now is InternetRadioPlayer.cs
and InternetRadioPlayerFactory.cs
. InternetRadioPlayerFactory.cs
is a factory that creates our controls for each code editor page. We will not change it we will place all useful code to InternetRadioPlayer.cs. But first let's create player class and player control.
Player Class
What do we need to play sound? Yes, the player! Let's create one.
I tried to find a good media library or control in Internet but finally I came out with the simplest solution - using the MediaElement
from WPF control library. It can play media from almost any source: single songs, videos, playlists, Internet radio etc.
Now let's create a new class file and name it Player.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.IO;
using System.Text.RegularExpressions;
namespace InternetRadioPlayer
{
public class Player
{
private MediaElement _player;
public PlayerState State { get; set; }
private Dictionary<string, string> _playlist = new Dictionary();
private static Player _instance;
public event EventHandler Changed;
public Dictionary<string, string> Playlist
{
get
{
return _playlist;
}
}
public double Volume
{
get
{
return _player.Volume;
}
set
{
_player.Volume = value;
OnChanged();
}
}
public static Player Instance
{
get
{
if (_instance == null)
{
_instance = new Player();
}
return _instance;
}
}
public int SelectedIndex { get; set; }
private Player()
{
_player = new MediaElement();
_player.LoadedBehavior = MediaState.Manual;
_player.UnloadedBehavior = MediaState.Manual;
}
public void SetSource(int index, string uri)
{
SelectedIndex = index;
_player.Source = new Uri(uri);
State = PlayerState.Stopped;
OnChanged();
}
public void Play()
{
_player.Play();
State = PlayerState.Playing;
OnChanged();
}
public void Stop()
{
_player.Stop();
State = PlayerState.Stopped;
OnChanged();
}
public void Pause()
{
_player.Pause();
State = PlayerState.Paused;
OnChanged();
}
public void Unload()
{
if (State != PlayerState.Stopped)
{
_player.Stop();
}
State = PlayerState.Unknown;
}
public void LoadPlaylist(string fileName)
{
_playlist.Clear();
string[] lines = File.ReadAllLines(fileName);
foreach (var item in lines)
{
string[] str = Regex.Split(item, ">>>");
_playlist.Add(str[0].Trim(), str[1].Trim());
}
OnChanged();
}
private void OnChanged()
{
if (Changed != null)
{
Changed(this, null);
}
}
}
public enum PlayerState
{
Unknown,
Stopped,
Paused,
Playing
}
}
Nothing is very hard to understand in this code. The player has four states: Unknown (nothing is loaded yet), Stopped, Paused and Playing. A few methods change player state. The LoadPlayList
method loads playlist from selected file.
There is also an event Changed
which tells subscribers (player user controls) to update the UI.
Another interesting thing here is a static variable instance. Remember I told you that there was a problem while making player common for all text editor pages.
InternetRadioPlayerFactory
creates instances of player control for each page. This means that if we had a standalone player encapsulated in each control we would have the situation when different songs are being played from different pages.
Having static instance of Player and a private constructor don't allow user controls to create new instances of Player.
Player Control
Now let's create a user control for our player. Click "Add new item" and choose User Control (WPF) from WPF tab. Name it PlayerControl
.
<UserControl x:Class="InternetRadioPlayer.PlayerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="30" Width="440">
<Border BorderThickness="1" CornerRadius="3,3,3,3" Width="436" Height="30">
<Border.BorderBrush>
<SolidColorBrush
Color="{DynamicResource {x:Static SystemColors.DesktopColorKey}}"/>
</Border.BorderBrush>
<Grid Height="30" Width="440" >
<ComboBox Name="comboBox1" Height="20" Width="134" Margin="179,4,127,6"
SelectionChanged="comboBox1_SelectionChanged"
ToolTip="Choose Playlist" IsEnabled="False" />
<Button Content="Play" Height="22" HorizontalAlignment="Left"
Margin="5,3,0,5" Name="btnPlay" VerticalAlignment="Center"
Width="55" Click="btnPlay_Click" />
<Button Content="Stop" Height="22" HorizontalAlignment="Left"
Margin="62,3,0,5" Name="btnStop" VerticalAlignment="Center"
Width="55" Click="btnStop_Click" />
<Button Content="Load" Height="22" HorizontalAlignment="Right"
Margin="0,3,265,5" Name="btnLoad" VerticalAlignment="Center"
Width="55" Click="btnLoad_Click"/>
<Slider Height="24" Margin="313,2,0,0" HorizontalAlignment="Left"
Name="slider1" VerticalAlignment="Center" Width="121" Maximum="1"
Value="0.5" ValueChanged="slider1_ValueChanged" />
</Grid>
</Border>
</UserControl>
Everything is really easy here.
And code-behind here does the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.Win32;
using System.IO;
using System.Text.RegularExpressions;
namespace InternetRadioPlayer
{
public partial class PlayerControl : UserControl
{
private Player _player;
public PlayerControl(Player player)
{
_player = player;
_player.Changed += new EventHandler(_player_Changed);
InitializeComponent();
}
void _player_Changed(object sender, EventArgs e)
{
UpdateState();
}
private void btnPlay_Click(object sender, RoutedEventArgs e)
{
try
{
if (_player.State == PlayerState.Playing)
_player.Pause();
else
_player.Play();
UpdateButtonContent();
}
catch (Exception)
{
MessageBox.Show("Error");
}
}
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
if (comboBox1.SelectedValue != null)
{
if (comboBox1.SelectedIndex != _player.SelectedIndex)
{
_player.SetSource(comboBox1.SelectedIndex,
comboBox1.SelectedValue.ToString());
_player.Play();
UpdateButtonContent();
}
}
}
catch (Exception)
{
MessageBox.Show("Error loading stream");
}
}
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
_player.Unload();
OpenFileDialog dlg = new OpenFileDialog();
if (dlg.ShowDialog().Value)
{
try
{
_player.LoadPlaylist(dlg.FileName);
comboBox1.ItemsSource = _player.Playlist;
comboBox1.DisplayMemberPath = "Key";
comboBox1.SelectedValuePath = "Value";
comboBox1.SelectedIndex = 0;
_player.SetSource(comboBox1.SelectedIndex,
comboBox1.SelectedValue.ToString());
UpdateButtonContent();
comboBox1.IsEnabled = true;
}
catch (Exception)
{
MessageBox.Show("Error while loading playlist");
}
}
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
_player.Stop();
UpdateButtonContent();
}
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
{
_player.Volume = slider1.Value;
}
private void UpdateState()
{
slider1.Value = _player.Volume;
comboBox1.ItemsSource = _player.Playlist;
comboBox1.DisplayMemberPath = "Key";
comboBox1.SelectedValuePath = "Value";
comboBox1.Items.Refresh();
if (comboBox1.SelectedIndex != _player.SelectedIndex)
{
comboBox1.SelectedIndex = _player.SelectedIndex;
}
UpdateButtonContent();
}
private void UpdateButtonContent()
{
btnPlay.IsEnabled = false;
btnStop.IsEnabled = false;
switch (_player.State)
{
case PlayerState.Stopped:
btnPlay.IsEnabled = true;
btnPlay.Content = "Play";
break;
case PlayerState.Playing:
btnPlay.IsEnabled = true;
btnStop.IsEnabled = true;
btnPlay.Content = "Pause";
break;
case PlayerState.Paused:
btnPlay.IsEnabled = true;
btnStop.IsEnabled = true;
btnPlay.Content = "Play";
break;
case PlayerState.Unknown:
break;
}
}
}
}
PlayerControl
takes Player instance as a constructor parameter, we could just get it from Player.Instance
but that doesn't matter.
Summary
At this moment we came to final solution structure
The last thing we have to do is adding some code to our InternetRadioPlayer.cs
. In the next code we are going to create PlayerControl
controls and add them to the text editor.
using System;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Editor;
namespace InternetRadioPlayer
{
class InternetRadioPlayer : Canvas, IWpfTextViewMargin
{
public const string MarginName = "InternetRadioPlayer";
private IWpfTextView _textView;
private bool _isDisposed = false;
private PlayerControl _player = new PlayerControl(Player.Instance);
public InternetRadioPlayer(IWpfTextView textView)
{
_textView = textView;
this.Height = 30;
this.ClipToBounds = true;
this.Children.Add(_player);
}
private void ThrowIfDisposed()
{
if (_isDisposed)
throw new ObjectDisposedException(MarginName);
}
#region IWpfTextViewMargin Members
public System.Windows.FrameworkElement VisualElement
{
get
{
ThrowIfDisposed();
return this;
}
}
#endregion
#region ITextViewMargin Members
public double MarginSize
{
get
{
ThrowIfDisposed();
return this.ActualHeight;
}
}
public bool Enabled
{
get
{
ThrowIfDisposed();
return true;
}
}
public ITextViewMargin GetTextViewMargin(string marginName)
{
return (marginName ==
InternetRadioPlayer.MarginName) ? (IWpfTextViewMargin)this : null;
}
public void Dispose()
{
if (!_isDisposed)
{
GC.SuppressFinalize(this);
_isDisposed = true;
}
}
#endregion
}
}
Run and Test
To test your extension simply press F5 (or Start Debugging from Debug menu). Visual Studio will launch "Experimental Instance" with your extension already installed. You can also debug your Extension.
Create new project or open any existing one.
Sometimes you might also need to change Debug environment. Go to Project Properties and set correct path to your devenv.exe.
Install the extension
To install your Extension you will need to build it first. You will get .vsix as output, which is the setup for the Extension.
Next go to Extension Manager (Tools > Extension Manager) and manager your extensions (Enable/Disable or Uninstall).
Publish
To publish your Extension simply upload .vsix file to Visual Studio Gallery
You can download this Player Extension from here
Summary
With Visual Studio 2010 developing your own extensions is really easy and fun!
History
31.03.2010 First version