diff --git a/frontend/src/assets/motorSound.js b/frontend/src/assets/motorSound.js index cb37b0c..42e1069 100644 --- a/frontend/src/assets/motorSound.js +++ b/frontend/src/assets/motorSound.js @@ -19,7 +19,8 @@ class MotorSound { async init() { // Verwende ScriptProcessor für bessere Kompatibilität console.log('Initialisiere MotorSound mit ScriptProcessor'); - this.workletNode = this.context.createScriptProcessor(1024); + // Kleinere Buffergröße für geringere Latenz + this.workletNode = this.context.createScriptProcessor(256); this.workletNode.onaudioprocess = this.process.bind(this); // Filter-Kette für motorähnlicheren Klang @@ -34,7 +35,7 @@ class MotorSound { this.lowPass.Q.value = 0.5; this.gainNode = this.context.createGain(); - this.gainNode.gain.value = 0.25; // Leiser starten + this.gainNode.gain.setValueAtTime(0.25, this.context.currentTime); // Verbindung: script -> lowshelf -> lowpass -> gain this.workletNode.connect(this.lowShelf); diff --git a/frontend/src/views/minigames/TaxiGame.vue b/frontend/src/views/minigames/TaxiGame.vue index 6363178..e876396 100644 --- a/frontend/src/views/minigames/TaxiGame.vue +++ b/frontend/src/views/minigames/TaxiGame.vue @@ -227,7 +227,8 @@ export default { obstacles: [], keys: {}, motorSound: null, - audioContext: null + audioContext: null, + audioUnlockHandler: null } }, async mounted() { @@ -238,6 +239,7 @@ export default { this.loadMaps(); this.setupEventListeners(); await this.initializeMotorSound(); + this.setupAudioUnlockHandlers(); }, beforeUnmount() { this.cleanup(); @@ -307,10 +309,25 @@ export default { document.addEventListener('keydown', this.handleKeyDown); document.addEventListener('keyup', this.handleKeyUp); }, + setupAudioUnlockHandlers() { + this.audioUnlockHandler = async () => { + await this.initAudioOnUserInteraction(); + if (this.audioContext && this.audioContext.state === 'suspended') { + await this.audioContext.resume().catch(() => {}); + } + document.removeEventListener('pointerdown', this.audioUnlockHandler, { capture: true }); + document.removeEventListener('touchstart', this.audioUnlockHandler, { capture: true }); + document.removeEventListener('keydown', this.audioUnlockHandler, { capture: true }); + this.audioUnlockHandler = null; + }; + document.addEventListener('pointerdown', this.audioUnlockHandler, { capture: true, passive: true }); + document.addEventListener('touchstart', this.audioUnlockHandler, { capture: true, passive: true }); + document.addEventListener('keydown', this.audioUnlockHandler, { capture: true }); + }, async initializeMotorSound() { // AudioContext wird erst bei erster Benutzerinteraktion erstellt - console.log('MotorSound wird bei erster Benutzerinteraktion initialisiert'); + if (import.meta.env?.DEV) console.log('[Audio] Lazy-Init: MotorSound wird bei erster Benutzerinteraktion initialisiert'); }, async initAudioOnUserInteraction() { @@ -318,17 +335,21 @@ export default { try { this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); + if (import.meta.env?.DEV) console.log('[Audio] init: Context erstellt. state=', this.audioContext.state, 'rate=', this.audioContext.sampleRate); // Autoplay-Policy: Context sofort im User-Gesture fortsetzen if (this.audioContext.state === 'suspended') { + if (import.meta.env?.DEV) console.log('[Audio] init: resume() versuchen'); await this.audioContext.resume(); + if (import.meta.env?.DEV) console.log('[Audio] init: state nach resume=', this.audioContext.state); } const generator = new NoiseGenerator(); this.motorSound = new MotorSound(this.audioContext, generator); await this.motorSound.init(); - console.log('MotorSound initialisiert'); + if (import.meta.env?.DEV) console.log('[Audio] init: MotorSound initialisiert'); // Falls bereits Bewegung anliegt, Sound direkt starten const speedKmh = this.taxi.speed * 5; if (speedKmh > 0) { + if (import.meta.env?.DEV) console.log('[Audio] init: direkter Start. speedKmh=', speedKmh); this.motorSound.start(); // Sofort Parameter setzen const speedFactor = Math.min(speedKmh / 120, 1); @@ -338,7 +359,7 @@ export default { this.motorSound.setVolume(volume); } } catch (error) { - console.warn('MotorSound konnte nicht initialisiert werden:', error); + console.warn('[Audio] init: Fehler', error); } }, @@ -347,15 +368,11 @@ export default { const speedKmh = this.taxi.speed * 5; // Geschwindigkeit in km/h const isMoving = speedKmh > 0; + if (import.meta.env?.DEV) console.log('[Audio] update:', { speedKmh, isMoving, ctx: this.audioContext?.state, playing: this.motorSound.isPlaying }); - // Sicherstellen, dass der AudioContext läuft - if (this.audioContext && this.audioContext.state === 'suspended') { - this.audioContext.resume().catch(() => {}); - } - - if (isMoving && !this.motorSound.isPlaying) { - this.motorSound.start(); - } else if (!isMoving && this.motorSound.isPlaying) { + // Start NICHT hier (kein User-Gesture). Nur Stop, wenn still. + if (!isMoving && this.motorSound.isPlaying) { + if (import.meta.env?.DEV) console.log('[Audio] update: motorSound.stop()'); this.motorSound.stop(); } @@ -364,6 +381,7 @@ export default { const speedFactor = Math.min(speedKmh / 120, 1); // 0-1 basierend auf 0-120 km/h const motorSpeed = 0.3 + (speedFactor * 0.3); // 0.3 bis 1.5 const volume = 0.1 + (speedFactor * 0.4); // 0.1 bis 0.5 + if (import.meta.env?.DEV) console.log('[Audio] update: params', { motorSpeed, volume }); this.motorSound.setSpeed(motorSpeed); this.motorSound.setVolume(volume); @@ -382,6 +400,12 @@ export default { } document.removeEventListener('keydown', this.handleKeyDown); document.removeEventListener('keyup', this.handleKeyUp); + if (this.audioUnlockHandler) { + document.removeEventListener('pointerdown', this.audioUnlockHandler, { capture: true }); + document.removeEventListener('touchstart', this.audioUnlockHandler, { capture: true }); + document.removeEventListener('keydown', this.audioUnlockHandler, { capture: true }); + this.audioUnlockHandler = null; + } }, generateLevel() { @@ -982,19 +1006,17 @@ export default { async handleCanvasClick(event) { // AudioContext bei erster Benutzerinteraktion initialisieren + if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: start, hasCtx=', !!this.audioContext); await this.initAudioOnUserInteraction(); if (this.audioContext && this.audioContext.state === 'suspended') { + if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: resume()'); await this.audioContext.resume().catch(() => {}); + if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: state=', this.audioContext.state); } - // Motor ggf. direkt starten + // Motor ggf. direkt starten (Klick ist User-Geste) if (this.motorSound && !this.motorSound.isPlaying) { + if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: motorSound.start()'); this.motorSound.start(); - const speedKmh = this.taxi.speed * 5; - const speedFactor = Math.min(speedKmh / 120, 1); - const motorSpeed = 0.3 + (speedFactor * 0.3); - const volume = 0.1 + (speedFactor * 0.4); - this.motorSound.setSpeed(motorSpeed); - this.motorSound.setVolume(volume); } }, @@ -1002,14 +1024,18 @@ export default { this.keys[event.key] = true; // AudioContext bei erster Benutzerinteraktion initialisieren + if (import.meta.env?.DEV) console.log('[Audio] KeyDown: key=', event.key, 'hasCtx=', !!this.audioContext); await this.initAudioOnUserInteraction(); if (this.audioContext && this.audioContext.state === 'suspended') { + if (import.meta.env?.DEV) console.log('[Audio] KeyDown: resume()'); await this.audioContext.resume().catch(() => {}); } - // Motor ggf. direkt starten - if (this.motorSound && !this.motorSound.isPlaying) { + // Bei Beschleunigungs-Key Motor starten (User-Geste garantiert) + if ((event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') && this.motorSound && !this.motorSound.isPlaying) { + if (import.meta.env?.DEV) console.log('[Audio] KeyDown: motorSound.start()'); this.motorSound.start(); - const speedKmh = this.taxi.speed * 5; + // Direkt Parameter setzen, um hörbares Feedback ohne Verzögerung zu bekommen + const speedKmh = Math.max(5, this.taxi.speed * 5); const speedFactor = Math.min(speedKmh / 120, 1); const motorSpeed = 0.3 + (speedFactor * 0.3); const volume = 0.1 + (speedFactor * 0.4);