diff --git a/ai_animation/public/sounds/background_ambience.mp3 b/ai_animation/public/sounds/background_ambience.mp3 new file mode 100644 index 0000000..5e6507c Binary files /dev/null and b/ai_animation/public/sounds/background_ambience.mp3 differ diff --git a/ai_animation/src/backgroundAudio.ts b/ai_animation/src/backgroundAudio.ts new file mode 100644 index 0000000..7b7231b --- /dev/null +++ b/ai_animation/src/backgroundAudio.ts @@ -0,0 +1,69 @@ +/** + * Background audio management for streaming mode + */ + +let backgroundAudio: HTMLAudioElement | null = null; +let isAudioInitialized = false; + +/** + * Initialize background audio for streaming + * Only loads in streaming mode to avoid unnecessary downloads + */ +export function initializeBackgroundAudio(): void { + const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true'; + + if (!isStreamingMode || isAudioInitialized) { + return; + } + + isAudioInitialized = true; + + // Create audio element + backgroundAudio = new Audio(); + backgroundAudio.loop = true; + backgroundAudio.volume = 0.4; // 40% volume as requested + + // For now, we'll use a placeholder - you should download and convert the wave file + // to a smaller MP3 format (aim for < 10MB) and place it in public/sounds/ + backgroundAudio.src = './sounds/background_ambience.mp3'; + + // Handle audio loading + backgroundAudio.addEventListener('canplaythrough', () => { + console.log('Background audio loaded and ready to play'); + }); + + backgroundAudio.addEventListener('error', (e) => { + console.error('Failed to load background audio:', e); + }); +} + +/** + * Start playing background audio + * Will only work after user interaction due to browser policies + */ +export function startBackgroundAudio(): void { + if (backgroundAudio && backgroundAudio.paused) { + backgroundAudio.play().catch(err => { + console.log('Background audio autoplay blocked, will retry on user interaction:', err); + }); + } +} + +/** + * Stop background audio + */ +export function stopBackgroundAudio(): void { + if (backgroundAudio && !backgroundAudio.paused) { + backgroundAudio.pause(); + } +} + +/** + * Set background audio volume + * @param volume - Volume level from 0 to 1 + */ +export function setBackgroundAudioVolume(volume: number): void { + if (backgroundAudio) { + backgroundAudio.volume = Math.max(0, Math.min(1, volume)); + } +} \ No newline at end of file diff --git a/ai_animation/src/domElements/chatWindows.ts b/ai_animation/src/domElements/chatWindows.ts index 0438b32..d5d8ff3 100644 --- a/ai_animation/src/domElements/chatWindows.ts +++ b/ai_animation/src/domElements/chatWindows.ts @@ -404,7 +404,15 @@ function animateMessageWords(message: string, contentSpanId: string, targetPower setTimeout(addNextWord, delay); // Scroll to ensure newest content is visible - messagesContainer.scrollTop = messagesContainer.scrollHeight; + // Use requestAnimationFrame to batch DOM updates in streaming mode + const isStreamingModeForScroll = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true'; + if (isStreamingModeForScroll) { + requestAnimationFrame(() => { + messagesContainer.scrollTop = messagesContainer.scrollHeight; + }); + } else { + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } }; // Start animation diff --git a/ai_animation/src/gameState.ts b/ai_animation/src/gameState.ts index 1391723..8b6dc75 100644 --- a/ai_animation/src/gameState.ts +++ b/ai_animation/src/gameState.ts @@ -376,6 +376,9 @@ class GameState { if (mapView === null) { throw Error("Cannot find mapView element, unable to continue.") } + + const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true'; + this.scene.background = new THREE.Color(0x87CEEB); // Camera @@ -386,14 +389,27 @@ class GameState { 3000 ); this.camera.position.set(0, 800, 900); // MODIFIED: Increased z-value to account for map shift - this.renderer = new THREE.WebGLRenderer({ antialias: true }); + + // Renderer with streaming optimizations + this.renderer = new THREE.WebGLRenderer({ + antialias: !isStreamingMode, // Disable antialiasing in streaming mode + powerPreference: "high-performance", + preserveDrawingBuffer: isStreamingMode // Required for streaming + }); this.renderer.setSize(mapView.clientWidth, mapView.clientHeight); - this.renderer.setPixelRatio(window.devicePixelRatio); + + // Force lower pixel ratio for streaming to reduce GPU load + if (isStreamingMode) { + this.renderer.setPixelRatio(1); + } else { + this.renderer.setPixelRatio(window.devicePixelRatio); + } + mapView.appendChild(this.renderer.domElement); - // Controls + // Controls with streaming optimizations this.camControls = new OrbitControls(this.camera, this.renderer.domElement); - this.camControls.enableDamping = true; + this.camControls.enableDamping = !isStreamingMode; // Disable damping for immediate response this.camControls.dampingFactor = 0.05; this.camControls.screenSpacePanning = true; this.camControls.minDistance = 100; diff --git a/ai_animation/src/main.ts b/ai_animation/src/main.ts index 780a870..a6d744f 100644 --- a/ai_animation/src/main.ts +++ b/ai_animation/src/main.ts @@ -14,6 +14,7 @@ import { closeTwoPowerConversation, showTwoPowerConversation } from "./component import { PowerENUM } from "./types/map"; import { debugMenuInstance } from "./debug/debugMenu"; import { sineWave } from "./utils/timing"; +import { initializeBackgroundAudio, startBackgroundAudio } from "./backgroundAudio"; //TODO: Create a function that finds a suitable unit location within a given polygon, for placing units better // Currently the location for label, unit, and SC are all the same manually picked location @@ -24,6 +25,9 @@ const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import function initScene() { gameState.createThreeScene() + // Initialize background audio for streaming mode + initializeBackgroundAudio(); + // Enable audio on first user interaction (to comply with browser autoplay policies) let audioEnabled = false; const enableAudio = () => { @@ -35,6 +39,11 @@ function initScene() { silentAudio.volume = 0; silentAudio.play().catch(() => {}); + // Start background audio in streaming mode + if (isStreamingMode) { + startBackgroundAudio(); + } + // Remove the listener after first interaction document.removeEventListener('click', enableAudio); document.removeEventListener('keydown', enableAudio); @@ -78,8 +87,10 @@ function initScene() { } if (isStreamingMode) { setTimeout(() => { - togglePlayback() - }, 5000) // Increased delay to 5 seconds for Chrome to stabilize + togglePlayback(); + // Try to start background audio when auto-starting + startBackgroundAudio(); + }, 10000) // Increased delay to 10 seconds for Chrome to fully load in Docker } }) }).catch(err => { @@ -191,20 +202,25 @@ function animate(currentTime: number = 0) { }, config.effectivePlaybackSpeed); } // Update any pulsing or wave animations on supply centers or units - if (gameState.scene.userData.animatedObjects) { + // In streaming mode, reduce animation frequency + const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true'; + const frameSkip = isStreamingMode ? 2 : 1; // Skip every other frame in streaming + + if (gameState.scene.userData.animatedObjects && (Math.floor(currentTime / 16.67) % frameSkip === 0)) { gameState.scene.userData.animatedObjects.forEach(obj => { if (obj.userData.pulseAnimation) { const anim = obj.userData.pulseAnimation; // Use delta time for consistent animation speed regardless of frame rate - anim.time += anim.speed * deltaTime; + anim.time += anim.speed * deltaTime * frameSkip; // Compensate for skipped frames if (obj.userData.glowMesh) { const pulseValue = sineWave(config.animation.supplyPulseFrequency, anim.time, anim.intensity, 0.5); obj.userData.glowMesh.material.opacity = 0.2 + (pulseValue * 0.3); const scale = 1 + (pulseValue * 0.1); obj.userData.glowMesh.scale.set(scale, scale, scale); } - // Subtle bobbing up/down - obj.position.y = 2 + sineWave(config.animation.supplyPulseFrequency, anim.time, 0.5); + // Subtle bobbing up/down - reduce in streaming mode + const bobAmount = isStreamingMode ? 0.25 : 0.5; + obj.position.y = 2 + sineWave(config.animation.supplyPulseFrequency, anim.time, bobAmount); } }); } diff --git a/twitch-streamer/chrome-launcher.sh b/twitch-streamer/chrome-launcher.sh index c412e18..f11ded7 100644 --- a/twitch-streamer/chrome-launcher.sh +++ b/twitch-streamer/chrome-launcher.sh @@ -31,8 +31,12 @@ while true; do --use-fake-ui-for-media-stream \ --enable-usermedia-screen-capturing \ --enable-gpu \ - --use-gl=swiftshader \ + --use-gl=angle \ + --use-angle=gl \ --disable-gpu-vsync \ + --disable-gpu-sandbox \ + --enable-accelerated-2d-canvas \ + --enable-accelerated-video-decode=false \ --force-device-scale-factor=1 \ --disable-web-security \ --disable-features=VizDisplayCompositor \