Week8
Request
- 利用自定义控件控制视频的播放,暂停,快进,快退
- 实现视频播放的全屏与退出全屏
- 自制 slider 实现视频的进度条
- 本地选择多媒体文件(视频,音乐)进行播放
- 添加 slider 版本音量控制
- 实现封面旋转
- 添加键盘按键响应
Accomplish
强烈推荐Microsoft Documents-MediaElement#multiple-audio-tracks)。这是第一次在官方文档找到如此棒的一个指引文档。
关于 MediaElement 的 xml 代码,可以仿照上面的文档进行设置,下面主要讲讲具体实现的基础功能按钮的代码。
利用自定义控件控制视频的播放,暂停,快进,快退
函数中的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40```csharp
// Play media
private void MdeiaPlay(object sender, RoutedEventArgs e) {
// Reset the play rate to normal
if (myMediaPlayer.DefaultPlaybackRate != 1) {
myMediaPlayer.DefaultPlaybackRate = 1.0;
}
myMediaPlayer.Play();
if (Cover.Visibility == Visibility.Visible) {
RotateCover.Begin();
}
}
// Pause media
private void MediaPause(object sender, RoutedEventArgs e) {
myMediaPlayer.Pause();
if (Cover.Visibility == Visibility.Visible) {
RotateCover.Pause();
}
}
// Stop media
private void MediaStop(object sender, RoutedEventArgs e) {
myMediaPlayer.Stop();
if (Cover.Visibility == Visibility.Visible) {
RotateCover.Stop();
}
}
// Set back playing rate
private void MediaBack(object sender, RoutedEventArgs e) {
myMediaPlayer.DefaultPlaybackRate = -2.0;
myMediaPlayer.Play();
}
// Set forward playing rate
private void MediaForward(object sender, RoutedEventArgs e) {
myMediaPlayer.DefaultPlaybackRate = 2.0;
myMediaPlayer.Play();
}实现视频播放的全屏与退出全屏
这里的全屏为应用全屏。
为了实现退出全屏,需要存储全屏之前的 size,所以需要一个 previousSize 来存储。
全屏退出采取按键响应。对应在 keyUp 函数内调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// Set full screen or not
private void FullScreenToggle() {
this.IsFullScreen = !this.IsFullScreen;
if (this.IsFullScreen) {
TransportControlsPanel.Visibility = Visibility.Collapsed;
previousSize.Width = videoContainer.ActualWidth;
previousSize.Height = videoContainer.ActualHeight;
videoContainer.Width = Window.Current.Bounds.Width;
videoContainer.Height = Window.Current.Bounds.Height;
myMediaPlayer.Width = Window.Current.Bounds.Width;
myMediaPlayer.Height = Window.Current.Bounds.Height;
} else {
TransportControlsPanel.Visibility = Visibility.Visible;
videoContainer.Width = previousSize.Width;
videoContainer.Height = previousSize.Height;
myMediaPlayer.Width = previousSize.Width;
myMediaPlayer.Height = previousSize.Height;
}
}
//Start full screen
private void MediaFullScreen(object sender, RoutedEventArgs e) {
FullScreenToggle();
}
// Listen for [ESC] to exit full screen
if (IsFullScreen && e.Key == Windows.System.VirtualKey.Escape) {
FullScreenToggle();
}自制 slider 实现视频的进度条
在完整实现了官方文档之后,我很是怀疑,这个进度条,真的需要这么复杂吗?
先来讲讲官方文档的实现逻辑:- 实现一个 slider 控件作为进度条
- 实现 slider 控件的拖动分辨率
- 实现一个 timer 跟随 mediaElement 一起播放计时和暂停
- 实现一个 pointerPressed handler 将 mediaElement 的 position 值和 slider 值通过 timer 进行绑定
- 每次新载入一个 media 文件,重置 timer
- 初始化整个 APP 时,为 slider 注册 handler 事件
其中 slider 拖动分辨率可以调整,这里我将其进一步精化了,为了适应 media 文件的总时长。
代码太长了,而且逻辑层叠太过复杂,具体实现看代码文件,下面附上函数名。
关于对这个冗杂的实现的改进,在文末提出解决方案。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// Get the frequency of the steps on the slider's scale
private double SliderFrequency(TimeSpan time) {}
// Initialize the timer
private void SetupTimer() {}
// Keep timer in sync with media
private void TimerTick(object sender, object e) {}
// Start timer to count
private void StartTimer() {}
// Stop timer to count
private void StopTimer() {}
// slider pressed
void SliderPointerEntered(object sender, PointerRoutedEventArgs e) {}
// slider lost pressed
void SliderPointerCaptureLost(object sender, PointerRoutedEventArgs e) {}
void TimelineSliderValueChanged(object sender, RangeBaseValueChangedEventArgs e) {}
// Reset timer for slider
private void MyMediaOpened(object sender, RoutedEventArgs e) {}
// Listen for the media state and timer
private void MyMediaCurrentStateChanged(object sender, RoutedEventArgs e) {}
本地选择多媒体文件(视频,音乐)进行播放
选择多媒体文件并不困难,使用 FileOpenPicker 就好了。
需要注意的是,当读取的是音乐文件时,需要隐藏视频页面,显示 cover 页面(有点面包屑的意味,不需要跳转页面,省去开销)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// Select media to play
private async void MediaSelect(object sender, RoutedEventArgs e) {
FileOpenPicker picker = new FileOpenPicker();
/// Initialize the picture file type to take
picker.FileTypeFilter.Add(".mp4");
picker.FileTypeFilter.Add(".wmv");
picker.FileTypeFilter.Add(".wma");
picker.FileTypeFilter.Add(".mp3");
picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
StorageFile file = await picker.PickSingleFileAsync();
if (file != null) {
/// Load the selected picture
IRandomAccessStream ir = await file.OpenAsync(FileAccessMode.Read);
myMediaPlayer.SetSource(ir, file.ContentType);
myMediaPlayer.Play();
if (file.ContentType == "audio/mpeg") {
Cover.Visibility = Visibility.Visible;
} else {
Cover.Visibility = Visibility.Collapsed;
}
}
}添加 slider 版本音量控制
理解数据绑定就很容易了,就是 slider 值和 mediaElement 的音量值绑定。
Attention! 音量值为0 - 1,分割值为0.1,这是 mediaElement 官方类中给出的说明。所以,设置一下 slider 的值是必须的。(在这里被坑了很久,以为实现不了 slider 音量控制)1
2
3<Slider x:Name="volumeslider" Maximum="1" Minimum="0" StepFrequency="0.1"
Value="{x:Bind myMediaPlayer.Volume, Mode=TwoWay}">
</Slider>其中,UI 页面提供了音量按钮,所以,点击按钮直接静音,然后需要储存静音前的音量。逻辑与全屏类似,不赘述,实现代码见代码文件。
实现封面旋转
- 播放视频时,隐藏封面页面(在读取文件中已实现)
- 播放音乐时,封面开始旋转
- 暂停音乐时,封面旋转暂停
重置音乐或结束时,封面复位
xml 中实现动画代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<Ellipse x:Name="Cover" RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<CompositeTransform></CompositeTransform>
</Ellipse.RenderTransform>
<Ellipse.Resources>
<Storyboard x:Name="RotateCover" RepeatBehavior="Forever">
<DoubleAnimation Duration="0:0:20" To="360" Storyboard.TargetName="Cover"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)">
</DoubleAnimation>
</Storyboard>
</Ellipse.Resources>
<Ellipse.Fill>
<ImageBrush x:Name="picture" ImageSource="Assets/cover.jpg"></ImageBrush>
</Ellipse.Fill>
</Ellipse>在需要动画开始,暂停,复位的地方,调用其对应函数即可。如,音乐暂停时,动画暂停:
1
2
3
4
5
6
7// Pause media
private void MediaPause(object sender, RoutedEventArgs e) {
myMediaPlayer.Pause();
if (Cover.Visibility == Visibility.Visible) {
RotateCover.Pause();
}
}
添加键盘按键响应
作为一个多媒体播放应用,空格键播放暂停,上下方向按键调节音量是基础功能,所以添加了这几个基础按键响应。
mediaElement 提供了 keyUp 和 keyDown 两个接口,前者响应一次按键,后者响应持续按键。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// Keyboard listener for one press
private void VideoContainerKeyUp(object sender, KeyRoutedEventArgs e) {
// Listen for [Space] to stop or play media
if (e.Key == Windows.System.VirtualKey.Space) {
if (myMediaPlayer.CurrentState == MediaElementState.Playing) {
myMediaPlayer.Pause();
} else {
myMediaPlayer.Play();
}
}
// Listen for [Up] and [Down] to control volume
if (e.Key == Windows.System.VirtualKey.Up) {
if (myMediaPlayer.Volume < 1) {
myMediaPlayer.Volume += 0.1;
}
} else if (e.Key == Windows.System.VirtualKey.Down) {
if (myMediaPlayer.Volume > 0) {
myMediaPlayer.Volume -= 0.1;
}
}
e.Handled = true;
}
// Keyboard listener for holding press
private void VideoContainerKeyDown(object sender, KeyRoutedEventArgs e) {
// Listen for [Up] and [Down] to control volume
if (e.Key == Windows.System.VirtualKey.Up) {
if (myMediaPlayer.Volume < 1) {
myMediaPlayer.Volume += 0.1;
}
} else if (e.Key == Windows.System.VirtualKey.Down) {
if (myMediaPlayer.Volume > 0) {
myMediaPlayer.Volume -= 0.1;
}
}
}
成品图
后记
是的,像前面说的,官网文档提供的实现方法,太过复杂。有什么改进方法吗?
考虑值绑定。我们将 slider 的值和 mediaElement 的 position 值绑定在一起,就实现了这个进度条。
类似之前我们实现的 MyList 中 checkbox 和 line 的绑定,我们写一个 converter 将 position 这个 TimeSpan 类型的值 转换为 double 类型,就可以与 slider 值绑定了。