UWP版本的网易云音乐已经上架,虽然还不支持Windows Phone但是整体而言功能已经比较齐全了!
那么如何在Windows 10 UWP实现后台播放呢?
我之前是一直在做Windows Phone 8.0的开发,在8.0中为了实现后台播放音乐可使用后台音频代理,但是在Windows 10中会发现只有一个Windows Runtime Component...
(有必要补充一下的是我没有Windows Phone 8.1 的开发经历)
现在想把8.0的App迁移到Windows 10,所以必须要解决后台音频播放的问题。
废话就不多说了,下面进入正题。
.......让我再唠叨一句,学生党,第一次来博客园写东西没什么经验,各位多多原宥............
一:如何创建后台任务
下面先从最简单的开始(如果你已经创建过后台任务的可以跳过)
先新建一个Black App,然后添加一个Windows运行时组件和一个类库(为了共享一些代码)
然后添加BackgroundAudioTask对BackgroundAudioShare的引用,同时添加主项目对BackgroundAudioTask和BackgroundAudioShare的引用
最后在 appxmanifest添加声明,注意勾选Audio
注意下面这一张图片
EntryPoint出填写的是后台任务的 命名空间.类名
二:Windows10 中后台音频代理的工作方式
先来借用msdn的一张图片
2.1如何通信
之所以要和后台通信,最简单的答案是我需要将音乐列表,播放顺序之类的信息传递给后台。
图上左侧的是App的前台部分,右侧为后台部分,前台与后台通信的方式可以用过BackgroundMedia实例的SendMessageToForeground和SendMessageToBackground方法,与MessageReceivedFromForeground和MessageReceivedFromBackground事件来实现。
2.2通信时需要注意的事项
1.当用户第一次在前台中调用BackgroundMediaPlayer.Current或者是注册BackgroundMediaPlayer.MessageReceivedFromBackground事件的时候会出发IBackgroundTask.Run()方法,也就是后台任务开始执行。为了防止错过来自后台的消息,建议先注册BackgroundMediaPlayer.MessageReceivedFromBackground事件。
2.SendMessageToForeground方法的参数为一个ValueSet,同理在对应的MessageReceivedFromBackground事件中可以接收到这个ValueSet,这样的话我们就可以通过这个ValueSet传递我们想要的信息。
好了还是来看一下具体的实现过程:
我们先在BackgroundAudioShare工程中添加一个Models的文件夹,然后添加一个Music类
Music类的代码如下:
public class Music { public string Id { get; set; } public string Title { get; set; } public string Artist { get; set; } /// <summary> /// 指定音乐文件位置 /// </summary> public Uri MusicUri { get; set; } /// <summary> /// 用于在UAC中显示图片 /// </summary> public Uri AlbumUri { get; set; } }
我们希望通过使用Json的方式将数据序列化后传递给后台或前台,所以这里我们需要为BackgroundAudioShare工程添加Json.Net引用。
可打开Tools->Nuget Package Manager->Package Manager Console这个工具,输入
Install-Package Newtonsoft.Json
如下图,注意选择的工程为BackgroundAudioShare
或者是在BackgroundAudioShare工程的References上点击Manage Nuget Packages,搜索Json.Net并安装这里我就不给图片展示了。
我更喜欢第一种方式因为更加快速,各位可以选择喜欢的方式安装。
然后我们向主工程中添加两首音乐和两张图片,当然也可选择使用链接的方式这样就无需添加了,这个为了演示方便我添加两首本地音乐用作演示
首先我们在MainPage的后台文件中添加一个音乐列表
private List<Music> list = new List<Music>();
然后添加一个InitializeMusicList方法,并且在构造函数中调用
private void InitializeMusicList() { var m1 = new Music(); m1.Id = "1"; m1.Title = "Tell Me Why"; m1.Artist = "Declan Galbraith"; m1.AlbumUri = new Uri("ms-appx:///Assets/Music/Tell Me Why.jpg", UriKind.Absolute); m1.MusicUri = new Uri("ms-appx:///Assets/Music/Tell Me Why.mp3", UriKind.Absolute); var m2 = new Music(); m2.Id = "1"; m2.Title = "潮鸣"; m2.Artist = "Clannad"; m2.AlbumUri = new Uri("ms-appx:///Assets/Music/潮鸣.jpg", UriKind.Absolute); m2.MusicUri = new Uri("ms-appx:///Assets/Music/潮鸣.mp3", UriKind.Absolute); List.Add(m1); List.Add(m2); }
在前台添加一个Button用于开始播放
<Button Content="Play" Click="Button_Click"></Button>
好的为了更好地完成前台和后台的通信我们需要在共享工程中添加一些代码
首先是添加个Message文件夹
然后添加一个MessageType的枚举
命名空间是BackgroundAudioShare.Message
public enum MessageType { /// <summary> /// 更新音乐列表 /// </summary> UpdateMusicList, /// <summary> /// 下一曲 /// </summary> SkipToNext, /// <summary> /// 上一曲 /// </summary> SkipToPrevious, /// <summary> /// 用于调到指定的某一首 /// </summary> TackChanged, /// <summary> /// 开始播放 /// </summary> StartPlayMusic, /// <summary> /// 后台任务启动 /// </summary> BackgroundTaskStart }
再添加一个MessageService用户完成传递信息的任务
public static class MessageService { /// <summary> /// ValueSet的Key /// </summary> public const string MessageType = "MessageType"; /// <summary> /// ValueSet的Key /// </summary> public const string MessageBody = "MessageBody"; /// <summary> /// 向前台传送信息 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="message"></param> /// <param name="type"></param> public static void SendMessageToForeground(object message, MessageType type) { var payload = new ValueSet(); payload.Add(MessageType, type); payload.Add(MessageBody, JsonConvert.SerializeObject(message)); BackgroundMediaPlayer.SendMessageToForeground(payload); } /// <summary> /// 向后台传送信息 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="message"></param> /// <param name="type"></param> public static void SendMessageToBackground(object message, MessageType type) { var payload = new ValueSet(); payload.Add(MessageType, type); payload.Add(MessageBody, JsonConvert.SerializeObject(message)); BackgroundMediaPlayer.SendMessageToBackground(payload); } public static bool ParseMessage<T>(ValueSet valueSet, out T message) { object messageBodyValue; message = default(T); // Get message payload if (valueSet.TryGetValue(MessageService.MessageBody, out messageBodyValue)) { // Validate type message = JsonConvert.DeserializeObject<T>(messageBodyValue.ToString()); return true; } return false; } }
再添加一个更新音乐列表的Message
public class UpdateMusicListMessage { public List<Music> List { get; set; } public UpdateMusicListMessage(List<Music> list) { List = list; } }
最后再添加一个EnumHelper
命名空间为BackgroundAudioShare
public static class EnumHelper { public static T Parse<T>(string value) where T : struct => (T)Enum.Parse(typeof(T), value); public static T Parse<T>(int value) where T : struct => (T)Enum.Parse(typeof(T), value.ToString()); }
好的现在就可以开始从前台向后台传输信息了
我们继续在MainPage的后台文件中添加相应代码
首先添加一个属性一个字段
/// <summary> /// 获取当前实例的引用 /// </summary> private MediaPlayer CurrentPlayer { get { MediaPlayer player = null; try { player = BackgroundMediaPlayer.Current; } catch { Debug.WriteLine("Failed to get MediaPlayer instance"); return null; } return player; } } /// <summary> /// 用于等待后台任务开启 /// </summary> private AutoResetEvent _backgroundAudioTaskStarted = new AutoResetEvent(false);
然后添加Click的处理事件
private void Button_Click(object sender, RoutedEventArgs e) { StartbackgroundTask(); } private async void StartbackgroundTask() { BackgroundMediaPlayer.MessageReceivedFromBackground += BackgroundMediaPlayer_MessageReceivedFromBackground; await Dispatcher.RunAsync( Windows.UI.Core.CoreDispatcherPriority.Normal,() => { //如果后台任务开启成功 var res = _backgroundAudioTaskStarted.WaitOne(2000); if (res) { MessageService.SendMessageToBackground(new UpdateMusicListMessage(List), MessageType.UpdateMusicList); MessageService.SendMessageToBackground(null, MessageType.StartPlayMusic); } }); } /// <summary> /// 用于接收来自后台的信息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void BackgroundMediaPlayer_MessageReceivedFromBackground(object sender, MediaPlayerDataReceivedEventArgs e) { MessageType type = EnumHelper.Parse<MessageType>(e.Data[MessageService.MessageType].ToString()); switch (type) { case MessageType.BackgroundTaskStart: //后台任务开启成功 _backgroundAudioTaskStarted.Set(); break; default: break; } }
现在已经完成了前台的任务,接下来我们去看一下后台应该如何编写
这次我们先看代码:
public sealed class MyBackgroundAudioTask : IBackgroundTask { private const string TrackIdKey = "trackid"; private const string TitleKey = "title"; private const string AlbumArtKey = "albumart"; private const string ArtistKey = "artist"; private BackgroundTaskDeferral _deferral; // Used to keep task alive private SystemMediaTransportControls _smtc; /// <summary> /// 音乐播放列表 /// </summary> private MediaPlaybackList _playbackList = new MediaPlaybackList(); public void Run(IBackgroundTaskInstance taskInstance) { _deferral = taskInstance.GetDeferral(); // Initialize SystemMediaTransportControls (SMTC) for integration with // the Universal Volume Control (UVC). // // The UI for the UVC must update even when the foreground process has been terminated // and therefore the SMTC is configured and updated from the background task. _smtc = BackgroundMediaPlayer.Current.SystemMediaTransportControls; _smtc.ButtonPressed += _smtc_ButtonPressed; _smtc.PropertyChanged += _smtc_PropertyChanged; _smtc.IsEnabled = true; _smtc.IsPauseEnabled = true; _smtc.IsPlayEnabled = true; _smtc.IsNextEnabled = true; _smtc.IsPreviousEnabled = true; // Add handlers for MediaPlayer BackgroundMediaPlayer.Current.CurrentStateChanged += Current_CurrentStateChanged; // Initialize message channel BackgroundMediaPlayer.MessageReceivedFromForeground += BackgroundMediaPlayer_MessageReceivedFromForeground; // Notify foreground app MessageService.SendMessageToForeground(null, MessageType.BackgroundTaskStart); taskInstance.Canceled += TaskInstance_Canceled; } private void BackgroundMediaPlayer_MessageReceivedFromForeground(object sender, MediaPlayerDataReceivedEventArgs e) { MessageType type = EnumHelper.Parse<MessageType>(e.Data[MessageService.MessageType].ToString()); switch (type) { case MessageType.StartPlayMusic: StartPlayback(); break; case MessageType.UpdateMusicList: { UpdateMusicListMessage message; var res = MessageService.ParseMessage(e.Data, out message); if (res) CreateMusicList(message.List); } break; default: break; } } private void StartPlayback() { //这里可以添加跟多的处理逻辑 try { BackgroundMediaPlayer.Current.Play(); } catch(Exception ex) { Debug.WriteLine(ex.Message); } } /// <summary> /// 创建音乐列表 /// </summary> /// <param name="list"></param> private void CreateMusicList(List<Music> musicList) { _playbackList = new MediaPlaybackList(); _playbackList.AutoRepeatEnabled = true; foreach (var music in musicList) { var source = MediaSource.CreateFromUri(music.MusicUri); //为音乐添加一些附加信息用于在UVC上显示 source.CustomProperties[TrackIdKey] = music.Id; source.CustomProperties[TitleKey] = music.Title; source.CustomProperties[AlbumArtKey] = music.AlbumUri; source.CustomProperties[ArtistKey] = music.Artist; _playbackList.Items.Add(new MediaPlaybackItem(source)); } // Don't auto start BackgroundMediaPlayer.Current.AutoPlay = false; // Assign the list to the player BackgroundMediaPlayer.Current.Source = _playbackList; // Add handler for future playlist item changes _playbackList.CurrentItemChanged += PlaybackList_CurrentItemChanged; } private void PlaybackList_CurrentItemChanged(MediaPlaybackList sender, CurrentMediaPlaybackItemChangedEventArgs args) { // Get the new item var item = args.NewItem; // Update the system view UpdateUVCOnNewTrack(item); //通知前台 歌曲已经更改 } private void UpdateUVCOnNewTrack(MediaPlaybackItem item) { if (item == null) { _smtc.PlaybackStatus = MediaPlaybackStatus.Stopped; _smtc.DisplayUpdater.MusicProperties.Title = String.Empty; _smtc.DisplayUpdater.Update(); return; } // 从附加信息中提取相关内容然,更新UVC _smtc.PlaybackStatus = MediaPlaybackStatus.Playing; _smtc.DisplayUpdater.Type = MediaPlaybackType.Music; _smtc.DisplayUpdater.MusicProperties.Title = item.Source.CustomProperties[TitleKey] as string; _smtc.DisplayUpdater.MusicProperties.Artist = item.Source.CustomProperties[ArtistKey] as string; //_smtc.DisplayUpdater.MusicProperties.AlbumTitle = "追梦"; //_smtc.DisplayUpdater.MusicProperties.AlbumArtist = "追梦"; var albumArtUri = item.Source.CustomProperties[AlbumArtKey] as Uri; if (albumArtUri != null) _smtc.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(albumArtUri); else _smtc.DisplayUpdater.Thumbnail = null; _smtc.DisplayUpdater.Update(); } private void Current_CurrentStateChanged(MediaPlayer sender, object args) { //播放状态更改, 更新UVC if (sender.CurrentState == MediaPlayerState.Playing) { _smtc.PlaybackStatus = MediaPlaybackStatus.Playing; } else if (sender.CurrentState == MediaPlayerState.Paused) { _smtc.PlaybackStatus = MediaPlaybackStatus.Paused; } else if (sender.CurrentState == MediaPlayerState.Closed) { _smtc.PlaybackStatus = MediaPlaybackStatus.Closed; } } private void _smtc_PropertyChanged(SystemMediaTransportControls sender, SystemMediaTransportControlsPropertyChangedEventArgs args) { // 比如音量改变,做出相应调整 } private void _smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args) { switch (args.Button) { case SystemMediaTransportControlsButton.Play: Debug.WriteLine("UVC play button pressed"); // Add some code BackgroundMediaPlayer.Current.Play(); break; case SystemMediaTransportControlsButton.Pause: Debug.WriteLine("UVC pause button pressed"); try { BackgroundMediaPlayer.Current.Pause(); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } break; case SystemMediaTransportControlsButton.Next: Debug.WriteLine("UVC next button pressed"); SkipToNext(); break; case SystemMediaTransportControlsButton.Previous: Debug.WriteLine("UVC previous button pressed"); SkipToPrevious(); break; } } private void SkipToNext() { _smtc.PlaybackStatus = MediaPlaybackStatus.Changing; _playbackList.MoveNext(); // TODO: Work around playlist bug that doesn't continue playing after a switch; remove later BackgroundMediaPlayer.Current.Play(); } private void SkipToPrevious() { _smtc.PlaybackStatus = MediaPlaybackStatus.Changing; _playbackList.MovePrevious(); // TODO: Work around playlist bug that doesn't continue playing after a switch; remove later BackgroundMediaPlayer.Current.Play(); } private void TaskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { _deferral.Complete(); } }
首先这是一个长期执行的任务所以需要调用_deferral = taskInstance.GetDeferral();
然后需要注意的是更新UVC和如何创建播放列表。这一部分代码都比较简单,就是事件比较多。
执行效果:
总结一下:上述的代码只能说非常非常的简陋,比如前台没有一个播放列表的显示,但是此篇随笔主要在阐述如何实现后台播放,如果写一个完整的Demo那么代码可能会多了一点。但是下面我会总结一下需要注意的地方,各位可以参考一下。
首先是,前台不能拿到的信息才需要从后台传递过来,比如播放状态更改之类的事件,前台是可以监听的所以就无需从后台传递;但是因为UVC切换或者是歌曲播放结束所引发的当前播放的音乐的更改,前台无法拿到此时就需要后台主动通知前台。下面我总结了一个列表,希望对各位有帮助
事件 | 前台 | 后台 |
播放状态更改 | 可以 | 可以 |
音量更改 | 可以 | 可以 |
UVC操作 | 不可以 | 可以 |
播放项目更改 | 不可以 | 可以 |
下面给出MSDN链接:MSDN
源代码:360云盘 (提取码:e9bd)
第一次来博客园,谢谢啦