@bililive-tools/manager 1.10.0 → 1.11.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/lib/downloader/BililiveDownloader.d.ts +4 -1
- package/lib/downloader/BililiveDownloader.js +21 -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 +17 -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
|
|
@@ -64,7 +65,6 @@ class BililiveRecorderCommand extends EventEmitter {
|
|
|
64
65
|
this.process.on("error", (error) => {
|
|
65
66
|
this.emit("error", error);
|
|
66
67
|
});
|
|
67
|
-
[];
|
|
68
68
|
this.process.on("close", (code) => {
|
|
69
69
|
if (code === 0) {
|
|
70
70
|
this.emit("end");
|
|
@@ -74,9 +74,15 @@ class BililiveRecorderCommand extends EventEmitter {
|
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
kill(
|
|
77
|
+
kill() {
|
|
78
78
|
if (this.process) {
|
|
79
|
-
this.process.
|
|
79
|
+
this.process.stdin?.write("q\n");
|
|
80
|
+
// this.process.kill("SIGTERM");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
cut() {
|
|
84
|
+
if (this.process) {
|
|
85
|
+
this.process.stdin?.write("s\n");
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
}
|
|
@@ -94,6 +100,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
94
100
|
getSavePath;
|
|
95
101
|
segment;
|
|
96
102
|
inputOptions = [];
|
|
103
|
+
disableDanma = false;
|
|
97
104
|
url;
|
|
98
105
|
debugLevel = "none";
|
|
99
106
|
headers;
|
|
@@ -104,16 +111,20 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
104
111
|
// 存在自动分段,永远为true
|
|
105
112
|
const hasSegment = true;
|
|
106
113
|
this.hasSegment = hasSegment;
|
|
114
|
+
this.disableDanma = opts.disableDanma ?? false;
|
|
107
115
|
this.debugLevel = opts.debugLevel ?? "none";
|
|
108
116
|
let videoFormat = "flv";
|
|
109
|
-
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, "bililive", videoFormat, {
|
|
117
|
+
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "bililive", videoFormat, {
|
|
110
118
|
onUpdateLiveInfo: this.onUpdateLiveInfo,
|
|
111
119
|
});
|
|
112
120
|
this.getSavePath = opts.getSavePath;
|
|
113
121
|
this.inputOptions = [];
|
|
114
122
|
this.url = opts.url;
|
|
115
123
|
this.segment = opts.segment;
|
|
116
|
-
this.headers =
|
|
124
|
+
this.headers = {
|
|
125
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
126
|
+
...(opts.headers || {}),
|
|
127
|
+
};
|
|
117
128
|
this.command = this.createCommand();
|
|
118
129
|
this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
|
|
119
130
|
this.emit("videoFileCreated", { filename, cover, rawFilename, title });
|
|
@@ -126,11 +137,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
126
137
|
});
|
|
127
138
|
}
|
|
128
139
|
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
|
-
];
|
|
140
|
+
const inputOptions = [...this.inputOptions, "--disable-log-file", "true"];
|
|
134
141
|
if (this.debugLevel === "verbose") {
|
|
135
142
|
inputOptions.push("-l", "Debug");
|
|
136
143
|
}
|
|
@@ -187,7 +194,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
187
194
|
async stop() {
|
|
188
195
|
try {
|
|
189
196
|
// 直接发送SIGINT信号,会导致数据丢失
|
|
190
|
-
this.command.kill(
|
|
197
|
+
this.command.kill();
|
|
191
198
|
await this.streamManager.handleVideoCompleted();
|
|
192
199
|
}
|
|
193
200
|
catch (err) {
|
|
@@ -200,4 +207,7 @@ export class BililiveDownloader extends EventEmitter {
|
|
|
200
207
|
get videoFilePath() {
|
|
201
208
|
return this.streamManager.videoFilePath;
|
|
202
209
|
}
|
|
210
|
+
cut() {
|
|
211
|
+
this.command.cut();
|
|
212
|
+
}
|
|
203
213
|
}
|
|
@@ -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 = "";
|
|
@@ -74,9 +74,10 @@ class MesioCommand extends EventEmitter {
|
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
kill(
|
|
77
|
+
kill() {
|
|
78
78
|
if (this.process) {
|
|
79
|
-
this.process.
|
|
79
|
+
this.process.stdin?.write("q");
|
|
80
|
+
this.process.stdin?.end();
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -94,6 +95,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
94
95
|
getSavePath;
|
|
95
96
|
segment;
|
|
96
97
|
inputOptions = [];
|
|
98
|
+
disableDanma = false;
|
|
97
99
|
url;
|
|
98
100
|
debugLevel = "none";
|
|
99
101
|
headers;
|
|
@@ -104,6 +106,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
104
106
|
// 存在自动分段,永远为true
|
|
105
107
|
const hasSegment = true;
|
|
106
108
|
this.hasSegment = hasSegment;
|
|
109
|
+
this.disableDanma = opts.disableDanma ?? false;
|
|
107
110
|
this.debugLevel = opts.debugLevel ?? "none";
|
|
108
111
|
let videoFormat = "flv";
|
|
109
112
|
if (opts.url.includes(".m3u8")) {
|
|
@@ -118,14 +121,17 @@ export class mesioDownloader extends EventEmitter {
|
|
|
118
121
|
else if (opts.formatName === "flv") {
|
|
119
122
|
videoFormat = "flv";
|
|
120
123
|
}
|
|
121
|
-
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, "mesio", videoFormat, {
|
|
124
|
+
this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "mesio", videoFormat, {
|
|
122
125
|
onUpdateLiveInfo: this.onUpdateLiveInfo,
|
|
123
126
|
});
|
|
124
127
|
this.getSavePath = opts.getSavePath;
|
|
125
128
|
this.inputOptions = [];
|
|
126
129
|
this.url = opts.url;
|
|
127
130
|
this.segment = opts.segment;
|
|
128
|
-
this.headers =
|
|
131
|
+
this.headers = {
|
|
132
|
+
"User-Agent": DEFAULT_USER_AGENT,
|
|
133
|
+
...(opts.headers || {}),
|
|
134
|
+
};
|
|
129
135
|
this.command = this.createCommand();
|
|
130
136
|
this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
|
|
131
137
|
this.emit("videoFileCreated", { filename, cover, rawFilename, title });
|
|
@@ -138,13 +144,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
138
144
|
});
|
|
139
145
|
}
|
|
140
146
|
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
|
-
];
|
|
147
|
+
const inputOptions = [...this.inputOptions, "--fix", "--no-proxy"];
|
|
148
148
|
if (this.debugLevel === "verbose") {
|
|
149
149
|
inputOptions.push("-v");
|
|
150
150
|
}
|
|
@@ -183,8 +183,8 @@ export class mesioDownloader extends EventEmitter {
|
|
|
183
183
|
}
|
|
184
184
|
async stop() {
|
|
185
185
|
try {
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
this.command.kill();
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
188
188
|
await this.streamManager.handleVideoCompleted();
|
|
189
189
|
}
|
|
190
190
|
catch (err) {
|
|
@@ -197,4 +197,7 @@ export class mesioDownloader extends EventEmitter {
|
|
|
197
197
|
get videoFilePath() {
|
|
198
198
|
return this.streamManager.videoFilePath;
|
|
199
199
|
}
|
|
200
|
+
cut() {
|
|
201
|
+
throw new Error("Mesio downloader does not support cut operation.");
|
|
202
|
+
}
|
|
200
203
|
}
|
|
@@ -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 */
|