@bililive-tools/manager 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,7 @@
17
17
  | B站 | `@bililive-tools/bilibili-recorder` |
18
18
  | 斗鱼 | `@bililive-tools/douyu-recorder` |
19
19
  | 虎牙 | `@bililive-tools/huya-recorder` |
20
+ | 抖音 | `@bililive-tools/douyin-recorder` |
20
21
 
21
22
  # 使用
22
23
 
@@ -30,6 +31,8 @@ const manager = createRecorderManager({
30
31
  providers: [provider],
31
32
  savePathRule: "D:\\录制\\{platforme}}/{owner}/{year}-{month}-{date} {hour}-{min}-{sec} {title}", // 保存路径,占位符见文档,支持 [ejs](https://ejs.co/) 模板引擎
32
33
  autoCheckInterval: 1000 * 60, // 自动检查间隔,单位秒
34
+ maxThreadCount: 3, // 检查并发数
35
+ waitTime: 0, // 检查后等待时间
33
36
  autoRemoveSystemReservedChars: true, // 移除系统非法字符串
34
37
  biliBatchQuery: false, // B站检查使用批量接口
35
38
  });
@@ -75,13 +78,16 @@ manager.startCheckLoop();
75
78
  ### setFFMPEGPath & setMesioPath
76
79
 
77
80
  ```ts
78
- import { setFFMPEGPath, setMesioPath } from "@bililive-tools/manager";
81
+ import { setFFMPEGPath, setMesioPath, setBililivePath } from "@bililive-tools/manager";
79
82
 
80
83
  // 设置ffmpeg可执行路径
81
84
  setFFMPEGPath("ffmpeg.exe");
82
85
 
83
86
  // 设置mesio可执行文件路径
84
87
  setMesioPath("mesio.exe");
88
+
89
+ // 设置录播姬录制器的可执行文件路径
90
+ setBililivePath("BililiveRecorder.Cli.exe");
85
91
  ```
86
92
 
87
93
  ## savePathRule 占位符参数
