broadcast

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package broadcast streams completed trunked-radio calls to external call aggregators — Broadcastify Calls, RdioScanner, OpenMHz — and to live Icecast/ShoutCast audio servers.

The Manager subscribes to events.KindCallComplete (published by the recorder once a call's WAV is flushed to disk), resolves which backends want the call, encodes the audio to MP3 once, and dispatches it to each backend with bounded exponential-backoff retry. A call is skipped entirely when its talkgroup is flagged Stream=false or when it is shorter than the configured minimum duration.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Backend

type Backend interface {
	// Name is a short stable identifier used in logs and stats.
	Name() string
	// Accepts reports whether this backend wants calls from the named
	// trunking system. An unfiltered backend accepts every system.
	Accepts(systemName string) bool
	// Send delivers the call. A non-nil error triggers a retry.
	Send(ctx context.Context, c *Call) error
}

Backend is one outbound streaming destination. Implementations must be safe for concurrent Send calls — the Manager runs several upload workers. Send should treat a retry as a fresh attempt; the Manager handles backoff between attempts.

func NewBroadcastify

func NewBroadcastify(cfg BroadcastifyConfig, hc *http.Client) (Backend, error)

NewBroadcastify builds a Broadcastify Calls backend.

func NewIcecast

func NewIcecast(cfg IcecastConfig, log *slog.Logger) (Backend, error)

NewIcecast builds a live Icecast/ShoutCast streaming backend and starts its background source connection.

func NewOpenMHz

func NewOpenMHz(cfg OpenMHzConfig, hc *http.Client) (Backend, error)

NewOpenMHz builds an OpenMHz upload backend.

func NewRdioScanner

func NewRdioScanner(cfg RdioScannerConfig, hc *http.Client) (Backend, error)

NewRdioScanner builds an RdioScanner call-upload backend.

func NewWebhook added in v0.5.2

func NewWebhook(cfg WebhookConfig, hc *http.Client) (Backend, error)

NewWebhook builds a generic JSON-webhook backend that POSTs one JSON object per completed call (issue #404 / #268). The payload is a stable, documented schema (see webhookPayload) carrying the call metadata and, when IncludeAudio is set, the base64 MP3.

type BroadcastifyConfig

type BroadcastifyConfig struct {
	// Name is an optional label used in logs; defaults to
	// "broadcastify".
	Name string
	// APIKey is the Broadcastify Calls API key for the system.
	APIKey string
	// SystemID is the numeric Broadcastify Calls system ID.
	SystemID int
	// Systems restricts the feed to these trunking-system names.
	// Empty streams every system.
	Systems []string
	// Endpoint overrides the upload API URL (tests). Empty uses the
	// production endpoint.
	Endpoint string
}

BroadcastifyConfig configures one Broadcastify Calls feed.

type Call

type Call struct {
	System         string
	Protocol       string
	Talkgroup      uint32
	TalkgroupLabel string
	Source         uint32
	FrequencyHz    uint32
	// ChannelID / RFSS / Site / NAC / Timeslot carry the P25 site
	// identity decoded off the control channel (issue #698). They let a
	// metadata sink (the webhook) label a call by site for downstream
	// dashboards instead of resolving an opaque channel ID by hand. All
	// stay zero on non-P25 calls and until the site's status TSBKs land;
	// audio-upload backends ignore them.
	ChannelID uint8
	RFSS      uint8
	Site      uint8
	NAC       uint16
	Timeslot  uint8
	Encrypted bool
	// AlgorithmID / KeyID surface the P25 encryption parameters when
	// the in-call signalling has revealed them. Zero on clear calls
	// and on encrypted calls whose Encryption Sync never arrived
	// before the call ended. Aggregators that accept the fields can
	// thread them into their API payload; aggregators that don't will
	// just ignore them.
	AlgorithmID   uint8
	KeyID         uint16
	Emergency     bool
	PatchedGroups []uint32
	StartedAt     time.Time
	EndedAt       time.Time
	AudioPath     string // .wav on disk written by the recorder
	SampleRate    int
	// contains filtered or unexported fields
}

Call is a completed call queued for outbound streaming. It carries the call metadata plus a lazy MP3 accessor — the WAV on AudioPath is encoded to MP3 at most once regardless of how many backends consume it.

func (*Call) Duration

func (c *Call) Duration() time.Duration

Duration reports how long the call ran.

func (*Call) MP3

func (c *Call) MP3() ([]byte, error)

MP3 returns the call audio encoded as an MP3 byte stream. The encode runs once on first use; later calls return the cached result (or the cached error). Safe for concurrent use across backend workers.

type IcecastConfig

type IcecastConfig struct {
	// Name is an optional log label; defaults to "icecast".
	Name string
	// Host and Port address the Icecast server.
	Host string
	Port int
	// Mount is the mountpoint path (e.g. "/gophertrunk"). A leading
	// slash is added when missing.
	Mount string
	// Username is the source username; defaults to "source".
	Username string
	// Password is the source password.
	Password string
	// StreamName is advertised to listeners via the Ice-Name header.
	StreamName string
	// SampleRate is the PCM rate used to synthesise inter-call
	// silence; defaults to 8000.
	SampleRate int
	// Systems restricts the feed to these trunking-system names.
	// Empty streams every system.
	Systems []string
}

IcecastConfig configures one live Icecast/ShoutCast feed.

type Manager

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

Manager fans completed calls out to the configured backends.

func NewManager

func NewManager(opts Options) (*Manager, error)

NewManager validates opts and returns a Manager that has already subscribed to the bus (so calls completed before Run starts are not lost). The caller must invoke Run and, on shutdown, Close.

func (*Manager) Backends

func (m *Manager) Backends() int

Backends reports how many outbound destinations are configured.

func (*Manager) Close

func (m *Manager) Close() error

Close releases the bus subscription, waits for Run to drain, then stops the upload workers. Safe to call multiple times.

func (*Manager) Run

func (m *Manager) Run(ctx context.Context) error

Run drains KindCallComplete events until ctx is cancelled or the bus subscription closes.

func (*Manager) Stats

func (m *Manager) Stats() Stats

Stats returns a snapshot of the Manager's counters.

func (*Manager) String

func (m *Manager) String() string

String renders a one-line Manager summary for log output.

type NormalizeConfig added in v0.4.0

type NormalizeConfig struct {
	Enabled bool
	Params  loudness.NormalizeParams
}

NormalizeConfig configures optional loudness normalization of the distributed MP3 copy. When Enabled, the WAV on disk is left untouched and the gain is applied in memory before MP3 encoding. Params are assumed to already carry defaults (see loudness.NormalizeParams).

type OpenMHzConfig

type OpenMHzConfig struct {
	// Name is an optional log label; defaults to "openmhz".
	Name string
	// APIKey is the OpenMHz system API key.
	APIKey string
	// ShortName is the OpenMHz system short name (the path segment in
	// the upload URL).
	ShortName string
	// Systems restricts the feed to these trunking-system names.
	// Empty streams every system.
	Systems []string
	// Endpoint overrides the API base URL (tests). Empty uses the
	// production endpoint.
	Endpoint string
}

OpenMHzConfig configures one OpenMHz upload feed.

type Options

type Options struct {
	Bus *events.Bus
	Log *slog.Logger
	// Backends is the set of outbound destinations. A Manager with no
	// backends is valid but inert.
	Backends []Backend
	// MinDuration drops calls shorter than this from every backend.
	// Zero streams calls of any length.
	MinDuration time.Duration
	// Workers is the number of concurrent upload goroutines. Default 2.
	Workers int
	// MaxRetries is the per-backend retry budget on a failed Send.
	// Default 3.
	MaxRetries int
	// RetryBase is the first backoff delay; it doubles per attempt.
	// Default 2s.
	RetryBase time.Duration
	// Normalize, when Enabled, loudness-normalizes the distributed MP3
	// copy in memory (the on-disk WAV is left pristine). Off by default.
	Normalize NormalizeConfig
}

Options configure a Manager.

type RdioScannerConfig

type RdioScannerConfig struct {
	// Name is an optional log label; defaults to "rdioscanner".
	Name string
	// URL is the RdioScanner base URL (e.g. "https://scanner.example").
	// The "/api/call-upload" path is appended automatically.
	URL string
	// APIKey is the RdioScanner system API key.
	APIKey string
	// SystemID is the numeric RdioScanner system ID.
	SystemID int
	// Systems restricts the feed to these trunking-system names.
	// Empty streams every system.
	Systems []string
	// Loc is the timezone the dateTime RFC3339 value renders in. Nil means
	// time.Local. RdioScanner parses dateTime as an instant, so the offset-
	// bearing RFC3339 (e.g. +02:00, or Z for UTC) is unambiguous.
	Loc *time.Location
}

RdioScannerConfig configures one RdioScanner call-upload feed.

type Stats

type Stats struct {
	Queued   int            `json:"queued"`
	Dropped  int            `json:"dropped"`
	Sent     map[string]int `json:"sent"`
	Failed   map[string]int `json:"failed"`
	Backends []string       `json:"backends"`
}

Stats is a point-in-time snapshot of a Manager's counters.

type WebhookConfig added in v0.5.2

type WebhookConfig struct {
	// Name is an optional log label; defaults to "webhook".
	Name string
	// URL is the endpoint the call payload is POSTed to (required).
	URL string
	// AuthHeader, when set, is sent verbatim as the Authorization header
	// (e.g. "Bearer <token>"). Empty omits the header.
	AuthHeader string
	// IncludeAudio, when true, embeds the MP3-encoded call audio as a
	// base64 string in the payload (audio_base64 / audio_format). Off by
	// default so the webhook stays a lightweight metadata feed; consumers
	// that want audio can opt in.
	IncludeAudio bool
	// Systems restricts the feed to these trunking-system names. Empty
	// streams every system.
	Systems []string
	// Loc is the timezone the StartedAt/EndedAt RFC3339 timestamps render in.
	// Nil means time.Local. The daemon passes display.timezone so the payload
	// matches the operator's other timestamps; the format stays RFC3339, so the
	// offset (e.g. +02:00, or Z for UTC) keeps the instant unambiguous.
	Loc *time.Location
}

WebhookConfig configures one generic JSON-webhook call sink.

Jump to

Keyboard shortcuts

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