Decode Worker
app.infrastructure.video.decode_workerBackground video decoding worker with ring buffer cache and hard-seek support.
See Also
VideoRingBuffer— stores decoded frames for playbackVideoReader— decodes the source video
Layer: Infrastructure. Context: Implements concrete processing and I/O behind application-facing contracts.
DecoderSignals
Bases: QObject
Container for Qt signals emitted by VideoDecodeWorker (must inherit QObject).
Layer: Infrastructure. Context: Implements concrete processing or I/O behind application-facing contracts.
seek_completed = Signal(int)
class-attribute
instance-attribute
VideoDecodeWorker
Bases: QThread
Background worker that decodes video frames into a ring buffer.
This thread preloads frames ahead of playback, handles seek requests, and manages memory usage by clearing cached frames when inactive.
| ATTRIBUTE | DESCRIPTION |
|---|---|
signals |
Signal container emitting seek completion events.
TYPE:
|
ring_buffer |
Frame cache (90-frame sliding window).
TYPE:
|
_reader |
Video reader used to decode frames.
TYPE:
|
_playback |
Shared playback view_state reference.
TYPE:
|
_is_active |
True when the worker should decode/cache frames.
TYPE:
|
_running |
True while the run loop is active.
TYPE:
|
_seek_request |
Pending seek target frame index.
TYPE:
|
_latest_read_idx |
Most recent frame index pushed to the buffer.
TYPE:
|
_mutex |
Guards mutable cross-thread view_state.
TYPE:
|
Notes
Core loop behavior
- If inactive, sleep briefly.
- If a seek is requested, perform a hard seek and emit completion.
- If the buffer is sufficiently ahead, throttle decode rate.
- Otherwise, decode the next frame and push to the ring buffer.
Memory behavior
- Buffer capacity is 90 frames.
- Buffer is cleared on seek and when deactivated.
- Deactivation is used to aggressively release cached frame memory.
Thread lifecycle (caller responsibility)
Call in this order: stop() -> quit() -> wait() -> deleteLater().
Example
UI seek flow:
slider moved -> set_active(False), request_seek(idx)
run loop handles seek and caches frame
`signals.seek_completed(idx)` emitted
UI resumes decode with `set_active(True)`
| PARAMETER | DESCRIPTION |
|---|---|
path
|
Filesystem path of the video or resource.
TYPE:
|
playback
|
Playback state shared with the decode worker.
TYPE:
|
parent
|
Optional Qt parent object.
TYPE:
|
ring_buffer = VideoRingBuffer(capacity=90)
instance-attribute
signals = DecoderSignals()
instance-attribute
__init__(path: str, playback: PlaybackState, parent: QObject | None = None) -> None
get_cached_frame_at_index(idx: int) -> RGBFrame | None
Return cached frame at idx, or None if not in buffer.
| PARAMETER | DESCRIPTION |
|---|---|
idx
|
Zero-based frame index to look up.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
RGBFrame | None
|
Cached RGB frame, or |
request_seek(frame_index: int) -> None
Request hard seek to frame_index (thread-safe, clears buffer).
| PARAMETER | DESCRIPTION |
|---|---|
frame_index
|
Zero-based frame index.
TYPE:
|
run() -> None
Main QThread loop — decode frames ahead of playback, handle seeks, manage buffer.
Workflow:
- Check if active; if not, sleep 10ms
- Handle pending seek request (hard seek via
VideoReader, emitseek_completed) - Throttle if buffer ahead by 45 frames (sleep 10ms)
- Read next frame sequentially, push to ring buffer
- Repeat until
_runningbecomes False
set_active(active: bool, resume_idx: int = 0) -> None
Toggle worker on/off (thread-safe via _mutex).
When False: Clears ring buffer (frees large chunk of RAM immediately).
When True: Schedules seek to resume_idx to refill buffer from playhead.
stop() -> None
Safely shutdown QThread with proper cleanup sequence.
Sequence:
- Set
_running=False(exit main loop) - Call
quit()(signal quit) - Call
wait(500)(up to 500ms for loop exit) - If stuck,
terminate()thenwait()(fallback for deadlock) - Close
VideoReader
IMPORTANT
Always call this before thread deletion to avoid zombie threads.