package/lib/index.d.ts CHANGED
@@ -18,7 +18,9 @@ export declare function defaultToJSON<E extends AnyObject>(provider: RecorderPro
18
18
  export declare function genRecorderUUID(): Recorder["id"];
19
19
  export declare function genRecordUUID(): RecordHandle["id"];
20
20
  export declare function setFFMPEGPath(newPath: string): void;
21
- export declare const createFFMPEGBuilder: (input?: string | import("stream").Readable | undefined, options?: ffmpeg.FfmpegCommandOptions | undefined) => ffmpeg.FfmpegCommand;
21
+ export declare const createFFMPEGBuilder: (...args: Parameters<typeof ffmpeg>) => ffmpeg.FfmpegCommand;
22
22
  export declare function setMesioPath(newPath: string): void;
23
23
  export declare function getMesioPath(): string;
24
+ export declare function setBililivePath(newPath: string): void;
25
+ export declare function getBililivePath(): string;
24
26
  export declare function getDataFolderPath<E extends AnyObject>(provider: RecorderProvider<E>): string;
package/lib/index.js CHANGED
@@ -64,6 +64,13 @@ export function setMesioPath(newPath) {
64
64
  export function getMesioPath() {
65
65
  return mesioPath;
66
66
  }
67
+ let bililivePath = "BililiveRecorder.Cli";
68
+ export function setBililivePath(newPath) {
69
+ bililivePath = newPath;
70
+ }
71
+ export function getBililivePath() {
72
+ return bililivePath;
73
+ }
67
74
  export function getDataFolderPath(provider) {
68
75
  return "./" + provider.id;
69
76
  }
package/lib/manager.d.ts CHANGED
@@ -13,14 +13,14 @@ export interface RecorderProvider<E extends AnyObject> {
13
13
  id: ChannelId;
14
14
  title: string;
15
15
  owner: string;
16
- uid?: number;
16
+ uid?: number | string;
17
17
  avatar?: string;
18
18
  } | null>;
19
19
  createRecorder: (this: RecorderProvider<E>, opts: Omit<RecorderCreateOpts<E>, "providerId">) => Recorder<E>;
20
20
  fromJSON: <T extends SerializedRecorder<E>>(this: RecorderProvider<E>, json: T) => Recorder<E>;
21
21
  setFFMPEGOutputArgs: (this: RecorderProvider<E>, args: string[]) => void;
22
22
  }
23
- declare const configurableProps: readonly ["savePathRule", "autoRemoveSystemReservedChars", "autoCheckInterval", "ffmpegOutputArgs", "biliBatchQuery", "recordRetryImmediately"];
23
+ declare const configurableProps: readonly ["savePathRule", "autoRemoveSystemReservedChars", "autoCheckInterval", "maxThreadCount", "waitTime", "ffmpegOutputArgs", "biliBatchQuery", "recordRetryImmediately"];
24
24
  type ConfigurableProp = (typeof configurableProps)[number];
25
25
  export interface RecorderManager<ME extends UnknownObject, P extends RecorderProvider<AnyObject> = RecorderProvider<UnknownObject>, PE extends AnyObject = GetProviderExtra<P>, E extends AnyObject = ME & PE> extends Emitter<{
26
26
  error: {
@@ -39,6 +39,7 @@ export interface RecorderManager<ME extends UnknownObject, P extends RecorderPro
39
39
  recorder: SerializedRecorder<E>;
40
40
  filename: string;
41
41
  cover?: string;
42
+ rawFilename?: string;
42
43
  };
43
44
  videoFileCompleted: {
44
45
  recorder: SerializedRecorder<E>;
@@ -80,6 +81,8 @@ export interface RecorderManager<ME extends UnknownObject, P extends RecorderPro
80
81
  stopRecord: (this: RecorderManager<ME, P, PE, E>, id: string) => Promise<Recorder<E> | undefined>;
81
82
  cutRecord: (this: RecorderManager<ME, P, PE, E>, id: string) => Promise<Recorder<E> | undefined>;
82
83
  autoCheckInterval: number;
84
+ maxThreadCount: number;
85
+ waitTime: number;
83
86
  isCheckLoopRunning: boolean;
84
87
  startCheckLoop: (this: RecorderManager<ME, P, PE, E>) => void;
85
88
  stopCheckLoop: (this: RecorderManager<ME, P, PE, E>) => void;
package/lib/manager.js CHANGED
@@ -4,13 +4,15 @@ import ejs from "ejs";
4
4
  import { omit, range } from "lodash-es";
5
5
  import { parseArgsStringToArgv } from "string-argv";
6
6
  import { getBiliStatusInfoByRoomIds } from "./api.js";
7
- import { formatDate, removeSystemReservedChars, formatTemplate, replaceExtName, downloadImage, isBetweenTimeRange, } from "./utils.js";
7
+ import { formatDate, removeSystemReservedChars, formatTemplate, replaceExtName, downloadImage, isBetweenTimeRange, sleep, } from "./utils.js";
8
8
  import { StreamManager } from "./recorder/streamManager.js";
9
9
  import { Cache } from "./cache.js";
10
10
  const configurableProps = [
11
11
  "savePathRule",
12
12
  "autoRemoveSystemReservedChars",
13
13
  "autoCheckInterval",
14
+ "maxThreadCount",
15
+ "waitTime",
14
16
  "ffmpegOutputArgs",
15
17
  "biliBatchQuery",
16
18
  "recordRetryImmediately",
@@ -38,7 +40,6 @@ export function createRecorderManager(opts) {
38
40
  }
39
41
  }
40
42
  };
41
- const maxThreadCount = 3;
42
43
  // 这里暂时不打算用 state == recording 来过滤,provider 必须内部自己处理录制过程中的 check,
43
44
  // 这样可以防止一些意外调用 checkLiveStatusAndRecord 时出现重复录制。
44
45
  let needCheckRecorders = recorders
@@ -75,10 +76,13 @@ export function createRecorderManager(opts) {
75
76
  banLiveId,
76
77
  });
77
78
  };
