Files
librodrome/app/composables/useAudioPlayer.ts
2026-02-20 12:55:10 +01:00

154 lines
3.0 KiB
TypeScript

import type { Song } from '~/types/song'
let audio: HTMLAudioElement | null = null
let animationFrameId: number | null = null
export function useAudioPlayer() {
const store = usePlayerStore()
function getAudio(): HTMLAudioElement {
if (!audio) {
audio = new Audio()
audio.preload = 'metadata'
audio.volume = store.volume
audio.addEventListener('loadedmetadata', () => {
store.setDuration(audio!.duration)
})
audio.addEventListener('ended', () => {
const next = store.nextSong()
if (next) {
loadAndPlay(next)
}
})
audio.addEventListener('error', (e) => {
console.error('Audio error:', e)
store.pause()
})
}
return audio
}
function startTimeUpdate() {
if (animationFrameId) return
const update = () => {
if (audio && !audio.paused) {
store.setCurrentTime(audio.currentTime)
}
animationFrameId = requestAnimationFrame(update)
}
animationFrameId = requestAnimationFrame(update)
}
function stopTimeUpdate() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
}
async function loadAndPlay(song: Song) {
const el = getAudio()
store.setSong(song)
// Try OGG first, fall back to MP3
const oggPath = song.file.replace(/\.mp3$/, '.ogg')
const canOgg = el.canPlayType('audio/ogg; codecs=vorbis')
el.src = canOgg ? oggPath : song.file
el.volume = store.volume
try {
await el.play()
store.play()
startTimeUpdate()
}
catch {
// If OGG failed, try MP3
if (el.src !== song.file) {
el.src = song.file
try {
await el.play()
store.play()
startTimeUpdate()
}
catch (err) {
console.error('Playback failed:', err)
}
}
}
}
function pause() {
getAudio().pause()
store.pause()
stopTimeUpdate()
}
function resume() {
const el = getAudio()
if (el.src) {
el.play()
store.play()
startTimeUpdate()
}
}
function togglePlayPause() {
if (store.isPlaying) {
pause()
}
else {
resume()
}
}
function seek(time: number) {
const el = getAudio()
el.currentTime = time
store.setCurrentTime(time)
}
function setVolume(vol: number) {
store.setVolume(vol)
getAudio().volume = store.volume
}
function playNext() {
const song = store.nextSong()
if (song) loadAndPlay(song)
}
function playPrev() {
const song = store.prevSong()
if (song) {
if (song === store.currentSong && store.currentTime <= 3) {
// prevSong already reset time
seek(0)
}
else {
loadAndPlay(song)
}
}
}
// Watch volume changes from store
watch(() => store.volume, (vol) => {
if (audio) audio.volume = vol
})
return {
loadAndPlay,
pause,
resume,
togglePlayPause,
seek,
setVolume,
playNext,
playPrev,
getAudio,
}
}