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语音助手功能.之前,我们只能使用本地语音识别,需要编写应用程序,下载到设备中才能实现.从现在开始,微软已经从系统 ...
随机推荐
- sed实例精解--例说sed完整版
原文地址:sed实例精解--例说sed完整版 作者:xiaozhenggang 最近在学习shell,怕学了后面忘了前面的就把学习和实验的过程记录下来了.这里是关于sed的,前面有三四篇分开的,现在都 ...
- Python基本语法,python入门到精通[二]
在上一篇博客Windows搭建python开发环境,python入门到精通[一]我们已经在自己的windows电脑上搭建好了python的开发环境,这篇博客呢我就开始学习一下Python的基本语法.现 ...
- cocos2d-x之 利用富文本控件解析xhml标签(文字标签,图片标签,换行标签,标签属性)
执行后效果: 前端使用: 后台SuperRichText解析code void SuperRichText::renderNode(tinyxml2::XMLNode *node){ while (n ...
- JAVA插入sql代码
插入数据 import java.sql.*; /** * @version 2012-02-22 * @author */ public class InsertDemo { public stat ...
- xargs用法详解
前言 最近我从svn上checkout出来了一个文件夹,然后加入了git的跟踪目录.用过svn的同学可能知道,这个文件夹里面每一层级都有个.svn隐藏文件夹,需要删除他们.本来我准备笨拙地一个一个手动 ...
- Terminal中输入命令直接打开QtCreator,以及创建其桌面快捷方式
工业项目设计学习第一步,熟悉开发工具 Qt学习论坛,东西多,但也杂 emouse的博客,以前学习STM32开发环境搭建时也是参考这位博主的 更多详细的步骤在上面都能找到,今天先不写,等明天把硬件设备全 ...
- 调试2440 RAM拷贝至SDRAM遇到的问题
汇编代码主要是初始化一些寄存器,关狗,初始化时钟,初始化存储管理器以便访问内存,然后将SoC上4k RAM数据拷贝至SDRAM,然后在SRAM里面运行,由于代码未正常跑起来,于是使用JLinkExe来 ...
- 认识与入门 Markdown,Markdown教程
一.认识 Markdown 在刚才的导语里提到,Markdown 是一种用来写作的轻量级「标记语言」,它用简洁的语法代替排版,而不像一般我们用的字处理软件 Word 或 Pages 有大量的排版.字体 ...
- redis存在大量脏页问题的追查记录
from:https://www.zybuluo.com/SailorXiao/note/136014 case现场 线上发现一台机器内存负载很重,top后发现一个redis进程占了大量的内存,TOP ...
- MySQL导入sql脚本错误:2006 - MySQL server has gone away
到如一些小脚本很少报错,但最近导入一个10+M的SQL脚本,却重复报错: Error occured at:2014-03-24 11:42:24 Line no.:85 Error Code: 20 ...