當你使用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,不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。