@bililive-tools/huya-recorder 1.3.1 → 1.6.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 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, formatPriorities?: Array<"flv" | "hls">): Promise<{
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, formatPriorities = ["flv", "hls"]) {
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: item.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: item.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, FFMPEGRecorder, } from "@bililive-tools/manager";
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";
@@ -114,6 +114,8 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
114
114
  });
115
115
  }
116
116
  catch (err) {
117
+ if (this.qualityRetry > 0)
118
+ this.qualityRetry -= 1;
117
119
  this.state = "idle";
118
120
  throw err;
119
121
  }
@@ -135,12 +137,13 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
135
137
  isEnded = true;
136
138
  this.emit("DebugLog", {
137
139
  type: "common",
138
- text: `ffmpeg end, reason: ${JSON.stringify(args, (_, v) => (v instanceof Error ? v.stack : v))}`,
140
+ text: `record end, reason: ${JSON.stringify(args, (_, v) => (v instanceof Error ? v.stack : v))}`,
139
141
  });
140
142
  const reason = args[0] instanceof Error ? args[0].message : String(args[0]);
141
143
  this.recordHandle?.stop(reason);
142
144
  };
143
- const recorder = new FFMPEGRecorder({
145
+ let recorderType = this.recorderType === "mesio" ? "mesio" : "ffmpeg";
146
+ const recorder = createBaseRecorder(recorderType, {
144
147
  url: stream.url,
145
148
  outputOptions: ffmpegOutputOptions,
146
149
  inputOptions: ffmpegInputOptions,
@@ -176,7 +179,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
176
179
  room_id: this.channelId,
177
180
  platform: provider?.id,
178
181
  liveStartTimestamp: liveInfo.startTime?.getTime(),
179
- recordStopTimestamp: Date.now(),
182
+ // recordStopTimestamp: Date.now(),
180
183
  title: title,
181
184
  user_name: owner,
182
185
  });
@@ -242,6 +245,12 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
242
245
  client.on("error", (e) => {
243
246
  this.emit("DebugLog", { type: "common", text: String(e) });
244
247
  });
248
+ client.on("retry", (e) => {
249
+ this.emit("DebugLog", {
250
+ type: "common",
251
+ text: `huya danmu retry: ${e.count}/${e.max}`,
252
+ });
253
+ });
245
254
  client.start();
246
255
  }
247
256
  const ffmpegArgs = recorder.getArguments();
@@ -260,13 +269,13 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
260
269
  if (!this.recordHandle)
261
270
  return;
262
271
  this.state = "stopping-record";
263
- client?.stop();
264
272
  try {
273
+ client?.stop();
265
274
  await recorder.stop();
266
275
  }
267
276
  catch (err) {
268
277
  this.emit("DebugLog", {
269
- type: "common",
278
+ type: "error",
270
279
  text: `stop ffmpeg error: ${String(err)}`,
271
280
  });
272
281
  }
@@ -306,6 +315,7 @@ export const provider = {
306
315
  id: info.roomId.toString(),
307
316
  title: info.title,
308
317
  owner: info.owner,
318
+ avatar: info.avatar,
309
319
  };
310
320
  },
311
321
  createRecorder(opts) {
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, options.formatPriorities);
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, options.formatPriorities);
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");
@@ -70,12 +77,17 @@ export async function getStream(opts) {
70
77
  .replace("&fs=bhct", "&fs=bgct");
71
78
  }
72
79
  }
80
+ let url = expectSource.url;
81
+ // MP协议下原画不需要添加ratio参数
82
+ if (expectStream.bitRate && expectStream.bitRate !== -1 && !url.includes("ratio=")) {
83
+ url = url + "&ratio=" + expectStream.bitRate;
84
+ }
73
85
  return {
74
86
  ...info,
75
87
  currentStream: {
76
88
  name: expectStream.desc,
77
89
  source: expectSource.name,
78
- url: expectSource.url + "&ratio=" + expectStream.bitRate,
90
+ url,
79
91
  },
80
92
  };
81
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bililive-tools/huya-recorder",
3
- "version": "1.3.1",
3
+ "version": "1.6.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
- "huya-danma-listener": "0.1.1",
41
- "@bililive-tools/manager": "^1.3.0"
40
+ "@bililive-tools/manager": "^1.6.0",
41
+ "huya-danma-listener": "0.1.2"
42
42
  },
43
43
  "devDependencies": {},
44
44
  "scripts": {