Dado que una llamada a AudioWorkletProcessor.process
es síncrona según https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/process , ¿qué sucede cuando una ejecución tarda demasiado tiempo, más de 1 segundo, ¿por ejemplo? ¿Se omitirán algunas muestras de audio en una próxima llamada? ¿O las muestras estarían en cola en algún lugar? No pude encontrar una documentación sobre esto.
Un AudioContext
en tiempo real suele estar ajustado a un determinado dispositivo de salida de audio físico. Por lo tanto, el currentTime
de un AudioContext
está controlado por el reloj de hardware de ese dispositivo de salida de audio.
Si el AudioContext
de alguna manera no entrega las muestras a tiempo, el resultado será el silencio. Por lo general, eso se puede escuchar como un clic si de repente hay un poco de silencio donde no debería estar. Las muestras que tardaron demasiado en renderizarse se utilizarán para el próximo cuanto de renderizado si es que están listas para entonces.
Sin embargo, hay una diferencia en la forma en que los navegadores avanzan el tiempo currentTime
en caso de que algunas muestras no se puedan entregar a tiempo. Firefox parece no contar las muestras perdidas, mientras que Chrome avanza en el tiempo contando todas las muestras que el hardware de audio escupe, incluidas aquellas que no se pudieron procesar a tiempo.
Para probar esto, creé un AudioWorkletProcessor
que se puede pausar o bloquear desde el hilo principal.
class BlockableProcessor extends AudioWorkletProcessor { constructor () { super(); this.int32Array = null; this.port.onmessage = ({ data }) => this.int32Array = data; } process() { if (this.int32Array !== null) { while (Atomics.load(this.int32Array, 0) === 0) { Atomics.store(this.int32Array, 1, currentTime); } Atomics.store(this.int32Array, 1, currentTime); } return true; } } registerProcessor('blockable-processor', BlockableProcessor);
Este BlockableProcessor
espera recibir un SharedArrayBuffer
que utiliza para verificar si debe bloquear la función process()
o no. Y también lo usa para comunicar el tiempo currentTime
al hilo principal.
Se puede usar así:
const audioWorkletNode = new AudioWorkletNode( audioContext, 'blockable-processor' ); const sharedArrayBuffer = new SharedArrayBuffer(8); const int32Array = new Int32Array(sharedArrayBuffer); Atomics.store(int32Array, 0, 1); Atomics.store(int32Array, 1, 0); audioWorkletNode.port.postMessage(int32Array); audioWorkletNode.connect(audioContext.destination);
Se puede bloquear configurando el primer valor de SharedArrayBuffer
en 0.
Atomics.store(int32Array, 0, 0);
Y así mismo se puede volver a desbloquear poniéndolo a 1 de nuevo.
Atomics.store(int32Array, 0, 1);
También es posible leer el AudioWorkletProcessor
currentTime
el subproceso principal.
Atomics.load(int32Array, 1);
Con esta configuración, pude ver que Chrome (v95) y Firefox (v93) detienen la progresión del tiempo en el subproceso principal y en el worklet en caso de que el procesador esté bloqueado.
Cuando se desbloquea, Firefox continúa donde se detuvo y Chrome continúa donde debería estar si todo hubiera ido como se esperaba.