import { ref, onUnmounted } from 'vue' const BGM_BASE = '/training/static/bgm' export type BgmMode = 'none' | 'training' | 'resting' export interface UseTrainingBgmOptions { trainingSrc?: string restingSrc?: string volume?: number fadeMs?: number } export function useTrainingBgm(options: UseTrainingBgmOptions = {}) { const { trainingSrc = `${BGM_BASE}/train-light.mp3`, restingSrc = `${BGM_BASE}/rest-meditation.mp3`, volume = 0.6, fadeMs = 600, } = options const mode = ref('none') const enabled = ref(true) let trainCtx: UniApp.InnerAudioContext | null = null let restCtx: UniApp.InnerAudioContext | null = null let fadeTimers: ReturnType[] = [] const ensureCtx = () => { if (!trainCtx) { trainCtx = uni.createInnerAudioContext() trainCtx.src = trainingSrc trainCtx.loop = true trainCtx.obeyMuteSwitch = false trainCtx.volume = 0 } if (!restCtx) { restCtx = uni.createInnerAudioContext() restCtx.src = restingSrc restCtx.loop = true restCtx.obeyMuteSwitch = false restCtx.volume = 0 } } const fade = ( ctx: UniApp.InnerAudioContext, from: number, to: number, duration = fadeMs, ) => { const steps = 16 const stepMs = Math.max(16, duration / steps) let i = 0 const t = setInterval(() => { i++ const v = from + (to - from) * (i / steps) ctx.volume = Math.max(0, Math.min(1, v)) if (i >= steps) { clearInterval(t) fadeTimers = fadeTimers.filter((x) => x !== t) } }, stepMs) fadeTimers.push(t) } const switchTo = (target: BgmMode) => { if (!enabled.value) return if (mode.value === target) return ensureCtx() if (target === 'training' && trainCtx && restCtx) { fade(restCtx, restCtx.volume, 0) setTimeout(() => restCtx?.pause(), fadeMs) trainCtx.play() fade(trainCtx, 0, volume) } else if (target === 'resting' && trainCtx && restCtx) { fade(trainCtx, trainCtx.volume, 0) setTimeout(() => trainCtx?.pause(), fadeMs) restCtx.play() fade(restCtx, 0, volume) } else if (target === 'none') { if (trainCtx) { fade(trainCtx, trainCtx.volume, 0) setTimeout(() => trainCtx?.pause(), fadeMs) } if (restCtx) { fade(restCtx, restCtx.volume, 0) setTimeout(() => restCtx?.pause(), fadeMs) } } mode.value = target } const setEnabled = (v: boolean) => { enabled.value = v if (!v) switchTo('none') } onUnmounted(() => { fadeTimers.forEach((t) => clearInterval(t)) fadeTimers = [] trainCtx?.destroy?.() restCtx?.destroy?.() trainCtx = null restCtx = null }) return { mode, enabled, switchTo, setEnabled, } }