diff --git a/src/renderer/hooks/MusicHook.ts b/src/renderer/hooks/MusicHook.ts index 0871c5d..e382cc3 100644 --- a/src/renderer/hooks/MusicHook.ts +++ b/src/renderer/hooks/MusicHook.ts @@ -1,4 +1,4 @@ -import { computed, ref } from 'vue'; +import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import { audioService } from '@/services/audioService'; import store from '@/store'; @@ -44,10 +44,9 @@ document.onkeyup = (e) => { watch( () => store.state.playMusicUrl, (newVal) => { - if (newVal) { - audioService.play(newVal); - sound.value = audioService.getCurrentSound(); - audioServiceOn(audioService); + if (newVal && playMusic.value) { + sound.value = audioService.play(newVal, playMusic.value); + setupAudioListeners(); } } ); @@ -70,11 +69,11 @@ watch( } ); -export const audioServiceOn = (audio: typeof audioService) => { +const setupAudioListeners = () => { let interval: any = null; // 监听播放 - audio.onPlay(() => { + audioService.on('play', () => { store.commit('setPlayMusic', true); interval = setInterval(() => { nowTime.value = sound.value?.seek() as number; @@ -83,12 +82,10 @@ export const audioServiceOn = (audio: typeof audioService) => { if (newIndex !== nowIndex.value) { nowIndex.value = newIndex; currentLrcProgress.value = 0; - // 当歌词索引更新时,发送歌词数据 if (isElectron && isLyricWindowOpen.value) { sendLyricToWin(); } } - // 定期发送歌词数据更新 if (isElectron && isLyricWindowOpen.value) { sendLyricToWin(); } @@ -96,33 +93,29 @@ export const audioServiceOn = (audio: typeof audioService) => { }); // 监听暂停 - audio.onPause(() => { + audioService.on('pause', () => { store.commit('setPlayMusic', false); clearInterval(interval); - // 暂停时也发送一次状态更新 if (isElectron && isLyricWindowOpen.value) { sendLyricToWin(); } }); // 监听结束 - audio.onEnd(() => { + audioService.on('end', () => { if (store.state.playMode === 1) { // 单曲循环模式 - audio.getCurrentSound()?.play(); + sound.value?.play(); } else if (store.state.playMode === 2) { // 随机播放模式 const { playList } = store.state; if (playList.length <= 1) { - // 如果播放列表只有一首歌或为空,则重新播放当前歌曲 - audio.getCurrentSound()?.play(); + sound.value?.play(); } else { - // 随机选择一首不同的歌 let randomIndex; do { randomIndex = Math.floor(Math.random() * playList.length); } while (randomIndex === store.state.playListIndex && playList.length > 1); - store.state.playListIndex = randomIndex; store.commit('setPlay', playList[randomIndex]); } @@ -131,6 +124,15 @@ export const audioServiceOn = (audio: typeof audioService) => { store.commit('nextPlay'); } }); + + // 监听上一曲/下一曲控制 + audioService.on('previoustrack', () => { + store.commit('prevPlay'); + }); + + audioService.on('nexttrack', () => { + store.commit('nextPlay'); + }); }; export const play = () => { @@ -357,3 +359,15 @@ if (isElectron) { } }); } + +// 在组件挂载时设置监听器 +onMounted(() => { + if (isPlaying.value) { + useLyricProgress(); + } +}); + +// 在组件卸载时清理 +onUnmounted(() => { + audioService.stop(); +}); diff --git a/src/renderer/services/audioService.ts b/src/renderer/services/audioService.ts index d74762e..f5f2141 100644 --- a/src/renderer/services/audioService.ts +++ b/src/renderer/services/audioService.ts @@ -1,13 +1,134 @@ import { Howl } from 'howler'; +import type { SongResult } from '@/type/music'; + class AudioService { private currentSound: Howl | null = null; - play(url: string) { + private currentTrack: SongResult | null = null; + + constructor() { + if ('mediaSession' in navigator) { + this.initMediaSession(); + } + } + + private initMediaSession() { + navigator.mediaSession.setActionHandler('play', () => { + this.currentSound?.play(); + }); + + navigator.mediaSession.setActionHandler('pause', () => { + this.currentSound?.pause(); + }); + + navigator.mediaSession.setActionHandler('stop', () => { + this.stop(); + }); + + navigator.mediaSession.setActionHandler('seekto', (event) => { + if (event.seekTime && this.currentSound) { + this.currentSound.seek(event.seekTime); + } + }); + + navigator.mediaSession.setActionHandler('seekbackward', (event) => { + if (this.currentSound) { + const currentTime = this.currentSound.seek() as number; + this.currentSound.seek(currentTime - (event.seekOffset || 10)); + } + }); + + navigator.mediaSession.setActionHandler('seekforward', (event) => { + if (this.currentSound) { + const currentTime = this.currentSound.seek() as number; + this.currentSound.seek(currentTime + (event.seekOffset || 10)); + } + }); + + navigator.mediaSession.setActionHandler('previoustrack', () => { + // 这里需要通过回调通知外部 + this.emit('previoustrack'); + }); + + navigator.mediaSession.setActionHandler('nexttrack', () => { + // 这里需要通过回调通知外部 + this.emit('nexttrack'); + }); + } + + private updateMediaSessionMetadata(track: SongResult) { + if (!('mediaSession' in navigator)) return; + + const artists = track.ar ? track.ar.map((a) => a.name) : track.song.artists?.map((a) => a.name); + const album = track.al ? track.al.name : track.song.album.name; + const artwork = ['96', '128', '192', '256', '384', '512'].map((size) => ({ + src: `${track.picUrl}?param=${size}y${size}`, + type: 'image/jpg', + sizes: `${size}x${size}` + })); + const metadata = { + title: track.name || '', + artist: artists ? artists.join(',') : '', + album: album || '', + artwork + }; + + navigator.mediaSession.metadata = new window.MediaMetadata(metadata); + } + + private updateMediaSessionState(isPlaying: boolean) { + if (!('mediaSession' in navigator)) return; + + navigator.mediaSession.playbackState = isPlaying ? 'playing' : 'paused'; + this.updateMediaSessionPositionState(); + } + + private updateMediaSessionPositionState() { + if (!this.currentSound || !('mediaSession' in navigator)) return; + + if ('setPositionState' in navigator.mediaSession) { + navigator.mediaSession.setPositionState({ + duration: this.currentSound.duration(), + playbackRate: 1.0, + position: this.currentSound.seek() as number + }); + } + } + + // 事件处理相关 + private callbacks: { [key: string]: Function[] } = {}; + + private emit(event: string, ...args: any[]) { + const eventCallbacks = this.callbacks[event]; + if (eventCallbacks) { + eventCallbacks.forEach((callback) => callback(...args)); + } + } + + on(event: string, callback: Function) { + if (!this.callbacks[event]) { + this.callbacks[event] = []; + } + this.callbacks[event].push(callback); + } + + off(event: string, callback: Function) { + const eventCallbacks = this.callbacks[event]; + if (eventCallbacks) { + this.callbacks[event] = eventCallbacks.filter((cb) => cb !== callback); + } + } + + // 播放控制相关 + play(url: string, track: SongResult) { + // Howler.unload(); if (this.currentSound) { this.currentSound.unload(); } this.currentSound = null; + this.currentTrack = track; + this.currentSound = new Howl({ src: [url], html5: true, @@ -17,6 +138,34 @@ class AudioService { : 1 }); + // 更新媒体会话元数据 + this.updateMediaSessionMetadata(track); + + // 设置音频事件监听 + this.currentSound.on('play', () => { + this.updateMediaSessionState(true); + this.emit('play'); + }); + + this.currentSound.on('pause', () => { + this.updateMediaSessionState(false); + this.emit('pause'); + }); + + this.currentSound.on('end', () => { + this.emit('end'); + }); + + this.currentSound.on('seek', () => { + this.updateMediaSessionPositionState(); + this.emit('seek'); + }); + + this.currentSound.on('load', () => { + this.updateMediaSessionPositionState(); + this.emit('load'); + }); + return this.currentSound; } @@ -24,32 +173,33 @@ class AudioService { return this.currentSound; } + getCurrentTrack() { + return this.currentTrack; + } + stop() { if (this.currentSound) { this.currentSound.stop(); this.currentSound.unload(); this.currentSound = null; } - } - - // 监听播放 - onPlay(callback: () => void) { - if (this.currentSound) { - this.currentSound.on('play', callback); + this.currentTrack = null; + if ('mediaSession' in navigator) { + navigator.mediaSession.playbackState = 'none'; } } - // 监听暂停 - onPause(callback: () => void) { + setVolume(volume: number) { if (this.currentSound) { - this.currentSound.on('pause', callback); + this.currentSound.volume(volume); + localStorage.setItem('volume', volume.toString()); } } - // 监听结束 - onEnd(callback: () => void) { + seek(time: number) { if (this.currentSound) { - this.currentSound.on('end', callback); + this.currentSound.seek(time); + this.updateMediaSessionPositionState(); } } }