mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-23 23:57:22 +08:00
✨ feat: 优化音乐播放 控制 系统控制功能 (#36,#16)
fixed #36,#16
This commit is contained in:
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user