@bililive-tools/bilibili-recorder 1.0.1 → 1.1.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
@@ -6,6 +6,8 @@
6
6
 
7
7
  # 安装
8
8
 
9
+ **建议所有录制器和manager包都升级到最新版,我不会对兼容性做过多考虑**
10
+
9
11
  `npm i @bililive-tools/bilibili-recorder @bililive-tools/manager`
10
12
 
11
13
  # 使用
@@ -33,11 +35,11 @@ manager.startCheckLoop();
33
35
  interface Options {
34
36
  channelId: string; // 长直播间ID,具体解析见文档,也可自行解析
35
37
  quality: number; // 见画质参数
36
- qualityRetry?: number; // 画质匹配重试次数
38
+ qualityRetry?: number; // 画质匹配重试次数, -1为强制匹配画质,0为自动配置,正整数为最大匹配次数
37
39
  streamPriorities: []; // 废弃
38
40
  sourcePriorities: []; // 废弃
39
41
  disableAutoCheck?: boolean; // 为 true 时 manager 将跳过自动检查
40
- segment?: number; // 分段参数
42
+ segment?: number; // 分段参数,单位分钟
41
43
  disableProvideCommentsWhenRecording?: boolean; // 禁用弹幕录制
42
44
  saveGiftDanma?: boolean; // 保存礼物弹幕,包含舰长
43
45
  saveSCDanma?: boolean; // 保存SC
@@ -49,6 +49,7 @@ export declare function getRoomBaseInfo<RoomId extends number>(roomId: RoomId):
49
49
  live_time: string;
50
50
  live_status: LiveStatus;
51
51
  cover: string;
52
+ is_encrypted: boolean;
52
53
  }>>;
