likes
comments
collection
share

Flutter 应用如何支持 SMTC

作者站长头像
站长
· 阅读数 33

SMTC 是什么

SMTC(SystemMediaTransportControls, 系统媒体传输控件) 是 Windows 10 和 Windows 11 的 “媒体通知”。外观如图(Windows 11):

Flutter 应用如何支持 SMTC

与之相关的类存在于 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 部分:

  1. 从 MediaPlayer 获取 SMTC 实例并禁用 MediaPlayer 提供的自动集成

    let _player = MediaPlayer::new()?;
    _player.CommandManager()?.SetIsEnabled(false)?;
    let _smtc = _player.SystemMediaTransportControls()?;
    
  2. 启用需要的控件(暂停、播放、上一首、下一首)

    _smtc.SetIsNextEnabled(true)?;
    _smtc.SetIsPauseEnabled(true)?;
    _smtc.SetIsPlayEnabled(true)?;
    _smtc.SetIsPreviousEnabled(true)?;
    
  3. 编写更新 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(())
    }
    
  4. 向 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

转载自:https://juejin.cn/post/7363556508603432972
评论
请登录