@bililive-tools/douyu-recorder 1.2.0 → 1.4.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/dy_api.d.ts +1 -0
- package/lib/dy_api.js +2 -0
- package/lib/dy_client/index.d.ts +1 -0
- package/lib/dy_client/index.js +6 -1
- package/lib/index.js +34 -9
- package/lib/stream.d.ts +1 -0
- package/lib/stream.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
package/lib/dy_api.d.ts
CHANGED
package/lib/dy_api.js
CHANGED
|
@@ -21,6 +21,8 @@ export async function getLiveInfo(opts) {
|
|
|
21
21
|
cdn: opts.cdn ?? "",
|
|
22
22
|
// 相当于清晰度类型的 id,给 -1 会由后端决定,0为原画
|
|
23
23
|
rate: String(opts.rate ?? 0),
|
|
24
|
+
// 是否只录制音频
|
|
25
|
+
fa: opts.onlyAudio ? "1" : "0",
|
|
24
26
|
}));
|
|
25
27
|
if (res.status !== 200) {
|
|
26
28
|
if (res.status === 403 && res.data === "鉴权失败" && !opts.rejectSignFnCache) {
|
package/lib/dy_client/index.d.ts
CHANGED
package/lib/dy_client/index.js
CHANGED
|
@@ -15,7 +15,12 @@ export function createDYClient(channelId, opts = {}) {
|
|
|
15
15
|
const send = (message) => ws?.send(coder.encode(STT.serialize(message)));
|
|
16
16
|
const sendLogin = () => send({ type: "loginreq", roomid: channelId });
|
|
17
17
|
const sendJoinGroup = () => send({ type: "joingroup", rid: channelId, gid: -9999 });
|
|
18
|
-
const sendHeartbeat = () =>
|
|
18
|
+
const sendHeartbeat = () => {
|
|
19
|
+
if (ws?.readyState !== WebSocket.OPEN) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
send({ type: "mrkl" });
|
|
23
|
+
};
|
|
19
24
|
const sendLogout = () => send({ type: "logout" });
|
|
20
25
|
const onOpen = () => {
|
|
21
26
|
sendLogin();
|
package/lib/index.js
CHANGED
|
@@ -60,7 +60,7 @@ const ffmpegOutputOptions = [
|
|
|
60
60
|
"-movflags",
|
|
61
61
|
"faststart+frag_keyframe+empty_moov",
|
|
62
62
|
"-min_frag_duration",
|
|
63
|
-
"
|
|
63
|
+
"10000000",
|
|
64
64
|
];
|
|
65
65
|
const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isManualStart, }) {
|
|
66
66
|
// 如果已经在录制中,只在需要检查标题关键词时才获取最新信息
|
|
@@ -106,7 +106,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
106
106
|
}
|
|
107
107
|
// 获取直播间信息
|
|
108
108
|
const liveInfo = await getInfo(this.channelId);
|
|
109
|
-
const { living, owner, title
|
|
109
|
+
const { living, owner, title } = liveInfo;
|
|
110
110
|
this.liveInfo = liveInfo;
|
|
111
111
|
if (liveInfo.liveId === banLiveId) {
|
|
112
112
|
this.tempStopIntervalCheck = true;
|
|
@@ -137,7 +137,6 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
137
137
|
return null;
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
this.emit("LiveStart", { liveId });
|
|
141
140
|
let res;
|
|
142
141
|
// TODO: 先不做什么错误处理,就简单包一下预期上会有错误的地方
|
|
143
142
|
try {
|
|
@@ -156,6 +155,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
156
155
|
quality: this.quality,
|
|
157
156
|
source: this.source,
|
|
158
157
|
strictQuality,
|
|
158
|
+
onlyAudio: this.onlyAudio,
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
161
|
catch (err) {
|
|
@@ -170,6 +170,10 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
170
170
|
this.usedStream = stream.name;
|
|
171
171
|
this.usedSource = stream.source;
|
|
172
172
|
const onEnd = (...args) => {
|
|
173
|
+
if (isCutting) {
|
|
174
|
+
isCutting = false;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
173
177
|
if (isEnded)
|
|
174
178
|
return;
|
|
175
179
|
isEnded = true;
|
|
@@ -181,14 +185,18 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
181
185
|
this.recordHandle?.stop(reason);
|
|
182
186
|
};
|
|
183
187
|
let isEnded = false;
|
|
188
|
+
let isCutting = false;
|
|
184
189
|
const recorder = new FFMPEGRecorder({
|
|
185
190
|
url: stream.url,
|
|
186
191
|
outputOptions: ffmpegOutputOptions,
|
|
187
192
|
segment: this.segment ?? 0,
|
|
188
|
-
getSavePath: (opts) => getSavePath({ owner, title, startTime: opts.startTime }),
|
|
193
|
+
getSavePath: (opts) => getSavePath({ owner, title: opts.title ?? title, startTime: opts.startTime }),
|
|
189
194
|
disableDanma: this.disableProvideCommentsWhenRecording,
|
|
190
195
|
videoFormat: this.videoFormat ?? "auto",
|
|
191
|
-
}, onEnd)
|
|
196
|
+
}, onEnd, async () => {
|
|
197
|
+
const info = await getInfo(this.channelId);
|
|
198
|
+
return info;
|
|
199
|
+
});
|
|
192
200
|
const savePath = getSavePath({
|
|
193
201
|
owner,
|
|
194
202
|
title,
|
|
@@ -200,14 +208,20 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
200
208
|
this.state = "idle";
|
|
201
209
|
throw err;
|
|
202
210
|
}
|
|
203
|
-
const handleVideoCreated = async ({ filename }) => {
|
|
204
|
-
this.emit("videoFileCreated", { filename });
|
|
211
|
+
const handleVideoCreated = async ({ filename, title, cover }) => {
|
|
212
|
+
this.emit("videoFileCreated", { filename, cover });
|
|
213
|
+
if (title && this?.liveInfo) {
|
|
214
|
+
this.liveInfo.title = title;
|
|
215
|
+
}
|
|
216
|
+
if (cover && this?.liveInfo) {
|
|
217
|
+
this.liveInfo.cover = cover;
|
|
218
|
+
}
|
|
205
219
|
const extraDataController = recorder.getExtraDataController();
|
|
206
220
|
extraDataController?.setMeta({
|
|
207
221
|
room_id: this.channelId,
|
|
208
222
|
platform: provider?.id,
|
|
209
223
|
liveStartTimestamp: liveInfo.startTime?.getTime(),
|
|
210
|
-
recordStopTimestamp: Date.now(),
|
|
224
|
+
// recordStopTimestamp: Date.now(),
|
|
211
225
|
title: title,
|
|
212
226
|
user_name: owner,
|
|
213
227
|
});
|
|
@@ -236,7 +250,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
236
250
|
case "chatmsg": {
|
|
237
251
|
const comment = {
|
|
238
252
|
type: "comment",
|
|
239
|
-
timestamp:
|
|
253
|
+
timestamp: Number(msg.cst),
|
|
240
254
|
text: msg.txt,
|
|
241
255
|
color: colorTab[msg.col] ?? "#ffffff",
|
|
242
256
|
sender: {
|
|
@@ -370,6 +384,16 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
370
384
|
const ffmpegArgs = recorder.getArguments();
|
|
371
385
|
recorder.run();
|
|
372
386
|
// TODO: 需要一个机制防止空录制,比如检查文件的大小变化、ffmpeg 的输出、直播状态等
|
|
387
|
+
const cut = utils.singleton(async () => {
|
|
388
|
+
if (!this.recordHandle)
|
|
389
|
+
return;
|
|
390
|
+
if (isCutting)
|
|
391
|
+
return;
|
|
392
|
+
isCutting = true;
|
|
393
|
+
await recorder.stop();
|
|
394
|
+
recorder.createCommand();
|
|
395
|
+
recorder.run();
|
|
396
|
+
});
|
|
373
397
|
const stop = utils.singleton(async (reason) => {
|
|
374
398
|
if (!this.recordHandle)
|
|
375
399
|
return;
|
|
@@ -400,6 +424,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
400
424
|
ffmpegArgs,
|
|
401
425
|
savePath: savePath,
|
|
402
426
|
stop,
|
|
427
|
+
cut,
|
|
403
428
|
};
|
|
404
429
|
this.emit("RecordStart", this.recordHandle);
|
|
405
430
|
return this.recordHandle;
|
package/lib/stream.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare function getStream(opts: Pick<Recorder, "channelId" | "quality">
|
|
|
12
12
|
rejectCache?: boolean;
|
|
13
13
|
strictQuality?: boolean;
|
|
14
14
|
source?: string;
|
|
15
|
+
onlyAudio?: boolean;
|
|
15
16
|
}): Promise<{
|
|
16
17
|
living: true;
|
|
17
18
|
sources: import("./dy_api.js").SourceProfile[];
|
package/lib/stream.js
CHANGED
|
@@ -48,6 +48,7 @@ export async function getStream(opts) {
|
|
|
48
48
|
channelId: opts.channelId,
|
|
49
49
|
rate: qn,
|
|
50
50
|
cdn: opts.source === "auto" ? undefined : opts.source,
|
|
51
|
+
onlyAudio: opts.onlyAudio,
|
|
51
52
|
});
|
|
52
53
|
if (!liveInfo.living)
|
|
53
54
|
throw new Error("It must be called getStream when living");
|
|
@@ -68,6 +69,7 @@ export async function getStream(opts) {
|
|
|
68
69
|
liveInfo = await getLiveInfo({
|
|
69
70
|
channelId: opts.channelId,
|
|
70
71
|
rate: liveInfo.streams[0].rate,
|
|
72
|
+
onlyAudio: opts.onlyAudio,
|
|
71
73
|
});
|
|
72
74
|
if (!liveInfo.living)
|
|
73
75
|
throw new Error("It must be called getStream when living");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bililive-tools/douyu-recorder",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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.3.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/ws": "^8.5.13"
|