代码改变世界

Silverlight实例教程 - Out of Browser音乐播放器

2010-07-28 04:41 jv9 阅读(...) 评论(...) 编辑 收藏

 

上一篇,我们了解了如何在Silverlight的Out of Browser模式下进行Debug调试,另外学习Silverlight OOB应用的一个新特性Notifications窗口。本篇,我们将结合以往的Out of Browser特性,创建一款新的Out of Browser实例, 音乐播放器。 该实例目的比较简单,实现音乐播放,实现音乐文件列表读取,实现音乐文件信息读取,另外音乐播放自动跳转等功能。

 

在实例开始前,我们仍旧需要了解一些基础知识。Silverlight对音频的支持是使用MediaElement类,该类使用方法非常简单,该类的详细解释,请看MSDN

 

1 <MediaElement 
2     x:Name="media" 
3     Source="xbox.wmv" 
4     CurrentStateChanged="media_state_changed" 
5     Width="300" Height="300"/>

 

在了解了音频播放类的简单使用后,让我们先看看项目完成后的效果图,

 

从上面效果图中可以看出整个实例项目UI分5个部分,

1. 音频控制部分,这部分是实例主要功能;

2. 音频文件信息部分,这部分是获取显示当前和下一首音乐文件信息;

3. 唱片图片信息,其实这部分也是属于音频文件信息,不过这里单独列出来,使用独立的类进行处理;

4. 音频文件列表,该列表是载入My Music目录中的音乐文件,并支持用户选择播放功能;

5. UI控制,该部分可以使播放器进入最小化状态。例如:

 

下面我们开始分别解释以上几个部分的实例设计方法。

我们仍旧使用SilverlightOOBDemo项目,不过为了使代码更清晰易读,这次不再使用OutofBrowserMainPage作为OOB应用主界面,我们重新创建一个新的OOB应用界面OutofBrowserMusicPlayer。

 

为了修改启动页面为OutofBrowserMusicPlayer,为此,我们需要修改App.xaml中的启动页面代码:

 

 1 private void Application_Startup(object sender, StartupEventArgs e)
 2 {
 3             if (!Application.Current.IsRunningOutOfBrowser)
 4             {
 5                 this.RootVisual = new MainPage();
 6             }
 7             else
 8             {
 9                 //this.RootVisual = new OutofBrowserMainPage();
10                 this.RootVisual = new OutofBrowserMusicPlayer();
11             }
12             
13 }

 

 

根据实例需求,我们最主要的功能就是播放音乐,所以,我们第一步首先实现Out of Browser应用音频控制。

1. 创建自定义音频控制控件;

对于音频控制,这里我们使用了自定义控件控制音乐的播放。AudioControl.xaml控件,

 
这里我仅贴上部分代码,大家可以在文章最后下载完整源代码。

 

 1 <Grid x:Name="LayoutRoot">
 2         <Grid.ColumnDefinitions>
 3             <ColumnDefinition Width="Auto" />
 4             <ColumnDefinition Width="*" />
 5             <ColumnDefinition Width="25" />
 6             <ColumnDefinition Width="Auto" />
 7             <ColumnDefinition Width="Auto" />
 8         </Grid.ColumnDefinitions>
 9         <Grid Grid.Column="0" Margin="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" x:Name="gridCol1">
