UNPKG

homebridge-plugin-utils

Version:

Opinionated utilities to provide common capabilities and create rich configuration webUI experiences for Homebridge plugins.

320 lines (319 loc) 13.8 kB
/** * FFmpeg process management for HomeKit Secure Video (HKSV) events and fMP4 livestreaming. * * This module defines classes for orchestrating FFmpeg processes that produce fMP4 segments suitable for HomeKit Secure Video and realtime livestreaming scenarios. It * handles process lifecycle, segment buffering, initialization segment detection, and streaming event generation, abstracting away the complexity of interacting directly * with FFmpeg for these workflows. * * Key features: * * - Automated setup and management of FFmpeg processes for HKSV event recording and livestreaming (with support for audio and video). * - Parsing and generation of fMP4 boxes/segments for HomeKit, including initialization and media segments. * - Async generator APIs for efficient, event-driven segment handling. * - Flexible error handling and timeouts for HomeKit's strict realtime requirements. * - Designed for Homebridge plugin authors or advanced users who need robust, platform-aware FFmpeg session control for HomeKit and related integrations. * * @module */ import { type CameraRecordingConfiguration } from "homebridge"; import { type Nullable, type PartialWithId } from "../util.js"; import type { FfmpegOptions } from "./options.js"; import { FfmpegProcess } from "./process.js"; /** * Base options shared by both fMP4 recording and livestream sessions. * * @property audioFilters - Audio filters for FFmpeg to process. These are passed as an array of filters. * @property audioStream - Audio stream input to use, if the input contains multiple audio streams. Defaults to `0` (the first audio stream). * @property codec - The codec for the input video stream. Valid values are `av1`, `h264`, and `hevc`. Defaults to `h264`. * @property enableAudio - Indicates whether to enable audio or not. * @property hardwareDecoding - Enable hardware-accelerated video decoding if available. Defaults to what was specified in `ffmpegOptions`. * @property hardwareTranscoding - Enable hardware-accelerated video transcoding if available. Defaults to what was specified in `ffmpegOptions`. * @property transcodeAudio - Transcode audio to AAC. This can be set to false if the audio stream is already in AAC. Defaults to `true`. * @property videoFilters - Video filters for FFmpeg to process. These are passed as an array of filters. * @property videoStream - Video stream input to use, if the input contains multiple video streams. Defaults to `0` (the first video stream). */ export interface FMp4BaseOptions { audioFilters: string[]; audioStream: number; codec: string; enableAudio: boolean; hardwareDecoding: boolean; hardwareTranscoding: boolean; transcodeAudio: boolean; videoFilters: string[]; videoStream: number; } /** * Configuration for a separate audio input source in an fMP4 livestream session. This interface describes the audio source when video and audio come from different * endpoints, such as cameras like DoorBird that expose audio through a separate HTTP API. * * When the audio source is a raw stream (not a self-describing container), specify `format`, `sampleRate`, and optionally `channels` so FFmpeg knows how to interpret * the input. For self-describing sources like RTSP or container-based HTTP streams, only `url` is required. * * @property channels - Optional. Number of audio channels. Defaults to `1`. * @property format - Optional. Raw audio format for the input stream. When set, FFmpeg is told to expect this format rather than probing the stream. Valid values * are `alaw` (G.711 A-law), `mulaw` (G.711 mu-law), and `s16le` (16-bit signed little-endian PCM). Omit for self-describing sources. * @property sampleRate - Optional. Audio sample rate in Hz (e.g., `8000`). Used when `format` is set. Defaults to `8000`. * @property url - The URL of the audio input source. * * @example * * ```ts * // Raw audio from a DoorBird audio.cgi endpoint. * const rawAudioInput: FMp4AudioInputConfig = { * * format: "mulaw", * sampleRate: 8000, * url: "http://doorbird-ip/bha-api/audio.cgi" * }; * * // Self-describing RTSP audio stream - only URL is needed. * const rtspAudioInput: FMp4AudioInputConfig = { * * url: "rtsp://camera-ip/audio" * }; * ``` * * @see FMp4LivestreamOptions * * @category FFmpeg */ export interface FMp4AudioInputConfig { channels?: number; format?: "alaw" | "mulaw" | "s16le"; sampleRate?: number; url: string; } /** * Options for configuring an fMP4 HKSV recording session. * * @property fps - The video frames per second for the session. * @property probesize - Number of bytes to analyze for stream information. * @property timeshift - Timeshift offset for event-based recording (in milliseconds). */ export interface FMp4RecordingOptions extends FMp4BaseOptions { fps: number; probesize: number; timeshift: number; } /** * Options for configuring an fMP4 livestream session. * * @property audioInput - Optional. A separate audio input source. When provided, audio is read from this source instead of the primary `url`. Can be a URL string * for self-describing sources (e.g., RTSP), or an `FMp4AudioInputConfig` object for raw audio streams that require format metadata. * @property url - Source URL for livestream (RTSP) remuxing to fMP4. * * @see FMp4AudioInputConfig * * @category FFmpeg */ export interface FMp4LivestreamOptions extends FMp4BaseOptions { audioInput?: FMp4AudioInputConfig | string; url: string; } /** * Abstract base class for fMP4 FFmpeg processes. Owns the shared command line skeleton (preamble, video mapping, movflags, audio encoding, output format) and the fMP4 * box-parsing loop. Subclasses provide mode-specific pieces (input args, encoder selection, box handling) via protected hook methods, following the template method * pattern. * * @see FfmpegRecordingProcess * @see FfmpegLivestreamProcess * @see FfmpegProcess * @see {@link https://ffmpeg.org/ffmpeg.html | FFmpeg Documentation} */ declare abstract class FfmpegFMp4Process extends FfmpegProcess { private isLoggingErrors; protected readonly fMp4Options: Required<FMp4BaseOptions>; protected readonly recordingConfig: CameraRecordingConfiguration; /** * Constructs a new fMP4 FFmpeg process. Stores shared state and applies defaults to the base options. The command line is not assembled here...subclasses call * `buildCommandLine()` after their own initialization to trigger the template method assembly. * * @param ffmpegOptions - FFmpeg configuration options. * @param recordingConfig - HomeKit recording configuration for the session. * @param fMp4Options - Partial base options with defaults applied for any unset fields. * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`. */ constructor(ffmpegOptions: FfmpegOptions, recordingConfig: CameraRecordingConfiguration, fMp4Options?: Partial<FMp4BaseOptions>, isVerbose?: boolean); private _isVerbose; protected buildCommandLine(): void; protected abstract inputArgs(): string[]; protected abstract separateAudioInputArgs(): string[]; protected abstract audioInputIndex(): number; protected abstract videoEncoderArgs(): string[]; protected abstract postFilterArgs(): string[]; protected abstract metadataLabel(): string; protected abstract handleParsedBox(header: Buffer, data: Buffer, dataLength: number, type: number): void; /** * Prepares and configures the FFmpeg process for reading and parsing output fMP4 data. The box parsing loop is shared...each complete box is dispatched to the * subclass via handleParsedBox(). */ protected configureProcess(): void; /** * Stops the FFmpeg process and performs cleanup. Subclasses override this to emit mode-specific events before calling super, which handles the shared teardown and * emits the "close" event. */ protected stopProcess(): void; /** * Stops the FFmpeg process and logs errors if specified. * * @param logErrors - If `true`, logs FFmpeg errors. Defaults to the internal process logging state. * * @example * * ```ts * process.stop(); * ``` */ stop(logErrors?: boolean): void; /** * Logs errors from FFmpeg process execution, handling known benign HKSV stream errors gracefully. * * @param exitCode - The exit code from the FFmpeg process. * @param signal - The signal (if any) used to terminate the process. */ protected logFfmpegError(exitCode: Nullable<number>, signal: Nullable<NodeJS.Signals>): void; } /** * Manages a HomeKit Secure Video recording FFmpeg process. * * @example * * ```ts * const process = new FfmpegRecordingProcess(ffmpegOptions, recordingConfig, 30, true, 5000000, 0); * process.start(); * ``` * * @see FfmpegFMp4Process * * @category FFmpeg */ export declare class FfmpegRecordingProcess extends FfmpegFMp4Process { /** * Indicates whether the recording has timed out waiting for FFmpeg output. */ isTimedOut: boolean; private readonly fps; private readonly probesize; private recordingBuffer; private readonly timeshift; /** * Constructs a new FFmpeg recording process for HKSV events. * * @param options - FFmpeg configuration options. * @param recordingConfig - HomeKit recording configuration for the session. * @param fMp4Options - fMP4 recording options. * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`. */ constructor(options: FfmpegOptions, recordingConfig: CameraRecordingConfiguration, fMp4Options?: Partial<FMp4RecordingOptions>, isVerbose?: boolean); protected inputArgs(): string[]; protected separateAudioInputArgs(): string[]; protected audioInputIndex(): number; protected videoEncoderArgs(): string[]; protected postFilterArgs(): string[]; protected metadataLabel(): string; protected handleParsedBox(header: Buffer, data: Buffer, dataLength: number, type: number): void; /** * Stops the FFmpeg process and performs cleanup, ensuring the segment generator can exit. */ protected stopProcess(): void; /** * Asynchronously generates complete segments from FFmpeg output, formatted for HomeKit Secure Video. * * This async generator yields fMP4 segments as Buffers, or ends on process termination or timeout. * * @yields A Buffer containing a complete MP4 segment suitable for HomeKit. * * @example * * ```ts * for await(const segment of process.segmentGenerator()) { * * // Process each segment for HomeKit. * } * ``` */ segmentGenerator(): AsyncGenerator<Buffer>; } /** * Manages a HomeKit livestream FFmpeg process for generating fMP4 segments. * * @example * * ```ts * const process = new FfmpegLivestreamProcess(ffmpegOptions, recordingConfig, url, 30, true); * process.start(); * * const initSegment = await process.getInitSegment(); * ``` * * @see FfmpegFMp4Process * * @category FFmpeg */ export declare class FfmpegLivestreamProcess extends FfmpegFMp4Process { /** * Optional override for the fMP4 fragment duration, in milliseconds. When set, the `-frag_duration` argument is updated before starting the FFmpeg process. */ segmentLength?: number; private _hasAudioInput; private _initSegment; private _initSegmentParts; private hasInitSegment; private readonly livestreamOptions; /** * Constructs a new FFmpeg livestream process. * * @param options - FFmpeg configuration options. * @param recordingConfig - HomeKit recording configuration for the session. * @param livestreamOptions - livestream segmenting options. * @param isVerbose - If `true`, enables more verbose logging for debugging purposes. Defaults to `false`. */ constructor(options: FfmpegOptions, recordingConfig: CameraRecordingConfiguration, livestreamOptions: PartialWithId<FMp4LivestreamOptions, "url">, isVerbose?: boolean); protected inputArgs(): string[]; protected separateAudioInputArgs(): string[]; protected audioInputIndex(): number; protected videoEncoderArgs(): string[]; protected postFilterArgs(): string[]; protected metadataLabel(): string; protected handleParsedBox(header: Buffer, data: Buffer, _dataLength: number, type: number): void; /** * Starts the FFmpeg process, adjusting the fragment duration if segmentLength has been set. * * @example * * ```ts * process.start(); * ``` */ start(): void; /** * Gets the fMP4 initialization segment generated by FFmpeg for the livestream. * * @returns A promise resolving to the initialization segment as a Buffer. * * @example * * ```ts * const initSegment = await process.getInitSegment(); * ``` */ getInitSegment(): Promise<Buffer>; /** * Returns the initialization segment as a Buffer, or null if not yet available. * * @returns The initialization segment Buffer, or `null` if not yet generated. * * @example * * ```ts * const init = process.initSegment; * if(init) { * * // Use the initialization segment. * } * ``` */ get initSegment(): Nullable<Buffer>; } export {};