smtc-player 0.1.0

Rust bindings for Windows System Media Transport Controls (SMTC)
use std::time::Duration;
use windows::{
    Foundation::{TimeSpan, TypedEventHandler},
    Media::{
        MediaPlaybackStatus, MediaPlaybackType, Playback::MediaPlayer,
        SystemMediaTransportControls, SystemMediaTransportControlsButton,
        SystemMediaTransportControlsButtonPressedEventArgs,
        SystemMediaTransportControlsTimelineProperties,
    },
    Storage::{FileProperties::ThumbnailMode, StorageFile, Streams::RandomAccessStreamReference},
    core::{HSTRING, Result},
};

/// SMTC 控制事件
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmtcEvent {
    Play,
    Pause,
    Previous,
    Next,
    Stop,
}

pub struct SmtcPlayer {
    _player: MediaPlayer,
    smtc: SystemMediaTransportControls,
}

impl SmtcPlayer {
    pub fn new() -> Result<Self> {
        let player = MediaPlayer::new()?;

        player.CommandManager()?.SetIsEnabled(false)?;

        let smtc = player.SystemMediaTransportControls()?;

        smtc.SetIsPlayEnabled(true)?;
        smtc.SetIsPauseEnabled(true)?;
        smtc.SetIsStopEnabled(true)?;
        smtc.SetIsNextEnabled(true)?;
        smtc.SetIsPreviousEnabled(true)?;
        smtc.SetIsEnabled(true)?;

        Ok(Self {
            _player: player,
            smtc,
        })
    }

    /// 订阅 SMTC 按钮事件
    pub fn on_event<F>(&self, mut callback: F) -> Result<()>
    where
        F: FnMut(SmtcEvent) + Send + 'static,
    {
        self.smtc.ButtonPressed(&TypedEventHandler::new(
            move |sender: &Option<SystemMediaTransportControls>,
                  args: &Option<SystemMediaTransportControlsButtonPressedEventArgs>| {
                let _ = sender;
                let args = args.as_ref().ok_or(windows::core::Error::empty())?;
                let button = args.Button()?;
                let event = match button {
                    SystemMediaTransportControlsButton::Play => SmtcEvent::Play,
                    SystemMediaTransportControlsButton::Pause => SmtcEvent::Pause,
                    SystemMediaTransportControlsButton::Next => SmtcEvent::Next,
                    SystemMediaTransportControlsButton::Previous => SmtcEvent::Previous,
                    SystemMediaTransportControlsButton::Stop => SmtcEvent::Stop,
                    _ => return Ok(()),
                };
                callback(event);
                Ok(())
            },
        ))?;
        Ok(())
    }

    /// 更新播放状态
    pub fn set_playback_status(&self, playing: bool) -> Result<()> {
        let status = if playing {
            MediaPlaybackStatus::Playing
        } else {
            MediaPlaybackStatus::Paused
        };
        self.smtc.SetPlaybackStatus(status)?;
        Ok(())
    }

    /// 更新媒体元数据
    pub fn set_media_info(
        &self,
        title: &str,
        artist: &str,
        album: &str,
        thumbnail_path: Option<&str>,
    ) -> Result<()> {
        let updater = self.smtc.DisplayUpdater()?;

        updater.SetType(MediaPlaybackType::Music)?;

        let music = updater.MusicProperties()?;
        music.SetTitle(&HSTRING::from(title))?;
        music.SetArtist(&HSTRING::from(artist))?;
        music.SetAlbumTitle(&HSTRING::from(album))?;

        if let Some(path) = thumbnail_path {
            let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path))?.get()?;
            let thumbnail = file
                .GetThumbnailAsyncOverloadDefaultSizeDefaultOptions(ThumbnailMode::MusicView)?
                .get()?;
            updater.SetThumbnail(&RandomAccessStreamReference::CreateFromStream(&thumbnail)?)?;
        }

        updater.Update()?;
        Ok(())
    }

    /// 更新时间轴
    pub fn set_timeline(&self, position: Duration, duration: Duration) -> Result<()> {
        let timeline = SystemMediaTransportControlsTimelineProperties::new()?;
        timeline.SetStartTime(TimeSpan::from(Duration::ZERO))?;
        timeline.SetEndTime(TimeSpan::from(duration))?;
        timeline.SetPosition(TimeSpan::from(position))?;
        timeline.SetMinSeekTime(TimeSpan::from(Duration::ZERO))?;
        timeline.SetMaxSeekTime(TimeSpan::from(duration))?;

        self.smtc.UpdateTimelineProperties(&timeline)?;
        Ok(())
    }

    /// 清理
    pub fn close(&self) -> Result<()> {
        self._player.Close()?;
        Ok(())
    }
}