Files
openai--openai-agents-python/examples/realtime/app/static/audio-playback.worklet.js
T
2026-07-13 12:39:17 +08:00

121 lines
3.7 KiB
JavaScript

class PCMPlaybackProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.buffers = [];
this.currentBuffer = null;
this.currentIndex = 0;
this.isCurrentlyPlaying = false;
this.fadeSamples = Math.round(sampleRate * 0.02);
this.port.onmessage = (event) => {
const message = event.data;
if (!message || typeof message !== 'object') return;
if (message.type === 'chunk') {
const payload = message.payload;
if (!(payload instanceof ArrayBuffer)) {
return;
}
const int16Data = new Int16Array(payload);
if (int16Data.length === 0) {
return;
}
const scale = 1 / 32768;
const floatData = new Float32Array(int16Data.length);
for (let i = 0; i < int16Data.length; i++) {
floatData[i] = Math.max(-1, Math.min(1, int16Data[i] * scale));
}
if (!this.hasPendingAudio()) {
const fadeSamples = Math.min(this.fadeSamples, floatData.length);
for (let i = 0; i < fadeSamples; i++) {
const gain = fadeSamples <= 1 ? 1 : (i / fadeSamples);
floatData[i] *= gain;
}
}
this.buffers.push(floatData);
} else if (message.type === 'stop') {
this.reset();
this.port.postMessage({ type: 'drained' });
} else if (message.type === 'config') {
const fadeSamples = message.fadeSamples;
if (Number.isFinite(fadeSamples) && fadeSamples >= 0) {
this.fadeSamples = fadeSamples >>> 0;
}
}
};
}
reset() {
this.buffers = [];
this.currentBuffer = null;
this.currentIndex = 0;
this.isCurrentlyPlaying = false;
}
hasPendingAudio() {
if (this.currentBuffer && this.currentIndex < this.currentBuffer.length) {
return true;
}
return this.buffers.length > 0;
}
pullSample() {
if (this.currentBuffer && this.currentIndex < this.currentBuffer.length) {
return this.currentBuffer[this.currentIndex++];
}
if (this.currentBuffer && this.currentIndex >= this.currentBuffer.length) {
this.currentBuffer = null;
this.currentIndex = 0;
}
while (this.buffers.length > 0) {
this.currentBuffer = this.buffers.shift();
this.currentIndex = 0;
if (this.currentBuffer && this.currentBuffer.length > 0) {
return this.currentBuffer[this.currentIndex++];
}
}
this.currentBuffer = null;
this.currentIndex = 0;
return 0;
}
process(inputs, outputs) {
const output = outputs[0];
if (!output || output.length === 0) {
return true;
}
const channel = output[0];
let wroteSamples = false;
for (let i = 0; i < channel.length; i++) {
const sample = this.pullSample();
channel[i] = sample;
if (sample !== 0) {
wroteSamples = true;
}
}
if (this.hasPendingAudio()) {
this.isCurrentlyPlaying = true;
} else if (!wroteSamples && this.isCurrentlyPlaying) {
this.isCurrentlyPlaying = false;
this.port.postMessage({ type: 'drained' });
}
return true;
}
}
registerProcessor('pcm-playback', PCMPlaybackProcessor);