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 ¶
- type Backend
- func NewBroadcastify(cfg BroadcastifyConfig, hc *http.Client) (Backend, error)
- func NewIcecast(cfg IcecastConfig, log *slog.Logger) (Backend, error)
- func NewOpenMHz(cfg OpenMHzConfig, hc *http.Client) (Backend, error)
- func NewRdioScanner(cfg RdioScannerConfig, hc *http.Client) (Backend, error)
- func NewWebhook(cfg WebhookConfig, hc *http.Client) (Backend, error)
- type BroadcastifyConfig
- type Call
- type IcecastConfig
- type Manager
- type NormalizeConfig
- type OpenMHzConfig
- type Options
- type RdioScannerConfig
- type Stats
- type WebhookConfig
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.
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 ¶
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) Close ¶
Close releases the bus subscription, waits for Run to drain, then stops the upload workers. Safe to call multiple times.
func (*Manager) Run ¶
Run drains KindCallComplete events until ctx is cancelled or the bus subscription closes.
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.