domain

package
v0.0.91 Latest Latest
Warning

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

Go to latest
Published: May 6, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package domain defines core types shared across Open Streamer modules.

Index

Constants

View Source
const (
	// DefaultBufferCapacity is the per-subscriber channel buffer size in
	// MPEG-TS packets. 1024 packets ≈ 1 MiB ≈ ~1.5s of 1080p60 video at
	// 5Mbps — enough headroom for HLS pull bursts (one segment per Read)
	// without growing memory unbounded across many subscribers.
	DefaultBufferCapacity = 1024

	// DefaultInputPacketTimeoutSec is the manager's failover threshold: the
	// maximum gap (in seconds) without a successful read on the active
	// input before it is marked failed and the next-priority input takes
	// over.
	DefaultInputPacketTimeoutSec = 30

	// DefaultLiveSegmentSec is the live HLS / DASH segment target length
	// in seconds when the operator leaves the field unset.
	DefaultLiveSegmentSec = 2
	// DefaultLiveWindow is the segment count in the sliding playlist
	// window (HLS / DASH live).
	DefaultLiveWindow = 12
	// DefaultLiveHistory is extra segments retained on disk after they
	// leave the live window — 0 = drop immediately after sliding out.
	DefaultLiveHistory = 0

	// DefaultDVRSegmentDuration is the DVR segment length in seconds when
	// StreamDVRConfig.SegmentDuration is zero.
	DefaultDVRSegmentDuration = 4
	// DefaultDVRRoot is the on-disk root directory used as the parent of
	// "{DefaultDVRRoot}/{streamCode}" when StreamDVRConfig.StoragePath is
	// empty.
	DefaultDVRRoot = "./out/dvr"

	// DefaultPushTimeoutSec is the publish handshake budget for an outbound
	// push destination when PushDestination.TimeoutSec is zero.
	DefaultPushTimeoutSec = 10
	// DefaultPushRetryTimeoutSec is the delay between retry attempts.
	DefaultPushRetryTimeoutSec = 5

	// DefaultHookMaxRetries is the per-hook retry budget when the user
	// leaves Hook.MaxRetries=0. For HTTP hooks this caps retries WITHIN a
	// single batch flush; events still re-queue across flushes regardless.
	DefaultHookMaxRetries = 3
	// DefaultHookTimeoutSec is the per-attempt delivery timeout when
	// Hook.TimeoutSec=0.
	DefaultHookTimeoutSec = 10
	// DefaultHookBatchMaxItems is the cap on events per HTTP POST body
	// when neither Hook.BatchMaxItems nor HooksConfig.BatchMaxItems is set.
	// Picked to keep payloads under typical 1 MiB receiver caps (each event
	// ~1-3 KiB after JSON encoding). File hooks ignore this — they always
	// write one line per event.
	DefaultHookBatchMaxItems = 100
	// DefaultHookBatchFlushIntervalSec is the maximum time a batch waits
	// before flushing even when below BatchMaxItems. Trade-off: lower =
	// fresher delivery, more requests; higher = bigger batches, more lag
	// for low-volume hooks.
	DefaultHookBatchFlushIntervalSec = 5
	// DefaultHookBatchMaxQueueItems caps the per-hook in-memory queue.
	// Events that fail delivery are re-queued for the next flush; if the
	// downstream stays unreachable, the queue eventually overflows and the
	// OLDEST events are dropped (with a warn log) to bound memory.
	DefaultHookBatchMaxQueueItems = 10000

	// DefaultVideoBitrateK is the fallback target video bitrate (kbps)
	// when a profile leaves Bitrate=0.
	DefaultVideoBitrateK = 2500

	// DefaultAudioBitrateK is the audio bitrate (kbps) when
	// AudioTranscodeConfig.Bitrate is zero.
	DefaultAudioBitrateK = 128

	// DefaultHLSPlaylistTimeoutSec is the upstream HLS pull playlist GET
	// timeout (seconds) when InputNetConfig.TimeoutSec is zero.
	DefaultHLSPlaylistTimeoutSec = 15
	// DefaultHLSSegmentTimeoutSec is the upstream HLS segment GET timeout
	// floor — segments can be many MB so this is held above the playlist
	// timeout.
	DefaultHLSSegmentTimeoutSec = 60
	// DefaultHLSMaxSegmentBuffer caps pre-fetched HLS segments held in
	// memory per stream when IngestorConfig.HLSMaxSegmentBuffer is zero.
	DefaultHLSMaxSegmentBuffer = 8

	// DefaultSRTLatencyMS is the SRT ARQ latency window (milliseconds) when
	// SRTListenerConfig.LatencyMS is zero. 120ms matches Haivision's
	// reference for low-latency contribution links.
	DefaultSRTLatencyMS = 120

	// DefaultFFmpegPath is the executable name resolved against $PATH when
	// TranscoderConfig.FFmpegPath is empty. Operators on bespoke layouts
	// (e.g. /opt/ffmpeg/bin/ffmpeg) must set the full path explicitly.
	DefaultFFmpegPath = "ffmpeg"

	// DefaultListenHost is the bind address used for RTMP / RTSP / SRT
	// listeners when ListenHost is empty. "0.0.0.0" listens on all
	// interfaces — the standard server behaviour.
	DefaultListenHost = "0.0.0.0"

	// DefaultRTMPTimeoutSec is the dial timeout (seconds) for RTMP pull
	// when InputNetConfig.TimeoutSec is zero.
	DefaultRTMPTimeoutSec = 10
	// DefaultRTSPTimeoutSec is the dial / read timeout (seconds) for RTSP
	// pull when InputNetConfig.TimeoutSec is zero.
	DefaultRTSPTimeoutSec = 10
)

Default* constants are the runtime-applied values when the matching configuration field is left zero / empty by the user. They are the SINGLE SOURCE OF TRUTH for implicit defaults — every consumer (handler, packager, transcoder, validator) must reference these instead of inlining its own literal so behaviour stays consistent across services.

When changing a default here, audit references with:

grep -RIn 'Default[A-Z][A-Za-z]*' internal/ config/

to make sure no stale literal still exists in a service module.

View Source
const MaxStreamCodeLen = 128

MaxStreamCodeLen is the maximum length of a user-defined stream code.

View Source
const MaxVODNameLen = 64

MaxVODNameLen bounds the length of a VOD mount name used as a URL host component.

View Source
const MaxWatermarkAssetBytes int64 = 8 * 1024 * 1024 // 8 MiB

MaxWatermarkAssetBytes caps a single uploaded watermark. Logos are typically a few hundred KB at most; the cap defends the assets directory against a runaway upload accidentally filling the volume.

View Source
const MaxWatermarkAssetIDLen = 64

MaxWatermarkAssetIDLen is the maximum length of an asset ID. The bound matches the rendered filename length the watermarks service produces on disk so it can never overflow PATH_MAX in practice.

View Source
const MaxWatermarkAssetNameLen = 128

MaxWatermarkAssetNameLen caps the human display name. UI dropdowns and activity logs render this; oversize values would push the layout around.

Variables

This section is empty.

Functions

func AggregateBitrateKbps added in v0.0.47

func AggregateBitrateKbps(tracks []MediaTrackInfo) int

AggregateBitrateKbps sums every track's BitrateKbps into a single number, the value the runtime envelope publishes as "input total" / "output total".

func CodecLabel added in v0.0.47

func CodecLabel(c AVCodec) string

CodecLabel returns the canonical lowercase string used in MediaTrackInfo.Codec for an AVCodec — kept here (rather than on the AVCodec type) so the JSON contract lives next to the struct that exposes it.

"mp2a" is the FourCC for MPEG-1/2 Audio (Layer I/II/III). Matches the Flussonic UI label convention so operators familiar with that tooling see the same string for the same codec on both servers; the underlying format covers everything that arrives via TS stream_type 0x03 or 0x04.

func IsCopyInput added in v0.0.20

func IsCopyInput(in Input) bool

IsCopyInput reports whether the given Input is a `copy://` reference. Convenience helper for callers that want to short-circuit per-input logic (e.g. skip network reconnect logic for copy inputs).

func IsCopyShapeError added in v0.0.20

func IsCopyShapeError(err error) bool

IsCopyShapeError reports whether err is a *CopyShapeError.

func IsMixerInput added in v0.0.22

func IsMixerInput(in Input) bool

IsMixerInput reports whether the given Input is a `mixer://` reference.

func IsMixerShapeError added in v0.0.22

func IsMixerShapeError(err error) bool

IsMixerShapeError reports whether err is a *MixerShapeError.

func ResolveAudioEncoder added in v0.0.30

func ResolveAudioEncoder(codec AudioCodec) string

ResolveAudioEncoder maps a user-facing codec to FFmpeg's encoder name. Empty / "copy" → AAC default (since copy is decided separately via AudioTranscodeConfig.Copy).

MP2 (mp2a) maps to FFmpeg's built-in `mp2` encoder — MPEG-1 Layer II, always available in standard FFmpeg builds (no external library needed, unlike libtwolame). Valid bitrates 32-384 kbps; FFmpeg silently rounds to the nearest valid Layer II rate. Used for DVB broadcast contribution feeds and legacy IPTV headends that consume Layer II input.

