NaiveMediaPlayer2.0

介绍

在之前的基础上添加两个功能,一个是从URL源直接播放文件,另一个是先缓存在系统音乐文件夹下然后再播放。
经历一番波折之后完成了NaiveMediaPlayer2.0。打开首界面如下
mainpage
在CommandBar添加了一个按钮,点击会出现二级界面
secondaryPage
在文本框中输入URL,点击justPlay按钮,播放器开始播放NEUSong并显示相应title,直接播放,所以本地音乐文件夹没有文件。
justPlay
点击cacheThenPlay按钮,稍等片刻,文件即会被下载到本地音乐文件夹,可在播放器开始播放。
cache

从URL源直接播放

首先想到的就是能否直接从URL源播放文件。根据上次测试播放本地文件的经验,试着将本地的URI改为给定的URL,写出如下代码,尝试能否播放。

1
2
3
4
5
6
var mediaSource = MediaSource.CreateFromUri(new Uri("http://www.neu.edu.cn/indexsource/neusong.mp3"));
Title.Text = mediaSource.Uri.ToString();
Title.Text = Title.Text.Substring(Title.Text.LastIndexOf('/') + 1);
myPlayer.Source = mediaSource;
mediaSimple.SetMediaPlayer(myPlayer);

运行可以直接播放。第一个功能完成。

创建新窗口

然后开始想怎样设计交互方式来实现对两个功能的选择,类比于openFile时在新窗口选择文件,所以初步设想是点击playOnline按钮后,弹出新窗口。
在新窗口放一个pivot,有justPlay,cacheThenPlay两个item,做成类似于选项卡的效果。于是参考微软文档 Multi-views 和一篇blog写出如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void AppBarButton_Click_1(object sender, RoutedEventArgs e)
{
CoreApplicationView newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Frame frame = new Frame();
frame.Navigate(sourcePageType: typeof(SecondaryPage));
Window.Current.Content = frame;
// You have to activate the window in order to show it later.
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}

多窗口就是在多个view之间切换,将新Page通过Window.Current.Activate()设为active状态。至此已经可以通过点击按钮弹出新窗口了。在新的页面设置布局,参考微软 Pivot 文档写出如下代码

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
41
42
43
44
45
<Page
x:Class="NaiveMediaPlayer.SecondaryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:NaiveMediaPlayer"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Pivot Title="playOnlineSource">
<PivotItem Header="JustPlay">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="input1" Header="URL:" PlaceholderText="http://..." Grid.Row="1"/>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Content="OK" Click ="Button_Click_1" Margin="50"/>
<Button Content="Cancel" Click ="Button_Click_2" Margin="50"/>
</StackPanel>
</Grid>
</PivotItem>
<PivotItem Header="CacheThenPlay">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name ="input2" Header="URL:" PlaceholderText="http://..." Grid.Row="1"/>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Content="OK" Click ="Button_Click_3" Margin="50"/>
<Button Content="Cancel" Click ="Button_Click_4" Margin="50"/>
</StackPanel>
</Grid>
</PivotItem>
</Pivot>
</Grid>
</Page>

每个pivot item 都有一个Textbox来输入URL,下方是两个按钮OK, Cancel。点击取消按钮时应该将新的窗口关闭,写这个事件时完全找不到参考的资料啥的,最后还是类比着Window.Current.Activate(),挨着看Window的方法列表。终于找到它!

1
2
3
4
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Window.Current.Close();
}

然后在写OK按钮的时候,发现自己深陷传值的大坑无法自拔。参考各种博客尝试了传参

1
frame.Navigate(sourcePageType: typeof(SecondaryPage),Parameter: mediaPlayer);

但是不行,新page就是没有这个变量。又尝试声明全局变量,是避免传参的问题了,但是在一开始就把MediaPlayerElement和空Source的MediaPlayer绑定了,之后再修改Source仍然不能播放。

