@bililive-tools/manager 1.13.0 → 1.16.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 +16 -7
- package/lib/downloader/BililiveDownloader.js +5 -0
- package/lib/downloader/FFmpegDownloader.js +1 -1
- package/lib/downloader/IDownloader.d.ts +2 -0
- package/lib/downloader/streamManager.d.ts +1 -0
- package/lib/downloader/streamManager.js +22 -4
- package/lib/manager.d.ts +16 -1
- package/lib/manager.js +80 -30
- package/lib/recorder.d.ts +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
## 支持的平台
|
|
14
14
|
|
|
15
|
-
| 平台
|
|
16
|
-
|
|
|
17
|
-
| B站
|
|
18
|
-
| 斗鱼
|
|
19
|
-
| 虎牙
|
|
20
|
-
| 抖音
|
|
15
|
+
| 平台 | 包名 |
|
|
16
|
+
| ------ | ----------------------------------- |
|
|
17
|
+
| B站 | `@bililive-tools/bilibili-recorder` |
|
|
18
|
+
| 斗鱼 | `@bililive-tools/douyu-recorder` |
|
|
19
|
+
| 虎牙 | `@bililive-tools/huya-recorder` |
|
|
20
|
+
| 抖音 | `@bililive-tools/douyin-recorder` |
|
|
21
|
+
| 小红书 | `@bililive-tools/xhs-recorder` |
|
|
21
22
|
|
|
22
23
|
# 使用
|
|
23
24
|
|
|
@@ -32,9 +33,17 @@ const manager = createRecorderManager({
|
|
|
32
33
|
savePathRule: "D:\\录制\\{platforme}}/{owner}/{year}-{month}-{date} {hour}-{min}-{sec} {title}", // 保存路径,占位符见文档,支持 [ejs](https://ejs.co/) 模板引擎
|
|
33
34
|
autoCheckInterval: 1000 * 60, // 自动检查间隔,单位秒
|
|
34
35
|
maxThreadCount: 3, // 检查并发数
|
|
35
|
-
waitTime: 0, //
|
|
36
|
+
waitTime: 0, // 检查后等待时间,单位毫秒
|
|
36
37
|
autoRemoveSystemReservedChars: true, // 移除系统非法字符串
|
|
37
38
|
biliBatchQuery: false, // B站检查使用批量接口
|
|
39
|
+
providerCheckConfig: {
|
|
40
|
+
// Bilibili 配置:高频检查,更多线程
|
|
41
|
+
Bilibili: {
|
|
42
|
+
autoCheckInterval: 5000, // 5秒检查一次
|
|
43
|
+
maxThreadCount: 5, // 最多5个并发线程
|
|
44
|
+
waitTime: 500, // 每次检查间隔0.5秒
|
|
45
|
+
},
|
|
46
|
+
},
|
|
38
47
|
});
|
|
39
48
|
|
|
40
49
|
// 不同provider支持的参数不尽相同,具体见相关文档
|
|
@@ -194,6 +194,11 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
194
194
|
if (timeMatch) {
|
|
195
195
|
time = timeMatch[1];
|
|
196
196
|
}
|
|
197
|
+
const spaceMath = line.match(/下载进度:\s*([\d.]+\s*MB)\s*/);
|
|
198
|
+
if (spaceMath) {
|
|
199
|
+
const space = spaceMath[1];
|
|
200
|
+
time = time ? `${time} ${space}` : space;
|
|
201
|
+
}
|
|
197
202
|
return {
|
|
198
203
|
time,
|
|
199
204
|
};
|
|
@@ -11,6 +11,7 @@ export interface BaseRecorderOptions {
|
|
|
11
11
|
getSavePath: (data: {
|
|
12
12
|
startTime: number;
|
|
13
13
|
title?: string;
|
|
14
|
+
extraMs?: boolean;
|
|
14
15
|
}) => string;
|
|
15
16
|
segment: Segment;
|
|
16
17
|
inputOptions?: string[];
|
|
@@ -38,6 +39,7 @@ export interface IDownloader extends EventEmitter {
|
|
|
38
39
|
readonly getSavePath: (data: {
|
|
39
40
|
startTime: number;
|
|
40
41
|
title?: string;
|
|
42
|
+
extraMs?: boolean;
|
|
41
43
|
}) => string;
|
|
42
44
|
run(): void;
|
|
43
45
|
stop(): Promise<void>;
|
|
@@ -5,6 +5,7 @@ import type { TrueVideoFormat } from "../index.js";
|
|
|
5
5
|
export type GetSavePath = (data: {
|
|
6
6
|
startTime: number;
|
|
7
7
|
title?: string;
|
|
8
|
+
extraMs?: boolean;
|
|
8
9
|
}) => string;
|
|
9
10
|
type RecorderType = Exclude<RecorderCreateOpts["recorderType"], undefined | "auto">;
|
|
10
11
|
export declare class Segment extends EventEmitter {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
|
+
import fsSync from "fs";
|
|
3
4
|
import { createRecordExtraDataController } from "../xml_stream_controller.js";
|
|
4
5
|
import { ensureFolderExist, isFfmpegStartSegment, isMesioStartSegment, isBililiveStartSegment, isFfmpegStart, retry, cleanTerminalText, } from "../utils.js";
|
|
5
6
|
export class Segment extends EventEmitter {
|
|
@@ -64,10 +65,19 @@ export class Segment extends EventEmitter {
|
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
+
let recordSavePath = this.getSavePath({
|
|
68
69
|
startTime: startTime,
|
|
69
|
-
title: liveInfo?.title,
|
|
70
|
+
title: liveInfo?.title ? liveInfo.title : undefined,
|
|
70
71
|
});
|
|
72
|
+
// 文件重复判断
|
|
73
|
+
if (fsSync.existsSync(recordSavePath + "." + this.videoExt)) {
|
|
74
|
+
recordSavePath = this.getSavePath({
|
|
75
|
+
startTime: startTime,
|
|
76
|
+
title: liveInfo?.title,
|
|
77
|
+
extraMs: true,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
this.outputVideoFilePath = recordSavePath;
|
|
71
81
|
ensureFolderExist(this.outputVideoFilePath);
|
|
72
82
|
if (!this.disableDanma) {
|
|
73
83
|
this.extraDataController = createRecordExtraDataController(`${this.outputVideoFilePath}.xml`);
|
|
@@ -112,12 +122,19 @@ export class StreamManager extends EventEmitter {
|
|
|
112
122
|
callBack;
|
|
113
123
|
constructor(getSavePath, hasSegment, disableDanma, recorderType, videoFormat, callBack) {
|
|
114
124
|
super();
|
|
115
|
-
const
|
|
116
|
-
|
|
125
|
+
const startTime = Date.now();
|
|
126
|
+
let recordSavePath = getSavePath({ startTime });
|
|
117
127
|
this.videoFormat = videoFormat;
|
|
118
128
|
this.recorderType = recorderType;
|
|
119
129
|
this.hasSegment = hasSegment;
|
|
120
130
|
this.callBack = callBack;
|
|
131
|
+
console.log("Initial recordSavePath:", recordSavePath);
|
|
132
|
+
// 文件重复判断
|
|
133
|
+
if (fsSync.existsSync(recordSavePath + "." + videoFormat)) {
|
|
134
|
+
console.log("File already exists, generating new save path with extraMs");
|
|
135
|
+
recordSavePath = getSavePath({ startTime, extraMs: true });
|
|
136
|
+
}
|
|
137
|
+
this.recordSavePath = recordSavePath;
|
|
121
138
|
if (hasSegment) {
|
|
122
139
|
this.segment = new Segment(getSavePath, disableDanma, this.videoExt);
|
|
123
140
|
this.segment.on("DebugLog", (data) => {
|
|
@@ -131,6 +148,7 @@ export class StreamManager extends EventEmitter {
|
|
|
131
148
|
});
|
|
132
149
|
}
|
|
133
150
|
else {
|
|
151
|
+
ensureFolderExist(recordSavePath);
|
|
134
152
|
const extraDataSavePath = `${recordSavePath}.xml`;
|
|
135
153
|
if (!disableDanma) {
|
|
136
154
|
this.extraDataController = createRecordExtraDataController(extraDataSavePath);
|
package/lib/manager.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ import { RecorderCache } from "./cache.js";
|
|
|
4
4
|
import { RecorderCreateOpts, Recorder, SerializedRecorder, RecordHandle, DebugLog, Progress } from "./recorder.js";
|
|
5
5
|
import { AnyObject, UnknownObject } from "./utils.js";
|
|
6
6
|
import { StreamManager } from "./downloader/streamManager.js";
|
|
7
|
+
export interface ProviderCheckConfig {
|
|
8
|
+
/** 检查循环间隔(毫秒) */
|
|
9
|
+
autoCheckInterval?: number;
|
|
10
|
+
/** 最大并发检查线程数 */
|
|
11
|
+
maxThreadCount?: number;
|
|
12
|
+
/** 每次检查之间的等待时间(毫秒) */
|
|
13
|
+
waitTime?: number;
|
|
14
|
+
}
|
|
7
15
|
export interface RecorderProvider<E extends AnyObject> {
|
|
8
16
|
id: string;
|
|
9
17
|
name: string;
|
|
@@ -20,7 +28,7 @@ export interface RecorderProvider<E extends AnyObject> {
|
|
|
20
28
|
fromJSON: <T extends SerializedRecorder<E>>(this: RecorderProvider<E>, json: T) => Recorder<E>;
|
|
21
29
|
setFFMPEGOutputArgs: (this: RecorderProvider<E>, args: string[]) => void;
|
|
22
30
|
}
|
|
23
|
-
declare const configurableProps: readonly ["savePathRule", "autoRemoveSystemReservedChars", "autoCheckInterval", "maxThreadCount", "waitTime", "ffmpegOutputArgs", "biliBatchQuery", "recordRetryImmediately"];
|
|
31
|
+
declare const configurableProps: readonly ["savePathRule", "autoRemoveSystemReservedChars", "autoCheckInterval", "maxThreadCount", "waitTime", "ffmpegOutputArgs", "biliBatchQuery", "recordRetryImmediately", "providerCheckConfig"];
|
|
24
32
|
type ConfigurableProp = (typeof configurableProps)[number];
|
|
25
33
|
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
34
|
error: {
|
|
@@ -98,11 +106,17 @@ export interface RecorderManager<ME extends UnknownObject, P extends RecorderPro
|
|
|
98
106
|
recordRetryImmediately: boolean;
|
|
99
107
|
/** 缓存系统 */
|
|
100
108
|
cache: RecorderCache;
|
|
109
|
+
/** 每个 provider 的检查配置 */
|
|
110
|
+
providerCheckConfig: Record<string, ProviderCheckConfig>;
|
|
111
|
+
/** 获取指定 provider 的检查配置(自动 fallback 到全局配置) */
|
|
112
|
+
getProviderCheckConfig: (this: RecorderManager<ME, P, PE, E>, providerId: string) => Required<ProviderCheckConfig>;
|
|
101
113
|
}
|
|
102
114
|
export type RecorderManagerCreateOpts<ME extends AnyObject = UnknownObject, P extends RecorderProvider<AnyObject> = RecorderProvider<UnknownObject>, PE extends AnyObject = GetProviderExtra<P>, E extends AnyObject = ME & PE> = Partial<Pick<RecorderManager<ME, P, PE, E>, ConfigurableProp>> & {
|
|
103
115
|
providers: P[];
|
|
104
116
|
/** 自定义缓存实现,不提供则使用默认的内存缓存 */
|
|
105
117
|
cache?: RecorderCache;
|
|
118
|
+
/** 每个 provider 的检查配置,key 为 provider.id */
|
|
119
|
+
providerCheckConfig?: Record<string, ProviderCheckConfig>;
|
|
106
120
|
};
|
|
107
121
|
export declare function createRecorderManager<ME extends AnyObject = UnknownObject, P extends RecorderProvider<AnyObject> = RecorderProvider<UnknownObject>, PE extends AnyObject = GetProviderExtra<P>, E extends AnyObject = ME & PE>(opts: RecorderManagerCreateOpts<ME, P, PE, E>): RecorderManager<ME, P, PE, E>;
|
|
108
122
|
export declare function genSavePathFromRule<ME extends AnyObject, P extends RecorderProvider<AnyObject>, PE extends AnyObject, E extends AnyObject>(manager: RecorderManager<ME, P, PE, E>, recorder: Recorder<E>, extData: {
|
|
@@ -111,6 +125,7 @@ export declare function genSavePathFromRule<ME extends AnyObject, P extends Reco
|
|
|
111
125
|
startTime: number;
|
|
112
126
|
liveStartTime: Date;
|
|
113
127
|
recordStartTime: Date;
|
|
128
|
+
extraMs?: boolean;
|
|
114
129
|
}): string;
|
|
115
130
|
export type GetProviderExtra<P> = P extends RecorderProvider<infer E> ? E : never;
|
|
116
131
|
export { StreamManager };
|
package/lib/manager.js
CHANGED
|
@@ -16,14 +16,16 @@ const configurableProps = [
|
|
|
16
16
|
"ffmpegOutputArgs",
|
|
17
17
|
"biliBatchQuery",
|
|
18
18
|
"recordRetryImmediately",
|
|
19
|
+
"providerCheckConfig",
|
|
19
20
|
];
|
|
20
21
|
function isConfigurableProp(prop) {
|
|
21
22
|
return configurableProps.includes(prop);
|
|
22
23
|
}
|
|
23
24
|
export function createRecorderManager(opts) {
|
|
24
25
|
const recorders = [];
|
|
25
|
-
|
|
26
|
-
const
|
|
26
|
+
// 存储每个 provider 的 timer,key 为 providerId
|
|
27
|
+
const checkLoopTimers = new Map();
|
|
28
|
+
const multiThreadCheck = async (manager, providerId) => {
|
|
27
29
|
const handleBatchQuery = async (obj) => {
|
|
28
30
|
for (const recorder of recorders
|
|
29
31
|
.filter((r) => !r.disableAutoCheck)
|
|
@@ -42,15 +44,16 @@ export function createRecorderManager(opts) {
|
|
|
42
44
|
};
|
|
43
45
|
// 这里暂时不打算用 state == recording 来过滤,provider 必须内部自己处理录制过程中的 check,
|
|
44
46
|
// 这样可以防止一些意外调用 checkLiveStatusAndRecord 时出现重复录制。
|
|
45
|
-
|
|
47
|
+
const needCheckRecorders = recorders
|
|
46
48
|
.filter((r) => !r.disableAutoCheck)
|
|
47
|
-
.filter((r) => isBetweenTimeRange(r.handleTime))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
.filter((r) => isBetweenTimeRange(r.handleTime))
|
|
50
|
+
.filter((r) => r.providerId === providerId);
|
|
51
|
+
const providerConfig = manager.getProviderCheckConfig(providerId);
|
|
52
|
+
const threads = [];
|
|
53
|
+
// Bilibili 批量查询特殊处理
|
|
54
|
+
if (providerId === "Bilibili" && manager.biliBatchQuery) {
|
|
55
|
+
const biliNeedCheckRecorders = needCheckRecorders.filter((r) => r.recordHandle == null);
|
|
56
|
+
// const biliRecordingRecorders = needCheckRecorders.filter((r) => r.recordHandle != null);
|
|
54
57
|
const roomIds = biliNeedCheckRecorders.map((r) => r.channelId).map(Number);
|
|
55
58
|
try {
|
|
56
59
|
if (roomIds.length !== 0) {
|
|
@@ -61,9 +64,13 @@ export function createRecorderManager(opts) {
|
|
|
61
64
|
catch (err) {
|
|
62
65
|
manager.emit("error", { source: "getBiliStatusInfoByRoomIds", err });
|
|
63
66
|
// 如果批量查询失败,则使用单个查询
|
|
64
|
-
needCheckRecorders
|
|
67
|
+
needCheckRecorders.push(...biliNeedCheckRecorders);
|
|
65
68
|
}
|
|
69
|
+
// 正在录制的也需要检查(放回队列)
|
|
70
|
+
// needCheckRecorders.length = 0;
|
|
71
|
+
// needCheckRecorders.push(...biliRecordingRecorders);
|
|
66
72
|
}
|
|
73
|
+
// 为当前 provider 创建线程池
|
|
67
74
|
const checkOnce = async () => {
|
|
68
75
|
const recorder = needCheckRecorders.shift();
|
|
69
76
|
if (recorder == null)
|
|
@@ -76,12 +83,12 @@ export function createRecorderManager(opts) {
|
|
|
76
83
|
banLiveId,
|
|
77
84
|
});
|
|
78
85
|
};
|
|
79
|
-
threads
|
|
86
|
+
threads.push(...range(0, providerConfig.maxThreadCount).map(async () => {
|
|
80
87
|
while (needCheckRecorders.length > 0) {
|
|
81
88
|
try {
|
|
82
89
|
await checkOnce();
|
|
83
|
-
if (
|
|
84
|
-
await sleep(
|
|
90
|
+
if (providerConfig.waitTime > 0) {
|
|
91
|
+
await sleep(providerConfig.waitTime);
|
|
85
92
|
}
|
|
86
93
|
}
|
|
87
94
|
catch (err) {
|
|
@@ -183,6 +190,7 @@ export function createRecorderManager(opts) {
|
|
|
183
190
|
this.emit("RecoderLiveStart", { recorder: recorder });
|
|
184
191
|
});
|
|
185
192
|
this.emit("RecorderAdded", recorder.toJSON());
|
|
193
|
+
// startCheckLoop 会为所有注册的 provider 启动检查循环,无需在此处额外处理
|
|
186
194
|
return recorder;
|
|
187
195
|
},
|
|
188
196
|
removeRecorder(recorder) {
|
|
@@ -241,7 +249,7 @@ export function createRecorderManager(opts) {
|
|
|
241
249
|
await recorder.recordHandle.cut();
|
|
242
250
|
return recorder;
|
|
243
251
|
},
|
|
244
|
-
autoCheckInterval: opts.autoCheckInterval ??
|
|
252
|
+
autoCheckInterval: opts.autoCheckInterval ?? 60000,
|
|
245
253
|
maxThreadCount: opts.maxThreadCount ?? 3,
|
|
246
254
|
waitTime: opts.waitTime ?? 0,
|
|
247
255
|
isCheckLoopRunning: false,
|
|
@@ -250,30 +258,60 @@ export function createRecorderManager(opts) {
|
|
|
250
258
|
return;
|
|
251
259
|
this.isCheckLoopRunning = true;
|
|
252
260
|
// TODO: emit updated event
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
finally {
|
|
261
|
-
if (!this.isCheckLoopRunning) {
|
|
262
|
-
// do nothing
|
|
261
|
+
// 为每个 provider 创建独立的检查循环
|
|
262
|
+
const startProviderCheckLoop = (providerId) => {
|
|
263
|
+
const providerConfig = this.getProviderCheckConfig(providerId);
|
|
264
|
+
const checkLoop = async () => {
|
|
265
|
+
try {
|
|
266
|
+
// 只检查当前 provider 的 recorders
|
|
267
|
+
await multiThreadCheck(this, providerId);
|
|
263
268
|
}
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
catch (err) {
|
|
270
|
+
this.emit("error", { source: "multiThreadCheck", err });
|
|
266
271
|
}
|
|
267
|
-
|
|
272
|
+
finally {
|
|
273
|
+
if (!this.isCheckLoopRunning) {
|
|
274
|
+
// 停止了,清理 timer
|
|
275
|
+
const timer = checkLoopTimers.get(providerId);
|
|
276
|
+
if (timer) {
|
|
277
|
+
clearTimeout(timer);
|
|
278
|
+
checkLoopTimers.delete(providerId);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
// 检查该 provider 是否还有 recorder
|
|
283
|
+
const hasRecorders = this.recorders.some((r) => r.providerId === providerId);
|
|
284
|
+
if (hasRecorders) {
|
|
285
|
+
// 继续循环
|
|
286
|
+
const timer = setTimeout(checkLoop, providerConfig.autoCheckInterval);
|
|
287
|
+
checkLoopTimers.set(providerId, timer);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
// 没有 recorder 了,停止该 provider 的检查循环
|
|
291
|
+
// TODO: 也许不需要删除定时器
|
|
292
|
+
checkLoopTimers.delete(providerId);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
void checkLoop();
|
|
268
298
|
};
|
|
269
|
-
|
|
299
|
+
// 直接从注册的 provider 获取所有 provider IDs
|
|
300
|
+
const providerIds = this.providers.map((p) => p.id);
|
|
301
|
+
for (const providerId of providerIds) {
|
|
302
|
+
startProviderCheckLoop(providerId);
|
|
303
|
+
}
|
|
270
304
|
},
|
|
271
305
|
stopCheckLoop() {
|
|
272
306
|
if (!this.isCheckLoopRunning)
|
|
273
307
|
return;
|
|
274
308
|
this.isCheckLoopRunning = false;
|
|
275
309
|
// TODO: emit updated event
|
|
276
|
-
|
|
310
|
+
// 清理所有 provider 的 timer
|
|
311
|
+
for (const timer of checkLoopTimers.values()) {
|
|
312
|
+
clearTimeout(timer);
|
|
313
|
+
}
|
|
314
|
+
checkLoopTimers.clear();
|
|
277
315
|
},
|
|
278
316
|
savePathRule: opts.savePathRule ??
|
|
279
317
|
path.join(process.cwd(), "{platform}/{owner}/{year}-{month}-{date} {hour}-{min}-{sec} {title}"),
|
|
@@ -281,6 +319,15 @@ export function createRecorderManager(opts) {
|
|
|
281
319
|
biliBatchQuery: opts.biliBatchQuery ?? false,
|
|
282
320
|
recordRetryImmediately: opts.recordRetryImmediately ?? false,
|
|
283
321
|
cache: opts.cache ?? new RecorderCacheImpl(new MemoryCacheStore()),
|
|
322
|
+
providerCheckConfig: opts.providerCheckConfig ?? {},
|
|
323
|
+
getProviderCheckConfig(providerId) {
|
|
324
|
+
const providerConfig = this.providerCheckConfig[providerId];
|
|
325
|
+
return {
|
|
326
|
+
autoCheckInterval: providerConfig?.autoCheckInterval ?? this.autoCheckInterval,
|
|
327
|
+
maxThreadCount: providerConfig?.maxThreadCount ?? this.maxThreadCount,
|
|
328
|
+
waitTime: providerConfig?.waitTime ?? this.waitTime,
|
|
329
|
+
};
|
|
330
|
+
},
|
|
284
331
|
ffmpegOutputArgs: opts.ffmpegOutputArgs ??
|
|
285
332
|
"-c copy" +
|
|
286
333
|
/**
|
|
@@ -334,6 +381,9 @@ export function genSavePathFromRule(manager, recorder, extData) {
|
|
|
334
381
|
channelId,
|
|
335
382
|
};
|
|
336
383
|
let savePathRule = manager.savePathRule;
|
|
384
|
+
if (extData?.extraMs) {
|
|
385
|
+
savePathRule += "_{ms}";
|
|
386
|
+
}
|
|
337
387
|
try {
|
|
338
388
|
savePathRule = ejs.render(savePathRule, params);
|
|
339
389
|
}
|
package/lib/recorder.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ export type GetSavePath = (data: {
|
|
|
93
93
|
startTime: number;
|
|
94
94
|
liveStartTime: Date;
|
|
95
95
|
recordStartTime: Date;
|
|
96
|
+
extraMs?: boolean;
|
|
96
97
|
}) => string;
|
|
97
98
|
export interface Recorder<E extends AnyObject = UnknownObject> extends Emitter<{
|
|
98
99
|
RecordStart: RecordHandle;
|
|
@@ -132,6 +133,7 @@ export interface Recorder<E extends AnyObject = UnknownObject> extends Emitter<{
|
|
|
132
133
|
cover: string;
|
|
133
134
|
liveId?: string;
|
|
134
135
|
recordStartTime: Date;
|
|
136
|
+
area?: string;
|
|
135
137
|
};
|
|
136
138
|
tempStopIntervalCheck?: boolean;
|
|
137
139
|
/** 缓存实例(命名空间) */
|
|
@@ -152,6 +154,7 @@ export interface Recorder<E extends AnyObject = UnknownObject> extends Emitter<{
|
|
|
152
154
|
channelId: ChannelId;
|
|
153
155
|
living: boolean;
|
|
154
156
|
liveStartTime: Date;
|
|
157
|
+
area: string;
|
|
155
158
|
}>;
|
|
156
159
|
getStream: (this: Recorder<E>) => Promise<{
|
|
157
160
|
source: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bililive-tools/manager",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Batch scheduling recorders",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"mitt": "^3.0.1",
|
|
39
39
|
"string-argv": "^0.3.2",
|
|
40
40
|
"lodash-es": "^4.17.21",
|
|
41
|
-
"axios": "^1.
|
|
41
|
+
"axios": "^1.15.0",
|
|
42
42
|
"fs-extra": "^11.2.0",
|
|
43
43
|
"ejs": "^3.1.10"
|
|
44
44
|
},
|