@bililive-tools/manager 1.10.0 → 1.11.1
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/lib/downloader/BililiveDownloader.d.ts +4 -1
- package/lib/downloader/BililiveDownloader.js +22 -11
- package/lib/downloader/FFmpegDownloader.d.ts +2 -0
- package/lib/downloader/FFmpegDownloader.js +10 -4
- package/lib/downloader/IDownloader.d.ts +3 -0
- package/lib/downloader/index.d.ts +1 -0
- package/lib/downloader/index.js +1 -0
- package/lib/downloader/mesioDownloader.d.ts +3 -1
- package/lib/downloader/mesioDownloader.js +18 -14
- package/lib/downloader/streamManager.d.ts +3 -2
- package/lib/downloader/streamManager.js +13 -7
- package/lib/manager.d.ts +1 -0
- package/lib/manager.js +4 -0
- package/lib/recorder.d.ts +2 -2
- package/package.json +1 -1
|
@@ -12,7 +12,8 @@ declare class BililiveRecorderCommand extends EventEmitter {
|
|
|
12
12
|
inputOptions(...options: string[]): BililiveRecorderCommand;
|
|
13
13
|
_getArguments(): string[];
|
|
14
14
|
run(): void;
|
|
15
|
-
kill(
|
|
15
|
+
kill(): void;
|
|
16
|
+
cut(): void;
|
|
16
17
|
}
|
|
17
18
|
export declare const createBililiveBuilder: () => BililiveRecorderCommand;
|
|
18
19
|
export declare class BililiveDownloader extends EventEmitter implements IDownloader {
|
|
@@ -28,6 +29,7 @@ export declare class BililiveDownloader extends EventEmitter implements IDownloa
|
|
|
28
29
|
}) => string;
|
|
29
30
|
readonly segment: Segment;
|
|
30
31
|
readonly inputOptions: string[];
|
|
32
|
+
readonly disableDanma: boolean;
|
|
31
33
|
readonly url: string;
|
|
32
34
|
readonly debugLevel: "none" | "basic" | "verbose";
|
|
33
35
|
readonly headers: {
|
|
@@ -46,5 +48,6 @@ export declare class BililiveDownloader extends EventEmitter implements IDownloa
|
|
|
46
48
|
stop(): Promise<void>;
|
|
47
49
|
getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
|
|
48
50
|
get videoFilePath(): string;
|
|
51
|
+
cut(): void;
|
|
49
52
|
}
|
|
50
53
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { DEFAULT_USER_AGENT } from "./index.js";
|
|
3
4
|
import { StreamManager, getBililivePath } from "../index.js";
|
|
4
5
|
import { byte2MB } from "../utils.js";
|
|
5
6
|
// Bililive command builder class similar to ffmpeg
|
|
@@ -46,6 +47,7 @@ class BililiveRecorderCommand extends EventEmitter {
|
|
|
46
47
|
const bililiveExecutable = getBililivePath();
|
|
47
48
|
this.process = spawn(bililiveExecutable, args, {
|
|
48
49
|
stdio: ["pipe", "pipe", "pipe"],
|
|
50
|
+
windowsHide: true,
|
|
49
51
|
});
|
|
50
52
|
if (this.process.stdout) {
|
|
51
53
|
this.process.stdout.on("data", (data) => {
|
|
@@ -64,7 +66,6 @@ class BililiveRecorderCommand extends EventEmitter {
|
|
|
64
66
|
this.process.on("error", (error) => {
|
|
65
67
|
this.emit("error", error);
|
|
66
68
|
});
|
|
67
|
-
[];
|
|
68
69
|
this.process.on("close", (code) => {
|
|
69
70
|
if (code === 0) {
|
|
70
71
|
this.emit("end");
|
|
@@ -74,9 +75,15 @@ class BililiveRecorderCommand extends EventEmitter {
|
|
|
74
75
|
}
|
|
75
76
|
});
|
|
76
77
|
}
|
|
77
|
-
kill(
|
|
78
|
+
kill() {
|
|
78
79
|
if (this.process) {
|
|
79
|
-
this.process.
|
|
80
|
+
this.process.stdin?.write("q\n");
|
|
81
|
+
// this.process.kill("SIGTERM");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
cut() {
|
|
85
|
+
if (this.process) {
|
|
86
|
+
this.process.stdin?.write("s\n");
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
89
|
}
|
|
@@ -94,6 +101,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
94
101
|
getSavePath;
|
|
95
102
|
segment;
|
|
96
103
|
inputOptions = [];
|
|
104
|
+
disableDanma = false;
|
|
97
105
|
url;
|
|
98
106
|
debugLevel = "none";
|
|
99
107
|
headers;
|
|
@@ -104,16 +112,20 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
104
112
|
// 存在自动分段,永远为true
|
|
105
113
|
const hasSegment = true;
|
|
106
114
|
this.hasSegment = hasSegment;
|
|
115
|
+
this.disableDanma = opts.disableDanma ?? false;
|
|
107
116
|
this.debugLevel = opts.debugLevel ?? "none";
|
|
108
117
|
let videoFormat = "flv";
|
|
109
|
-
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, "bililive", videoFormat, {
|
|
118
|
+
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "bililive", videoFormat, {
|
|
110
119
|
onUpdateLiveInfo: this.onUpdateLiveInfo,
|
|
111
120
|
});
|
|
112
121
|
this.getSavePath = opts.getSavePath;
|
|
113
122
|
this.inputOptions = [];
|
|
114
123
|
this.url = opts.url;
|
|
115
124
|
this.segment = opts.segment;
|
|
116
|
-
this.headers =
|
|
125
|
+
this.headers = {
|
|
126
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
127
|
+
...(opts.headers || {}),
|
|
128
|
+
};
|
|
117
129
|
this.command = this.createCommand();
|
|
118
130
|
this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
|
|
119
131
|
this.emit("videoFileCreated", { filename, cover, rawFilename, title });
|
|
@@ -126,11 +138,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
126
138
|
});
|
|
127
139
|
}
|
|
128
140
|
createCommand() {
|
|
129
|
-
const inputOptions = [
|
|
130
|
-
...this.inputOptions,
|
|
131
|
-
"-h",
|
|
132
|
-
"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",
|
|
133
|
-
];
|
|
141
|
+
const inputOptions = [...this.inputOptions, "--disable-log-file", "true"];
|
|
134
142
|
if (this.debugLevel === "verbose") {
|
|
135
143
|
inputOptions.push("-l", "Debug");
|
|
136
144
|
}
|
|
@@ -187,7 +195,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
187
195
|
async stop() {
|
|
188
196
|
try {
|
|
189
197
|
// 直接发送SIGINT信号,会导致数据丢失
|
|
190
|
-
this.command.kill(
|
|
198
|
+
this.command.kill();
|
|
191
199
|
await this.streamManager.handleVideoCompleted();
|
|
192
200
|
}
|
|
193
201
|
catch (err) {
|
|
@@ -200,4 +208,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
200
208
|
get videoFilePath() {
|
|
201
209
|
return this.streamManager.videoFilePath;
|
|
202
210
|
}
|
|
211
|
+
cut() {
|
|
212
|
+
this.command.cut();
|
|
213
|
+
}
|
|
203
214
|
}
|
|
@@ -18,6 +18,7 @@ export declare class FFmpegDownloader extends EventEmitter implements IDownloade
|
|
|
18
18
|
ffmpegOutputOptions: string[];
|
|
19
19
|
readonly inputOptions: string[];
|
|
20
20
|
readonly isHls: boolean;
|
|
21
|
+
readonly disableDanma: boolean;
|
|
21
22
|
readonly url: string;
|
|
22
23
|
formatName: FormatName;
|
|
23
24
|
videoFormat: VideoFormat;
|
|
@@ -39,4 +40,5 @@ export declare class FFmpegDownloader extends EventEmitter implements IDownloade
|
|
|
39
40
|
stop(): Promise<void>;
|
|
40
41
|
getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
|
|
41
42
|
get videoFilePath(): string;
|
|
43
|
+
cut(): void;
|
|
42
44
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import { createFFMPEGBuilder, StreamManager, utils } from "../index.js";
|
|
3
3
|
import { createInvalidStreamChecker, assert } from "../utils.js";
|
|
4
|
+
import { DEFAULT_USER_AGENT } from "./index.js";
|
|
4
5
|
export class FFmpegDownloader extends EventEmitter {
|
|
5
6
|
onEnd;
|
|
6
7
|
onUpdateLiveInfo;
|
|
@@ -14,6 +15,7 @@ export class FFmpegDownloader extends EventEmitter {
|
|
|
14
15
|
ffmpegOutputOptions = [];
|
|
15
16
|
inputOptions = [];
|
|
16
17
|
isHls;
|
|
18
|
+
disableDanma = false;
|
|
17
19
|
url;
|
|
18
20
|
formatName;
|
|
19
21
|
videoFormat;
|
|
@@ -50,7 +52,8 @@ export class FFmpegDownloader extends EventEmitter {
|
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
this.videoFormat = videoFormat;
|
|
53
|
-
this.
|
|
55
|
+
this.disableDanma = opts.disableDanma ?? false;
|
|
56
|
+
this.streamManager = new StreamManager(opts.getSavePath, this.hasSegment, this.disableDanma, "ffmpeg", this.videoFormat, {
|
|
54
57
|
onUpdateLiveInfo: this.onUpdateLiveInfo,
|
|
55
58
|
});
|
|
56
59
|
this.timeoutChecker = utils.createTimeoutChecker(() => this.onEnd("ffmpeg timeout"), 3 * 10e3, false);
|
|
@@ -78,7 +81,7 @@ export class FFmpegDownloader extends EventEmitter {
|
|
|
78
81
|
const inputOptions = [
|
|
79
82
|
...this.inputOptions,
|
|
80
83
|
"-user_agent",
|
|
81
|
-
"
|
|
84
|
+
this.headers?.["User-Agent"] ?? DEFAULT_USER_AGENT,
|
|
82
85
|
];
|
|
83
86
|
if (this.isHls) {
|
|
84
87
|
inputOptions.push(...["-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "3"]);
|
|
@@ -89,8 +92,8 @@ export class FFmpegDownloader extends EventEmitter {
|
|
|
89
92
|
if (this.headers) {
|
|
90
93
|
const headers = [];
|
|
91
94
|
Object.entries(this.headers).forEach(([key, value]) => {
|
|
92
|
-
if (!value)
|
|
93
|
-
return;
|
|
95
|
+
if (!value || key === "User-Agent")
|
|
96
|
+
return; // User-Agent单独处理
|
|
94
97
|
headers.push(`${key}:${value}`);
|
|
95
98
|
});
|
|
96
99
|
if (headers.length) {
|
|
@@ -186,4 +189,7 @@ export class FFmpegDownloader extends EventEmitter {
|
|
|
186
189
|
get videoFilePath() {
|
|
187
190
|
return this.streamManager.videoFilePath;
|
|
188
191
|
}
|
|
192
|
+
cut() {
|
|
193
|
+
throw new Error("FFmpeg downloader does not support cut operation.");
|
|
194
|
+
}
|
|
189
195
|
}
|
|
@@ -14,6 +14,7 @@ export interface BaseRecorderOptions {
|
|
|
14
14
|
}) => string;
|
|
15
15
|
segment: Segment;
|
|
16
16
|
inputOptions?: string[];
|
|
17
|
+
disableDanma?: boolean;
|
|
17
18
|
formatName: FormatName;
|
|
18
19
|
debugLevel?: "none" | "basic" | "verbose";
|
|
19
20
|
headers?: {
|
|
@@ -29,6 +30,7 @@ export interface IDownloader extends EventEmitter {
|
|
|
29
30
|
readonly hasSegment: boolean;
|
|
30
31
|
readonly segment: Segment;
|
|
31
32
|
readonly inputOptions: string[];
|
|
33
|
+
readonly disableDanma: boolean;
|
|
32
34
|
readonly url: string;
|
|
33
35
|
readonly headers: {
|
|
34
36
|
[key: string]: string | undefined;
|
|
@@ -39,6 +41,7 @@ export interface IDownloader extends EventEmitter {
|
|
|
39
41
|
}) => string;
|
|
40
42
|
run(): void;
|
|
41
43
|
stop(): Promise<void>;
|
|
44
|
+
cut(): void;
|
|
42
45
|
getArguments(): string[];
|
|
43
46
|
getExtraDataController(): XmlStreamController | null;
|
|
44
47
|
createCommand(): any;
|
|
@@ -19,6 +19,7 @@ export type RecorderOptions<T extends DownloaderType> = T extends "ffmpeg" ? FFM
|
|
|
19
19
|
*/
|
|
20
20
|
export type RecorderInstance<T extends DownloaderType> = T extends "ffmpeg" ? FFmpegDownloader : T extends "mesio" ? mesioDownloader : BililiveDownloader;
|
|
21
21
|
type RecorderOpts = FFMPEGRecorderOptions | MesioRecorderOptions | BililiveRecorderOptions;
|
|
22
|
+
export declare const DEFAULT_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";
|
|
22
23
|
/**
|
|
23
24
|
* 创建录制器的工厂函数
|
|
24
25
|
*/
|
package/lib/downloader/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { parseSizeToBytes } from "../utils.js";
|
|
|
5
5
|
export { FFmpegDownloader } from "./FFmpegDownloader.js";
|
|
6
6
|
export { mesioDownloader } from "./mesioDownloader.js";
|
|
7
7
|
export { BililiveDownloader } from "./BililiveDownloader.js";
|
|
8
|
+
export const DEFAULT_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";
|
|
8
9
|
/**
|
|
9
10
|
* 创建录制器的工厂函数
|
|
10
11
|
*/
|
|
@@ -12,7 +12,7 @@ declare class MesioCommand extends EventEmitter {
|
|
|
12
12
|
inputOptions(...options: string[]): MesioCommand;
|
|
13
13
|
_getArguments(): string[];
|
|
14
14
|
run(): void;
|
|
15
|
-
kill(
|
|
15
|
+
kill(): void;
|
|
16
16
|
}
|
|
17
17
|
export declare const createMesioBuilder: () => MesioCommand;
|
|
18
18
|
export declare class mesioDownloader extends EventEmitter implements IDownloader {
|
|
@@ -28,6 +28,7 @@ export declare class mesioDownloader extends EventEmitter implements IDownloader
|
|
|
28
28
|
}) => string;
|
|
29
29
|
readonly segment: Segment;
|
|
30
30
|
readonly inputOptions: string[];
|
|
31
|
+
readonly disableDanma: boolean;
|
|
31
32
|
readonly url: string;
|
|
32
33
|
readonly debugLevel: "none" | "basic" | "verbose";
|
|
33
34
|
readonly headers: {
|
|
@@ -43,5 +44,6 @@ export declare class mesioDownloader extends EventEmitter implements IDownloader
|
|
|
43
44
|
stop(): Promise<void>;
|
|
44
45
|
getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
|
|
45
46
|
get videoFilePath(): string;
|
|
47
|
+
cut(): void;
|
|
46
48
|
}
|
|
47
49
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import EventEmitter from "node:events";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import { DEFAULT_USER_AGENT } from "./index.js";
|
|
4
5
|
import { StreamManager, getMesioPath } from "../index.js";
|
|
5
|
-
// Mesio command builder class similar to ffmpeg
|
|
6
6
|
class MesioCommand extends EventEmitter {
|
|
7
7
|
_input = "";
|
|
8
8
|
_output = "";
|
|
@@ -46,6 +46,7 @@ class MesioCommand extends EventEmitter {
|
|
|
46
46
|
const mesioExecutable = getMesioPath();
|
|
47
47
|
this.process = spawn(mesioExecutable, args, {
|
|
48
48
|
stdio: ["pipe", "pipe", "pipe"],
|
|
49
|
+
windowsHide: true,
|
|
49
50
|
});
|
|
50
51
|
if (this.process.stdout) {
|
|
51
52
|
this.process.stdout.on("data", (data) => {
|
|
@@ -74,9 +75,10 @@ class MesioCommand extends EventEmitter {
|
|
|
74
75
|
}
|
|
75
76
|
});
|
|
76
77
|
}
|
|
77
|
-
kill(
|
|
78
|
+
kill() {
|
|
78
79
|
if (this.process) {
|
|
79
|
-
this.process.
|
|
80
|
+
this.process.stdin?.write("q");
|
|
81
|
+
this.process.stdin?.end();
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -94,6 +96,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
94
96
|
getSavePath;
|
|
95
97
|
segment;
|
|
96
98
|
inputOptions = [];
|
|
99
|
+
disableDanma = false;
|
|
97
100
|
url;
|
|
98
101
|
debugLevel = "none";
|
|
99
102
|
headers;
|
|
@@ -104,6 +107,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
104
107
|
// 存在自动分段,永远为true
|
|
105
108
|
const hasSegment = true;
|
|
106
109
|
this.hasSegment = hasSegment;
|
|
110
|
+
this.disableDanma = opts.disableDanma ?? false;
|
|
107
111
|
this.debugLevel = opts.debugLevel ?? "none";
|
|
108
112
|
let videoFormat = "flv";
|
|
109
113
|
if (opts.url.includes(".m3u8")) {
|
|
@@ -118,14 +122,17 @@ export class mesioDownloader extends EventEmitter {
|
|
|
118
122
|
else if (opts.formatName === "flv") {
|
|
119
123
|
videoFormat = "flv";
|
|
120
124
|
}
|
|
121
|
-
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, "mesio", videoFormat, {
|
|
125
|
+
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "mesio", videoFormat, {
|
|
122
126
|
onUpdateLiveInfo: this.onUpdateLiveInfo,
|
|
123
127
|
});
|
|
124
128
|
this.getSavePath = opts.getSavePath;
|
|
125
129
|
this.inputOptions = [];
|
|
126
130
|
this.url = opts.url;
|
|
127
131
|
this.segment = opts.segment;
|
|
128
|
-
this.headers =
|
|
132
|
+
this.headers = {
|
|
133
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
134
|
+
...(opts.headers || {}),
|
|
135
|
+
};
|
|
129
136
|
this.command = this.createCommand();
|
|
130
137
|
this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
|
|
131
138
|
this.emit("videoFileCreated", { filename, cover, rawFilename, title });
|
|
@@ -138,13 +145,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
138
145
|
});
|
|
139
146
|
}
|
|
140
147
|
createCommand() {
|
|
141
|
-
const inputOptions = [
|
|
142
|
-
...this.inputOptions,
|
|
143
|
-
"--fix",
|
|
144
|
-
"-H",
|
|
145
|
-
"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",
|
|
146
|
-
"--no-proxy",
|
|
147
|
-
];
|
|
148
|
+
const inputOptions = [...this.inputOptions, "--fix", "--no-proxy"];
|
|
148
149
|
if (this.debugLevel === "verbose") {
|
|
149
150
|
inputOptions.push("-v");
|
|
150
151
|
}
|
|
@@ -183,8 +184,8 @@ export class mesioDownloader extends EventEmitter {
|
|
|
183
184
|
}
|
|
184
185
|
async stop() {
|
|
185
186
|
try {
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
this.command.kill();
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
188
189
|
await this.streamManager.handleVideoCompleted();
|
|
189
190
|
}
|
|
190
191
|
catch (err) {
|
|
@@ -197,4 +198,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
197
198
|
get videoFilePath() {
|
|
198
199
|
return this.streamManager.videoFilePath;
|
|
199
200
|
}
|
|
201
|
+
cut() {
|
|
202
|
+
throw new Error("Mesio downloader does not support cut operation.");
|
|
203
|
+
}
|
|
200
204
|
}
|
|
@@ -15,8 +15,9 @@ export declare class Segment extends EventEmitter {
|
|
|
15
15
|
rawRecordingVideoPath: string;
|
|
16
16
|
/** 输出文件名名,不包含拓展名 */
|
|
17
17
|
outputVideoFilePath: string;
|
|
18
|
+
disableDanma: boolean;
|
|
18
19
|
videoExt: TrueVideoFormat;
|
|
19
|
-
constructor(getSavePath: GetSavePath, videoExt: TrueVideoFormat);
|
|
20
|
+
constructor(getSavePath: GetSavePath, disableDanma: boolean, videoExt: TrueVideoFormat);
|
|
20
21
|
handleSegmentEnd(): Promise<void>;
|
|
21
22
|
onSegmentStart(stderrLine: string, callBack?: {
|
|
22
23
|
onUpdateLiveInfo: () => Promise<{
|
|
@@ -35,7 +36,7 @@ export declare class StreamManager extends EventEmitter {
|
|
|
35
36
|
recorderType: RecorderType;
|
|
36
37
|
private videoFormat;
|
|
37
38
|
private callBack?;
|
|
38
|
-
constructor(getSavePath: GetSavePath, hasSegment: boolean, recorderType: RecorderType, videoFormat: TrueVideoFormat, callBack?: {
|
|
39
|
+
constructor(getSavePath: GetSavePath, hasSegment: boolean, disableDanma: boolean, recorderType: RecorderType, videoFormat: TrueVideoFormat, callBack?: {
|
|
39
40
|
onUpdateLiveInfo: () => Promise<{
|
|
40
41
|
title?: string;
|
|
41
42
|
cover?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import EventEmitter from "node:events";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import { createRecordExtraDataController } from "../xml_stream_controller.js";
|
|
4
|
-
import {
|
|
4
|
+
import { ensureFolderExist, isFfmpegStartSegment, isMesioStartSegment, isBililiveStartSegment, isFfmpegStart, retry, cleanTerminalText, } from "../utils.js";
|
|
5
5
|
export class Segment extends EventEmitter {
|
|
6
6
|
extraDataController = null;
|
|
7
7
|
init = true;
|
|
@@ -10,10 +10,12 @@ export class Segment extends EventEmitter {
|
|
|
10
10
|
rawRecordingVideoPath;
|
|
11
11
|
/** 输出文件名名,不包含拓展名 */
|
|
12
12
|
outputVideoFilePath;
|
|
13
|
+
disableDanma;
|
|
13
14
|
videoExt;
|
|
14
|
-
constructor(getSavePath, videoExt) {
|
|
15
|
+
constructor(getSavePath, disableDanma, videoExt) {
|
|
15
16
|
super();
|
|
16
17
|
this.getSavePath = getSavePath;
|
|
18
|
+
this.disableDanma = disableDanma;
|
|
17
19
|
this.videoExt = videoExt;
|
|
18
20
|
}
|
|
19
21
|
async handleSegmentEnd() {
|
|
@@ -67,7 +69,9 @@ export class Segment extends EventEmitter {
|
|
|
67
69
|
title: liveInfo?.title,
|
|
68
70
|
});
|
|
69
71
|
ensureFolderExist(this.outputVideoFilePath);
|
|
70
|
-
|
|
72
|
+
if (!this.disableDanma) {
|
|
73
|
+
this.extraDataController = createRecordExtraDataController(`${this.outputVideoFilePath}.xml`);
|
|
74
|
+
}
|
|
71
75
|
// 支持两种格式的正则表达式
|
|
72
76
|
// 1. FFmpeg格式: Opening 'filename' for writing
|
|
73
77
|
// 2. Mesio格式: Opening FLV segment path=filename Processing
|
|
@@ -106,7 +110,7 @@ export class StreamManager extends EventEmitter {
|
|
|
106
110
|
recorderType;
|
|
107
111
|
videoFormat;
|
|
108
112
|
callBack;
|
|
109
|
-
constructor(getSavePath, hasSegment, recorderType, videoFormat, callBack) {
|
|
113
|
+
constructor(getSavePath, hasSegment, disableDanma, recorderType, videoFormat, callBack) {
|
|
110
114
|
super();
|
|
111
115
|
const recordSavePath = getSavePath({ startTime: Date.now() });
|
|
112
116
|
this.recordSavePath = recordSavePath;
|
|
@@ -115,7 +119,7 @@ export class StreamManager extends EventEmitter {
|
|
|
115
119
|
this.hasSegment = hasSegment;
|
|
116
120
|
this.callBack = callBack;
|
|
117
121
|
if (hasSegment) {
|
|
118
|
-
this.segment = new Segment(getSavePath, this.videoExt);
|
|
122
|
+
this.segment = new Segment(getSavePath, disableDanma, this.videoExt);
|
|
119
123
|
this.segment.on("DebugLog", (data) => {
|
|
120
124
|
this.emit("DebugLog", data);
|
|
121
125
|
});
|
|
@@ -127,8 +131,10 @@ export class StreamManager extends EventEmitter {
|
|
|
127
131
|
});
|
|
128
132
|
}
|
|
129
133
|
else {
|
|
130
|
-
const extraDataSavePath =
|
|
131
|
-
|
|
134
|
+
const extraDataSavePath = `${recordSavePath}.xml`;
|
|
135
|
+
if (!disableDanma) {
|
|
136
|
+
this.extraDataController = createRecordExtraDataController(extraDataSavePath);
|
|
137
|
+
}
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
async handleVideoStarted(stderrLine) {
|
package/lib/manager.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export interface RecorderManager<ME extends UnknownObject, P extends RecorderPro
|
|
|
77
77
|
recorders: Recorder<E>[];
|
|
78
78
|
addRecorder: (this: RecorderManager<ME, P, PE, E>, opts: RecorderCreateOpts<E>) => Recorder<E>;
|
|
79
79
|
removeRecorder: (this: RecorderManager<ME, P, PE, E>, recorder: Recorder<E>) => void;
|
|
80
|
+
getRecorder: (this: RecorderManager<ME, P, PE, E>, id: string) => Recorder<E> | null;
|
|
80
81
|
startRecord: (this: RecorderManager<ME, P, PE, E>, id: string) => Promise<Recorder<E> | undefined>;
|
|
81
82
|
stopRecord: (this: RecorderManager<ME, P, PE, E>, id: string) => Promise<Recorder<E> | undefined>;
|
|
82
83
|
cutRecord: (this: RecorderManager<ME, P, PE, E>, id: string) => Promise<Recorder<E> | undefined>;
|
package/lib/manager.js
CHANGED
|
@@ -194,6 +194,10 @@ export function createRecorderManager(opts) {
|
|
|
194
194
|
delete tempBanObj[recorder.channelId];
|
|
195
195
|
this.emit("RecorderRemoved", recorder.toJSON());
|
|
196
196
|
},
|
|
197
|
+
getRecorder(id) {
|
|
198
|
+
const recorder = this.recorders.find((item) => item.id === id);
|
|
199
|
+
return recorder ?? null;
|
|
200
|
+
},
|
|
197
201
|
async startRecord(id) {
|
|
198
202
|
const recorder = this.recorders.find((item) => item.id === id);
|
|
199
203
|
if (recorder == null)
|
package/lib/recorder.d.ts
CHANGED
|
@@ -40,8 +40,8 @@ export interface RecorderCreateOpts<E extends AnyObject = UnknownObject> {
|
|
|
40
40
|
formatName?: FormatName;
|
|
41
41
|
/** 流编码 */
|
|
42
42
|
codecName?: CodecName;
|
|
43
|
-
/** 选择使用的api,虎牙支持: auto,web,mp,抖音支持:web,webHTML,mobile,userHTML */
|
|
44
|
-
api?: "auto" | "web" | "mp" | "webHTML" | "mobile" | "userHTML";
|
|
43
|
+
/** 选择使用的api,虎牙支持: auto,web,mp,wup,抖音支持:web,webHTML,mobile,userHTML */
|
|
44
|
+
api?: "auto" | "web" | "mp" | "wup" | "webHTML" | "mobile" | "userHTML" | "balance" | "random" | string;
|
|
45
45
|
/** 标题关键词,如果直播间标题包含这些关键词,则不会自动录制(仅对斗鱼有效),多个关键词用英文逗号分隔 */
|
|
46
46
|
titleKeywords?: string;
|
|
47
47
|
/** 用于指定录制文件格式,auto时,分段使用ts,不分段使用mp4 */
|