88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
import prism from "prism-media";
|
|
import { config } from "../config";
|
|
|
|
export interface OpusDecoderOptions {
|
|
cooldownMs: number;
|
|
rotateMs: number;
|
|
createDecoder?: () => prism.opus.Decoder;
|
|
onData: (pcm: Buffer) => void;
|
|
}
|
|
|
|
export class OpusDecoder {
|
|
private decoder: prism.opus.Decoder | null = null;
|
|
private disabledUntil = 0;
|
|
private createdAt = 0;
|
|
private readonly cooldownMs: number;
|
|
private readonly rotateMs: number;
|
|
private readonly createDecoderFn: () => prism.opus.Decoder;
|
|
private readonly onData: (pcm: Buffer) => void;
|
|
|
|
constructor(options: OpusDecoderOptions) {
|
|
this.cooldownMs = options.cooldownMs;
|
|
this.rotateMs = options.rotateMs;
|
|
this.onData = options.onData;
|
|
this.createDecoderFn =
|
|
options.createDecoder ??
|
|
(() =>
|
|
new prism.opus.Decoder({
|
|
frameSize: config.opusFrameSize,
|
|
channels: config.audioChannels as 1 | 2,
|
|
rate: config.audioSampleRate as 8000 | 12000 | 16000 | 24000 | 48000,
|
|
}));
|
|
}
|
|
|
|
rotateIfNeeded(): void {
|
|
if (!this.decoder || this.rotateMs <= 0) return;
|
|
if (Date.now() - this.createdAt < this.rotateMs) return;
|
|
this.destroy();
|
|
this.ensureDecoder();
|
|
}
|
|
|
|
write(chunk: Buffer): void {
|
|
const decoder = this.ensureDecoder();
|
|
if (!decoder) return;
|
|
try {
|
|
decoder.write(chunk);
|
|
} catch (error) {
|
|
console.warn(
|
|
"[recorder] Opus decoder write failed, cooling down:",
|
|
error,
|
|
);
|
|
this.coolDown();
|
|
}
|
|
}
|
|
|
|
destroy(): void {
|
|
if (!this.decoder) return;
|
|
this.decoder.removeAllListeners();
|
|
this.decoder.destroy();
|
|
this.decoder = null;
|
|
this.createdAt = 0;
|
|
}
|
|
|
|
private ensureDecoder(): prism.opus.Decoder | null {
|
|
if (this.decoder) return this.decoder;
|
|
if (Date.now() < this.disabledUntil) return null;
|
|
try {
|
|
const decoder = this.createDecoderFn();
|
|
decoder.on("data", this.onData);
|
|
decoder.on("error", (error) => {
|
|
console.warn("[recorder] Opus decoder error, cooling down:", error);
|
|
this.coolDown();
|
|
});
|
|
this.decoder = decoder;
|
|
this.createdAt = Date.now();
|
|
return decoder;
|
|
} catch (error) {
|
|
console.warn("[recorder] Opus decoder init failed, cooling down:", error);
|
|
this.disabledUntil = Date.now() + this.cooldownMs;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private coolDown(): void {
|
|
this.disabledUntil = Date.now() + this.cooldownMs;
|
|
this.destroy();
|
|
}
|
|
}
|