Visual Studio 2010 中的媒体播放器 (VSX)






4.93/5 (15投票s)
用于 Visual Studio 的播放器扩展,可在互联网广播电台之间切换
引言
我一直认为为 Visual Studio 开发扩展是一项非常艰巨的任务。我确定它曾经是,但现在创建自己的扩展甚至与全世界分享它们变得非常容易。
这是我第一次为 Visual Studio 开发扩展的经历,我希望不是最后一次。
我喜欢在编写代码时听互联网广播或 PC 上的音乐。有时切换不同的广播电台(例如当某个电台停止时)会很困难,因为我还必须切换我的“上下文”来寻找另一个电台。
然后我想,如果我能从 Visual Studio 中做到这一点,那该多酷啊。这甚至可以提高我的工作效率!这就是播放器扩展 for Visual Studio 的想法的由来。
扩展开发的前提条件
要开发 Visual Studio 2010 扩展,您将需要以下内容:
使用扩展
播放器扩展的播放列表是某种奇怪格式的文本文件(这是因为我创建了它)。
播放列表可能看起来像这样:
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
其中“>>>”是分隔符。分隔符左边的所有内容都是电台名称(歌曲/播放列表或 Windows Media Player 可以播放的任何内容,不包括视频)。您可以打印任何您喜欢的内容。
分隔符右边是媒体的实际路径。
首先,您必须(在您喜欢的文本编辑器中)创建播放列表,然后用播放器扩展(“加载”按钮)加载它。接下来,从列表中选择您想要收听的媒体。
之后,您就可以使用标准控件(播放/暂停、停止、音量滑块)了。
为 Visual Studio 2010 开发媒体播放器扩展
在开发播放器扩展时,我遇到了两个问题:
- 如何播放不同来源的媒体?
- 如何使播放器通用(我的意思是 Visual Studio 中的代码编辑器)?
这些问题的解决方案将在下一章中讨论。
Using the Code
创建项目
首先,我们需要创建一个项目。
播放器应该放在文本编辑器中,所以我们从“扩展性”菜单中选择“编辑器边距”。我将项目命名为“InternetRadioPlayer”。创建项目后,您会立即获得一些已经为您编写好的文件。
现在我们最感兴趣的是 InternetRadioPlayer.cs
和 InternetRadioPlayerFactory.cs
。InternetRadioPlayerFactory.cs
是一个工厂,为每个代码编辑器页面创建我们的控件。我们不会更改它,我们将所有有用的代码放在 InternetRadioPlayer.cs 中。但首先,让我们创建播放器类和播放器控件。
播放器类
要播放声音,我们需要什么?是的,播放器!让我们创建一个。
我尝试在网上寻找一个好的媒体库或控件,但最终我选择了最简单的解决方案——使用 WPF 控件库中的 MediaElement
。它可以播放几乎任何来源的媒体:单曲、视频、播放列表、互联网广播等。
现在让我们创建一个新的类文件并将其命名为 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
}
}
这段代码并不难理解。播放器有四种状态:未知(尚未加载任何内容)、停止、暂停和播放。几个方法可以改变播放器的状态。LoadPlayList
方法从选定的文件中加载播放列表。
还有一个 Changed
事件,它会通知订阅者(播放器用户控件)更新 UI。
另一个有趣的事情是静态变量实例。还记得我说过,在使播放器对所有文本编辑器页面通用时遇到了一个问题吗?
InternetRadioPlayerFactory
为每个页面创建播放器控件的实例。这意味着,如果我们有一个独立的播放器封装在每个控件中,就会出现从不同页面播放不同歌曲的情况。
拥有静态的 Player 实例和私有构造函数不允许用户控件创建新的 Player 实例。
播放器控件
现在让我们为我们的播放器创建一个用户控件。点击“添加新项”,然后在 WPF 选项卡中选择“用户控件 (WPF)”。将其命名为 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>
这里一切都很简单。
后面的代码执行以下操作:
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
{
/// <summary>
/// Interaction logic for PlayerControl.xaml
/// </summary>
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
将 Player 实例作为构造函数参数,我们可以直接从 Player.Instance
获取它,但这并不重要。
摘要
此刻,我们达到了最终的解决方案结构。
我们最后要做的是向 InternetRadioPlayer.cs
添加一些代码。在接下来的代码中,我们将创建 PlayerControl
控件并将它们添加到文本编辑器中。
using System;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Editor;
namespace InternetRadioPlayer
{
/// <summary>
/// A class detailing the margin's visual definition including both size and content.
/// </summary>
class InternetRadioPlayer : Canvas, IWpfTextViewMargin
{
public const string MarginName = "InternetRadioPlayer";
private IWpfTextView _textView;
private bool _isDisposed = false;
private PlayerControl _player = new PlayerControl(Player.Instance);
/// <summary>
/// Creates a <see cref="InternetRadioPlayer"/> for a
/// given <see cref="IWpfTextView"/>.
/// </summary>
/// <param name="textView">The <see cref="IWpfTextView"/> to attach
/// the margin to.</param>
public InternetRadioPlayer(IWpfTextView textView)
{
_textView = textView;
this.Height = 30;
this.ClipToBounds = true;
//Add Control
this.Children.Add(_player);
}
private void ThrowIfDisposed()
{
if (_isDisposed)
throw new ObjectDisposedException(MarginName);
}
#region IWpfTextViewMargin Members
/// <summary>
/// The <see cref="Sytem.Windows.FrameworkElement"/> that implements
/// the visual representation
/// of the margin.
/// </summary>
public System.Windows.FrameworkElement VisualElement
{
// Since this margin implements Canvas, this is the object which renders
// the margin.
get
{
ThrowIfDisposed();
return this;
}
}
#endregion
#region ITextViewMargin Members
public double MarginSize
{
// Since this is a horizontal margin, its width will be bound to
// the width of the text view.
// Therefore, its size is its height.
get
{
ThrowIfDisposed();
return this.ActualHeight;
}
}
public bool Enabled
{
// The margin should always be enabled
get
{
ThrowIfDisposed();
return true;
}
}
/// <summary>
/// Returns an instance of the margin if this is the margin that has
/// been requested.
/// </summary>
/// <param name="marginName">The name of the margin requested</param>
/// <returns>An instance of InternetRadioPlayer or null</returns>
public ITextViewMargin GetTextViewMargin(string marginName)
{
return (marginName ==
InternetRadioPlayer.MarginName) ? (IWpfTextViewMargin)this : null;
}
public void Dispose()
{
if (!_isDisposed)
{
GC.SuppressFinalize(this);
_isDisposed = true;
}
}
#endregion
}
}
运行和测试
要测试您的扩展,只需按 F5(或从“调试”菜单中选择“启动调试”)。Visual Studio 将启动“实验实例”,其中已安装您的扩展。您也可以调试您的扩展。
创建新项目或打开任何现有项目。
有时您可能还需要更改调试环境。转到“项目属性”并设置正确的 devenv.exe 路径。
安装扩展
要安装您的扩展,您需要先构建它。输出将是一个 .vsix 文件,它是扩展的安装程序。
接下来,转到“扩展管理器”(工具 > 扩展管理器)并管理您的扩展(启用/禁用或卸载)。
Publish (发布)
要发布您的扩展,只需将 .vsix 文件上传到 Visual Studio Gallery。
您可以从 这里 下载此播放器扩展。
摘要
有了 Visual Studio 2010,开发您自己的扩展就变得非常简单有趣!
历史
2010年3月31日 首次发布