@bililive-tools/manager
Version:
Batch scheduling recorders
170 lines (169 loc) • 6.14 kB
TypeScript
import { Emitter } from "mitt";
import { ChannelId, Message, Quality } from "./common.js";
import { RecorderProvider } from "./manager.js";
import { AnyObject, PickRequired, UnknownObject } from "./utils.js";
import type { NamespacedCache } from "./cache.js";
import type { DownloaderType } from "./downloader/index.js";
import type { XmlStreamStats } from "./xml_stream_controller.js";
type FormatName = "auto" | "flv" | "hls" | "fmp4" | "flv_only" | "hls_only" | "fmp4_only";
type CodecName = "auto" | "avc" | "hevc" | "avc_only" | "hevc_only";
export interface RecorderCreateOpts<E extends AnyObject = UnknownObject> {
providerId: RecorderProvider<E>["id"];
channelId: ChannelId;
id?: string;
remarks?: string;
weight?: number;
disableAutoCheck?: boolean;
disableProvideCommentsWhenRecording?: boolean;
quality: Quality;
streamPriorities: string[];
sourcePriorities: string[];
formatPriorities?: Array<"flv" | "hls">;
source?: string;
segment?: string;
saveGiftDanma?: boolean;
saveSCDanma?: boolean;
/** 保存封面 */
saveCover?: boolean;
/** 转封装为 mp4 */
convert2Mp4?: boolean;
/** 身份验证 */
auth?: string;
/** cookie所有者uid,B站弹幕录制 */
uid?: number | string;
/** 画质匹配重试次数 */
qualityRetry?: number;
/** 抖音是否使用双屏直播流,开启后如果是双屏直播,那么就使用拼接的流,默认为true */
doubleScreen?: boolean;
/** B站是否使用m3u8代理 */
useM3U8Proxy?: boolean;
/**B站m3u8代理url */
m3u8ProxyUrl?: string;
/** B站自定义Host,用于替换直播流链接中的host */
customHost?: string;
/** 流格式 */
formatName?: FormatName;
/** 流编码 */
codecName?: CodecName;
/** 选择使用的api,虎牙支持: auto,web,mp,wup,抖音支持:web,webHTML,mobile,userHTML */
api?: "auto" | "web" | "mp" | "wup" | "webHTML" | "mobile" | "userHTML" | "balance" | "random" | string;
/** 标题关键词,如果直播间标题包含这些关键词,则不会自动录制,支持两种格式:
* 1. 逗号分隔的关键词:'回放,录播,重播'
* 2. 正则表达式:'/pattern/flags'(如:'/回放|录播/i')
*/
titleKeywords?: string;
/** 用于指定录制文件格式,auto时,分段使用ts,不分段使用mp4 */
videoFormat?: "auto" | "ts" | "mkv" | "flv";
/** 录制类型 */
recorderType?: "auto" | "ffmpeg" | "mesio" | "bililive";
/** 流格式优先级 */
formatriorities?: Array<"flv" | "hls">;
/** 只录制音频 */
onlyAudio?: boolean;
/** 监控时间段 */
handleTime?: [string | null, string | null];
/** 控制弹幕是否使用服务端时间戳 */
useServerTimestamp?: boolean;
extra?: Partial<E>;
/** 调试等级 */
debugLevel?: "none" | "basic" | "verbose";
}
export type SerializedRecorder<E extends AnyObject> = PickRequired<RecorderCreateOpts<E>, "id"> & Pick<Recorder<E>, "id" | "channelId" | "remarks" | "disableAutoCheck" | "quality" | "streamPriorities" | "sourcePriorities" | "extra" | "segment" | "saveSCDanma" | "saveCover" | "convert2Mp4" | "saveGiftDanma" | "disableProvideCommentsWhenRecording" | "liveInfo" | "uid" | "titleKeywords">;
/** 录制状态,idle: 空闲中,recording: 录制中,stopping-record: 停止录制中,check-error: 检查错误,title-blocked: 标题黑名单 */
export type RecorderState = "idle" | "recording" | "stopping-record" | "check-error" | "title-blocked";
export type Progress = {
time: string | null;
};
export interface RecordHandle {
id: string;
stream: string;
source: string;
recorderType?: DownloaderType;
url: string;
downloaderArgs?: string[];
progress?: Progress;
savePath: string;
stop: (this: RecordHandle, reason?: string) => Promise<void>;
cut: (this: RecordHandle) => Promise<void>;
}
export interface DebugLog {
type: string | "common" | "ffmpeg" | "error";
text: string;
}
export type GetSavePath = (data: {
owner: string;
title: string;
startTime: number;
liveStartTime: Date;
recordStartTime: Date;
extraMs?: boolean;
}) => string;
export interface Recorder<E extends AnyObject = UnknownObject> extends Emitter<{
RecordStart: RecordHandle;
RecordSegment?: RecordHandle;
videoFileCreated: {
filename: string;
cover?: string;
rawFilename?: string;
};
videoFileCompleted: {
filename: string;
stats?: XmlStreamStats;
};
progress: Progress;
RecordStop: {
recordHandle: RecordHandle;
reason?: string;
};
Updated: (string | keyof Recorder)[];
Message: Message;
DebugLog: DebugLog;
}>, RecorderCreateOpts<E> {
id: string;
extra: Partial<E>;
availableStreams: string[];
availableSources: string[];
usedStream?: string;
usedSource?: string;
state: RecorderState;
qualityRetry: number;
uid?: number | string;
liveInfo?: {
living: boolean;
owner: string;
title: string;
liveStartTime: Date;
avatar: string;
cover: string;
liveId?: string;
recordStartTime: Date;
area?: string;
};
tempStopIntervalCheck?: boolean;
/** 缓存实例(命名空间) */
cache: NamespacedCache;
getChannelURL: (this: Recorder<E>) => string;
checkLiveStatusAndRecord: (this: Recorder<E>, opts: {
getSavePath: GetSavePath;
banLiveId?: string;
isManualStart?: boolean;
}) => Promise<RecordHandle | null>;
recordHandle?: RecordHandle;
toJSON: (this: Recorder<E>) => SerializedRecorder<E>;
getLiveInfo: (this: Recorder<E>) => Promise<{
owner: string;
title: string;
avatar: string;
cover: string;
channelId: ChannelId;
living: boolean;
liveStartTime: Date;
area: string;
}>;
getStream: (this: Recorder<E>) => Promise<{
source: string;
name: string;
url: string;
}>;
}
export {};