65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (15投票s)

2010 年 3 月 31 日

CPOL

4分钟阅读

viewsIcon

85072

downloadIcon

5360

用于 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 可以播放的任何内容,不包括视频)。您可以打印任何您喜欢的内容。

分隔符右边是媒体的实际路径。

mediaplayerinvisualstudio1.png

首先,您必须(在您喜欢的文本编辑器中)创建播放列表,然后用播放器扩展(“加载”按钮)加载它。接下来,从列表中选择您想要收听的媒体。

之后,您就可以使用标准控件(播放/暂停、停止、音量滑块)了。

为 Visual Studio 2010 开发媒体播放器扩展

在开发播放器扩展时,我遇到了两个问题:

  • 如何播放不同来源的媒体?
  • 如何使播放器通用(我的意思是 Visual Studio 中的代码编辑器)?

这些问题的解决方案将在下一章中讨论。

Using the Code

创建项目

首先,我们需要创建一个项目。

mediaplayerinvisualstudio2.png

播放器应该放在文本编辑器中,所以我们从“扩展性”菜单中选择“编辑器边距”。我将项目命名为“InternetRadioPlayer”。创建项目后,您会立即获得一些已经为您编写好的文件。

现在我们最感兴趣的是 InternetRadioPlayer.csInternetRadioPlayerFactory.csInternetRadioPlayerFactory.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 获取它,但这并不重要。

摘要

此刻,我们达到了最终的解决方案结构。

mediaplayerinvisualstudio3.png

我们最后要做的是向 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 将启动“实验实例”,其中已安装您的扩展。您也可以调试您的扩展。

创建新项目或打开任何现有项目。

mediaplayerinvisualstudio4.png

有时您可能还需要更改调试环境。转到“项目属性”并设置正确的 devenv.exe 路径。

mediaplayerinvisualstudio5.png

安装扩展

要安装您的扩展,您需要先构建它。输出将是一个 .vsix 文件,它是扩展的安装程序。

接下来,转到“扩展管理器”(工具 > 扩展管理器)并管理您的扩展(启用/禁用或卸载)。

mediaplayerinvisualstudio6.png

Publish (发布)

要发布您的扩展,只需将 .vsix 文件上传到 Visual Studio Gallery

您可以从 这里 下载此播放器扩展。

摘要

有了 Visual Studio 2010,开发您自己的扩展就变得非常简单有趣!

历史

2010年3月31日 首次发布

© . All rights reserved.