10             <ToggleButton Cursor="Hand" Margin="0,0,0,0" x:Name="btnPlay" RenderTransformOrigin="0.5,0.5" Template="{StaticResource playControlTemplate}">
11                 <ToggleButton.RenderTransform>
12                     <TransformGroup>
13                         <ScaleTransform ScaleX="1" ScaleY="1"/>
14                         <SkewTransform/>
15                         <RotateTransform/>
16                         <TranslateTransform/>
17                     </TransformGroup>
18                 </ToggleButton.RenderTransform>
19             </ToggleButton>
20         </Grid>
21         <Grid Grid.Column="1" Margin="0,0,0,0" HorizontalAlignment="Stretch" x:Name="gridCol2" VerticalAlignment="Center">
22             <Grid.ColumnDefinitions>
23                 <ColumnDefinition Width="*" />
24                 <ColumnDefinition Width="40" />
25                 <ColumnDefinition Width="10" />
26                 <ColumnDefinition Width="40" />
27             </Grid.ColumnDefinitions>
28             <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"/>
29             <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"/>
30             <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"/>
31             <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"/>
32         </Grid>
33         <Grid Grid.Column="2" Margin="4,0,4,0" HorizontalAlignment="Stretch" x:Name="gridCol3" VerticalAlignment="Center">
34             <local:Spinner Margin="0,0,0,0" x:Name="spinner" Width="17" Height="17" HorizontalAlignment="Center" VerticalAlignment="Center"/>
35         </Grid>
36         <Grid Grid.Column="3" Margin="0,10.30,0,10.30" HorizontalAlignment="Stretch" x:Name="gridCol4" Width="70" VerticalAlignment="Stretch" d:LayoutOverrides="Height">
37             <Grid Margin="0,0,0,0" HorizontalAlignment="Right" VerticalAlignment="Center" Width="70">
38                 <Grid.ColumnDefinitions>
39                     <ColumnDefinition Width="Auto" />
40                     <ColumnDefinition Width="*" />
41                 </Grid.ColumnDefinitions>
42                 <ToggleButton HorizontalAlignment="Left" IsChecked="True" Margin="0,0,0,0" x:Name="btnSpeaker" Template="{StaticResource speakerControlTemplate}"/>
43                 <Slider Grid.Column="1" HorizontalAlignment="Stretch" Margin="3,0,0,0" VerticalAlignment="Center" Maximum="1" x:Name="sliderVolume" Style="{StaticResource volumeSliderStyle}" Background="#FF777777"/>
44             </Grid>
45         </Grid>
46         <Grid Grid.Column="4" Margin="0,10.3120002746582,4,10.3120002746582" HorizontalAlignment="Right" x:Name="gridCol5" VerticalAlignment="Stretch" d:LayoutOverrides="Height">
47             <ToggleButton Cursor="Hand" HorizontalAlignment="Left" Margin="0,0,0,0" x:Name="btnFullScreen" Template="{StaticResource fullScreenControlTemplate}"/>
48         </Grid>
49     </Grid>

 

 

从以上代码可以看到,在AudioControl中有两个自定义控件local:MediaSliderlocal:Spinner

MediaSlider:

