@bililive-tools/douyu-recorder 1.5.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
@@ -49,6 +49,7 @@ interface Options {
49
49
  saveCover?: boolean; // 保存封面
50
50
  videoFormat?: "auto"; // 视频格式: "auto", "ts", "mkv" ,auto模式下, 分段使用 "ts",不分段使用 "mp4"
51
51
  onlyAudio?: boolean; // 只录制音频,默认为否
52
+ recorderType?: "auto" | "ffmpeg" | "mesio"; // 底层录制器,使用mesio时videoFormat参数无效
52
53
  }
53
54
  ```
54
55
 
@@ -77,6 +78,8 @@ const { id } = await provider.resolveChannelInfoFromURL(url);
77
78
 
78
79
  ## cdn
79
80
 
81
+ 在 `cdn=auto` 且 `recorderType=mesio` 时,默认使用 `hw-h5` 线路
82
+
80
83
  如果有更多线路或者错误,请发issue
81
84
 
82
85
  | 线路 | 值 |
package/lib/danma.js CHANGED
@@ -11,6 +11,59 @@ export const colorTab = {
11
11
  */
12
12
  // 粉丝荧光棒被手动置为0了
13
13
  export const giftMap = {
14
+ "20000": { name: "100鱼丸", pc: 0 },
15
+ "20001": { name: "弱鸡", pc: 20 },
16
+ "20002": { name: "办卡", pc: 600 },
17
+ "20003": { name: "飞机", pc: 10000 },
18
+ "20004": { name: "火箭", pc: 50000 },
19
+ "20005": { name: "超级火箭", pc: 200000 },
20
+ "20006": { name: "赞", pc: 10 },
21
+ "20008": { name: "超大丸星", pc: 0 },
22
+ "20541": { name: "大气", pc: 10 },
23
+ "20542": { name: "666", pc: 100 },
24
+ "23335": { name: "宇宙飞船", pc: 500000 },
25
+ "23338": { name: "告白卡", pc: 600 },
26
+ "23509": { name: "为爱发电", pc: 500 },
27
+ "23515": { name: "星际飞车", pc: 5000 },
28
+ "23622": { name: "至尊飞船", pc: 500000 },
29
+ "23623": { name: "至尊超火", pc: 200000 },
30
+ "23624": { name: "至尊火箭", pc: 50000 },
31
+ "23625": { name: "至尊飞机", pc: 10000 },
32
+ "23669": { name: "钻粉灯牌", pc: 600 },
33
+ "23670": { name: "粉丝灯牌", pc: 600 },
34
+ "24468": { name: "粉丝卡", pc: 600 },
35
+ "24470": { name: "爱的CD", pc: 100 },
36
+ "24472": { name: "怦然心动", pc: 600 },
37
+ "24473": { name: "浪漫旅行车", pc: 6600 },
38
+ "24474": { name: "童话马车", pc: 16600 },
39
+ "24475": { name: "星空丘比特", pc: 131400 },
40
+ "24478": { name: "全力守护", pc: 10 },
41
+ "24489": { name: "老司机", pc: 600 },
42
+ "24490": { name: "牛啤", pc: 600 },
43
+ "24491": { name: "小心心", pc: 10 },
44
+ "24492": { name: "GG", pc: 10 },
45
+ "24493": { name: "炒CP", pc: 5000 },
46
+ "24494": { name: "爱神丘比特", pc: 6600 },
47
+ "24495": { name: "陪伴飞机", pc: 10000 },
48
+ "24496": { name: "挚爱之吻", pc: 100000 },
49
+ "24497": { name: "斗鱼666号", pc: 100000 },
50
+ "24503": { name: "城堡气球", pc: 200000 },
51
+ "24504": { name: "挚爱之心", pc: 300000 },
52
+ "24505": { name: "珍珠奶茶", pc: 100 },
53
+ "24506": { name: "翻车了", pc: 20 },
54
+ "24507": { name: "古堡公主", pc: 18800 },
55
+ "24532": { name: "穿越千年", pc: 200000 },
56
+ "24533": { name: "青瓷絮语", pc: 30000 },
57
+ "24534": { name: "千里江山", pc: 10000 },
58
+ "24535": { name: "烽面惊鸿", pc: 600 },
59
+ "24553": { name: "能量魔方", pc: 5000 },
60
+ "24554": { name: "超能战舰", pc: 200000 },
61
+ "24560": { name: "破空飞机", pc: 10000 },
62
+ "24561": { name: "星际卡", pc: 600 },
63
+ "24597": { name: "高能弹幕", pc: 1000 },
64
+ "24623": { name: "带宽券", pc: 10 },
65
+ "24625": { name: "梦境小熊", pc: 10000 },
66
+ "24626": { name: "星跃猫娘", pc: 50000 },
14
67
  "192": { name: "赞", pc: 10 },
15
68
  "193": { name: "弱鸡", pc: 20 },
16
69
  "194": { name: "666", pc: 600 },
package/lib/dy_api.js CHANGED
@@ -46,12 +46,15 @@ export async function getLiveInfo(opts) {
46
46
  delete signCaches[opts.channelId];
47
47
  throw new Error("Unexpected error code, " + json.error);
48
48
  }
49
- // console.log("json", json, {
50
- // ...signed,
51
- // cdn: opts.cdn ?? "",
52
- // // 相当于清晰度类型的 id,给 -1 会由后端决定,0 为原画
53
- // rate: String(opts.rate ?? 0),
54
- // });
49
+ const streamUrl = `${json.data.rtmp_url}/${json.data.rtmp_live}`;
50
+ let cdn = json.data.rtmp_cdn;
51
+ try {
52
+ const url = new URL(streamUrl);
53
+ cdn = url.searchParams.get("fcdn") ?? "";
54
+ }
55
+ catch (error) {
56
+ console.warn("解析 rtmp_url 失败", error);
57
+ }
55
58
  return {
56
59
  living: true,
57
60
  sources: json.data.cdnsWithName,
@@ -59,12 +62,12 @@ export async function getLiveInfo(opts) {
59
62
  isSupportRateSwitch: json.data.rateSwitch === 1,
60
63
  isOriginalStream: json.data.rateSwitch !== 1,
61
64
  currentStream: {
62
- source: json.data.rtmp_cdn,
65
+ source: cdn,
63
66
  name: json.data.rateSwitch !== 1
64
67
  ? "原画"
65
68
  : (json.data.multirates.find(({ rate }) => rate === json.data.rate)?.name ?? "未知"),
66
69
  rate: json.data.rate,
67
- url: `${json.data.rtmp_url}/${json.data.rtmp_live}`,
70
+ url: streamUrl,
68
71
  },
69
72
  };
70
73
  }
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import mitt from "mitt";
2
- import { defaultFromJSON, defaultToJSON, genRecorderUUID, genRecordUUID, utils, FFMPEGRecorder, } from "@bililive-tools/manager";
2
+ import { defaultFromJSON, defaultToJSON, genRecorderUUID, genRecordUUID, utils, createBaseRecorder, } from "@bililive-tools/manager";
3
3
  import { getInfo, getStream } from "./stream.js";
4
4
  import { getRoomInfo } from "./dy_api.js";
5
5
  import { ensureFolderExist } from "./utils.js";
@@ -138,8 +138,8 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
138
138
  return null;
139
139
  }
140
140
  }
141
+ let recorderType = this.recorderType === "mesio" ? "mesio" : "ffmpeg";
141
142
  let res;
142
- // TODO: 先不做什么错误处理,就简单包一下预期上会有错误的地方
143
143
  try {
144
144
  let strictQuality = false;
145
145
  if (this.qualityRetry > 0) {
@@ -151,17 +151,20 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
151
151
  if (isManualStart) {
152
152
  strictQuality = false;
153
153
  }
154
+ // TODO: 还需要测试仅音频流的情况,mesio可能并不支持
154
155
  res = await getStream({
155
156
  channelId: this.channelId,
156
157
  quality: this.quality,
157
158
  source: this.source,
158
159
  strictQuality,
159
160
  onlyAudio: this.onlyAudio,
161
+ avoidEdgeCDN: recorderType === "mesio",
160
162
  });
161
163
  }
162
164
  catch (err) {
165
+ if (this.qualityRetry > 0)
166
+ this.qualityRetry -= 1;
163
167
  this.state = "idle";
164
- this.qualityRetry -= 1;
165
168
  throw err;
166
169
  }
167
170
  this.state = "recording";
@@ -180,15 +183,16 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
180
183
  isEnded = true;
181
184
  this.emit("DebugLog", {
182
185
  type: "common",
183
- text: `ffmpeg end, reason: ${JSON.stringify(args, (_, v) => (v instanceof Error ? v.stack : v))}`,
186
+ text: `record end, reason: ${JSON.stringify(args, (_, v) => (v instanceof Error ? v.stack : v))}`,
184
187
  });
185
188
  const reason = args[0] instanceof Error ? args[0].message : String(args[0]);
186
189
  this.recordHandle?.stop(reason);
187
190
  };
188
191
  let isEnded = false;
189
192
  let isCutting = false;
190
- const recorder = new FFMPEGRecorder({
193
+ const recorder = createBaseRecorder(recorderType, {
191
194
  url: stream.url,
195
+ // @ts-ignore
192
196
  outputOptions: ffmpegOutputOptions,
193
197
  segment: this.segment ?? 0,
194
198
  getSavePath: (opts) => getSavePath({ owner, title: opts.title ?? title, startTime: opts.startTime }),
@@ -379,7 +383,6 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
379
383
  client.on("error", (err) => {
380
384
  this.emit("DebugLog", { type: "common", text: String(err) });
381
385
  });
382
- // console.log("this.disableProvideCommentsWhenRecording", this.disableProvideCommentsWhenRecording);
383
386
  if (!this.disableProvideCommentsWhenRecording) {
384
387
  client.start();
385
388
  }
package/lib/stream.d.ts CHANGED
@@ -13,6 +13,7 @@ export declare function getStream(opts: Pick<Recorder, "channelId" | "quality">
13
13
  strictQuality?: boolean;
14
14
  source?: string;
15
15
  onlyAudio?: boolean;
16
+ avoidEdgeCDN?: boolean;
16
17
  }): Promise<{
17
18
  living: true;
18
19
  sources: import("./dy_api.js").SourceProfile[];
package/lib/stream.js CHANGED
@@ -44,10 +44,14 @@ export async function getInfo(channelId) {
44
44
  }
45
45
  export async function getStream(opts) {
46
46
  const qn = (DouyuQualities.includes(opts.quality) ? opts.quality : 0);
47
+ let cdn = opts.source === "auto" ? undefined : opts.source;
48
+ if (opts.source === "auto" && opts.avoidEdgeCDN) {
49
+ cdn = "hw-h5";
50
+ }
47
51
  let liveInfo = await getLiveInfo({
48
52
  channelId: opts.channelId,
49
53
  rate: qn,
50
- cdn: opts.source === "auto" ? undefined : opts.source,
54
+ cdn,
51
55
  onlyAudio: opts.onlyAudio,
52
56
  });
53
57
  if (!liveInfo.living)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bililive-tools/douyu-recorder",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "bililive-tools douyu recorder implemention",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "lodash-es": "^4.17.21",
42
42
  "axios": "^1.7.8",
43
43
  "douyu-api": "^0.1.0",
44
- "@bililive-tools/manager": "^1.4.1"
44
+ "@bililive-tools/manager": "^1.6.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/ws": "^8.5.13"