@bililive-tools/manager
Version:
Batch scheduling recorders
148 lines (147 loc) • 5.11 kB
JavaScript
import EventEmitter from "node:events";
import fs from "fs-extra";
import { createRecordExtraDataController } from "./record_extra_data_controller.js";
import { replaceExtName, ensureFolderExist, isFfmpegStartSegment, isFfmpegStart } from "./utils.js";
export class Segment extends EventEmitter {
extraDataController = null;
init = true;
getSavePath;
/** 原始的ffmpeg文件名,用于重命名 */
rawRecordingVideoPath;
/** 输出文件名名,不包含拓展名 */
outputVideoFilePath;
disableDanma;
videoExt;
constructor(getSavePath, disableDanma, videoExt) {
super();
this.getSavePath = getSavePath;
this.disableDanma = disableDanma;
this.videoExt = videoExt;
}
async handleSegmentEnd() {
if (!this.outputVideoFilePath) {
this.emit("DebugLog", {
type: "common",
text: "Should call onSegmentStart first",
});
return;
}
try {
await Promise.all([
fs.rename(this.rawRecordingVideoPath, this.outputFilePath),
this.extraDataController?.flush(),
]);
this.emit("videoFileCompleted", { filename: this.outputFilePath });
}
catch (err) {
this.emit("DebugLog", {
type: "common",
text: "videoFileCompleted error " + String(err),
});
}
}
async onSegmentStart(stderrLine) {
if (!this.init) {
await this.handleSegmentEnd();
}
this.init = false;
const startTime = Date.now();
this.outputVideoFilePath = this.getSavePath({
startTime: startTime,
});
ensureFolderExist(this.outputVideoFilePath);
if (!this.disableDanma) {
this.extraDataController = createRecordExtraDataController(`${this.outputVideoFilePath}.json`);
}
const regex = /'([^']+)'/;
const match = stderrLine.match(regex);
if (match) {
const filename = match[1];
this.rawRecordingVideoPath = filename;
this.emit("videoFileCreated", { filename: this.outputFilePath });
}
else {
this.emit("DebugLog", { type: "ffmpeg", text: "No match found" });
}
}
get outputFilePath() {
return `${this.outputVideoFilePath}.${this.videoExt}`;
}
}
export class StreamManager extends EventEmitter {
segment = null;
extraDataController = null;
recordSavePath;
recordStartTime;
videoFormat;
constructor(getSavePath, hasSegment, disableDanma, videoFormat) {
super();
const recordSavePath = getSavePath({ startTime: Date.now() });
this.recordSavePath = recordSavePath;
this.videoFormat = videoFormat;
if (hasSegment) {
this.segment = new Segment(getSavePath, disableDanma, this.videoExt);
this.segment.on("DebugLog", (data) => {
this.emit("DebugLog", data);
});
this.segment.on("videoFileCreated", (data) => {
this.emit("videoFileCreated", data);
});
this.segment.on("videoFileCompleted", (data) => {
this.emit("videoFileCompleted", data);
});
}
else {
const extraDataSavePath = replaceExtName(recordSavePath, ".json");
if (!disableDanma) {
this.extraDataController = createRecordExtraDataController(extraDataSavePath);
}
}
}
async handleVideoStarted(stderrLine) {
if (this.segment) {
if (isFfmpegStartSegment(stderrLine)) {
await this.segment.onSegmentStart(stderrLine);
}
}
else {
// 不能直接在onStart回调进行判断,在某些情况下会链接无法录制的情况
if (isFfmpegStart(stderrLine)) {
if (this.recordStartTime)
return;
this.recordStartTime = Date.now();
this.emit("videoFileCreated", { filename: this.videoFilePath });
}
}
}
async handleVideoCompleted() {
if (this.segment) {
await this.segment.handleSegmentEnd();
}
else {
if (this.recordStartTime) {
await this.getExtraDataController()?.flush();
this.emit("videoFileCompleted", { filename: this.videoFilePath });
}
}
}
getExtraDataController() {
return this.segment?.extraDataController || this.extraDataController;
}
get videoExt() {
if (this.videoFormat === "mkv") {
return "mkv";
}
else if (this.videoFormat === "auto") {
if (!this.segment) {
return "mp4";
}
}
return "ts";
}
get videoFilePath() {
return this.segment
? `${this.recordSavePath}-PART%03d.${this.videoExt}`
: `${this.recordSavePath}.${this.videoExt}`;
}
}