fix: address Phase 3 review — Logger type export, PlayerEvents interface, PCM backpressure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.6 committed 2026-03-30 00:37:16 +08:00
1 parent 5afc8c5224
commit 17d2decc91
3 files changed
+30 -7

No files matched your search

+24 -3
View File
@@ -1,10 +1,19 @@
import { spawn, type ChildProcess } from "node:child_process"; import { spawn, type ChildProcess } from "node:child_process";
import { EventEmitter } from "node:events"; import { EventEmitter } from "node:events";
import { createOpusEncoder, PCM_FRAME_BYTES, type Encoder } from "./encoder.js"; import { createOpusEncoder, PCM_FRAME_BYTES, type Encoder } from "./encoder.js";
import type pino from "pino"; import type { Logger } from "../logger.js";
export interface PlayerEvents {
frame: (opusFrame: Buffer) => void;
trackEnd: () => void;
error: (err: Error) => void;
}
export type PlayerState = "idle" | "playing" | "paused"; export type PlayerState = "idle" | "playing" | "paused";
// ~5 seconds of 48kHz stereo 16-bit audio
const PCM_HIGH_WATER_MARK = PCM_FRAME_BYTES * 250;
export class AudioPlayer extends EventEmitter { export class AudioPlayer extends EventEmitter {
private ffmpeg: ChildProcess | null = null; private ffmpeg: ChildProcess | null = null;
private encoder: Encoder; private encoder: Encoder;
@@ -12,9 +21,9 @@ export class AudioPlayer extends EventEmitter {
private volume = 75; // 0-100 private volume = 75; // 0-100
private frameTimer: ReturnType<typeof setInterval> | null = null; private frameTimer: ReturnType<typeof setInterval> | null = null;
private pcmBuffer: Buffer = Buffer.alloc(0); private pcmBuffer: Buffer = Buffer.alloc(0);
private logger: pino.Logger; private logger: Logger;
constructor(logger: pino.Logger) { constructor(logger: Logger) {
super(); super();
this.encoder = createOpusEncoder(); this.encoder = createOpusEncoder();
this.logger = logger; this.logger = logger;
@@ -43,6 +52,10 @@ export class AudioPlayer extends EventEmitter {
this.ffmpeg.stdout!.on("data", (chunk: Buffer) => { this.ffmpeg.stdout!.on("data", (chunk: Buffer) => {
this.pcmBuffer = Buffer.concat([this.pcmBuffer, chunk]); this.pcmBuffer = Buffer.concat([this.pcmBuffer, chunk]);
// Backpressure: pause FFmpeg if buffer is too large
if (this.pcmBuffer.length > PCM_HIGH_WATER_MARK && this.ffmpeg?.stdout) {
this.ffmpeg.stdout.pause();
}
}); });
this.ffmpeg.on("close", (code) => { this.ffmpeg.on("close", (code) => {
@@ -73,6 +86,14 @@ export class AudioPlayer extends EventEmitter {
const pcmFrame = this.pcmBuffer.subarray(0, PCM_FRAME_BYTES); const pcmFrame = this.pcmBuffer.subarray(0, PCM_FRAME_BYTES);
this.pcmBuffer = this.pcmBuffer.subarray(PCM_FRAME_BYTES); this.pcmBuffer = this.pcmBuffer.subarray(PCM_FRAME_BYTES);
// Resume FFmpeg if buffer drained below threshold
if (
this.pcmBuffer.length < PCM_HIGH_WATER_MARK / 2 &&
this.ffmpeg?.stdout?.isPaused()
) {
this.ffmpeg.stdout.resume();
}
const opusFrame = this.encoder.encode(Buffer.from(pcmFrame)); const opusFrame = this.encoder.encode(Buffer.from(pcmFrame));
this.emit("frame", opusFrame); this.emit("frame", opusFrame);
} }
+3 -1
View File
@@ -2,7 +2,9 @@ import pino from "pino";
import { mkdirSync } from "node:fs"; import { mkdirSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
export function createLogger(logDir?: string): pino.Logger { export type Logger = pino.Logger;
export function createLogger(logDir?: string): Logger {
if (!logDir) { if (!logDir) {
return pino({ level: "info" }); return pino({ level: "info" });
} }
+3 -3
View File
@@ -7,7 +7,7 @@ import {
exportIdentity, exportIdentity,
type TS3Identity, type TS3Identity,
} from "./identity.js"; } from "./identity.js";
import type pino from "pino"; import type { Logger } from "../logger.js";
export interface TS3ClientOptions { export interface TS3ClientOptions {
host: string; host: string;
@@ -33,9 +33,9 @@ export class TS3Client extends EventEmitter {
private identity: TS3Identity; private identity: TS3Identity;
private clientId = 0; private clientId = 0;
private keepAliveInterval: ReturnType<typeof setInterval> | null = null; private keepAliveInterval: ReturnType<typeof setInterval> | null = null;
private logger: pino.Logger; private logger: Logger;
constructor(private options: TS3ClientOptions, logger: pino.Logger) { constructor(private options: TS3ClientOptions, logger: Logger) {
super(); super();
this.logger = logger; this.logger = logger;