
在上一篇,我们了解了如何在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:MediaSlider和local: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(
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。该类库的主要功能就是读取和修改音乐文件的标签信息。

其调用方法非常简单:
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(一群)