Out of Browser音乐播放器

最后更新于:2022-04-01 14:20:45

![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c868925.jpg) 在[上一篇](http://www.cnblogs.com/jv9/archive/2010/07/27/1785689.html),我们了解了如何在Silverlight的Out of Browser模式下进行Debug调试,另外学习Silverlight OOB应用的一个新特性Notifications窗口。本篇,我们将结合以往的Out of Browser特性,创建一款新的Out of Browser实例, 音乐播放器。 该实例目的比较简单,实现音乐播放,实现音乐文件列表读取,实现音乐文件信息读取,另外音乐播放自动跳转等功能。 在实例开始前,我们仍旧需要了解一些基础知识。Silverlight对音频的支持是使用MediaElement类,该类使用方法非常简单,该类的详细解释,[请看MSDN](http://msdn.microsoft.com/en-us/library/system.windows.controls.mediaelement(VS.95).aspx) ~~~  <MediaElement       x:Name="media"       Source="xbox.wmv"       CurrentStateChanged="media_state_changed"       Width="300" Height="300"/> ~~~ 在了解了音频播放类的简单使用后,让我们先看看项目完成后的效果图, ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c8809b6.jpg) 从上面效果图中可以看出整个实例项目UI分5个部分, 1. 音频控制部分,这部分是实例主要功能; 2. 音频文件信息部分,这部分是获取显示当前和下一首音乐文件信息; 3. 唱片图片信息,其实这部分也是属于音频文件信息,不过这里单独列出来,使用独立的类进行处理; 4. 音频文件列表,该列表是载入My Music目录中的音乐文件,并支持用户选择播放功能; 5. UI控制,该部分可以使播放器进入最小化状态。例如: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c898ed8.jpg) 下面我们开始分别解释以上几个部分的实例设计方法。 我们仍旧使用SilverlightOOBDemo项目,不过为了使代码更清晰易读,这次不再使用OutofBrowserMainPage作为OOB应用主界面,我们重新创建一个新的OOB应用界面OutofBrowserMusicPlayer。 为了修改启动页面为OutofBrowserMusicPlayer,为此,我们需要修改App.xaml中的启动页面代码: ~~~   private void Application_Startup(object sender, StartupEventArgs e)   {               if (!Application.Current.IsRunningOutOfBrowser)               {                   this.RootVisual = new MainPage();              }               else               {                   //this.RootVisual = new OutofBrowserMainPage();                  this.RootVisual = new OutofBrowserMusicPlayer();              } } ~~~ 根据实例需求,我们最主要的功能就是播放音乐,所以,我们第一步首先实现Out of Browser应用音频控制。 **1. 创建自定义音频控制控件;** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c8ab7da.jpg) 对于音频控制,这里我们使用了自定义控件控制音乐的播放。AudioControl.xaml控件, ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c8c2576.jpg) 这里我仅贴上部分代码,大家可以在文章最后下载完整源代码。 ~~~   <Grid x:Name="LayoutRoot">           <Grid.ColumnDefinitions>               <ColumnDefinition Width="Auto" />               <ColumnDefinition Width="*" />               <ColumnDefinition Width="25" />               <ColumnDefinition Width="Auto" />               <ColumnDefinition Width="Auto" />           </Grid.ColumnDefinitions>           <Grid Grid.Column="0" Margin="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="gridCol1">              <ToggleButton Cursor="Hand" Margin="0,0,0,0" x:Name="btnPlay" RenderTransformOrigin="0.5,0.5" Template="{StaticResource playControlTemplate}">                  <ToggleButton.RenderTransform>                      <TransformGroup>                          <ScaleTransform ScaleX="1" ScaleY="1"/>                          <SkewTransform/>                          <RotateTransform/>                          <TranslateTransform/>                      </TransformGroup>                  </ToggleButton.RenderTransform>              </ToggleButton>         </Grid>          <Grid Grid.Column="1" Margin="0,0,0,0" HorizontalAlignment="Stretch" x:Name="gridCol2" VerticalAlignment="Center">              <Grid.ColumnDefinitions>                  <ColumnDefinition Width="*" />                  <ColumnDefinition Width="40" />                  <ColumnDefinition Width="10" />                  <ColumnDefinition Width="40" />              </Grid.ColumnDefinitions>              <TextBlock x:Name="tbCurrentTime" Margin="0,1.5,0,0"  Height="12" FontFamily="Verdana" FontSize="10" Text="00:00" TextWrapping="Wrap" Foreground="#FFFFFFFF" FontStyle="Normal" HorizontalAlignment="Right" TextAlignment="Right" Grid.Column="1"/>              <TextBlock Margin="0,1.5,0,0"  Height="12" FontFamily="Verdana" FontSize="10" Text="/" TextWrapping="Wrap" Foreground="#FFFFFFFF" FontStyle="Normal" HorizontalAlignment="Center" TextAlignment="Right" Grid.Column="2"/>              <TextBlock x:Name="tbTotalTime" Margin="0,1.5,0,0"  Height="12" FontFamily="Verdana" FontSize="10" Text="00:00" TextWrapping="Wrap" Foreground="#FFFFFFFF" FontStyle="Normal" HorizontalAlignment="Left" TextAlignment="Right" Grid.Column="3"/>              <local:MediaSlider Margin="0,1.5,0,0" HorizontalAlignment="Stretch" Maximum="100" x:Name="sliderTimeline" Style="{StaticResource progressSliderStyle}" Grid.Column="0" Value="0" Visibility="Visible"/>          </Grid>          <Grid Grid.Column="2" Margin="4,0,4,0" HorizontalAlignment="Stretch" x:Name="gridCol3" VerticalAlignment="Center">              <local:Spinner Margin="0,0,0,0" x:Name="spinner" Width="17" Height="17" HorizontalAlignment="Center" VerticalAlignment="Center"/>          </Grid>          <Grid Grid.Column="3" Margin="0,10.30,0,10.30" HorizontalAlignment="Stretch" x:Name="gridCol4" Width="70" VerticalAlignment="Stretch" d:LayoutOverrides="Height">              <Grid Margin="0,0,0,0" HorizontalAlignment="Right" VerticalAlignment="Center" Width="70">                  <Grid.ColumnDefinitions>                      <ColumnDefinition Width="Auto" />                      <ColumnDefinition Width="*" />                  </Grid.ColumnDefinitions>                  <ToggleButton HorizontalAlignment="Left" IsChecked="True" Margin="0,0,0,0" x:Name="btnSpeaker" Template="{StaticResource speakerControlTemplate}"/>                  <Slider Grid.Column="1" HorizontalAlignment="Stretch" Margin="3,0,0,0" VerticalAlignment="Center" Maximum="1" x:Name="sliderVolume" Style="{StaticResource volumeSliderStyle}" Background="#FF777777"/>              </Grid>          </Grid>          <Grid Grid.Column="4" Margin="0,10.3120002746582,4,10.3120002746582" HorizontalAlignment="Right" x:Name="gridCol5" VerticalAlignment="Stretch" d:LayoutOverrides="Height">              <ToggleButton Cursor="Hand" HorizontalAlignment="Left" Margin="0,0,0,0" x:Name="btnFullScreen" Template="{StaticResource fullScreenControlTemplate}"/>          </Grid>      </Grid> ~~~ 从以上代码可以看到,在AudioControl中有两个自定义控件local:MediaSlider和local:Spinner。 MediaSlider: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c8d18eb.jpg) 其功能是控制音乐播放进度,支持拖拽前进或者后退音乐播放进度。其代码如下: ~~~    public class MediaSlider : Slider        {            public Thumb              horizontalThumb;            private FrameworkElement horizontalLeftTrack;            private FrameworkElement horizontalRightTrack;            private double oldValue = 0, newValue = 0, prevNewValue = 0;            public event RoutedPropertyChangedEventHandler<double> MyValueChanged;            public event RoutedPropertyChangedEventHandler<double> MyValueChangedInDrag;            private DispatcherTimer dragtimer = new DispatcherTimer();           private double dragTimeElapsed = 0;           private const short DragWaitThreshold = 200, DragWaitInterval = 100;           public Rectangle progressRect = null;           private bool dragSeekJustFired = false;          public MediaSlider()           {              this.ValueChanged += new RoutedPropertyChangedEventHandler<double>(CustomSlider_ValueChanged);              dragtimer.Interval = new TimeSpan(0, 0, 0, 0, DragWaitInterval);               dragtimer.Tick += new EventHandler(dragtimer_Tick);          }           void dragtimer_Tick(object sender, EventArgs e)          {               dragTimeElapsed += DragWaitInterval;               if (dragTimeElapsed >= DragWaitThreshold)               {                   RoutedPropertyChangedEventHandler<double> handler = MyValueChangedInDrag;                                      if ((handler != null) && (newValue != prevNewValue))                   {                       handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, newValue));                       dragSeekJustFired = true;                       prevNewValue = newValue;                   }                  dragTimeElapsed = 0;               }           }          void CustomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)           {               oldValue = e.OldValue;               newValue = e.NewValue;              if (horizontalThumb.IsDragging)               {                   dragTimeElapsed = 0;                   dragtimer.Stop();                   dragtimer.Start();                   dragSeekJustFired = false;               }           }          public override void OnApplyTemplate()           {               base.OnApplyTemplate();               horizontalThumb = GetTemplateChild("HorizontalThumb") as Thumb;               horizontalLeftTrack = GetTemplateChild("LeftTrack") as FrameworkElement;               horizontalRightTrack = GetTemplateChild("RightTrack") as FrameworkElement;               progressRect = GetTemplateChild("Progress") as Rectangle;               if (horizontalLeftTrack != null) horizontalLeftTrack.MouseLeftButtonDown += new MouseButtonEventHandler(OnMoveThumbToMouse);               if (horizontalRightTrack != null) horizontalRightTrack.MouseLeftButtonDown += new MouseButtonEventHandler(OnMoveThumbToMouse);               horizontalThumb.DragCompleted += new DragCompletedEventHandler(DragCompleted);               progressRect.Width = this.Width;           }           public Storyboard ProgressStoryboard { get { return (GetTemplateChild("ProgressStoryboard") as Storyboard); } }           public Rectangle ProgressBar { get { return (GetTemplateChild("Progress") as Rectangle); } }           protected override Size ArrangeOverride(Size finalSize)          {               Size s = base.ArrangeOverride(finalSize);               if (double.IsNaN(horizontalThumb.Width) && (horizontalThumb.ActualWidth != 0))               {                   horizontalThumb.Width = horizontalThumb.ActualWidth;               }               if (double.IsNaN(horizontalThumb.Height) && (horizontalThumb.ActualHeight != 0))               {                   horizontalThumb.Height = horizontalThumb.ActualHeight;               }               if (double.IsNaN(horizontalThumb.Width)) horizontalThumb.Width = horizontalThumb.Height;               if (double.IsNaN(horizontalThumb.Height)) horizontalThumb.Height = horizontalThumb.Width;               return (s);           }                      private void OnMoveThumbToMouse(object sender, MouseButtonEventArgs e)           {              e.Handled = true;              Point p = e.GetPosition(this);              if (this.Orientation == Orientation.Horizontal)              {                  Value = (p.X - (horizontalThumb.ActualWidth / 2)) / (ActualWidth - horizontalThumb.ActualWidth) * Maximum;              }              RoutedPropertyChangedEventHandler<double> handler = MyValueChanged;              if (handler != null)              {                  handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, Value));              }          }          private void DragCompleted(object sender, DragCompletedEventArgs e)          {              dragtimer.Stop();              dragTimeElapsed = 0;              RoutedPropertyChangedEventHandler<double> handler = MyValueChanged;               if ((handler != null) && (!dragSeekJustFired))              {                  handler(this, new RoutedPropertyChangedEventArgs<double>(oldValue, this.Value));              }          }      } ~~~ 而Spinner控件,是一个载入标识,当音频载入时,会显示该控件。该控件为Path绘制的控件,这里不再贴出代码描述。 **2. 获取音频文件信息部分** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c8e37ab.jpg) 该部分我们同样也创建一个自定义控件来实现,TrackInfo.xaml,主要是负责在客户端显示音频文件的信息,而Silverlight没有相关API可以实现读取音频文件的标签信息,这里,我们需要引入一个微软开源类库TagLib。该类库的主要功能就是读取和修改音乐文件的标签信息。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c8f3622.jpg) 其调用方法非常简单: 1 // 获取标签 2 tags = TagLib.File.Create(MediaFile.ID); 3 // 设置标签属性 4 MediaFile.Artist = tags.Tag.FirstPerformer; 5 MediaFile.Title = tags.Tag.Title; 6 MediaFile.Album = tags.Tag.Album; 7 MediaFile.Genre = tags.Tag.FirstGenre; 当音乐标签信息获取成功后,即可将信息绑定到TrackInfo.DataContext。 **3. 唱片图片信息** 对于唱片的图片信息,这里需要读取Image从本地目录,当没有唱片图片时,则显示默认Music.png图片。这里需要注意的是,读取本地文件,需要OOB应用权限信任。 ~~~   public ImageSource AlbumArtStream   {               get               {                   BitmapImage image;                   if (string.IsNullOrEmpty(AlbumArtPath))                   {                       if (null == _default)                      {                          _default = new BitmapImage(new Uri("../Images/Music.png", UriKind.Relative));                      }                      image = _default;                  }                  else                  {                      FileStream stream = File.Open(AlbumArtPath, FileMode.Open, FileAccess.Read);                      image = new BitmapImage();                      image.SetSource(stream);                      stream.Close();                  }                  return image;              } } ~~~ **4. 获取音频文件列表** 从演示图片可以看出,我们的音频文件列表,是用了一个绑定了音乐播放文件信息的Datagrid。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c91061a.jpg) 其代码非常简单,创建两列,分别绑定歌手和歌曲名: ~~~   <data:DataGrid x:Name="playList"                          Grid.Row="1"                          Grid.Column="1"                          Grid.RowSpan="3"                          VerticalAlignment="Top"                          Margin="4"                          Height="296"                          Style="{StaticResource DataGridStyle}"                          AutoGenerateColumns="False"                         CanUserResizeColumns="True"                         CanUserSortColumns="False"                         SelectionChanged="playList_SelectionChanged">              <data:DataGrid.Columns>                  <data:DataGridTextColumn Header="歌手"                                           Binding="{Binding Artist}"                                           FontSize="12" />                  <data:DataGridTextColumn Header="歌名"                                           Binding="{Binding Title}"                                           FontSize="12"                                           Width="*" />              </data:DataGrid.Columns>  </data:DataGrid> ~~~ 而后台,在读取了My Music目录后,将数据集绑定到datagrid.ItemsSource就可以正常实现歌曲列表了。 ~~~   public static List<MediaFile> GetMediaFiles()   {               List<MediaFile> files = null; ;               MediaFile mf;               string path = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);               IEnumerable<string> list = Directory.EnumerateFiles(path, "*.mp3", SearchOption.AllDirectories);               TagLib.File tags;               files = GetCachedList(list);               if (null == files || files.Count == 0)              {                  files = new List<MediaFile>();                  foreach (string file in list)                  {                      mf = new MediaFile();                      mf.ID = file;                      mf.AlbumArtPath = GetAlbumArtPath(file);                      files.Add(mf);                  }                  for (int idx = 0; idx < files.Count; idx++)                  {                      mf = files[idx];                      tags = TagLib.File.Create(mf.ID);                      mf.Artist = tags.Tag.FirstPerformer;                      mf.Title = tags.Tag.Title;                      mf.Album = tags.Tag.Album;                      mf.Genre = tags.Tag.FirstGenre;                  }                  SaveCachedList(files);              }              return files; } ~~~ 在绑定成功后,同时,我们支持用户选择指定音乐播放,使用Datagrid的SelectionChanged事件即可。 ~~~   private void playList_SelectionChanged(object sender, SelectionChangedEventArgs e)   {               DataGrid dg = (sender as DataGrid);               if (dg.SelectedIndex != _nowPlaying)               {                   if (dg.SelectedIndex != 0)                   {                       me.AutoPlay = true;                  }                  OpenAndPlay(dg.SelectedIndex);              }               } ~~~ **5. UI控制** 对于UI的控制,这里我们只是简单的实现了隐藏和显示音乐信息框的功能,其代码实现: ~~~           private void Minimize_Click(object sender, MouseButtonEventArgs e)           {               Window main = Application.Current.MainWindow;              if (!_min)               {                   main.Height = 40;                   rot.Angle = 0;               }              else              {                  main.Height = 340;                  rot.Angle = 180;             }              _min = !_min;          } ~~~ 上面是OOB音乐播放器5个部分的核心功能代码,这里,我想同时将[上一篇](http://www.cnblogs.com/jv9/archive/2010/07/27/1785689.html)讲到的Notifications窗口应用到实例中,我们可以仍旧使用NotificationControl文件,在其中对播放音乐Title进行绑定,即当音乐播放完毕后,即弹出消息提示播放下一首“XXX”音乐。效果如下图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c91d818.jpg) 根据上一篇介绍Notifications窗口的代码,我们简单进行修改,即可实现本篇实例需求: ~~~           NotificationWindow notifyWindow = null;           private void ShowToast()           {               notifyWindow = new NotificationWindow();               if (notifyWindow.Visibility == Visibility.Visible)                  notifyWindow.Close();              NotificationControl myNotify = new NotificationControl();              myNotify.DataContext = _playList[_nowPlaying];              notifyWindow.Width = 300;              notifyWindow.Height = 100;              notifyWindow.Content = myNotify;              notifyWindow.Show(10000);          } ~~~ 至此,一款基于Silverlight的Out of Browser模式的音乐播放器基本完成了。大家可以根据该实例添加更多自定义功能,例如添加互联网音乐播放功能,音乐搜索功能等,创建属于自己的Silverlight版酷我音乐盒。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-18_56eb67c92e3bd.jpg) [本篇源代码下载](http://www.silverlightchina.net/resource/code/SilverlightOOBDemo0727.zip) 欢迎大家加入"专注Silverlight" 技术讨论群: 32679955(六群) 23413513(五群) 32679922(四群) 100844510(三群) 37891947(二群) 22308706(一群)
';