recorder

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: AGPL-3.0 Imports: 41 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultSegmentDur = 10 * time.Minute
	DefaultRingBufCap = 300
)

Variables

This section is empty.

Functions

func DetectDarkAVIFile added in v0.9.0

func DetectDarkAVIFile(filePath string, threshold int) (bool, int, error)

DetectDarkAVIFile checks if an AVI file contains only dark MJPEG frames. It uses raw AVI chunk reading to extract up to 3 video chunks (first, middle, last) without full demuxing. Each chunk is a JPEG frame that can be decoded.

Returns (isDark, avgBrightness, error).

func DetectDarkMJPEGDir added in v0.9.0

func DetectDarkMJPEGDir(dirPath string, threshold int) (bool, int, error)

DetectDarkMJPEGDir checks if an MJPEG segment directory contains only dark frames. It samples up to 3 JPEG files (first, middle, last by sorted filename) and computes the average luminance of each. If all sampled frames are below the threshold, the segment is classified as "dark".

Returns (isDark, avgBrightness, error).

func ProbeRTSPEncoding added in v0.9.0

func ProbeRTSPEncoding(rtspURL, username, password string) string

ProbeRTSPEncoding connects to an RTSP stream and reports its video format. It is the exported wrapper around probeRTSPEncodingFor for callers outside the recorder package (e.g. the add-camera API handler validating an ONVIF camera's declared encoding against the real stream). Returns "H265", "H264", "MJPEG", or "" if the format is unknown or the probe fails.

func StorageBackoffWithJitter added in v0.8.0

func StorageBackoffWithJitter() time.Duration

StorageBackoffWithJitter returns a long backoff (~60s + jitter) for use when the storage subsystem is in a failed state. See backoff.StorageBackoffWithJitter.

func TieredBackoff added in v0.5.0

func TieredBackoff(attempt int) time.Duration

TieredBackoff returns a retry delay based on attempt count. Thin wrapper over backoff.TieredBackoff; retained so existing recorder call sites keep compiling. New code outside this package should use backoff.* directly.

func TieredBackoffWithJitter added in v0.5.0

func TieredBackoffWithJitter(attempt int) time.Duration

TieredBackoffWithJitter returns TieredBackoff(attempt) with up to 1 second of jitter. Thin wrapper over backoff.TieredBackoffWithJitter.

Types

type BaseConfig added in v0.8.0

type BaseConfig struct {
	CameraID               string
	RTSPURL                string
	Username               string
	Password               string
	SegmentDur             time.Duration
	RingBufCap             int
	DB                     RecordingDB
	AudioEnabled           bool
	FrameWatchdogTimeout   time.Duration // default 30s (0 = use defaultFrameWatchdogTimeout)
	EventBus               *event.EventBus
	DarkFrameFilterEnabled bool // skip dark/night segments (MJPEG/AVI only)
	DarkFrameThreshold     int  // luminance threshold 0-255 (default 15)
	// RecordEnabled gates whether segments are written to disk. When false the
	// recorder stays connected and keeps feeding the StreamHub (so live preview,
	// relay, and health all work) but writes nothing — a "live-only" / stream-
	// forward-only mode. Driven by per-camera recording_enabled (issue #36:
	// users running the NVR purely as a live/relay gateway, no SD-card writes).
	// Defaults to true (nil => record); set to a pointer to false to opt out.
	RecordEnabled *bool
}

BaseConfig holds the shared configuration fields used by all RTSP video recorders. H264Config and H265Config embed BaseConfig to eliminate the duplication of these fields across the H.264 and H.265 recorder configs.

type GB28181Config added in v0.11.0

type GB28181Config struct {
	CameraID      string
	Encoding      string
	SegmentDur    time.Duration
	Store         *storage.Manager
	DB            *storage.DB
	Metrics       *metrics.Metrics
	EventBus      *event.EventBus
	RecordEnabled bool
	// AudioEnabled gates the PS audio path: demuxed G.711/AAC frames are
	// muxed into MP4 segments and broadcast on the hub only when set
	// (mirrors the per-camera audio_enabled flag of the RTSP recorders).
	AudioEnabled bool
}

GB28181Config configures the passive GB28181 recorder. It mirrors IngestConfig: Store/DB/Metrics/EventBus wire the recorder into the normal NVR recording pipeline (segments on disk + recordings rows + events), and RecordEnabled=false keeps the camera live-only (hub fan-out, no segments).

type GB28181Recorder added in v0.11.0

type GB28181Recorder struct {

	// Hub is set by camera.initStreamHub (same pattern as H264Recorder.Hub).
	Hub *model.StreamHub
	// contains filtered or unexported fields
}

GB28181Recorder is a passive recorder for GB/T 28181 channels: media does not arrive from a client connection we dial — the SIP server INVITEs the channel and bridges RTP receiver output into WriteNALU. Start puts the recorder into Reconnecting ("waiting for INVITE"); the SIP server flips it to Recording via OnInvite once the INVITE succeeds.

func NewGB28181Recorder added in v0.11.0

func NewGB28181Recorder(cfg GB28181Config, hub *model.StreamHub) *GB28181Recorder

NewGB28181Recorder creates a recorder for a GB28181 channel camera.

func (*GB28181Recorder) AudioChannels added in v0.11.0

func (r *GB28181Recorder) AudioChannels() int

AudioChannels implements audioInfoProvider.

func (*GB28181Recorder) AudioCodec added in v0.11.0

func (r *GB28181Recorder) AudioCodec() string

AudioCodec implements the audioInfoProvider interface consumed by the WS streaming layer (handlers_ws.go). Returns "" until the first audio frame.

func (*GB28181Recorder) AudioConfig added in v0.11.0

func (r *GB28181Recorder) AudioConfig() []byte

AudioConfig implements audioInfoProvider. G.711: 1-byte μ-law flag + 4-byte rate (muxer convention); AAC: the AudioSpecificConfig.

func (*GB28181Recorder) AudioSampleRate added in v0.11.0

func (r *GB28181Recorder) AudioSampleRate() int

AudioSampleRate implements audioInfoProvider.

func (*GB28181Recorder) CodecParams added in v0.11.0

func (r *GB28181Recorder) CodecParams() (codec model.Format, sps, pps, vps []byte)

func (*GB28181Recorder) GetHub added in v0.11.0

func (r *GB28181Recorder) GetHub() *model.StreamHub

func (*GB28181Recorder) OnBye added in v0.11.0

func (r *GB28181Recorder) OnBye()

OnBye transitions back to Reconnecting — the session ended (device BYE, device offline); the periodic re-REGISTER auto-INVITE will resume media.

func (*GB28181Recorder) OnInvite added in v0.11.0

func (r *GB28181Recorder) OnInvite()

OnInvite transitions to Recording — called by the SIP server after the INVITE 200 OK + ACK handshake succeeded.

func (*GB28181Recorder) Start added in v0.11.0

func (r *GB28181Recorder) Start(ctx context.Context) error

Start marks the recorder as waiting for its INVITE (Reconnecting). The SIP server calls OnInvite after the INVITE handshake completes.

func (*GB28181Recorder) Status added in v0.11.0

func (r *GB28181Recorder) Status() model.RecorderStatus

func (*GB28181Recorder) Stop added in v0.11.0

func (r *GB28181Recorder) Stop() error

func (*GB28181Recorder) WriteAudio added in v0.11.0

func (r *GB28181Recorder) WriteAudio(codec string, data, config []byte, ptsTicks int64, samples int)

WriteAudio ingests one demuxed audio frame from the PS stream. codec: "g711a" | "g711u" | "aac". config is the AAC AudioSpecificConfig (nil for G.711); samples is the frame's sample count for duration math. Frames are broadcast on the hub (live WS audio) and muxed into the open MP4 segment. A no-op when the camera has audio_enabled=false.

func (*GB28181Recorder) WriteNALU added in v0.11.0

func (r *GB28181Recorder) WriteNALU(au [][]byte, ptsTicks int64, isIDR bool)

WriteNALU ingests one complete access unit from the RTP receiver bridge. ptsTicks is the RTP timestamp (90kHz) of the AU.

type H264Config

type H264Config = BaseConfig

H264Config is a type alias for BaseConfig. This allows flat struct literals (e.g., H264Config{CameraID: "..."}) while sharing the common configuration fields across all RTSP recorder types.

type H264NALDriver added in v0.8.0

type H264NALDriver struct{}

H264NALDriver implements codecDriver for H.264 video.

type H264Recorder

type H264Recorder struct {
	// contains filtered or unexported fields
}

H264Recorder records H.264 video from an RTSP source.

func NewH264Recorder

func NewH264Recorder(cfg H264Config, store SegmentStore, opts ...*metrics.Metrics) *H264Recorder

NewH264Recorder creates a new H264Recorder.

func (*H264Recorder) AudioChannels added in v0.8.0

func (r *H264Recorder) AudioChannels() int

AudioChannels returns the number of audio channels, or 0 if no audio.

func (*H264Recorder) AudioCodec added in v0.8.0

func (r *H264Recorder) AudioCodec() string

AudioCodec returns the audio codec name ("aac", "g711", or "" for no audio). Reads the immutable audio snapshot (#226); safe to call from any goroutine (WS/relay/status handlers concurrently with connectAndRecord).

func (*H264Recorder) AudioConfig added in v0.8.0

func (r *H264Recorder) AudioConfig() []byte

AudioConfig returns a copy of the audio codec configuration bytes. Returns nil when no audio is configured. The returned slice is a fresh copy so callers may mutate it freely (#226).

func (*H264Recorder) AudioSampleRate added in v0.8.0

func (r *H264Recorder) AudioSampleRate() int

AudioSampleRate returns the audio sample rate in Hz, or 0 if no audio.

func (*H264Recorder) CodecParams added in v0.10.0

func (r *H264Recorder) CodecParams() (codec model.Format, sps, pps, vps []byte)

CodecParams implements model.HLSProvider, returning the current H.264 codec and parameter-set snapshot in a single atomic read. This lets getCodecParams (handlers_stream.go) use the HLSProvider fast-path instead of the concrete type switch, and guarantees a consistent (non-torn) SPS/PPS pair (#219). vps is always nil for H.264.

func (*H264Recorder) GetHub added in v0.4.0

func (r *H264Recorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out.

func (*H264Recorder) PPS added in v0.2.0

func (r *H264Recorder) PPS() []byte

PPS returns the current H264 Picture Parameter Set NAL unit (without start bytes). Reads the atomic codec snapshot — safe for concurrent live-preview reads (#219).

func (*H264Recorder) SPS added in v0.2.0

func (r *H264Recorder) SPS() []byte

SPS returns the current H264 Sequence Parameter Set NAL unit (without start bytes). Reads the atomic codec snapshot — safe for concurrent live-preview reads (#219).

func (*H264Recorder) Start

func (r *H264Recorder) Start(ctx context.Context) error

Start implements model.Recorder.

func (*H264Recorder) Status

func (r *H264Recorder) Status() model.RecorderStatus

Status implements model.Recorder.

func (*H264Recorder) Stop

func (r *H264Recorder) Stop() error

Stop implements model.Recorder.

type H265Config added in v0.2.0

type H265Config = BaseConfig

H265Config is a type alias for BaseConfig. This allows flat struct literals (e.g., H265Config{CameraID: "..."}) while sharing the common configuration fields across all RTSP recorder types.

type H265NALDriver added in v0.8.0

type H265NALDriver struct{}

H265NALDriver implements codecDriver for H.265/HEVC video.

type H265Recorder added in v0.2.0

type H265Recorder struct {
	// contains filtered or unexported fields
}

H265Recorder records H.265/HEVC video from an RTSP source.

func NewH265Recorder added in v0.2.0

func NewH265Recorder(cfg H265Config, store SegmentStore, opts ...*metrics.Metrics) *H265Recorder

NewH265Recorder creates a new H265Recorder.

func (*H265Recorder) AudioChannels added in v0.8.0

func (r *H265Recorder) AudioChannels() int

AudioChannels returns the number of audio channels, or 0 if no audio.

func (*H265Recorder) AudioCodec added in v0.8.0

func (r *H265Recorder) AudioCodec() string

AudioCodec returns the audio codec name ("aac", "g711", or "" for no audio). Reads the immutable audio snapshot (#226); safe from any goroutine.

func (*H265Recorder) AudioConfig added in v0.8.0

func (r *H265Recorder) AudioConfig() []byte

AudioConfig returns a copy of the audio codec configuration bytes, or nil. Returned slice is a fresh copy callers may mutate (#226).

func (*H265Recorder) AudioSampleRate added in v0.8.0

func (r *H265Recorder) AudioSampleRate() int

AudioSampleRate returns the audio sample rate in Hz, or 0 if no audio.

func (*H265Recorder) CodecParams added in v0.10.0

func (r *H265Recorder) CodecParams() (codec model.Format, sps, pps, vps []byte)

CodecParams implements model.HLSProvider, returning the current H.265 codec and parameter-set snapshot in a single atomic read. This lets getCodecParams (handlers_stream.go) use the HLSProvider fast-path instead of the concrete type switch, and guarantees a consistent (non-torn) VPS/SPS/PPS triplet (#219).

func (*H265Recorder) GetHub added in v0.4.0

func (r *H265Recorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out.

func (*H265Recorder) PPS added in v0.2.0

func (r *H265Recorder) PPS() []byte

PPS returns the current H265 Picture Parameter Set NAL unit (without start bytes). Reads the atomic codec snapshot — safe for concurrent live-preview reads (#219).

func (*H265Recorder) SPS added in v0.2.0

func (r *H265Recorder) SPS() []byte

SPS returns the current H265 Sequence Parameter Set NAL unit (without start bytes). Reads the atomic codec snapshot — safe for concurrent live-preview reads (#219).

func (*H265Recorder) Start added in v0.2.0

func (r *H265Recorder) Start(ctx context.Context) error

Start implements model.Recorder.

func (*H265Recorder) Status added in v0.2.0

func (r *H265Recorder) Status() model.RecorderStatus

Status implements model.Recorder.

func (*H265Recorder) Stop added in v0.2.0

func (r *H265Recorder) Stop() error

Stop implements model.Recorder.

func (*H265Recorder) VPS added in v0.2.0

func (r *H265Recorder) VPS() []byte

VPS returns the current H265 Video Parameter Set NAL unit (without start bytes). Reads the atomic codec snapshot — safe for concurrent live-preview reads (#219).

type HTTPJPEGConfig

type HTTPJPEGConfig struct {
	CameraID               string
	URL                    string
	SegmentDur             time.Duration
	Username               string // for basic auth (optional)
	Password               string // for basic auth (optional)
	DB                     RecordingDB
	EventBus               *event.EventBus
	AVI                    bool // when true, write AVI single-file instead of MJPEG directory
	Width                  int  // video width (0 = auto-detect from first frame)
	Height                 int  // video height (0 = auto-detect from first frame)
	DarkFrameFilterEnabled bool // skip dark/night segments
	DarkFrameThreshold     int  // luminance threshold 0-255 (default 15)
	// RecordEnabled gates segment writes (nil => record; ptr-to-false => live-only).
	// See BaseConfig.RecordEnabled for details.
	RecordEnabled *bool
}

HTTPJPEGConfig holds configuration for the HTTP JPEG recorder.

type HTTPJPEGRecorder

type HTTPJPEGRecorder struct {
	Hub *model.StreamHub // Frame fan-out (nil for HTTP-JPEG — no HLS support, reserved for future consumers)
	// contains filtered or unexported fields
}

HTTPJPEGRecorder captures JPEG frames from a continuous MJPEG stream over HTTP.

func NewHTTPJPEGRecorder

func NewHTTPJPEGRecorder(cfg HTTPJPEGConfig, store SegmentStore, opts ...*metrics.Metrics) *HTTPJPEGRecorder

func (*HTTPJPEGRecorder) GetHub added in v0.4.0

func (r *HTTPJPEGRecorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out.

func (*HTTPJPEGRecorder) LatestFrame added in v0.7.1

func (r *HTTPJPEGRecorder) LatestFrame() []byte

LatestFrame returns the most recently captured JPEG frame WITHOUT copying. The returned slice is shared and must be treated as read-only by callers (the only consumer, handleLatestFrame, only reads it via w.Write). Returns nil if no frame has been captured yet. Safe for concurrent use.

func (*HTTPJPEGRecorder) Start

func (r *HTTPJPEGRecorder) Start(ctx context.Context) error

func (*HTTPJPEGRecorder) Status

func (*HTTPJPEGRecorder) Stop

func (r *HTTPJPEGRecorder) Stop() error

func (*HTTPJPEGRecorder) StreamURL added in v0.7.1

func (r *HTTPJPEGRecorder) StreamURL() string

StreamURL returns the MJPEG stream URL.

type IngestConfig added in v0.8.0

type IngestConfig struct {
	CameraID   string
	Encoding   string // "h264" (H.265 over SRT is a follow-up; RTMP is H.264 only)
	SegmentDur time.Duration

	Store SegmentStore // satisfies *storage.Manager
	DB    RecordingDB
	// Metrics, EventBus optional (nil-safe)
	Metrics  *metrics.Metrics
	EventBus *event.EventBus
	// RecordEnabled gates whether pushed frames are written to disk as segments.
	// nil or true (default) = record normally. false = "live-only" mode: the
	// recorder keeps accepting publishers and the StreamHub fan-out keeps feeding
	// live preview (HLS/WebRTC/FLV/WS) and relay, but NO segments are written —
	// useful when the NVR is used purely as a live/relay gateway and disk writes
	// must be avoided. Mirrors RecordEnabled on the pull recorders (base.go).
	RecordEnabled *bool
}

IngestConfig holds configuration for the IngestRecorder.

Unlike the pull recorders (H264/H265/ONVIF), the IngestRecorder does not dial out to a source. Frames arrive via WriteNALU(), called from the SRT listener and RTMP server callbacks. It therefore has no URL/credentials — those are irrelevant because the publisher connects to us.

type IngestRecorder added in v0.8.0

type IngestRecorder struct {

	// Hub is set by camera.initStreamHub (same pattern as H264Recorder.Hub).
	Hub *model.StreamHub
	// contains filtered or unexported fields
}

IngestRecorder records H.264 video pushed into the NVR via SRT/RTMP ingest.

Lifecycle: Start() enters the Idle (waiting) state — no network activity. When a publisher connects, the ingest server calls WriteConnected(). Each incoming access unit is delivered via WriteNALU(), which fans the frames out to the StreamHub (live HLS/WebRTC/FLV/WS) AND writes rolling MP4 segments to disk (recordings). When the publisher disconnects, OnDisconnect() closes the in-flight segment and returns to Idle, ready for the next publisher.

It implements model.Recorder so it slots into the existing CameraManager / HLS / WebRTC / FLV / WS pipeline unchanged.

func NewIngestRecorder added in v0.8.0

func NewIngestRecorder(cfg IngestConfig) *IngestRecorder

NewIngestRecorder constructs an IngestRecorder. The Hub is injected later by camera.initStreamHub (consistent with every other recorder).

func (*IngestRecorder) AudioChannels added in v0.8.0

func (r *IngestRecorder) AudioChannels() int

AudioChannels returns the number of audio channels. Always 0 for ingest (no audio).

func (*IngestRecorder) AudioCodec added in v0.8.0

func (r *IngestRecorder) AudioCodec() string

AudioCodec returns the audio codec name. IngestRecorder does not currently support audio; returns empty string.

func (*IngestRecorder) AudioConfig added in v0.8.0

func (r *IngestRecorder) AudioConfig() []byte

AudioConfig returns audio config bytes. Always nil for ingest (no audio).

func (*IngestRecorder) AudioSampleRate added in v0.8.0

func (r *IngestRecorder) AudioSampleRate() int

AudioSampleRate returns the audio sample rate. Always 0 for ingest (no audio).

func (*IngestRecorder) CodecParams added in v0.8.0

func (r *IngestRecorder) CodecParams() (codec model.Format, sps, pps, vps []byte)

CodecParams implements model.HLSProvider so the HLS handler can initialize a stream from the SPS/PPS captured during ingest (push cameras). Returns H.264 with the current SPS/PPS (vps is nil for H.264). Returns nil params before the publisher's first keyframe arrives.

func (*IngestRecorder) GetHub added in v0.8.0

func (r *IngestRecorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out (satisfies the hubber interface used by getRecorderHub across the API layer).

func (*IngestRecorder) OnDisconnect added in v0.8.0

func (r *IngestRecorder) OnDisconnect()

OnDisconnect is called by the ingest server when the publisher disconnects. It flushes the in-flight segment and returns the recorder to Idle, ready to accept the next publisher without being restarted.

func (*IngestRecorder) PPS added in v0.8.0

func (r *IngestRecorder) PPS() []byte

PPS returns the most recently captured H.264 PPS NAL unit (without start code). nil until the publisher has sent a keyframe with param sets. Thread-safe.

func (*IngestRecorder) SPS added in v0.8.0

func (r *IngestRecorder) SPS() []byte

SPS returns the most recently captured H.264 SPS NAL unit (without start code). nil until the publisher has sent a keyframe with param sets. Thread-safe.

func (*IngestRecorder) Start added in v0.8.0

func (r *IngestRecorder) Start(_ context.Context) error

Start initializes the recorder into the Idle state (awaiting a publisher). It does NOT dial any source — unlike the pull recorders, there is nothing to connect to here. Returns immediately.

func (*IngestRecorder) Status added in v0.8.0

func (r *IngestRecorder) Status() model.RecorderStatus

Status returns the current recorder status.

func (*IngestRecorder) Stop added in v0.8.0

func (r *IngestRecorder) Stop() error

Stop closes any in-flight segment and marks the recorder stopped.

func (*IngestRecorder) WriteConnected added in v0.8.0

func (r *IngestRecorder) WriteConnected()

WriteConnected signals that a publisher has connected and is about to stream. Called by the SRT listener / RTMP server on publisher connect.

func (*IngestRecorder) WriteNALU added in v0.8.0

func (r *IngestRecorder) WriteNALU(au [][]byte, ptsTicks int64, isIDR bool)

WriteNALU ingests one H.264 access unit (slice of NAL units, WITHOUT start codes) delivered by an ingest server (SRT tsdemux / RTMP reader).

ptsTicks is a 90 kHz clock value (matching the RTP/StreamHub convention used by the pull recorders). isIDR indicates the AU contains a keyframe.

It performs three jobs:

  1. Broadcasts the AU to the StreamHub for live consumers (HLS/WebRTC/FLV/WS).
  2. Captures SPS/PPS and rolls the MP4 segment when they change.
  3. Writes VCL NALUs (types 1, 5) to the rolling MP4 segment for recordings.

type MJPEGConfig

type MJPEGConfig struct {
	CameraID               string
	RTSPURL                string
	SegmentDur             time.Duration
	SampleInterval         int // if >1, only save every Nth frame
	DB                     RecordingDB
	EventBus               *event.EventBus
	AudioEnabled           bool
	DarkFrameFilterEnabled bool // skip dark/night segments
	DarkFrameThreshold     int  // luminance threshold 0-255 (default 15)
	// RecordEnabled gates segment writes (nil => record; ptr-to-false => live-only).
	// See BaseConfig.RecordEnabled for details.
	RecordEnabled *bool
}

MJPEGConfig holds configuration for the MJPEG recorder.

type MJPEGRecorder

type MJPEGRecorder struct {
	Hub *model.StreamHub // Frame fan-out (nil for MJPEG — no HLS support, reserved for future consumers)
	// contains filtered or unexported fields
}

MJPEGRecorder records Motion-JPEG video from an RTSP source. When audio is present (AudioEnabled + G.711 in SDP), it creates AVI files with MJPEG video + G.711 audio. Without audio, it stores JPEG frames to a directory (backward compatible).

func NewMJPEGRecorder

func NewMJPEGRecorder(cfg MJPEGConfig, store SegmentStore, opts ...*metrics.Metrics) *MJPEGRecorder

func (*MJPEGRecorder) GetHub added in v0.4.0

func (r *MJPEGRecorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out.

func (*MJPEGRecorder) LatestFrame added in v0.9.0

func (r *MJPEGRecorder) LatestFrame() []byte

LatestFrame returns the most recently decoded JPEG frame WITHOUT copying. The returned slice is shared and must be treated as read-only by callers. Returns nil if no frame has been decoded yet. Safe for concurrent use. Used by dual-mode timelapse frame polling (LatestFrame()) and the MJPEG snapshot endpoint.

func (*MJPEGRecorder) Start

func (r *MJPEGRecorder) Start(ctx context.Context) error

func (*MJPEGRecorder) Status

func (r *MJPEGRecorder) Status() model.RecorderStatus

func (*MJPEGRecorder) Stop

func (r *MJPEGRecorder) Stop() error

type ONVIFConfig added in v0.2.0

type ONVIFConfig struct {
	CameraID             string
	ProfileToken         string
	StreamEncoding       string // "H264" or "H265". Empty = auto-detect via ONVIF profile or RTSP DESCRIBE.
	Username             string // RTSP credentials (may differ from ONVIF credentials)
	Password             string
	SegmentDur           time.Duration
	DB                   RecordingDB
	AudioEnabled         bool
	FrameWatchdogTimeout time.Duration // default 30s (0 = use constant default)
	ONVIFEndpoint        string        // ONVIF device endpoint URL (for HTTP MJPEG probe base)
	EventBus             *event.EventBus
	AVI                  bool // when true, JPEG delegate writes AVI single-file
	// RecordEnabled gates segment writes for all delegate recorders (H264/H265/
	// MJPEG/HTTP-JPEG). nil => record (default); pointer to false => live-only
	// (recorder stays connected for live preview/relay/health but writes nothing).
	// Required because ONVIF cameras delegate to the codec-specific recorder at
	// Start time — without this, recording_enabled=false had no effect on ONVIF
	// cameras (the delegate always recorded).
	RecordEnabled *bool
}

ONVIFConfig holds configuration for the ONVIF recorder.

type ONVIFRecorder added in v0.2.0

type ONVIFRecorder struct {
	Hub *model.StreamHub // Frame fan-out, passed to delegate recorders
	// contains filtered or unexported fields
}

ONVIFRecorder implements model.Recorder by resolving the RTSP stream URI via ONVIF GetStreamURI, then delegating to an internal H264Recorder or H265Recorder.

func NewONVIFRecorder added in v0.2.0

func NewONVIFRecorder(cfg ONVIFConfig, client onvif.DeviceClient, store SegmentStore, opts ...*metrics.Metrics) *ONVIFRecorder

NewONVIFRecorder creates a new ONVIF recorder that delegates to H264/H265 recorder.

func (*ONVIFRecorder) Delegate added in v0.2.0

func (r *ONVIFRecorder) Delegate() model.Recorder

Delegate returns the internal H264/H265 recorder delegate. Returns nil if the recorder hasn't been started yet. This is used by the HLS handler to access SPS/PPS and subscribe to StreamHub for HLS streaming.

func (*ONVIFRecorder) GetHub added in v0.4.0

func (r *ONVIFRecorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out.

func (*ONVIFRecorder) RTSPURL added in v0.2.0

func (r *ONVIFRecorder) RTSPURL() string

RTSPURL returns the resolved RTSP URL from ONVIF (may be empty before Start).

func (*ONVIFRecorder) ResolvedEncoding added in v0.10.0

func (r *ONVIFRecorder) ResolvedEncoding() string

ResolvedEncoding returns the video codec resolved during Start (e.g. "H264", "H265", "MJPEG", "JPEG"). Empty if Start hasn't run yet or detection failed. Used by the camera manager to persist the resolved encoding so a later device outage doesn't leave the camera with an empty encoding in DB/YAML — which would make the frontend lose the codec and thrash through the protocol chain (issue #112). Mirrors ResolvedProfileToken's accessor pattern.

func (*ONVIFRecorder) ResolvedProfileToken added in v0.9.0

func (r *ONVIFRecorder) ResolvedProfileToken() string

ResolvedProfileToken returns the profile token resolved during Start (either from config or auto-selected via SelectMainProfile). Empty if Start hasn't run yet or the token was never resolved. Used by the camera manager to persist the auto-selected token so GetProfiles isn't re-run on every restart.

func (*ONVIFRecorder) SetResolvedEncodingForTest added in v0.10.0

func (r *ONVIFRecorder) SetResolvedEncodingForTest(enc string, status model.RecorderStatus)

SetResolvedEncodingForTest sets the resolved encoding AND the recorder status (so ensureEncoding's Status()==StatusRecording gate passes) for unit tests that inject a recorder via CameraManager.SetTestRecorder without running a real Start(). Test-only: production code populates these fields in Start. The delegate is intentionally left nil so Status() reports r.status directly.

func (*ONVIFRecorder) Start added in v0.2.0

func (r *ONVIFRecorder) Start(ctx context.Context) error

Start connects to the ONVIF device, resolves the RTSP URI, creates an internal H264Recorder or H265Recorder based on the profile encoding, and starts it.

Concurrency: the ONVIF handshake (Connect, GetProfiles, GetStreamURI) and the delegate Start run OUTSIDE r.mu — these are multi-second network operations that must NOT block Status()/Delegate()/RTSPURL() (polled every 500ms by the grid's latest-frame handler). Only the initial already-running guard and the final state publication take the (short) lock. SOAP goroutine-safety is guaranteed by the onvif.Client's own internal mutex, NOT by r.mu.

func (*ONVIFRecorder) Status added in v0.2.0

func (r *ONVIFRecorder) Status() model.RecorderStatus

Status returns the current recorder status, delegating to the internal recorder if available. Snapshots the delegate pointer under the short lock, then calls its Status() OUTSIDE the lock — so a long-running Start handshake (which no longer holds r.mu) can't block this hot path (polled every 500ms by grid latest-frame).

func (*ONVIFRecorder) Stop added in v0.2.0

func (r *ONVIFRecorder) Stop() error

Stop stops the internal delegate recorder.

type RecordingDB

type RecordingDB interface {
	InsertRecording(ctx context.Context, r *model.Recording) error
	InsertRecordingWithRetry(ctx context.Context, r *model.Recording, maxRetries int, backoff time.Duration) error
	SetMergeStatus(ctx context.Context, ids []string, status string) error
}

RecordingDB abstracts database operations needed by the recorder.

type SegmentStore

type SegmentStore interface {
	CreateSegment(cameraID string, fmt string) (tempPath string, finalPath string, err error)
	WriteFrame(tempPath string, data []byte) (int, error)
	CloseSegment(tempPath, finalPath string) error
}

SegmentStore abstracts the storage operations needed by the recorder. *storage.Manager satisfies this interface.

type StubRecorder added in v0.7.0

type StubRecorder struct {
	Hub *model.StreamHub
	// contains filtered or unexported fields
}

StubRecorder is a no-op recorder that implements model.Recorder. It provides a camera struct for the timelapse subsystem when a standalone timelapse camera uses rtsp_keyframe frame source but has no host recorder. It does not connect to any stream, record frames, or produce output — it exists solely to satisfy the recorder interface so the camera can be managed by the timelapse schedule monitor.

func (*StubRecorder) GetHub added in v0.7.0

func (r *StubRecorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out. Always nil for StubRecorder.

func (*StubRecorder) Start added in v0.7.0

func (r *StubRecorder) Start(_ context.Context) error

Start initializes the stub recorder. It returns immediately without error.

func (*StubRecorder) Status added in v0.7.0

func (r *StubRecorder) Status() model.RecorderStatus

Status returns the current recorder status.

func (*StubRecorder) Stop added in v0.7.0

func (r *StubRecorder) Stop() error

Stop stops the stub recorder. It returns immediately without error.

type TimelapseRecorder added in v0.6.0

type TimelapseRecorder struct {
	Hub *model.StreamHub
	// contains filtered or unexported fields
}

TimelapseRecorder captures JPEG frames at a configurable interval from an HTTP MJPEG stream and stores them as zero-padded JPEG sequences in timestamped segment directories. Implements model.Recorder.

func NewTimelapseRecorder added in v0.6.0

func NewTimelapseRecorder(cfg TimelapseRecorderConfig, store SegmentStore, opts ...*metrics.Metrics) *TimelapseRecorder

NewTimelapseRecorder creates a new TimelapseRecorder.

func (*TimelapseRecorder) GetHub added in v0.6.0

func (r *TimelapseRecorder) GetHub() *model.StreamHub

GetHub returns the StreamHub for frame fan-out (nil for timelapse — no live streaming).

func (*TimelapseRecorder) Start added in v0.6.0

func (r *TimelapseRecorder) Start(ctx context.Context) error

func (*TimelapseRecorder) Status added in v0.6.0

func (*TimelapseRecorder) Stop added in v0.6.0

func (r *TimelapseRecorder) Stop() error

type TimelapseRecorderConfig added in v0.6.0

type TimelapseRecorderConfig struct {
	CameraID   string
	Interval   time.Duration // frame capture interval (e.g., 5s)
	SegmentDur time.Duration // segment duration
	URL        string        // HTTP MJPEG stream URL
	Username   string        // for basic auth (optional)
	Password   string        // for basic auth (optional)
	DataDir    string        // base data directory
	DB         RecordingDB
	Metrics    *metrics.Metrics
	MergeMgr   *timelapse.RollingMergeManager // optional rolling merge manager
	// RecordEnabled gates whether captured frames are written to disk, mirroring
	// the segment recorder's RecordEnabled (internal/recorder/base.go).
	// nil or true = write timelapse frames (default). false = "preview-only":
	// the capture loop keeps the MJPEG connection alive but performs no
	// segment/frame I/O — useful when recording_enabled=false and the user
	// expects zero disk writes.
	RecordEnabled *bool
}

TimelapseRecorderConfig holds configuration for the timelapse recorder.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL