
在[上一篇](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"/>
~~~
在了解了音頻播放類的簡單使用后,讓我們先看看項目完成后的效果圖,

從上面效果圖中可以看出整個實例項目UI分5個部分,
1. 音頻控制部分,這部分是實例主要功能;
2. 音頻文件信息部分,這部分是獲取顯示當前和下一首音樂文件信息;
3. 唱片圖片信息,其實這部分也是屬于音頻文件信息,不過這里單獨列出來,使用獨立的類進行處理;
4. 音頻文件列表,該列表是載入My Music目錄中的音樂文件,并支持用戶選擇播放功能;
5. UI控制,該部分可以使播放器進入最小化狀態。例如:

下面我們開始分別解釋以上幾個部分的實例設計方法。
我們仍舊使用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. 創建自定義音頻控制控件;**

對于音頻控制,這里我們使用了自定義控件控制音樂的播放。AudioControl.xaml控件,

這里我僅貼上部分代碼,大家可以在文章最后下載完整源代碼。
~~~
??<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:

其功能是控制音樂播放進度,支持拖拽前進或者后退音樂播放進度。其代碼如下:
~~~
???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. 獲取音頻文件信息部分**

該部分我們同樣也創建一個自定義控件來實現,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應用權限信任。
~~~
??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。

其代碼非常簡單,創建兩列,分別綁定歌手和歌曲名:
~~~
??<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”音樂。效果如下圖:

根據上一篇介紹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版酷我音樂盒。

[本篇源代碼下載](http://www.silverlightchina.net/resource/code/SilverlightOOBDemo0727.zip)
歡迎大家加入"專注Silverlight" 技術討論群:
32679955(六群)
23413513(五群)
32679922(四群)
100844510(三群)
37891947(二群)
22308706(一群)
- 前言
- Out of Browser開篇
- Out of Browser配置,安裝和卸載
- Out of Browser的自定義應用
- Out of Browser存取本地文件系統
- Out of Browser與COM的交互基礎
- Out of Browser與Office的互操作
- Out of Browser的Debug和Notifications窗口
- Out of Browser音樂播放器
- Out of Browser與COM互操作實例
- Out of Browser在線更新和Silent安裝
- Validation數據驗證開篇
- Validation數據驗證基礎屬性和事件
- Validation數據驗證DataAnnotation機制和調試技巧
- Validation服務器端異步數據驗證
- Validation客戶端同步數據驗證
- Validation用戶提交數據驗證捕獲
- Datagrid,Dataform數據驗證和ValidationSummary
- 自定義擴展Validation類,驗證框架的總結和建議
- Navigation導航框架開篇
- 理解Navigation導航框架Frame類
- 理解Navigation導航框架Page類
- Navigation導航框架URI映射機制
- Navigation導航框架傳遞參數