.NET App 与Windows系统媒体控制(SMTC)交互

net,app,windows,smtc · 浏览次数 : 2

小编点评

本文主要介绍了如何在桌面应用程序中集成和使用SystemMediaTransportControls(SMTC)API,以实现与系统媒体交互和控制播放功能。文章首先解释了SMTC的作用和优势,然后详细说明了如何在UWP App和桌面应用中使用SMTC。接着,作者分享了自己封装的SMTCCreator和SMTCListener类,以便更方便地集成和管理SMTC。最后,文章还提到了在使用SMTC时可能遇到的一些问题及其解决方案。 1. **SMTC介绍**:SMTC是一个Windows App SDK提供的API,用于与系统媒体交互。它可以共享媒体控制和媒体信息,方便与其他支持SMTC的应用交互。 2. **UWP中的应用**:在UWP App中,可以使用SystemMediaTransportControls.GetForCurrentView()方法获取SMTC对象。但需要注意的是,该方法仅限在有效的UWP App中调用。 3. **桌面应用的挑战**:桌面应用不支持ApplicationView类,因此也不支持XxxForCurrentView方法。官方文档提供了一个可替代的接口ISystemMediaTransportControlsInterop,但无法访问。 4. **绕过限制**:作者发现UWP的MediaPlayer类可以直接与SMTC集成,而不需要使用合法的UWP Window句柄。这是通过在MediaPlayer内部通过某种COM组件创建NativeObject实现的。 5. **封装类介绍**:文章中提供了一个封装好的SMTCCreator和SMTCListener类,可以直接使用它们来与SMTC交互,而无需直接使用MediaPlayer。 6. **获取和控制系统媒体**:作者展示了如何使用GlobalSystemMediaTransportControlsSession获取媒体信息和控制媒体播放。 7. **遇到的问题**:在使用SMTC时,可能会遇到无法显示媒体来源、信息更新不一致、暂未实现点击跳转到App等问题。 8. **解决方案**:对于这些问题,作者提供了一些可能的解决方案,例如确保使用合法的UWP句柄,或者在UI线程上处理SMTC事件。 总的来说,本文为开发者提供了一种在桌面应用程序中集成和使用SMTC的方法,以便更好地控制媒体播放和获取媒体信息。同时,作者也分享了一些实际应用的案例和解决方案,帮助开发者更好地理解和应对使用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

2)桌面应用中不支持 Windows 运行时 API - Windows 应用 |Microsoft学习 --- Windows Runtime APIs not supported in desktop apps - Windows apps | Microsoft Learn

3)GlobalSystemMediaTransportControlsSessionManager Class (Windows.Media.Control) - Windows UWP applications | Microsoft Learn

 

  打个小广告,我的顶部栏项目正在开发中,现已集成SMTC和众多小功能,欢迎支持:TwilightLemon/MyToolBar: 为Surface Pro而生的顶部工具栏 支持触控和笔快捷方式 (github.com)

   全局媒体播放控制:

   未来将支持更多插件:

 

  本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名TwilightLemon,不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

与.NET App 与Windows系统媒体控制(SMTC)交互相似的内容:

.NET App 与Windows系统媒体控制(SMTC)交互

当你使用Edge等浏览器或系统软件播放媒体时,Windows控制中心就会出现相应的媒体信息以及控制播放的功能,如图。 SMTC (SystemMediaTransportControls) 是一个Windows App SDK (旧为UWP) 中提供的一个API,用于与系统媒体交互。接入SMTC的好

.NET集成DeveloperSharp实现http网络请求&与其它工具的比较

爆了,爆了,DeveloperSharp系列近期又被制造业ERP、民航飞行App、建筑BIM、电力掌上营业厅、等多家大型采用,站在巨人的肩膀上你能走的更远。 支持.Net Core2.0及以上,支持.Net Framework4.0及以上 http请求调用是开发中经常会用到的功能。在内,调用自有项目

[转帖]badboy与jmeter的结合使用

`https://blog.csdn.net/weixin_41754309/article/details/107106855` 欢迎关注【无量测试之道】公众号,回复【领取资源】, Python编程学习资源干货、 Python+Appium框架APP的UI自动化、 Python+Selenium框

【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Linux/Linux Container)

在前一篇文章中,我们是把.NET 8应用读取SSL证书(X509)示例部署在App Service Windows环境中,那么如果部署在Linux环境,以及Linux Container中呢? 根据前文中的第一种方法,直接在把证书文件包含在源文件中,通过相对路径读取证书文件的方式,经测试,可以正常工

【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Windows)

在使用App Service服务部署业务应用,因为有些第三方的接口需要调用者携带TLS/SSL证书(X509 Certificate),在官方文档中介绍了两种方式在代码中使用证书: 1) 直接使用证书文件路径加载证书 new X509Certificate2 2) 从系统的证书库中通过指纹加载...

一款.NET开源的i茅台自动预约小助手

前言 今天大姚给大家分享一款.NET开源、基于WPF实现的i茅台APP接口自动化每日自动预约(抢茅台)小助手:HyggeImaotai。 项目介绍 该项目通过接口自动化模拟i茅台APP实现每日自动预约茅台酒的功能,软件会在指定时间开始对管理的用户进行批量预约。 项目功能 用户管理 预约项目 店铺管理

.NET集成DeveloperSharp实现"高效分页"&"无主键分页"

DeveloperSharp系列近期又被制造业ERP、民航飞行App、建筑BIM、电力掌上营业厅、等多家大型采用,站在巨人的肩膀上你能走的更远。 支持.Net Core2.0及以上,支持.Net Framework4.0及以上 数据分页,几乎是任何应用系统的必备功能。但当数据量较大时,分页操作的效率

【Azure 应用服务】在App Service for Windows中实现反向代理

问题描述 如何在App Service for Windows(.NET Stack)中,如何实现反向代理呢? 正向代理:客户端想要访问一个服务器,但是它可能无法直接访问这台服务器,这时候这可找一台可以访问目标服务器的另外一台服务器,而这台服务器就被当做是代理人的角色 ,称之为代理服务器,于是客户端

【Azure 应用服务】Azure App Service(Windows)环境中如何让.NET应用调用SAP NetWeaver RFC函数

问题描述 在Azure App Service for Windows的环境中,部署.NET应用,其中使用了 SAP NetWeaver RFC函数 (需要加载 sapnwrfc.dll)。详细的错误为: “System.DllNotFoundException: Unable to load DL

如何在.NET程序崩溃时自动创建Dump?

今天在浏览张队转载文章的留言时,遇到一个读者问了这样的问题,如下图所示: 首先能明确的一点是"程序崩溃退出了是不能用常规的方式dump的",因为整个进程树都已经退出。现场已经无法使用常规的方式读取到。 一般来说常规的方法是没办法读取到的,也有一些特殊的方式,比如有关部门在调查取证时,就可以通过一些工