@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 CHANGED
@@ -47,6 +47,7 @@ interface Options {
47
47
  saveSCDanma?: boolean; // 保存高能弹幕
48
48
  saveCover?: boolean; // 保存封面
49
49
  videoFormat?: "auto"; // 视频格式: "auto", "ts", "mkv" ,auto模式下, 分段使用 "ts",不分段使用 "mp4"
50
+ onlyAudio?: boolean; // 只录制音频,默认为否
50
51
  }
51
52
  ```
52
53
 
package/lib/dy_api.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare function getLiveInfo(opts: {
6
6
  cdn?: string;
7
7
  rate?: number;
8
8
  rejectSignFnCache?: boolean;
9
+ onlyAudio?: boolean;
9
10
  }): Promise<{
10
11
  living: false;
11
12
  } | {
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) {
@@ -17,6 +17,7 @@ interface Message$Chat {
17
17
  dc: string;
18
18
  bdlv: string;
19
19
  ic: string;
20
+ cst: string;
20
21
  }
21
22
  interface Message$Gift {
22
23
  type: "dgb";
@@ -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 = () => send({ type: "mrkl" });
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
- "60000000",
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, liveId } = liveInfo;
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: Date.now(),
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.2.0",
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.2.0"
44
+ "@bililive-tools/manager": "^1.3.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/ws": "^8.5.13"