78
- threads = threads.concat(range(0, maxThreadCount).map(async () => {
79
+ threads = threads.concat(range(0, manager.maxThreadCount).map(async () => {
79
80
  while (needCheckRecorders.length > 0) {
80
81
  try {
81
82
  await checkOnce();
83
+ if (manager.waitTime > 0) {
84
+ await sleep(manager.waitTime);
85
+ }
82
86
  }
83
87
  catch (err) {
84
88
  manager.emit("error", { source: "checkOnceInThread", err });
@@ -116,12 +120,12 @@ export function createRecorderManager(opts) {
116
120
  this.recorders.push(recorder);
117
121
  recorder.on("RecordStart", (recordHandle) => this.emit("RecordStart", { recorder: recorder.toJSON(), recordHandle }));
118
122
  recorder.on("RecordSegment", (recordHandle) => this.emit("RecordSegment", { recorder: recorder.toJSON(), recordHandle }));
119
- recorder.on("videoFileCreated", ({ filename, cover }) => {
123
+ recorder.on("videoFileCreated", ({ filename, cover, rawFilename }) => {
120
124
  if (recorder.saveCover && recorder?.liveInfo?.cover) {
121
125
  const coverPath = replaceExtName(filename, ".jpg");
122
126
  downloadImage(cover ?? recorder?.liveInfo?.cover, coverPath);
123
127
  }
124
- this.emit("videoFileCreated", { recorder: recorder.toJSON(), filename });
128
+ this.emit("videoFileCreated", { recorder: recorder.toJSON(), filename, rawFilename });
125
129
  });
126
130
  recorder.on("videoFileCompleted", ({ filename }) => this.emit("videoFileCompleted", { recorder: recorder.toJSON(), filename }));
127
131
  recorder.on("Message", (message) => this.emit("Message", { recorder: recorder.toJSON(), message }));
@@ -227,6 +231,8 @@ export function createRecorderManager(opts) {
227
231
  return recorder;
228
232
  },
229
233
  autoCheckInterval: opts.autoCheckInterval ?? 1000,
234
+ maxThreadCount: opts.maxThreadCount ?? 3,
235
+ waitTime: opts.waitTime ?? 0,
230
236
  isCheckLoopRunning: false,
231
237
  startCheckLoop() {
232
238
  if (this.isCheckLoopRunning)
@@ -0,0 +1,50 @@
1
+ import EventEmitter from "node:events";
2
+ import { IRecorder, BililiveRecorderOptions } from "./IRecorder.js";
3
+ declare class BililiveRecorderCommand extends EventEmitter {
4
+ private _input;
5
+ private _output;
6
+ private _inputOptions;
7
+ private process;
8
+ constructor();
9
+ input(source: string): BililiveRecorderCommand;
10
+ output(target: string): BililiveRecorderCommand;
11
+ inputOptions(options: string[]): BililiveRecorderCommand;
12
+ inputOptions(...options: string[]): BililiveRecorderCommand;
13
+ _getArguments(): string[];
14
+ run(): void;
15
+ kill(signal?: NodeJS.Signals): void;
16
+ }
17
+ export declare const createBililiveBuilder: () => BililiveRecorderCommand;
18
+ export declare class BililiveRecorder extends EventEmitter implements IRecorder {
19
+ private onEnd;
20
+ private onUpdateLiveInfo;
21
+ type: "bililive";
22
+ private command;
23
+ private streamManager;
24
+ readonly hasSegment: boolean;
25
+ readonly getSavePath: (data: {
26
+ startTime: number;
27
+ title?: string;
28
+ }) => string;
29
+ readonly segment: number;
30
+ readonly inputOptions: string[];
31
+ readonly disableDanma: boolean;
32
+ readonly url: string;
33
+ readonly debugLevel: "none" | "basic" | "verbose";
34
+ readonly headers: {
35
+ [key: string]: string | undefined;
36
+ } | undefined;
37
+ constructor(opts: BililiveRecorderOptions, onEnd: (...args: unknown[]) => void, onUpdateLiveInfo: () => Promise<{
38
+ title?: string;
39
+ cover?: string;
40
+ }>);
41
+ createCommand(): BililiveRecorderCommand;
42
+ formatLine(line: string): {
43
+ time: string | null;
44
+ } | null;
45
+ run(): void;
46
+ getArguments(): string[];
47
+ stop(): Promise<void>;
48
+ getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
49
+ }
50
+ export {};
@@ -0,0 +1,196 @@
1
+ import EventEmitter from "node:events";
2
+ import { spawn } from "node:child_process";
3
+ import { StreamManager, getBililivePath } from "../index.js";
4
+ // Bililive command builder class similar to ffmpeg
5
+ class BililiveRecorderCommand extends EventEmitter {
6
+ _input = "";
7
+ _output = "";
8
+ _inputOptions = [];
9
+ process = null;
10
+ constructor() {
11
+ super();
12
+ }
13
+ input(source) {
14
+ this._input = source;
15
+ return this;
16
+ }
17
+ output(target) {
18
+ this._output = target;
19
+ return this;
20
+ }
21
+ inputOptions(...options) {
22
+ const opts = Array.isArray(options[0]) ? options[0] : options;
23
+ this._inputOptions.push(...opts);
24
+ return this;
25
+ }
26
+ _getArguments() {
27
+ const args = ["downloader", "-p"];
28
+ // Add input source
29
+ if (this._input) {
30
+ args.push(this._input);
31
+ }
32
+ // Add input options first
33
+ args.push(...this._inputOptions);
34
+ // Add output target
35
+ if (this._output) {
36
+ // const { dir, name } = path.parse(this._output);
37
+ // args.push("-o", dir);
38
+ args.push(this._output);
39
+ }
40
+ // args.push("-v");
41
+ return args;
42
+ }
43
+ run() {
44
+ const args = this._getArguments();
45
+ const bililiveExecutable = getBililivePath();
46
+ console.log("Starting BililiveRecorder with args:", bililiveExecutable, args);
47
+ this.process = spawn(bililiveExecutable, args, {
48
+ stdio: ["pipe", "pipe", "pipe"],
49
+ });
50
+ if (this.process.stdout) {
51
+ this.process.stdout.on("data", (data) => {
52
+ const output = data.toString();
53
+ // console.log(output);
54
+ this.emit("stderr", output);
55
+ });
56
+ }
57
+ if (this.process.stderr) {
58
+ this.process.stderr.on("data", (data) => {
59
+ const output = data.toString();
60
+ // console.error(output);
61
+ this.emit("stderr", output);
62
+ });
63
+ }
64
+ this.process.on("error", (error) => {
65
+ this.emit("error", error);
66
+ });
67
+ [];
68
+ this.process.on("close", (code) => {
69
+ if (code === 0) {
70
+ this.emit("end");
71
+ }
72
+ else {
73
+ this.emit("error", new Error(`bililive process exited with code ${code}`));
74
+ }
75
+ });
76
+ }
77
+ kill(signal = "SIGTERM") {
78
+ if (this.process) {
79
+ this.process.kill(signal);
80
+ }
81
+ }
82
+ }
83
+ // Factory function similar to createFFMPEGBuilder
84
+ export const createBililiveBuilder = () => {
85
+ return new BililiveRecorderCommand();
86
+ };
87
+ export class BililiveRecorder extends EventEmitter {
88
+ onEnd;
89
+ onUpdateLiveInfo;
90
+ type = "bililive";
91
+ command;
92
+ streamManager;
93
+ hasSegment;
94
+ getSavePath;
95
+ segment;
96
+ inputOptions = [];
97
+ disableDanma = false;
98
+ url;
99
+ debugLevel = "none";
100
+ headers;
101
+ constructor(opts, onEnd, onUpdateLiveInfo) {
102
+ super();
103
+ this.onEnd = onEnd;
104
+ this.onUpdateLiveInfo = onUpdateLiveInfo;
105
+ const hasSegment = true;
106
+ this.disableDanma = opts.disableDanma ?? false;
107
+ this.debugLevel = opts.debugLevel ?? "none";
108
+ let videoFormat = "flv";
109
+ this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "bililive", videoFormat, {
110
+ onUpdateLiveInfo: this.onUpdateLiveInfo,
111
+ });
112
+ this.hasSegment = hasSegment;
113
+ this.getSavePath = opts.getSavePath;
114
+ this.inputOptions = [];
115
+ this.url = opts.url;
116
+ this.segment = opts.segment;
117
+ this.headers = opts.headers;
118
+ this.command = this.createCommand();
119
+ this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename }) => {
120
+ this.emit("videoFileCreated", { filename, cover, rawFilename });
121
+ });
122
+ this.streamManager.on("videoFileCompleted", ({ filename }) => {
123
+ this.emit("videoFileCompleted", { filename });
124
+ });
125
+ this.streamManager.on("DebugLog", (data) => {
126
+ this.emit("DebugLog", data);
127
+ });
128
+ }
129
+ createCommand() {
130
+ const inputOptions = [
131
+ ...this.inputOptions,
132
+ "-h",
133
+ "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
134
+ ];
135
+ if (this.debugLevel === "verbose") {
136
+ inputOptions.push("-l", "Debug");
137
+ }
138
+ if (this.headers) {
139
+ Object.entries(this.headers).forEach(([key, value]) => {
140
+ if (!value)
141
+ return;
142
+ inputOptions.push("-h", `${key}: ${value}`);
143
+ });
144
+ }
145
+ if (this.hasSegment) {
146
+ inputOptions.push("-d", `${this.segment}`);
147
+ }
148
+ const command = createBililiveBuilder()
149
+ .input(this.url)
150
+ .inputOptions(inputOptions)
151
+ .output(this.streamManager.videoFilePath)
152
+ .on("error", this.onEnd)
153
+ .on("end", () => this.onEnd("finished"))
154
+ .on("stderr", async (stderrLine) => {
155
+ this.emit("DebugLog", { type: "ffmpeg", text: stderrLine });
156
+ await this.streamManager.handleVideoStarted(stderrLine);
157
+ const info = this.formatLine(stderrLine);
158
+ if (info) {
159
+ this.emit("progress", info);
160
+ }
161
+ });
162
+ return command;
163
+ }
164
+ formatLine(line) {
165
+ if (!line.includes("下载进度:")) {
166
+ return null;
167
+ }
168
+ let time = null;
169
+ const timeMatch = line.match(/录制时长:\s*([0-9:]+)\s/);
170
+ if (timeMatch) {
171
+ time = timeMatch[1];
172
+ }
173
+ return {
174
+ time,
175
+ };
176
+ }
177
+ run() {
178
+ this.command.run();
179
+ }
180
+ getArguments() {
181
+ return this.command._getArguments();
182
+ }
183
+ async stop() {
184
+ try {
185
+ // 直接发送SIGINT信号,会导致数据丢失
186
+ this.command.kill("SIGINT");
187
+ await this.streamManager.handleVideoCompleted();
188
+ }
189
+ catch (err) {
190
+ this.emit("DebugLog", { type: "error", text: String(err) });
191
+ }
192
+ }
193
+ getExtraDataController() {
194
+ return this.streamManager?.getExtraDataController();
195
+ }
196
+ }
@@ -1,42 +1,31 @@
1
1
  import EventEmitter from "node:events";
2
- export declare class FFMPEGRecorder extends EventEmitter {
2
+ import { IRecorder, FFMPEGRecorderOptions } from "./IRecorder.js";
3
+ import type { FormatName } from "./index.js";
4
+ export declare class FFMPEGRecorder extends EventEmitter implements IRecorder {
3
5
  private onEnd;
4
6
  private onUpdateLiveInfo;
7
+ type: "ffmpeg";
5
8
  private command;
6
9
  private streamManager;
7
10
  private timeoutChecker;
8
- hasSegment: boolean;
9
- getSavePath: (data: {
11
+ readonly hasSegment: boolean;
12
+ readonly getSavePath: (data: {
10
13
  startTime: number;
11
14
  title?: string;
12
15
  }) => string;
13
- segment: number;
16
+ readonly segment: number;
14
17
  ffmpegOutputOptions: string[];
15
- inputOptions: string[];
16
- isHls: boolean;
17
- disableDanma: boolean;
18
- url: string;
19
- formatName: "flv" | "ts" | "fmp4";
18
+ readonly inputOptions: string[];
19
+ readonly isHls: boolean;
20
+ readonly disableDanma: boolean;
21
+ readonly url: string;
22
+ formatName: FormatName;
20
23
  videoFormat: "ts" | "mkv" | "mp4";
21
- headers: {
24
+ readonly debugLevel: "none" | "basic" | "verbose";
25
+ readonly headers: {
22
26
  [key: string]: string | undefined;
23
27
  } | undefined;
24
- constructor(opts: {
25
- url: string;
26
- getSavePath: (data: {
27
- startTime: number;
28
- title?: string;
29
- }) => string;
30
- segment: number;
31
- outputOptions: string[];
32
- inputOptions?: string[];
33
- disableDanma?: boolean;
34
- videoFormat?: "auto" | "ts" | "mkv" | "mp4";
35
- formatName?: "flv" | "ts" | "fmp4";
36
- headers?: {
37
- [key: string]: string | undefined;
38
- };
39
- }, onEnd: (...args: unknown[]) => void, onUpdateLiveInfo: () => Promise<{
28
+ constructor(opts: FFMPEGRecorderOptions, onEnd: (...args: unknown[]) => void, onUpdateLiveInfo: () => Promise<{
40
29
  title?: string;
41
30
  cover?: string;
42
31
  }>);
@@ -47,5 +36,5 @@ export declare class FFMPEGRecorder extends EventEmitter {
47
36
  run(): void;
48
37
  getArguments(): string[];
49
38
  stop(): Promise<void>;
50
- getExtraDataController(): import("../record_extra_data_controller.js").RecordExtraDataController | null;
39
+ getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
51
40
  }
@@ -4,6 +4,7 @@ import { createInvalidStreamChecker, assert } from "../utils.js";
4
4
  export class FFMPEGRecorder extends EventEmitter {
5
5
  onEnd;
6
6
  onUpdateLiveInfo;
7
+ type = "ffmpeg";
7
8
  command;
8
9
  streamManager;
9
10
  timeoutChecker;
@@ -17,6 +18,7 @@ export class FFMPEGRecorder extends EventEmitter {
17
18
  url;
18
19
  formatName;
19
20
  videoFormat;
21
+ debugLevel = "none";
20
22
  headers;
21
23
  constructor(opts, onEnd, onUpdateLiveInfo) {
22
24
  super();
@@ -24,11 +26,8 @@ export class FFMPEGRecorder extends EventEmitter {
24
26
  this.onUpdateLiveInfo = onUpdateLiveInfo;
25
27
  const hasSegment = !!opts.segment;
26
28
  this.hasSegment = hasSegment;
27
- let formatName = "flv";
28
- if (opts.url.includes(".m3u8")) {
29
- formatName = "ts";
30
- }
31
- this.formatName = opts.formatName ?? formatName;
29
+ this.debugLevel = opts.debugLevel ?? "none";
30
+ this.formatName = opts.formatName;
32
31
  if (this.formatName === "fmp4" || this.formatName === "ts") {
33
32
  this.isHls = true;
34
33
  }
@@ -60,8 +59,8 @@ export class FFMPEGRecorder extends EventEmitter {
60
59
  this.segment = opts.segment;
61
60
  this.headers = opts.headers;
62
61
  this.command = this.createCommand();
63
- this.streamManager.on("videoFileCreated", ({ filename, cover }) => {
64
- this.emit("videoFileCreated", { filename, cover });
62
+ this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename }) => {
63
+ this.emit("videoFileCreated", { filename, cover, rawFilename });
65
64
  });
66
65
  this.streamManager.on("videoFileCompleted", ({ filename }) => {
67
66
  this.emit("videoFileCompleted", { filename });
@@ -79,6 +78,12 @@ export class FFMPEGRecorder extends EventEmitter {
79
78
  "-user_agent",
80
79
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
81
80
  ];
81
+ if (this.isHls) {
82
+ inputOptions.push(...["-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "3"]);
83
+ }
84
+ if (this.debugLevel === "verbose") {
85
+ inputOptions.push("-loglevel", "debug");
86
+ }
82
87
  if (this.headers) {
83
88
  const headers = [];
84
89
  Object.entries(this.headers).forEach(([key, value]) => {
@@ -0,0 +1,90 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { FormatName } from "./index.js";
3
+ /**
4
+ * 录制器构造函数选项的基础接口
5
+ */
6
+ export interface BaseRecorderOptions {
7
+ url: string;
8
+ getSavePath: (data: {
9
+ startTime: number;
10
+ title?: string;
11
+ }) => string;
12
+ segment: number;
13
+ inputOptions?: string[];
14
+ disableDanma?: boolean;
15
+ formatName: FormatName;
16
+ debugLevel?: "none" | "basic" | "verbose";
17
+ headers?: {
18
+ [key: string]: string | undefined;
19
+ };
20
+ videoFormat?: "auto" | "ts" | "mkv" | "mp4";
21
+ }
22
+ /**
23
+ * 录制器接口定义
24
+ */
25
+ export interface IRecorder extends EventEmitter {
26
+ type: "ffmpeg" | "mesio" | "bililive";
27
+ readonly hasSegment: boolean;
28
+ readonly segment: number;
29
+ readonly inputOptions: string[];
30
+ readonly disableDanma: boolean;
31
+ readonly url: string;
32
+ readonly headers: {
33
+ [key: string]: string | undefined;
34
+ } | undefined;
35
+ readonly getSavePath: (data: {
36
+ startTime: number;
37
+ title?: string;
38
+ }) => string;
39
+ run(): void;
40
+ stop(): Promise<void>;
41
+ getArguments(): string[];
42
+ getExtraDataController(): any;
43
+ createCommand(): any;
44
+ on(event: "videoFileCreated", listener: (data: {
45
+ filename: string;
46
+ cover?: string;
47
+ rawFilename?: string;
48
+ }) => void): this;
49
+ on(event: "videoFileCompleted", listener: (data: {
50
+ filename: string;
51
+ }) => void): this;
52
+ on(event: "DebugLog", listener: (data: {
53
+ type: string;
54
+ text: string;
55
+ }) => void): this;
56
+ on(event: "progress", listener: (info: any) => void): this;
57
+ on(event: string, listener: (...args: any[]) => void): this;
58
+ emit(event: "videoFileCreated", data: {
59
+ filename: string;
60
+ cover?: string;
61
+ rawFilename?: string;
62
+ }): boolean;
63
+ emit(event: "videoFileCompleted", data: {
64
+ filename: string;
65
+ }): boolean;
66
+ emit(event: "DebugLog", data: {
67
+ type: string;
68
+ text: string;
69
+ }): boolean;
70
+ emit(event: "progress", info: any): boolean;
71
+ emit(event: string, ...args: any[]): boolean;
72
+ }
73
+ /**
74
+ * FFMPEG录制器特定选项
75
+ */
76
+ export interface FFMPEGRecorderOptions extends BaseRecorderOptions {
77
+ outputOptions: string[];
78
+ }
79
+ /**
80
+ * Mesio录制器特定选项
81
+ */
82
+ export interface MesioRecorderOptions extends BaseRecorderOptions {
83
+ outputOptions?: string[];
84
+ }
85
+ /**
86
+ * Bililive录制器特定选项
87
+ */
88
+ export interface BililiveRecorderOptions extends BaseRecorderOptions {
89
+ outputOptions?: string[];
90
+ }
@@ -0,0 +1 @@
1
+ export {};