@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 +3 -0
- package/lib/danma.js +53 -0
- package/lib/dy_api.js +11 -8
- package/lib/index.js +9 -6
- package/lib/stream.d.ts +1 -0
- package/lib/stream.js +5 -1
- package/package.json +2 -2
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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: `
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
44
|
+
"@bililive-tools/manager": "^1.6.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/ws": "^8.5.13"
|