diff --git a/data/src/emulator.js b/data/src/emulator.js index 8add2cbe..869e3ee2 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -420,6 +420,34 @@ class EmulatorJS { this.netplayCanvas = null; this.netplayShowTurnWarning = false; this.netplayWarningShown = false; + // Ensure the netplay button is visible by default (workaround for styling issues) + try { + if (netplay && netplay.style) netplay.style.display = ""; + } catch (e) {} + this.bindListeners(); + + // Resolution (host stream source): 1080p | 720p | 480p | 360p (default 480p, optimized for latency) + const normalizeResolution = (v) => { + const s = (typeof v === "string" ? v.trim() : "").toLowerCase(); + if (s === "1080p" || s === "720p" || s === "480p" || s === "360p") + return s; + return "480p"; + }; + const storedResolution = this.preGetSetting("netplayStreamResolution"); + const envResolution = + typeof window.EJS_NETPLAY_STREAM_RESOLUTION === "string" + ? window.EJS_NETPLAY_STREAM_RESOLUTION + : null; + const configResolution = + typeof this.config.netplayStreamResolution === "string" + ? this.config.netplayStreamResolution + : envResolution; + this.netplayStreamResolution = normalizeResolution( + typeof storedResolution === "string" + ? storedResolution + : configResolution, + ); + window.EJS_NETPLAY_STREAM_RESOLUTION = this.netplayStreamResolution; const storedSimulcast = this.preGetSetting("netplaySimulcast"); const envSimulcast = @@ -470,12 +498,14 @@ class EmulatorJS { typeof window.EJS_NETPLAY_HOST_CODEC === "string" ? window.EJS_NETPLAY_HOST_CODEC : null; - const configHostCodec = - typeof this.config.netplayHostCodec === "string" - ? this.config.netplayHostCodec - : envHostCodec; - this.netplayHostCodec = normalizeHostCodec( - typeof storedHostCodec === "string" ? storedHostCodec : configHostCodec, + const configHostScalability = + typeof this.config.netplayHostScalabilityMode === "string" + ? this.config.netplayHostScalabilityMode + : envHostScalability; + this.netplayHostScalabilityMode = normalizeHostScalability( + typeof storedHostScalability === "string" + ? storedHostScalability + : configHostScalability, ); window.EJS_NETPLAY_HOST_CODEC = this.netplayHostCodec; @@ -1733,7 +1763,35 @@ class EmulatorJS { window .EJS_Runtime({ noInitialRun: true, - onRuntimeInitialized: null, + onRuntimeInitialized: () => { + // Hook into Emscripten OpenAL to expose audio nodes for EmulatorJS + if (this.Module && this.Module.AL) { + const originalAlcCreateContext = this.Module.AL.alcCreateContext; + if (originalAlcCreateContext) { + this.Module.AL.alcCreateContext = (...args) => { + const ctx = originalAlcCreateContext.apply( + this.Module.AL, + args, + ); + if (ctx && ctx.audioCtx) { + // Expose the master gain node for EmulatorJS audio capture + if (!ctx.masterGain) { + ctx.masterGain = ctx.audioCtx.createGain(); + ctx.masterGain.gain.value = 1.0; + // Connect to destination if not already connected + if (ctx.masterGain && ctx.audioCtx.destination) { + ctx.masterGain.connect(ctx.audioCtx.destination); + } + console.log( + "[EmulatorJS] Exposed masterGain node for audio capture", + ); + } + } + return ctx; + }; + } + } + }, arguments: [], preRun: [], postRun: [], @@ -1772,6 +1830,35 @@ class EmulatorJS { }) .then((module) => { this.Module = module; + + // Set up audio node exposure for EmulatorJS after module loads + const setupAudioExposure = () => { + if (this.Module && this.Module.AL && this.Module.AL.currentCtx) { + const ctx = this.Module.AL.currentCtx; + if (ctx.audioCtx && !ctx.masterGain) { + ctx.masterGain = ctx.audioCtx.createGain(); + ctx.masterGain.gain.value = 1.0; + // Connect to destination if there's a gain chain + if (ctx.gain && ctx.gain.connect) { + ctx.gain.connect(ctx.masterGain); + ctx.masterGain.connect(ctx.audioCtx.destination); + } else if (ctx.audioCtx.destination) { + ctx.masterGain.connect(ctx.audioCtx.destination); + } + console.log( + "[EmulatorJS] Exposed masterGain node for audio capture", + ); + } + } + }; + + // Check immediately and then periodically + setupAudioExposure(); + const audioCheckInterval = setInterval(setupAudioExposure, 1000); + + // Clear interval after 10 seconds + setTimeout(() => clearInterval(audioCheckInterval), 10000); + this.downloadFiles(); }) .catch((e) => { @@ -7026,14 +7113,12 @@ class EmulatorJS { this.gameManager.setAltKeyEnabled(value === "enabled"); } else if (option === "lockMouse") { this.enableMouseLock = value === "enabled"; - } else if (option === "netplayVP9SVC") { - const normalizeVP9SVCMode = (v) => { - const s = typeof v === "string" ? v.trim() : ""; - const sl = s.toLowerCase(); - if (sl === "l1t1") return "L1T1"; - if (sl === "l1t3") return "L1T3"; - if (sl === "l2t3") return "L2T3"; - return "L1T1"; + } else if (option === "netplayStreamResolution") { + const normalizeResolution = (v) => { + const s = (typeof v === "string" ? v.trim() : "").toLowerCase(); + if (s === "1080p" || s === "720p" || s === "480p" || s === "360p") + return s; + return "480p"; }; this.netplayVP9SVCMode = normalizeVP9SVCMode(value); window.EJS_NETPLAY_VP9_SVC_MODE = this.netplayVP9SVCMode; @@ -7082,10 +7167,9 @@ class EmulatorJS { return s; return "auto"; }; - this.netplayHostCodec = normalizeHostCodec(value); - window.EJS_NETPLAY_HOST_CODEC = this.netplayHostCodec; - - // If host is currently producing SFU video, re-produce so codec takes effect. + this.netplayHostScalabilityMode = normalizeHostScalability(value); + window.EJS_NETPLAY_HOST_SCALABILITY_MODE = + this.netplayHostScalabilityMode; try { if ( this.isNetplay && @@ -8644,36 +8728,62 @@ class EmulatorJS { }); } - // Always populate slot UI from current preference, and wire live switching once. - try { - if (this.netplay && this.netplay.slotSelect) { - const s = - typeof this.netplay.localSlot === "number" - ? this.netplay.localSlot - : typeof this.netplayPreferredSlot === "number" - ? this.netplayPreferredSlot - : 0; - this.netplay.slotSelect.value = String(Math.max(0, Math.min(3, s))); - - if (!this.netplay._slotSelectWired) { - this.netplay._slotSelectWired = true; - this.addEventListener(this.netplay.slotSelect, "change", () => { - const raw = parseInt(this.netplay.slotSelect.value, 10); - const slot = isNaN(raw) ? 0 : Math.max(0, Math.min(3, raw)); - this.netplay.localSlot = slot; - this.netplayPreferredSlot = slot; - window.EJS_NETPLAY_PREFERRED_SLOT = slot; - if (this.netplay.extra) { - this.netplay.extra.player_slot = slot; - } - // Update player table with new slot - this.netplayUpdatePlayerSlot(slot); - if (this.settings) { - this.settings.netplayPreferredSlot = String(slot); + // Fallback: Try to tap into existing OpenAL sources + // Create a master gain node and connect all source gains to it + console.log("[EmulatorJS] Checking OpenAL sources fallback:", { + hasSources: !!openALCtx.sources, + sourcesLength: openALCtx.sources + ? openALCtx.sources.length + : "undefined", + }); + if (openALCtx.sources && openALCtx.sources.length > 0) { + console.log( + "[EmulatorJS] Attempting to create master gain from OpenAL sources", + ); + try { + // Find the audio context from the first source + const firstSource = openALCtx.sources[0]; + console.log("[EmulatorJS] First source info:", { + hasSource: !!firstSource, + hasGain: !!firstSource.gain, + gainType: firstSource.gain ? typeof firstSource.gain : "undefined", + hasContext: !!(firstSource.gain && firstSource.gain.context), + }); + if (firstSource && firstSource.gain && firstSource.gain.context) { + const audioContext = firstSource.gain.context; + const masterGain = audioContext.createGain(); + masterGain.gain.value = 1.0; + + // Connect all source gains to the master gain + let connectedCount = 0; + openALCtx.sources.forEach((source) => { + if (source.gain && typeof source.gain.connect === "function") { + source.gain.connect(masterGain); + connectedCount++; } this.saveSettings(); }); + + if (connectedCount > 0) { + console.log( + `[EmulatorJS] Created master gain node from ${connectedCount} OpenAL sources`, + ); + return masterGain; + } else { + console.log( + "[EmulatorJS] No OpenAL sources could be connected to master gain", + ); + } + } else { + console.log( + "[EmulatorJS] OpenAL sources found but no valid gain context", + ); } + } catch (e) { + console.warn( + "[EmulatorJS] Failed to create master gain from OpenAL sources:", + e, + ); } } catch (e) { // ignore