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语音助手功能.之前,我们只能使用本地语音识别,需要编写应用程序,下载到设备中才能实现.从现在开始,微软已经从系统 ...
随机推荐
- mysql-mmm 安装配置(双主)
原文地址:mysql-mmm 安装配置 作者:chinaunix1116 MMM即Master-Master Replication Managerfor MySQL(mysql主主复制管理器)关于m ...
- JS代码判断字符串中有多少汉字
$("form").submit(function () { var content = editor.getContentTxt(); var sum = 0; re = /[\ ...
- C++之STL
5.子类模板访问基类模板在子类模板中访问那些在基类模板中声明且依赖于模板参数的符号,应该在它前面加上作用域限定符"::" 或者显示使用this指针否则,编译器将试图在全局域中寻找该 ...
- delphi 安装.dpk;package
打开.dpk,若要将包直接安装在delphi的默认安装目录下(D:\Program Files (x86)\Borland\Delphi7\Projects\Bpl), 可清除,点击options打开 ...
- PCI在linux系统中注册与注销示例
1. pci_driver结构struct pci_driver { struct list_head node; const char *name; const struct pc ...
- redis 下载启动,设置、查询超时时间
1.定义 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted ...
- 警惕多iframe下的同名id引起的诡异问题
遇到个诡异bug,虽然bug中套bug,忽略次要bug,其中最诡异最典型的现象是多行window.top.$("#id")取值操作,其中有一行却取不到值.这个着实让我费解.因为用到 ...
- [译] libvirt 虚机的生命周期 (Libvirt Virtual Machine Lifecycle)
翻译自:http://wiki.libvirt.org/page/VM_lifecycle 这篇文章描述虚机生命周期的基本概念.其目的在于在一篇文章中提供完整的关于虚机创建.运行.停止.迁移和删除 ...
- NOIP 2008提高组第三题题解by rLq
啊啊啊啊啊啊今天已经星期三了吗 那么,来一波题解吧 本题地址http://www.luogu.org/problem/show?pid=1006 传纸条 题目描述 小渊和小轩是好朋友也是同班同学,他们 ...
- 三维网格分割算法(Random Walks)
首先以一维随机游走(1D Random Walks)为例来介绍下随机游走(Random Walks)算法,如下图所示,从某点出发,随机向左右移动,向左和向右的概率相同,都为1/2,并且到达0点或N点则 ...