feat: 添加自动播放 和自动保存正在播放列表功能

This commit is contained in:
alger
2025-01-22 22:16:46 +08:00
parent 80770d6c75
commit 2e06711600
6 changed files with 224 additions and 78 deletions
+3 -1
View File
@@ -16,5 +16,7 @@
"closeAction": "ask", "closeAction": "ask",
"musicQuality": "higher", "musicQuality": "higher",
"fontFamily": "system-ui", "fontFamily": "system-ui",
"fontScope": "global" "fontScope": "global",
"autoPlay": false,
"downloadPath": ""
} }
+2 -1
View File
@@ -63,6 +63,7 @@ onMounted(() => {
store.dispatch('initializeSettings'); store.dispatch('initializeSettings');
store.dispatch('initializeTheme'); store.dispatch('initializeTheme');
store.dispatch('initializeSystemFonts'); store.dispatch('initializeSystemFonts');
store.dispatch('initializePlayState');
if (isMobile.value) { if (isMobile.value) {
store.commit( store.commit(
'setMenus', 'setMenus',
@@ -72,7 +73,7 @@ onMounted(() => {
}); });
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.app-container { .app-container {
@apply h-full w-full; @apply h-full w-full;
user-select: none; user-select: none;
+22 -7
View File
@@ -288,18 +288,33 @@ const MusicFullRef = ref<any>(null);
// 播放暂停按钮事件 // 播放暂停按钮事件
const playMusicEvent = async () => { const playMusicEvent = async () => {
try { try {
// 检查是否有有效的音乐对象和 URL
if (!playMusic.value?.id || !store.state.playMusicUrl) {
console.warn('No valid music or URL available');
store.commit('setPlay', playMusic.value);
return;
}
if (play.value) { if (play.value) {
audioService.pause(); // 暂停播放
store.commit('setPlayMusic', false); if (audioService.getCurrentSound()) {
audioService.pause();
store.commit('setPlayMusic', false);
}
} else { } else {
audioService.play(); // 开始播放
if (audioService.getCurrentSound()) {
// 如果已经有音频实例,直接播放
audioService.play();
} else {
// 如果没有音频实例,重新创建并播放
await audioService.play(store.state.playMusicUrl, playMusic.value);
}
store.commit('setPlayMusic', true); store.commit('setPlayMusic', true);
} }
} catch (error) { } catch (error) {
console.log('error', error); console.error('播放出错:', error);
if (play.value) { store.commit('nextPlay');
store.commit('nextPlay');
}
} }
}; };
+85 -62
View File
@@ -122,78 +122,94 @@ class AudioService {
// 播放控制相关 // 播放控制相关
play(url?: string, track?: SongResult): Promise<Howl> { play(url?: string, track?: SongResult): Promise<Howl> {
// 如果没有提供新的 URL 和 track,且当前有音频实例,则继续播放
if (this.currentSound && !url && !track) { if (this.currentSound && !url && !track) {
this.currentSound.play(); this.currentSound.play();
return Promise.resolve(this.currentSound as Howl); return Promise.resolve(this.currentSound);
} }
// 如果没有提供必要的参数,返回错误
if (!url || !track) {
return Promise.reject(new Error('Missing required parameters: url and track'));
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let retryCount = 0; let retryCount = 0;
const maxRetries = 1; const maxRetries = 1;
const tryPlay = () => { const tryPlay = () => {
// 清理现有的音频实例
if (this.currentSound) { if (this.currentSound) {
this.currentSound.unload(); this.currentSound.unload();
this.currentSound = null;
} }
this.currentSound = null;
this.currentTrack = track as SongResult;
this.currentSound = new Howl({ try {
src: [url as string], this.currentTrack = track;
html5: true, this.currentSound = new Howl({
autoplay: true, src: [url],
volume: localStorage.getItem('volume') html5: true,
? parseFloat(localStorage.getItem('volume') as string) autoplay: true,
: 1, volume: localStorage.getItem('volume')
onloaderror: () => { ? parseFloat(localStorage.getItem('volume') as string)
console.error('Audio load error'); : 1,
if (retryCount < maxRetries) { format: ['mp3', 'aac'],
retryCount++; onloaderror: (id, error) => {
console.log(`Retrying playback (${retryCount}/${maxRetries})...`); console.error('Audio load error:', error);
setTimeout(tryPlay, 1000 * retryCount); if (retryCount < maxRetries) {
} else { retryCount++;
reject(new Error('音频加载失败,请尝试切换其他歌曲')); console.log(`Retrying playback (${retryCount}/${maxRetries})...`);
} setTimeout(tryPlay, 1000 * retryCount);
}, } else {
onplayerror: () => { reject(new Error('音频加载失败,请尝试切换其他歌曲'));
console.error('Audio play error'); }
if (retryCount < maxRetries) { },
retryCount++; onplayerror: (id, error) => {
console.log(`Retrying playback (${retryCount}/${maxRetries})...`); console.error('Audio play error:', error);
setTimeout(tryPlay, 1000 * retryCount); if (retryCount < maxRetries) {
} else { retryCount++;
reject(new Error('音频播放失败,请尝试切换其他歌曲')); console.log(`Retrying playback (${retryCount}/${maxRetries})...`);
setTimeout(tryPlay, 1000 * retryCount);
} else {
reject(new Error('音频播放失败,请尝试切换其他歌曲'));
}
},
onload: () => {
// 音频加载成功后更新媒体会话
if (track && this.currentSound) {
this.updateMediaSessionMetadata(track);
this.updateMediaSessionPositionState();
this.emit('load');
resolve(this.currentSound);
}
} }
});
// 设置音频事件监听
if (this.currentSound) {
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');
});
} }
}); } catch (error) {
console.error('Error creating audio instance:', error);
// 更新媒体会话元数据 reject(error);
this.updateMediaSessionMetadata(track as SongResult); }
// 设置音频事件监听
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');
resolve(this.currentSound as Howl);
});
}; };
tryPlay(); tryPlay();
@@ -210,8 +226,12 @@ class AudioService {
stop() { stop() {
if (this.currentSound) { if (this.currentSound) {
this.currentSound.stop(); try {
this.currentSound.unload(); this.currentSound.stop();
this.currentSound.unload();
} catch (error) {
console.error('Error stopping audio:', error);
}
this.currentSound = null; this.currentSound = null;
} }
this.currentTrack = null; this.currentTrack = null;
@@ -236,10 +256,13 @@ class AudioService {
pause() { pause() {
if (this.currentSound) { if (this.currentSound) {
this.currentSound.pause(); try {
this.currentSound.pause();
} catch (error) {
console.error('Error pausing audio:', error);
}
} }
} }
clearAllListeners() { clearAllListeners() {
this.callbacks = {}; this.callbacks = {};
+101 -7
View File
@@ -4,6 +4,7 @@ import setData from '@/../main/set.json';
import { getLikedList, likeSong } from '@/api/music'; import { getLikedList, likeSong } from '@/api/music';
import { useMusicListHook } from '@/hooks/MusicListHook'; import { useMusicListHook } from '@/hooks/MusicListHook';
import homeRouter from '@/router/home'; import homeRouter from '@/router/home';
import { audioService } from '@/services/audioService';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { isElectron } from '@/utils'; import { isElectron } from '@/utils';
import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme'; import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme';
@@ -11,9 +12,63 @@ import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme';
// 默认设置 // 默认设置
const defaultSettings = setData; const defaultSettings = setData;
function isValidUrl(urlString: string): boolean {
try {
return Boolean(new URL(urlString));
} catch (e) {
return false;
}
}
function getLocalStorageItem<T>(key: string, defaultValue: T): T { function getLocalStorageItem<T>(key: string, defaultValue: T): T {
const item = localStorage.getItem(key); try {
return item ? JSON.parse(item) : defaultValue; const item = localStorage.getItem(key);
if (!item) return defaultValue;
// 尝试解析 JSON
const parsedItem = JSON.parse(item);
// 对于音乐 URL,检查是否是有效的 URL 格式或本地文件路径
if (key === 'currentPlayMusicUrl' && typeof parsedItem === 'string') {
if (!parsedItem.startsWith('local://') && !isValidUrl(parsedItem)) {
console.warn(`Invalid URL in localStorage for key ${key}, using default value`);
localStorage.removeItem(key);
return defaultValue;
}
}
// 对于播放列表,检查是否是数组且每个项都有必要的字段
if (key === 'playList') {
if (!Array.isArray(parsedItem)) {
console.warn(`Invalid playList format in localStorage, using default value`);
localStorage.removeItem(key);
return defaultValue;
}
// 检查每个歌曲对象是否有必要的字段
const isValid = parsedItem.every((item) => item && typeof item === 'object' && 'id' in item);
if (!isValid) {
console.warn(`Invalid song objects in playList, using default value`);
localStorage.removeItem(key);
return defaultValue;
}
}
// 对于当前播放音乐,检查是否是对象且包含必要的字段
if (key === 'currentPlayMusic') {
if (!parsedItem || typeof parsedItem !== 'object' || !('id' in parsedItem)) {
console.warn(`Invalid currentPlayMusic format in localStorage, using default value`);
localStorage.removeItem(key);
return defaultValue;
}
}
return parsedItem;
} catch (error) {
console.warn(`Error parsing localStorage item for key ${key}:`, error);
// 如果解析失败,删除可能损坏的数据
localStorage.removeItem(key);
return defaultValue;
}
} }
export interface State { export interface State {
@@ -44,11 +99,11 @@ const state: State = {
menus: homeRouter, menus: homeRouter,
play: false, play: false,
isPlay: false, isPlay: false,
playMusic: {} as SongResult, playMusic: getLocalStorageItem('currentPlayMusic', {} as SongResult),
playMusicUrl: '', playMusicUrl: getLocalStorageItem('currentPlayMusicUrl', ''),
user: getLocalStorageItem('user', null), user: getLocalStorageItem('user', null),
playList: [], playList: getLocalStorageItem('playList', []),
playListIndex: 0, playListIndex: getLocalStorageItem('playListIndex', 0),
setData: defaultSettings, setData: defaultSettings,
lyric: {}, lyric: {},
isMobile: false, isMobile: false,
@@ -72,12 +127,16 @@ const mutations = {
}, },
async setPlay(state: State, playMusic: SongResult) { async setPlay(state: State, playMusic: SongResult) {
await handlePlayMusic(state, playMusic); await handlePlayMusic(state, playMusic);
localStorage.setItem('currentPlayMusic', JSON.stringify(state.playMusic));
localStorage.setItem('currentPlayMusicUrl', state.playMusicUrl);
}, },
setIsPlay(state: State, isPlay: boolean) { setIsPlay(state: State, isPlay: boolean) {
state.isPlay = isPlay; state.isPlay = isPlay;
localStorage.setItem('isPlaying', isPlay.toString());
}, },
setPlayMusic(state: State, play: boolean) { async setPlayMusic(state: State, play: boolean) {
state.play = play; state.play = play;
localStorage.setItem('isPlaying', play.toString());
}, },
setMusicFull(state: State, musicFull: boolean) { setMusicFull(state: State, musicFull: boolean) {
state.musicFull = musicFull; state.musicFull = musicFull;
@@ -85,6 +144,8 @@ const mutations = {
setPlayList(state: State, playList: SongResult[]) { setPlayList(state: State, playList: SongResult[]) {
state.playListIndex = playList.findIndex((item) => item.id === state.playMusic.id); state.playListIndex = playList.findIndex((item) => item.id === state.playMusic.id);
state.playList = playList; state.playList = playList;
localStorage.setItem('playList', JSON.stringify(playList));
localStorage.setItem('playListIndex', state.playListIndex.toString());
}, },
async nextPlay(state: State) { async nextPlay(state: State) {
await nextPlay(state); await nextPlay(state);
@@ -238,6 +299,39 @@ const actions = {
} catch (error) { } catch (error) {
console.error('获取系统字体失败:', error); console.error('获取系统字体失败:', error);
} }
},
async initializePlayState({ state, commit }: { state: State; commit: any }) {
const savedPlayList = getLocalStorageItem('playList', []);
const savedPlayMusic = getLocalStorageItem('currentPlayMusic', null);
if (savedPlayList.length > 0) {
commit('setPlayList', savedPlayList);
}
if (savedPlayMusic && Object.keys(savedPlayMusic).length > 0) {
// 不直接使用保存的 URL,而是重新获取
try {
// 使用 handlePlayMusic 来重新获取音乐 URL
// 根据自动播放设置决定是否恢复播放状态
const shouldAutoPlay = state.setData.autoPlay;
if (shouldAutoPlay) {
await handlePlayMusic(state, savedPlayMusic);
}
state.play = shouldAutoPlay;
state.isPlay = true;
} catch (error) {
console.error('重新获取音乐链接失败:', error);
// 清除无效的播放状态
state.play = false;
state.isPlay = false;
state.playMusic = {} as SongResult;
state.playMusicUrl = '';
localStorage.removeItem('currentPlayMusic');
localStorage.removeItem('currentPlayMusicUrl');
localStorage.removeItem('isPlaying');
}
}
} }
}; };
+11
View File
@@ -136,6 +136,17 @@
style="width: 160px" style="width: 160px"
/> />
</div> </div>
<div class="set-item">
<div>
<div class="set-item-title">自动播放</div>
<div class="set-item-content">重新打开应用时是否自动继续播放</div>
</div>
<n-switch v-model:value="setData.autoPlay">
<template #checked>开启</template>
<template #unchecked>关闭</template>
</n-switch>
</div>
</div> </div>
</div> </div>