翼度科技»论坛 编程开发 .net 查看内容

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

9

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
  当你使用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:
  1. <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
复制代码
  或者一些其他的方法,可以参考这篇博客:如何在WPF中调用Windows 10/11 API(UWP/WinRT) - zhaotianff - 博客园 (cnblogs.com)
二、通过MediaPlayer获取SMTC对象
  1. using Windows.Media;
  2. using Windows.Storage.Streams;
  3. ...
  4. private readonly Windows.Media.Playback.MediaPlayer _player = new();
  5. private readonly SystemMediaTransportControls _smtc;
  6. ...
  7. //先禁用系统播放器的命令
  8. _player.CommandManager.IsEnabled = false;
  9. //直接创建SystemMediaTransportControls对象被平台限制,神奇的是MediaPlayer对象可以创建该NativeObject
  10. _smtc = _player.SystemMediaTransportControls;
  11. //启用smtc以进行自定义
  12. _smtc.IsEnabled = true;
复制代码
  拿到SMTC对象之后的操作与UWP中无异,这里简单看一下:
1.设置可交互性
  1. _smtc.IsPlayEnabled = true;
  2. _smtc.IsPauseEnabled = true;
  3. _smtc.IsNextEnabled = true;
  4. _smtc.IsPreviousEnabled = true;
复制代码
2.设置媒体信息
  1. 1 var updater = _smtc.DisplayUpdater;
  2. 2 updater.AppMediaId = "xxx"; //用于区分不同来源的媒体
  3. 3 updater.Type = MediaPlaybackType.Music; //必须指定媒体类型否则抛异常
  4. 4 updater.MusicProperties.Title = “Title”;//标题
  5. 5 /*...省略相同的字段设置...*/
  6. 6 updater.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri(ImgUrl));//设置封面图
  7. 7 updater.Update();//最后调用以生效
复制代码
  播放状态需要单独设置:
  1. _smtc.PlaybackStatus = MediaPlaybackStatus.Playing; //Paused \ Stopped
  2. //直接设置无需更新
复制代码
3.响应SMTC交互
  1. 1 _smtc.ButtonPressed += _smtc_ButtonPressed;
  2. 2 ...
  3. 3  private void _smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
  4. 4         {
  5. 5             switch(args.Button)
  6. 6             {
  7. 7                 case SystemMediaTransportControlsButton.Play:
  8. 8                     //Play
  9. 9                     break;
  10. 10                 case SystemMediaTransportControlsButton.Pause:
  11. 11                     //Pause
  12. 12                     break;
  13. 13                 case SystemMediaTransportControlsButton.Next:
  14. 14                     //Next
  15. 15                     break;
  16. 16                 case SystemMediaTransportControlsButton.Previous:
  17. 17                     //Previous
  18. 18                     break;
  19. 19             }
  20. 20         }
复制代码
  注意,文中所有SMTC的事件均由系统触发,意味着非同一线程,需要用Dispatcher来操作UI
三、获取和控制系统媒体

  好消息是,负责这部分的模块GlobalSystemMediaTransportControlsSession公开可以任意使用,不受UWP平台限制。
1.获取媒体信息
  1. 1 var gsmtcsm = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();//获取SMTC会话管理器
  2. 2 gsmtcsm.CurrentSessionChanged += xxx; //当前会话改变或退出时发生,微软对CurrentSession的解释是用户可能最希望控制的媒体会话,实测为Windows控制中心顶部显示的媒体,当有多个媒体时用户可以在此选择切换
  3. 3 ...
  4. 4 var session = gsmtcsm.GetCurrentSession();
  5. 5 if(session == null)
  6. 6     return; //当前没有注册的SMTC会话
  7. 7
  8. 8 //接下来操作session即可,下面仅提供参考信息
  9. 9
  10. 10 //媒体信息改变时发生,奇怪的是这些事件提供的参数e并没有任何信息
  11. 11 session.MediaPropertiesChanged += async (sender, e)=>{
  12. 12     //触发事件时主动拉取信息
  13. 13     var info = await _globalSMTCSession.TryGetMediaPropertiesAsync();
  14. 14 };
  15. 15 //播放状态改变时发生
  16. 16 session.PlaybackInfoChanged +=(sender,e)=>{
  17. 17     var status = globalSMTCSession.GetPlaybackInfo().PlaybackStatus;
  18. 18 };
复制代码
2.控制媒体播放

  直接调用即可
  1. await session.TryPauseAsync();
  2. await session.TryPlayAsync();
  3. await session.TrySkipPreviousAsync();
  4. 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:
  1. SMTCCreator? _smtcCreator = null;
  2. ...
  3. _smtcCreator ??= new SMTCCreator("MediaTest");
  4. //修改播放状态
  5. _smtcCreator.SetMediaStatus(SMTCMediaStatus.Playing);
  6. //设置媒体信息
  7. _smtcCreator.Info.SetAlbumTitle("AlbumTitle")
  8.                     .SetArtist("Taylor Swift")
  9.                     .SetTitle("Dancing With Our Hands Tied")
  10.                     .SetThumbnail("https://y.qq.com/music/photo_new/T002R300x300M000003OK4yP2MBOip_1.jpg?max_age=2592000")
  11.                     .Update();
  12. //注册交互响应
  13. _smtcCreator.PlayOrPause += _smtcCreator_PlayOrPause;
  14. _smtcCreator.Previous += _smtcCreator_Previous;
  15. _smtcCreator.Next += _smtcCreator_Next;<br><br>//合适的时候调用释放资源<br>_smtcCreator.Dispose();
复制代码
  简单地控制系统媒体:
  1. SMTCListener _smtcListener = null;
  2. ...
  3. _smtcListener = await SMTCListener.CreateInstance();
  4. _smtcListener.MediaPropertiesChanged += _smtcListener_MediaPropertiesChanged;
  5. _smtcListener.PlaybackInfoChanged += _smtcListener_PlaybackInfoChanged;
  6. _smtcListener.SessionExited += _smtcListener_SessionExited;
  7. ...
  8. //媒体退出
  9. private void _smtcListener_SessionExited(object? sender, EventArgs e) { }
  10. //播放状态改变
  11. private void _smtcListener_PlaybackInfoChanged(object? sender, EventArgs e)
  12. {
  13.     Dispatcher.Invoke(() =>
  14.     {
  15.         var info = _smtcListener.GetPlaybackStatus();
  16.         if (info == null) return;
  17.         StatusTb.Text = info.ToString();
  18.     });
  19. }
  20. //媒体信息改变
  21. private void _smtcListener_MediaPropertiesChanged(object? sender, EventArgs e)
  22. {
  23.     Dispatcher.Invoke(async () =>
  24.     {
  25.         var info = await _smtcListener.GetMediaInfoAsync();
  26.         if (info == null) return;
  27.         TitleTb.Text = info.Title;
  28.         ArtistTb.Text = info.Artist;
  29.         AlbumTitleTb.Text = info.AlbumTitle;
  30.         //获取封面图的方法
  31.         if (info.Thumbnail != null)
  32.         {
  33.             var img = new BitmapImage();
  34.             img.BeginInit();
  35.             img.StreamSource = (await info.Thumbnail.OpenReadAsync()).AsStream();
  36.             img.EndInit();
  37.             ThumbnailImg.Source = img;
  38.         }
  39.     });
  40. }
  41. ...
  42. //控制播放
  43. await _smtcListener.Previous();
  44. await _smtcListener.Next();
  45. await _smtcListener.Pause();
  46. 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,不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

来源:https://www.cnblogs.com/TwilightLemon/p/18279496
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具