@bililive-tools/huya-recorder 1.3.2 → 1.7.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 +1 -0
- package/lib/huya_api.d.ts +5 -1
- package/lib/huya_api.js +13 -4
- package/lib/index.js +18 -9
- package/lib/stream.d.ts +1 -0
- package/lib/stream.js +10 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ interface Options {
|
|
|
46
46
|
saveCover?: boolean; // 保存封面
|
|
47
47
|
api?: "auto" | "mp" | "web"; // 默认为auto,在星秀区使用mp接口,其他使用web接口,你也可以强制指定
|
|
48
48
|
videoFormat?: "auto"; // 视频格式: "auto", "ts", "mkv" ,auto模式下, 分段使用 "ts",不分段使用 "mp4"
|
|
49
|
+
recorderType?: "auto" | "ffmpeg" | "mesio"; // 底层录制器,使用mesio时videoFormat参数无效
|
|
49
50
|
}
|
|
50
51
|
```
|
|
51
52
|
|
package/lib/huya_api.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import type { Recorder } from "@bililive-tools/manager";
|
|
1
2
|
import type { StreamProfile } from "./types.js";
|
|
2
|
-
export declare function getRoomInfo(roomIdOrShortId: string,
|
|
3
|
+
export declare function getRoomInfo(roomIdOrShortId: string, opts?: {
|
|
4
|
+
formatPriorities?: Array<"flv" | "hls">;
|
|
5
|
+
quality?: Recorder["quality"];
|
|
6
|
+
}): Promise<{
|
|
3
7
|
living: boolean;
|
|
4
8
|
id: number;
|
|
5
9
|
owner: string;
|
package/lib/huya_api.js
CHANGED
|
@@ -5,7 +5,7 @@ import { initInfo } from "./anticode.js";
|
|
|
5
5
|
const requester = axios.create({
|
|
6
6
|
timeout: 10e3,
|
|
7
7
|
});
|
|
8
|
-
export async function getRoomInfo(roomIdOrShortId,
|
|
8
|
+
export async function getRoomInfo(roomIdOrShortId, opts = {}) {
|
|
9
9
|
const res = await requester.get(`https://www.huya.com/${roomIdOrShortId}`);
|
|
10
10
|
const html = res.data;
|
|
11
11
|
const match = html.match(/var hyPlayerConfig = ({[^]+?};)/);
|
|
@@ -18,6 +18,7 @@ export async function getRoomInfo(roomIdOrShortId, formatPriorities = ["flv", "h
|
|
|
18
18
|
desc: info.sDisplayName,
|
|
19
19
|
bitRate: info.iBitRate,
|
|
20
20
|
}));
|
|
21
|
+
streams.push({ desc: "真原画", bitRate: -1 });
|
|
21
22
|
const data = hyPlayerConfig.stream.data[0];
|
|
22
23
|
assert(data, `Unexpected resp, data is null`);
|
|
23
24
|
const sources = {
|
|
@@ -35,9 +36,13 @@ export async function getRoomInfo(roomIdOrShortId, formatPriorities = ["flv", "h
|
|
|
35
36
|
// }));
|
|
36
37
|
for (const item of data?.gameStreamInfoList ?? []) {
|
|
37
38
|
if (item.sFlvAntiCode && item.sFlvAntiCode.length > 0) {
|
|
39
|
+
let sStreamName = item.sStreamName;
|
|
40
|
+
if (opts.quality === -1) {
|
|
41
|
+
sStreamName = sStreamName.replace("-imgplus", "");
|
|
42
|
+
}
|
|
38
43
|
const url = initInfo({
|
|
39
44
|
baseUrl: item.sFlvUrl,
|
|
40
|
-
sStreamName:
|
|
45
|
+
sStreamName: sStreamName,
|
|
41
46
|
antiCode: item.sFlvAntiCode,
|
|
42
47
|
suffix: item.sFlvUrlSuffix,
|
|
43
48
|
_sessionId: Date.now(),
|
|
@@ -48,9 +53,13 @@ export async function getRoomInfo(roomIdOrShortId, formatPriorities = ["flv", "h
|
|
|
48
53
|
});
|
|
49
54
|
}
|
|
50
55
|
if (item.sHlsAntiCode && item.sHlsAntiCode.length > 0) {
|
|
56
|
+
let sStreamName = item.sStreamName;
|
|
57
|
+
if (opts.quality === -1) {
|
|
58
|
+
sStreamName = sStreamName.replace("-imgplus", "");
|
|
59
|
+
}
|
|
51
60
|
const url = initInfo({
|
|
52
61
|
baseUrl: item.sHlsUrl,
|
|
53
|
-
sStreamName:
|
|
62
|
+
sStreamName: sStreamName,
|
|
54
63
|
antiCode: item.sHlsAntiCode,
|
|
55
64
|
suffix: item.sHlsUrlSuffix,
|
|
56
65
|
_sessionId: Date.now(),
|
|
@@ -62,7 +71,7 @@ export async function getRoomInfo(roomIdOrShortId, formatPriorities = ["flv", "h
|
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
const startTime = new Date(data.gameLiveInfo?.startTime * 1000);
|
|
65
|
-
const formatSources = getFormatSources(sources, formatPriorities);
|
|
74
|
+
const formatSources = getFormatSources(sources, opts.formatPriorities);
|
|
66
75
|
return {
|
|
67
76
|
living: vMultiStreamInfo.length > 0 && data.gameStreamInfoList.length > 0,
|
|
68
77
|
id: data.gameLiveInfo.profileRoom,
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import mitt from "mitt";
|
|
3
|
-
import { defaultFromJSON, defaultToJSON, genRecorderUUID, genRecordUUID, utils,
|
|
3
|
+
import { defaultFromJSON, defaultToJSON, genRecorderUUID, genRecordUUID, utils, createBaseRecorder, } from "@bililive-tools/manager";
|
|
4
4
|
import { getInfo, getStream } from "./stream.js";
|
|
5
5
|
import { ensureFolderExist } from "./utils.js";
|
|
6
6
|
import HuYaDanMu from "huya-danma-listener";
|
|
@@ -77,10 +77,16 @@ const ffmpegInputOptions = [
|
|
|
77
77
|
const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isManualStart, }) {
|
|
78
78
|
if (this.recordHandle != null)
|
|
79
79
|
return this.recordHandle;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
try {
|
|
81
|
+
const liveInfo = await getInfo(this.channelId);
|
|
82
|
+
this.liveInfo = liveInfo;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
this.state = "check-error";
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
const { living, owner, title } = this.liveInfo;
|
|
89
|
+
if (this.liveInfo.liveId === banLiveId) {
|
|
84
90
|
this.tempStopIntervalCheck = true;
|
|
85
91
|
}
|
|
86
92
|
else {
|
|
@@ -114,7 +120,9 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
114
120
|
});
|
|
115
121
|
}
|
|
116
122
|
catch (err) {
|
|
117
|
-
this.
|
|
123
|
+
if (this.qualityRetry > 0)
|
|
124
|
+
this.qualityRetry -= 1;
|
|
125
|
+
this.state = "check-error";
|
|
118
126
|
throw err;
|
|
119
127
|
}
|
|
120
128
|
this.state = "recording";
|
|
@@ -135,12 +143,13 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
135
143
|
isEnded = true;
|
|
136
144
|
this.emit("DebugLog", {
|
|
137
145
|
type: "common",
|
|
138
|
-
text: `
|
|
146
|
+
text: `record end, reason: ${JSON.stringify(args, (_, v) => (v instanceof Error ? v.stack : v))}`,
|
|
139
147
|
});
|
|
140
148
|
const reason = args[0] instanceof Error ? args[0].message : String(args[0]);
|
|
141
149
|
this.recordHandle?.stop(reason);
|
|
142
150
|
};
|
|
143
|
-
|
|
151
|
+
let recorderType = this.recorderType === "mesio" ? "mesio" : "ffmpeg";
|
|
152
|
+
const recorder = createBaseRecorder(recorderType, {
|
|
144
153
|
url: stream.url,
|
|
145
154
|
outputOptions: ffmpegOutputOptions,
|
|
146
155
|
inputOptions: ffmpegInputOptions,
|
|
@@ -175,7 +184,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
175
184
|
extraDataController?.setMeta({
|
|
176
185
|
room_id: this.channelId,
|
|
177
186
|
platform: provider?.id,
|
|
178
|
-
liveStartTimestamp: liveInfo
|
|
187
|
+
liveStartTimestamp: this?.liveInfo?.startTime?.getTime(),
|
|
179
188
|
// recordStopTimestamp: Date.now(),
|
|
180
189
|
title: title,
|
|
181
190
|
user_name: owner,
|
package/lib/stream.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare function getInfo(channelId: string): Promise<{
|
|
|
12
12
|
}>;
|
|
13
13
|
export declare function getStream(opts: Pick<Recorder, "channelId" | "quality" | "streamPriorities" | "sourcePriorities" | "api" | "formatPriorities"> & {
|
|
14
14
|
strictQuality?: boolean;
|
|
15
|
+
api?: "web" | "mp" | "auto";
|
|
15
16
|
}): Promise<{
|
|
16
17
|
currentStream: {
|
|
17
18
|
name: string;
|
package/lib/stream.js
CHANGED
|
@@ -18,7 +18,10 @@ export async function getInfo(channelId) {
|
|
|
18
18
|
}
|
|
19
19
|
async function getRoomInfo(channelId, options) {
|
|
20
20
|
if (options.api == "auto") {
|
|
21
|
-
const info = await getRoomInfoByWeb(channelId,
|
|
21
|
+
const info = await getRoomInfoByWeb(channelId, {
|
|
22
|
+
formatPriorities: options.formatPriorities,
|
|
23
|
+
quality: options.quality,
|
|
24
|
+
});
|
|
22
25
|
if (info.gid == 1663) {
|
|
23
26
|
return getRoomInfoByMobile(channelId, options.formatPriorities);
|
|
24
27
|
}
|
|
@@ -28,7 +31,10 @@ async function getRoomInfo(channelId, options) {
|
|
|
28
31
|
return getRoomInfoByMobile(channelId, options.formatPriorities);
|
|
29
32
|
}
|
|
30
33
|
else if (options.api == "web") {
|
|
31
|
-
return getRoomInfoByWeb(channelId,
|
|
34
|
+
return getRoomInfoByWeb(channelId, {
|
|
35
|
+
formatPriorities: options.formatPriorities,
|
|
36
|
+
quality: options.quality,
|
|
37
|
+
});
|
|
32
38
|
}
|
|
33
39
|
assert(false, "Invalid api");
|
|
34
40
|
}
|
|
@@ -36,6 +42,7 @@ export async function getStream(opts) {
|
|
|
36
42
|
const info = await getRoomInfo(opts.channelId, {
|
|
37
43
|
api: opts.api ?? "auto",
|
|
38
44
|
formatPriorities: opts.formatPriorities ?? ["flv", "hls"],
|
|
45
|
+
quality: opts.quality,
|
|
39
46
|
});
|
|
40
47
|
if (!info.living) {
|
|
41
48
|
throw new Error("It must be called getStream when living");
|
|
@@ -72,7 +79,7 @@ export async function getStream(opts) {
|
|
|
72
79
|
}
|
|
73
80
|
let url = expectSource.url;
|
|
74
81
|
// MP协议下原画不需要添加ratio参数
|
|
75
|
-
if (expectStream.bitRate) {
|
|
82
|
+
if (expectStream.bitRate && expectStream.bitRate !== -1 && !url.includes("ratio=")) {
|
|
76
83
|
url = url + "&ratio=" + expectStream.bitRate;
|
|
77
84
|
}
|
|
78
85
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bililive-tools/huya-recorder",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.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.2",
|
|
41
|
+
"@bililive-tools/manager": "^1.6.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {},
|
|
44
44
|
"scripts": {
|