这个问题,第二天决定换个交互方式来解决,不用新窗口,直接弹一个spliteView来搞。将两个功能变成两个按钮,同样是二选一的逻辑。
在MainPage.xaml中添加如下代码:

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
<SplitView IsPaneOpen="False"
DisplayMode="Inline"
PanePlacement="Right"
OpenPaneLength="400" Name="choice" Grid.Row="1" >
<SplitView.Pane>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Name ="inputUrl" Header="URL:" PlaceholderText="http://..." Grid.Row="1" Grid.ColumnSpan="2"
Width ="268"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Button Content="justPlay" Click ="Button_Click" Margin="50" Grid.Row="2" Grid.Column="0"/>
<Button Content="cacheThenPlay" Click ="Button_Click_1" Margin="50" Grid.Row="2" Grid.Column="1"/>
</Grid>
</SplitView.Pane>
</SplitView>

仍然不明白,讲道理Window和page用的都是一个navigate 方法,page间直接传参就行,怎么这个window就不可以吶。可能是当时写的有问题。不过这里传参只能传一个,还是不能实现之前设想的效果。还是换spliteView吧。

缓存文件

缓存文件就是读写文件,通过参看微软 Create, write, and read a file 文档,需要先new 一个 folder,然后调用folder的方法在其路径下新建文件。
又参考微软 Files and folders in the Music, Pictures, and Videos libraries 文档得知可用如下代码获得Folder:

1
var myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync(Windows.Storage.KnownLibraryId.Pictures);

写出如下代码:

1
2
3
4
5
6
7
8
9
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
choice.IsPaneOpen = false;
String inputU = inputUrl.Text.ToString();
if (inputU == "") return;
var myfolder = KnownFolders.MusicLibrary;
String fileName = inputU.Substring(inputU.LastIndexOf('/') + 1);
StorageFile cacheFile = await myfolder.CreateFileAsync(fileName,CreationCollisionOption.ReplaceExisting);
}

但是只有这样是不行的,又参考这篇blog了解到,想要使用系统文件夹,需要设置应用程序-包清单-勾选音乐库。解决权限不够的问题。
创建文件之后怎么将URL源的文件写入创建的文件呢?研究了好大会儿写文件,但是没有从URL源直接写的。然后尝试直接从URL源创建文件,也没啥资料,就是各种试StorageFile的方法列表里带Uri的方法。找到一个StorageFile.CreateStreamedFileFromUriAsync()的方法,然鹅直接用会抛异常。
然后就又搜索这个方法的资料,然后!非常意外的在Stack Overflow找到了另一种解决方法:

1
2
3
4
5
StorageFolder fileFolder = await ApplicationData.Current.LocalFolder.GetFolderAsync("MyFiles");
StorageFile file =await fileFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
var download = new BackgroundDownloader().CreateDownload(source, file);
await download.StartAsync();

从源直接下载到目标文件,非常nice。写出如下代码解决了第二个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
choice.IsPaneOpen = false;
String inputU = inputUrl.Text.ToString();
if (inputU == "") return;
var myfolder = KnownFolders.MusicLibrary;
String fileName = inputU.Substring(inputU.LastIndexOf('/') + 1);
StorageFile cacheFile = await myfolder.CreateFileAsync(fileName,CreationCollisionOption.ReplaceExisting);
var download = new BackgroundDownloader().CreateDownload(new Uri(inputU), cacheFile);
await download.StartAsync();
var cacheMediaSource = MediaSource.CreateFromStorageFile(cacheFile);
myPlayer.Source = cacheMediaSource;
mediaSimple.SetMediaPlayer(myPlayer);
Title.Text = fileName;
}

总结

出现错误时还是要认真研究报错信息哇,这样才能更快解决问题;
回看代码感觉犯了老师第一节课说的错误,一个button为什么要知道怎么获取json哇。同样这里一个button为什么要知道怎么从URL下载文件,怎么创建文件,设置播放器哇。没有好好封装起来。敲代码的时候只是简单的想着实现功能就好,没有考虑代码的结构啥的。