你好

UWP学习(四) -- Naïve MondrianPlayer Pro

GitHub项目地址

成果展示


技术问题

作为naive player1.0 的升级版,这一次主要加入两个功能,即在线媒体资源的播放和缓存。有了互联网的活力使它不再那样Naive。做完之后确实觉得:我的播放器终于有那么一丢丢流动的血液了..

下面介绍本次作业遇到的技术问题。


1. 播放在线url资源

在开始做这部分功能之前,我认为这可能是最复杂的一部分,我需要解析url吗?怎么提到url里面的媒体文件呢…边缓存边播放是怎么做到的呢?但是我记得老师上课说过这部分非常简单,所以一定是我想多了。之后知道了我们要实现就是uri资源的播放,这一下就简单很多了,既定的mediaPlayer.source一下就有着落了。先不管其他的,查一波微软官方uri文档再说。

Uri文档:https://docs.microsoft.com/en-us/uwp/api/windows.foundation.uri

https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.mediaplayerelement

1
2
3
MediaPlayerElement mediaPlayerElement1 = new MediaPlayerElement();
mediaPlayerElement1.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Media/video1.mp4"));
mediaPlayerElement1.AutoPlay = true;

通过这一段代码我找到了实现方法,于是很容易写出了我的代码:

1
2
3
4
5
6
7
8
9
var media_url = url_input.Text.Trim();  //去掉uri里的空格
string str = media_url.Substring(media_url.Length - 3);
string str1 = media_url.Substring(0, 4);
if(!string.Equals(str1,"http"))
{
media_url = "http://" + media_url;
}
my_player.Source = MediaSource.CreateFromUri(new Uri(media_url)); //媒体源设置为uri源
my_player.MediaPlayer.Play();

这里还得注意一个坑:uri资源链接头部若不是以http开头的话,解析uri媒体源的时候会炸掉!所以以www或者其他形式开头的,为了兼容性更强,请务必编程手动加上‘http://’的协议header。

写完后迫不及待贴一个mp4的uri进去试试!哇,我的naive player也让可以在线播放视频了。。
至于之前想的怎么边缓存边播放?这一切原来MediaPlayerElement都已经封装实现好了,不需要我瞎操心,棒。
第一个大任务完成。


2. 下载资源

第二个大任务,缓存uri媒体资源。首先我想我得找到下载这个文件的方法,然后再使用根文件写入相关的类把文件写到指定文件夹,就实现了缓存。
首先在微软官方文档发现了一个叫BackgroundTransfer的东西,不认识啊,不过看示例代码似乎很靠谱。先贴过来试了一下。

https://docs.microsoft.com/en-us/uwp/api/windows.networking.backgroundtransfer.downloadoperation

示例代码:

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
using Windows.Foundation;
using Windows.Networking.BackgroundTransfer;
using Windows.Storage;

private async void StartDownload_Click(object sender, RoutedEventArgs e)
{
try
{
Uri source = new Uri(serverAddressField.Text.Trim());
string destination = fileNameField.Text.Trim();

StorageFile destinationFile = await KnownFolders.PicturesLibrary.CreateFileAsync(
destination, CreationCollisionOption.GenerateUniqueName);

BackgroundDownloader downloader = new BackgroundDownloader();
DownloadOperation download = downloader.CreateDownload(source, destinationFile);

// Attach progress and completion handlers.
HandleDownloadAsync(download, true);
}
catch (Exception ex)
{
LogException("Download Error", ex);
}
}

emm结果,不好使。网上也不好找这个类的使用方法实例,我准备弃坑了..投入经典的HttpClient怀抱吧..

http
https://docs.microsoft.com/en-us/uwp/api/windows.web.http.httpclient

后来还是发现我太菜了,看不懂。为什么就没有一个完美的实例呢。。。微软文档为了照顾他们实现的类的多种强大接口,超级多的接口示例,且每个接口文档就一小段,但是我只想要一个最naive最simple和完整的,把一个故事讲好怎么就这么难呢。除了微软爸爸,
还好我有外援
https://www.cnblogs.com/T-ARF/p/5886153.html

保障先放在这里,继续去看写入文件夹相关实现。


3. 缓存到指定目录

搞定了下载,再找到写文件到指定文件夹的方法就完成基本任务了。
继续官方文档走起。

https://docs.microsoft.com/en-us/windows/uwp/files/quickstart-managing-folders-in-the-music-pictures-and-videos-libraries

示例代码:

1
2
3
StorageFolder storageFolder = KnownFolders.PicturesLibrary;
StorageFile file = await storageFolder.CreateFileAsync("sample.png", CreationCollisionOption.ReplaceExisting);
// Do something with the new file.

这次体验很不错,因为无意中发现了KnownFolders.PictureLibrary这个东西。这难道不像要求的指定到Music文件夹吗??我觉得我只要将PictureLibrary换成MusicLibrary就行了。发现新大陆真是太好,开始以为要进行一堆字符串匹配文件夹名呢.. 迅速去KnownFolders走一波:

https://docs.microsoft.com/en-us/uwp/api/Windows.Storage.KnownFolders#Windows_Storage_KnownFolders_MusicLibrary

