
























@@ -4,6 +4,7 @@ const TELEPHONY_CHUNK_MS = 20;
44const DEFAULT_SPEECH_RMS_THRESHOLD = 0.02;
55const DEFAULT_REQUIRED_LOUD_CHUNKS = 2;
66const DEFAULT_REQUIRED_QUIET_CHUNKS = 10;
7+const DEFAULT_MAX_QUEUED_AUDIO_BYTES = TELEPHONY_SAMPLE_RATE * 120;
78const PCM16_MAX_AMPLITUDE = 32768;
89const MULAW_LINEAR_SAMPLES = new Int16Array(256);
910@@ -27,10 +28,13 @@ export type RealtimeTwilioAudioPacerSendJson = (message: unknown) => boolean;
2728export class RealtimeTwilioAudioPacer {
2829private queue: RealtimeTwilioAudioQueueItem[] = [];
2930private timer: ReturnType<typeof setTimeout> | null = null;
31+private queuedAudioBytes = 0;
3032private closed = false;
31333234constructor(
3335private readonly params: {
36+maxQueuedAudioBytes?: number;
37+onBackpressure?: () => void;
3438sendJson: RealtimeTwilioAudioPacerSendJson;
3539streamSid: string;
3640},
@@ -40,13 +44,19 @@ export class RealtimeTwilioAudioPacer {
4044if (this.closed || muLaw.length === 0) {
4145return;
4246}
47+const maxQueuedAudioBytes = this.params.maxQueuedAudioBytes ?? DEFAULT_MAX_QUEUED_AUDIO_BYTES;
4348for (let offset = 0; offset < muLaw.length; offset += TELEPHONY_CHUNK_BYTES) {
4449const chunk = Buffer.from(muLaw.subarray(offset, offset + TELEPHONY_CHUNK_BYTES));
50+if (this.queuedAudioBytes + chunk.length > maxQueuedAudioBytes) {
51+this.failBackpressure();
52+return;
53+}
4554this.queue.push({
4655type: "audio",
4756 chunk,
4857durationMs: Math.max(1, Math.round((chunk.length / TELEPHONY_SAMPLE_RATE) * 1000)),
4958});
59+this.queuedAudioBytes += chunk.length;
5060}
5161this.ensurePump();
5262}
@@ -65,13 +75,15 @@ export class RealtimeTwilioAudioPacer {
6575}
6676this.clearTimer();
6777this.queue = [];
78+this.queuedAudioBytes = 0;
6879this.params.sendJson({ event: "clear", streamSid: this.params.streamSid });
6980}
70817182close(): void {
7283this.closed = true;
7384this.clearTimer();
7485this.queue = [];
86+this.queuedAudioBytes = 0;
7587}
76887789private clearTimer(): void {
@@ -88,6 +100,11 @@ export class RealtimeTwilioAudioPacer {
88100}
89101}
90102103+private failBackpressure(): void {
104+this.close();
105+this.params.onBackpressure?.();
106+}
107+91108private pump(): void {
92109this.timer = null;
93110if (this.closed) {
@@ -101,6 +118,7 @@ export class RealtimeTwilioAudioPacer {
101118let delayMs = 0;
102119let sent = true;
103120if (item.type === "audio") {
121+this.queuedAudioBytes = Math.max(0, this.queuedAudioBytes - item.chunk.length);
104122sent = this.params.sendJson({
105123event: "media",
106124streamSid: this.params.streamSid,
@@ -117,6 +135,7 @@ export class RealtimeTwilioAudioPacer {
117135118136if (!sent) {
119137this.queue = [];
138+this.queuedAudioBytes = 0;
120139return;
121140}
122141if (this.queue.length > 0) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。