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

243 lines
7.2 KiB
TypeScript

import { computed, readonly, ref, watch } from 'vue'
import { useLocale } from '~/utils/locale'
import { getLoginStatus } from '~/utils/neteaseApi'
// 音质配置
export const QUALITY_OPTIONS = {
netease: [
{ value: 2, key: 'standard' },
{ value: 4, key: 'neteaseHq' },
{ value: 5, key: 'neteaseSq' },
{ value: 6, key: 'neteaseHiRes' },
{ value: 9, key: 'neteaseMaster' }
],
tencent: [
{ value: 4, key: 'standard' },
{ value: 8, key: 'tencentHq' },
{ value: 10, key: 'tencentSq' },
{ value: 11, key: 'tencentHiRes' },
{ value: 14, key: 'tencentMaster' }
],
bilibili: [{ value: 1, key: 'bilibiliDefault' }],
migu: [
{ value: 1, key: 'standard' },
{ value: 2, key: 'miguHq' },
{ value: 3, key: 'miguSq' },
{ value: 4, key: 'miguZq24' },
]
}
// 默认音质设置
const DEFAULT_QUALITY = {
netease: 4, // HQ极高 (320k)
tencent: 8, // HQ高音质
bilibili: 1,
migu: 1 // 标准音质(咪咕匿名仅提供 128k)
}
// 全局音质状态,确保所有组件共享同一个状态
let globalAudioQuality: any = null
// 网易云登录状态,全局共享
const isNeteaseLoggedIn = ref(false)
let isLoginStatusInitialized = false
// 网易 VIP 标志(localStorage netease_vip):vipType 非 0 即 VIP,仅 VIP 登录态才优先官方播放源
const persistNeteaseVipFlag = (profile: any) => {
if (typeof window === 'undefined') return
try {
if (profile && typeof profile.vipType === 'number') {
localStorage.setItem('netease_vip', profile.vipType !== 0 ? '1' : '0')
} else {
localStorage.removeItem('netease_vip')
}
} catch (error) {
console.error('[audioQuality] Failed to persist netease_vip:', error)
}
}
const clearNeteaseVipFlag = () => {
if (typeof window === 'undefined') return
try {
localStorage.removeItem('netease_vip')
} catch {
// localStorage 不可用时忽略
}
}
// 每会话一次的 VIP 状态静默刷新守卫
let isNeteaseVipRefreshing = false
let isNeteaseVipRefreshed = false
// 静默刷新 VIP 状态:有 cookie 时调 /login/status 校验登录态并同步 VIP 标志
const refreshNeteaseVipStatus = async () => {
if (typeof window === 'undefined' || isNeteaseVipRefreshed || isNeteaseVipRefreshing) return
let cookie: string | null = null
try {
cookie = localStorage.getItem('netease_cookie')
} catch {
// localStorage 不可用时跳过刷新
}
if (!cookie) return
isNeteaseVipRefreshing = true
try {
const res = await getLoginStatus(cookie)
const dataObj = res.body?.data || res.body
if (dataObj && dataObj.account) {
isNeteaseLoggedIn.value = true
persistNeteaseVipFlag(dataObj.profile)
} else {
// 登录失效:仅清 VIP 标志,cookie 与组件本地状态由组件级校验负责清理
clearNeteaseVipFlag()
isNeteaseLoggedIn.value = false
}
} catch {
// 网络异常保留本地状态
} finally {
isNeteaseVipRefreshing = false
isNeteaseVipRefreshed = true
}
}
export function useAudioQuality() {
const { ui } = useLocale()
const locale = computed(() => ui.value?.audioQuality || {})
// 检查网易云登录状态
const checkNeteaseLoginStatus = () => {
if (typeof window === 'undefined') return
const cookie = localStorage.getItem('netease_cookie')
isNeteaseLoggedIn.value = !!cookie
}
// 从localStorage读取音质设置
const getStoredQuality = () => {
try {
const stored = localStorage.getItem('audioQuality')
return stored ? JSON.parse(stored) : DEFAULT_QUALITY
} catch {
return DEFAULT_QUALITY
}
}
// 使用全局状态,确保所有组件共享
if (!globalAudioQuality) {
globalAudioQuality = ref(getStoredQuality())
// 监听状态变化,自动保存到localStorage
watch(
globalAudioQuality,
(newValue) => {
try {
localStorage.setItem('audioQuality', JSON.stringify(newValue))
} catch (error) {
console.error('[audioQuality] Failed to save quality settings:', error)
}
},
{ deep: true }
)
}
// 初始化登录状态监听
if (!isLoginStatusInitialized && typeof window !== 'undefined') {
checkNeteaseLoginStatus()
refreshNeteaseVipStatus()
window.addEventListener('storage', (e) => {
if (e.key === 'netease_cookie') {
checkNeteaseLoginStatus()
// 其他标签页登录态变化时重新同步 VIP 标志
isNeteaseVipRefreshed = false
refreshNeteaseVipStatus()
}
})
isLoginStatusInitialized = true
}
const audioQuality = globalAudioQuality
// 保存音质设置到localStorage
const saveQuality = (platform: string, quality: number) => {
audioQuality.value[platform] = quality
// localStorage保存已经通过watch自动处理
}
// 获取指定平台的音质设置
const getQuality = (platform: string) => {
// 处理 netease-podcast 别名
if (platform === 'netease-podcast') {
platform = 'netease'
}
const stored = audioQuality.value[platform]
// 已保存的音质值不再存在于选项列表时(如咪咕音质收敛后残留旧值),回落默认值
const platformOptions =
(QUALITY_OPTIONS as Record<string, Array<{ value: number; key: string }>>)[platform] || []
const isValid = platformOptions.some((option) => option.value === stored)
if (isValid) {
return stored
}
return (DEFAULT_QUALITY as Record<string, number>)[platform]
}
// 获取指定平台的音质选项
const getQualityOptions = (platform: string) => {
// 处理 netease-podcast 别名
if (platform === 'netease-podcast') {
platform = 'netease'
}
return (QUALITY_OPTIONS[platform] || []).map((option) => ({
...option,
label: locale.value?.options?.[option.key]?.label || option.key,
description: locale.value?.options?.[option.key]?.description || '使用推荐音质设置'
}))
}
// 获取音质标签
const getQualityLabel = (platform: string, quality: number) => {
// 处理 netease-podcast 别名
if (platform === 'netease-podcast') {
platform = 'netease'
}
const options = getQualityOptions(platform)
const option = options.find((opt) => opt.value === quality)
return option ? option.label : locale.value.unknown
}
// 获取音质描述
const getQualityDescription = (platform: string, quality: number) => {
// 处理 netease-podcast 别名
if (platform === 'netease-podcast') {
platform = 'netease'
}
const options = getQualityOptions(platform)
const option = options.find((opt) => opt.value === quality)
return option ? option.description : ''
}
// 计算属性:当前音质设置的可读文本
const currentQualityText = computed(() => {
const netease = getQualityLabel('netease', getQuality('netease'))
const tencent = getQualityLabel('tencent', getQuality('tencent'))
return {
netease,
tencent
}
})
return {
audioQuality: readonly(audioQuality),
saveQuality,
getQuality,
getQualityOptions,
getQualityLabel,
getQualityDescription,
currentQualityText,
QUALITY_OPTIONS,
DEFAULT_QUALITY,
checkNeteaseLoginStatus,
isNeteaseLoggedIn: readonly(isNeteaseLoggedIn),
persistNeteaseVipFlag,
clearNeteaseVipFlag
}
}