@bililive-tools/huya-recorder 1.7.1 → 1.9.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 +2 -0
- package/lib/huya_api.js +1 -4
- package/lib/huya_mobile_api.js +6 -8
- package/lib/index.js +66 -26
- package/lib/requester.d.ts +2 -0
- package/lib/requester.js +7 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ interface Options {
|
|
|
37
37
|
quality: number; // 见画质参数
|
|
38
38
|
qualityRetry?: number; // 画质匹配重试次数, -1为强制匹配画质,0为自动配置,正整数为最大匹配次数
|
|
39
39
|
streamPriorities: []; // 废弃
|
|
40
|
+
titleKeywords?: string; // 禁止录制的标题关键字,英文逗号分开多个
|
|
40
41
|
sourcePriorities: []; // 按提供的源优先级去给CDN列表排序,并过滤掉不在优先级配置中的源,在未匹配到的情况下会优先使用TX的CDN,具体参数见 CDN 参数
|
|
41
42
|
formatPriorities?: string[]; // 支持,`flv`和`hls` 参数,默认为['flv','hls']
|
|
42
43
|
disableAutoCheck?: boolean; // 为 true 时 manager 将跳过自动检查
|
|
@@ -47,6 +48,7 @@ interface Options {
|
|
|
47
48
|
api?: "auto" | "mp" | "web"; // 默认为auto,在星秀区使用mp接口,其他使用web接口,你也可以强制指定
|
|
48
49
|
videoFormat?: "auto"; // 视频格式: "auto", "ts", "mkv" ,auto模式下, 分段使用 "ts",不分段使用 "mp4"
|
|
49
50
|
recorderType?: "auto" | "ffmpeg" | "mesio"; // 底层录制器,使用mesio时videoFormat参数无效
|
|
51
|
+
debugLevel?: `verbose` | "basic"; // verbose参数时,录制器会输出更加详细的log
|
|
50
52
|
}
|
|
51
53
|
```
|
|
52
54
|
|
package/lib/huya_api.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { requester } from "./requester.js";
|
|
2
2
|
import { utils } from "@bililive-tools/manager";
|
|
3
3
|
import { assert, getFormatSources } from "./utils.js";
|
|
4
4
|
import { initInfo } from "./anticode.js";
|
|
5
|
-
const requester = axios.create({
|
|
6
|
-
timeout: 10e3,
|
|
7
|
-
});
|
|
8
5
|
export async function getRoomInfo(roomIdOrShortId, opts = {}) {
|
|
9
6
|
const res = await requester.get(`https://www.huya.com/${roomIdOrShortId}`);
|
|
10
7
|
const html = res.data;
|
package/lib/huya_mobile_api.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
// import { createHash, randomInt } from "node:crypto";
|
|
2
2
|
// import { URLSearchParams } from "node:url";
|
|
3
|
-
import
|
|
3
|
+
import { requester } from "./requester.js";
|
|
4
4
|
import { utils } from "@bililive-tools/manager";
|
|
5
5
|
import { assert, getFormatSources } from "./utils.js";
|
|
6
|
-
const requester = axios.create({
|
|
7
|
-
timeout: 10e3,
|
|
8
|
-
headers: {
|
|
9
|
-
"User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko)",
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
6
|
export async function getRoomInfo(roomIdOrShortId, formatPriorities = ["flv", "hls"]) {
|
|
13
|
-
const res = await requester.get(`https://mp.huya.com/cache.php?m=Live&do=profileRoom&roomid=${roomIdOrShortId}
|
|
7
|
+
const res = await requester.get(`https://mp.huya.com/cache.php?m=Live&do=profileRoom&roomid=${roomIdOrShortId}`, {
|
|
8
|
+
headers: {
|
|
9
|
+
"User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko)",
|
|
10
|
+
},
|
|
11
|
+
});
|
|
14
12
|
const html = res.data;
|
|
15
13
|
assert(html, `Unexpected resp, hyPlayerConfig is null`);
|
|
16
14
|
if (res.status !== 200) {
|
package/lib/index.js
CHANGED
|
@@ -56,27 +56,44 @@ function createRecorder(opts) {
|
|
|
56
56
|
});
|
|
57
57
|
return recorderWithSupportUpdatedEvent;
|
|
58
58
|
}
|
|
59
|
-
const ffmpegOutputOptions = [
|
|
60
|
-
|
|
61
|
-
"copy",
|
|
62
|
-
"-movflags",
|
|
63
|
-
"faststart+frag_keyframe+empty_moov",
|
|
64
|
-
"-min_frag_duration",
|
|
65
|
-
"10000000",
|
|
66
|
-
];
|
|
67
|
-
const ffmpegInputOptions = [
|
|
68
|
-
"-reconnect",
|
|
69
|
-
"1",
|
|
70
|
-
"-reconnect_streamed",
|
|
71
|
-
"1",
|
|
72
|
-
"-reconnect_delay_max",
|
|
73
|
-
"10",
|
|
74
|
-
"-rw_timeout",
|
|
75
|
-
"15000000",
|
|
76
|
-
];
|
|
59
|
+
const ffmpegOutputOptions = [];
|
|
60
|
+
const ffmpegInputOptions = ["-rw_timeout", "10000000", "-timeout", "10000000"];
|
|
77
61
|
const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isManualStart, }) {
|
|
78
|
-
|
|
62
|
+
// 如果已经在录制中,只在需要检查标题关键词时才获取最新信息
|
|
63
|
+
if (this.recordHandle != null) {
|
|
64
|
+
// 只有当设置了标题关键词时,并且不是手动启动的录制,才获取最新的直播间信息
|
|
65
|
+
if (utils.shouldCheckTitleKeywords(isManualStart, this.titleKeywords)) {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
// 每5分钟检查一次标题变化
|
|
68
|
+
const titleCheckInterval = 5 * 60 * 1000; // 5分钟
|
|
69
|
+
// 获取上次检查时间
|
|
70
|
+
const lastCheckTime = typeof this.extra.lastTitleCheckTime === "number" ? this.extra.lastTitleCheckTime : 0;
|
|
71
|
+
// 如果距离上次检查时间不足指定间隔,则跳过检查
|
|
72
|
+
if (now - lastCheckTime < titleCheckInterval) {
|
|
73
|
+
return this.recordHandle;
|
|
74
|
+
}
|
|
75
|
+
// 更新检查时间
|
|
76
|
+
this.extra.lastTitleCheckTime = now;
|
|
77
|
+
// 获取直播间信息
|
|
78
|
+
const liveInfo = await getInfo(this.channelId);
|
|
79
|
+
const { title } = liveInfo;
|
|
80
|
+
// 检查标题是否包含关键词
|
|
81
|
+
if (utils.hasBlockedTitleKeywords(title, this.titleKeywords)) {
|
|
82
|
+
this.state = "title-blocked";
|
|
83
|
+
this.emit("DebugLog", {
|
|
84
|
+
type: "common",
|
|
85
|
+
text: `检测到标题包含关键词,停止录制:直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
|
|
86
|
+
});
|
|
87
|
+
// 停止录制
|
|
88
|
+
await this.recordHandle.stop("直播间标题包含关键词");
|
|
89
|
+
// 返回 null,停止录制
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// 已经在录制中,直接返回
|
|
79
94
|
return this.recordHandle;
|
|
95
|
+
}
|
|
96
|
+
// 获取直播间信息
|
|
80
97
|
try {
|
|
81
98
|
const liveInfo = await getInfo(this.channelId);
|
|
82
99
|
this.liveInfo = liveInfo;
|
|
@@ -86,7 +103,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
86
103
|
this.state = "check-error";
|
|
87
104
|
throw error;
|
|
88
105
|
}
|
|
89
|
-
const { living, owner, title } = this.liveInfo;
|
|
106
|
+
const { living, owner, title, startTime } = this.liveInfo;
|
|
90
107
|
if (this.liveInfo.liveId === banLiveId) {
|
|
91
108
|
this.tempStopIntervalCheck = true;
|
|
92
109
|
}
|
|
@@ -97,6 +114,18 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
97
114
|
return null;
|
|
98
115
|
if (!living)
|
|
99
116
|
return null;
|
|
117
|
+
// 检查标题是否包含关键词,如果包含则不自动录制
|
|
118
|
+
// 手动开始录制时不检查标题关键词
|
|
119
|
+
if (utils.shouldCheckTitleKeywords(isManualStart, this.titleKeywords)) {
|
|
120
|
+
if (utils.hasBlockedTitleKeywords(title, this.titleKeywords)) {
|
|
121
|
+
this.state = "title-blocked";
|
|
122
|
+
this.emit("DebugLog", {
|
|
123
|
+
type: "common",
|
|
124
|
+
text: `跳过录制:直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
|
|
125
|
+
});
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
100
129
|
let res;
|
|
101
130
|
// TODO: 先不做什么错误处理,就简单包一下预期上会有错误的地方
|
|
102
131
|
try {
|
|
@@ -149,15 +178,22 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
149
178
|
const reason = args[0] instanceof Error ? args[0].message : String(args[0]);
|
|
150
179
|
this.recordHandle?.stop(reason);
|
|
151
180
|
};
|
|
152
|
-
|
|
153
|
-
const recorder = createBaseRecorder(recorderType, {
|
|
181
|
+
const recordStartTime = new Date();
|
|
182
|
+
const recorder = createBaseRecorder(this.recorderType, {
|
|
154
183
|
url: stream.url,
|
|
155
184
|
outputOptions: ffmpegOutputOptions,
|
|
156
185
|
inputOptions: ffmpegInputOptions,
|
|
157
186
|
segment: this.segment ?? 0,
|
|
158
|
-
getSavePath: (opts) => getSavePath({
|
|
187
|
+
getSavePath: (opts) => getSavePath({
|
|
188
|
+
owner,
|
|
189
|
+
title: opts.title ?? title,
|
|
190
|
+
startTime: opts.startTime,
|
|
191
|
+
liveStartTime: startTime,
|
|
192
|
+
recordStartTime,
|
|
193
|
+
}),
|
|
159
194
|
disableDanma: this.disableProvideCommentsWhenRecording,
|
|
160
195
|
videoFormat: this.videoFormat ?? "auto",
|
|
196
|
+
debugLevel: this.debugLevel ?? "none",
|
|
161
197
|
}, onEnd, async () => {
|
|
162
198
|
const info = await getInfo(this.channelId);
|
|
163
199
|
return info;
|
|
@@ -165,6 +201,9 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
165
201
|
const savePath = getSavePath({
|
|
166
202
|
owner,
|
|
167
203
|
title,
|
|
204
|
+
startTime: Date.now(),
|
|
205
|
+
liveStartTime: startTime,
|
|
206
|
+
recordStartTime,
|
|
168
207
|
});
|
|
169
208
|
try {
|
|
170
209
|
ensureFolderExist(savePath);
|
|
@@ -173,8 +212,8 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
173
212
|
this.state = "idle";
|
|
174
213
|
throw err;
|
|
175
214
|
}
|
|
176
|
-
const handleVideoCreated = async ({ filename, title, cover }) => {
|
|
177
|
-
this.emit("videoFileCreated", { filename, cover });
|
|
215
|
+
const handleVideoCreated = async ({ filename, title, cover, rawFilename }) => {
|
|
216
|
+
this.emit("videoFileCreated", { filename, cover, rawFilename });
|
|
178
217
|
if (title && this?.liveInfo) {
|
|
179
218
|
this.liveInfo.title = title;
|
|
180
219
|
}
|
|
@@ -255,7 +294,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
255
294
|
client.on("retry", (e) => {
|
|
256
295
|
this.emit("DebugLog", {
|
|
257
296
|
type: "common",
|
|
258
|
-
text:
|
|
297
|
+
text: `${this?.liveInfo?.owner}:${this.channelId} huya danmu retry: ${e.count}/${e.max}`,
|
|
259
298
|
});
|
|
260
299
|
});
|
|
261
300
|
client.start();
|
|
@@ -297,6 +336,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
297
336
|
id: genRecordUUID(),
|
|
298
337
|
stream: stream.name,
|
|
299
338
|
source: stream.source,
|
|
339
|
+
recorderType: recorder.type,
|
|
300
340
|
url: stream.url,
|
|
301
341
|
ffmpegArgs,
|
|
302
342
|
savePath: savePath,
|
package/lib/requester.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bililive-tools/huya-recorder",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "bililive-tools huya recorder implemention",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"mitt": "^3.0.1",
|
|
38
38
|
"lodash-es": "^4.17.21",
|
|
39
39
|
"axios": "^1.7.8",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
40
|
+
"huya-danma-listener": "0.1.3",
|
|
41
|
+
"@bililive-tools/manager": "^1.9.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {},
|
|
44
44
|
"scripts": {
|