feat: 优化音乐播放 控制 系统控制功能 (#36,#16)

fixed #36,#16
This commit is contained in:
alger
2025-01-12 19:14:25 +08:00
parent e70fed37da
commit 3c64473dbb
2 changed files with 194 additions and 30 deletions
+31 -17
View File
@@ -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();
});
+163 -13
View File
@@ -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();
}
}
}