import { ref, computed, onUnmounted } from 'vue' /** * 背景音乐控制 composable(支持多曲目) * - tracks: [{ url, name }] 播放列表 * - activeUrl: 当前选中的曲目地址 * - 处理浏览器自动播放限制:首次用户交互后再尝试播放 */ export function useAudio() { const isPlaying = ref(false) const isReady = ref(false) const tracks = ref([]) // [{ url, name }] const activeUrl = ref('') // 当前选中曲目 url let audio = null let unlockHandler = null const currentName = computed(() => { const t = tracks.value.find((t) => t.url === activeUrl.value) return t?.name || '背景音乐' }) const ensureAudio = () => { if (audio) return audio audio = new Audio() audio.loop = true audio.volume = 0.5 audio.preload = 'auto' audio.addEventListener('canplay', () => (isReady.value = true)) audio.addEventListener('play', () => (isPlaying.value = true)) audio.addEventListener('pause', () => (isPlaying.value = false)) return audio } const playUrl = async (url) => { if (!url) return const a = ensureAudio() if (a.src !== url) a.src = url activeUrl.value = url try { await a.play() isPlaying.value = true } catch (e) { // 被浏览器拦截,等待用户手势 isPlaying.value = false } } // 设置播放列表与默认选中项 const setTracks = (list, active) => { tracks.value = Array.isArray(list) ? list.slice().filter((t) => t && t.url) : [] const first = tracks.value[0]?.url || '' const chosen = active && tracks.value.some((t) => t.url === active) ? active : first activeUrl.value = chosen if (audio && isPlaying.value && chosen) playUrl(chosen) } // 切换当前播放曲目(若正在播放则立即切换) const setActive = (url) => { if (!tracks.value.some((t) => t.url === url)) return if (isPlaying.value) playUrl(url) else activeUrl.value = url } const play = () => playUrl(activeUrl.value) const pause = () => { if (audio) audio.pause() isPlaying.value = false } const toggle = () => { if (isPlaying.value) pause() else play() } // 首次交互解锁自动播放 const armAutoplay = () => { const handler = () => { play() removeUnlock() } unlockHandler = handler window.addEventListener('pointerdown', handler, { once: true }) window.addEventListener('keydown', handler, { once: true }) } const removeUnlock = () => { if (!unlockHandler) return window.removeEventListener('pointerdown', unlockHandler) window.removeEventListener('keydown', unlockHandler) unlockHandler = null } onUnmounted(() => { pause() removeUnlock() if (audio) { audio.src = '' audio = null } }) return { isPlaying, isReady, tracks, activeUrl, currentName, setTracks, setActive, play, pause, toggle, armAutoplay } }