.NET App 与Windows系统媒体控制(SMTC)交互
当你使用Edge等浏览器或系统软件播放媒体时,Windows控制中心就会出现相应的媒体信息以及控制播放的功能,如图。
SMTC (SystemMediaTransportControls) 是一个Windows App SDK (旧为UWP) 中提供的一个API,用于与系统媒体交互。接入SMTC的好处在于,将媒体控制和媒体信息共享给系统,使用通用的特性(例如接受键盘快捷键的播放暂停、接受蓝牙设备的控制),便于与其它支持SMTC的应用交互等。
在UWP App中使用它很简单,只需要调用SystemMediaTransportControls.GetForCurrentView()方法即可,但是该方法仅限在有效的UWP App中调用,否则将抛出“Invalid window handle”异常。实际上,在官方文档中提到所有XXXForCurrentView方法均不适用于UWP App以外的程序调用。
这些 XxxForCurrentView 方法对 ApplicationView 类型具有隐式依赖关系,桌面应用不支持该类型。由于桌面应用不支持 ApplicationView,因此也不支持任何 XxxForCurrentView 方法。
此外官方文档还给出一个可替代的接口ISystemMediaTransportControlsInterop,然而这个接口在给的SDK中有保护性,无法访问。
至此,直接创建SMTC的方法走不通。但是我发现一个奇怪的地方,UWP提供的在Windows.Media.Playback命名空间下的MediaPlayer可以和SMTC自动集成,并且可以通过SystemMediaTransportControls属性直接拿到SMTC对象。MediaPlayer内部通过某种COM组件直接创建了该NativeObject,而没有走API提供的GetForCurrentView或FromAbi方法。也就是说,SMTC组件其实不需要使用合法的UWP Window句柄来创建,只不过可能为了某些特性而加上了该限制(后文将提到)。幸运的是,MediaPlayer帮我们绕过了这点。
下文讲解手动与SMTC交互而不是直接使用MediaPlayer进行播放,你的项目可能已经有了其它的解码器(如WPF版本的MediaPlayer、Bass.Net解码器、NAudio等),则只需要将交互部分接入SMTC而不更换解码器。
文末提供了我封装好的SMTCCreator和SMTCListener,可以直接使用。
一、引用WinRT API到项目
最便捷的方法是直接修改目标框架到win10,这样就能自动引入WinRT API:
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
或者一些其他的方法,可以参考这篇博客:如何在WPF中调用Windows 10/11 API(UWP/WinRT) - zhaotianff - 博客园 (cnblogs.com)
二、通过MediaPlayer获取SMTC对象
using Windows.Media;
using Windows.Storage.Streams;
...
private readonly Windows.Media.Playback.MediaPlayer _player = new();
private readonly SystemMediaTransportControls _smtc;
...
//先禁用系统播放器的命令
_player.CommandManager.IsEnabled = false;
//直接创建SystemMediaTransportControls对象被平台限制,神奇的是MediaPlayer对象可以创建该NativeObject
_smtc = _player.SystemMediaTransportControls;
//启用smtc以进行自定义
_smtc.IsEnabled = true;
拿到SMTC对象之后的操作与UWP中无异,这里简单看一下:
1.设置可交互性
_smtc.IsPlayEnabled = true;
_smtc.IsPauseEnabled = true;
_smtc.IsNextEnabled = true;
_smtc.IsPreviousEnabled = true;
2.设置媒体信息
1 var updater = _smtc.DisplayUpdater;
2 updater.AppMediaId = "xxx"; //用于区分不同来源的媒体
3 updater.Type = MediaPlaybackType.Music; //必须指定媒体类型否则抛异常
4 updater.MusicProperties.Title = “Title”;//标题
5 /*...省略相同的字段设置...*/
6 updater.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri(ImgUrl));//设置封面图
7 updater.Update();//最后调用以生效
播放状态需要单独设置:
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing; //Paused \ Stopped
//直接设置无需更新
3.响应SMTC交互
1 _smtc.ButtonPressed += _smtc_ButtonPressed;
2 ...
3 private void _smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
4 {
5 switch(args.Button)
6 {
7 case SystemMediaTransportControlsButton.Play:
8 //Play
9 break;
10 case SystemMediaTransportControlsButton.Pause:
11 //Pause
12 break;
13 case SystemMediaTransportControlsButton.Next:
14 //Next
15 break;
16 case SystemMediaTransportControlsButton.Previous:
17 //Previous
18 break;
19 }
20 }
注意,文中所有SMTC的事件均由系统触发,意味着非同一线程,需要用Dispatcher来操作UI
三、获取和控制系统媒体
好消息是,负责这部分的模块GlobalSystemMediaTransportControlsSession公开可以任意使用,不受UWP平台限制。
1.获取媒体信息
1 var gsmtcsm = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();//获取SMTC会话管理器
2 gsmtcsm.CurrentSessionChanged += xxx; //当前会话改变或退出时发生,微软对CurrentSession的解释是用户可能最希望控制的媒体会话,实测为Windows控制中心顶部显示的媒体,当有多个媒体时用户可以在此选择切换
3 ...
4 var session = gsmtcsm.GetCurrentSession();
5 if(session == null)
6 return; //当前没有注册的SMTC会话
7
8 //接下来操作session即可,下面仅提供参考信息
9
10 //媒体信息改变时发生,奇怪的是这些事件提供的参数e并没有任何信息
11 session.MediaPropertiesChanged += async (sender, e)=>{
12 //触发事件时主动拉取信息
13 var info = await _globalSMTCSession.TryGetMediaPropertiesAsync();
14 };
15 //播放状态改变时发生
16 session.PlaybackInfoChanged +=(sender,e)=>{
17 var status = globalSMTCSession.GetPlaybackInfo().PlaybackStatus;
18 };
2.控制媒体播放
直接调用即可
await session.TryPauseAsync();
await session.TryPlayAsync();
await session.TrySkipPreviousAsync();
await session.TrySkipNextAsync();
四、一些奇怪的地方
1.无法显示媒体来源,并且不会清空上一个来源的信息
可能是因为没有提供合法的UWP句柄,Windows虽然能确定是哪个exe调用的SMTC,但是拒绝直接显示exe的信息。逻辑上来说这个来源信息会被空覆盖掉,但是并没有。
2.信息更新不一致和延时
系统显示的会话以及提供GlobalSMTCSessionMng.获取的信息有时会不一致,二者都有可能和应用真实在播放的不一致,后者获取的封面图有时也会不一致。此外,MusicProperty的更新有时并不会实时反馈到GlobalSMTCSession的Changed事件,我测试的时候当系统内存爆满(98% 我开了一堆浏览器标签页和4个vs)的时候,更新丢失的概率在70%左右,而资源充足时可以做到几乎即时更新。
3.暂未实现点击跳转到App
正统UWP App的SMTC会话是可以点击跳转到App播放界面的,但是我并没有找到相关的事件。
4.奇怪的MediaId
Windows系统似乎通过这个来区分不同的媒体来源(明明可以获得调用者- -),神奇的是如果你为两个应用设置了同样的MediaId,那么只有两个都关闭时,SMTC会话才会释放。此外通过GlobalSMTCSession.SourceAppUserModelId并不能获得你设置的MediaId,而是调用者的文件名"xxx.exe"。
五、使用我封装的库
Demo和库已经开源:TwilightLemon/MediaTest: .NET 8 WPF using SMTC (github.com)
简单地将现有的解码器接入SMTC:
SMTCCreator? _smtcCreator = null;
...
_smtcCreator ??= new SMTCCreator("MediaTest");
//修改播放状态
_smtcCreator.SetMediaStatus(SMTCMediaStatus.Playing);
//设置媒体信息
_smtcCreator.Info.SetAlbumTitle("AlbumTitle")
.SetArtist("Taylor Swift")
.SetTitle("Dancing With Our Hands Tied")
.SetThumbnail("https://y.qq.com/music/photo_new/T002R300x300M000003OK4yP2MBOip_1.jpg?max_age=2592000")
.Update();
//注册交互响应
_smtcCreator.PlayOrPause += _smtcCreator_PlayOrPause;
_smtcCreator.Previous += _smtcCreator_Previous;
_smtcCreator.Next += _smtcCreator_Next;
//合适的时候调用释放资源
_smtcCreator.Dispose();
简单地控制系统媒体:
SMTCListener _smtcListener = null;
...
_smtcListener = await SMTCListener.CreateInstance();
_smtcListener.MediaPropertiesChanged += _smtcListener_MediaPropertiesChanged;
_smtcListener.PlaybackInfoChanged += _smtcListener_PlaybackInfoChanged;
_smtcListener.SessionExited += _smtcListener_SessionExited;
...
//媒体退出
private void _smtcListener_SessionExited(object? sender, EventArgs e) { } //播放状态改变
private void _smtcListener_PlaybackInfoChanged(object? sender, EventArgs e)
{
Dispatcher.Invoke(() =>
{
var info = _smtcListener.GetPlaybackStatus();
if (info == null) return;
StatusTb.Text = info.ToString();
});
}
//媒体信息改变
private void _smtcListener_MediaPropertiesChanged(object? sender, EventArgs e)
{
Dispatcher.Invoke(async () =>
{
var info = await _smtcListener.GetMediaInfoAsync();
if (info == null) return;
TitleTb.Text = info.Title;
ArtistTb.Text = info.Artist;
AlbumTitleTb.Text = info.AlbumTitle;
//获取封面图的方法
if (info.Thumbnail != null)
{
var img = new BitmapImage();
img.BeginInit();
img.StreamSource = (await info.Thumbnail.OpenReadAsync()).AsStream();
img.EndInit();
ThumbnailImg.Source = img;
}
});
}
...
//控制播放
await _smtcListener.Previous();
await _smtcListener.Next();
await _smtcListener.Pause();
await _smtcListener.Play();
六、写在最后
参考资料:
1)SystemMediaTransportControls 类 (Windows.Media) - Windows UWP applications | Microsoft Learn
打个小广告,我的顶部栏项目正在开发中,现已集成SMTC和众多小功能,欢迎支持:TwilightLemon/MyToolBar: 为Surface Pro而生的顶部工具栏 支持触控和笔快捷方式 (github.com)
全局媒体播放控制:
未来将支持更多插件:
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名TwilightLemon,不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
.NET App 与Windows系统媒体控制(SMTC)交互的更多相关文章
- C#实现控制Windows系统关机、重启和注销的方法:
shutdown命令的参数: shutdown.exe -s:关机shutdown.exe -r:关机并重启shutdown.exe -l:注销当前用户 shutdown.exe -s -t 时间:设 ...
- C#实现控制Windows系统关机、重启和注销的方法
shutdown命令的参数: shutdown.exe -s:关机shutdown.exe -r:关机并重启shutdown.exe -l:注销当前用户 shutdown.exe -s -t 时间:设 ...
- 使用Kali中的Metasploit生成木马控制Windows系统 (第九天 9.20)
本文转自:https://www.cnblogs.com/yankaohaitaiwei/p/11556921.html 一.kali及Metasploit kali基于debin的数字取证系统,上面 ...
- 如何在Windows系统下使用you-get下载网上的媒体资源
关于you-get的专业介绍可以点击这个链接:中文说明 1,首先你要在你的电脑上安装python环境 Windows系统下: 首先,你需要去官网下载相应的版本: 也可以下载我网盘里的(注意看好自己的电 ...
- 通过使用 NTLite 工具实现精简Windows系统
NTLite 是一款专业于Windows平台的系统精简工具,NTLite主要面对系统封装人员使用,比如各大下载站及GHO镜像下载站,Windows系统二次精简封装打包使用,NTLite可以对系统进行极 ...
- Windows系统错误代码大全
1 Microsoft Windows 系统错误代码简单分析: 0000 操作已成功完成.0001 错误的函数. 0002 系统找不到指定的文件. 0003 系统找不到指定的路径. 0004 系统无法 ...
- linux/windows系统oracle数据库简单冷备同步
linux/windows系统oracle数据库简单冷备同步 我们有一个财务系统比较看重财务数据的安全性,同时我们拥有两套系统,一个生产环境(linux),一个应急备份环境(windows).备份环境 ...
- 以上帝模式管理Windows系统
上帝模式,,即"God Mode",或称为"完全控制面板".是Windows Vista/7系统中隐藏的一个简单的文件夹窗口,但包含了几乎所有Windows系统 ...
- Windows系统自带工具的 cmd 命令
目标 与计算机高手无关,只是为了减少鼠标点击的次数,提高效率. 适用范围 Windows XP,Windows 7,Window 8 (在Windows 7 下验证通过.) 使用方法 在 “运行“ 对 ...
- windows系统下安装MySQL
可以运行在本地windows版本的MySQL数据库程 序自从3.21版以后已经可以从MySQL AB公司获得,而且 MYSQL每日的下载百分比非常大.这部分描述在windows上安装MySQL的过程. ...
随机推荐
- Python基础知识——缩进、标识符、保留字
标识符 标识符就是程序中,使用的各种名称,例如:变量名.常量名.类名等等. 在 Python 中,对标识符格式的要求与 C/C++.Java 等差不多: 第一个字符必须是字母表中的字母或下划线 _ ; ...
- WEB服务与NGINX(18)- nginx rewrite功能详解
目录 1. nginx的rewrite功能详解 1.1 rewrite功能概述 1.2 rewrite模块的常用指令 1.2.1 if指令 1.2.2 set指令 1.2.3 break指令 1.2. ...
- CSS样式(第三篇)
<div class="box1"> <div class="line"></div> <div class=&q ...
- uniapp中利用renderjs引入leaflet
由于uniapp中要使用地图,虽然uni-app有地图组件map,但是很难用,而且性能很差.在app中是不能操作dom,所以直接用leaflet是不可能的.最终发现了renderjs,官网提出,在ap ...
- cesium教程8-官方示例翻译-图层亮度对比度调整
完整示例代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="u ...
- 通过ref返回解决C# struct结构体链式调用的问题
通常结构体不能进行链式调用,因为返回值是一个新的值,需要赋回原值.但现在通过ref关键字配合扩展方法,也能进行链式调用了. 结构体: public struct Foo { public int a; ...
- C 语言编程 — 输入/输出与文件操作
目录 文章目录 目录 前文列表 输入/输出 scanf() 和 printf() getchar() 和 putchar() 文件操作 打开文件 关闭文件 写入文件 读取文件 二进制 I/O 函数 前 ...
- AIRIOT物联网低代码平台如何配置db-driver驱动?
设备接入能力包括接入驱动类型及接入数据量,性能方面需要考量数据采集的稳定性和驱动的丰富性等多个因素.用户在选择物联网平台时,往往存在一些误区,比如很关注平台支持的驱动数量,越多越好.市场上确有支持上千 ...
- saltstack实践案例
master某个配置参考案例 [root@]# cat /etc/salt/master file_ignore_regex: - '/\.git($|/)' file_ignore_glob: - ...
- vulnhub靶场-->MATRIX-BREAKOUT: 2 MORPHEUS
靶机下载地址 MATRIX-BREAKOUT: 2 MORPHEUS << 点我下载 开始打靶 IP发现 nmap扫描网段发现靶机ip:192.168.111.139 端口发现 对靶机进行 ...