mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
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:
1 parent
5afc8c5224
commit
17d2decc91
3 files changed
+30
-7
No files matched your search
+24
-3
@@ -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
@@ -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" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user