func ResolveVideoEncoder added in v0.0.30

func ResolveVideoEncoder(codec VideoCodec, hw HWAccel) string

ResolveVideoEncoder maps a user-facing codec string + global HW backend to the FFmpeg encoder name that buildFFmpegArgs will emit.

Routing highlights:

  • "" / "h264" / "avc" + HW=nvenc → "h264_nvenc"; else "libx264"
  • "h265" / "hevc" + HW=nvenc → "hevc_nvenc"; else "libx265"
  • VAAPI / QSV / VideoToolbox: no implicit routing — caller must spell the full encoder name (e.g. "h264_vaapi"); empty stays "libx264".

func StreamMainBufferIsTS added in v0.0.62

func StreamMainBufferIsTS(s *Stream) bool

StreamMainBufferIsTS reports whether the stream's main playback buffer contains raw MPEG-TS bytes (Packet.TS) rather than decoded AVPackets (Packet.AV). Three sources of TS in the main buffer:

  • any transcoder is active → buffer holds the transcoder's stdout TS;
  • source is a raw-TS protocol (UDP / HLS / SRT / File) → ingestor uses TSPassthroughPacketReader and writes Packet.TS directly;
  • rendition buffers always hold TS (transcoder per-profile output) — but this helper reports on the MAIN buffer; callers that need to know about rendition shape should use streamHasRenditions instead.

Used by mixer / copy paths to decide whether to subscribe-and-read AV packets vs run a TS demuxer over the buffer's chunks. Misclassifying the shape silently drops audio (rare AV packets but a mode-mismatched consumer filters them all out).

func ValidateCopyShape added in v0.0.20

func ValidateCopyShape(s *Stream, lookup StreamLookup) error

ValidateCopyShape enforces copy:// constraints on a single stream:

  1. Self-copy (`copy://A` inside stream A) is rejected — a stream cannot re-stream itself; that's an infinite fan-out loop.
  2. When upstream X has an ABR ladder, downstream MUST satisfy: a) `copy://X` is the SOLE input (no fallback in v1) b) downstream's own `transcoder` field is nil (ladder is inherited from upstream; configuring local re-transcode is ambiguous)
  3. When mixing copy:// inputs with regular network inputs in the same priority list, all referenced upstreams must be single-stream (no ABR), since failover between rendition shapes is not supported in v1.

`lookup` resolves upstream streams. Missing upstream is treated as "shape unknown" — the rule it enables is skipped, never failed. The coordinator validates upstream presence at start time as a hard error.

func ValidateMixerShape added in v0.0.22

func ValidateMixerShape(s *Stream, lookup StreamLookup) error

ValidateMixerShape enforces mixer:// constraints on a single stream:

  1. Self-mix (`mixer://A,X` or `mixer://X,A` inside stream A) is rejected — a stream can't mix with itself.
  2. mixer:// must be the SOLE input — fallback inputs are not supported in v1 (failure semantics differ from regular failover; mixing across two unrelated mixer specs is undefined).

ABR upstreams ARE allowed:

  • When the video upstream has an ABR ladder AND the downstream has no own transcoder, the runtime mirrors the video ladder (N rungs out, audio fanned-out across them). When the downstream HAS its own transcoder, the runtime taps only the best rendition of each upstream and feeds the encoder.
  • Either upstream's audio is always single-source — the best rendition of the audio upstream when it's ABR.

`lookup` resolves upstream streams. Missing upstream is treated as "shape unknown" — the rule it enables is skipped, never failed. The MixerReader catches missing upstream at runtime as a hard error.

func ValidateStreamCode

func ValidateStreamCode(s string) error

ValidateStreamCode reports whether s is a non-empty valid stream code.

func ValidateVODName

func ValidateVODName(s string) error

ValidateVODName reports whether s is a non-empty, URL-host-safe VOD mount name.

func ValidateWatermarkAssetID added in v0.0.44

func ValidateWatermarkAssetID(id string) error

ValidateWatermarkAssetID enforces the safe filename charset. Asset IDs flow into filesystem paths, so we forbid anything that would let a caller escape the assets directory or collide with sidecar metadata names.

Types

type AVCodec

type AVCodec uint8

AVCodec identifies elementary stream codec for AVPacket payloads.

const (
	AVCodecUnknown AVCodec = iota
	AVCodecH264
	AVCodecH265
	AVCodecAAC

	// AVCodecRawTSChunk is a marker codec used to carry raw MPEG-TS bytes
	// through the AVPacket-shaped pipeline without forcing a demux/remux
	// round-trip. The buffer-write step recognises this codec and writes the
	// chunk to `buffer.Packet.TS` instead of `buffer.Packet.AV`. Consumers
	// that prefer raw TS (HLS/DASH segmenters, transcoder ffmpeg-stdin pump)
	// then forward bytes verbatim — preserving PCR continuity and original
	// PIDs that a demux/remux cycle would lose.
	//
	// Used only by sources that ingest pre-muxed MPEG-TS (UDP, HLS, SRT,
	// File). RTSP / RTMP / Copy / Mixer continue to emit decoded AVPackets.
	AVCodecRawTSChunk

	// AVCodecMP2 is MPEG-1 / MPEG-2 Audio Layer I or II. Common in DVB
	// radio channels (e.g. VOH FM) and SD TV audio. TS stream_type 0x03 /
	// 0x04 both land here — the container doesn't encode Layer; we look
	// at the frame header to distinguish Layer III (= MP3, AVCodecMP3).
	AVCodecMP2

	// AVCodecMP3 is MPEG-1 / MPEG-2 Audio Layer III (the familiar "MP3").
	// Same TS stream_type as MP2 (0x03 / 0x04); detected by parsing the
	// MPEG audio frame header so the UI label and downstream codec-aware
	// consumers (mixer, transcoder configs targeting "mp3") see the right
	// codec instead of the generic mp2a fallback.
	AVCodecMP3

	// AVCodecAC3 is Dolby Digital. TS stream_type 0x81 in ATSC headends,
	// or 0x06 + AC-3 descriptor (tag 0x6A) in DVB. Common in HD broadcast
	// audio for premium / international channels.
	AVCodecAC3

	// AVCodecEAC3 is Enhanced AC-3 / Dolby Digital Plus. TS stream_type
	// 0x87 in ATSC, or 0x06 + Enhanced AC-3 descriptor (tag 0x7A) in DVB.
	// Carries 5.1+ surround for premium HD/UHD broadcast.
	AVCodecEAC3

	// AVCodecAV1 is the AOMedia AV1 video codec. TS support added in DVB
	// TS 101 154 v2.7.1 via stream_type 0x06 + registration descriptor
	// format ID "AV01" — modern UHD streaming uses this. Encode via
	// libsvtav1; ingest recognises the descriptor.
	AVCodecAV1

	// AVCodecMPEG2Video is MPEG-2 Part 2 (H.262) video. TS stream_type
	// 0x02 (also 0x01 for legacy MPEG-1 video, lumped here since the
	// downstream pipeline treats them identically). Standard for DVB SD
	// television broadcast — many older IPTV channels still carry video
	// in this codec.
	AVCodecMPEG2Video
)

AVCodec values.

func (AVCodec) IsAudio added in v0.0.22

func (c AVCodec) IsAudio() bool

IsAudio reports whether the codec carries an audio elementary stream.

func (AVCodec) IsVideo added in v0.0.22

func (c AVCodec) IsVideo() bool

IsVideo reports whether the codec carries a video elementary stream.

type AVPacket

type AVPacket struct {
	Codec         AVCodec
	Data          []byte
	PTSms         uint64
	DTSms         uint64
	KeyFrame      bool
	Discontinuity bool
}

AVPacket is one decoded video access unit (Annex B H.264/H.265) or one AAC frame (ADTS in Data). PTSms and DTSms are presentation / decode timestamps in milliseconds (MPEG-TS / gomedia convention).

func (*AVPacket) Clone

func (p *AVPacket) Clone() *AVPacket

Clone returns a deep copy suitable for buffer fan-out.

type AudioCodec

type AudioCodec string

AudioCodec identifies the audio compression format.

const (
	AudioCodecAAC  AudioCodec = "aac"  // default for HLS/DASH
	AudioCodecMP2  AudioCodec = "mp2a" // MPEG-1/2 Audio Layer II — DVB broadcast contribution feeds
	AudioCodecMP3  AudioCodec = "mp3"  // legacy compatibility
	AudioCodecAC3  AudioCodec = "ac3"  // Dolby Digital — broadcast use
	AudioCodecEAC3 AudioCodec = "eac3" // Dolby Digital Plus — premium HD/UHD broadcast (5.1+)
	AudioCodecCopy AudioCodec = "copy" // passthrough — no re-encode
)

AudioCodec values name supported output audio codecs.

type AudioConfig

type AudioConfig struct {
	Codec AudioCodec `json:"codec" yaml:"codec"`

	// Bitrate is the audio bitrate in kbps. Typical: 128 (stereo), 192 (high quality).
	Bitrate int `json:"bitrate" yaml:"bitrate"`

	// SampleRate is the output sample rate in Hz. Typical: 44100, 48000.
	SampleRate int `json:"sample_rate" yaml:"sample_rate"`

	// Channels: 1 = mono, 2 = stereo, 6 = 5.1 surround.
	Channels int `json:"channels" yaml:"channels"`

	// Language is the ISO 639-1 code embedded in HLS/DASH metadata, e.g. "en", "vi".
	Language string `json:"language" yaml:"language"`

	// Normalize applies EBU R128 loudness normalization (-23 LUFS).
	// Useful for broadcast compliance.
	Normalize bool `json:"normalize" yaml:"normalize"`
}

AudioConfig defines the audio encoding settings applied to all output profiles.

type AudioTranscodeConfig

type AudioTranscodeConfig struct {
	// Copy copies origin audio without re-encoding.
	Copy bool `json:"copy" yaml:"copy"`

	Codec AudioCodec `json:"codec" yaml:"codec"`

	// Bitrate is the audio bitrate in kbps.
	Bitrate int `json:"bitrate" yaml:"bitrate"`

	// SampleRate is output sample rate in Hz.
	SampleRate int `json:"sample_rate" yaml:"sample_rate"`

	// Channels: 1 = mono, 2 = stereo, 6 = 5.1.
	Channels int `json:"channels" yaml:"channels"`

	// Language is ISO 639-1 code, e.g. "en", "vi".
	Language string `json:"language" yaml:"language"`

	// Normalize applies EBU R128 loudness normalization.
	Normalize bool `json:"normalize" yaml:"normalize"`
}

AudioTranscodeConfig defines audio transcoding behavior.

type CopyShapeError added in v0.0.20

type CopyShapeError struct {
	StreamCode StreamCode
	Reason     string
}

CopyShapeError reports a copy:// configuration that violates the v1 constraints (e.g. local transcoder set when upstream has ABR, mixed shapes in the input list). The Reason string is API-surface text — the handler returns it verbatim in the 400 response.

func (*CopyShapeError) Error added in v0.0.20

func (e *CopyShapeError) Error() string

type DVRGap

type DVRGap struct {
	From     time.Time     `json:"from" yaml:"from"`         // wall time gap started
	To       time.Time     `json:"to" yaml:"to"`             // wall time recording resumed
	Duration time.Duration `json:"duration" yaml:"duration"` // To - From
}

DVRGap represents a period of signal loss or server downtime.

type DVRIndex

type DVRIndex struct {
	StreamCode    StreamCode `json:"stream_code" yaml:"stream_code"`
	StartedAt     time.Time  `json:"started_at" yaml:"started_at"`
	LastSegmentAt time.Time  `json:"last_segment_at,omitempty" yaml:"last_segment_at,omitempty"`

	SegmentCount   int   `json:"segment_count" yaml:"segment_count"`
	TotalSizeBytes int64 `json:"total_size_bytes" yaml:"total_size_bytes"`

	// Gaps is the list of known signal-loss / server-restart interruptions.
	Gaps []DVRGap `json:"gaps,omitempty" yaml:"gaps,omitempty"`
}

DVRIndex is the on-disk metadata index for a stream's DVR recording. Written atomically to {SegmentDir}/index.json after every segment flush.

Deliberately lightweight — no per-segment details. Per-segment timeline (wall time, duration, discontinuity) lives in playlist.m3u8 via #EXT-X-PROGRAM-DATE-TIME and #EXTINF tags.

type DecoderConfig

type DecoderConfig struct {
	// Name is the FFmpeg decoder name.
	// "" = let FFmpeg choose automatically.
	// Examples: "h264_cuvid", "h264_qsv".
	Name string `json:"name,omitempty" yaml:"name,omitempty"`
}

DecoderConfig defines decoder behavior.

type ErrorEntry added in v0.0.8

type ErrorEntry struct {
	Message string    `json:"message"`
	At      time.Time `json:"at"`
}

ErrorEntry is a single recorded error with the time it occurred. Used by manager (per input) and transcoder (per profile) to expose a short rolling history of errors via their RuntimeStatus APIs.

type Event

type Event struct {
	ID         string         `json:"id"` // UUID for idempotent delivery
	Type       EventType      `json:"type"`
	StreamCode StreamCode     `json:"stream_code"`
	OccurredAt time.Time      `json:"occurred_at"`
	Payload    map[string]any `json:"payload,omitempty"` // event-specific fields
}

Event is an immutable fact describing a domain state change.

type EventType

type EventType string

EventType identifies the kind of domain event that occurred.

const (
	// Stream lifecycle — published by coordinator and API handler.
	EventStreamCreated EventType = "stream.created"
	EventStreamStarted EventType = "stream.started"
	EventStreamStopped EventType = "stream.stopped"
	EventStreamDeleted EventType = "stream.deleted"

	// Input health — published by ingestor worker and stream manager.
	EventInputConnected    EventType = "input.connected"    // source connected successfully
	EventInputReconnecting EventType = "input.reconnecting" // transient error, retrying
	EventInputDegraded     EventType = "input.degraded"     // error detected by manager
	EventInputFailed       EventType = "input.failed"       // worker exited / non-retriable
	EventInputFailover     EventType = "input.failover"     // switched to lower-priority input

	// DVR recordings — published by dvr.Service.
	EventRecordingStarted EventType = "recording.started"
	EventRecordingStopped EventType = "recording.stopped"
	EventRecordingFailed  EventType = "recording.failed"
	EventSegmentWritten   EventType = "segment.written"

	// Transcoder — published by transcoder.Service.
	EventTranscoderStarted EventType = "transcoder.started"
	EventTranscoderStopped EventType = "transcoder.stopped"
	EventTranscoderError   EventType = "transcoder.error"
)

EventType values are emitted on the event bus for stream lifecycle, inputs, recordings, and segments.

const (
	EventSessionOpened EventType = "session.opened"
	EventSessionClosed EventType = "session.closed"
)

Session-related event types emitted on the bus when sessions open / close. Hooks can subscribe to persist analytics, send notifications, etc.

type GlobalConfig

type GlobalConfig struct {
	Server     *config.ServerConfig     `json:"server,omitempty" yaml:"server,omitempty"`
	Listeners  *config.ListenersConfig  `json:"listeners,omitempty" yaml:"listeners,omitempty"`
	Ingestor   *config.IngestorConfig   `json:"ingestor,omitempty" yaml:"ingestor,omitempty"`
	Buffer     *config.BufferConfig     `json:"buffer,omitempty" yaml:"buffer,omitempty"`
	Transcoder *config.TranscoderConfig `json:"transcoder,omitempty" yaml:"transcoder,omitempty"`
	Publisher  *config.PublisherConfig  `json:"publisher,omitempty" yaml:"publisher,omitempty"`
	Manager    *config.ManagerConfig    `json:"manager,omitempty" yaml:"manager,omitempty"`
	Hooks      *config.HooksConfig      `json:"hooks,omitempty" yaml:"hooks,omitempty"`
	Sessions   *config.SessionsConfig   `json:"sessions,omitempty" yaml:"sessions,omitempty"`
	Watermarks *config.WatermarksConfig `json:"watermarks,omitempty" yaml:"watermarks,omitempty"`
	Log        *config.LogConfig        `json:"log,omitempty" yaml:"log,omitempty"`
}

GlobalConfig holds all runtime configuration that is persisted in the store (as opposed to config.StorageConfig which is bootstrap-only from config.yaml/env).

Pointer fields: nil means the section is not configured and the corresponding service should not start. This allows users to enable/disable entire subsystems by adding or removing config sections via the API.

type HWAccel

type HWAccel string

HWAccel selects the hardware acceleration backend for encoding/decoding.

const (
	HWAccelNone         HWAccel = "none"         // CPU only (libx264, libx265)
	HWAccelNVENC        HWAccel = "nvenc"        // NVIDIA GPU (h264_nvenc, hevc_nvenc)
	HWAccelVAAPI        HWAccel = "vaapi"        // Intel/AMD GPU via VA-API (Linux)
	HWAccelVideoToolbox HWAccel = "videotoolbox" // Apple GPU (macOS)
	HWAccelQSV          HWAccel = "qsv"          // Intel Quick Sync Video
)

HWAccel values map to FFmpeg hardware device options.

type Hook

