Skip to content

Decode Worker

app.infrastructure.video.decode_worker

View source code

Background video decoding worker with ring buffer cache and hard-seek support.

See Also

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: DecoderSignals

ring_buffer

Frame cache (90-frame sliding window).

TYPE: VideoRingBuffer

_reader

Video reader used to decode frames.

TYPE: VideoReader

_playback

Shared playback view_state reference.

TYPE: PlaybackState

_is_active

True when the worker should decode/cache frames.

TYPE: bool

_running

True while the run loop is active.

TYPE: bool

_seek_request

Pending seek target frame index.

TYPE: int | None

_latest_read_idx

Most recent frame index pushed to the buffer.

TYPE: int

_mutex

Guards mutable cross-thread view_state.

TYPE: QMutex

Notes
Core loop behavior
  1. If inactive, sleep briefly.
  2. If a seek is requested, perform a hard seek and emit completion.
  3. If the buffer is sufficiently ahead, throttle decode rate.
  4. 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: str

playback

Playback state shared with the decode worker.

TYPE: PlaybackState

parent

Optional Qt parent object.

TYPE: QObject | None DEFAULT: None

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: int

RETURNS DESCRIPTION
RGBFrame | None

Cached RGB frame, or None when the index is outside the buffer's range.

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: int

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, emit seek_completed)
  • Throttle if buffer ahead by 45 frames (sleep 10ms)
  • Read next frame sequentially, push to ring buffer
  • Repeat until _running becomes 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:

  1. Set _running=False (exit main loop)
  2. Call quit() (signal quit)
  3. Call wait(500) (up to 500ms for loop exit)
  4. If stuck, terminate() then wait() (fallback for deadlock)
  5. Close VideoReader

IMPORTANT

Always call this before thread deletion to avoid zombie threads.