Initial commit
This commit is contained in:
125
composables/useMediaProvider.ts
Normal file
125
composables/useMediaProvider.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { getSoundscapeByTitle } from '~/tracks.config'
|
||||
import { ensureAudio, useAudioStore } from '~/stores/audio'
|
||||
import { watch } from 'vue'
|
||||
|
||||
/**
|
||||
* Media Provider Composable
|
||||
* Verwaltet AudioElemente und Web Audio Nodes für Soundscapes.
|
||||
*/
|
||||
export function useMediaProvider() {
|
||||
const audioElements = new Map<string, HTMLAudioElement>()
|
||||
/**
|
||||
* Gibt ein HTMLAudioElement zurück für ein Soundscape-Titel
|
||||
* @param title z.B. "Forest"
|
||||
*/
|
||||
const getAudioElementForTitle = async (title: string): Promise<HTMLAudioElement | null> => {
|
||||
const src = getSoundscapeByTitle(title) || 'Forest'
|
||||
const audioElement = await createPlayableAudioElement(src)
|
||||
return audioElement
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein AudioElement, wartet bis es abspielbereit ist (canplay),
|
||||
* oder gibt nach Timeout einen Fehler zurück.
|
||||
*
|
||||
* @param {string} src - Die Quelle der Audiodatei
|
||||
* @param {number} timeoutMs - Maximale Wartezeit in Millisekunden
|
||||
* @returns {Promise<HTMLAudioElement>}
|
||||
*/
|
||||
|
||||
async function createPlayableAudioElement(src: string, timeoutMs = 10000): Promise<HTMLAudioElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const audio = new Audio(src)
|
||||
const timeout = setTimeout(() => {
|
||||
audio.src = ''
|
||||
reject(new Error(`AudioElement did not become playable within ${timeoutMs}ms`))
|
||||
}, timeoutMs)
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeout)
|
||||
audio.removeEventListener('canplay', onCanPlay)
|
||||
audio.removeEventListener('error', onError)
|
||||
}
|
||||
|
||||
const onCanPlay = () => {
|
||||
audioElements.set(src, audio)
|
||||
cleanup()
|
||||
resolve(audio)
|
||||
}
|
||||
|
||||
const onError = () => {
|
||||
cleanup()
|
||||
reject(new Error(`Failed to load audio: ${src}`))
|
||||
}
|
||||
|
||||
audio.addEventListener('canplay', onCanPlay)
|
||||
audio.addEventListener('error', onError)
|
||||
audio.load() // wichtig!
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen MediaElementAudioSourceNode + verbindet ihn mit masterGainNoise.
|
||||
* Fängt Fehler ab und gibt `null` zurück bei Problemen.
|
||||
*/
|
||||
const getMediaElementSourceByTitle = async (title: string): Promise<{ element: HTMLAudioElement; source: MediaElementAudioSourceNode } | null> => {
|
||||
const audioStore = useAudioStore()
|
||||
const audioContext = audioStore.getContext()
|
||||
const masterGain = audioStore.masterGainNoise
|
||||
|
||||
if (!audioContext || !masterGain) {
|
||||
console.warn('[MediaProvider] AudioContext oder masterGainNoise fehlt.')
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const element = await createPlayableAudioElement(title)
|
||||
if (!element) return null
|
||||
const source = audioContext.createMediaElementSource(element)
|
||||
source.connect(masterGain)
|
||||
return { element, source }
|
||||
} catch (err) {
|
||||
console.error('[MediaProvider] Fehler beim Erstellen von MediaElementSource:', err)
|
||||
await ensureAudio
|
||||
const source = audioContext.createMediaElementSource(await createPlayableAudioElement(title))
|
||||
source.connect(masterGain)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaktiver Binder für die Lautstärke an masterGainNoise.gain
|
||||
*/
|
||||
const bindVolumeToMasterGain = () => {
|
||||
const audioStore = useAudioStore()
|
||||
const masterGain = audioStore.masterGainNoise
|
||||
if (!masterGain) return
|
||||
|
||||
watch(
|
||||
() => audioStore.noiseVolume,
|
||||
async (volume) => {
|
||||
try {
|
||||
if(audioStore.audioContext) {
|
||||
masterGain.gain.setTargetAtTime(volume, audioStore.audioContext.currentTime, 0.05)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[MediaProvider] Fehler beim Setzen der Lautstärke:', err)
|
||||
if(audioStore && audioStore.audioContext) {
|
||||
await ensureAudio
|
||||
masterGain.gain.setTargetAtTime(volume, audioStore.audioContext.currentTime, 0.05)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
}
|
||||
|
||||
bindVolumeToMasterGain()
|
||||
|
||||
return {
|
||||
getAudioElementForTitle,
|
||||
getMediaElementSourceByTitle,
|
||||
createPlayableAudioElement
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user