From fc141a3454b50c484b197442e878454d43ea3d4a Mon Sep 17 00:00:00 2001 From: JeremyGamer13 <69337718+JeremyGamer13@users.noreply.github.com> Date: Fri, 1 Aug 2025 23:44:59 -0600 Subject: [PATCH 1/4] Update runtime.js --- src/engine/runtime.js | 102 +++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 27 deletions(-) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 8076a7154b9..bf10e51d490 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -584,11 +584,18 @@ class Runtime extends EventEmitter { this.extensionButtons = new Map(); /** - * Contains the audio context and gain node for each extension that registers them. - * Used to make sure the extensions respect addons or the pause button. - * @type {Map} + * Stores information from extensions that integrate features into the GUI, VM, Addons, etc. + * Do not update this directly. Use Runtime.registerExtensionIntegrationComponents() instead. + * @private + * @type {Map, + * whitelistUsed: boolean, + * audioContexts: Array, + * audioNodes: Array, + * gainNodes: Array, + * }>} */ - this._extensionAudioObjects = new Map(); + this._extensionIntegrationObjects = new Map(); /** * Responsible for managing custom fonts. @@ -958,6 +965,15 @@ class Runtime extends EventEmitter { return 'EXTENSION_FIELD_ADDED'; } + /** + * Event name for reporting that an extension registered components with Runtime.registerExtensionIntegrationComponents() + * This event can run several times for the same extension. + * @const {string} + */ + static get EXTENSION_INTEGRATION_REGISTERED () { + return 'EXTENSION_INTEGRATION_REGISTERED'; + } + /** * Event name for updating the available set of peripheral devices. * This causes the peripheral connection modal to update a list of @@ -1260,25 +1276,53 @@ class Runtime extends EventEmitter { } /** - * Allows AudioContexts and GainNodes from an extension to respect addons and runtime pausing by default. - * If audioContext is not supplied, recording addon + pause button will not work with the extension this way. - * If gainNode is not supplied, recording addon + volume slider will not work with the extension this way. - * @param {string} extensionId The extension's ID. May be used internally in the future, or by other extensions. - * @param {AudioContext} audioContext The AudioContext being used in the extension. - * @param {GainNode} gainNode The GainNode that is connected to the AudioContext. All other nodes in the extension should be connected to this GainNode, and this GainNode should be connected to the destination of the AudioContext. + * @deprecated Use registerExtensionIntegrationComponents() instead. + * @param {string} extensionId + * @param {AudioContext} audioContext + * @param {GainNode} gainNode */ registerExtensionAudioContext(extensionId, audioContext, gainNode) { - if (typeof extensionId !== "string") throw new TypeError('Extension ID must be string'); - if (!extensionId) throw new Error('No extension ID specified'); // empty string - - const obj = {}; - if (audioContext) { - obj.audioContext = audioContext; - } - if (gainNode) { - obj.gainNode = gainNode; - } - this._extensionAudioObjects.set(extensionId, obj); + const whitelist = []; + if (audioContext) whitelist.push("audioContextSuspend", "audioContextResume", "audioMediaStream"); + if (gainNode) whitelist.push("audioMediaStream", "gainNodeSet"); + this.registerExtensionIntegrationComponents(extensionId, [...new Set(whitelist)], audioContext, gainNode); + } + /** + * Allows extensions to register components that are used for other PenguinMod features. + * This can include GUI menus, addons, or other extensions. + * + * The `audioContexts` are used for features that need "audioContextSuspend", "audioContextResume", "audioMediaStream" + * + * The `audioNodes` are used for features that need "audioMediaStream" + * + * The `gainNodes` are used for features that need "gainNodeSet" + * + * You can re-run this function if you need to change any information. + * However, the original information will be replaced, not appended to. + * @param {string} extensionId The extension's ID. May be used internally in the future, or by other extensions. + * @param {Array< + * "audioContextSuspend"|"audioContextResume" + * |"audioMediaStream" + * |"gainNodeSet" + * >} whitelistFeatures Which features to enable. Passing an empty array will allow all components provided to be used by PenguinMod. + * @param {object} components Parts of your extension. + * @param {Array} components.audioContexts Any `AudioContext`s being used in the extension that you want to register. + * @param {Array} components.audioNodes Any destination `AudioNode`s you want to register. These nodes should be connected to the `destination` of the `AudioContext`s provided. + * @param {Array} components.gainNodes The deepest `GainNode`s you want to register. Do not include `GainNode`s within the same tree of `AudioNode`s, only specify the `GainNode` closest to the `AudioContext`'s `destination`. + */ + registerExtensionIntegrationComponents(extensionId, whitelistFeatures, components) { + if (!extensionId || typeof extensionId !== "string") throw new TypeError('Extension ID must be string'); + if (!Array.isArray(whitelistFeatures)) throw new TypeError("whitelistFeatures must be Array"); + + const information = { + whitelist: whitelistFeatures, + whitelistUsed: whitelistFeatures.length > 0, + audioContexts: components.audioContexts ? components.audioContexts : [], + audioNodes: components.audioNodes ? components.audioNodes : [], + gainNodes: components.gainNodes ? components.gainNodes : [], + }; + this._extensionIntegrationObjects.set(extensionId, information); + this.emit(Runtime.EXTENSION_INTEGRATION_REGISTERED, information); } getMonitorState () { @@ -2884,11 +2928,13 @@ class Runtime extends EventEmitter { if (this.paused) return; this.emit(Runtime.RUNTIME_PRE_PAUSED); this.paused = true; + // pause all audio contexts (that includes exts with their own AC or gain node) this.audioEngine.audioContext.suspend(); - for (const audioData of this._extensionAudioObjects.values()) { - if (audioData.audioContext) { - audioData.audioContext.suspend(); + for (const extensionInformation of this._extensionIntegrationObjects.values()) { + if (extensionInformation.whitelistUsed && !extensionInformation.whitelist.includes("audioContextSuspend")) continue; + for (const audioContext of extensionInformation.audioContexts) { + audioContext.suspend(); } } @@ -2905,11 +2951,13 @@ class Runtime extends EventEmitter { play() { if (!this.paused) return; this.paused = false; + // resume all audio contexts (that includes exts with their own AC or gain node) this.audioEngine.audioContext.resume(); - for (const audioData of this._extensionAudioObjects.values()) { - if (audioData.audioContext) { - audioData.audioContext.resume(); + for (const extensionInformation of this._extensionIntegrationObjects.values()) { + if (extensionInformation.whitelistUsed && !extensionInformation.whitelist.includes("audioContextResume")) continue; + for (const audioContext of extensionInformation.audioContexts) { + audioContext.resume(); } } From 02cabc5fd286fea9f6dd12fb74240d1549c4d0ce Mon Sep 17 00:00:00 2001 From: JeremyGamer13 <69337718+JeremyGamer13@users.noreply.github.com> Date: Sat, 2 Aug 2025 00:34:56 -0600 Subject: [PATCH 2/4] fix deprecated function --- src/engine/runtime.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index bf10e51d490..bdc3176c2fe 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -1285,7 +1285,10 @@ class Runtime extends EventEmitter { const whitelist = []; if (audioContext) whitelist.push("audioContextSuspend", "audioContextResume", "audioMediaStream"); if (gainNode) whitelist.push("audioMediaStream", "gainNodeSet"); - this.registerExtensionIntegrationComponents(extensionId, [...new Set(whitelist)], audioContext, gainNode); + this.registerExtensionIntegrationComponents(extensionId, [...new Set(whitelist)], { + audioContexts: [audioContext], + gainNodes: [gainNode], + }); } /** * Allows extensions to register components that are used for other PenguinMod features. From 2d350901680b0385cf945888c0fe9d7784eb1340 Mon Sep 17 00:00:00 2001 From: JeremyGamer13 <69337718+JeremyGamer13@users.noreply.github.com> Date: Sat, 2 Aug 2025 01:03:47 -0600 Subject: [PATCH 3/4] actually fix the deprecated func --- src/engine/runtime.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index bdc3176c2fe..298a44969e5 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -1287,6 +1287,7 @@ class Runtime extends EventEmitter { if (gainNode) whitelist.push("audioMediaStream", "gainNodeSet"); this.registerExtensionIntegrationComponents(extensionId, [...new Set(whitelist)], { audioContexts: [audioContext], + audioNodes: [gainNode], gainNodes: [gainNode], }); } From cadd56d067f77f31a98a26d79b2ead40e6d5a616 Mon Sep 17 00:00:00 2001 From: JeremyGamer13 <69337718+JeremyGamer13@users.noreply.github.com> Date: Sat, 2 Aug 2025 01:13:44 -0600 Subject: [PATCH 4/4] update Sound Systems --- src/extensions/jg_audio/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extensions/jg_audio/index.js b/src/extensions/jg_audio/index.js index cd8d11e08cf..289b29036d3 100644 --- a/src/extensions/jg_audio/index.js +++ b/src/extensions/jg_audio/index.js @@ -28,7 +28,11 @@ class AudioExtension { } }); - this.runtime.registerExtensionAudioContext("jgExtendedAudio", this.helper.audioContext, this.helper.audioGlobalVolumeNode); + this.runtime.registerExtensionIntegrationComponents("jgExtendedAudio", [], { + audioContexts: [this.helper.audioContext], + audioNodes: [this.helper.audioGlobalVolumeNode], + gainNodes: [this.helper.audioGlobalVolumeNode], + }); } deserialize(data) {