Silverlight實例教程 - Out of Browser音樂播放器
Silverlight 實例教程索引
- Silverlight 實例教程 - Out of Browser開篇
- Silverlight 實例教程 - Out of Browser配置,安裝和卸載
- Silverlight 實例教程 - Out of Browser的自定義應用
- Silverlight 實例教程 - Out of Browser存取本地文件系統
- Silverlight 實例教程 - Out of Browser與COM的交互基礎
- Silverlight 實例教程 - Out of Browser與Office的互操作
- Silverlight 實例教程 - Out of Browser的Debug和Notifications窗口
- Silverlight 實例教程 - Out of Browser音樂播放器
- Silverlight 實例教程 - Out of Browser與COM互操作實例
- Silverlight 實例教程 - Out of Browser在線更新和Silent安裝
本篇,我們將結合以往的Out of Browser特性,創建一款新的Out of Browser實例, 音樂播放器。 該實例目的比較簡單,實現音樂播放,實現音樂文件列表讀取,實現音樂文件信息讀取,另外音樂播放自動跳轉等功能。
在實例開始前,我們仍舊需要了解一些基礎知識。Silverlight對音頻的支持是使用MediaElement類,該類使用方法非常簡單,該類的詳細解釋,請看MSDN 。
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中的啟動頁面代碼:
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控件。
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:MediaSlider和local:Spinner。
MediaSlider:
其功能是控制音樂播放進度,支持拖拽前進或者后退音樂播放進度。其代碼如下:
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(0, 0, 0, 0, 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(this, new 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(this, new 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(this, new RoutedPropertyChangedEventArgs<double>(oldValue, this.Value));
126 }
127 }
128 }
而Spinner控件,是一個載入標識,當音頻載入時,會顯示該控件。該控件為Path繪制的控件,這里不再貼出代碼描述。
2. 獲取音頻文件信息部分
該部分我們同樣也創建一個自定義控件來實現,TrackInfo.xaml,主要是負責在客戶端顯示音頻文件的信息,而Silverlight沒有相關API可以實現讀取音頻文件的標簽信息,這里,我們需要引入一個微軟開源類庫TagLib。該類庫的主要功能就是讀取和修改音樂文件的標簽信息。
其調用方法非常簡單:
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應用權限信任。
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。
其代碼非常簡單,創建兩列,分別綁定歌手和歌曲名:
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就可以正常實現歌曲列表了。
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事件即可。
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的控制,這里我們只是簡單的實現了隱藏和顯示音樂信息框的功能,其代碼實現:
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窗口的代碼,我們簡單進行修改,即可實現本篇實例需求:
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版酷我音樂盒。