From 655473699a1e48a60c7d338c526c5d2c5a6b1800 Mon Sep 17 00:00:00 2001 From: Java-wyx <12211306@mail.sustech.edu.cn> Date: Mon, 19 May 2025 17:59:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E9=80=9F=E5=BA=A6=E6=8E=A7=E5=88=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现有播放器不支持改变播放速度,用户无法实现 0.5×、1.5×、2.0× 等快进/慢放需求。为了提升可用性和灵活性,决定在播放栏增加速度选择菜单,并支持 Media Session API 同步速率 --- src/i18n/lang/en-US/player.ts | 3 +- src/i18n/lang/zh-CN/player.ts | 3 +- src/renderer/components/player/PlayBar.vue | 54 ++++++++++++++++++++++ src/renderer/services/audioService.ts | 34 +++++++++++++- src/renderer/store/modules/player.ts | 25 +++++++++- 5 files changed, 115 insertions(+), 4 deletions(-) diff --git a/src/i18n/lang/en-US/player.ts b/src/i18n/lang/en-US/player.ts index 8ec5932..538a97a 100644 --- a/src/i18n/lang/en-US/player.ts +++ b/src/i18n/lang/en-US/player.ts @@ -58,7 +58,8 @@ export default { next: 'Next', volume: 'Volume', favorite: 'Favorite {name}', - unFavorite: 'Unfavorite {name}' + unFavorite: 'Unfavorite {name}', + playbackSpeed: 'Playback Speed' }, eq: { title: 'Equalizer', diff --git a/src/i18n/lang/zh-CN/player.ts b/src/i18n/lang/zh-CN/player.ts index bc5296b..b9986da 100644 --- a/src/i18n/lang/zh-CN/player.ts +++ b/src/i18n/lang/zh-CN/player.ts @@ -59,7 +59,8 @@ export default { volume: '音量', favorite: '已收藏{name}', unFavorite: '已取消收藏{name}', - miniPlayBar: '迷你播放栏' + miniPlayBar: '迷你播放栏', + playbackSpeed: '播放速度' }, eq: { title: '均衡器', diff --git a/src/renderer/components/player/PlayBar.vue b/src/renderer/components/player/PlayBar.vue index cc2b5f2..cc4fc91 100644 --- a/src/renderer/components/player/PlayBar.vue +++ b/src/renderer/components/player/PlayBar.vue @@ -161,6 +161,23 @@ {{ t('player.playBar.playList') }} + + + + + {{ t('player.playBar.playbackSpeed') }} + + @@ -319,6 +336,23 @@ const playModeText = computed(() => { } }); +// 播放速度控制 +const playbackRate = ref(1.0); +const playbackRateOptions = [ + { label: '0.5x', key: 0.5 }, + { label: '0.75x', key: 0.75 }, + { label: '1.0x', key: 1.0 }, + { label: '1.25x', key: 1.25 }, + { label: '1.5x', key: 1.5 }, + { label: '2.0x', key: 2.0 } +]; + + +const handlePlaybackRateChange = (rate: number) => { + playbackRate.value = rate; + audioService.setPlaybackRate(rate); +}; + // 切换播放模式 const togglePlayMode = () => { playerStore.togglePlayMode(); @@ -702,4 +736,24 @@ const openPlayListDrawer = () => { color: white; animation: spin 1s linear infinite; } + +.play-speed { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + padding: 0 8px; +} + +.speed-button { + font-size: 14px; + color: var(--text-color); + padding: 4px 8px; + border-radius: 4px; + background: var(--hover-color); +} + +.speed-button:hover { + background: var(--hover-color-dark); +} diff --git a/src/renderer/services/audioService.ts b/src/renderer/services/audioService.ts index 585f015..5347c51 100644 --- a/src/renderer/services/audioService.ts +++ b/src/renderer/services/audioService.ts @@ -18,6 +18,8 @@ class AudioService { private bypass = false; + private playbackRate = 1.0; // 添加播放速度属性 + // 预设的 EQ 频段 private readonly frequencies = [31, 62, 125, 250, 500, 1000, 2000, 4000, 8000, 16000]; @@ -143,7 +145,7 @@ class AudioService { if ('setPositionState' in navigator.mediaSession) { navigator.mediaSession.setPositionState({ duration: this.currentSound.duration(), - playbackRate: 1.0, + playbackRate: this.playbackRate, position: this.currentSound.seek() as number }); } @@ -565,6 +567,7 @@ class AudioService { volume: localStorage.getItem('volume') ? parseFloat(localStorage.getItem('volume') as string) : 1, + rate: this.playbackRate, // 设置初始播放速度 format: ['mp3', 'aac'], onloaderror: (_, error) => { console.error('Audio load error:', error); @@ -747,6 +750,35 @@ class AudioService { public setCurrentPreset(preset: string): void { localStorage.setItem('currentPreset', preset); } + + public setPlaybackRate(rate: number) { + if (!this.currentSound) return; + this.playbackRate = rate; + + // Howler 的 rate() 在 html5 模式下不生效 + this.currentSound.rate(rate); + + // 取出底层 HTMLAudioElement,改原生 playbackRate + const sounds = (this.currentSound as any)._sounds as any[]; + sounds.forEach(({ _node }) => { + if (_node instanceof HTMLAudioElement) { + _node.playbackRate = rate; + } + }); + + // 同步给 Media Session UI + if ('mediaSession' in navigator && 'setPositionState' in navigator.mediaSession) { + navigator.mediaSession.setPositionState({ + duration: this.currentSound.duration(), + playbackRate: rate, + position: this.currentSound.seek() as number + }); + } + } + + public getPlaybackRate(): number { + return this.playbackRate; + } } export const audioService = new AudioService(); diff --git a/src/renderer/store/modules/player.ts b/src/renderer/store/modules/player.ts index 7994031..852b30d 100644 --- a/src/renderer/store/modules/player.ts +++ b/src/renderer/store/modules/player.ts @@ -399,6 +399,9 @@ export const usePlayerStore = defineStore('player', () => { value: 0 })); + // 添加播放速度状态 + const playbackRate = ref(1.0); + // 清空播放列表 const clearPlayAll = async () => { audioService.pause() @@ -1042,6 +1045,23 @@ export const usePlayerStore = defineStore('player', () => { setPlayList(newPlayList); }; + // 设置播放速度 + const setPlaybackRate = (rate: number) => { + playbackRate.value = rate; + audioService.setPlaybackRate(rate); + // 保存到本地存储 + localStorage.setItem('playbackRate', rate.toString()); + }; + + // 初始化播放速度 + const initializePlaybackRate = () => { + const savedRate = localStorage.getItem('playbackRate'); + if (savedRate) { + playbackRate.value = parseFloat(savedRate); + audioService.setPlaybackRate(playbackRate.value); + } + }; + // 初始化播放状态 const initializePlayState = async () => { const settingStore = useSettingsStore(); @@ -1093,6 +1113,7 @@ export const usePlayerStore = defineStore('player', () => { localStorage.removeItem('playProgress'); } } + initializePlaybackRate(); }; const initializeFavoriteList = async () => { @@ -1343,6 +1364,8 @@ export const usePlayerStore = defineStore('player', () => { playAudio, reparseCurrentSong, setPlayListDrawerVisible, - handlePause + handlePause, + playbackRate, + setPlaybackRate }; });