103 lines
2.9 KiB
JavaScript
103 lines
2.9 KiB
JavaScript
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 }
|
||
}
|