53
54
  export declare function getPlayURL(roomId: number, opts?: {
54
55
  useHLS?: boolean;
@@ -85,7 +86,7 @@ export declare function getRoomPlayInfo(roomIdOrShortId: number, opts?: {
85
86
  };
86
87
  }>;
87
88
  export interface ProtocolInfo {
88
- protocol_name: string | "http_stream" | "http_hls";
89
+ protocol_name: "http_stream" | "http_hls";
89
90
  format: FormatInfo[];
90
91
  }
91
92
  export interface FormatInfo {
package/lib/danma.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { EventEmitter } from "node:events";
2
+ declare class DanmaClient extends EventEmitter {
3
+ private client;
4
+ private roomId;
5
+ private auth;
6
+ private uid;
7
+ private retryCount;
8
+ constructor(roomId: number, auth: string | undefined, uid: number | undefined);
9
+ start(): void;
10
+ stop(): void;
11
+ }
12
+ export default DanmaClient;
package/lib/danma.js ADDED
@@ -0,0 +1,124 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { startListen } from "./blive-message-listener/index.js";
3
+ class DanmaClient extends EventEmitter {
4
+ client = null;
5
+ roomId;
6
+ auth;
7
+ uid;
8
+ retryCount = 5;
9
+ constructor(roomId, auth, uid) {
10
+ super();
11
+ this.roomId = roomId;
12
+ this.auth = auth;
13
+ this.uid = uid;
14
+ }
15
+ start() {
16
+ const handler = {
17
+ onIncomeDanmu: (msg) => {
18
+ let content = msg.body.content;
19
+ content = content.replace(/(^\s*)|(\s*$)/g, "").replace(/[\r\n]/g, "");
20
+ if (content === "")
21
+ return;
22
+ const comment = {
23
+ type: "comment",
24
+ timestamp: msg.timestamp,
25
+ text: content,
26
+ color: msg.body.content_color,
27
+ mode: msg.body.type,
28
+ sender: {
29
+ uid: String(msg.body.user.uid),
30
+ name: msg.body.user.uname,
31
+ avatar: msg.body.user.face,
32
+ extra: {
33
+ badgeName: msg.body.user.badge?.name,
34
+ badgeLevel: msg.body.user.badge?.level,
35
+ },
36
+ },
37
+ };
38
+ this.emit("Message", comment);
39
+ },
40
+ onIncomeSuperChat: (msg) => {
41
+ const content = msg.body.content.replaceAll(/[\r\n]/g, "");
42
+ const comment = {
43
+ type: "super_chat",
44
+ timestamp: msg.timestamp,
45
+ text: content,
46
+ price: msg.body.price,
47
+ sender: {
48
+ uid: String(msg.body.user.uid),
49
+ name: msg.body.user.uname,
50
+ avatar: msg.body.user.face,
51
+ extra: {
52
+ badgeName: msg.body.user.badge?.name,
53
+ badgeLevel: msg.body.user.badge?.level,
54
+ },
55
+ },
56
+ };
57
+ this.emit("Message", comment);
58
+ },
59
+ onGuardBuy: (msg) => {
60
+ const gift = {
61
+ type: "guard",
62
+ timestamp: msg.timestamp,
63
+ name: msg.body.gift_name,
64
+ price: msg.body.price,
65
+ count: 1,
66
+ level: msg.body.guard_level,
67
+ sender: {
68
+ uid: String(msg.body.user.uid),
69
+ name: msg.body.user.uname,
70
+ avatar: msg.body.user.face,
71
+ extra: {
72
+ badgeName: msg.body.user.badge?.name,
73
+ badgeLevel: msg.body.user.badge?.level,
74
+ },
75
+ },
76
+ };
77
+ this.emit("Message", gift);
78
+ },
79
+ onGift: (msg) => {
80
+ const gift = {
81
+ type: "give_gift",
82
+ timestamp: msg.timestamp,
83
+ name: msg.body.gift_name,
84
+ count: msg.body.amount,
85
+ price: msg.body.coin_type === "silver" ? 0 : msg.body.price / 1000,
86
+ sender: {
87
+ uid: String(msg.body.user.uid),
88
+ name: msg.body.user.uname,
89
+ avatar: msg.body.user.face,
90
+ extra: {
91
+ badgeName: msg.body.user.badge?.name,
92
+ badgeLevel: msg.body.user.badge?.level,
93
+ },
94
+ },
95
+ extra: {
96
+ hits: msg.body.combo?.combo_num,
97
+ },
98
+ };
99
+ this.emit("Message", gift);
100
+ },
101
+ };
102
+ this.client = startListen(this.roomId, handler, {
103
+ ws: {
104
+ headers: {
105
+ Cookie: this.auth ?? "",
106
+ },
107
+ uid: this.uid ?? 0,
108
+ },
109
+ });
110
+ this.client.live.on("error", (err) => {
111
+ this.retryCount -= 1;
112
+ if (this.retryCount > 0) {
113
+ setTimeout(() => {
114
+ this.client && this.client.reconnect();
115
+ }, 2000 * (5 - this.retryCount));
116
+ }
117
+ this.emit("error", err);
118
+ });
119
+ }
120
+ stop() {
121
+ this.client?.close();
122
+ }
123
+ }
124
+ export default DanmaClient;
package/lib/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { RecorderProvider } from "@bililive-tools/manager";
1
+ import type { RecorderProvider } from "@bililive-tools/manager";
2
2
  export declare const provider: RecorderProvider<Record<string, unknown>>;
package/lib/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
2
  import mitt from "mitt";
3
- import { createFFMPEGBuilder, defaultFromJSON, defaultToJSON, genRecorderUUID, genRecordUUID, StreamManager, utils, } from "@bililive-tools/manager";
3
+ import { defaultFromJSON, defaultToJSON, genRecorderUUID, genRecordUUID, utils, FFMPEGRecorder, } from "@bililive-tools/manager";
4
4
  import { getInfo, getStream, getLiveStatus, getStrictStream } from "./stream.js";
5
- import { assertStringType, ensureFolderExist, createInvalidStreamChecker } from "./utils.js";
6
- import { startListen } from "./blive-message-listener/index.js";
5
+ import { ensureFolderExist } from "./utils.js";
6
+ import DanmaClient from "./danma.js";
7
7
  function createRecorder(opts) {
8
8
  // 内部实现时,应该只有 proxy 包裹的那一层会使用这个 recorder 标识符,不应该有直接通过
9
9
  // 此标志来操作这个对象的地方,不然会跳过 proxy 的拦截。
@@ -68,27 +68,32 @@ const ffmpegOutputOptions = [
68
68
  "faststart+frag_keyframe+empty_moov",
69
69
  "-min_frag_duration",
70
70
  "60000000",
71
+ ];
72
+ const ffmpegInputOptions = [
71
73
  "-reconnect",
72
74
  "1",
73
75
  "-reconnect_streamed",
74
76
  "1",
75
77
  "-reconnect_delay_max",
76
- "5",
78
+ "10",
77
79
  "-rw_timeout",
78
- "5000000",
80
+ "15000000",
81
+ "-headers",
82
+ "Referer:https://live.bilibili.com/",
79
83
  ];
80
- const checkLiveStatusAndRecord = async function ({ getSavePath, qualityRetry, banLiveId, }) {
84
+ const checkLiveStatusAndRecord = async function ({ getSavePath, isManualStart, banLiveId, }) {
81
85
  if (this.recordHandle != null)
82
86
  return this.recordHandle;
83
- const { living, liveId } = await getLiveStatus(this.channelId);
87
+ const { living, liveId, owner: _owner, title: _title } = await getLiveStatus(this.channelId);
84
88
  this.liveInfo = {
85
89
  living,
86
- owner: "",
87
- title: "",
90
+ owner: _owner,
91
+ title: _title,
88
92
  avatar: "",
89
93
  cover: "",
90
- liveId: "",
94
+ liveId: liveId,
91
95
  };
96
+ this.emit("LiveStart", { liveId });
92
97
  if (liveId === banLiveId) {
93
98
  this.tempStopIntervalCheck = true;
94
99
  }
@@ -100,15 +105,20 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, qualityRetry, ba
100
105
  if (!living)
101
106
  return null;
102
107
  const liveInfo = await getInfo(this.channelId);
103
- const { owner, title, roomId, cover } = liveInfo;
108
+ const { owner, title, roomId } = liveInfo;
104
109
  this.liveInfo = liveInfo;
105
- this.state = "recording";
106
110
  let res;
107
111
  // TODO: 先不做什么错误处理,就简单包一下预期上会有错误的地方
108
112
  try {
109
- let strictQuality = !!this.qualityRetry;
110
- if (qualityRetry !== undefined) {
111
- strictQuality = !!qualityRetry;
113
+ let strictQuality = false;
114
+ if (this.qualityRetry > 0) {
115
+ strictQuality = true;
116
+ }
117
+ if (this.qualityMaxRetry < 0) {
118
+ strictQuality = true;
119
+ }
120
+ if (isManualStart) {
121
+ strictQuality = false;
112
122
  }
113
123
  res = await getStream({
114
124
  channelId: this.channelId,
@@ -124,6 +134,7 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, qualityRetry, ba
124
134
  this.state = "idle";
125
135
  throw err;
126
136
  }
137
+ this.state = "recording";
127
138
  const { streamOptions, currentStream: stream, sources: availableSources, streams: availableStreams, } = res;
128
139
  this.availableStreams = availableStreams.map((s) => s.desc);
129
140
  this.availableSources = availableSources.map((s) => s.name);
@@ -145,16 +156,40 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, qualityRetry, ba
145
156
  format_name: streamOptions.format_name,
146
157
  codec_name: streamOptions.codec_name,
147
158
  });
148
- stream.url = url;
159
+ if (this.recordHandle) {
160
+ this.recordHandle.url = url;
161
+ }
149
162
  this.emit("DebugLog", {
150
163
  type: "common",
151
164
  text: `update stream: ${url}`,
152
165
  });
153
166
  }, 50 * 60 * 1000);
154
167
  }
155
- // console.log(streamOptions.protocol_name, url);
156
- const savePath = getSavePath({ owner, title });
157
- const hasSegment = !!this.segment;
168
+ let isEnded = false;
169
+ const onEnd = (...args) => {
170
+ if (isEnded)
171
+ return;
172
+ isEnded = true;
173
+ this.emit("DebugLog", {
174
+ type: "common",
175
+ text: `ffmpeg end, reason: ${JSON.stringify(args, (_, v) => (v instanceof Error ? v.stack : v))}`,
176
+ });
177
+ const reason = args[0] instanceof Error ? args[0].message : String(args[0]);
178
+ this.recordHandle?.stop(reason);
179
+ };
180
+ const recorder = new FFMPEGRecorder({
181
+ url: url,
182
+ outputOptions: ffmpegOutputOptions,
183
+ inputOptions: ffmpegInputOptions,
184
+ segment: this.segment ?? 0,
185
+ getSavePath: (opts) => getSavePath({ owner, title, startTime: opts.startTime }),
186
+ isHls: streamOptions.protocol_name === "http_hls",
187
+ disableDanma: this.disableProvideCommentsWhenRecording,
188
+ }, onEnd);
189
+ const savePath = getSavePath({
190
+ owner,
191
+ title,
192
+ });
158
193
  try {
159
194
  ensureFolderExist(savePath);
160
195
  }
@@ -162,221 +197,70 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, qualityRetry, ba
162
197
  this.state = "idle";
163
198
  throw err;
164
199
  }
165
- const streamManager = new StreamManager(this, getSavePath, owner, title, savePath, hasSegment);
166
200
  const handleVideoCreated = async ({ filename }) => {
167
- const extraDataController = streamManager?.getExtraDataController();
201
+ this.emit("videoFileCreated", { filename });
202
+ const extraDataController = recorder.getExtraDataController();
168
203
  extraDataController?.setMeta({
169
204
  room_id: String(roomId),
170
205
  platform: provider?.id,
171
206
  liveStartTimestamp: liveInfo.startTime?.getTime(),
207
+ recordStopTimestamp: Date.now(),
208
+ title: title,
209
+ user_name: owner,
172
210
  });
173
- if (this.saveCover) {
174
- const coverPath = utils.replaceExtName(filename, ".jpg");
175
- utils.downloadImage(cover, coverPath);
176
- }
177
211
  };
178
- this.on("videoFileCreated", handleVideoCreated);
179
- let client = null;
212
+ recorder.on("videoFileCreated", handleVideoCreated);
213
+ recorder.on("videoFileCompleted", ({ filename }) => {
214
+ this.emit("videoFileCompleted", { filename });
215
+ });
216
+ recorder.on("DebugLog", (data) => {
217
+ this.emit("DebugLog", data);
218
+ });
219
+ recorder.on("progress", (progress) => {
220
+ if (this.recordHandle) {
221
+ this.recordHandle.progress = progress;
222
+ }
223
+ this.emit("progress", progress);
224
+ });
225
+ let danmaClient = new DanmaClient(roomId, this.auth, this.uid);
180
226
  if (!this.disableProvideCommentsWhenRecording) {
181
- const handler = {
182
- onIncomeDanmu: (msg) => {
183
- const extraDataController = streamManager.getExtraDataController();
184
- if (!extraDataController)
185
- return;
186
- let content = msg.body.content;
187
- // 去除前后空格,回车,换行
188
- content = content.replace(/(^\s*)|(\s*$)/g, "").replace(/[\r\n]/g, "");
189
- if (content === "")
190
- return;
191
- const comment = {
192
- type: "comment",
193
- timestamp: msg.timestamp,
194
- text: content,
195
- color: msg.body.content_color,
196
- mode: msg.body.type,
197
- sender: {
198
- uid: String(msg.body.user.uid),
199
- name: msg.body.user.uname,
200
- avatar: msg.body.user.face,
201
- extra: {
202
- badgeName: msg.body.user.badge?.name,
203
- badgeLevel: msg.body.user.badge?.level,
204
- },
205
- },
206
- };
207
- this.emit("Message", comment);
208
- extraDataController.addMessage(comment);
209
- },
210
- onIncomeSuperChat: (msg) => {
211
- const extraDataController = streamManager.getExtraDataController();
212
- if (!extraDataController)
213
- return;
214
- if (this.saveSCDanma === false)
215
- return;
216
- const content = msg.body.content.replaceAll(/[\r\n]/g, "");
217
- // console.log(msg.id, msg.body);
218
- const comment = {
219
- type: "super_chat",
220
- timestamp: msg.timestamp,
221
- text: content,
222
- price: msg.body.price,
223
- sender: {
224
- uid: String(msg.body.user.uid),
225
- name: msg.body.user.uname,
226
- avatar: msg.body.user.face,
227
- extra: {
228
- badgeName: msg.body.user.badge?.name,
229
- badgeLevel: msg.body.user.badge?.level,
230
- },
231
- },
232
- };
233
- this.emit("Message", comment);
234
- extraDataController.addMessage(comment);
235
- },
236
- onGuardBuy: (msg) => {
237
- const extraDataController = streamManager.getExtraDataController();
238
- if (!extraDataController)
239
- return;
240
- // console.log("guard", msg);
241
- if (this.saveGiftDanma === false)
242
- return;
243
- const gift = {
244
- type: "guard",
245
- timestamp: msg.timestamp,
246
- name: msg.body.gift_name,
247
- price: msg.body.price,
248
- count: 1,
249
- level: msg.body.guard_level,
250
- sender: {
251
- uid: String(msg.body.user.uid),
252
- name: msg.body.user.uname,
253
- avatar: msg.body.user.face,
254
- extra: {
255
- badgeName: msg.body.user.badge?.name,
256
- badgeLevel: msg.body.user.badge?.level,
257
- },
258
- },
259
- };
260
- this.emit("Message", gift);
261
- extraDataController.addMessage(gift);
262
- },
263
- onGift: (msg) => {
264
- const extraDataController = streamManager.getExtraDataController();
265
- if (!extraDataController)
266
- return;
267
- // console.log("gift", msg);
268
- if (this.saveGiftDanma === false)
269
- return;
270
- const gift = {
271
- type: "give_gift",
272
- timestamp: msg.timestamp,
273
- name: msg.body.gift_name,
274
- count: msg.body.amount,
275
- price: msg.body.coin_type === "silver" ? 0 : msg.body.price / 1000,
276
- sender: {
277
- uid: String(msg.body.user.uid),
278
- name: msg.body.user.uname,
279
- avatar: msg.body.user.face,
280
- extra: {
281
- badgeName: msg.body.user.badge?.name,
282
- badgeLevel: msg.body.user.badge?.level,
283
- },
284
- },
285
- extra: {
286
- hits: msg.body.combo?.combo_num,
287
- },
288
- };
289
- this.emit("Message", gift);
290
- extraDataController.addMessage(gift);
291
- },
292
- };
293
- // 弹幕协议不能走短 id,所以不能直接用 channelId。
294
- client = startListen(roomId, handler, {
295
- ws: {
296
- headers: {
297
- Cookie: this.auth ?? "",
298
- },
299
- uid: this.uid ?? 0,
300
- },
227
+ danmaClient = danmaClient.on("Message", (msg) => {
228
+ const extraDataController = recorder.getExtraDataController();
229
+ if (!extraDataController)
230
+ return;
231
+ if (msg.type === "super_chat" && this.saveSCDanma === false)
232
+ return;
233
+ if ((msg.type === "give_gift" || msg.type === "guard") && this.saveGiftDanma === false)
234
+ return;
235
+ this.emit("Message", msg);
236
+ extraDataController.addMessage(msg);
301
237
  });
238
+ danmaClient.start();
302
239
  }
303
- let isEnded = false;
304
- const onEnd = (...args) => {
305
- if (isEnded)
306
- return;
307
- isEnded = true;
308
- this.emit("DebugLog", {
309
- type: "common",
310
- text: `ffmpeg end, reason: ${JSON.stringify(args, (_, v) => (v instanceof Error ? v.stack : v))}`,
311
- });
312
- const reason = args[0] instanceof Error ? args[0].message : String(args[0]);
313
- this.recordHandle?.stop(reason);
314
- };
315
- const isInvalidStream = createInvalidStreamChecker();
316
- const timeoutChecker = utils.createTimeoutChecker(() => onEnd("ffmpeg timeout"), 3 * 10e3);
317
- const command = createFFMPEGBuilder()
318
- .input(url)
319
- .addInputOptions("-user_agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:107.0) Gecko/20100101 Firefox/107.0", "-headers", "Referer: https://live.bilibili.com/")
320
- .outputOptions(ffmpegOutputOptions)
321
- .output(streamManager.videoFilePath)
322
- .on("start", async () => {
323
- try {
324
- await streamManager.handleVideoStarted();
325
- }
326
- catch (err) {
327
- onEnd("ffmpeg start error");
328
- this.emit("DebugLog", { type: "common", text: String(err) });
329
- }
330
- })
331
- .on("error", onEnd)
332
- .on("end", () => onEnd("finished"))
333
- .on("stderr", async (stderrLine) => {
334
- assertStringType(stderrLine);
335
- if (utils.isFfmpegStartSegment(stderrLine)) {
336
- try {
337
- await streamManager.handleVideoStarted(stderrLine);
338
- }
339
- catch (err) {
340
- onEnd("ffmpeg start error");
341
- this.emit("DebugLog", { type: "common", text: String(err) });
342
- }
343
- }
344
- this.emit("DebugLog", { type: "ffmpeg", text: stderrLine });
345
- if (isInvalidStream(stderrLine)) {
346
- onEnd("invalid stream");
347
- }
348
- })
349
- .on("stderr", timeoutChecker.update);
350
- if (hasSegment) {
351
- command.outputOptions("-f", "segment", "-segment_time", String(this.segment * 60), "-reset_timestamps", "1");
352
- }
353
- const ffmpegArgs = command._getArguments();
354
- command.run();
240
+ const ffmpegArgs = recorder.getArguments();
241
+ recorder.run();
355
242
  const stop = utils.singleton(async (reason) => {
356
243
  if (!this.recordHandle)
357
244
  return;
358
245
  this.state = "stopping-record";
359
- // TODO: emit update event
360
- timeoutChecker.stop();
246
+ intervalId && clearInterval(intervalId);
247
+ danmaClient.stop();
361
248
  try {
362
- // @ts-ignore
363
- command.ffmpegProc?.stdin?.write("q");
364
- client?.close();
365
- this.usedStream = undefined;
366
- this.usedSource = undefined;
367
- await streamManager.handleVideoCompleted();
249
+ await recorder.stop();
368
250
  }
369
251
  catch (err) {
370
- // TODO: 这个 stop 经常报错,这里先把错误吞掉,以后再处理。
371
- this.emit("DebugLog", { type: "common", text: String(err) });
252
+ this.emit("DebugLog", {
253
+ type: "common",
254
+ text: `stop ffmpeg error: ${String(err)}`,
255
+ });
372
256
  }
257
+ this.usedStream = undefined;
258
+ this.usedSource = undefined;
373
259
  this.emit("RecordStop", { recordHandle: this.recordHandle, reason });
374
- this.off("videoFileCreated", handleVideoCreated);
375
260
  this.recordHandle = undefined;
376
261
  this.liveInfo = undefined;
377
262
  this.state = "idle";
378
263
  this.qualityRetry = this.qualityMaxRetry;
379
- intervalId && clearInterval(intervalId);
380
264
  });
381
265
  this.recordHandle = {
382
266
  id: genRecordUUID(),
package/lib/stream.d.ts CHANGED
@@ -11,13 +11,14 @@ export declare function getStrictStream(roomId: number, options: {
11
11
  export declare function getLiveStatus(channelId: string): Promise<{
12
12
  living: boolean;
13
13
  liveId: string;
14
+ owner: string;
15
+ title: string;
14
16
  }>;
15
17
  export declare function getInfo(channelId: string): Promise<{
16
18
  living: boolean;
17
19
  owner: string;
18
20
  title: string;
19
21
  roomId: number;
20
- shortId: number;
21
22
  avatar: string;
22
23
  cover: string;
23
24
  startTime: Date;
@@ -39,7 +40,7 @@ export declare function getStream(opts: Pick<Recorder, "channelId" | "quality">
39
40
  sources: SourceProfile[];
40
41
  name: string;
41
42
  streamOptions: {
42
- protocol_name: string;
43
+ protocol_name: "http_stream" | "http_hls";
43
44
  format_name: string;
44
45
  codec_name: string;
45
46
  qn: number;
package/lib/stream.js CHANGED
@@ -13,12 +13,25 @@ export async function getStrictStream(roomId, options) {
13
13
  return url;
14
14
  }
15
15
  export async function getLiveStatus(channelId) {
16
+ const obj = await getRoomBaseInfo(Number(channelId));
17
+ const data = obj[Number(channelId)];
18
+ if (data) {
19
+ const startTime = new Date(data.live_time);
20
+ return {
21
+ living: data.live_status === 1 && !data.is_encrypted,
22
+ liveId: utils.md5(`${channelId}-${startTime?.getTime()}`),
23
+ owner: data.uname,
24
+ title: data.title,
25
+ };
26
+ }
16
27
  const roomInit = await getRoomInit(Number(channelId));
17
28
  const startTime = new Date(roomInit.live_time * 1000);
18
29
  return {
19
30
  living: roomInit.live_status === 1 && !roomInit.encrypted,
20
31
  liveId: utils.md5(`${roomInit.room_id}-${startTime?.getTime()}`),
21
32
  ...roomInit,
33
+ owner: "",
34
+ title: "",
22
35
  };
23
36
  }
24
37
  export async function getInfo(channelId) {
@@ -38,7 +51,6 @@ export async function getInfo(channelId) {
38
51
  avatar: "",
39
52
  cover: status.cover,
40
53
  roomId: roomInit.room_id,
41
- shortId: roomInit.short_id,
42
54
  liveId: utils.md5(`${roomInit.room_id}-${startTime?.getTime()}`),
43
55
  };
44
56
  }
@@ -51,7 +63,6 @@ export async function getInfo(channelId) {
51
63
  avatar: status.face,
52
64
  cover: status.cover_from_user,
53
65
  roomId: roomInit.room_id,
54
- shortId: roomInit.short_id,
55
66
  startTime: startTime,
56
67
  liveId: utils.md5(`${roomInit.room_id}-${startTime.getTime()}`),
57
68
  };
@@ -68,13 +79,13 @@ async function getLiveInfo(roomIdOrShortId, opts) {
68
79
  },
69
80
  {
70
81
  protocol_name: "http_hls",
71
- format_name: "fmp4",
82
+ format_name: "ts",
72
83
  codec_name: "avc",
73
84
  sort: 8,
74
85
  },
75
86
  {
76
87
  protocol_name: "http_hls",
77
- format_name: "ts",
88
+ format_name: "fmp4",
78
89
  codec_name: "avc",
79
90
  sort: 7,
80
91
  },
@@ -86,13 +97,13 @@ async function getLiveInfo(roomIdOrShortId, opts) {
86
97
  },
87
98
  {
88
99
  protocol_name: "http_hls",
89
- format_name: "fmp4",
100
+ format_name: "ts",
90
101
  codec_name: "hevc",
91
102
  sort: 5,
92
103
  },
93
104
  {
94
105
  protocol_name: "http_hls",
95
- format_name: "ts",
106
+ format_name: "fmp4",
96
107
  codec_name: "hevc",
97
108
  sort: 4,
98
109
  },
package/lib/utils.d.ts CHANGED
@@ -20,4 +20,4 @@ export declare function assert(assertion: unknown, msg?: string): asserts assert
20
20
  export declare function assertStringType(data: unknown, msg?: string): asserts data is string;
21
21
  export declare function assertNumberType(data: unknown, msg?: string): asserts data is number;
22
22
  export declare function assertObjectType(data: unknown, msg?: string): asserts data is object;
23
- export declare function createInvalidStreamChecker(): (ffmpegLogLine: string) => boolean;
23
+ export declare function createInvalidStreamChecker(count?: number): (ffmpegLogLine: string) => boolean;
package/lib/utils.js CHANGED
@@ -59,7 +59,7 @@ export function assertNumberType(data, msg) {
59
59
  export function assertObjectType(data, msg) {
60
60
  assert(typeof data === "object", msg);
61
61
  }
62
- export function createInvalidStreamChecker() {
62
+ export function createInvalidStreamChecker(count = 10) {
63
63
  let prevFrame = 0;
64
64
  let frameUnchangedCount = 0;
65
65
  return (ffmpegLogLine) => {
@@ -68,7 +68,7 @@ export function createInvalidStreamChecker() {
68
68
  const [, frameText] = streamInfo;
69
69
  const frame = Number(frameText);
70
70
  if (frame === prevFrame) {
71
- if (++frameUnchangedCount >= 10) {
71
+ if (++frameUnchangedCount >= count) {
72
72
  return true;
73
73
  }
74
74
  }
@@ -78,9 +78,6 @@ export function createInvalidStreamChecker() {
78
78
  }
79
79
  return false;
80
80
  }
81
- // if (ffmpegLogLine.includes("HTTP error 404 Not Found")) {
82
- // return true;
83
- // }
84
81
  return false;
85
82
  };
86
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bililive-tools/bilibili-recorder",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "bililive-tools bilibili recorder implemention",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -39,7 +39,7 @@
39
39
  "tiny-bilibili-ws": "^1.0.1",
40
40
  "lodash-es": "^4.17.21",
41
41
  "axios": "^1.7.8",
42
- "@bililive-tools/manager": "1.0.1"
42
+ "@bililive-tools/manager": "^1.1.0"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsc",