Windows 10 后台音频
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 = "";
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 = "";
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();
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)
第一次来博客园,谢谢啦
Windows 10 后台音频的更多相关文章
- Windows 10 的音频和 MIDI API将统一
微软一统 Windows 10 的音频和 MIDI API 微软在夏季NAMM上的A3E大会上做了主题演讲,他们对Windows 10的音频和MIDI API都做了新的规划,开发者针对Windows ...
- Windows Phone background Audio 后台音频
Windows Phone 后台音频的确不是什么新鲜的话题了,但发现目前在WP平台的音频播放应用多多少少会有一些瑕疵,所以在此给大家在此介绍下这个功能给有需要的朋友们. 首先介绍下我们的应用在后台播放 ...
- 背水一战 Windows 10 (120) - 后台任务: 后台上传任务
[源码下载] 背水一战 Windows 10 (120) - 后台任务: 后台上传任务 作者:webabcd 介绍背水一战 Windows 10 之 后台任务 后台上传任务 示例演示 uwp 的后台上 ...
- 背水一战 Windows 10 (119) - 后台任务: 后台下载任务(任务分组,组完成后触发后台任务)
[源码下载] 背水一战 Windows 10 (119) - 后台任务: 后台下载任务(任务分组,组完成后触发后台任务) 作者:webabcd 介绍背水一战 Windows 10 之 后台任务 后台下 ...
- 背水一战 Windows 10 (118) - 后台任务: 后台下载任务(任务分组,并行或串行执行,组完成后通知)
[源码下载] 背水一战 Windows 10 (118) - 后台任务: 后台下载任务(任务分组,并行或串行执行,组完成后通知) 作者:webabcd 介绍背水一战 Windows 10 之 后台任务 ...
- 背水一战 Windows 10 (117) - 后台任务: 后台下载任务
[源码下载] 背水一战 Windows 10 (117) - 后台任务: 后台下载任务 作者:webabcd 介绍背水一战 Windows 10 之 后台任务 后台下载任务 示例演示 uwp 的后台下 ...
- Windows 10更新后无法启动Dolby音频驱动程序
在电脑更新Windows 10 1903版本后,重启出现如下问题: 经查,这与驱动强制签名有关.解决方法如下: 打开"设置"->"更新与安全"->& ...
- 《深入浅出Windows 10通用应用开发》
<深入浅出Windows 10通用应用开发>采用Windows 10的SDK进行重新改版,整合了<深入浅出Windows Phone 8.1应用开发>和<深入解析 ...
- Windows 10 IoT Serials 4 - 如何在树莓派上使用Cortana语音助手
从Windows 10 IoT Core 14986版本开始,微软已经加入Cortana语音助手功能.之前,我们只能使用本地语音识别,需要编写应用程序,下载到设备中才能实现.从现在开始,微软已经从系统 ...
随机推荐
- php 批量更新某字段内容的部分内容 replace(要替换的字段,'被替换的字符串,'替换成的字符串')
要求: 一个字段值: ------预约---- 要将其中 "预约",改成"预定". 但是我开始写的时候,写成了 update 表名 set smscontent ...
- C++类成员函数的重载、覆盖和隐藏区别?
C++类成员函数的重载.覆盖和隐藏区别? a.成员函数被重载的特征:(1)相同的范围(在同一个类中):(2)函数名字相同:(3)参数不同:(4)virtual 关键字可有可无.b.覆盖是指派生类函数覆 ...
- monkeyrunner之安卓开发环境搭建(一)
在学习monkeyrunner之前,让我们先搭建好eclipse安卓开发环境. 对于程序开发人员而言,eclipse并不陌生,它提供了一个非常广阔的平台来开发程序.同样也可以用它来开发android程 ...
- [转]com.devicepush.cordova-phonegap Device Push Notification Plugin
本文转自:https://www.npmjs.com/package/com.devicepush.cordova-phonegap Device Push Notification Plugin D ...
- PHP&MySQL(三)——数组
前一段忙着比赛忙着找实习,最后一地鸡毛,就是长长教训罢了.... 看书还是多必须的,试着高效的.踏实的做吧!! <?php //PHP数组其实能创建很多种数据结构,列表,堆栈,队列,树等 //数 ...
- 借用Snippet插件美化博客中的代码
书写博客,难免要贴出代码.然而直接贴出代码,则不美观.于是,应运而生出现了很多代码美化的插件.其中比较有名的是Syntax Highlighting插件. 笔者在网上翻阅的时候发现了Snippet ...
- UIScrollView 滑动复位
需求 在每次打开界面滑动列表都是复位状态(未滑动). 分析 在制作滑动列表时常常会结合UIPanel和UIScrollView 要让滑动列表回到未滑动时的位置,那么就需要改变Panel的Clippin ...
- VS的快捷键F12改成和ECLIPSE一样用ctrl+点击下载线
安装resharper 插件即可 不过这个插件是收费的,可免费体验30天
- 阿里云377秒完成100TB数据排序:秒三星百度
阿里云377秒完成100TB数据排序:秒三星百度 今日,Sort Benchmark 在官方网站公布了 2015 年排序竞赛的最终成绩.其中,阿里云用不到 7 分钟(377 秒)就完成了 100TB ...
- memcache的安装和使用
Memcache Memcached是一个高性能的分布式缓存系统.memcached自身不会实现分布式,分布式是由程序来实现的. Memcached一旦安装之后,自身进行管理!预申请一个很大的内存空间 ...