Files
BYW-VoiceHub/VoiceHub-main/app/composables/useLyricManager.ts
T
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

227 lines
7.2 KiB
TypeScript

import { ref, watch } from 'vue'
import { useAudioPlayer } from './useAudioPlayer'
import { useMusicSources } from './useMusicSources'
import { useLyricSettings } from './useLyricSettings'
import {
parseSmartLrc,
alignLyrics,
cleanTTMLTranslations,
parseQRCLyric,
type LrcFormat
} from '~/utils/lyric/lyricParser'
import { formatLyric } from '~/utils/lyric/lyricFormat'
import type { LyricLine } from '@applemusic-like-lyrics/lyric'
import { parseTTML, parseYrc } from '@applemusic-like-lyrics/lyric'
export const useLyricManager = () => {
const audioPlayer = useAudioPlayer()
const { getLyrics } = useMusicSources()
const settings = useLyricSettings()
// 状态
const lyrics = ref<LyricLine[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const currentTrackId = ref<string | null>(null)
const hasTranslation = ref(false)
const hasRoma = ref(false)
const lyricFormat = ref<LrcFormat | 'ttml' | 'qrc'>('line')
/**
* 清空歌词
*/
const clearLyrics = () => {
lyrics.value = []
error.value = null
hasTranslation.value = false
hasRoma.value = false
currentTrackId.value = null
}
/**
* 获取并解析歌词
*/
const fetchLyric = async (track: any) => {
if (!track || !track.id) return
// 如果已经在加载同一首歌,跳过
if (loading.value && currentTrackId.value === track.id?.toString()) return
// 如果是同一首歌且已有歌词,不重新加载
if (currentTrackId.value === track.id?.toString() && lyrics.value.length > 0) return
currentTrackId.value = track.id?.toString()
loading.value = true
try {
// 确定平台
const platform = track.musicPlatform || 'netease'
const musicId = track.musicId || track.id
console.log(`[LyricManager] 开始获取歌词: ${track.title} (${platform})`)
// 1. 获取歌词数据
const result = await getLyrics(platform, musicId)
if (!result.success || !result.data) {
throw new Error(result.error || '未找到歌词')
}
const { lrc, trans, yrc, ttml } = result.data
console.log(
`[LyricManager] 获取结果: LRC=${!!lrc}, TRANS=${!!trans}, YRC=${!!yrc}, TTML=${!!ttml}`
)
// 2. 解析歌词
let parsedLyrics: LyricLine[] = []
let format: LrcFormat | 'ttml' | 'qrc' = 'line'
// 优先处理 TTML (如果启用且存在)
if (settings.enableOnlineTTMLLyric.value && ttml) {
try {
const cleaned = cleanTTMLTranslations(ttml)
const parsed = parseTTML(cleaned)
const lines = (parsed as any)?.lines || []
if (lines.length > 0) {
parsedLyrics = lines
format = 'ttml'
console.log('[LyricManager] 使用 TTML 格式')
}
} catch (e) {
console.warn('[LyricManager] TTML解析失败,回退到其他格式', e)
}
}
// 如果没有 TTML 或解析失败,尝试 YRC
if (parsedLyrics.length === 0 && yrc) {
// 检测 QRC (QQ音乐 XML格式)
if (yrc.trim().startsWith('<') || yrc.includes('LyricContent="')) {
try {
const qrcLines = parseQRCLyric(yrc, trans, undefined) // QQ 音乐 QRC 可能带有翻译
if (qrcLines.length > 0) {
parsedLyrics = qrcLines
format = 'qrc'
console.log(`[LyricManager] 使用 QRC 格式`)
}
} catch (e) {
console.warn('[LyricManager] QRC解析失败', e)
}
}
if (parsedLyrics.length === 0) {
try {
// 尝试使用库的 parseYrc (Netease JSON)
const lines = parseYrc(yrc)
if (lines && lines.length > 0) {
// 检查解析质量:如果所有行的 words 都为空,说明解析失败
const validLines = lines.filter((l) => l.words && l.words.length > 0)
if (validLines.length > 0) {
parsedLyrics = lines
format = 'word-by-word'
console.log(`[LyricManager] 使用 YRC 格式 (Library)`)
} else {
// 解析结果无效,回退
console.warn('[LyricManager] YRC (Library) 解析结果无效(无words),尝试回退')
parsedLyrics = [] // 触发后续逻辑
}
} else {
const { lines, format: detectedFormat } = parseSmartLrc(yrc)
if (lines.length > 0) {
parsedLyrics = lines
format = detectedFormat
console.log(`[LyricManager] 使用 YRC 格式 (Smart: ${detectedFormat})`)
}
}
} catch (e) {
// 回退到 parseSmartLrc
const { lines, format: detectedFormat } = parseSmartLrc(yrc)
if (lines.length > 0) {
parsedLyrics = lines
format = detectedFormat
console.log(`[LyricManager] 使用 YRC 格式 (Fallback: ${detectedFormat})`)
}
}
}
}
// 如果还是没有,使用 LRC
if (parsedLyrics.length === 0 && lrc) {
const { lines, format: detectedFormat } = parseSmartLrc(lrc)
parsedLyrics = lines
format = detectedFormat
console.log(`[LyricManager] 使用 LRC 格式 (${detectedFormat})`)
}
// 3. 对齐翻译和罗马音
if (parsedLyrics.length > 0) {
// 如果有翻译
if (trans) {
const { lines: transLines } = parseSmartLrc(trans)
if (transLines.length > 0) {
parsedLyrics = alignLyrics(parsedLyrics, transLines, 'translatedLyric')
hasTranslation.value = true
console.log('[LyricManager] 已对齐翻译')
}
}
} else {
// 没有任何歌词
error.value = '暂无歌词'
}
// 4. 格式化 (应用配置,如括号替换)
if (parsedLyrics.length > 0) {
// 检查当前播放的歌曲 ID 是否仍然是请求的歌曲 ID
if (currentTrackId.value !== track.id?.toString()) {
console.log('[LyricManager] 歌曲已切换,丢弃过期的歌词响应')
return
}
// 提取元数据用于清洗
const metadata = {
title: track.title,
artists: track.artist ? [track.artist] : undefined
}
lyrics.value = formatLyric(parsedLyrics, settings, metadata)
lyricFormat.value = format
error.value = null
// 调试
console.log('[LyricManager] 解析完成,首行:', lyrics.value[0])
console.log('[LyricManager] 解析完成,总行数:', lyrics.value.length)
}
} catch (e: any) {
console.error('[LyricManager] 获取歌词失败:', e)
error.value = e.message || '获取歌词失败'
lyrics.value = []
} finally {
loading.value = false
}
}
// 监听当前播放歌曲变化
watch(
() => audioPlayer.getCurrentSong().value,
(newTrack) => {
if (newTrack) {
fetchLyric(newTrack)
} else {
clearLyrics()
}
},
{ immediate: true }
)
return {
lyrics,
loading,
error,
currentTrackId,
hasTranslation,
hasRoma,
lyricFormat,
fetchLyric,
clearLyrics
}
}