SMTC 是什么
SMTC(SystemMediaTransportControls, 系统媒体传输控件) 是 Windows 10 和 Windows 11 的 “媒体通知”。外观如图(Windows 11):
与之相关的类存在于 WinRT API 中。因此,我们需要给 Flutter 提供调用 WinRT 的方法。
WinRT 语言选择
WinRT 的语言投影有很多种,比如 C++、Rust、C# 等。考虑到 Flutter 社区有 flutter_rust_bridge 库来帮我们生成跨语言需要的 Binding,我选择了 Rust/WinRT。
具体实现
这一部分只给出代码片段。完整代码见 coriander_player/rust/src/api/smtc_flutter.rs at main · Ferry-200/coriander_player (github.com) 。
这里假设你已经准备好 flutter_rust_bridge 的环境,如果没有,先按照 Introduction | flutter_rust_bridge (cjycode.com) 里的教程准备。
首先你要在 cargo.toml 文件中添加依赖:
[dependencies]
flutter_rust_bridge = "=2.0.0-dev.32"
windows = { version = "0.56.0", features = [
"Media_Playback",
"Storage",
"Storage_Streams",
]}
接着是 Rust 部分:
-
从 MediaPlayer 获取 SMTC 实例并禁用 MediaPlayer 提供的自动集成
let _player = MediaPlayer::new()?; _player.CommandManager()?.SetIsEnabled(false)?; let _smtc = _player.SystemMediaTransportControls()?;
-
启用需要的控件(暂停、播放、上一首、下一首)
_smtc.SetIsNextEnabled(true)?; _smtc.SetIsPauseEnabled(true)?; _smtc.SetIsPlayEnabled(true)?; _smtc.SetIsPreviousEnabled(true)?;
-
编写更新 SMTC 状态和展示内容的函数
fn _update_state(&self, state: SMTCState) -> Result<(), windows::core::Error> { let state = match state { SMTCState::Playing => MediaPlaybackStatus::Playing, SMTCState::Paused => MediaPlaybackStatus::Paused, }; self._smtc.SetPlaybackStatus(state)?; Ok(()) } fn _update_display( &self, title: HSTRING, artist: HSTRING, album: HSTRING, path: HSTRING, ) -> Result<(), windows::core::Error> { let updater = self._smtc.DisplayUpdater()?; // 指示 Windows 以什么样式展示信息,否则 SMTC 只会展示文件路径。 updater.SetType(MediaPlaybackType::Music)?; let music_properties = updater.MusicProperties()?; music_properties.SetTitle(&title)?; music_properties.SetArtist(&artist)?; music_properties.SetAlbumTitle(&album)?; // 读取文件的缩略图(也就是音乐文件的封面)。 let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path))?.get()?; let thumbnail = file .GetThumbnailAsyncOverloadDefaultSizeDefaultOptions(ThumbnailMode::MusicView)? .get()? .CloneStream()?; updater.SetThumbnail(&RandomAccessStreamReference::CreateFromStream(&thumbnail)?)?; updater.Update()?; if !(self._smtc.IsEnabled()?) { self._smtc.SetIsEnabled(true)?; } Ok(()) }
-
向 Flutter 提供订阅按钮事件的方法 下面的方法会被 flutter_rust_bridge 翻译成
Stream<SMTCControlEvent> subscribeToControlEvents()
。在 Flutter 中可以使用listen
方法来监听事件。pub fn subscribe_to_control_events( &self, sink: StreamSink<SMTCControlEvent>, ) { self._smtc.ButtonPressed(&TypedEventHandler::< SystemMediaTransportControls, SystemMediaTransportControlsButtonPressedEventArgs, >::new(move |_, event| { let event = event.as_ref().unwrap().Button().unwrap(); let event = match event { SystemMediaTransportControlsButton::Play => SMTCControlEvent::Play, SystemMediaTransportControlsButton::Pause => SMTCControlEvent::Pause, SystemMediaTransportControlsButton::Next => SMTCControlEvent::Next, SystemMediaTransportControlsButton::Previous => SMTCControlEvent::Previous, _ => SMTCControlEvent::Unknown, }; sink.add(event).unwrap(); Ok(()) })).unwrap(); }
上面 4 个步骤写的 Rust 代码的概要如下:
pub struct SMTCFlutter { _smtc: SystemMediaTransportControls, _player: MediaPlayer }
pub enum SMTCControlEvent { Play, Pause, Previous, Next, Unknown }
pub enum SMTCState { Paused, Playing }
impl SMTCFlutter {
#[frb(sync)]
pub fn new();
pub fn subscribe_to_control_events(&self, sink: StreamSink<SMTCControlEvent>);
pub fn update_state(&self, state: SMTCState);
pub fn update_display(&self, title: String, artist: String, album: String, path: String);
pub fn close(self);
}
然后被翻译成这样:
class SmtcFlutter extends RustOpaque {
Future<void> close({dynamic hint});
factory SmtcFlutter({dynamic hint});
Stream<SMTCControlEvent> subscribeToControlEvents({dynamic hint});
Future<void> updateDisplay({required String title, required String artist, required String album,
required String path,
dynamic hint});
Future<void> updateState({required SMTCState state, dynamic hint});
}
enum SMTCControlEvent { play, pause, previous, next, unknown }
enum SMTCState { paused, playing }
接下来在 Flutter 中调用它:
// 订阅按钮事件
_smtc.subscribeToControlEvents().listen((event) {
switch (event) {
case SMTCControlEvent.play:
start();
break;
case SMTCControlEvent.pause:
pause();
break;
case SMTCControlEvent.previous:
lastAudio();
break;
case SMTCControlEvent.next:
nextAudio();
break;
case SMTCControlEvent.unknown:
}
});
// 更新状态和信息
_smtc.updateState(state: SMTCState.playing);
_smtc.updateDisplay(
title: nowPlaying!.title, artist: nowPlaying!.artist, album: nowPlaying!.album,
path: nowPlaying!.path,
);
效果展示
更改播放内核为 Windows 的 MediaPlayer 以自动支持 SMTC 功能 · Issue #24 · Ferry-200/coriander_player · GitHub
宣传
以上代码属于我开发的 Windows 本地音乐播放器 Coriander Player。欢迎使用、欢迎提 Issue 和 PR。 Coriander Player