@bililive-tools/manager 1.9.0 → 1.10.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/lib/cache.d.ts CHANGED
@@ -1,17 +1,56 @@
1
- export declare class Cache {
2
- private static instance;
3
- private data;
4
- private constructor();
5
- static getInstance(): Cache;
6
- set(key: string, value: any): void;
7
- get(key: string): any;
8
- has(key: string): boolean;
9
- delete(key: string): boolean;
10
- clear(): void;
11
- get size(): number;
12
- keys(): IterableIterator<string>;
13
- values(): IterableIterator<any>;
14
- entries(): IterableIterator<[string, any]>;
15
- forEach(callbackfn: (value: any, key: string, map: Map<string, any>) => void, thisArg?: any): void;
16
- [Symbol.iterator](): IterableIterator<[string, any]>;
1
+ /**
2
+ * Cache system for RecorderManager
3
+ * 提供统一的缓存接口用于处理持久化事务
4
+ */
5
+ export interface CacheStore {
6
+ get<T = any>(key: string): Promise<T | undefined>;
7
+ set<T = any>(key: string, value: T, ttl?: number): Promise<void>;
8
+ delete(key: string): Promise<void>;
9
+ clear(): Promise<void>;
10
+ has(key: string): Promise<boolean>;
11
+ }
12
+ export interface RecorderCache {
13
+ /**
14
+ * 为每个录制器创建独立的命名空间
15
+ * @param recorderId 录制器 ID
16
+ */
17
+ createNamespace(recorderId: string): NamespacedCache;
18
+ /**
19
+ * 获取全局缓存
20
+ */
21
+ global(): NamespacedCache;
22
+ }
23
+ export interface NamespacedCache {
24
+ /**
25
+ * 通用的 key-value 存储
26
+ */
27
+ get<T = any>(key: string): Promise<T | undefined>;
28
+ /**
29
+ * 通用的 key-value 存储
30
+ */
31
+ set<T = any>(key: string, value: T): Promise<void>;
32
+ /**
33
+ * 删除指定 key
34
+ */
35
+ delete(key: string): Promise<void>;
36
+ }
37
+ /**
38
+ * 内存缓存实现
39
+ */
40
+ export declare class MemoryCacheStore implements CacheStore {
41
+ private store;
42
+ get<T = any>(key: string): Promise<T | undefined>;
43
+ set<T = any>(key: string, value: T, ttl?: number): Promise<void>;
44
+ delete(key: string): Promise<void>;
45
+ clear(): Promise<void>;
46
+ has(key: string): Promise<boolean>;
47
+ }
48
+ /**
49
+ * RecorderCache 实现
50
+ */
51
+ export declare class RecorderCacheImpl implements RecorderCache {
52
+ private store;
53
+ constructor(store: CacheStore);
54
+ createNamespace(recorderId: string): NamespacedCache;
55
+ global(): NamespacedCache;
17
56
  }
package/lib/cache.js CHANGED
@@ -1,47 +1,75 @@
1
- export class Cache {
2
- static instance;
3
- data;
4
- constructor() {
5
- this.data = new Map();
6
- }
7
- static getInstance() {
8
- if (!Cache.instance) {
9
- Cache.instance = new Cache();
1
+ /**
2
+ * Cache system for RecorderManager
3
+ * 提供统一的缓存接口用于处理持久化事务
4
+ */
5
+ /**
6
+ * 内存缓存实现
7
+ */
8
+ export class MemoryCacheStore {
9
+ store = new Map();
10
+ async get(key) {
11
+ const item = this.store.get(key);
12
+ if (!item)
13
+ return undefined;
14
+ if (item.expireAt && Date.now() > item.expireAt) {
15
+ this.store.delete(key);
16
+ return undefined;
10
17
  }
11
- return Cache.instance;
18
+ return item.value;
12
19
  }
13
- set(key, value) {
14
- this.data.set(key, value);
20
+ async set(key, value, ttl) {
21
+ const item = { value };
22
+ if (ttl) {
23
+ item.expireAt = Date.now() + ttl;
24
+ }
25
+ this.store.set(key, item);
26
+ }
27
+ async delete(key) {
28
+ this.store.delete(key);
15
29
  }
16
- get(key) {
17
- return this.data.get(key);
30
+ async clear() {
31
+ this.store.clear();
18
32
  }
19
- has(key) {
20
- return this.data.has(key);
33
+ async has(key) {
34
+ const value = await this.get(key);
35
+ return value !== undefined;
21
36
  }
22
- delete(key) {
23
- return this.data.delete(key);
37
+ }
38
+ /**
39
+ * RecorderCache 实现
40
+ */
41
+ export class RecorderCacheImpl {
42
+ store;
43
+ constructor(store) {
44
+ this.store = store;
24
45
  }
25
- clear() {
26
- this.data.clear();
46
+ createNamespace(recorderId) {
47
+ return new NamespacedCacheImpl(this.store, `recorder:${recorderId}`);
27
48
  }
28
- get size() {
29
- return this.data.size;
49
+ global() {
50
+ return new NamespacedCacheImpl(this.store, "global");
30
51
  }
31
- keys() {
32
- return this.data.keys();
52
+ }
53
+ /**
54
+ * 命名空间缓存实现
55
+ */
56
+ class NamespacedCacheImpl {
57
+ store;
58
+ namespace;
59
+ constructor(store, namespace) {
60
+ this.store = store;
61
+ this.namespace = namespace;
33
62
  }
34
- values() {
35
- return this.data.values();
63
+ getKey(key) {
64
+ return `${this.namespace}:${key}`;
36
65
  }
37
- entries() {
38
- return this.data.entries();
66
+ async get(key) {
67
+ return this.store.get(this.getKey(key));
39
68
  }
40
- forEach(callbackfn, thisArg) {
41
- this.data.forEach(callbackfn, thisArg);
69
+ async set(key, value) {
70
+ return this.store.set(this.getKey(key), value);
42
71
  }
43
- // 实现 Symbol.iterator 接口,支持 for...of 循环
44
- [Symbol.iterator]() {
45
- return this.data[Symbol.iterator]();
72
+ async delete(key) {
73
+ return this.store.delete(this.getKey(key));
46
74
  }
47
75
  }
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "node:events";
2
- import { IRecorder, BililiveRecorderOptions } from "./IRecorder.js";
2
+ import { IDownloader, BililiveRecorderOptions, Segment } from "./IDownloader.js";
3
3
  declare class BililiveRecorderCommand extends EventEmitter {
4
4
  private _input;
5
5
  private _output;
@@ -15,7 +15,7 @@ declare class BililiveRecorderCommand extends EventEmitter {
15
15
  kill(signal?: NodeJS.Signals): void;
16
16
  }
17
17
  export declare const createBililiveBuilder: () => BililiveRecorderCommand;
18
- export declare class BililiveRecorder extends EventEmitter implements IRecorder {
18
+ export declare class BililiveDownloader extends EventEmitter implements IDownloader {
19
19
  private onEnd;
20
20
  private onUpdateLiveInfo;
21
21
  type: "bililive";
@@ -26,9 +26,8 @@ export declare class BililiveRecorder extends EventEmitter implements IRecorder
26
26
  startTime: number;
27
27
  title?: string;
28
28
  }) => string;
29
- readonly segment: number;
29
+ readonly segment: Segment;
30
30
  readonly inputOptions: string[];
31
- readonly disableDanma: boolean;
32
31
  readonly url: string;
33
32
  readonly debugLevel: "none" | "basic" | "verbose";
34
33
  readonly headers: {
@@ -46,5 +45,6 @@ export declare class BililiveRecorder extends EventEmitter implements IRecorder
46
45
  getArguments(): string[];
47
46
  stop(): Promise<void>;
48
47
  getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
48
+ get videoFilePath(): string;
49
49
  }
50
50
  export {};
@@ -1,6 +1,7 @@
1
1
  import EventEmitter from "node:events";
2
2
  import { spawn } from "node:child_process";
3
3
  import { StreamManager, getBililivePath } from "../index.js";
4
+ import { byte2MB } from "../utils.js";
4
5
  // Bililive command builder class similar to ffmpeg
5
6
  class BililiveRecorderCommand extends EventEmitter {
6
7
  _input = "";
@@ -83,7 +84,7 @@ class BililiveRecorderCommand extends EventEmitter {
83
84
  export const createBililiveBuilder = () => {
84
85
  return new BililiveRecorderCommand();
85
86
  };
86
- export class BililiveRecorder extends EventEmitter {
87
+ export class BililiveDownloader extends EventEmitter {
87
88
  onEnd;
88
89
  onUpdateLiveInfo;
89
90
  type = "bililive";
@@ -93,7 +94,6 @@ export class BililiveRecorder extends EventEmitter {
93
94
  getSavePath;
94
95
  segment;
95
96
  inputOptions = [];
96
- disableDanma = false;
97
97
  url;
98
98
  debugLevel = "none";
99
99
  headers;
@@ -101,14 +101,14 @@ export class BililiveRecorder extends EventEmitter {
101
101
  super();
102
102
  this.onEnd = onEnd;
103
103
  this.onUpdateLiveInfo = onUpdateLiveInfo;
104
+ // 存在自动分段,永远为true
104
105
  const hasSegment = true;
105
- this.disableDanma = opts.disableDanma ?? false;
106
+ this.hasSegment = hasSegment;
106
107
  this.debugLevel = opts.debugLevel ?? "none";
107
108
  let videoFormat = "flv";
108
- this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "bililive", videoFormat, {
109
+ this.streamManager = new StreamManager(opts.getSavePath, hasSegment, "bililive", videoFormat, {
109
110
  onUpdateLiveInfo: this.onUpdateLiveInfo,
110
111
  });
111
- this.hasSegment = hasSegment;
112
112
  this.getSavePath = opts.getSavePath;
113
113
  this.inputOptions = [];
114
114
  this.url = opts.url;
@@ -141,8 +141,13 @@ export class BililiveRecorder extends EventEmitter {
141
141
  inputOptions.push("-h", `${key}: ${value}`);
142
142
  });
143
143
  }
144
- if (this.hasSegment) {
145
- inputOptions.push("-d", `${this.segment}`);
144
+ if (this.segment) {
145
+ if (typeof this.segment === "number") {
146
+ inputOptions.push("-d", `${this.segment}`);
147
+ }
148
+ else if (typeof this.segment === "string") {
149
+ inputOptions.push("-m", byte2MB(Number(this.segment)).toFixed(2));
150
+ }
146
151
  }
147
152
  const command = createBililiveBuilder()
148
153
  .input(this.url)
@@ -192,4 +197,7 @@ export class BililiveRecorder extends EventEmitter {
192
197
  getExtraDataController() {
193
198
  return this.streamManager?.getExtraDataController();
194
199
  }
200
+ get videoFilePath() {
201
+ return this.streamManager.videoFilePath;
202
+ }
195
203
  }
@@ -1,8 +1,8 @@
1
1
  import EventEmitter from "node:events";
2
- import { IRecorder, FFMPEGRecorderOptions } from "./IRecorder.js";
2
+ import { IDownloader, FFMPEGRecorderOptions, Segment } from "./IDownloader.js";
3
3
  import { FormatName } from "./index.js";
4
4
  import type { VideoFormat } from "../index.js";
5
- export declare class FFMPEGRecorder extends EventEmitter implements IRecorder {
5
+ export declare class FFmpegDownloader extends EventEmitter implements IDownloader {
6
6
  private onEnd;
7
7
  private onUpdateLiveInfo;
8
8
  type: "ffmpeg";
@@ -14,11 +14,10 @@ export declare class FFMPEGRecorder extends EventEmitter implements IRecorder {
14
14
  startTime: number;
15
15
  title?: string;
16
16
  }) => string;
17
- readonly segment: number;
17
+ readonly segment: Segment;
18
18
  ffmpegOutputOptions: string[];
19
19
  readonly inputOptions: string[];
20
20
  readonly isHls: boolean;
21
- readonly disableDanma: boolean;
22
21
  readonly url: string;
23
22
  formatName: FormatName;
24
23
  videoFormat: VideoFormat;
@@ -39,4 +38,5 @@ export declare class FFMPEGRecorder extends EventEmitter implements IRecorder {
39
38
  getArguments(): string[];
40
39
  stop(): Promise<void>;
41
40
  getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
41
+ get videoFilePath(): string;
42
42
  }
@@ -1,7 +1,7 @@
1
1
  import EventEmitter from "node:events";
2
2
  import { createFFMPEGBuilder, StreamManager, utils } from "../index.js";
3
3
  import { createInvalidStreamChecker, assert } from "../utils.js";
4
- export class FFMPEGRecorder extends EventEmitter {
4
+ export class FFmpegDownloader extends EventEmitter {
5
5
  onEnd;
6
6
  onUpdateLiveInfo;
7
7
  type = "ffmpeg";
@@ -14,7 +14,6 @@ export class FFMPEGRecorder extends EventEmitter {
14
14
  ffmpegOutputOptions = [];
15
15
  inputOptions = [];
16
16
  isHls;
17
- disableDanma = false;
18
17
  url;
19
18
  formatName;
20
19
  videoFormat;
@@ -24,7 +23,11 @@ export class FFMPEGRecorder extends EventEmitter {
24
23
  super();
25
24
  this.onEnd = onEnd;
26
25
  this.onUpdateLiveInfo = onUpdateLiveInfo;
27
- const hasSegment = !!opts.segment;
26
+ let hasSegment = false;
27
+ // 只有数字才表示时间分段,只有时间分段才会在ffmpeg走分段逻辑
28
+ if (opts.segment && typeof opts.segment === "number") {
29
+ hasSegment = true;
30
+ }
28
31
  this.hasSegment = hasSegment;
29
32
  this.debugLevel = opts.debugLevel ?? "none";
30
33
  this.formatName = opts.formatName;
@@ -47,8 +50,7 @@ export class FFMPEGRecorder extends EventEmitter {
47
50
  }
48
51
  }
49
52
  this.videoFormat = videoFormat;
50
- this.disableDanma = opts.disableDanma ?? false;
51
- this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "ffmpeg", this.videoFormat, {
53
+ this.streamManager = new StreamManager(opts.getSavePath, this.hasSegment, "ffmpeg", this.videoFormat, {
52
54
  onUpdateLiveInfo: this.onUpdateLiveInfo,
53
55
  });
54
56
  this.timeoutChecker = utils.createTimeoutChecker(() => this.onEnd("ffmpeg timeout"), 3 * 10e3, false);
@@ -123,8 +125,14 @@ export class FFMPEGRecorder extends EventEmitter {
123
125
  const options = [];
124
126
  options.push(...this.ffmpegOutputOptions);
125
127
  options.push("-c", "copy", "-movflags", "+frag_keyframe+empty_moov+separate_moof", "-fflags", "+genpts+igndts", "-min_frag_duration", "10000000");
126
- if (this.hasSegment) {
127
- options.push("-f", "segment", "-segment_time", String(this.segment * 60), "-reset_timestamps", "1");
128
+ if (this.segment) {
129
+ if (typeof this.segment === "number") {
130
+ options.push("-f", "segment", "-segment_time", String(this.segment * 60));
131
+ }
132
+ else if (typeof this.segment === "string") {
133
+ options.push("-fs", String(this.segment));
134
+ }
135
+ options.push("-reset_timestamps", "1");
128
136
  if (this.videoFormat === "m4s") {
129
137
  options.push("-segment_format", "mp4");
130
138
  }
@@ -175,4 +183,7 @@ export class FFMPEGRecorder extends EventEmitter {
175
183
  getExtraDataController() {
176
184
  return this.streamManager?.getExtraDataController();
177
185
  }
186
+ get videoFilePath() {
187
+ return this.streamManager.videoFilePath;
188
+ }
178
189
  }
@@ -2,6 +2,7 @@ import { EventEmitter } from "node:events";
2
2
  import type { VideoFormat } from "../index.js";
3
3
  import type { FormatName } from "./index.js";
4
4
  import type { XmlStreamController } from "../xml_stream_controller.js";
5
+ export type Segment = number | string | undefined;
5
6
  /**
6
7
  * 录制器构造函数选项的基础接口
7
8
  */
@@ -11,9 +12,8 @@ export interface BaseRecorderOptions {
11
12
  startTime: number;
12
13
  title?: string;
13
14
  }) => string;
14
- segment: number;
15
+ segment: Segment;
15
16
  inputOptions?: string[];
16
- disableDanma?: boolean;
17
17
  formatName: FormatName;
18
18
  debugLevel?: "none" | "basic" | "verbose";
19
19
  headers?: {
@@ -24,12 +24,11 @@ export interface BaseRecorderOptions {
24
24
  /**
25
25
  * 录制器接口定义
26
26
  */
27
- export interface IRecorder extends EventEmitter {
27
+ export interface IDownloader extends EventEmitter {
28
28
  type: "ffmpeg" | "mesio" | "bililive";
29
29
  readonly hasSegment: boolean;
30
- readonly segment: number;
30
+ readonly segment: Segment;
31
31
  readonly inputOptions: string[];
32
- readonly disableDanma: boolean;
33
32
  readonly url: string;
34
33
  readonly headers: {
35
34
  [key: string]: string | undefined;
@@ -43,6 +42,7 @@ export interface IRecorder extends EventEmitter {
43
42
  getArguments(): string[];
44
43
  getExtraDataController(): XmlStreamController | null;
45
44
  createCommand(): any;
45
+ get videoFilePath(): string;
46
46
  on(event: "videoFileCreated", listener: (data: {
47
47
  filename: string;
48
48
  cover?: string;
@@ -0,0 +1,48 @@
1
+ import { FFmpegDownloader } from "./FFmpegDownloader.js";
2
+ import { mesioDownloader } from "./mesioDownloader.js";
3
+ import { BililiveDownloader } from "./BililiveDownloader.js";
4
+ export { FFmpegDownloader } from "./FFmpegDownloader.js";
5
+ export { mesioDownloader } from "./mesioDownloader.js";
6
+ export { BililiveDownloader } from "./BililiveDownloader.js";
7
+ import type { IDownloader, FFMPEGRecorderOptions, MesioRecorderOptions, BililiveRecorderOptions } from "./IDownloader.js";
8
+ /**
9
+ * 录制器类型
10
+ */
11
+ export type DownloaderType = "ffmpeg" | "mesio" | "bililive";
12
+ export type FormatName = "flv" | "ts" | "fmp4";
13
+ /**
14
+ * 根据录制器类型获取对应的配置选项类型
15
+ */
16
+ export type RecorderOptions<T extends DownloaderType> = T extends "ffmpeg" ? FFMPEGRecorderOptions : T extends "mesio" ? MesioRecorderOptions : BililiveRecorderOptions;
17
+ /**
18
+ * 根据录制器类型获取对应的录制器实例类型
19
+ */
20
+ export type RecorderInstance<T extends DownloaderType> = T extends "ffmpeg" ? FFmpegDownloader : T extends "mesio" ? mesioDownloader : BililiveDownloader;
21
+ type RecorderOpts = FFMPEGRecorderOptions | MesioRecorderOptions | BililiveRecorderOptions;
22
+ /**
23
+ * 创建录制器的工厂函数
24
+ */
25
+ export declare function createBaseDownloader<T extends DownloaderType>(type: T, opts: RecorderOptions<T> & {
26
+ onlyAudio?: boolean;
27
+ }, onEnd: (...args: unknown[]) => void, onUpdateLiveInfo: () => Promise<{
28
+ title?: string;
29
+ cover?: string;
30
+ }>): IDownloader;
31
+ /**
32
+ * 选择录制器
33
+ */
34
+ export declare function selectRecorder(preferredRecorder: "auto" | DownloaderType | undefined): DownloaderType;
35
+ /**
36
+ * 判断原始录制流格式,flv, ts, m4s
37
+ */
38
+ export declare function getSourceFormatName(streamUrl: string, formatName: FormatName | undefined): FormatName;
39
+ type PickPartial<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> & Partial<Pick<T, K>>;
40
+ /**
41
+ * 创建录制器的工厂函数
42
+ */
43
+ export declare function createDownloader(type: "auto" | DownloaderType | undefined, opts: PickPartial<RecorderOpts, "formatName"> & {
44
+ onlyAudio?: boolean;
45
+ }, onEnd: (...args: unknown[]) => void, onUpdateLiveInfo: () => Promise<{
46
+ title?: string;
47
+ cover?: string;
48
+ }>): IDownloader;
@@ -1,27 +1,30 @@
1
- import { FFMPEGRecorder } from "./FFMPEGRecorder.js";
2
- import { MesioRecorder } from "./mesioRecorder.js";
3
- import { BililiveRecorder } from "./BililiveRecorder.js";
4
- export { FFMPEGRecorder } from "./FFMPEGRecorder.js";
5
- export { MesioRecorder } from "./mesioRecorder.js";
6
- export { BililiveRecorder } from "./BililiveRecorder.js";
1
+ import { FFmpegDownloader } from "./FFmpegDownloader.js";
2
+ import { mesioDownloader } from "./mesioDownloader.js";
3
+ import { BililiveDownloader } from "./BililiveDownloader.js";
4
+ import { parseSizeToBytes } from "../utils.js";
5
+ export { FFmpegDownloader } from "./FFmpegDownloader.js";
6
+ export { mesioDownloader } from "./mesioDownloader.js";
7
+ export { BililiveDownloader } from "./BililiveDownloader.js";
7
8
  /**
8
9
  * 创建录制器的工厂函数
9
10
  */
10
- export function createRecorder(type, opts, onEnd, onUpdateLiveInfo) {
11
+ export function createBaseDownloader(type, opts, onEnd, onUpdateLiveInfo) {
12
+ const segment = parseSizeToBytes(String(opts.segment));
13
+ const newOpts = { ...opts, segment };
11
14
  if (type === "ffmpeg") {
12
- return new FFMPEGRecorder(opts, onEnd, onUpdateLiveInfo);
15
+ return new FFmpegDownloader(newOpts, onEnd, onUpdateLiveInfo);
13
16
  }
14
17
  else if (type === "mesio") {
15
- return new MesioRecorder(opts, onEnd, onUpdateLiveInfo);
18
+ return new mesioDownloader(newOpts, onEnd, onUpdateLiveInfo);
16
19
  }
17
20
  else if (type === "bililive") {
18
21
  if (opts.formatName === "flv") {
19
22
  // 录播姬引擎不支持只录音频
20
23
  if (!opts.onlyAudio) {
21
- return new BililiveRecorder(opts, onEnd, onUpdateLiveInfo);
24
+ return new BililiveDownloader(newOpts, onEnd, onUpdateLiveInfo);
22
25
  }
23
26
  }
24
- return new FFMPEGRecorder(opts, onEnd, onUpdateLiveInfo);
27
+ return new FFmpegDownloader(newOpts, onEnd, onUpdateLiveInfo);
25
28
  }
26
29
  else {
27
30
  throw new Error(`Unsupported recorder type: ${type}`);
@@ -71,8 +74,8 @@ export function getSourceFormatName(streamUrl, formatName) {
71
74
  /**
72
75
  * 创建录制器的工厂函数
73
76
  */
74
- export function createBaseRecorder(type, opts, onEnd, onUpdateLiveInfo) {
77
+ export function createDownloader(type, opts, onEnd, onUpdateLiveInfo) {
75
78
  const recorderType = selectRecorder(type);
76
79
  const sourceFormatName = getSourceFormatName(opts.url, opts.formatName);
77
- return createRecorder(recorderType, { ...opts, formatName: sourceFormatName }, onEnd, onUpdateLiveInfo);
80
+ return createBaseDownloader(recorderType, { ...opts, formatName: sourceFormatName }, onEnd, onUpdateLiveInfo);
78
81
  }
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "node:events";
2
- import { IRecorder, MesioRecorderOptions } from "./IRecorder.js";
2
+ import { IDownloader, MesioRecorderOptions, Segment } from "./IDownloader.js";
3
3
  declare class MesioCommand extends EventEmitter {
4
4
  private _input;
5
5
  private _output;
@@ -15,7 +15,7 @@ declare class MesioCommand extends EventEmitter {
15
15
  kill(signal?: NodeJS.Signals): void;
16
16
  }
17
17
  export declare const createMesioBuilder: () => MesioCommand;
18
- export declare class MesioRecorder extends EventEmitter implements IRecorder {
18
+ export declare class mesioDownloader extends EventEmitter implements IDownloader {
19
19
  private onEnd;
20
20
  private onUpdateLiveInfo;
21
21
  type: "mesio";
@@ -26,9 +26,8 @@ export declare class MesioRecorder extends EventEmitter implements IRecorder {
26
26
  startTime: number;
27
27
  title?: string;
28
28
  }) => string;
29
- readonly segment: number;
29
+ readonly segment: Segment;
30
30
  readonly inputOptions: string[];
31
- readonly disableDanma: boolean;
32
31
  readonly url: string;
33
32
  readonly debugLevel: "none" | "basic" | "verbose";
34
33
  readonly headers: {
@@ -43,5 +42,6 @@ export declare class MesioRecorder extends EventEmitter implements IRecorder {
43
42
  getArguments(): string[];
44
43
  stop(): Promise<void>;
45
44
  getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
45
+ get videoFilePath(): string;
46
46
  }
47
47
  export {};
@@ -84,7 +84,7 @@ class MesioCommand extends EventEmitter {
84
84
  export const createMesioBuilder = () => {
85
85
  return new MesioCommand();
86
86
  };
87
- export class MesioRecorder extends EventEmitter {
87
+ export class mesioDownloader extends EventEmitter {
88
88
  onEnd;
89
89
  onUpdateLiveInfo;
90
90
  type = "mesio";
@@ -94,7 +94,6 @@ export class MesioRecorder extends EventEmitter {
94
94
  getSavePath;
95
95
  segment;
96
96
  inputOptions = [];
97
- disableDanma = false;
98
97
  url;
99
98
  debugLevel = "none";
100
99
  headers;
@@ -102,8 +101,9 @@ export class MesioRecorder extends EventEmitter {
102
101
  super();
103
102
  this.onEnd = onEnd;
104
103
  this.onUpdateLiveInfo = onUpdateLiveInfo;
104
+ // 存在自动分段,永远为true
105
105
  const hasSegment = true;
106
- this.disableDanma = opts.disableDanma ?? false;
106
+ this.hasSegment = hasSegment;
107
107
  this.debugLevel = opts.debugLevel ?? "none";
108
108
  let videoFormat = "flv";
109
109
  if (opts.url.includes(".m3u8")) {
@@ -118,10 +118,9 @@ export class MesioRecorder extends EventEmitter {
118
118
  else if (opts.formatName === "flv") {
119
119
  videoFormat = "flv";
120
120
  }
121
- this.streamManager = new StreamManager(opts.getSavePath, hasSegment, this.disableDanma, "mesio", videoFormat, {
121
+ this.streamManager = new StreamManager(opts.getSavePath, hasSegment, "mesio", videoFormat, {
122
122
  onUpdateLiveInfo: this.onUpdateLiveInfo,
123
123
  });
124
- this.hasSegment = hasSegment;
125
124
  this.getSavePath = opts.getSavePath;
126
125
  this.inputOptions = [];
127
126
  this.url = opts.url;
@@ -156,8 +155,13 @@ export class MesioRecorder extends EventEmitter {
156
155
  inputOptions.push("-H", `${key}: ${value}`);
157
156
  });
158
157
  }
159
- if (this.hasSegment) {
160
- inputOptions.push("-d", `${this.segment * 60}s`);
158
+ if (this.segment) {
159
+ if (typeof this.segment === "number") {
160
+ inputOptions.push("-d", `${this.segment * 60}s`);
161
+ }
162
+ else if (typeof this.segment === "string") {
163
+ inputOptions.push("-m", this.segment);
164
+ }
161
165
  }
162
166
  const command = createMesioBuilder()
163
167
  .input(this.url)
@@ -190,4 +194,7 @@ export class MesioRecorder extends EventEmitter {
190
194
  getExtraDataController() {
191
195
  return this.streamManager?.getExtraDataController();
192
196
  }
197
+ get videoFilePath() {
198
+ return this.streamManager.videoFilePath;
199
+ }
193
200
  }
@@ -1,7 +1,7 @@
1
1
  import EventEmitter from "node:events";
2
2
  import { createRecordExtraDataController } from "../xml_stream_controller.js";
3
3
  import type { RecorderCreateOpts } from "../recorder.js";
4
- import type { VideoFormat } from "../index.js";
4
+ import type { TrueVideoFormat } from "../index.js";
5
5
  export type GetSavePath = (data: {
6
6
  startTime: number;
7
7
  title?: string;
@@ -15,9 +15,8 @@ export declare class Segment extends EventEmitter {
15
15
  rawRecordingVideoPath: string;
16
16
  /** 输出文件名名,不包含拓展名 */
17
17
  outputVideoFilePath: string;
18
- disableDanma: boolean;
19
- videoExt: VideoFormat;
20
- constructor(getSavePath: GetSavePath, disableDanma: boolean, videoExt: VideoFormat);
18
+ videoExt: TrueVideoFormat;
19
+ constructor(getSavePath: GetSavePath, videoExt: TrueVideoFormat);
21
20
  handleSegmentEnd(): Promise<void>;
22
21
  onSegmentStart(stderrLine: string, callBack?: {
23
22
  onUpdateLiveInfo: () => Promise<{
@@ -36,7 +35,7 @@ export declare class StreamManager extends EventEmitter {
36
35
  recorderType: RecorderType;
37
36
  private videoFormat;
38
37
  private callBack?;
39
- constructor(getSavePath: GetSavePath, hasSegment: boolean, disableDanma: boolean, recorderType: RecorderType, videoFormat: VideoFormat, callBack?: {
38
+ constructor(getSavePath: GetSavePath, hasSegment: boolean, recorderType: RecorderType, videoFormat: TrueVideoFormat, callBack?: {
40
39
  onUpdateLiveInfo: () => Promise<{
41
40
  title?: string;
42
41
  cover?: string;
@@ -45,7 +44,7 @@ export declare class StreamManager extends EventEmitter {
45
44
  handleVideoStarted(stderrLine: string): Promise<void>;
46
45
  handleVideoCompleted(): Promise<void>;
47
46
  getExtraDataController(): import("../xml_stream_controller.js").XmlStreamController | null;
48
- get videoExt(): VideoFormat;
47
+ get videoExt(): TrueVideoFormat;
49
48
  get videoFilePath(): string;
50
49
  }
51
50
  export {};