Documentation
¶
Overview ¶
Package replay provides an instant replay system with variable-speed playback for live video switching.
Each source gets a GOP-aligned circular buffer that captures encoded H.264 frames with wall-clock timestamps. The Manager orchestrates mark-in/out points, playback, and per-source buffer lifecycle. The replayPlayer decodes clips, sorts by PTS, and re-encodes with frame duplication for slow-motion (0.25x-1x speed).
Audio is time-stretched for pitch-preserved slow-motion playback using a phase vocoder (STFT-based spectral processing) as the primary path, with WSOLA (Waveform Similarity Overlap-Add) as a fallback. Frame interpolation is pluggable via the FrameInterpolator interface, with alpha-blend and MCFI (motion-compensated frame interpolation) implementations available.
Key types:
- Manager: Replay orchestration (mark-in/out, play, stop, buffer management)
- Config: Buffer duration, codec factories, replay relay reference
- Status: Current player state, mark points, active source
- SourceBufferInfo: Per-source buffer fill level and time range
- FrameInterpolator: Pluggable frame interpolation (blend, MCFI)
Replay output is routed to a dedicated "replay" relay so browsers can subscribe via MoQ for replay monitoring.
Index ¶
- Variables
- func PhaseVocoderTimeStretch(input []float32, channels, sampleRate int, speed float64) []float32
- func WSOLATimeStretch(input []float32, channels, sampleRate int, speed float64) []float32
- type AdjustMarksRequest
- type Config
- type FrameInterpolator
- type GPUPlayerConfig
- type InterpolationMode
- type Manager
- func (m *Manager) AddSource(key string) error
- func (m *Manager) Close()
- func (m *Manager) DebugSnapshot() map[string]any
- func (m *Manager) MarkIn(source string) error
- func (m *Manager) MarkOut(source string) error
- func (m *Manager) OnPlaybackLifecycle(onStart, onStop func())
- func (m *Manager) OnStateChange(fn func())
- func (m *Manager) OnVideoInfoChange(fn func(sps, pps []byte, width, height int))
- func (m *Manager) Pause() error
- func (m *Manager) PeekFrame(source string) ([]byte, error)
- func (m *Manager) Play(source string, speed float64, loop bool) error
- func (m *Manager) QuickReplay(source string, seconds int, speed float64) error
- func (m *Manager) RecordFrame(key string, frame *media.VideoFrame)
- func (m *Manager) RemoveSource(key string)
- func (m *Manager) Resume() error
- func (m *Manager) Seek(position float64) error
- func (m *Manager) SetAudioCodecFactories(decFactory audio.DecoderFactory, encFactory audio.EncoderFactory)
- func (m *Manager) SetAudioOutput(fn func(frame *media.AudioFrame))
- func (m *Manager) SetGPUReplay(ctx *gpu.Context, fruc *gpu.FRUC, pool *gpu.FramePool)
- func (m *Manager) SetMarks(markInMs, markOutMs *int64) error
- func (m *Manager) SetOnClipExported(fn func(source string, filePath string))
- func (m *Manager) SetPTSProvider(fn func() int64)
- func (m *Manager) SetProxyEncoderFactory(factory transition.EncoderFactory)
- func (m *Manager) SetRawVideoOutput(fn func(yuv []byte, w, h int, pts int64))
- func (m *Manager) SetShutterAngle(degrees float64)
- func (m *Manager) SetSpeed(speed float64) error
- func (m *Manager) ShutterAngle() float64
- func (m *Manager) Status() Status
- func (m *Manager) Stop() error
- func (m *Manager) Viewer(key string) *replayViewer
- type MarkInRequest
- type MarkOutRequest
- type PlayRequest
- type PlayerConfig
- type PlayerState
- type QuickReplayRequest
- type Relay
- type SeekRequest
- type SourceBufferInfo
- type SpeedRequest
- type Status
Constants ¶
This section is empty.
Variables ¶
var ( ErrNoSource = errors.New("replay: source not found") ErrNoMarkIn = errors.New("replay: mark-in not set") ErrNoMarkOut = errors.New("replay: mark-out not set") ErrInvalidMarks = errors.New("replay: mark-out must be after mark-in") ErrPlayerActive = errors.New("replay: player already active") ErrNoPlayer = errors.New("replay: no active player") ErrEmptyClip = errors.New("replay: clip contains no frames") ErrInvalidSpeed = errors.New("replay: speed must be between 0.25 and 1.0") ErrBufferDisabled = errors.New("replay: buffer is disabled (0 duration)") ErrSourceMismatch = errors.New("replay: mark-out source must match mark-in source") ErrMaxSources = errors.New("replay: maximum sources reached") ErrNotPlaying = errors.New("replay: not playing") ErrNotPaused = errors.New("replay: not paused") ErrInvalidSeek = errors.New("replay: seek position must be between 0.0 and 1.0") )
Sentinel errors for the replay subsystem.
var ErrFrameRecycled = errors.New("filering: frame slot recycled by wrap")
ErrFrameRecycled is returned by Read when the requested frame slot has been overwritten by a circular wrap since it was written. The buffer metadata still references it by wall-clock time, but the bytes on disk no longer hold that frame, so reading them would yield silent corruption.
Functions ¶
func PhaseVocoderTimeStretch ¶
PhaseVocoderTimeStretch performs high-quality pitch-preserved time-stretching using an STFT-based phase vocoder with identity phase locking and transient detection.
- input: interleaved PCM samples
- channels: number of audio channels (1 or 2)
- sampleRate: sample rate in Hz
- speed: playback speed (0.1-1.0)
Returns the time-stretched output samples.
func WSOLATimeStretch ¶
WSOLATimeStretch performs Waveform Similarity Overlap-Add time-stretching. Preserves pitch while changing duration.
- input: interleaved PCM samples
- channels: number of audio channels (1 or 2)
- sampleRate: sample rate in Hz
- speed: playback speed (0.25-1.0)
For speeds below 0.5x, uses cascaded stretching (two passes at sqrt(speed)) to avoid the artifacts that single-pass extreme stretching produces.
Returns the time-stretched output samples.
Types ¶
type AdjustMarksRequest ¶
type AdjustMarksRequest struct {
MarkIn *int64 `json:"markIn,omitempty"` // Unix ms
MarkOut *int64 `json:"markOut,omitempty"` // Unix ms
}
AdjustMarksRequest is the JSON body for adjusting mark points.
type Config ¶
type Config struct {
// BufferDurationSecs is the per-source buffer duration in seconds.
// Default 300, max 300.
BufferDurationSecs int
// MaxSources is the maximum number of sources to buffer simultaneously.
// Default 8.
MaxSources int
// MaxBufferBytes is the per-source byte limit for the replay buffer.
// When exceeded, oldest GOPs are trimmed. Default 200MB. 0 disables.
MaxBufferBytes int64
// TmpfsDir is the directory for tmpfs-backed replay buffers.
// Empty string = auto-detect (/dev/shm/switchframe/replay).
// "none" = disable tmpfs, use in-memory buffers only.
TmpfsDir string
}
Config holds configuration for the replay manager.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns the default replay configuration.
type FrameInterpolator ¶
type FrameInterpolator interface {
Interpolate(frameA, frameB []byte, width, height int, alpha float64) []byte
}
FrameInterpolator generates an interpolated frame between two YUV420 frames.
type GPUPlayerConfig ¶
type GPUPlayerConfig struct {
Clip []bufferedFrame
AudioClip []bufferedAudioFrame
Speed float64
Loop bool
InitialPTS int64
ShutterAngle float64 // 0.0-1.0 normalized (0.5 = 180°)
GPUCtx *gpu.Context
FRUC *gpu.FRUC
Pool *gpu.FramePool
Output func(frame *media.VideoFrame)
AudioOutput func(frame *media.AudioFrame)
RawVideoOutput func(yuv []byte, w, h int, pts int64)
OnDone func()
OnReady func()
OnVideoInfo func(sps, pps []byte, width, height int)
}
GPUPlayerConfig configures a GPU-accelerated replay player instance. The GPU player stays entirely on GPU: NVDEC decode → NVOFA optical flow → motion-blur CUDA kernel → NVENC encode. No CPU-side YUV touching in the hot path (except the optional RawVideoOutput callback which downloads to CPU).
type InterpolationMode ¶
type InterpolationMode string
InterpolationMode selects the frame interpolation algorithm.
const ( InterpolationNone InterpolationMode = "none" // frame duplication (current behavior) InterpolationBlend InterpolationMode = "blend" // alpha blend adjacent frames InterpolationMCFI InterpolationMode = "mcfi" // motion-compensated frame interpolation InterpolationHoldCrossfade InterpolationMode = "hold-crossfade" // hold clean frames, crossfade at transitions )
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager orchestrates the replay system: per-source buffers, viewers, mark-in/out points, and the active player.
func NewManager ¶
func NewManager(relay Relay, cfg Config, decoderFactory transition.DecoderFactory, encoderFactory transition.EncoderFactory) *Manager
NewManager creates a replay manager.
func (*Manager) AddSource ¶
AddSource registers a source for replay buffering. Returns an error if the maximum number of sources has been reached.
func (*Manager) Close ¶
func (m *Manager) Close()
Close stops any active player, waits for background export goroutines, and releases resources. Cleans up any tmpfs-backed ring buffer files.
func (*Manager) DebugSnapshot ¶
DebugSnapshot returns debug information about the replay system.
func (*Manager) OnPlaybackLifecycle ¶
func (m *Manager) OnPlaybackLifecycle(onStart, onStop func())
OnPlaybackLifecycle registers callbacks invoked when playback starts and stops. onStart is called when the player transitions to playing (first frame decoded). onStop is called when the player finishes naturally or is stopped manually.
func (*Manager) OnStateChange ¶
func (m *Manager) OnStateChange(fn func())
OnStateChange registers a callback invoked when replay state changes.
func (*Manager) OnVideoInfoChange ¶
OnVideoInfoChange registers a callback invoked when the replay player produces its first keyframe with SPS/PPS. Used to set VideoInfo on the replay relay so MoQ subscribers can discover tracks.
func (*Manager) PeekFrame ¶
PeekFrame returns a JPEG thumbnail of the most recent frame for a source. Currently returns nil (no thumbnail available) — the frontend falls back to the replay canvas.
func (*Manager) QuickReplay ¶
QuickReplay combines mark + play in one call, auto-stopping any active player first. Sets marks to [now-seconds, now] and starts playback.
func (*Manager) RecordFrame ¶
func (m *Manager) RecordFrame(key string, frame *media.VideoFrame)
RecordFrame records a frame into the source's replay buffer. Called directly from the streamCallbackRouter's viewer.
func (*Manager) RemoveSource ¶
RemoveSource stops buffering for a source.
func (*Manager) SetAudioCodecFactories ¶
func (m *Manager) SetAudioCodecFactories(decFactory audio.DecoderFactory, encFactory audio.EncoderFactory)
SetAudioCodecFactories sets the AAC decoder/encoder factories used for WSOLA audio time-stretching during slow-motion playback.
func (*Manager) SetAudioOutput ¶
func (m *Manager) SetAudioOutput(fn func(frame *media.AudioFrame))
SetAudioOutput registers a callback for sending audio directly to the mixer, bypassing the relay encode/decode hop.
func (*Manager) SetGPUReplay ¶
SetGPUReplay enables GPU-accelerated replay with NVOFA motion blur. When set and NVOFA is available, Play() will use the GPU player instead of the CPU player. Pass nil values to disable GPU replay.
func (*Manager) SetMarks ¶
SetMarks adjusts the mark-in and/or mark-out points from Unix milliseconds.
func (*Manager) SetOnClipExported ¶
SetOnClipExported registers a callback invoked after Play() extracts a clip from the replay buffer. The callback receives the source name and a path to a temporary MPEG-TS file containing the muxed clip frames. The caller is responsible for moving or removing the temp file.
func (*Manager) SetPTSProvider ¶
SetPTSProvider registers a function that returns the current program PTS. The replay player uses this to anchor its output PTS to the program timeline, preventing backward PTS jumps when cut to program.
func (*Manager) SetProxyEncoderFactory ¶
func (m *Manager) SetProxyEncoderFactory(factory transition.EncoderFactory)
SetProxyEncoderFactory sets a browser-compatible encoder factory (typically H.264) for dual-encode mode. When set, the main encoder factory creates the pipeline codec encoder (e.g. HEVC), and this factory creates the H.264 proxy for browser playback.
func (*Manager) SetRawVideoOutput ¶
SetRawVideoOutput registers a callback for sending decoded YUV frames directly to the switcher pipeline (primary output path).
func (*Manager) SetShutterAngle ¶
SetShutterAngle sets the motion-blur shutter angle (0-360 degrees). Only affects GPU-accelerated replay. A shutter angle of 0 disables motion blur (pure interpolation). 180° is the film standard.
func (*Manager) ShutterAngle ¶
ShutterAngle returns the current shutter angle in degrees.
type MarkInRequest ¶
type MarkInRequest struct {
Source string `json:"source"`
}
MarkInRequest is the JSON body for the mark-in endpoint.
type MarkOutRequest ¶
type MarkOutRequest struct {
Source string `json:"source"`
}
MarkOutRequest is the JSON body for the mark-out endpoint.
type PlayRequest ¶
type PlayRequest struct {
Source string `json:"source"`
Speed float64 `json:"speed"`
Loop bool `json:"loop"`
}
PlayRequest is the JSON body for the play endpoint.
type PlayerConfig ¶
type PlayerConfig struct {
Clip []bufferedFrame
AudioClip []bufferedAudioFrame
Speed float64
Loop bool
InitialPTS int64 // Starting PTS for output (anchors to program timeline).
Interpolation InterpolationMode
DecoderFactory transition.DecoderFactory
EncoderFactory transition.EncoderFactory
Output func(frame *media.VideoFrame)
AudioOutput func(frame *media.AudioFrame)
OnDone func()
OnReady func() // Called when first GOP decoded and encoder created.
OnVideoInfo func(sps, pps []byte, width, height int) // Called once on first encoded keyframe.
// RawVideoOutput sends decoded YUV directly to the switcher pipeline.
// Called for every output frame (including slow-mo duplicates/interpolations).
RawVideoOutput func(yuv []byte, w, h int, pts int64)
// ProxyEncoderFactory creates a browser-compatible encoder (typically H.264)
// for the browser relay output. When set, the main EncoderFactory creates the
// pipeline codec encoder (e.g., HEVC), and this factory creates the browser proxy.
// The proxy encoder's output goes to the Output callback (browser relay).
// The main encoder's output goes to PipelineVideoOutput.
// When nil, the main EncoderFactory is used for Output (legacy behavior).
ProxyEncoderFactory transition.EncoderFactory
// PipelineVideoOutput sends pipeline-codec encoded frames for recording/SRT.
// Only used when ProxyEncoderFactory is set (dual-encode mode).
PipelineVideoOutput func(frame *media.VideoFrame)
// AudioDecoderFactory creates an AAC decoder for WSOLA pre-processing.
// Required when Speed < 1.0 for pitch-preserved slow-motion audio.
AudioDecoderFactory audio.DecoderFactory
// AudioEncoderFactory creates an AAC encoder for WSOLA post-processing.
AudioEncoderFactory audio.EncoderFactory
}
PlayerConfig configures a replay player instance.
type PlayerState ¶
type PlayerState string
PlayerState represents the current state of the replay player.
const ( PlayerIdle PlayerState = "idle" PlayerLoading PlayerState = "loading" PlayerPlaying PlayerState = "playing" PlayerPaused PlayerState = "paused" )
type QuickReplayRequest ¶
type QuickReplayRequest struct {
Seconds int `json:"seconds"`
Speed float64 `json:"speed"`
Source string `json:"source"` // Empty = current program source
}
QuickReplayRequest is the JSON body for the quick-replay endpoint.
type Relay ¶
type Relay interface {
BroadcastVideo(frame *media.VideoFrame)
BroadcastAudio(frame *media.AudioFrame)
}
Relay is the interface for the replay output relay.
type SeekRequest ¶
type SeekRequest struct {
Position float64 `json:"position"` // 0.0-1.0
}
SeekRequest is the JSON body for the seek endpoint.
type SourceBufferInfo ¶
type SourceBufferInfo struct {
Source string `json:"source"`
FrameCount int `json:"frameCount"`
GOPCount int `json:"gopCount"`
DurationSecs float64 `json:"durationSecs"`
BytesUsed int64 `json:"bytesUsed"`
}
SourceBufferInfo describes the buffer state for a single source.
type SpeedRequest ¶
type SpeedRequest struct {
Speed float64 `json:"speed"` // 0.25-1.0
}
SpeedRequest is the JSON body for the speed-change endpoint.
type Status ¶
type Status struct {
State PlayerState `json:"state"`
Source string `json:"source,omitempty"`
Speed float64 `json:"speed,omitempty"`
Loop bool `json:"loop,omitempty"`
Position float64 `json:"position,omitempty"` // 0.0–1.0 playback progress
MarkIn *time.Time `json:"markIn,omitempty"`
MarkOut *time.Time `json:"markOut,omitempty"`
MarkSource string `json:"markSource,omitempty"`
ShutterAngle float64 `json:"shutterAngle,omitempty"` // 0-360 degrees
GPUAccelerated bool `json:"gpuAccelerated,omitempty"`
Buffers []SourceBufferInfo `json:"buffers,omitempty"`
}
Status is the JSON-serializable status for the replay system, included in ControlRoomState for the browser.
func (Status) MarkInUnixMs ¶
MarkInUnixMs returns the mark-in time as Unix milliseconds, or nil if not set.
func (Status) MarkOutUnixMs ¶
MarkOutUnixMs returns the mark-out time as Unix milliseconds, or nil if not set.