@bililive-tools/manager 1.0.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/LICENSE +674 -0
- package/README.md +91 -0
- package/lib/api.d.ts +1 -0
- package/lib/api.js +21 -0
- package/lib/api.js.map +1 -0
- package/lib/common.d.ts +55 -0
- package/lib/common.js +4 -0
- package/lib/common.js.map +1 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +61 -0
- package/lib/index.js.map +1 -0
- package/lib/manager.d.ts +92 -0
- package/lib/manager.js +256 -0
- package/lib/manager.js.map +1 -0
- package/lib/record_extra_data_controller.d.ts +28 -0
- package/lib/record_extra_data_controller.js +157 -0
- package/lib/record_extra_data_controller.js.map +1 -0
- package/lib/recorder.d.ts +118 -0
- package/lib/recorder.js +2 -0
- package/lib/recorder.js.map +1 -0
- package/lib/streamManager.d.ts +30 -0
- package/lib/streamManager.js +119 -0
- package/lib/streamManager.js.map +1 -0
- package/lib/utils.d.ts +62 -0
- package/lib/utils.js +232 -0
- package/lib/utils.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Message } from "./common.js";
|
|
2
|
+
export interface RecordExtraData {
|
|
3
|
+
meta: {
|
|
4
|
+
title?: string;
|
|
5
|
+
recordStartTimestamp: number;
|
|
6
|
+
recordStopTimestamp?: number;
|
|
7
|
+
liveStartTimestamp?: number;
|
|
8
|
+
ffmpegArgs?: string[];
|
|
9
|
+
platform?: string;
|
|
10
|
+
user_name?: string;
|
|
11
|
+
room_id?: string;
|
|
12
|
+
};
|
|
13
|
+
/** 这个数组预期上是一个根据 timestamp 排序的有序数组,方便做一些时间段查询 */
|
|
14
|
+
messages: Message[];
|
|
15
|
+
}
|
|
16
|
+
export interface RecordExtraDataController {
|
|
17
|
+
/** 设计上来说,外部程序不应该能直接修改 data 上的东西 */
|
|
18
|
+
readonly data: RecordExtraData;
|
|
19
|
+
addMessage: (message: Message) => void;
|
|
20
|
+
setMeta: (meta: Partial<RecordExtraData["meta"]>) => void;
|
|
21
|
+
flush: () => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare function createRecordExtraDataController(savePath: string): RecordExtraDataController;
|
|
24
|
+
/**
|
|
25
|
+
* 转换弹幕为b站格式xml
|
|
26
|
+
* @link: https://socialsisteryi.github.io/bilibili-API-collect/docs/danmaku/danmaku_xml.html#%E5%B1%9E%E6%80%A7-p
|
|
27
|
+
*/
|
|
28
|
+
export declare function convert2Xml(data: RecordExtraData): string;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用一个视频之外的独立文件来存储元信息和弹幕、礼物等信息。
|
|
3
|
+
* 暂时使用 json 作为存储方案试试效果,猜测可能会有实时写入差、占用空间大的问题。
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { XMLBuilder } from "fast-xml-parser";
|
|
8
|
+
import { pick } from "lodash-es";
|
|
9
|
+
import { asyncThrottle } from "./utils.js";
|
|
10
|
+
export function createRecordExtraDataController(savePath) {
|
|
11
|
+
const data = {
|
|
12
|
+
meta: {
|
|
13
|
+
recordStartTimestamp: Date.now(),
|
|
14
|
+
},
|
|
15
|
+
messages: [],
|
|
16
|
+
};
|
|
17
|
+
const scheduleSave = asyncThrottle(() => save(), 30e3, {
|
|
18
|
+
immediateRunWhenEndOfDefer: true,
|
|
19
|
+
});
|
|
20
|
+
const save = () => {
|
|
21
|
+
return fs.promises.writeFile(savePath, JSON.stringify(data));
|
|
22
|
+
};
|
|
23
|
+
// TODO: 将所有数据存放在内存中可能存在问题
|
|
24
|
+
const addMessage = (comment) => {
|
|
25
|
+
data.messages.push(comment);
|
|
26
|
+
scheduleSave();
|
|
27
|
+
};
|
|
28
|
+
const setMeta = (meta) => {
|
|
29
|
+
data.meta = {
|
|
30
|
+
...data.meta,
|
|
31
|
+
...meta,
|
|
32
|
+
};
|
|
33
|
+
scheduleSave();
|
|
34
|
+
};
|
|
35
|
+
const flush = async () => {
|
|
36
|
+
scheduleSave.flush();
|
|
37
|
+
scheduleSave.cancel();
|
|
38
|
+
const xmlContent = convert2Xml(data);
|
|
39
|
+
const parsedPath = path.parse(savePath);
|
|
40
|
+
const xmlPath = path.join(parsedPath.dir, parsedPath.name + ".xml");
|
|
41
|
+
await fs.promises.writeFile(xmlPath, xmlContent);
|
|
42
|
+
await fs.promises.rm(savePath);
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
data,
|
|
46
|
+
addMessage,
|
|
47
|
+
setMeta,
|
|
48
|
+
flush,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 转换弹幕为b站格式xml
|
|
53
|
+
* @link: https://socialsisteryi.github.io/bilibili-API-collect/docs/danmaku/danmaku_xml.html#%E5%B1%9E%E6%80%A7-p
|
|
54
|
+
*/
|
|
55
|
+
export function convert2Xml(data) {
|
|
56
|
+
const metadata = data.meta;
|
|
57
|
+
const builder = new XMLBuilder({
|
|
58
|
+
ignoreAttributes: false,
|
|
59
|
+
attributeNamePrefix: "@@",
|
|
60
|
+
format: true,
|
|
61
|
+
});
|
|
62
|
+
const comments = data.messages
|
|
63
|
+
.filter((item) => item.type === "comment")
|
|
64
|
+
.map((ele) => {
|
|
65
|
+
const progress = (ele.timestamp - metadata?.recordStartTimestamp) / 1000;
|
|
66
|
+
const data = {
|
|
67
|
+
"@@p": "",
|
|
68
|
+
"@@progress": progress,
|
|
69
|
+
"@@mode": String(ele.mode ?? 1),
|
|
70
|
+
"@@fontsize": String(25),
|
|
71
|
+
"@@color": String(parseInt((ele.color || "#ffffff").replace("#", ""), 16)),
|
|
72
|
+
"@@midHash": String(ele?.sender?.uid),
|
|
73
|
+
"#text": String(ele?.text || ""),
|
|
74
|
+
"@@ctime": String(ele.timestamp),
|
|
75
|
+
"@@pool": String(0),
|
|
76
|
+
"@@weight": String(0),
|
|
77
|
+
"@@user": String(ele.sender?.name),
|
|
78
|
+
"@@uid": String(ele?.sender?.uid),
|
|
79
|
+
};
|
|
80
|
+
data["@@p"] = [
|
|
81
|
+
data["@@progress"],
|
|
82
|
+
data["@@mode"],
|
|
83
|
+
data["@@fontsize"],
|
|
84
|
+
data["@@color"],
|
|
85
|
+
data["@@ctime"],
|
|
86
|
+
data["@@pool"],
|
|
87
|
+
data["@@midHash"],
|
|
88
|
+
data["@@uid"],
|
|
89
|
+
data["@@weight"],
|
|
90
|
+
].join(",");
|
|
91
|
+
return pick(data, ["@@p", "#text", "@@user", "@@uid"]);
|
|
92
|
+
});
|
|
93
|
+
const gifts = data.messages
|
|
94
|
+
.filter((item) => item.type === "give_gift")
|
|
95
|
+
.map((ele) => {
|
|
96
|
+
const progress = (ele.timestamp - metadata?.recordStartTimestamp) / 1000;
|
|
97
|
+
const data = {
|
|
98
|
+
"@@ts": progress,
|
|
99
|
+
"@@giftname": String(ele.name),
|
|
100
|
+
"@@giftcount": String(ele.count),
|
|
101
|
+
"@@price": String(ele.price * 1000),
|
|
102
|
+
"@@user": String(ele.sender?.name),
|
|
103
|
+
"@@uid": String(ele?.sender?.uid),
|
|
104
|
+
// "@@raw": JSON.stringify(ele),
|
|
105
|
+
};
|
|
106
|
+
return data;
|
|
107
|
+
});
|
|
108
|
+
const superChats = data.messages
|
|
109
|
+
.filter((item) => item.type === "super_chat")
|
|
110
|
+
.map((ele) => {
|
|
111
|
+
const progress = (ele.timestamp - metadata?.recordStartTimestamp) / 1000;
|
|
112
|
+
const data = {
|
|
113
|
+
"@@ts": progress,
|
|
114
|
+
"@@price": String(ele.price * 1000),
|
|
115
|
+
"#text": String(ele.text),
|
|
116
|
+
"@@user": String(ele.sender?.name),
|
|
117
|
+
"@@uid": String(ele?.sender?.uid),
|
|
118
|
+
// "@@raw": JSON.stringify(ele),
|
|
119
|
+
};
|
|
120
|
+
return data;
|
|
121
|
+
});
|
|
122
|
+
const guardGift = data.messages
|
|
123
|
+
.filter((item) => item.type === "guard")
|
|
124
|
+
.map((ele) => {
|
|
125
|
+
const progress = (ele.timestamp - metadata?.recordStartTimestamp) / 1000;
|
|
126
|
+
const data = {
|
|
127
|
+
"@@ts": progress,
|
|
128
|
+
"@@price": String(ele.price * 1000),
|
|
129
|
+
"@@giftname": String(ele.name),
|
|
130
|
+
"@@giftcount": String(ele.count),
|
|
131
|
+
"@@level": String(ele.level),
|
|
132
|
+
"@@user": String(ele.sender?.name),
|
|
133
|
+
"@@uid": String(ele?.sender?.uid),
|
|
134
|
+
// "@@raw": JSON.stringify(ele),
|
|
135
|
+
};
|
|
136
|
+
return data;
|
|
137
|
+
});
|
|
138
|
+
const xmlContent = builder.build({
|
|
139
|
+
i: {
|
|
140
|
+
metadata: {
|
|
141
|
+
platform: metadata.platform,
|
|
142
|
+
video_start_time: metadata.recordStartTimestamp,
|
|
143
|
+
live_start_time: metadata.liveStartTimestamp,
|
|
144
|
+
room_title: metadata.title,
|
|
145
|
+
user_name: metadata.user_name,
|
|
146
|
+
room_id: metadata.room_id,
|
|
147
|
+
},
|
|
148
|
+
d: comments,
|
|
149
|
+
gift: gifts,
|
|
150
|
+
sc: superChats,
|
|
151
|
+
guard: guardGift,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
155
|
+
${xmlContent}`;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=record_extra_data_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record_extra_data_controller.js","sourceRoot":"","sources":["../src/record_extra_data_controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Emitter } from "mitt";
|
|
2
|
+
import { ChannelId, Message, Quality } from "./common.js";
|
|
3
|
+
import { RecorderProvider } from "./manager.js";
|
|
4
|
+
import { AnyObject, PickRequired, UnknownObject } from "./utils.js";
|
|
5
|
+
type FormatName = "auto" | "flv" | "hls" | "fmp4" | "flv_only" | "hls_only" | "fmp4_only";
|
|
6
|
+
type CodecName = "auto" | "avc" | "hevc" | "avc_only" | "hevc_only";
|
|
7
|
+
export interface RecorderCreateOpts<E extends AnyObject = UnknownObject> {
|
|
8
|
+
providerId: RecorderProvider<E>["id"];
|
|
9
|
+
channelId: ChannelId;
|
|
10
|
+
id?: string;
|
|
11
|
+
remarks?: string;
|
|
12
|
+
disableAutoCheck?: boolean;
|
|
13
|
+
disableProvideCommentsWhenRecording?: boolean;
|
|
14
|
+
quality: Quality;
|
|
15
|
+
streamPriorities: string[];
|
|
16
|
+
sourcePriorities: string[];
|
|
17
|
+
segment?: number;
|
|
18
|
+
saveGiftDanma?: boolean;
|
|
19
|
+
saveSCDanma?: boolean;
|
|
20
|
+
/** 保存封面 */
|
|
21
|
+
saveCover?: boolean;
|
|
22
|
+
/** 身份验证 */
|
|
23
|
+
auth?: string;
|
|
24
|
+
/** cookie所有者uid,B站弹幕录制 */
|
|
25
|
+
uid?: number;
|
|
26
|
+
/** 画质匹配重试次数 */
|
|
27
|
+
qualityRetry?: number;
|
|
28
|
+
/** B站是否使用m3u8代理 */
|
|
29
|
+
useM3U8Proxy?: boolean;
|
|
30
|
+
/**B站m3u8代理url */
|
|
31
|
+
m3u8ProxyUrl?: string;
|
|
32
|
+
/** 流格式 */
|
|
33
|
+
formatName?: FormatName;
|
|
34
|
+
/** 流编码 */
|
|
35
|
+
codecName?: CodecName;
|
|
36
|
+
extra?: Partial<E>;
|
|
37
|
+
}
|
|
38
|
+
export type SerializedRecorder<E extends AnyObject> = PickRequired<RecorderCreateOpts<E>, "id">;
|
|
39
|
+
export type RecorderState = "idle" | "recording" | "stopping-record";
|
|
40
|
+
export interface RecordHandle {
|
|
41
|
+
id: string;
|
|
42
|
+
stream: string;
|
|
43
|
+
source: string;
|
|
44
|
+
url: string;
|
|
45
|
+
ffmpegArgs?: string[];
|
|
46
|
+
savePath: string;
|
|
47
|
+
stop: (this: RecordHandle, reason?: string, tempStopIntervalCheck?: boolean) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export interface DebugLog {
|
|
50
|
+
type: string | "common" | "ffmpeg";
|
|
51
|
+
text: string;
|
|
52
|
+
}
|
|
53
|
+
export type GetSavePath = (data: {
|
|
54
|
+
owner: string;
|
|
55
|
+
title: string;
|
|
56
|
+
startTime?: number;
|
|
57
|
+
}) => string;
|
|
58
|
+
export interface Recorder<E extends AnyObject = UnknownObject> extends Emitter<{
|
|
59
|
+
RecordStart: RecordHandle;
|
|
60
|
+
RecordSegment?: RecordHandle;
|
|
61
|
+
videoFileCreated: {
|
|
62
|
+
filename: string;
|
|
63
|
+
};
|
|
64
|
+
videoFileCompleted: {
|
|
65
|
+
filename: string;
|
|
66
|
+
};
|
|
67
|
+
RecordStop: {
|
|
68
|
+
recordHandle: RecordHandle;
|
|
69
|
+
reason?: string;
|
|
70
|
+
};
|
|
71
|
+
Updated: (string | keyof Recorder)[];
|
|
72
|
+
Message: Message;
|
|
73
|
+
DebugLog: DebugLog;
|
|
74
|
+
}>, RecorderCreateOpts<E> {
|
|
75
|
+
id: string;
|
|
76
|
+
extra: Partial<E>;
|
|
77
|
+
availableStreams: string[];
|
|
78
|
+
availableSources: string[];
|
|
79
|
+
usedStream?: string;
|
|
80
|
+
usedSource?: string;
|
|
81
|
+
state: RecorderState;
|
|
82
|
+
qualityMaxRetry: number;
|
|
83
|
+
qualityRetry: number;
|
|
84
|
+
uid?: number;
|
|
85
|
+
liveInfo?: {
|
|
86
|
+
living: boolean;
|
|
87
|
+
owner: string;
|
|
88
|
+
title: string;
|
|
89
|
+
startTime?: Date;
|
|
90
|
+
avatar: string;
|
|
91
|
+
cover: string;
|
|
92
|
+
liveId?: string;
|
|
93
|
+
};
|
|
94
|
+
tempStopIntervalCheck?: boolean;
|
|
95
|
+
getChannelURL: (this: Recorder<E>) => string;
|
|
96
|
+
checkLiveStatusAndRecord: (this: Recorder<E>, opts: {
|
|
97
|
+
getSavePath: GetSavePath;
|
|
98
|
+
qualityRetry?: number;
|
|
99
|
+
banLiveId?: string;
|
|
100
|
+
}) => Promise<RecordHandle | null>;
|
|
101
|
+
recordHandle?: RecordHandle;
|
|
102
|
+
toJSON: (this: Recorder<E>) => SerializedRecorder<E>;
|
|
103
|
+
getLiveInfo: (this: Recorder<E>) => Promise<{
|
|
104
|
+
owner: string;
|
|
105
|
+
title: string;
|
|
106
|
+
avatar: string;
|
|
107
|
+
cover: string;
|
|
108
|
+
channelId: ChannelId;
|
|
109
|
+
living: boolean;
|
|
110
|
+
startTime: Date;
|
|
111
|
+
}>;
|
|
112
|
+
getStream: (this: Recorder<E>) => Promise<{
|
|
113
|
+
source: string;
|
|
114
|
+
name: string;
|
|
115
|
+
url: string;
|
|
116
|
+
}>;
|
|
117
|
+
}
|
|
118
|
+
export {};
|
package/lib/recorder.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRecordExtraDataController } from "./record_extra_data_controller.js";
|
|
2
|
+
import type { Recorder, GetSavePath } from "./recorder.js";
|
|
3
|
+
export declare class Segment {
|
|
4
|
+
extraDataController: ReturnType<typeof createRecordExtraDataController> | null;
|
|
5
|
+
init: boolean;
|
|
6
|
+
getSavePath: GetSavePath;
|
|
7
|
+
owner: string;
|
|
8
|
+
title: string;
|
|
9
|
+
recorder: Recorder;
|
|
10
|
+
/** 原始的ffmpeg文件名,用于重命名 */
|
|
11
|
+
rawRecordingVideoPath: string;
|
|
12
|
+
/** 输出文件名名,不包含拓展名 */
|
|
13
|
+
outputVideoFilePath: string;
|
|
14
|
+
constructor(recorder: Recorder, getSavePath: GetSavePath, owner: string, title: string);
|
|
15
|
+
handleSegmentEnd(): Promise<void>;
|
|
16
|
+
onSegmentStart(stderrLine: string): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare class StreamManager {
|
|
19
|
+
private segmentManager;
|
|
20
|
+
private extraDataController;
|
|
21
|
+
recorder: Recorder;
|
|
22
|
+
owner: string;
|
|
23
|
+
title: string;
|
|
24
|
+
recordSavePath: string;
|
|
25
|
+
constructor(recorder: Recorder, getSavePath: GetSavePath, owner: string, title: string, recordSavePath: string, hasSegment: boolean);
|
|
26
|
+
handleVideoStarted(stderrLine?: string): Promise<void>;
|
|
27
|
+
handleVideoCompleted(): Promise<void>;
|
|
28
|
+
getExtraDataController(): import("./record_extra_data_controller.js").RecordExtraDataController | null;
|
|
29
|
+
get videoFilePath(): string;
|
|
30
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import { createRecordExtraDataController } from "./record_extra_data_controller.js";
|
|
3
|
+
import { replaceExtName, ensureFolderExist } from "./utils.js";
|
|
4
|
+
export class Segment {
|
|
5
|
+
extraDataController = null;
|
|
6
|
+
init = true;
|
|
7
|
+
getSavePath;
|
|
8
|
+
owner;
|
|
9
|
+
title;
|
|
10
|
+
recorder;
|
|
11
|
+
/** 原始的ffmpeg文件名,用于重命名 */
|
|
12
|
+
rawRecordingVideoPath;
|
|
13
|
+
/** 输出文件名名,不包含拓展名 */
|
|
14
|
+
outputVideoFilePath;
|
|
15
|
+
constructor(recorder, getSavePath, owner, title) {
|
|
16
|
+
this.getSavePath = getSavePath;
|
|
17
|
+
this.owner = owner;
|
|
18
|
+
this.title = title;
|
|
19
|
+
this.recorder = recorder;
|
|
20
|
+
}
|
|
21
|
+
async handleSegmentEnd() {
|
|
22
|
+
if (!this.outputVideoFilePath) {
|
|
23
|
+
this.recorder.emit("DebugLog", {
|
|
24
|
+
type: "common",
|
|
25
|
+
text: "Should call onSegmentStart first",
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.extraDataController?.setMeta({ recordStopTimestamp: Date.now() });
|
|
30
|
+
try {
|
|
31
|
+
await Promise.all([
|
|
32
|
+
fs.rename(this.rawRecordingVideoPath, `${this.outputVideoFilePath}.ts`),
|
|
33
|
+
this.extraDataController?.flush(),
|
|
34
|
+
]);
|
|
35
|
+
this.recorder.emit("videoFileCompleted", { filename: `${this.outputVideoFilePath}.ts` });
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
this.recorder.emit("DebugLog", {
|
|
39
|
+
type: "common",
|
|
40
|
+
text: "videoFileCompleted error " + String(err),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async onSegmentStart(stderrLine) {
|
|
45
|
+
if (!this.init) {
|
|
46
|
+
await this.handleSegmentEnd();
|
|
47
|
+
}
|
|
48
|
+
this.init = false;
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
this.outputVideoFilePath = this.getSavePath({
|
|
51
|
+
owner: this.owner,
|
|
52
|
+
title: this.title,
|
|
53
|
+
startTime: startTime,
|
|
54
|
+
});
|
|
55
|
+
ensureFolderExist(this.outputVideoFilePath);
|
|
56
|
+
this.extraDataController = createRecordExtraDataController(`${this.outputVideoFilePath}.json`);
|
|
57
|
+
this.extraDataController.setMeta({ title: this.title, user_name: this.owner });
|
|
58
|
+
const regex = /'([^']+)'/;
|
|
59
|
+
const match = stderrLine.match(regex);
|
|
60
|
+
if (match) {
|
|
61
|
+
const filename = match[1];
|
|
62
|
+
this.rawRecordingVideoPath = filename;
|
|
63
|
+
this.recorder.emit("videoFileCreated", { filename: `${this.outputVideoFilePath}.ts` });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.recorder.emit("DebugLog", { type: "ffmpeg", text: "No match found" });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export class StreamManager {
|
|
71
|
+
segmentManager = null;
|
|
72
|
+
extraDataController = null;
|
|
73
|
+
recorder;
|
|
74
|
+
owner;
|
|
75
|
+
title;
|
|
76
|
+
recordSavePath;
|
|
77
|
+
constructor(recorder, getSavePath, owner, title, recordSavePath, hasSegment) {
|
|
78
|
+
this.recordSavePath = recordSavePath;
|
|
79
|
+
this.recorder = recorder;
|
|
80
|
+
this.owner = owner;
|
|
81
|
+
this.title = title;
|
|
82
|
+
if (hasSegment) {
|
|
83
|
+
this.segmentManager = new Segment(recorder, getSavePath, owner, title);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const extraDataSavePath = replaceExtName(recordSavePath, ".json");
|
|
87
|
+
this.extraDataController = createRecordExtraDataController(extraDataSavePath);
|
|
88
|
+
this.extraDataController.setMeta({ title, user_name: owner });
|
|
89
|
+
// TODO: 增加platform参数,直播开始时间
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async handleVideoStarted(stderrLine) {
|
|
93
|
+
if (this.segmentManager) {
|
|
94
|
+
if (stderrLine) {
|
|
95
|
+
await this.segmentManager.onSegmentStart(stderrLine);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.recorder.emit("videoFileCreated", { filename: this.videoFilePath });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async handleVideoCompleted() {
|
|
103
|
+
if (this.segmentManager) {
|
|
104
|
+
await this.segmentManager.handleSegmentEnd();
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.getExtraDataController()?.setMeta({ recordStopTimestamp: Date.now() });
|
|
108
|
+
await this.getExtraDataController()?.flush();
|
|
109
|
+
this.recorder.emit("videoFileCompleted", { filename: this.videoFilePath });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
getExtraDataController() {
|
|
113
|
+
return this.segmentManager?.extraDataController || this.extraDataController;
|
|
114
|
+
}
|
|
115
|
+
get videoFilePath() {
|
|
116
|
+
return this.segmentManager ? `${this.recordSavePath}-PART%03d.ts` : `${this.recordSavePath}.ts`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=streamManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamManager.js","sourceRoot":"","sources":["../src/streamManager.ts"],"names":[],"mappings":"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"}
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DebouncedFunc } from "lodash-es";
|
|
2
|
+
export type AnyObject = Record<string, any>;
|
|
3
|
+
export type UnknownObject = Record<string, unknown>;
|
|
4
|
+
export type PickRequired<T, K extends keyof T> = T & Pick<Required<T>, K>;
|
|
5
|
+
export declare function asyncThrottle(fn: () => Promise<void>, time: number, opts?: {
|
|
6
|
+
immediateRunWhenEndOfDefer?: boolean;
|
|
7
|
+
}): DebouncedFunc<() => void>;
|
|
8
|
+
export declare function replaceExtName(filePath: string, newExtName: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* 接收 fn ,返回一个和 fn 签名一致的函数 fn'。当已经有一个 fn' 在运行时,再调用
|
|
11
|
+
* fn' 会直接返回运行中 fn' 的 Promise,直到 Promise 结束 pending 状态
|
|
12
|
+
*/
|
|
13
|
+
export declare function singleton<Fn extends (...args: any) => Promise<any>>(fn: Fn): Fn;
|
|
14
|
+
/**
|
|
15
|
+
* 从数组中按照特定算法提取一些值(允许同个索引重复提取)。
|
|
16
|
+
* 算法的行为类似 flex 的 space-between。
|
|
17
|
+
*
|
|
18
|
+
* examples:
|
|
19
|
+
* ```
|
|
20
|
+
* console.log(getValuesFromArrayLikeFlexSpaceBetween([1, 2, 3, 4, 5, 6, 7], 1))
|
|
21
|
+
* // [1]
|
|
22
|
+
* console.log(getValuesFromArrayLikeFlexSpaceBetween([1, 2, 3, 4, 5, 6, 7], 3))
|
|
23
|
+
* // [1, 4, 7]
|
|
24
|
+
* console.log(getValuesFromArrayLikeFlexSpaceBetween([1, 2, 3, 4, 5, 6, 7], 4))
|
|
25
|
+
* // [1, 3, 5, 7]
|
|
26
|
+
* console.log(getValuesFromArrayLikeFlexSpaceBetween([1, 2, 3, 4, 5, 6, 7], 11))
|
|
27
|
+
* // [1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 7]
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function getValuesFromArrayLikeFlexSpaceBetween<T>(array: T[], columnCount: number): T[];
|
|
31
|
+
export declare function ensureFolderExist(fileOrFolderPath: string): void;
|
|
32
|
+
export declare function assert(assertion: unknown, msg?: string): asserts assertion;
|
|
33
|
+
export declare function assertStringType(data: unknown, msg?: string): asserts data is string;
|
|
34
|
+
export declare function assertNumberType(data: unknown, msg?: string): asserts data is number;
|
|
35
|
+
export declare function assertObjectType(data: unknown, msg?: string): asserts data is object;
|
|
36
|
+
export declare function formatDate(date: Date, format: string): string;
|
|
37
|
+
export declare function removeSystemReservedChars(filename: string): string;
|
|
38
|
+
export declare function isFfmpegStartSegment(line: string): boolean;
|
|
39
|
+
export declare const formatTemplate: (string: string, ...args: any[]) => string;
|
|
40
|
+
export declare function createInvalidStreamChecker(): (ffmpegLogLine: string) => boolean;
|
|
41
|
+
export declare function createTimeoutChecker(onTimeout: () => void, time: number): {
|
|
42
|
+
update: () => void;
|
|
43
|
+
stop: () => void;
|
|
44
|
+
};
|
|
45
|
+
declare function downloadImage(imageUrl: string, savePath: string): Promise<void>;
|
|
46
|
+
declare const _default: {
|
|
47
|
+
replaceExtName: typeof replaceExtName;
|
|
48
|
+
singleton: typeof singleton;
|
|
49
|
+
getValuesFromArrayLikeFlexSpaceBetween: typeof getValuesFromArrayLikeFlexSpaceBetween;
|
|
50
|
+
ensureFolderExist: typeof ensureFolderExist;
|
|
51
|
+
assert: typeof assert;
|
|
52
|
+
assertStringType: typeof assertStringType;
|
|
53
|
+
assertNumberType: typeof assertNumberType;
|
|
54
|
+
assertObjectType: typeof assertObjectType;
|
|
55
|
+
asyncThrottle: typeof asyncThrottle;
|
|
56
|
+
isFfmpegStartSegment: typeof isFfmpegStartSegment;
|
|
57
|
+
createInvalidStreamChecker: typeof createInvalidStreamChecker;
|
|
58
|
+
createTimeoutChecker: typeof createTimeoutChecker;
|
|
59
|
+
downloadImage: typeof downloadImage;
|
|
60
|
+
md5: (str: string) => string;
|
|
61
|
+
};
|
|
62
|
+
export default _default;
|