先不高兴太早,坑又来了。权限问题。不会解释了,直接到Packages.appxanifest>功能>音乐 打上勾吧。。我很懵逼。(这一点是问了同学才知道的)

添加完权限后,我的实现代码:

1
2
3
4
5
6
7
var uri = url_input.Text.Trim();   //获取在线文件uri
var media_url = new Uri(uri);
var MusicFileLocation = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Music);
//定位到默认Music文件夹
var MusicFolder = MusicFileLocation.SaveFolder; //将Music设为读入文件目录
var file_name = Path.GetFileName(media_url.LocalPath);
StorageFile music_file = await MusicFolder.CreateFileAsync(file_name, CreationCollisionOption.FailIfExists);

整合以上两部分,所以从下载资源到将文件写入到指定目录(Music)的完整代码为:

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
46
47
async private Task Download_Save_media()
{
var uri = url_input.Text.Trim();
var media_url = new Uri(uri); //媒体下载源
var MusicFileLocation = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Music); //获取本地Music文件夹路径
var MusicFolder = MusicFileLocation.SaveFolder; //设置Music文件夹为写入文件夹
var dialog = new ContentDialog()
{
Title = "提示",
Content = "下载情况",
PrimaryButtonText = "确定",
// SecondaryButtonText = "取消",
FullSizeDesired = false,
};
try
{
var file_name = Path.GetFileName(media_url.LocalPath); //设置写入媒体文件名
StorageFile music_file = await MusicFolder.CreateFileAsync(file_name, CreationCollisionOption.FailIfExists);
if (music_file != null)
{
HttpClient httpClient = new HttpClient();
IBuffer buffer;
try
{
buffer = await httpClient.GetBufferAsync(media_url);
await FileIO.WriteBufferAsync(music_file, buffer); //写入指定目录

dialog.Content = "下载完成!";
dialog.PrimaryButtonClick += (_s, _e) => { };
await dialog.ShowAsync();
}
catch (Exception ex)
{
dialog.Content = "未找到下载源! 下载失败";
dialog.PrimaryButtonClick += (_s, _e) => { };
await dialog.ShowAsync();
}
}
}
catch (Exception ex)
{
dialog.Content = "下载失败!未找到下载源或已存在文件";
dialog.PrimaryButtonClick += (_s, _e) => { };
await dialog.ShowAsync();
Debug.WriteLine("A file has existed.");
}
}

4. 从缓存目录读取资源

稍微扩展一下,让用户可以直接播放刚才缓存的文件。

其实之前的FileOpenPicker技术和代码就能完全解决,只是需要增加默认打开Music那个缓存文件目录这个特性。
继续参考FileOpenPicker这个文档:

https://docs.microsoft.com/en-us/uwp/api/Windows.Storage.Pickers.FileOpenPicker

这次收获依然很佳。发现了openPicker.SuggestedStartLocation这个东西。这就是选择推荐打开的文件夹,很棒。这段示例代码赞一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.Thumbnail;
openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");

StorageFile file = await openPicker.PickSingleFileAsync();
if (file != null)
{
// Application now has read/write access to the picked file
OutputTextBlock.Text = "Picked photo: " + file.Name;
}
else
{
OutputTextBlock.Text = "Operation cancelled.";
}

很容易的,我的代码:

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
async private Task choose_cache()
{
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.FileTypeFilter.Add(".mp3");
openPicker.FileTypeFilter.Add(".wma");
openPicker.FileTypeFilter.Add(".mp4");
openPicker.FileTypeFilter.Add(".wmv");
//设置推荐文件夹为缓存路径Music文件夹
openPicker.SuggestedStartLocation = PickerLocationId.MusicLibrary;
var file = await openPicker.PickSingleFileAsync();
if (file != null)
{
//播放器标题栏设为文件名,文件名可通过file的属性值直接获取
media_title.Text = file.Name;
my_player.Source = MediaSource.CreateFromStorageFile(file);
my_player.MediaPlayer.Play();
Debug.WriteLine(file.FileType);
if(file.FileType == ".mp3" || file.FileType ==".wma" )
{
mp3_logo.Opacity = 1;
}
else if(file.FileType == ".mp4" || file.FileType == ".wmv")
{
mp3_logo.Opacity = 0;
}
hideWelcomeUI();
}
}

5. 其他技术

至此,所以功能全部完成,加鸡腿。但是下载的时候没有提示,下载出现的异常也没有提示,似乎有点不人性化。所以加一个Dialog吧。搜索了解到uwp dialog主要有messageDialog和contentDialog,这里使用contenDialog。

https://www.cnblogs.com/TianFang/p/4857205.html
示例代码很简单,直接贴上我的代码:

1
2
3
4
5
6
7
8
9
var dialog = new ContentDialog()
{
Title = "提示",
Content = "下载情况",
PrimaryButtonText = "确定",
FullSizeDesired = false,
};
dialog.PrimaryButtonClick += (_s, _e) => { };
await dialog.ShowAsync();

挖个小坑,下次加入下载进度条提示的功能。

⬅️ Go back