UNPKG

homebridge-plugin-utils

Version:

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

206 lines (205 loc) 8.1 kB
/** * RTP and RTCP packet demultiplexer and UDP port management for FFmpeg-based HomeKit livestreaming. * * This module supplies classes and helpers to support realtime streaming via FFmpeg in Homebridge and similar HomeKit environments. It enables the demultiplexing of RTP * and RTCP packets on a single UDP port, as required by HomeKit and RFC 5761, working around FFmpeg’s lack of native support for RTP/RTCP multiplexing. It also manages * the allocation and tracking of UDP ports for RTP and RTCP, helping prevent conflicts in dynamic, multi-session streaming scenarios. * * Key features: * * - Demultiplexes RTP and RTCP packets received on a single UDP port, forwarding them to the correct FFmpeg destinations for HomeKit livestream compatibility. * - Injects periodic heartbeat messages to keep two-way audio streams alive with FFmpeg’s strict timeout requirements. * - Dynamically allocates and reserves UDP ports for RTP/RTCP, supporting consecutive port pairing for correct FFmpeg operation. * - Event-driven architecture for integration with plugin or automation logic. * * Designed for plugin developers and advanced users implementing HomeKit livestreaming, audio/video bridging, or similar applications requiring precise RTP/RTCP handling * with FFmpeg. * * @module */ import { EventEmitter } from "node:events"; import type { HomebridgePluginLogging } from "../util.js"; /** * Utility for demultiplexing RTP and RTCP packets on a single UDP port for HomeKit compatibility. * * FFmpeg does not support multiplexing RTP and RTCP data on a single UDP port (RFC 5761) and HomeKit requires this for livestreaming. This class listens on a UDP port * and demultiplexes RTP and RTCP traffic, forwarding them to separate RTP and RTCP ports as required by FFmpeg. * * Credit to [dgreif](https://github.com/dgreif), [brandawg93](https://github.com/brandawg93), and [Sunoo](https://github.com/Sunoo) for foundational ideas and * collaboration. * * @example * * ```ts * // Create an RtpDemuxer to split packets for FFmpeg compatibility. * const demuxer = new RtpDemuxer("ipv4", 50000, 50002, 50004, log); * * // Close the demuxer when finished. * demuxer.close(); * ``` * * @see {@link https://tools.ietf.org/html/rfc5761 | RFC 5761} * @see {@link https://github.com/homebridge/homebridge-camera-ffmpeg | homebridge-camera-ffmpeg} * * @category FFmpeg */ export declare class RtpDemuxer extends EventEmitter { private heartbeatTimer; private heartbeatMsg; private _isRunning; private log?; private inputPort; readonly socket: import("node:dgram").Socket; /** * Constructs a new RtpDemuxer for a specified IP family and port set. * * @param ipFamily - The IP family: "ipv4" or "ipv6". * @param inputPort - The UDP port to listen on for incoming packets. * @param rtcpPort - The UDP port to forward RTCP packets to. * @param rtpPort - The UDP port to forward RTP packets to. * @param log - Logger instance for debug and error messages. * * @example * * ```ts * const demuxer = new RtpDemuxer("ipv4", 50000, 50002, 50004, log); * ``` */ constructor(ipFamily: ("ipv4" | "ipv6"), inputPort: number, rtcpPort: number, rtpPort: number, log: HomebridgePluginLogging); /** * Sends periodic heartbeat messages to the RTP port to keep the FFmpeg process alive. * * This is necessary because FFmpeg times out input streams if it does not receive data for more than five seconds. * * @param port - The RTP port to send the heartbeat to. */ private heartbeat; /** * Closes the demuxer, its socket, and any heartbeat timers. * * @example * * ```ts * demuxer.close(); * ``` */ close(): void; /** * Extracts the RTP payload type from a UDP packet. * * Used internally to distinguish RTP from RTCP messages. * * @param message - The UDP packet buffer. * * @returns The RTP payload type as a number. */ private getPayloadType; /** * Determines if the provided UDP packet is an RTP message. * * @param message - The UDP packet buffer. * * @returns `true` if the packet is RTP, `false` if RTCP. */ private isRtpMessage; /** * Indicates if the demuxer is running and accepting packets. * * @returns `true` if running, otherwise `false`. * * @example * * ```ts * if(demuxer.isRunning) { * // Demuxer is active. * } * ``` */ get isRunning(): boolean; } /** * Allocates and tracks UDP ports for RTP and RTCP to avoid port conflicts in environments with high network activity. * * This utility class is used to find and reserve available UDP ports for demuxing FFmpeg streams or other network activities. * * @example * * ```ts * const allocator = new RtpPortAllocator(); * * // Reserve two consecutive ports for RTP and RTCP. * const rtpPort = await allocator.reserve("ipv4", 2); * * // Cancel reservation if not needed. * allocator.cancel(rtpPort); * ``` * * @category FFmpeg */ export declare class RtpPortAllocator { private portsInUse; /** * Instantiates a new RTP port allocator and tracker. */ constructor(); /** * Finds an available UDP port by attempting to bind a new socket. * * Loops until an available port not already marked as in use is found. * * @param ipFamily - "ipv4" or "ipv6". * @param port - Optional. The port to try to bind to. If 0, selects a random port. * * @returns A promise resolving to the available port number, or `-1` on error. */ private getPort; /** * Internal method to reserve one or two consecutive UDP ports for FFmpeg or network use. * * If two ports are reserved, ensures they are consecutive for RTP and RTCP usage. Returns the first port in the sequence, or `-1` if we're unable to allocate. * * @param ipFamily - Optional. "ipv4" or "ipv6". Defaults to "ipv4". * @param portCount - Optional. The number of consecutive ports to reserve (1 or 2). Defaults to 1. * @param attempts - Internal. The number of allocation attempts. Used for recursion. * * @returns A promise resolving to the first reserved port, or `-1` if unavailable. */ private _reserve; /** * Reserves one or two consecutive UDP ports for FFmpeg or network use. * * If two ports are reserved, ensures they are consecutive for RTP and RTCP usage. Returns the first port in the sequence, or `-1` if we're unable to allocate. * * @param ipFamily - Optional. "ipv4" or "ipv6". Defaults to "ipv4". * @param portCount - Optional. The number of consecutive ports to reserve (1 or 2). Defaults to 1. * * @returns A promise resolving to the first reserved port, or `-1` if unavailable. * * @remarks FFmpeg currently lacks the ability to specify both the RTP and RTCP ports. FFmpeg always assumes, by convention, that when you specify an RTP port, the RTCP * port is the RTP port + 1. In order to work around that challenge, we need to always ensure that when we reserve multiple ports for RTP (primarily for two-way audio * use cases) that we we are reserving consecutive ports only. * * @example * * ```ts * // Reserve a single port. * const port = await allocator.reserve(); * * // Reserve two consecutive ports for RTP/RTCP. * const rtpPort = await allocator.reserve("ipv4", 2); * ``` */ reserve(ipFamily?: ("ipv4" | "ipv6"), portCount?: (1 | 2)): Promise<number>; /** * Cancels and releases a previously reserved port, making it available for future use. * * @param port - The port number to release. * * @example * * ```ts * allocator.cancel(50000); * ``` */ cancel(port: number): void; }