diff --git a/platform/web/js/libs/audio.position.worklet.js b/platform/web/js/libs/audio.position.worklet.js index bf3ac4ae2d2..7dbeb8a0add 100644 --- a/platform/web/js/libs/audio.position.worklet.js +++ b/platform/web/js/libs/audio.position.worklet.js @@ -28,10 +28,20 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +const POST_THRESHOLD_S = 0.1; + class GodotPositionReportingProcessor extends AudioWorkletProcessor { - constructor() { - super(); + constructor(...args) { + super(...args); + this.lastPostTime = currentTime; this.position = 0; + + this.port.onmessage = (event) => { + if (event?.data?.type === 'clear') { + this.lastPostTime = currentTime; + this.position = 0; + } + }; } process(inputs, _outputs, _parameters) { @@ -39,10 +49,15 @@ class GodotPositionReportingProcessor extends AudioWorkletProcessor { const input = inputs[0]; if (input.length > 0) { this.position += input[0].length; - this.port.postMessage({ 'type': 'position', 'data': this.position }); - return true; } } + + // Posting messages is expensive. Let's limit the number of posts. + if (currentTime - this.lastPostTime > POST_THRESHOLD_S) { + this.lastPostTime = currentTime; + this.port.postMessage({ 'type': 'position', 'data': this.position }); + } + return true; } } diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js index 0c83e3587d8..c5fa7d5f0fa 100644 --- a/platform/web/js/libs/library_godot_audio.js +++ b/platform/web/js/libs/library_godot_audio.js @@ -460,7 +460,11 @@ class SampleNode { const sampleNodeBus = this.getSampleNodeBus(bus); sampleNodeBus.setVolume(options.volume); - this.connectPositionWorklet(options.start); + this.connectPositionWorklet(options.start).catch((err) => { + const newErr = new Error('Failed to create PositionWorklet.'); + newErr.cause = err; + GodotRuntime.error(newErr); + }); } /** @@ -612,44 +616,34 @@ class SampleNode { * Sets up and connects the source to the GodotPositionReportingProcessor * If the worklet module is not loaded in, it will be added */ - connectPositionWorklet(start) { - try { - this._positionWorklet = this.createPositionWorklet(); - this._source.connect(this._positionWorklet); - if (start) { - this.start(); - } - } catch (error) { - if (error?.name !== 'InvalidStateError') { - throw error; - } - const path = GodotConfig.locate_file('godot.audio.position.worklet.js'); - GodotAudio.ctx.audioWorklet - .addModule(path) - .then(() => { - if (!this.isCanceled) { - this._positionWorklet = this.createPositionWorklet(); - this._source.connect(this._positionWorklet); - if (start) { - this.start(); - } - } - }).catch((addModuleError) => { - GodotRuntime.error('Failed to create PositionWorklet.', addModuleError); - }); + async connectPositionWorklet(start) { + await GodotAudio.audioPositionWorkletPromise; + if (this.isCanceled) { + return; + } + this.getPositionWorklet(); + this._source.connect(this._positionWorklet); + if (start) { + this.start(); } } /** - * Creates the AudioWorkletProcessor used to track playback position. - * @returns {AudioWorkletNode} + * Get a AudioWorkletProcessor from the pool, or create one if no processor is available. */ - createPositionWorklet() { - const worklet = new AudioWorkletNode( - GodotAudio.ctx, - 'godot-position-reporting-processor' - ); - worklet.port.onmessage = (event) => { + getPositionWorklet() { + if (this._positionWorklet != null) { + return; + } + if (GodotAudio.audioPositionWorkletPool.length == 0) { + this._positionWorklet = new AudioWorkletNode( + GodotAudio.ctx, + 'godot-position-reporting-processor' + ); + } else { + this._positionWorklet = GodotAudio.audioPositionWorkletPool.pop(); + } + this._positionWorklet.port.onmessage = (event) => { switch (event.data['type']) { case 'position': this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset; @@ -658,7 +652,6 @@ class SampleNode { // Do nothing. } }; - return worklet; } /** @@ -688,6 +681,8 @@ class SampleNode { if (this._positionWorklet) { this._positionWorklet.disconnect(); this._positionWorklet.port.onmessage = null; + this._positionWorklet.port.postMessage({ type: 'clear' }); + GodotAudio.audioPositionWorkletPool.push(this._positionWorklet); this._positionWorklet = null; } @@ -731,7 +726,10 @@ class SampleNode { const pauseTime = this.isPaused ? this.pauseTime : 0; - this.connectPositionWorklet(); + if (this._positionWorklet != null) { + this._positionWorklet.port.postMessage({ type: 'clear' }); + this._source.connect(this._positionWorklet); + } this._source.start(this.startTime, this.offset + pauseTime); this.isStarted = true; } @@ -1199,6 +1197,10 @@ const _GodotAudio = { driver: null, interval: 0, + /** @type {Promise} */ + audioPositionWorkletPromise: null, + audioPositionWorkletPool: [], + /** * Converts linear volume to Db. * @param {number} linear Linear value to convert. @@ -1265,6 +1267,10 @@ const _GodotAudio = { onlatencyupdate(computed_latency); }, 1000); GodotOS.atexit(GodotAudio.close_async); + + const path = GodotConfig.locate_file('godot.audio.position.worklet.js'); + GodotAudio.audioPositionWorkletPromise = ctx.audioWorklet.addModule(path); + return ctx.destination.channelCount; },