其功能是控制音乐播放进度,支持拖拽前进或者后退音乐播放进度。其代码如下:

 

  1 public class MediaSlider : Slider
  2     {
  3         public Thumb              horizontalThumb;
  4         private FrameworkElement horizontalLeftTrack;
  5         private FrameworkElement horizontalRightTrack;
  6         private double oldValue = 0, newValue = 0, prevNewValue = 0;
  7         public event RoutedPropertyChangedEventHandler<double> MyValueChanged;
  8         public event RoutedPropertyChangedEventHandler<double> MyValueChangedInDrag;
  9         private DispatcherTimer dragtimer = new DispatcherTimer();
 10         private double dragTimeElapsed = 0;
 11         private const short DragWaitThreshold = 200, DragWaitInterval = 100;
 12         public Rectangle progressRect = null;
 13         private bool dragSeekJustFired = false;
 14 
 15         public MediaSlider()
 16         {
 17 
 18             this.ValueChanged += new RoutedPropertyChangedEventHandler<double>(CustomSlider_ValueChanged);
 19             dragtimer.Interval = new TimeSpan(0000, DragWaitInterval);
 20             dragtimer.Tick += new EventHandler(dragtimer_Tick);
 21         }
 22 
 23         void dragtimer_Tick(object sender, EventArgs e)
 24         {
 25             dragTimeElapsed += DragWaitInterval;
 26 
 27             if (dragTimeElapsed >= DragWaitThreshold)
 28             {
 29                 RoutedPropertyChangedEventHandler<double> handler = MyValueChangedInDrag;
 30                 
 31                 if ((handler != null&& (newValue != prevNewValue))
 32                 {
 33                     handler(thisnew RoutedPropertyChangedEventArgs<double>(oldValue, newValue));
 34                     dragSeekJustFired = true;
 35                     prevNewValue = newValue;
 36                 }
 37 
 38                 dragTimeElapsed = 0;
 39             }
 40         }
 41 
 42         void CustomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
 43         {
 44             oldValue = e.OldValue;
 45             newValue = e.NewValue;
 46 
 47             if (horizontalThumb.IsDragging)
 48             {
 49                 dragTimeElapsed = 0;
 50                 dragtimer.Stop();
 51                 dragtimer.Start();
 52                 dragSeekJustFired = false;
 53             }
 54         }
 55 
 56         public override void OnApplyTemplate()
 57         {
 58             base.OnApplyTemplate();
 59 
 60             horizontalThumb = GetTemplateChild("HorizontalThumb"as Thumb;
 61             horizontalLeftTrack = GetTemplateChild("LeftTrack"as FrameworkElement;
 62             horizontalRightTrack = GetTemplateChild("RightTrack"as FrameworkElement;
 63             progressRect = GetTemplateChild("Progress"as Rectangle;
 64 
 65             if (horizontalLeftTrack != null) horizontalLeftTrack.MouseLeftButtonDown += new MouseButtonEventHandler(OnMoveThumbToMouse);
 66 
 67             if (horizontalRightTrack != null) horizontalRightTrack.MouseLeftButtonDown += new MouseButtonEventHandler(OnMoveThumbToMouse);
 68 
 69             horizontalThumb.DragCompleted += new DragCompletedEventHandler(DragCompleted);
 70 
 71             progressRect.Width = this.Width;
 72         }
 73 
 74         public Storyboard ProgressStoryboard { get { return (GetTemplateChild("ProgressStoryboard"as Storyboard); } }
 75 
 76         public Rectangle ProgressBar { get { return (GetTemplateChild("Progress"as Rectangle); } }
 77 
 78         protected override Size ArrangeOverride(Size finalSize)
 79         {
 80             Size s = base.ArrangeOverride(finalSize);
 81 
 82             if (double.IsNaN(horizontalThumb.Width) && (horizontalThumb.ActualWidth != 0))
 83             {
 84                 horizontalThumb.Width = horizontalThumb.ActualWidth;
 85             }
 86 
 87             if (double.IsNaN(horizontalThumb.Height) && (horizontalThumb.ActualHeight != 0))
 88             {
 89                 horizontalThumb.Height = horizontalThumb.ActualHeight;
 90             }
 91 
 92             if (double.IsNaN(horizontalThumb.Width)) horizontalThumb.Width = horizontalThumb.Height;
 93             if (double.IsNaN(horizontalThumb.Height)) horizontalThumb.Height = horizontalThumb.Width;
 94 
 95             return (s);
 96         }
 97         
 98         private void OnMoveThumbToMouse(object sender, MouseButtonEventArgs e)
 99         {
100             e.Handled = true;
101             Point p = e.GetPosition(this);
102 
103             if (this.Orientation == Orientation.Horizontal)
104             {
105                 Value = (p.X - (horizontalThumb.ActualWidth / 2)) / (ActualWidth - horizontalThumb.ActualWidth) * Maximum;
106             }
107 
108             RoutedPropertyChangedEventHandler<double> handler = MyValueChanged;
109 
110             if (handler != null)
111             {
112                 handler(thisnew RoutedPropertyChangedEventArgs<double>(oldValue, Value));
113             }
114         }
115 
116         private void DragCompleted(object sender, DragCompletedEventArgs e)
117         {
118             dragtimer.Stop();
119             dragTimeElapsed = 0;
120 
121             RoutedPropertyChangedEventHandler<double> handler = MyValueChanged;
122 
123             if ((handler != null&& (!dragSeekJustFired))
124             {
125                 handler(thisnew RoutedPropertyChangedEventArgs<double>(oldValue, this.Value));
126             }
127         }
128     }

 

 

而Spinner控件,是一个载入标识,当音频载入时,会显示该控件。该控件为Path绘制的控件,这里不再贴出代码描述。

 

2. 获取音频文件信息部分

该部分我们同样也创建一个自定义控件来实现,TrackInfo.xaml,主要是负责在客户端显示音频文件的信息,而Silverlight没有相关API可以实现读取音频文件的标签信息,这里,我们需要引入一个微软开源类库TagLib。该类库的主要功能就是读取和修改音乐文件的标签信息。

其调用方法非常简单:

 

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应用权限信任。

 1 public ImageSource AlbumArtStream
 2 {
 3             get
 4             {
 5                 BitmapImage image;
 6 
 7                 if (string.IsNullOrEmpty(AlbumArtPath))
 8                 {
 9                     if (null == _default)
10                     {
11                         _default = new BitmapImage(new Uri("../Images/Music.png", UriKind.Relative));
12                     }
13 
14                     image = _default;
15                 }
16                 else
17                 {
18                     FileStream stream = File.Open(AlbumArtPath, FileMode.Open, FileAccess.Read);
19 
20                     image = new BitmapImage();
21                     image.SetSource(stream);
22                     stream.Close();
23                 }
24 
25                 return image;
26             }
27 }

 

 

4. 获取音频文件列表

从演示图片可以看出,我们的音频文件列表,是用了一个绑定了音乐播放文件信息的Datagrid。

其代码非常简单,创建两列,分别绑定歌手和歌曲名:

 1 <data:DataGrid x:Name="playList"
 2                        Grid.Row="1"
 3                        Grid.Column="1"
 4                        Grid.RowSpan="3"
 5                        VerticalAlignment="Top"
 6                        Margin="4"
 7                        Height="296"
 8                        Style="{StaticResource DataGridStyle}"
 9                        AutoGenerateColumns="False"
10                        CanUserResizeColumns="True"
11                        CanUserSortColumns="False"
12                        SelectionChanged="playList_SelectionChanged">
13             <data:DataGrid.Columns>
14                 <data:DataGridTextColumn Header="歌手"
15                                          Binding="{Binding Artist}"
16                                          FontSize="12" />
17                 <data:DataGridTextColumn Header="歌名"
18                                          Binding="{Binding Title}"
19                                          FontSize="12"
20                                          Width="*" />
21             </data:DataGrid.Columns>
22 </data:DataGrid>

 

而后台,在读取了My Music目录后,将数据集绑定到datagrid.ItemsSource就可以正常实现歌曲列表了。

 1 public static List<MediaFile> GetMediaFiles()
 2 {
 3             List<MediaFile> files = null; ;
 4             MediaFile mf;
 5             string path = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
 6             IEnumerable<string> list = Directory.EnumerateFiles(path, "*.mp3", SearchOption.AllDirectories);
 7             TagLib.File tags;
 8             files = GetCachedList(list);
 9             if (null == files || files.Count == 0)
10             {
11                 files = new List<MediaFile>();
12                 foreach (string file in list)
13                 {
14                     mf = new MediaFile();
15                     mf.ID = file;
16                     mf.AlbumArtPath = GetAlbumArtPath(file);
17                     files.Add(mf);
18                 }
19 
20                 for (int idx = 0; idx < files.Count; idx++)
21                 {
22                     mf = files[idx];
23                     tags = TagLib.File.Create(mf.ID);
24                     mf.Artist = tags.Tag.FirstPerformer;
25                     mf.Title = tags.Tag.Title;
26                     mf.Album = tags.Tag.Album;
27                     mf.Genre = tags.Tag.FirstGenre;
28                 }
29                 SaveCachedList(files);
30             }
31 
32             return files;
33 }

 

 

在绑定成功后,同时,我们支持用户选择指定音乐播放,使用Datagrid的SelectionChanged事件即可。

 

 1 private void playList_SelectionChanged(object sender, SelectionChangedEventArgs e)
 2 {
 3             DataGrid dg = (sender as DataGrid);
 4 
 5             if (dg.SelectedIndex != _nowPlaying)
 6             {
 7                 if (dg.SelectedIndex != 0)
 8                 {
 9                     me.AutoPlay = true;
10                 }
11                 OpenAndPlay(dg.SelectedIndex);
12             }
13             
14 }

 

 

5. UI控制

对于UI的控制,这里我们只是简单的实现了隐藏和显示音乐信息框的功能,其代码实现:

 

 1         private void Minimize_Click(object sender, MouseButtonEventArgs e)
 2         {
 3             Window main = Application.Current.MainWindow;
 4 
 5             if (!_min)
 6             {
 7                 main.Height = 40;
 8                 rot.Angle = 0;
 9             }
10             else
11             {
12                 main.Height = 340;
13                 rot.Angle = 180;
14             }
15 
16             _min = !_min;
17         }

 

 

上面是OOB音乐播放器5个部分的核心功能代码,这里,我想同时将上一篇讲到的Notifications窗口应用到实例中,我们可以仍旧使用NotificationControl文件,在其中对播放音乐Title进行绑定,即当音乐播放完毕后,即弹出消息提示播放下一首“XXX”音乐。效果如下图:

根据上一篇介绍Notifications窗口的代码,我们简单进行修改,即可实现本篇实例需求:

 

 1         NotificationWindow notifyWindow = null;
 2         private void ShowToast()
 3         {
 4             notifyWindow = new NotificationWindow();
 5 
 6             if (notifyWindow.Visibility == Visibility.Visible)
 7                 notifyWindow.Close();
 8 
 9             NotificationControl myNotify = new NotificationControl();
10             myNotify.DataContext = _playList[_nowPlaying];
11             notifyWindow.Width = 300;
12             notifyWindow.Height = 100;
13             notifyWindow.Content = myNotify;
14             notifyWindow.Show(10000);
15         }

 

至此,一款基于Silverlight的Out of Browser模式的音乐播放器基本完成了。大家可以根据该实例添加更多自定义功能,例如添加互联网音乐播放功能,音乐搜索功能等,创建属于自己的Silverlight版酷我音乐盒。

 

本篇源代码下载

 

欢迎大家加入"专注Silverlight" 技术讨论群:

32679955(六群)
23413513(五群)
32679922(四群)
100844510(三群)
37891947(二群)
22308706(一群)