From a710dbb2f4b826ed88017608448930d14582adfd Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 21 Aug 2025 13:56:05 +0800 Subject: [PATCH] feat(music player): media session API & playback speed control (cloudreve/cloudreve#2629) --- public/locales/en-US/application.json | 5 + public/locales/ja-JP/application.json | 5 + public/locales/zh-CN/application.json | 5 + public/locales/zh-TW/application.json | 5 + .../Viewers/MusicPlayer/MusicPlayer.tsx | 26 ++- .../Viewers/MusicPlayer/PlayerPopup.tsx | 41 +++- .../Viewers/MusicPlayer/RepeatModePopover.tsx | 145 +++++++++++++ src/hooks/useMediaSession.ts | 203 ++++++++++++++++++ 8 files changed, 423 insertions(+), 12 deletions(-) create mode 100644 src/component/Viewers/MusicPlayer/RepeatModePopover.tsx create mode 100644 src/hooks/useMediaSession.ts diff --git a/public/locales/en-US/application.json b/public/locales/en-US/application.json index 91e8255..36e867d 100644 --- a/public/locales/en-US/application.json +++ b/public/locales/en-US/application.json @@ -347,6 +347,11 @@ "subtitle": "Subtitles", "playlist": "Playlist", "openInExternalPlayer": "Open in external player", + "repeatMode": "Repeat Mode", + "listRepeat": "List Repeat", + "singleRepeat": "Single Repeat", + "shuffle": "Shuffle", + "playbackSpeed": "Playback Speed", "searchResult": "Search Results", "preparingBathDownload": "Preparing batch download...", "preparingDownload": "Preparing to download...", diff --git a/public/locales/ja-JP/application.json b/public/locales/ja-JP/application.json index baf3fed..bc51f31 100644 --- a/public/locales/ja-JP/application.json +++ b/public/locales/ja-JP/application.json @@ -347,6 +347,11 @@ "subtitle": "字幕選択", "playlist": "プレイリスト", "openInExternalPlayer": "外部プレーヤーで開く", + "repeatMode": "リピートモード", + "listRepeat": "リストリピート", + "singleRepeat": "シングルリピート", + "shuffle": "シャッフル", + "playbackSpeed": "再生速度", "searchResult": "検索結果", "preparingBathDownload": "ダウンロード準備中...", "preparingDownload": "ダウンロード準備中...", diff --git a/public/locales/zh-CN/application.json b/public/locales/zh-CN/application.json index a5dc787..7d640b8 100644 --- a/public/locales/zh-CN/application.json +++ b/public/locales/zh-CN/application.json @@ -347,6 +347,11 @@ "subtitle": "选择字幕", "playlist": "播放列表", "openInExternalPlayer": "用外部播放器打开", + "repeatMode": "循环模式", + "listRepeat": "列表循环", + "singleRepeat": "单曲循环", + "shuffle": "随机播放", + "playbackSpeed": "播放速度", "searchResult": "搜索结果", "preparingBathDownload": "正在准备打包下载...", "preparingDownload": "正在准备下载...", diff --git a/public/locales/zh-TW/application.json b/public/locales/zh-TW/application.json index 07c2772..dae7fe9 100644 --- a/public/locales/zh-TW/application.json +++ b/public/locales/zh-TW/application.json @@ -347,6 +347,11 @@ "subtitle": "選擇字幕", "playlist": "播放列表", "openInExternalPlayer": "用外部播放器開啟", + "repeatMode": "循環模式", + "listRepeat": "清單循環", + "singleRepeat": "單曲循環", + "shuffle": "隨機播放", + "playbackSpeed": "播放速度", "searchResult": "搜尋結果", "preparingBathDownload": "正在準備打包下載...", "preparingDownload": "正在準備下載...", diff --git a/src/component/Viewers/MusicPlayer/MusicPlayer.tsx b/src/component/Viewers/MusicPlayer/MusicPlayer.tsx index 1ed5e3f..ecd2582 100644 --- a/src/component/Viewers/MusicPlayer/MusicPlayer.tsx +++ b/src/component/Viewers/MusicPlayer/MusicPlayer.tsx @@ -1,12 +1,12 @@ import { IconButton, Tooltip } from "@mui/material"; import { useCallback, useEffect, useRef, useState } from "react"; -import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; -import MusicNote2Play from "../../Icons/MusicNote2Play.tsx"; import { getFileEntityUrl } from "../../../api/api.ts"; -import { getFileLinkedUri } from "../../../util"; -import PlayerPopup from "./PlayerPopup.tsx"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; import SessionManager, { UserSettings } from "../../../session"; +import { getFileLinkedUri } from "../../../util"; import MusicNote2 from "../../Icons/MusicNote2.tsx"; +import MusicNote2Play from "../../Icons/MusicNote2Play.tsx"; +import PlayerPopup from "./PlayerPopup.tsx"; export const LoopMode = { list_repeat: 0, @@ -27,6 +27,7 @@ const MusicPlayer = () => { const [duration, setDuration] = useState(0); const [current, setCurrent] = useState(0); const [loopMode, setLoopMode] = useState(LoopMode.list_repeat); + const [playbackSpeed, setPlaybackSpeed] = useState(1); const playHistory = useRef([]); useEffect(() => { @@ -62,12 +63,13 @@ const MusicPlayer = () => { audio.current.currentTime = 0; audio.current.play(); audio.current.volume = latestVolume ?? volume; + audio.current.playbackRate = playbackSpeed; } catch (e) { console.error(e); } } }, - [playerState, volume], + [playerState, volume, playbackSpeed], ); const loopProceed = useCallback( @@ -153,6 +155,17 @@ const MusicPlayer = () => { setLoopMode((loopMode) => (loopMode + 1) % 3); }, []); + const setLoopModeHandler = useCallback((mode: number) => { + setLoopMode(mode); + }, []); + + const setPlaybackSpeedHandler = useCallback((speed: number) => { + setPlaybackSpeed(speed); + if (audio.current) { + audio.current.playbackRate = speed; + } + }, []); + return ( <>