Support audio worklets

This commit is contained in:
Mid 2025-02-12 22:25:29 +02:00
parent 9c26df784d
commit 880fd39ea9
2 changed files with 97 additions and 31 deletions

View File

@ -16,22 +16,43 @@
var CanvCtx = Canvus.getContext("2d")
var AudCtx
var AudScript
var AudScript, AudWorklet
var AudHz
function create_audio(hz, channels) {
if(AudCtx) {
AudCtx.close()
AudScript = null
AudWorklet = null
}
AudHz = hz
var DebugSine = 0
AudCtx = new AudioContext({sampleRate: hz})
AudScript = AudCtx.createScriptProcessor(1024, channels, channels)
if(AudCtx.audioWorklet) {
AudCtx.audioWorklet.addModule("rawpcmworklet.js").then(function() {
AudWorklet = new AudioWorkletNode(AudCtx, "rawpcmworklet", {
outputChannelCount: [2]
})
AudWorklet.connect(AudCtx.destination)
})
} else {
AudScript = AudCtx.createScriptProcessor(4096, channels, channels)
AudScript.onaudioprocess = function(e) {
var outL = e.outputBuffer.getChannelData(0)
var outR = channels > 1 ? e.outputBuffer.getChannelData(1) : null
/*for(var i = 0; i < outL.length; i++) {
outL[i] = Math.sin(440 * 2 * 3.14159 * (DebugSine / AudHz))
DebugSine++
}
AudioQueue = []
return*/
var leftToWrite = outL.length
var offset = 0
@ -51,13 +72,10 @@
leftToWrite -= amount
offset += amount
}
if(RenderStartTime && leftToWrite) {
buffering(1000)
}
}
AudScript.connect(AudCtx.destination)
}
}
var LastControlsInterrupt
function interruptcontrols() {
@ -149,11 +167,6 @@
}
var VideoBufferingOffset = 0
function buffering(millis) {
//var silence = new Float32Array(millis * 48);
//AudioQueue.push({left: silence, right: silence})
//VideoBufferingOffset += millis
}
function toHex(buffer) {
return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join('');
@ -386,6 +399,12 @@
ws.onmessage = function(ev) {
ebml.poosh(new Uint8Array(ev.data))
ebml.parse()
// It would make more sense for this to be in `render` but we need the guarantee that this will run when the tab is out of focus
if(AudCtx.state == "running" && AudWorklet && AudioQueue.length) {
AudWorklet.port.postMessage({msg: "data", audio: AudioQueue})
AudioQueue.length = 0
}
}
ws.onclose = function(ev) {
setTimeout(reconnect_ws, 5000)

47
rawpcmworklet.js Normal file
View File

@ -0,0 +1,47 @@
// To explain succinctly, the people who designed AudioWorklet and
// deprecated ScriptProcessorNode are retarded and we need a worklet
// that does basically nothing to get glitchless audio.
class RawPCMWorklet extends AudioWorkletProcessor {
constructor() {
super()
this.left = new Float32Array()
this.right = new Float32Array()
this.port.onmessage = (event) => {
var newaudioframes = event.data.audio
for(var i = 0; i < newaudioframes.length; i++) {
var newleft = new Float32Array(this.left.length + newaudioframes[i].left.length)
newleft.set(this.left, 0)
newleft.set(newaudioframes[i].left, this.left.length)
this.left = newleft
var newright = new Float32Array(this.right.length + newaudioframes[i].right.length)
newright.set(this.right, 0)
newright.set(newaudioframes[i].right, this.right.length)
this.right = newright
}
}
}
process(inputs, outputs, parameters) {
const output = outputs[0]
var left = output[0]
var right = output[1]
var available = Math.min(left.length, this.left.length)
left.set(this.left.slice(0, available))
right.set(this.right.slice(0, available))
this.left = this.left.slice(available)
this.right = this.right.slice(available)
return true;
}
}
registerProcessor('rawpcmworklet', RawPCMWorklet);