Files
BYW-VoiceHub/app/composables/useSongPlayer.ts
T

95 lines
3.4 KiB
TypeScript

import { getMusicUrl } from '~/utils/musicUrl'
import { isBilibiliSong } from '~/utils/bilibiliSource'
import { useAudioPlayer, type PlayableSong } from './useAudioPlayer'
import { useToast } from './useToast'
import { useLocale } from '~/utils/locale'
import type { Song } from '~/types'
export const useSongPlayer = () => {
const audioPlayer = useAudioPlayer()
const { showToast } = useToast()
const { audioPlayer: audioPlayerLocale } = useLocale()
// 判断歌曲是否可入队(有自定义链接、B站视频或平台ID)
const isPlayableSong = (s: Song | PlayableSong) => {
return Boolean(
(s.playUrl && s.playUrl.trim()) ||
isBilibiliSong(s) ||
(s.musicPlatform && s.musicId)
)
}
// 根据队列来源构建播放队列,返回队列与当前歌曲索引;来源为空时退化为单曲队列
const buildQueue = (song: PlayableSong, queueSource?: (Song | PlayableSong)[]) => {
const queue = (queueSource || []).filter(isPlayableSong) as PlayableSong[]
const index = queue.findIndex((s) => String(s.id) === String(song.id))
if (index === -1) {
return { queue: [song, ...queue], index: 0 }
}
return { queue, index }
}
const playSong = async (song: Song | PlayableSong, queueSource?: (Song | PlayableSong)[]) => {
// 如果是当前选中的歌曲
if (audioPlayer.isCurrentSong(song.id)) {
// 如果正在播放,则暂停
if (audioPlayer.getPlayingStatus().value) {
audioPlayer.pauseSong()
return
}
// 如果是当前歌曲但暂停了,则恢复播放
const currentGlobalSong = audioPlayer.getCurrentSong().value
if (currentGlobalSong && (currentGlobalSong.musicUrl || isBilibiliSong(currentGlobalSong))) {
// 恢复播放时同步刷新播放队列
const { queue, index } = buildQueue(currentGlobalSong, queueSource)
audioPlayer.playSong(currentGlobalSong, queue, index)
return
}
}
try {
let url = null
// 如果有手动填入的 playUrl,优先使用它(统一处理所有平台)
if (song.playUrl && song.playUrl.trim()) {
url = song.playUrl.trim()
} else if (!isBilibiliSong(song)) {
// 如果不是哔哩哔哩歌曲且没有手动 playUrl,通过 API 获取 URL
url = await getMusicUrl(song.musicPlatform, song.musicId, song.playUrl, {
musicInfo: {
name: song.title,
artist: song.artist,
album: song.album || undefined
}
})
}
// 如果是哔哩哔哩歌曲且没有手动 playUrl,url 保持为 null,播放器会处理
const playableSong: PlayableSong = {
...song,
musicUrl: url
}
const { queue, index } = buildQueue(playableSong, queueSource)
audioPlayer.playSong(playableSong, queue, index)
} catch (error: any) {
console.error('播放失败:', error)
const errorMessage = error instanceof Error ? error.message : String(error)
showToast(audioPlayerLocale.value.playFailed(errorMessage), 'error')
// 即使获取 URL 失败,也应该调用 playSong 以触发播放器的错误处理和弹窗逻辑
const playableSong: PlayableSong = {
...song,
musicUrl: null
}
const { queue, index } = buildQueue(playableSong, queueSource)
audioPlayer.playSong(playableSong, queue, index)
}
}
return {
playSong
}
}