type Hook struct {
	ID     HookID   `json:"id" yaml:"id"`
	Name   string   `json:"name" yaml:"name"`
	Type   HookType `json:"type" yaml:"type"`
	Target string   `json:"target" yaml:"target"` // HTTP(S) URL or absolute file path
	Secret string   `json:"secret" yaml:"secret"` // HMAC-SHA256 signing secret (HTTP only)

	// EventTypes filters which events trigger delivery. Empty = all events.
	EventTypes []EventType `json:"event_types,omitempty" yaml:"event_types,omitempty"`

	// StreamCodes filters delivery by stream code.
	// Only and Except are mutually exclusive; Only takes precedence when both are set.
	// Omitting the field (nil) means all streams are included.
	StreamCodes *StreamCodeFilter `json:"stream_codes,omitempty" yaml:"stream_codes,omitempty"`

	// Metadata holds user-defined key-value pairs merged into every event payload
	// delivered by this hook. Useful for tagging events with custom context
	// (e.g. environment, tenant ID, region) without modifying the server config.
	Metadata map[string]string `json:"metadata,omitempty" yaml:"metadata,omitempty"`

	Enabled bool `json:"enabled" yaml:"enabled"`

	// MaxRetries is the number of delivery attempts before giving up.
	// 0 means use the server default (3). For HTTP hooks this caps retries
	// inside a single batch flush; events that still fail are re-queued
	// for the next flush regardless of MaxRetries.
	MaxRetries int `json:"max_retries" yaml:"max_retries"`

	// TimeoutSec is the per-attempt delivery timeout in seconds.
	// 0 means use the server default (10s).
	TimeoutSec int `json:"timeout_sec" yaml:"timeout_sec"`

	// BatchMaxItems caps the number of events bundled into one HTTP POST
	// body. 0 = use HooksConfig.BatchMaxItems, then DefaultHookBatchMaxItems.
	// Ignored for File hooks (they always write one event per line).
	BatchMaxItems int `json:"batch_max_items,omitempty" yaml:"batch_max_items,omitempty"`

	// BatchFlushIntervalSec is the maximum time a batch may sit before being
	// flushed even when below BatchMaxItems. 0 = use HooksConfig default,
	// then DefaultHookBatchFlushIntervalSec.
	BatchFlushIntervalSec int `json:"batch_flush_interval_sec,omitempty" yaml:"batch_flush_interval_sec,omitempty"`

	// BatchMaxQueueItems caps the per-hook in-memory queue (pending +
	// re-queued failures). When exceeded, the OLDEST events are dropped
	// with a warning log so the queue never grows unbounded against an
	// unreachable target. 0 = use HooksConfig default, then
	// DefaultHookBatchMaxQueueItems.
	BatchMaxQueueItems int `json:"batch_max_queue_items,omitempty" yaml:"batch_max_queue_items,omitempty"`
}

Hook is a registered external integration that receives domain events.

type HookID

type HookID string

HookID is the unique identifier for a registered hook.

type HookType

type HookType string

HookType is the delivery mechanism for a hook.

const (
	HookTypeHTTP HookType = "http"
	// HookTypeFile appends each event as a single JSON line to the path
	// in Hook.Target. Useful for downstream log shippers (Filebeat,
	// Vector, Promtail) and for ops-friendly local audit trails without
	// running an HTTP receiver.
	HookTypeFile HookType = "file"
)

HookType values name supported hook transports.

type Input

