mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-25 00:37:24 +08:00
🔧 chore:改进播放器组件的加载状态显示, 优化 GD音乐解析逻辑,增加超时处理,调整音源列表
This commit is contained in:
+85
-67
@@ -28,82 +28,100 @@ export interface ParsedMusicResult {
|
|||||||
* @param id 音乐ID
|
* @param id 音乐ID
|
||||||
* @param data 音乐数据,包含名称和艺术家信息
|
* @param data 音乐数据,包含名称和艺术家信息
|
||||||
* @param quality 音质设置
|
* @param quality 音质设置
|
||||||
|
* @param timeout 超时时间(毫秒),默认15000ms
|
||||||
* @returns 解析后的音乐URL及相关信息
|
* @returns 解析后的音乐URL及相关信息
|
||||||
*/
|
*/
|
||||||
export const parseFromGDMusic = async (
|
export const parseFromGDMusic = async (
|
||||||
id: number,
|
id: number,
|
||||||
data: any,
|
data: any,
|
||||||
quality: string = '320'
|
quality: string = '999',
|
||||||
|
timeout: number = 15000
|
||||||
): Promise<ParsedMusicResult | null> => {
|
): Promise<ParsedMusicResult | null> => {
|
||||||
|
// 创建一个超时Promise
|
||||||
|
const timeoutPromise = new Promise<null>((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error('GD音乐台解析超时'));
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 处理不同数据结构
|
// 使用Promise.race竞争主解析流程和超时
|
||||||
if (!data) {
|
return await Promise.race([
|
||||||
console.error('GD音乐台解析:歌曲数据为空');
|
(async () => {
|
||||||
throw new Error('歌曲数据为空');
|
// 处理不同数据结构
|
||||||
}
|
if (!data) {
|
||||||
|
console.error('GD音乐台解析:歌曲数据为空');
|
||||||
const songName = data.name || '';
|
throw new Error('歌曲数据为空');
|
||||||
let artistNames = '';
|
|
||||||
|
|
||||||
// 处理不同的艺术家字段结构
|
|
||||||
if (data.artists && Array.isArray(data.artists)) {
|
|
||||||
artistNames = data.artists.map(artist => artist.name).join(' ');
|
|
||||||
} else if (data.ar && Array.isArray(data.ar)) {
|
|
||||||
artistNames = data.ar.map(artist => artist.name).join(' ');
|
|
||||||
} else if (data.artist) {
|
|
||||||
artistNames = typeof data.artist === 'string' ? data.artist : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchQuery = `${songName} ${artistNames}`.trim();
|
|
||||||
|
|
||||||
if (!searchQuery || searchQuery.length < 2) {
|
|
||||||
console.error('GD音乐台解析:搜索查询过短', { name: songName, artists: artistNames });
|
|
||||||
throw new Error('搜索查询过短');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有可用的音乐源
|
|
||||||
const allSources = [
|
|
||||||
'tencent', 'kugou', 'kuwo', 'migu', 'netease',
|
|
||||||
'joox', 'ytmusic', 'spotify', 'qobuz', 'deezer'
|
|
||||||
] as MusicSourceType[];
|
|
||||||
|
|
||||||
console.log('GD音乐台开始搜索:', searchQuery);
|
|
||||||
|
|
||||||
// 依次尝试所有音源
|
|
||||||
for (const source of allSources) {
|
|
||||||
try {
|
|
||||||
const result = await searchAndGetUrl(source, searchQuery, quality);
|
|
||||||
if (result) {
|
|
||||||
console.log(`GD音乐台成功通过 ${result.source} 解析音乐!`);
|
|
||||||
// 返回符合原API格式的数据
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
data: {
|
|
||||||
url: result.url.replace(/\\/g, ''),
|
|
||||||
br: parseInt(result.br, 10) * 1000 || 320000,
|
|
||||||
size: result.size || 0,
|
|
||||||
md5: '',
|
|
||||||
platform: 'gdmusic',
|
|
||||||
gain: 0
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
id: parseInt(String(id), 10),
|
|
||||||
type: 'song'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error(`GD音乐台 ${source} 音源解析失败:`, error);
|
const songName = data.name || '';
|
||||||
// 该音源失败,继续尝试下一个音源
|
let artistNames = '';
|
||||||
continue;
|
|
||||||
}
|
// 处理不同的艺术家字段结构
|
||||||
|
if (data.artists && Array.isArray(data.artists)) {
|
||||||
|
artistNames = data.artists.map(artist => artist.name).join(' ');
|
||||||
|
} else if (data.ar && Array.isArray(data.ar)) {
|
||||||
|
artistNames = data.ar.map(artist => artist.name).join(' ');
|
||||||
|
} else if (data.artist) {
|
||||||
|
artistNames = typeof data.artist === 'string' ? data.artist : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchQuery = `${songName} ${artistNames}`.trim();
|
||||||
|
|
||||||
|
if (!searchQuery || searchQuery.length < 2) {
|
||||||
|
console.error('GD音乐台解析:搜索查询过短', { name: songName, artists: artistNames });
|
||||||
|
throw new Error('搜索查询过短');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有可用的音乐源 netease、kuwo、joox、tidal
|
||||||
|
const allSources = [
|
||||||
|
'kuwo', 'joox', 'tidal', 'netease'
|
||||||
|
] as MusicSourceType[];
|
||||||
|
|
||||||
|
console.log('GD音乐台开始搜索:', searchQuery);
|
||||||
|
|
||||||
|
// 依次尝试所有音源
|
||||||
|
for (const source of allSources) {
|
||||||
|
try {
|
||||||
|
const result = await searchAndGetUrl(source, searchQuery, quality);
|
||||||
|
if (result) {
|
||||||
|
console.log(`GD音乐台成功通过 ${result.source} 解析音乐!`);
|
||||||
|
// 返回符合原API格式的数据
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
url: result.url.replace(/\\/g, ''),
|
||||||
|
br: parseInt(result.br, 10) * 1000 || 320000,
|
||||||
|
size: result.size || 0,
|
||||||
|
md5: '',
|
||||||
|
platform: 'gdmusic',
|
||||||
|
gain: 0
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
id: parseInt(String(id), 10),
|
||||||
|
type: 'song'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`GD音乐台 ${source} 音源解析失败:`, error);
|
||||||
|
// 该音源失败,继续尝试下一个音源
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('GD音乐台所有音源均解析失败');
|
||||||
|
return null;
|
||||||
|
})(),
|
||||||
|
timeoutPromise
|
||||||
|
]);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.message === 'GD音乐台解析超时') {
|
||||||
|
console.error('GD音乐台解析超时(15秒):', error);
|
||||||
|
} else {
|
||||||
|
console.error('GD音乐台解析完全失败:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('GD音乐台所有音源均解析失败');
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('GD音乐台解析完全失败:', error);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,32 +31,32 @@
|
|||||||
|
|
||||||
<!-- 控制按钮区域 -->
|
<!-- 控制按钮区域 -->
|
||||||
<div class="control-buttons">
|
<div class="control-buttons">
|
||||||
<button class="control-button previous" @click="handlePrev">
|
<div class="control-button previous" @click="handlePrev">
|
||||||
<i class="iconfont icon-prev"></i>
|
<i class="iconfont icon-prev"></i>
|
||||||
</button>
|
</div>
|
||||||
<button class="control-button play" @click="playMusicEvent">
|
<div class="control-button play" @click="playMusicEvent">
|
||||||
<i class="iconfont" :class="play ? 'icon-stop' : 'icon-play'"></i>
|
<i class="iconfont" :class="play ? 'icon-stop' : 'icon-play'"></i>
|
||||||
</button>
|
</div>
|
||||||
<button class="control-button next" @click="handleNext">
|
<div class="control-button next" @click="handleNext">
|
||||||
<i class="iconfont icon-next"></i>
|
<i class="iconfont icon-next"></i>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧功能按钮 -->
|
<!-- 右侧功能按钮 -->
|
||||||
<div class="function-buttons">
|
<div class="function-buttons">
|
||||||
<button class="function-button">
|
<div class="function-button">
|
||||||
<i
|
<i
|
||||||
class="iconfont icon-likefill"
|
class="iconfont icon-likefill"
|
||||||
:class="{ 'like-active': isFavorite }"
|
:class="{ 'like-active': isFavorite }"
|
||||||
@click="toggleFavorite"
|
@click="toggleFavorite"
|
||||||
></i>
|
></i>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<n-popover trigger="click" :z-index="99999999" placement="top" :show-arrow="false">
|
<n-popover trigger="click" :z-index="99999999" placement="top" :show-arrow="false">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<button class="function-button" @click="mute">
|
<div class="function-button" @click="mute">
|
||||||
<i class="iconfont" :class="getVolumeIcon"></i>
|
<i class="iconfont" :class="getVolumeIcon"></i>
|
||||||
</button>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="volume-slider-wrapper">
|
<div class="volume-slider-wrapper">
|
||||||
<n-slider
|
<n-slider
|
||||||
@@ -69,15 +69,15 @@
|
|||||||
</n-popover>
|
</n-popover>
|
||||||
|
|
||||||
<!-- 播放列表按钮 -->
|
<!-- 播放列表按钮 -->
|
||||||
<button v-if="!component" class="function-button" @click="togglePlaylist">
|
<div v-if="!component" class="function-button" @click="togglePlaylist">
|
||||||
<i class="iconfont icon-list"></i>
|
<i class="iconfont icon-list"></i>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 关闭按钮 -->
|
<!-- 关闭按钮 -->
|
||||||
<button v-if="!component" class="close-button" @click="handleClose">
|
<div v-if="!component" class="close-button" @click="handleClose">
|
||||||
<i class="iconfont ri-close-line"></i>
|
<i class="iconfont ri-close-line"></i>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 进度条 -->
|
<!-- 进度条 -->
|
||||||
|
|||||||
@@ -39,6 +39,9 @@
|
|||||||
lazy
|
lazy
|
||||||
preview-disabled
|
preview-disabled
|
||||||
/>
|
/>
|
||||||
|
<div v-if="playMusic?.playLoading" class="loading-overlay">
|
||||||
|
<i class="ri-loader-4-line loading-icon"></i>
|
||||||
|
</div>
|
||||||
<div class="hover-arrow">
|
<div class="hover-arrow">
|
||||||
<div class="hover-content">
|
<div class="hover-content">
|
||||||
<!-- <i class="ri-arrow-up-s-line text-3xl" :class="{ 'ri-arrow-down-s-line': musicFullVisible }"></i> -->
|
<!-- <i class="ri-arrow-up-s-line text-3xl" :class="{ 'ri-arrow-down-s-line': musicFullVisible }"></i> -->
|
||||||
@@ -758,4 +761,25 @@ const handleDeleteSong = (song: SongResult) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
@apply absolute inset-0 flex items-center justify-center rounded-2xl;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,13 +34,18 @@
|
|||||||
:class="{ 'only-cover': config.hideLyrics }"
|
:class="{ 'only-cover': config.hideLyrics }"
|
||||||
:style="{ color: textColors.theme === 'dark' ? '#000000' : '#ffffff' }"
|
:style="{ color: textColors.theme === 'dark' ? '#000000' : '#ffffff' }"
|
||||||
>
|
>
|
||||||
<n-image
|
<div class="img-container relative">
|
||||||
ref="PicImgRef"
|
<n-image
|
||||||
:src="getImgUrl(playMusic?.picUrl, '500y500')"
|
ref="PicImgRef"
|
||||||
class="img"
|
:src="getImgUrl(playMusic?.picUrl, '500y500')"
|
||||||
lazy
|
class="img"
|
||||||
preview-disabled
|
lazy
|
||||||
/>
|
preview-disabled
|
||||||
|
/>
|
||||||
|
<div v-if="playMusic?.playLoading" class="loading-overlay">
|
||||||
|
<i class="ri-loader-4-line loading-icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="music-info">
|
<div class="music-info">
|
||||||
<div class="music-content-name">{{ playMusic.name }}</div>
|
<div class="music-content-name">{{ playMusic.name }}</div>
|
||||||
<div class="music-content-singer">
|
<div class="music-content-singer">
|
||||||
@@ -549,10 +554,14 @@ defineExpose({
|
|||||||
max-width: none;
|
max-width: none;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
|
|
||||||
.img {
|
.img-container {
|
||||||
@apply w-[50vh] h-[50vh] mb-8;
|
@apply w-[50vh] h-[50vh] mb-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
@apply w-full h-full;
|
||||||
|
}
|
||||||
|
|
||||||
.music-info {
|
.music-info {
|
||||||
@apply text-center w-[600px];
|
@apply text-center w-[600px];
|
||||||
|
|
||||||
@@ -568,6 +577,10 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.img-container {
|
||||||
|
@apply relative w-full h-full;
|
||||||
|
}
|
||||||
|
|
||||||
.img {
|
.img {
|
||||||
@apply rounded-xl w-full h-full shadow-2xl transition-all duration-300;
|
@apply rounded-xl w-full h-full shadow-2xl transition-all duration-300;
|
||||||
}
|
}
|
||||||
@@ -763,4 +776,25 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
@apply absolute inset-0 flex items-center justify-center rounded-xl;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: white;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -189,17 +189,26 @@ export const loadLrc = async (id: string | number): Promise<ILyric> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getSongDetail = async (playMusic: SongResult) => {
|
const getSongDetail = async (playMusic: SongResult) => {
|
||||||
playMusic.playLoading = true;
|
// playMusic.playLoading 在 handlePlayMusic 中已设置,这里不再设置
|
||||||
|
|
||||||
if (playMusic.source === 'bilibili') {
|
if (playMusic.source === 'bilibili') {
|
||||||
console.log('处理B站音频详情');
|
console.log('处理B站音频详情');
|
||||||
const { backgroundColor, primaryColor } =
|
try {
|
||||||
playMusic.backgroundColor && playMusic.primaryColor
|
// 如果需要获取URL
|
||||||
? playMusic
|
if (!playMusic.playMusicUrl && playMusic.bilibiliData) {
|
||||||
: await getImageLinearBackground(getImgUrl(playMusic?.picUrl, '30y30'));
|
playMusic.playMusicUrl = await getBilibiliAudioUrl(
|
||||||
|
playMusic.bilibiliData.bvid,
|
||||||
playMusic.playLoading = false;
|
playMusic.bilibiliData.cid
|
||||||
return { ...playMusic, backgroundColor, primaryColor } as SongResult;
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
playMusic.playLoading = false;
|
||||||
|
return { ...playMusic} as SongResult;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取B站音频详情失败:', error);
|
||||||
|
playMusic.playLoading = false;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playMusic.expiredAt && playMusic.expiredAt < Date.now()) {
|
if (playMusic.expiredAt && playMusic.expiredAt < Date.now()) {
|
||||||
@@ -207,17 +216,23 @@ const getSongDetail = async (playMusic: SongResult) => {
|
|||||||
playMusic.playMusicUrl = undefined;
|
playMusic.playMusicUrl = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const playMusicUrl = playMusic.playMusicUrl || (await getSongUrl(playMusic.id, playMusic));
|
try {
|
||||||
playMusic.createdAt = Date.now();
|
const playMusicUrl = playMusic.playMusicUrl || (await getSongUrl(playMusic.id, playMusic));
|
||||||
// 半小时后过期
|
playMusic.createdAt = Date.now();
|
||||||
playMusic.expiredAt = playMusic.createdAt + 1800000;
|
// 半小时后过期
|
||||||
const { backgroundColor, primaryColor } =
|
playMusic.expiredAt = playMusic.createdAt + 1800000;
|
||||||
playMusic.backgroundColor && playMusic.primaryColor
|
const { backgroundColor, primaryColor } =
|
||||||
? playMusic
|
playMusic.backgroundColor && playMusic.primaryColor
|
||||||
: await getImageLinearBackground(getImgUrl(playMusic?.picUrl, '30y30'));
|
? playMusic
|
||||||
|
: await getImageLinearBackground(getImgUrl(playMusic?.picUrl, '30y30'));
|
||||||
|
|
||||||
playMusic.playLoading = false;
|
playMusic.playLoading = false;
|
||||||
return { ...playMusic, playMusicUrl, backgroundColor, primaryColor } as SongResult;
|
return { ...playMusic, playMusicUrl, backgroundColor, primaryColor } as SongResult;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取音频URL失败:', error);
|
||||||
|
playMusic.playLoading = false;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const preloadNextSong = (nextSongUrl: string) => {
|
const preloadNextSong = (nextSongUrl: string) => {
|
||||||
@@ -389,71 +404,72 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
const currentPlayListIndex = computed(() => playListIndex.value);
|
const currentPlayListIndex = computed(() => playListIndex.value);
|
||||||
|
|
||||||
const handlePlayMusic = async (music: SongResult, isPlay: boolean = true) => {
|
const handlePlayMusic = async (music: SongResult, isPlay: boolean = true) => {
|
||||||
// 处理B站视频,确保URL有效
|
const currentSound = audioService.getCurrentSound();
|
||||||
if (music.source === 'bilibili' && music.bilibiliData) {
|
if (currentSound) {
|
||||||
try {
|
console.log('主动停止并卸载当前音频实例');
|
||||||
console.log('处理B站视频,检查URL有效性');
|
currentSound.stop();
|
||||||
// 清除之前的URL,强制重新获取
|
currentSound.unload();
|
||||||
music.playMusicUrl = undefined;
|
|
||||||
|
|
||||||
// 重新获取B站视频URL
|
|
||||||
if (music.bilibiliData.bvid && music.bilibiliData.cid) {
|
|
||||||
music.playMusicUrl = await getBilibiliAudioUrl(
|
|
||||||
music.bilibiliData.bvid,
|
|
||||||
music.bilibiliData.cid
|
|
||||||
);
|
|
||||||
console.log('获取B站URL成功:', music.playMusicUrl);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取B站音频URL失败:', error);
|
|
||||||
message.error(i18n.global.t('player.playFailed'));
|
|
||||||
return false; // 返回失败状态
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 先切换歌曲数据,更新播放状态
|
||||||
|
// 加载歌词
|
||||||
|
await loadLrcAsync(music);
|
||||||
|
const originalMusic = { ...music };
|
||||||
|
// 获取背景色
|
||||||
|
const { backgroundColor, primaryColor } =
|
||||||
|
music.backgroundColor && music.primaryColor
|
||||||
|
? music
|
||||||
|
: await getImageLinearBackground(getImgUrl(music?.picUrl, '30y30'));
|
||||||
|
music.backgroundColor = backgroundColor;
|
||||||
|
music.primaryColor = primaryColor;
|
||||||
|
music.playLoading = true; // 设置加载状态
|
||||||
|
playMusic.value = music;
|
||||||
|
|
||||||
|
// 更新播放相关状态
|
||||||
|
play.value = isPlay;
|
||||||
|
|
||||||
|
// 更新标题
|
||||||
|
let title = music.name;
|
||||||
|
if (music.source === 'netease' && music?.song?.artists) {
|
||||||
|
title += ` - ${music.song.artists.reduce(
|
||||||
|
(prev: string, curr: any) => `${prev}${curr.name}/`,
|
||||||
|
''
|
||||||
|
)}`;
|
||||||
|
} else if (music.source === 'bilibili' && music?.song?.ar?.[0]) {
|
||||||
|
title += ` - ${music.song.ar[0].name}`;
|
||||||
|
}
|
||||||
|
document.title = 'AlgerMusic - ' + title;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updatedPlayMusic = await getSongDetail(music);
|
|
||||||
playMusic.value = updatedPlayMusic;
|
// 添加到历史记录
|
||||||
playMusicUrl.value = updatedPlayMusic.playMusicUrl as string;
|
musicHistory.addMusic(music);
|
||||||
|
|
||||||
play.value = isPlay;
|
// 查找歌曲在播放列表中的索引
|
||||||
|
|
||||||
localStorage.setItem('currentPlayMusic', JSON.stringify(playMusic.value));
|
|
||||||
localStorage.setItem('currentPlayMusicUrl', playMusicUrl.value);
|
|
||||||
localStorage.setItem('isPlaying', play.value.toString());
|
|
||||||
|
|
||||||
let title = updatedPlayMusic.name;
|
|
||||||
|
|
||||||
if (updatedPlayMusic.source === 'netease' && updatedPlayMusic?.song?.artists) {
|
|
||||||
title += ` - ${updatedPlayMusic.song.artists.reduce(
|
|
||||||
(prev: string, curr: any) => `${prev}${curr.name}/`,
|
|
||||||
''
|
|
||||||
)}`;
|
|
||||||
} else if (updatedPlayMusic.source === 'bilibili' && updatedPlayMusic?.song?.ar?.[0]) {
|
|
||||||
title += ` - ${updatedPlayMusic.song.ar[0].name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.title = title;
|
|
||||||
|
|
||||||
loadLrcAsync(playMusic.value);
|
|
||||||
|
|
||||||
musicHistory.addMusic(playMusic.value);
|
|
||||||
|
|
||||||
// 找到歌曲在播放列表中的索引,如果是通过 nextPlay/prevPlay 调用的,不会更新 playListIndex
|
|
||||||
const songIndex = playList.value.findIndex(
|
const songIndex = playList.value.findIndex(
|
||||||
(item: SongResult) => item.id === music.id && item.source === music.source
|
(item: SongResult) => item.id === music.id && item.source === music.source
|
||||||
);
|
);
|
||||||
|
|
||||||
// 只有在 songIndex 有效,并且与当前 playListIndex 不同时才更新
|
// 只有在 songIndex 有效,并且与当前 playListIndex 不同时才更新
|
||||||
// 这样可以避免与 nextPlay/prevPlay 中的索引更新冲突
|
|
||||||
if (songIndex !== -1 && songIndex !== playListIndex.value) {
|
if (songIndex !== -1 && songIndex !== playListIndex.value) {
|
||||||
console.log('歌曲索引不匹配,更新为:', songIndex);
|
console.log('歌曲索引不匹配,更新为:', songIndex);
|
||||||
playListIndex.value = songIndex;
|
playListIndex.value = songIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取歌曲详情,包括URL
|
||||||
|
const updatedPlayMusic = await getSongDetail(originalMusic);
|
||||||
|
playMusic.value = updatedPlayMusic;
|
||||||
|
playMusicUrl.value = updatedPlayMusic.playMusicUrl as string;
|
||||||
|
|
||||||
|
// 保存到本地存储
|
||||||
|
localStorage.setItem('currentPlayMusic', JSON.stringify(playMusic.value));
|
||||||
|
localStorage.setItem('currentPlayMusicUrl', playMusicUrl.value);
|
||||||
|
localStorage.setItem('isPlaying', play.value.toString());
|
||||||
|
|
||||||
// 无论如何都预加载更多歌曲
|
// 无论如何都预加载更多歌曲
|
||||||
if (songIndex !== -1) {
|
if (songIndex !== -1) {
|
||||||
fetchSongs(playList.value, songIndex + 1, songIndex + 3);
|
setTimeout(() => {
|
||||||
|
fetchSongs(playList.value, songIndex + 1, songIndex + 2);
|
||||||
|
}, 3000);
|
||||||
} else {
|
} else {
|
||||||
console.warn('当前歌曲未在播放列表中找到');
|
console.warn('当前歌曲未在播放列表中找到');
|
||||||
}
|
}
|
||||||
@@ -461,7 +477,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
// 使用标记防止循环调用
|
// 使用标记防止循环调用
|
||||||
let playInProgress = false;
|
let playInProgress = false;
|
||||||
|
|
||||||
// 直接调用 playAudio 方法播放音频,不需要依赖外部监听
|
// 直接调用 playAudio 方法播放音频
|
||||||
try {
|
try {
|
||||||
if (playInProgress) {
|
if (playInProgress) {
|
||||||
console.warn('播放操作正在进行中,避免重复调用');
|
console.warn('播放操作正在进行中,避免重复调用');
|
||||||
@@ -469,8 +485,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playInProgress = true;
|
playInProgress = true;
|
||||||
|
|
||||||
// 因为调用 playAudio 前我们已经设置了 play.value,所以不需要额外传递 shouldPlay 参数
|
|
||||||
const result = await playAudio();
|
const result = await playAudio();
|
||||||
|
|
||||||
playInProgress = false;
|
playInProgress = false;
|
||||||
@@ -483,6 +497,10 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('处理播放音乐失败:', error);
|
console.error('处理播放音乐失败:', error);
|
||||||
message.error(i18n.global.t('player.playFailed'));
|
message.error(i18n.global.t('player.playFailed'));
|
||||||
|
// 出现错误时,更新加载状态
|
||||||
|
if (playMusic.value) {
|
||||||
|
playMusic.value.playLoading = false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -717,20 +735,13 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 修改nextPlay方法,改进播放失败的处理逻辑
|
// 修改nextPlay方法,改进播放逻辑
|
||||||
const nextPlay = async () => {
|
const nextPlay = async () => {
|
||||||
// 静态标志,防止多次调用造成递归
|
|
||||||
if ((nextPlay as any).isRunning) {
|
|
||||||
console.log('下一首播放正在执行中,忽略重复调用');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
(nextPlay as any).isRunning = true;
|
|
||||||
|
|
||||||
if (playList.value.length === 0) {
|
if (playList.value.length === 0) {
|
||||||
play.value = true;
|
play.value = true;
|
||||||
(nextPlay as any).isRunning = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,7 +750,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
sleepTimer.value.type === SleepTimerType.PLAYLIST_END) {
|
sleepTimer.value.type === SleepTimerType.PLAYLIST_END) {
|
||||||
// 已是最后一首且为顺序播放模式,触发停止
|
// 已是最后一首且为顺序播放模式,触发停止
|
||||||
stopPlayback();
|
stopPlayback();
|
||||||
(nextPlay as any).isRunning = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,29 +771,23 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
nowPlayListIndex = (playListIndex.value + 1) % playList.value.length;
|
nowPlayListIndex = (playListIndex.value + 1) % playList.value.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取下一首歌曲
|
||||||
|
let nextSong = { ...playList.value[nowPlayListIndex] };
|
||||||
|
|
||||||
// 记录尝试播放过的索引,防止无限循环
|
// 记录尝试播放过的索引,防止无限循环
|
||||||
const attemptedIndices = new Set<number>();
|
const attemptedIndices = new Set<number>();
|
||||||
attemptedIndices.add(nowPlayListIndex);
|
attemptedIndices.add(nowPlayListIndex);
|
||||||
|
|
||||||
// 更新当前播放索引
|
// 先更新当前播放索引
|
||||||
playListIndex.value = nowPlayListIndex;
|
playListIndex.value = nowPlayListIndex;
|
||||||
|
|
||||||
// 获取下一首歌曲
|
// 尝试播放
|
||||||
let nextSong = playList.value[nowPlayListIndex];
|
|
||||||
let success = false;
|
let success = false;
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = Math.min(3, playList.value.length);
|
const maxRetries = Math.min(3, playList.value.length);
|
||||||
|
|
||||||
// 尝试播放,最多尝试maxRetries次
|
// 尝试播放,最多尝试maxRetries次
|
||||||
while (!success && retryCount < maxRetries) {
|
while (!success && retryCount < maxRetries) {
|
||||||
// 如果是B站视频,确保重新获取URL
|
|
||||||
if (nextSong.source === 'bilibili' && nextSong.bilibiliData) {
|
|
||||||
// 清除之前的URL,确保重新获取
|
|
||||||
nextSong.playMusicUrl = undefined;
|
|
||||||
console.log(`尝试播放B站视频 (尝试 ${retryCount + 1}/${maxRetries})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试播放,并明确传递应该播放的状态
|
|
||||||
success = await handlePlayMusic(nextSong, shouldPlayNext);
|
success = await handlePlayMusic(nextSong, shouldPlayNext);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -798,7 +802,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
|
|
||||||
if (newPlayList.length > 0) {
|
if (newPlayList.length > 0) {
|
||||||
// 更新播放列表,但保持当前索引不变
|
// 更新播放列表,但保持当前索引不变
|
||||||
// 这是关键修改,防止索引重置到-1
|
|
||||||
const keepCurrentIndexPosition = true;
|
const keepCurrentIndexPosition = true;
|
||||||
setPlayList(newPlayList, keepCurrentIndexPosition);
|
setPlayList(newPlayList, keepCurrentIndexPosition);
|
||||||
|
|
||||||
@@ -829,7 +832,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
attemptedIndices.add(nowPlayListIndex);
|
attemptedIndices.add(nowPlayListIndex);
|
||||||
|
|
||||||
if (newPlayList[nowPlayListIndex]) {
|
if (newPlayList[nowPlayListIndex]) {
|
||||||
nextSong = newPlayList[nowPlayListIndex];
|
nextSong = { ...newPlayList[nowPlayListIndex] };
|
||||||
retryCount = 0; // 重置重试计数器,为新歌曲准备
|
retryCount = 0; // 重置重试计数器,为新歌曲准备
|
||||||
} else {
|
} else {
|
||||||
// 处理索引无效的情况
|
// 处理索引无效的情况
|
||||||
@@ -857,25 +860,17 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('切换下一首出错:', error);
|
console.error('切换下一首出错:', error);
|
||||||
} finally {
|
|
||||||
(nextPlay as any).isRunning = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 修改 prevPlay 方法,使用与 nextPlay 相似的逻辑改进
|
// 修改 prevPlay 方法,使用与 nextPlay 相似的逻辑改进
|
||||||
const prevPlay = async () => {
|
const prevPlay = async () => {
|
||||||
// 静态标志,防止多次调用造成递归
|
|
||||||
if ((prevPlay as any).isRunning) {
|
|
||||||
console.log('上一首播放正在执行中,忽略重复调用');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
(prevPlay as any).isRunning = true;
|
|
||||||
|
|
||||||
if (playList.value.length === 0) {
|
if (playList.value.length === 0) {
|
||||||
play.value = true;
|
play.value = true;
|
||||||
(prevPlay as any).isRunning = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,22 +879,17 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
const nowPlayListIndex =
|
const nowPlayListIndex =
|
||||||
(playListIndex.value - 1 + playList.value.length) % playList.value.length;
|
(playListIndex.value - 1 + playList.value.length) % playList.value.length;
|
||||||
|
|
||||||
|
// 获取上一首歌曲
|
||||||
|
const prevSong = { ...playList.value[nowPlayListIndex] };
|
||||||
|
|
||||||
// 重要:首先更新当前播放索引
|
// 重要:首先更新当前播放索引
|
||||||
playListIndex.value = nowPlayListIndex;
|
playListIndex.value = nowPlayListIndex;
|
||||||
|
|
||||||
// 获取上一首歌曲
|
// 尝试播放
|
||||||
const prevSong = playList.value[nowPlayListIndex];
|
|
||||||
let success = false;
|
let success = false;
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = 2;
|
const maxRetries = 2;
|
||||||
|
|
||||||
// 如果是B站视频,确保重新获取URL
|
|
||||||
if (prevSong.source === 'bilibili' && prevSong.bilibiliData) {
|
|
||||||
// 清除之前的URL,确保重新获取
|
|
||||||
prevSong.playMusicUrl = undefined;
|
|
||||||
console.log('上一首是B站视频,已清除URL强制重新获取');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试播放,最多尝试maxRetries次
|
// 尝试播放,最多尝试maxRetries次
|
||||||
while (!success && retryCount < maxRetries) {
|
while (!success && retryCount < maxRetries) {
|
||||||
success = await handlePlayMusic(prevSong);
|
success = await handlePlayMusic(prevSong);
|
||||||
@@ -932,7 +922,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
|
|
||||||
// 延迟一点时间再尝试,避免可能的无限循环
|
// 延迟一点时间再尝试,避免可能的无限循环
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(prevPlay as any).isRunning = false;
|
|
||||||
prevPlay(); // 递归调用,尝试再上一首
|
prevPlay(); // 递归调用,尝试再上一首
|
||||||
}, 300);
|
}, 300);
|
||||||
return;
|
return;
|
||||||
@@ -945,9 +934,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (!success) {
|
||||||
await fetchSongs(playList.value, playListIndex.value - 3, nowPlayListIndex);
|
|
||||||
} else {
|
|
||||||
console.error('所有尝试都失败,无法播放上一首歌曲');
|
console.error('所有尝试都失败,无法播放上一首歌曲');
|
||||||
// 如果尝试了所有可能的歌曲仍然失败,恢复到原始索引
|
// 如果尝试了所有可能的歌曲仍然失败,恢复到原始索引
|
||||||
playListIndex.value = currentIndex;
|
playListIndex.value = currentIndex;
|
||||||
@@ -956,8 +943,6 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('切换上一首出错:', error);
|
console.error('切换上一首出错:', error);
|
||||||
} finally {
|
|
||||||
(prevPlay as any).isRunning = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user