feat: align with upstream 1.5.9.9
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user