type Input struct {
	// URL is the source endpoint. See the package doc for supported formats.
	URL string `json:"url" yaml:"url"`

	// Priority determines failover order. Lower value = higher priority.
	// The Stream Manager always prefers the lowest-priority alive input.
	Priority int `json:"priority" yaml:"priority"`

	// Headers are arbitrary HTTP headers sent with every request for HTTP/HLS inputs.
	// Common uses:
	//   "Authorization": "Bearer <token>"
	//   "Authorization": "Basic <base64(user:pass)>"
	//   "X-Custom-Token": "secret"
	Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`

	// Params are extra URL query parameters merged into the source URL before
	// connecting. Used for protocols that carry credentials or options in the
	// query string (SRT ?passphrase=, S3 ?access_key= / ?secret_key=, etc.).
	//   "passphrase": "my-srt-passphrase"
	//   "access_key": "AKID..."   (S3)
	//   "secret_key": "wJal..."   (S3)
	Params map[string]string `json:"params,omitempty" yaml:"params,omitempty"`

	// Program selects a single MPEG-TS program when the source is a
	// multi-program transport stream (MPTS) — common in DVB headend feeds
	// where one multicast carries many channels. When > 0, the ingest
	// pipeline rewrites the PAT to advertise only the chosen program and
	// drops PMT / ES packets belonging to other programs, producing a
	// clean SPTS for downstream HLS / DASH / push consumers.
	//
	// Zero (default) disables filtering — the entire stream is forwarded
	// unchanged. Currently applies to UDP only; HLS / SRT / File ingest
	// are SPTS by convention so the filter is not wired for those (extend
	// reader.go if a real MPTS file/SRT use case arises). Ignored for
	// RTSP / RTMP, which are single-program by protocol design.
	Program int `json:"program,omitempty" yaml:"program,omitempty"`

	// Pids is an explicit allowlist of TS PIDs to keep — every other PID
	// is dropped at ingest. Used when the source PSI is unreliable (legacy
	// encoders with malformed PAT/PMT) or when the operator wants to cherry-
	// pick a subset (e.g. drop a teletext PID, keep only one of N audio
	// languages). The filter is purely PID-level: no PAT/PMT rewrite, no
	// CRC recompute. Operators must include every PID needed for playback
	// (typically PID 0 for PAT, the PMT PID, and the desired ES PIDs).
	//
	// Layers with Program when both are set: Program runs first (auto-
	// detect ES PIDs + rewrite PAT to single-program), then Pids further
	// restricts the output. Empty (default) disables the filter.
	//
	// Currently applies to UDP only — same rationale as Program.
	Pids []int `json:"pids,omitempty" yaml:"pids,omitempty"`

	// Net controls reconnect and timeout behaviour.
	Net InputNetConfig `json:"net,omitempty" yaml:"net,omitempty"`

	// Alive is a runtime-only field updated by the Stream Manager health checker.
	// Not persisted to storage.
	Alive bool `json:"-" yaml:"-"`
}

Input is a single ingest source for a stream. Multiple inputs can be configured; the Stream Manager selects the active one based on Priority and runtime health (Alive flag).

The only required field is URL. The ingestor derives the ingest protocol and connection mode (pull vs push-listen) automatically from the URL scheme and host — no additional protocol configuration is needed.

Supported URL formats:

Pull (server connects to remote source):
  rtmp://server.com/live/stream_key       RTMP pull from remote
  rtsp://camera.local:554/stream          RTSP pull (IP camera)
  http://cdn.example.com/live.ts          HTTP MPEG-TS stream
  https://cdn.example.com/playlist.m3u8   HLS pull (grafov m3u8 parser)
  udp://239.1.1.1:5000                    UDP multicast MPEG-TS
  srt://relay.example.com:9999            SRT pull (caller mode)
  file:///recordings/source.ts            local file (loops if ?loop=true)

Push (external encoder connects to our server):
  rtmp://0.0.0.0:1935/live/stream_key     RTMP push — our RTMP server listens
  srt://0.0.0.0:9999?streamid=stream_key  SRT push  — our SRT server listens

Push mode is detected automatically when the URL host is a wildcard address (0.0.0.0, ::, empty) and the scheme is rtmp or srt.

type InputNetConfig

type InputNetConfig struct {
	// TimeoutSec is the per-protocol operation budget the reader applies
	// when this input is opened. Semantics differ by protocol:
	//
	//   - HLS:  HTTP request timeout (entire round-trip incl. body) for
	//           the playlist GET. Segment GETs derive from this — typically
	//           4× the playlist budget, floored at the segment default.
	//   - RTMP: TCP dial timeout (handshake budget).
	//   - RTSP: dial + initial read timeout (until first packet).
	//   - SRT:  connection / handshake timeout.
	//
	// Zero uses the reader's per-protocol default
	// (DefaultHLSPlaylistTimeoutSec for HLS; DefaultRTMPTimeoutSec /
	// DefaultRTSPTimeoutSec for the rest).
	TimeoutSec int `json:"timeout_sec,omitempty" yaml:"timeout_sec,omitempty"`

	// InsecureTLS disables TLS certificate verification for HTTPS pulls
	// (HLS playlist + segment GETs). Default false — leave secure-by-default
	// for production. Use only when the source uses a self-signed,
	// expired, or otherwise-invalid certificate that you trust at the
	// network level (private VLAN, fixed IP allowlist).
	InsecureTLS bool `json:"insecure_tls,omitempty" yaml:"insecure_tls,omitempty"`
}

InputNetConfig controls per-input network behaviour.

Reconnect / silence-detection knobs were removed because they were declared but never consumed by any reader: pull workers use a hardcoded exponential backoff on transient errors (worker.go), and stream-level liveness is the manager's job (manager.input_packet_timeout_sec). Reintroduce specific knobs only when a reader actually wires them.

type InterlaceMode added in v0.0.6

type InterlaceMode string

InterlaceMode selects deinterlacing behavior for the source.

const (
	InterlaceAuto        InterlaceMode = "auto"        // detect parity each frame
	InterlaceTopField    InterlaceMode = "tff"         // top field first (BBC HD, most ATSC)
	InterlaceBottomField InterlaceMode = "bff"         // bottom field first (legacy DV)
	InterlaceProgressive InterlaceMode = "progressive" // assert source is progressive — skip filter
)

InterlaceMode values map to yadif/yadif_cuda parameters. "" disables the filter.

type MediaTrackInfo added in v0.0.47

type MediaTrackInfo struct {
	Kind        MediaTrackKind `json:"kind"`
	Codec       string         `json:"codec"`            // "h264" | "h265" | "aac" | "mp2a"
	Width       int            `json:"width,omitempty"`  // video only
	Height      int            `json:"height,omitempty"` // video only
	BitrateKbps int            `json:"bitrate_kbps"`
}

MediaTrackInfo describes one elementary track in a stream's input or output.

The runtime envelope exposes lists of these so the UI can render the "Input media info / Output media info" panels (codec, resolution, bitrate) without inferring shape from per-protocol config blobs.

Fields are best-effort:

  • Width/Height are populated only after the first keyframe whose SPS could be parsed; zero means "not yet decoded".
  • BitrateKbps is an EWMA over the last few seconds of bytes seen for this codec on this input; zero means "no bytes yet" (just-connected stream, or output not running).

func OutputTracks added in v0.0.47

func OutputTracks(tc *TranscoderConfig) []MediaTrackInfo

OutputTracks derives the list of output media tracks from a stream's persisted Transcoder config. One MediaTrackInfo per ABR rendition is emitted, plus one entry for the audio output. Passthrough renditions (Codec=="copy") show codec="copy" with zero resolution/bitrate — the UI can render them as "follows input" or fall back to the input track.

When `tc` is nil the stream has no transcoder configured — caller is expected to fall back to input tracks (no-transcode mirror).

type MediaTrackKind added in v0.0.47

type MediaTrackKind string

MediaTrackKind identifies whether a track carries video or audio.

const (
	MediaTrackVideo MediaTrackKind = "video"
	MediaTrackAudio MediaTrackKind = "audio"
)

MediaTrackKind values.

type MixerShapeError added in v0.0.22

type MixerShapeError struct {
	StreamCode StreamCode
	Reason     string
}

MixerShapeError reports a mixer:// configuration that violates the v1 constraints. Reason is API-surface text — handler returns it verbatim.

func (*MixerShapeError) Error added in v0.0.22

func (e *MixerShapeError) Error() string

type OutputProtocols

type OutputProtocols struct {
	// HLS enables Apple HTTP Live Streaming (m3u8 + segments over HTTP).
	// Compatible with browsers, iOS, Android, Smart TVs.
	HLS bool `json:"hls" yaml:"hls"`

	// DASH enables MPEG-DASH packaging over HTTP.
	// Required for Widevine/PlayReady DRM.
	DASH bool `json:"dash" yaml:"dash"`

	// RTSP opens an RTSP listener for pull clients (VLC, broadcast tools).
	RTSP bool `json:"rtsp" yaml:"rtsp"`

	// RTMP opens an RTMP publish endpoint for legacy players/CDNs.
	RTMP bool `json:"rtmp" yaml:"rtmp"`

	// SRT opens an SRT listener port for contribution-quality pull.
	SRT bool `json:"srt" yaml:"srt"`

	// MPEGTS exposes raw MPEG-TS over chunked HTTP at /<code>/mpegts —
	// the lowest-latency relay path between Open-Streamer instances (and
	// any HTTP client that can consume chunked TS, e.g. ffmpeg / VLC).
	// Latency is bounded only by network RTT and one buffer-hub chunk
	// (typically 50–200 ms vs 4–10 s for HLS / DASH).
	//
	// No goroutine is started per-stream; the endpoint subscribes to the
	// playback buffer on demand. Disabling the flag turns the endpoint
	// into a 404 for that stream so operators can opt out per-stream
	// without changing the global router.
	MPEGTS bool `json:"mpegts" yaml:"mpegts"`
}

OutputProtocols defines which delivery protocols are opened for a stream. Each enabled protocol starts a corresponding listener or packager. Protocol-level settings (ports, segment duration, CDN URL, etc.) are configured globally in the server config.

type PlaySession added in v0.0.42

type PlaySession struct {
	// ID uniquely identifies the session across the server's lifetime. For
	// segment protocols (HLS/DASH) it is the deterministic fingerprint hash
	// (so reconnects within the idle window collapse onto one session); for
	// connection-bound protocols (RTMP/SRT/RTSP) it is a random UUID.
	ID string `json:"id"`

	// StreamCode is the foreign key to Stream.Code.
	StreamCode StreamCode `json:"stream_code"`

	// Protocol is the wire protocol used to deliver this session.
	Protocol SessionProto `json:"proto"`

	// IP is the remote peer address (no port). For HTTP-based protocols this
	// is the X-Forwarded-For head when present, otherwise the RemoteAddr.
	IP string `json:"ip"`

	// UserAgent is the browser/player User-Agent (HTTP) or the equivalent
	// flashVer field for RTMP. May be empty.
	UserAgent string `json:"user_agent,omitempty"`

	// Referer is the HTTP Referer when present (HLS/DASH only).
	Referer string `json:"referer,omitempty"`

	// QueryString is the raw query of the FIRST request that opened the
	// session (HLS/DASH) — useful when token/abr-variant info is encoded there.
	QueryString string `json:"query_string,omitempty"`

	// Token is the value of `?token=` from the first request, when present.
	// When set, NamedBy is SessionNamedByToken.
	Token string `json:"token,omitempty"`

	// UserName is a human label. Resolved from token claims if available,
	// otherwise the fingerprint hash short form. Empty when no identity could
	// be resolved.
	UserName string `json:"user_name,omitempty"`

	// NamedBy records how UserName was resolved.
	NamedBy SessionNamedBy `json:"named_by,omitempty"`

	// Country is an ISO 3166-1 alpha-2 country code from the configured
	// GeoIP resolver, or "" when GeoIP is disabled / lookup failed.
	Country string `json:"country,omitempty"`

	// Bytes is the cumulative bytes sent to this client since session open.
	// HLS/DASH: sum of segment + manifest response bodies. RTMP/SRT: write
	// bytes from the publisher's outbound pipeline.
	Bytes int64 `json:"bytes"`

	// Secure is true when the underlying transport was TLS / SRTS / RTMPS.
	Secure bool `json:"secure"`

	// DVR is true when playback originated from the DVR window (timeshift),
	// false for live edge. Open-Streamer's DVR delivery is not yet routed
	// through here so this is currently always false; field reserved.
	DVR bool `json:"dvr"`

	// OpenedAt is the time the first activity for this session was observed.
	OpenedAt time.Time `json:"opened_at"`

	// StartedAt is when the first byte of media was delivered (segment data
	// for HLS/DASH, first frame after RTMP play handshake). Zero before
	// media flows.
	StartedAt time.Time `json:"started_at,omitempty"`

	// UpdatedAt is the time of the most recent activity. Idle reaper closes
	// sessions whose UpdatedAt is older than (now - idle_timeout).
	UpdatedAt time.Time `json:"updated_at"`

	// ClosedAt is set when the session ends. nil while active.
	ClosedAt *time.Time `json:"closed_at,omitempty"`

	// CloseReason explains why the session ended. Empty while active.
	CloseReason SessionCloseReason `json:"close_reason,omitempty"`
}

PlaySession is the audit record for one client watching a stream. It is created when the first activity for a given (stream, fingerprint) pair is observed and stays live until the idle timer expires or the transport-level connection closes.

All time fields are wall-clock UTC; default Go RFC3339 JSON encoding.

func (*PlaySession) Active added in v0.0.42

func (s *PlaySession) Active() bool

Active reports whether the session is still considered open (ClosedAt unset).

func (*PlaySession) Duration added in v0.0.42

func (s *PlaySession) Duration() time.Duration

Duration returns the elapsed time between OpenedAt and either ClosedAt (when set) or now. Zero when OpenedAt is the zero value.

type PushDestination

type PushDestination struct {
	// URL is the destination ingest endpoint.
	// Supported schemes:
	//   rtmp://  — plain TCP, default port 1935 (e.g. rtmp://a.rtmp.youtube.com/live2/{key})
	//   rtmps:// — TLS-wrapped RTMP, default port 443 (e.g. rtmps://live-api-s.facebook.com:443/rtmp/{key})
	URL string `json:"url" yaml:"url"`

	// Enabled controls whether this destination is active.
	Enabled bool `json:"enabled" yaml:"enabled"`

	// TimeoutSec is the connection/write timeout in seconds.
	TimeoutSec int `json:"timeout_sec" yaml:"timeout_sec"`

	// RetryTimeoutSec is the delay between retry attempts in seconds.
	RetryTimeoutSec int `json:"retry_timeout_sec" yaml:"retry_timeout_sec"`

	// Limit is the maximum number of retry attempts. 0 = unlimited.
	Limit int `json:"limit" yaml:"limit"`

	// Comment is a human-readable note for this destination.
	Comment string `json:"comment" yaml:"comment"`

	// Status is a runtime-only field updated by the publisher.
	// Not persisted to storage.
	Status PushStatus `json:"status,omitempty" yaml:"-"`
}

PushDestination is an external endpoint the server actively pushes the stream to.

type PushStatus

type PushStatus string

PushStatus is the runtime state of a push destination.

const (
	PushStatusIdle       PushStatus = "idle"
	PushStatusConnecting PushStatus = "connecting"
	PushStatusActive     PushStatus = "active"
	PushStatusRetrying   PushStatus = "retrying"
	PushStatusFailed     PushStatus = "failed"
	PushStatusDisabled   PushStatus = "disabled"
)

PushStatus values describe outbound publisher connectivity.

type Recording

type Recording struct {
	ID         RecordingID     `json:"id" yaml:"id"`
	StreamCode StreamCode      `json:"stream_code" yaml:"stream_code"`
	StartedAt  time.Time       `json:"started_at" yaml:"started_at"`
	StoppedAt  *time.Time      `json:"stopped_at,omitempty" yaml:"stopped_at,omitempty"`
	Status     RecordingStatus `json:"status" yaml:"status"`

	// SegmentDir is the absolute path to the directory holding TS files,
	// playlist.m3u8, and index.json.
	SegmentDir string `json:"segment_dir" yaml:"segment_dir"`
}

Recording represents the lifecycle metadata for a DVR recording session. ID equals StreamCode — one persistent recording per stream. Segment data lives in DVRIndex (index.json on disk), not here.

type RecordingID

type RecordingID string

RecordingID is the unique identifier for a DVR recording. Always equals the stream code — one recording per stream.

type RecordingStatus

type RecordingStatus string

RecordingStatus represents the lifecycle state of a recording.

const (
	RecordingStatusRecording RecordingStatus = "recording"
	RecordingStatusStopped   RecordingStatus = "stopped"
	RecordingStatusFailed    RecordingStatus = "failed"
)

RecordingStatus values.

type ResizeMode added in v0.0.6

type ResizeMode string

ResizeMode controls how the source frame is fitted into the output dimensions.

const (
	ResizeModePad     ResizeMode = "pad"     // letterbox: keep aspect, fill remainder with black
	ResizeModeCrop    ResizeMode = "crop"    // fill: keep aspect, crop excess
	ResizeModeStretch ResizeMode = "stretch" // distort: scale to W:H, ignore source aspect
	ResizeModeFit     ResizeMode = "fit"     // keep aspect, no padding (output may be smaller than W:H)
)

ResizeMode values match Flussonic's resize modes. "" defaults to ResizeModePad.

func ResolveResizeMode added in v0.0.30

func ResolveResizeMode(m ResizeMode) ResizeMode

ResolveResizeMode normalizes a free-form resize mode to the canonical constant. Empty / unknown → ResizeModePad.

type SessionCloseReason added in v0.0.42

type SessionCloseReason string

SessionCloseReason is set on PlaySession.CloseReason when the session ends.

const (
	SessionCloseIdle     SessionCloseReason = "idle"        // no activity within the configured idle window
	SessionCloseClient   SessionCloseReason = "client_gone" // TCP/UDP peer closed (RTMP/SRT/RTSP)
	SessionCloseShutdown SessionCloseReason = "shutdown"    // server shutting down
	SessionCloseKicked   SessionCloseReason = "kicked"      // operator force-closed via API
)

SessionCloseReason values.

type SessionNamedBy added in v0.0.42

type SessionNamedBy string

SessionNamedBy describes how the session's UserName was resolved. Mirrors Flussonic's `named_by` so external dashboards expecting that vocabulary keep working.

const (
	SessionNamedByToken       SessionNamedBy = "token"       // resolved from a `token` query param
	SessionNamedByConfig      SessionNamedBy = "config"      // resolved from server-side config (future: signed URL)
	SessionNamedByFingerprint SessionNamedBy = "fingerprint" // synthesised hash of ip+ua+stream
)

SessionNamedBy values.

type SessionProto added in v0.0.42

type SessionProto string

SessionProto identifies the network protocol carrying a play session. It mirrors the on-the-wire transport — application-layer flavours (e.g. browser-embedded HLS.js vs native Safari) are not distinguished.

const (
	SessionProtoHLS  SessionProto = "hls"
	SessionProtoDASH SessionProto = "dash"
	SessionProtoRTMP SessionProto = "rtmp"
	SessionProtoSRT  SessionProto = "srt"
	SessionProtoRTSP SessionProto = "rtsp"
)

SessionProto values for each playback protocol Open-Streamer serves.

type Stream

type Stream struct {
	// Code is the unique key chosen by the user ([a-zA-Z0-9_]).
	Code StreamCode `json:"code" yaml:"code"`

	Name        string   `json:"name" yaml:"name"`
	Description string   `json:"description" yaml:"description"`
	Tags        []string `json:"tags" yaml:"tags"`

	// StreamKey is used to authenticate RTMP/SRT push ingest.
	StreamKey string `json:"stream_key" yaml:"stream_key"`

	// Status is the runtime lifecycle state.
	// It is never persisted — always computed from the coordinator's in-memory
	// state and overlaid by the API layer before returning responses to clients.
	Status StreamStatus `json:"-" yaml:"-"`

	// Disabled when true excludes the stream from server bootstrap and rejects pipeline Start.
	Disabled bool `json:"disabled" yaml:"disabled"`

	// Inputs are the available ingest sources ordered by Priority.
	// The Stream Manager monitors health and switches between them on failure.
	Inputs []Input `json:"inputs" yaml:"inputs"`

	// Transcoder controls encoding/decoding settings.
	// nil means no transcoding for this stream.
	Transcoder *TranscoderConfig `json:"transcoder,omitempty" yaml:"transcoder,omitempty"`

	// Protocols defines which delivery protocols are opened for this stream.
	// The server opens a listener/packager for each enabled protocol.
	// Protocol-level config (ports, segment duration, CDN URL) lives in server config.
	Protocols OutputProtocols `json:"protocols" yaml:"protocols"`

	// Push is the list of external destinations the server actively pushes to.
	// Each entry defines one push target (YouTube, Facebook, Twitch, CDN relay, etc.).
	Push []PushDestination `json:"push" yaml:"push"`

	// DVR overrides the global DVR settings for this specific stream.
	// If nil, the global config is used (when DVR is enabled globally).
	DVR *StreamDVRConfig `json:"dvr,omitempty" yaml:"dvr,omitempty"`

	// Watermark is an optional text or image overlay applied before encoding.
	Watermark *WatermarkConfig `json:"watermark,omitempty" yaml:"watermark,omitempty"`

	// Thumbnail controls periodic screenshot generation for preview images.
	Thumbnail *ThumbnailConfig `json:"thumbnail,omitempty" yaml:"thumbnail,omitempty"`
}

Stream is the central domain entity. It describes everything needed to ingest, process, and deliver a live stream.

func (*Stream) ValidateInputPriorities

func (s *Stream) ValidateInputPriorities() error

ValidateInputPriorities enforces that input priorities are contiguous and sorted. For N inputs, expected priorities are exactly 0..N-1 in ascending order.

func (*Stream) ValidateUniqueInputs

func (s *Stream) ValidateUniqueInputs() error

ValidateUniqueInputs enforces that inputs in one stream are not duplicated. Two inputs are considered duplicates if their URL (trimmed) is identical.

type StreamCode

type StreamCode string

StreamCode is the user-assigned primary key for a stream. Allowed characters: a-z, A-Z, 0-9, underscore.

func CopyInputTarget added in v0.0.20

func CopyInputTarget(in Input) (StreamCode, error)

CopyInputTarget extracts the upstream code from a copy:// input. Returns ("", error) for non-copy or malformed inputs. The error wraps protocol.CopyTarget's error so callers can preserve the user-facing message.

func MixerInputSpec added in v0.0.22

func MixerInputSpec(in Input) (videoCode, audioCode StreamCode, audioFailureContinue bool, err error)

MixerInputSpec parses a mixer:// input. Returns ("", "", false, error) for non-mixer or malformed URLs. Wraps protocol.MixerTargets so callers can preserve the user-facing message.

type StreamCodeFilter

type StreamCodeFilter struct {
	// Only delivers events only for streams in this list.
	Only []StreamCode `json:"only,omitempty" yaml:"only,omitempty"`
	// Except delivers events for all streams except those in this list.
	Except []StreamCode `json:"except,omitempty" yaml:"except,omitempty"`
}

StreamCodeFilter defines include/exclude rules for stream code matching. Only and Except are mutually exclusive; Only takes precedence when both are set.

func (*StreamCodeFilter) Matches

func (f *StreamCodeFilter) Matches(code StreamCode) bool

Matches reports whether the given stream code passes the filter.

type StreamDVRConfig

type StreamDVRConfig struct {
	Enabled bool `json:"enabled" yaml:"enabled"`

	// RetentionSec is the retention window in seconds.
	// 0 = keep forever.
	RetentionSec int `json:"retention_sec" yaml:"retention_sec"`

	// SegmentDuration overrides the global segment length in seconds.
	// 0 = use default (4s).
	SegmentDuration int `json:"segment_duration" yaml:"segment_duration"`

	// StoragePath overrides the default DVR root directory for this stream.
	// "" = use "./dvr/{streamCode}".
	StoragePath string `json:"storage_path" yaml:"storage_path"`

	// MaxSizeGB caps total disk usage. Oldest segments pruned when exceeded.
	// 0 = no limit.
	MaxSizeGB float64 `json:"max_size_gb" yaml:"max_size_gb"`
}

StreamDVRConfig overrides the global DVR settings for a specific stream.

type StreamLookup added in v0.0.20

type StreamLookup func(StreamCode) (*Stream, bool)

StreamLookup returns the upstream stream by code. Used by ValidateCopyShape to inspect upstream transcoder shape without coupling domain to the repository layer. The bool reflects "found"; missing upstream is not an error here (handled at runtime by the coordinator).

type StreamStatus

type StreamStatus string

StreamStatus represents the lifecycle state of a stream.

const (
	StatusIdle     StreamStatus = "idle"
	StatusActive   StreamStatus = "active"
	StatusDegraded StreamStatus = "degraded"
	StatusStopped  StreamStatus = "stopped"
)

StreamStatus values are used by the stream manager and API.

type ThumbnailConfig

type ThumbnailConfig struct {
	Enabled bool `json:"enabled" yaml:"enabled"`

	// IntervalSec generates one thumbnail every N seconds.
	IntervalSec int `json:"interval_sec" yaml:"interval_sec"`

	// Width and Height of the output thumbnail in pixels.
	// 0 = match source resolution.
	Width  int `json:"width" yaml:"width"`
	Height int `json:"height" yaml:"height"`

	// Quality is the JPEG quality (1–31, lower = better). Default: 5.
	Quality int `json:"quality" yaml:"quality"`

	// OutputDir is relative to the publisher HLS directory.
	// E.g. "thumbnails" → written to {hls_dir}/{stream_code}/thumbnails/thumb.jpg
	OutputDir string `json:"output_dir" yaml:"output_dir"`
}

ThumbnailConfig controls periodic screenshot generation for stream preview. Thumbnails are written as JPEG files alongside HLS segments.

type TranscoderConfig

type TranscoderConfig struct {
	// Mode selects the FFmpeg process topology. Empty = TranscoderModeMulti.
	Mode TranscoderMode `json:"mode,omitempty" yaml:"mode,omitempty"`

	Video   VideoTranscodeConfig   `json:"video" yaml:"video"`
	Audio   AudioTranscodeConfig   `json:"audio" yaml:"audio"`
	Decoder DecoderConfig          `json:"decoder" yaml:"decoder"`
	Global  TranscoderGlobalConfig `json:"global" yaml:"global"`

	// ExtraArgs are raw FFmpeg arguments appended after the generated command.
	// Use with caution — may conflict with generated arguments.
	ExtraArgs []string `json:"extra_args,omitempty" yaml:"extra_args,omitempty"`

	// Watermark is a runtime-only field populated by the coordinator from
	// Stream.Watermark before each transcoder.Start. Not persisted on the
	// transcoder section (json/yaml "-") so the API surface keeps
	// Stream.Watermark as the single source of truth.
	Watermark *WatermarkConfig `json:"-" yaml:"-"`
}

TranscoderConfig is the complete transcoding configuration for a stream.

func (*TranscoderConfig) IsMultiOutput added in v0.0.51

func (t *TranscoderConfig) IsMultiOutput() bool

IsMultiOutput reports whether the resolved transcoder mode is multi-output. Treats empty Mode as the default (multi). Hot path so callers don't have to repeat the empty-string fallback.

func (*TranscoderConfig) ValidateMode added in v0.0.51

func (t *TranscoderConfig) ValidateMode() error

ValidateMode rejects unknown TranscoderMode values at save time so a typo can't silently pin the stream into legacy / multi without operator awareness. Empty Mode is allowed and routes to multi via IsMultiOutput.

type TranscoderGlobalConfig

type TranscoderGlobalConfig struct {
	// HW selects the acceleration backend.
	HW HWAccel `json:"hw" yaml:"hw"`

	// FPS sets output framerate. 0 = source/default.
	FPS int `json:"fps" yaml:"fps"`

	// GOP sets keyframe interval in frames. 0 = encoder default.
	GOP int `json:"gop" yaml:"gop"`

	// DeviceID selects hardware device index.
	DeviceID int `json:"deviceid" yaml:"deviceid"`
}

TranscoderGlobalConfig holds global transcoder parameters.

type TranscoderMode added in v0.0.51

type TranscoderMode string

TranscoderMode picks the FFmpeg process topology for a stream.

Per-stream so operators can mix policies on the same server — e.g. a flaky SRT source on legacy mode for per-rendition isolation, while stable studio feeds run multi to halve decode work.

const (
	// TranscoderModeMulti runs ONE FFmpeg per stream that decodes the input
	// once and emits every rendition through its own output pipe. Default
	// when Mode is empty. Saves ~40% RAM and ~50% NVDEC sessions on ABR
	// streams; trade-off is that any FFmpeg-fatal input glitch (corrupt
	// frame, source restart) takes down all renditions together for the
	// 2–3 s it takes to respawn.
	TranscoderModeMulti TranscoderMode = "multi"

	// TranscoderModePerProfile runs ONE FFmpeg per rendition. Higher RAM /
	// decode cost in exchange for per-rendition crash isolation: a single
	// rendition's encoder failing doesn't disrupt the others. Recommended
	// for upstreams known to deliver bursts of malformed packets.
	TranscoderModePerProfile TranscoderMode = "per_profile"
)

TranscoderMode values.

type VODMount

type VODMount struct {
	Name    VODName `json:"name" yaml:"name"`
	Storage string  `json:"storage" yaml:"storage"`
	Comment string  `json:"comment,omitempty" yaml:"comment,omitempty"`
}

VODMount registers a host filesystem directory as a named media library. The system does not maintain a file index; file lookups and listings are resolved live against Storage.

Ingest URLs take the form file://<Name>/<relative/path/inside/storage>. Any path that would escape Storage (via "..", absolute components, or symlink traversal) must be rejected by the resolver.

func (*VODMount) ValidateStorage

func (m *VODMount) ValidateStorage() error

ValidateStorage checks that Storage is a syntactically valid absolute path. The directory itself is not required to exist at validation time — operators may create it later — but the path must be absolute to avoid surprises from the server's working directory.

type VODName

type VODName string

VODName identifies a VOD mount. It appears as the URL host in ingest sources (e.g. file://<name>/path/to/file.mp4), so it must be URL-host-safe.

type VideoCodec

type VideoCodec string

VideoCodec identifies the video compression format.

const (
	VideoCodecH264  VideoCodec = "h264" // AVC — widest device support
	VideoCodecH265  VideoCodec = "h265" // HEVC — ~50% smaller than H.264
	VideoCodecAV1   VideoCodec = "av1"  // royalty-free, best compression (high CPU)
	VideoCodecMPEG2 VideoCodec = "mp2v" // MPEG-2 Part 2 — DVB legacy contribution feeds, transmitter chains
	VideoCodecCopy  VideoCodec = "copy" // passthrough — no re-encode
)

VideoCodec values name supported output video codecs.

type VideoProfile

type VideoProfile struct {
	// Width and Height define the output resolution.
	// Set to 0 to keep the source dimensions (Width=0 & Height=0 = no scaling).
	Width  int `json:"width" yaml:"width"`
	Height int `json:"height" yaml:"height"`

	// Bitrate is the target video bitrate in kbps. 0 = encoder auto.
	Bitrate int `json:"bitrate" yaml:"bitrate"`

	// MaxBitrate caps the peak bitrate in kbps (CBR/VBR ceiling). 0 = no cap.
	MaxBitrate int `json:"max_bitrate" yaml:"max_bitrate"`

	// Framerate is the output frame rate (fps). 0 = match source.
	Framerate float64 `json:"framerate" yaml:"framerate"`

	// KeyframeInterval is the GOP size in seconds.
	// Must match or be a multiple of the HLS/DASH segment duration.
	KeyframeInterval int `json:"keyframe_interval" yaml:"keyframe_interval"`

	Codec VideoCodec `json:"codec" yaml:"codec"`

	// Preset controls the encoder speed/quality tradeoff.
	// libx264: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "veryslow"
	// NVENC:   "p1" (fastest) .. "p7" (highest quality)
	Preset string `json:"preset" yaml:"preset"`

	// Profile controls the H.264/H.265 encoding profile.
	// "baseline" | "main" | "high" (H.264); "main" | "main10" (H.265)
	Profile string `json:"profile" yaml:"profile"`

	// Level controls the H.264/H.265 encoding level.
	// Common: "3.1", "4.0", "4.1", "4.2", "5.0", "5.1"
	Level string `json:"level" yaml:"level"`

	// Bframes is the number of consecutive B-frames the encoder may emit.
	// nil = encoder default; 0 = explicit none (low-latency live);
	// 2-3 = typical VOD; NVENC HW B-ref pyramid is auto-enabled when >0.
	Bframes *int `json:"bframes,omitempty" yaml:"bframes,omitempty"`

	// Refs is the number of reference frames. nil = encoder default.
	// Higher = better compression at cost of CPU/latency. NVENC has its own caps.
	Refs *int `json:"refs,omitempty" yaml:"refs,omitempty"`

	// SAR is the output Sample Aspect Ratio, "N:M". "" = inherit from source.
	// Use "1:1" for square pixels (web); "16:11", "59:54" etc. for anamorphic.
	SAR string `json:"sar,omitempty" yaml:"sar,omitempty"`

	// ResizeMode chooses how the source is fitted to Width/Height.
	// "" defaults to ResizeModePad.
	ResizeMode ResizeMode `json:"resize_mode,omitempty" yaml:"resize_mode,omitempty"`
}

VideoProfile is a single rendition in the ABR (Adaptive Bitrate) ladder. The Transcoder produces one FFmpeg output per profile. Stable rendition ids are derived from slice order: track_1, track_2, track_3, … (1-based).

type VideoTranscodeConfig

type VideoTranscodeConfig struct {
	// Copy copies origin video without re-encoding.
	Copy bool `json:"copy" yaml:"copy"`

	// Interlace selects the deinterlace pre-filter applied before scaling.
	// Applies once per FFmpeg subprocess (i.e. per profile in the ABR ladder).
	// "" disables the filter; use ResizeModeProgressive to assert progressive source.
	Interlace InterlaceMode `json:"interlace,omitempty" yaml:"interlace,omitempty"`

	// Profiles defines ABR renditions when re-encoding.
	Profiles []VideoProfile `json:"profiles,omitempty" yaml:"profiles,omitempty"`
}

VideoTranscodeConfig defines video transcoding behavior.

type WatermarkAsset added in v0.0.44

type WatermarkAsset struct {
	// ID is the immutable filesystem-safe identifier (also the on-disk basename).
	ID WatermarkAssetID `json:"id" yaml:"id"`

	// Name is the human display label shown in UIs. Defaults to the upload's
	// original filename when the operator doesn't override.
	Name string `json:"name" yaml:"name"`

	// FileName is the original filename at upload time, preserved for
	// audit / download responses. Includes extension (e.g. "logo.png").
	FileName string `json:"file_name" yaml:"file_name"`

	// ContentType is the MIME type sniffed at upload time
	// (e.g. "image/png", "image/jpeg"). Used for Content-Type on /raw GET.
	ContentType string `json:"content_type" yaml:"content_type"`

	// SizeBytes is the on-disk size of the image. Sidecar JSON is excluded.
	SizeBytes int64 `json:"size_bytes" yaml:"size_bytes"`

	// UploadedAt is the wall-clock UTC time the asset was first stored.
	UploadedAt time.Time `json:"uploaded_at" yaml:"uploaded_at"`
}

WatermarkAsset is the persisted metadata for one uploaded watermark image. The actual image bytes live next to the metadata sidecar in the assets directory. Both files share the asset ID basename:

<assets_dir>/<id>.<ext>     ← image
<assets_dir>/<id>.json      ← this struct serialised

The simple two-file layout means we don't need a separate database for watermark metadata; an `os.ReadDir` rebuilds the registry after any restart.

type WatermarkAssetID added in v0.0.44

type WatermarkAssetID string

WatermarkAssetID is the stable identifier for an uploaded watermark asset. It is the basename (without extension) of the file on disk and also the identifier referenced from Stream.Watermark.AssetID.

type WatermarkConfig

type WatermarkConfig struct {
	Enabled bool          `json:"enabled" yaml:"enabled"`
	Type    WatermarkType `json:"type" yaml:"type"`

	// Text is the string to render. Supports strftime directives for live timestamps.
	// E.g. "LIVE %{localtime:%H:%M:%S}"
	Text string `json:"text" yaml:"text"`

	// FontFile is the path to a .ttf/.otf font file.
	// "" = FFmpeg default font.
	FontFile string `json:"font_file" yaml:"font_file"`

	// FontSize in pixels. Default: 24.
	FontSize int `json:"font_size" yaml:"font_size"`

	// FontColor in FFmpeg color syntax. E.g. "white", "#FFFFFF", "white@0.8".
	FontColor string `json:"font_color" yaml:"font_color"`

	// AssetID, when set, references a WatermarkAsset uploaded via the
	// /watermarks API. Coordinator resolves it to an on-disk file path
	// before passing the config to the transcoder. Takes precedence over
	// ImagePath when both are set.
	AssetID WatermarkAssetID `json:"asset_id,omitempty" yaml:"asset_id,omitempty"`

	// ImagePath is the absolute path to a watermark image (PNG with alpha
	// recommended). Use this for assets pre-staged on the host outside the
	// /watermarks library. Mutually exclusive with AssetID.
	ImagePath string `json:"image_path,omitempty" yaml:"image_path,omitempty"`

	// Opacity controls transparency: 0.0 = fully transparent, 1.0 = fully opaque.
	Opacity float64 `json:"opacity" yaml:"opacity"`

	// Position selects how the (X, Y) of the overlay are computed:
	//   - top_left/top_right/bottom_left/bottom_right/center: convenience
	//     anchors. OffsetX/Y act as inward padding from the chosen edge
	//     (Center ignores offsets).
	//   - custom: X / Y below are used as raw FFmpeg expressions —
	//     pixel ints ("100"), expressions ("main_w-overlay_w-50"), or
	//     time-aware fades ("if(gt(t,5),10,-100)") all work.
	Position WatermarkPosition `json:"position" yaml:"position"`

	// OffsetX and OffsetY are pixel offsets from the chosen position edge.
	// Ignored when Position == custom.
	OffsetX int `json:"offset_x" yaml:"offset_x"`
	OffsetY int `json:"offset_y" yaml:"offset_y"`

	// X / Y are raw FFmpeg coordinate expressions used only when Position
	// == custom. Empty string defaults to "0". The exact variables exposed
	// depend on the filter:
	//   - drawtext (text watermark): w/h = frame size, tw/th = text size
	//   - overlay (image watermark):  W/H = main video size, w/h = overlay size
	X string `json:"x,omitempty" yaml:"x,omitempty"`
	Y string `json:"y,omitempty" yaml:"y,omitempty"`

	// Resize, when true, makes the watermark render at a consistent on-screen
	// ratio across every rendition in an ABR ladder. The largest profile
	// renders the asset at its NATIVE pixel size (operators design at the top
	// rendition); smaller profiles shrink the asset by the ratio of their
	// width to the largest profile's width. Pixel-scale fields (FontSize for
	// text, OffsetX/OffsetY for both) shrink with the same factor so corner
	// padding and glyph height stay visually proportional.
	//
	// When false (default), the watermark uses native pixel dimensions and
	// fixed offsets on every profile — appearing larger on lower-resolution
	// renditions because they cover fewer pixels of frame.
	Resize bool `json:"resize,omitempty" yaml:"resize,omitempty"`
}

WatermarkConfig defines an overlay applied to the video before encoding. Applied via FFmpeg drawtext (text) or overlay (image) filter.

func (*WatermarkConfig) IsActive added in v0.0.44

func (w *WatermarkConfig) IsActive() bool

IsActive reports whether the watermark should actually be applied. Returns false for nil, disabled, or fully-transparent configs (any of which would emit a no-op filter — easier to just skip).

For image watermarks, the ImagePath check accepts EITHER an explicit path OR an AssetID — coordinator resolves AssetID to ImagePath before transcoder picks up the config, so by the time the filter is built ImagePath is always populated when the watermark is active.

func (*WatermarkConfig) Resolved added in v0.0.44

func (w *WatermarkConfig) Resolved() *WatermarkConfig

Resolved returns a copy with empty / zero fields replaced by defaults. Caller treats the returned value as fully populated. Returns nil when the input is nil so callers can chain safely.

func (*WatermarkConfig) Validate added in v0.0.44

func (w *WatermarkConfig) Validate() error

Validate enforces invariants the FFmpeg filter graph relies on. Called from the API layer at save time so misconfigured streams never reach the transcoder. Disabled watermarks (Enabled=false) skip validation entirely so an operator can park a draft config without tripping errors.

type WatermarkPosition

type WatermarkPosition string

WatermarkPosition controls where the overlay is placed in the frame.

const (
	WatermarkTopLeft     WatermarkPosition = "top_left"
	WatermarkTopRight    WatermarkPosition = "top_right"
	WatermarkBottomLeft  WatermarkPosition = "bottom_left"
	WatermarkBottomRight WatermarkPosition = "bottom_right"
	WatermarkCenter      WatermarkPosition = "center"
	WatermarkCustom      WatermarkPosition = "custom"
)

WatermarkPosition values name corners, center, or `custom` for overlay placement. `custom` activates the X / Y fields below — operators set raw FFmpeg expressions ("100", "main_w-overlay_w-50", "if(gt(t,5),10,-100)") for full positional flexibility while the corners stay convenient defaults.

type WatermarkType

type WatermarkType string

WatermarkType determines whether the overlay is text or an image.

const (
	WatermarkTypeText  WatermarkType = "text"
	WatermarkTypeImage WatermarkType = "image"
)

WatermarkType values select overlay content kind.

Jump to

Keyboard shortcuts

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