Files
dc-recorder/src/recorder/segment.ts
T

88 lines
2.2 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import * as prism from "prism-media";
import type { SegmentState } from "../types";
export function buildSegmentPaths(
userDir: string,
startTime: number,
): { filename: string; jsonFilename: string } {
return {
filename: path.join(userDir, `${startTime}.ogg`),
jsonFilename: path.join(userDir, `${startTime}.json`),
};
}
export function shouldRotateSegment(
startTime: number,
now: number,
recordingSegmentMs: number,
): boolean {
return recordingSegmentMs > 0 && now - startTime >= recordingSegmentMs;
}
export class SegmentManager {
private currentSegment: SegmentState | null = null;
private segmentIndex = 0;
constructor(
private readonly userDir: string,
private readonly recordingSegmentMs: number,
) {}
open(oggPacketStream: NodeJS.ReadableStream): SegmentState {
const index = this.segmentIndex++;
const startTime = Date.now();
const { filename, jsonFilename } = buildSegmentPaths(
this.userDir,
startTime,
);
const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({ channelCount: 2, sampleRate: 48000 }),
pageSizeControl: { maxPackets: 10 },
crc: true,
});
const out = fs.createWriteStream(filename);
oggPacketStream.pipe(oggStream).pipe(out);
this.currentSegment = {
index,
startTime,
endTime: null,
filename,
jsonFilename,
oggStream,
out,
};
return this.currentSegment;
}
close(oggPacketStream: NodeJS.ReadableStream): SegmentState | null {
if (!this.currentSegment) return null;
const segment = this.currentSegment;
segment.endTime = Date.now();
oggPacketStream.unpipe(segment.oggStream);
segment.oggStream.end();
this.currentSegment = null;
return segment;
}
rotateIfNeeded(oggPacketStream: NodeJS.ReadableStream): SegmentState | null {
if (!this.currentSegment) return null;
if (
!shouldRotateSegment(
this.currentSegment.startTime,
Date.now(),
this.recordingSegmentMs,
)
)
return null;
this.close(oggPacketStream);
return this.open(oggPacketStream);
}
getCurrent(): SegmentState | null {
return this.currentSegment;
}
}