diff --git a/src/composables/useGlobalAudioPlayer.ts b/src/composables/useGlobalAudioPlayer.ts index 7f04225..0abcdb9 100644 --- a/src/composables/useGlobalAudioPlayer.ts +++ b/src/composables/useGlobalAudioPlayer.ts @@ -41,6 +41,10 @@ const pendingLanguage = ref('') const activeHostId = ref('') const lastClosedSource = ref(null) const closeVersion = ref(0) +const playbackRate = ref(1) +const muted = ref(false) + +const PLAYBACK_RATES = [1, 1.25, 1.5, 2] as const let audioElement: HTMLAudioElement | null = null let retryOnError: GlobalAudioRetryHandler | null = null @@ -74,11 +78,17 @@ const syncAudioDuration = () => { : currentAudio.value?.duration || 0 } +const applyAudioPreferences = (audio: HTMLAudioElement) => { + audio.playbackRate = playbackRate.value + audio.muted = muted.value +} + const ensureAudioElement = () => { if (audioElement || typeof window === 'undefined' || !window.Audio) return audioElement const audio = new window.Audio() audio.preload = 'metadata' + applyAudioPreferences(audio) audio.addEventListener('loadedmetadata', syncAudioDuration) audio.addEventListener('play', () => { playing.value = true @@ -124,6 +134,11 @@ const resetState = () => { duration.value = 0 displayMode.value = 'mini' pendingLanguage.value = '' + playbackRate.value = 1 + muted.value = false + if (audioElement) { + applyAudioPreferences(audioElement) + } retryOnError = null retrying = false retryUsed = false @@ -227,6 +242,7 @@ const play = async (audio: AudioItem, options: GlobalAudioPlayOptions = {}) => { element.src = audio.audioUrl element.load() } + applyAudioPreferences(element) try { await element.play() @@ -448,6 +464,22 @@ const seekToPercent = (percent: number) => { currentTime.value = targetTime } +const cyclePlaybackRate = () => { + const currentIndex = PLAYBACK_RATES.indexOf(playbackRate.value as typeof PLAYBACK_RATES[number]) + const nextIndex = currentIndex >= 0 ? (currentIndex + 1) % PLAYBACK_RATES.length : 0 + playbackRate.value = PLAYBACK_RATES[nextIndex] + if (audioElement) { + audioElement.playbackRate = playbackRate.value + } +} + +const toggleMuted = () => { + muted.value = !muted.value + if (audioElement) { + audioElement.muted = muted.value + } +} + const registerHost = () => { hostSequence += 1 const hostId = `global-audio-host-${hostSequence}` @@ -490,6 +522,8 @@ export const useGlobalAudioPlayer = () => ({ activeHostId, lastClosedSource, closeVersion, + playbackRate, + muted, hasAudio: computed(() => Boolean(currentAudio.value)), play, pause, @@ -502,6 +536,8 @@ export const useGlobalAudioPlayer = () => ({ handleEnded, handleError, seekToPercent, + cyclePlaybackRate, + toggleMuted, registerHost, activateHost, unregisterHost, diff --git a/src/pages/exhibit/detail.vue b/src/pages/exhibit/detail.vue index 3443440..f732202 100644 --- a/src/pages/exhibit/detail.vue +++ b/src/pages/exhibit/detail.vue @@ -104,6 +104,30 @@ {{ audioDockTitle }} {{ audioDockSubtitle }} + +