Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 155 additions & 45 deletions data/src/emulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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: [],
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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
Expand Down
Loading