mindboost-rnbo-test-project/stores/audio.ts

226 lines
7.5 KiB
TypeScript

import { defineStore } from 'pinia'
import type { Logger } from 'pino'
interface State {
audioContext: AudioContext | null,
masterGainNoise: GainNode | null,
masterGainMusic: GainNode | null,
audioSink: typeof Audio | null,
showOverlay: boolean,
monitoringInterval: NodeJS.Timeout | null,
playing: boolean,
scene: string,
state: string | null,
volume: number,
noiseVolume: number
}
function createOverlay (audioContext: AudioContext) {
const logger = useNuxtApp().$logger as Logger
logger.info('Create Overlay')
const overlay = document.createElement('div')
overlay.className = 'overlay'
// Erstelle den Button
const button = document.createElement('button')
button.className = 'btn btn-primary btn-lg'
button.style.backgroundColor = '#e9c046'
button.style.border = 'none'
button.style.color = 'black'
button.innerText = 'Ready'
// Füge Event-Listener hinzu, um den AudioContext zu entsperren
button.addEventListener('click', async () => {
try {
await audioContext.resume()
logger.info('AudioContext resumed.')
overlay.remove() // Entferne das Overlay nach der Interaktion
} catch (error) {
useAudioStore().resetAudioContext()
useAudioStore().initializeAudioContext()
logger.error('Error resuming AudioContext:', error)
}
})
// Füge Button dem Overlay hinzu
overlay.appendChild(button)
// Füge Overlay zum DOM hinzu
document.body.appendChild(overlay)
}
export const useAudioStore = defineStore('audio', {
state: (): State => ({
audioContext: null as AudioContext | null,
masterGainNoise: null as GainNode | null,
masterGainMusic: null as GainNode | null,
audioSink: null as typeof Audio | null,
showOverlay: false,
monitoringInterval: null as NodeJS.Timeout | null,
playing: false,
scene: 'Lagoon',
state: null as string | null,
volume: parseFloat(localStorage.getItem('volume') || '1'),
noiseVolume: parseFloat(localStorage.getItem('noiseVolume') || '1')
}),
actions: {
getNewAudioElement (newTrack: string) {
const logger = useNuxtApp().$logger as Logger
logger.info(`Current track changed to: ${newTrack}`)
return useMediaProvider().getAudioElementForTitle(newTrack)
// Additional logic for changing tracks can be added here
// For example, you might want to stop the current audio, load the new track, etc.
// This depends on how your audio playback is implemented
},
async ensureAudioContextRunning () {
if (!this.audioContext) {
this.initializeAudioContext()
}
if (this.audioContext?.state === 'suspended') {
try {
await this.audioContext.resume()
} catch (error) {
const logger = useNuxtApp().$logger as Logger
logger.error('Error resuming AudioContext:', error)
this.showOverlay = true
}
}
},
initializeAudioContext () {
const logger = useNuxtApp().$logger as Logger
if (!this.audioContext) {
this.audioContext = new AudioContext()
this.prepareGains(this.audioContext)
logger.info('AudioContext initialized and Gains prepared.')
// Attach some error handling, to ensure audiocontext is running all the time.
this.startAudioContextMonitor()
this.addStateMonitor()
}
},
prepareGains(audioContext: AudioContext){
this.masterGainNoise = null
this.masterGainMusic = null
this.masterGainNoise = audioContext.createGain()
this.masterGainMusic = audioContext.createGain()
this.masterGainNoise.connect(audioContext.destination)
this.masterGainMusic.connect(audioContext.destination)
},
addStateMonitor () {
const logger = useNuxtApp().$logger as Logger
if (!this.audioContext) {
this.initializeAudioContext()
return
}
// Überwache Änderungen im AudioContext-State
this.audioContext.onstatechange = () => {
const currentState = this.audioContext?.state
logger.info('AudioContext state changed:', currentState)
if (currentState === 'suspended') {
createOverlay(this.getContext())
}
}
// Optional: Überwache Änderungen in den Audioausgabe-Sinks
if ('onsinkchange' in this.audioContext) {
this.audioContext.onsinkchange = () => {
logger.info('Audio sink configuration has changed.')
// Füge hier zusätzliche Logik hinzu, falls erforderlich
}
}
},
getMasterGainNoise (): GainNode {
if(this.masterGainNoise) {
return this.masterGainNoise
} else {
const context = this.getContext()
this.masterGainNoise = context.createGain()
this.masterGainNoise.connect(context.destination)
return this.masterGainNoise
}
},
getContext (): AudioContext {
if (!this.audioContext ) {
this.audioContext ||= new AudioContext()
}
return this.audioContext
},
async resumeAudioContext () {
const logger = useNuxtApp().$logger as Logger
if (this.audioContext?.state === 'suspended') {
try {
await this.audioContext.resume()
this.state = this.audioContext.state
const state = this.state
logger.info('AudioContext resumed. ', { state })
this.stopAudioContextMonitor()
} catch (error) {
logger.error('Error resuming AudioContext:', error)
this.resetAudioContext()
this.initializeAudioContext()
await this.ensureAudioContextRunning()
}
}
},
togglePlaying (): void {
this.playing = !this.playing
},
isPlaying (): boolean {
return this.playing
},
setPlaying (stateChange: boolean): void {
this.playing = stateChange
},
setVolume (newVolume: number) {
this.volume = newVolume
localStorage.setItem('volume', newVolume.toString())
},
setNoiseVolume (newVolume: number) {
this.noiseVolume = newVolume
localStorage.setItem('noiseVolume', newVolume.toString())
},
startAudioContextMonitor () {
const logger = useNuxtApp().$logger as Logger
// Starte ein Intervall, wenn keines aktiv ist
if (!this.monitoringInterval) {
this.monitoringInterval = setInterval(() => {
if (this.audioContext?.state === 'suspended') {
logger.info('AudioContext is suspended. Attempting to resume...')
this.audioContext.resume()
}
}, 500) // Überprüft alle 500ms
logger.info('AudioContext monitoring started.')
}
},
stopAudioContextMonitor () {
const logger = useNuxtApp().$logger as Logger
// Stoppe das Intervall, falls es läuft
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval)
this.monitoringInterval = null
logger.info('AudioContext monitoring stopped.')
}
},
resetAudioContext () {
const logger = useNuxtApp().$logger as Logger
if (this.audioContext) {
this.audioContext.close()
this.audioContext = null
logger.info('AudioContext has been closed.')
}
}
},
getters: {
getPlaying: state => state.playing,
getVolume: state => state.volume,
getNoiseVolume: state => state.noiseVolume
}
})
// Provide a method to ensure audio context is running
export const ensureAudio = async () => {
const audioStore = useAudioStore()
await audioStore.ensureAudioContextRunning()
}