Skip to content

Refactor WebM playback for asynchronous, creator-controlled movies - #361

Merged
DCurrent merged 26 commits into
masterfrom
webn-playback-refactor
Aug 18, 2026
Merged

Refactor WebM playback for asynchronous, creator-controlled movies#361
DCurrent merged 26 commits into
masterfrom
webn-playback-refactor

Conversation

@DCurrent

Copy link
Copy Markdown
Owner

Summary

Replace the unfinished WebM playback path with a reusable, asynchronous movie system designed for stable audio synchronization and creator-controlled screen composition.

The new API separates source loading, playback state, movie channels, audio routing, and drawing. Active movies advance independently, while scripts explicitly copy retained frames to the desired 32-bit screen with movie_draw_to_screen().

Movie API

  • Add reusable streamed, cached, and automatically selected movie sources.
  • Add a fixed bank of 64 movie channels with automatic or explicit allocation.
  • Expose stable playback objects and synchronized property access.
  • Add explicit creator-controlled drawing to main screens or subscreens.
  • Support destination-relative, native, or explicit dimensions.
  • Support signed placement offsets, clipping, repeat, pause, seeking, forward speed control, interruption control, and exact-black protection.
  • Allow one decoded playback to be drawn into multiple destinations.
  • Route embedded audio through selectable OpenBOR sound channels.
  • Preserve blocking playwebm() and scene video behavior through compatibility wrappers.

Playback stability

  • Correct cross-thread SDL audio locking.
  • Give each audible movie an owned playback clock and use it as the video master.
  • Replace unrestricted compressed-video backlog handling with audio-starvation-aware keyframe recovery.
  • Move decoder open, seek, preroll, replacement, and teardown work off the engine thread.
  • Disable expensive reverse decoding; backward navigation now uses explicit seeking.
  • Bound packet, decoded-frame, and per-update presentation work.
  • Preserve decoded read-ahead frames through ordinary scheduling jitter.
  • Prevent unpresented retained frames from being replaced prematurely.
  • Rate-limit diagnostics while retaining clear reporting for underruns, late-frame loss, preroll timeouts, and backlog recovery.
  • Optimize scaled YUV-to-RGB composition without adding a second rendering path.

Audio integration

  • Add playback-owned producer stream state and clock snapshots.
  • Keep audio callbacks independent of decoder-thread SDL locking.
  • Preserve unrelated sound channels during modern movie playback.
  • Retain historical replace-all audio behavior for blocking legacy playback.
  • Keep existing music active when a legacy WebM contains no audio track.
  • Report unsupported or failed audio tracks clearly in the log.

Additional corrections

  • Correct the onblocky script command name.
  • Correct reversed plane and index arguments in onblockwscript.

Compatibility and limitations

  • Modern movie playback requires explicit movie_draw_to_screen() calls. Playback no longer owns or retains a destination screen.
  • Reverse playback is intentionally unsupported. Scripts may implement rewind controls through position seeks.
  • WebM support currently targets VP8 video with optional Vorbis audio.
  • Video decoding and RGB screen composition remain software-based. Hardware acceleration is reserved for a future engine-wide rendering update.

Validation

Manual runtime testing was performed on Windows 11 using a Dell OptiPlex Micro 7010 with an Intel Core i5-13500T and 16 GB RAM.

Various movies played back in differing resolutions, including a 13-minute, 1920×1080, 60 FPS VP8 WebM with 48 kHz stereo Vorbis audio drawn into a 360×204 subscreen. Playback completed without a crash, lockup, PCM underrun, or sustained audio desynchronization. One guarded keyframe recovery discarded 255 compressed video packets during the complete run.

Unsupported audio encoding also produced an explicit diagnostic instead of an ambiguous playback failure.

Open reverse movie decoder contexts without an active audio track. This allows interleaved audio packets to be discarded during demuxing instead of filling paused PCM buffers and blocking video delivery.

Restore embedded audio when playback returns to a positive speed while preserving normal forward and legacy playback behavior.
Disable audio decoding during stationary or reverse playback to prevent
audio queues from blocking video demuxing.

Align decoded Vorbis PCM with the requested seek position. Account for
codec delay, discard samples preceding the target, and insert silence
when the first available audio packet begins after it.

Keep the sound stream paused until the movie playback clock is anchored.
Disable audio decoding during stationary and reverse playback to prevent unconsumed audio queues from blocking video demuxing.

Synchronize audio after seeking by accounting for codec delay, discarding PCM before the requested position, and inserting silence when the first available packet begins after it. Hold the sound channel paused until the
movie clock is anchored.

Preserve decimal playback rates through the audio period calculation instead of rounding them to integer percentages.
Add movie_draw_to_screen() so creators explicitly draw a retained movie frame to a selected screen and control its position in the composition order.

Remove automatic main-screen and subscreen rendering, persistent screen ownership, movie_set_screen(), and MOVIE_PROPERTY_SCREEN.

Update legacy playwebm() to clear and present only when a new movie frame arrives instead of presenting on every one-millisecond decoder poll.

Preserve final-frame presentation and invalidate scaled-frame caches correctly when drawing one movie to multiple target sizes.
Add MOVIE_LOADING_AUTO as an opt-in loading mode while retaining streaming as the default behavior.

Allow automatic caching when the movie fits within one-quarter of system memory and leaves a reserve of one-eighth system memory or 128 MiB, whichever is greater.

Track all resident movie caches against the cumulative automatic budget without imposing a fixed upper limit.

Add nonfatal movie cache allocation and bounded packfile reads. Automatic mode falls back to streaming under memory pressure, while forced cache mode returns a clean load failure.
Restore compatibility with valid WebM files that omit the optional DefaultDuration element. Playback now relies on packet timestamps and uses packet duration to space laced frames when a default duration is unavailable.

Correct the Nestegg streaming callbacks to return the expected seek result, use 64-bit packfile positions, verify read boundaries, and complete partial reads safely.

Add explicit diagnostics for container initialization, track discovery, allocation, and worker-thread failures instead of allowing movie playback to fail silently.

These fixes apply to both the movie playback API and legacy playwebm().
Convert creator-supplied playback speed to one canonical float.

Clamp supported range and round to thousandths so video timing, audio resampling, property reads, and later decoder reopens all consume the same applied rate.
- increase streaming audio reserves and improve decoder scheduling
- retain underrun telemetry with playback-aware producer backoff
- convert YUV directly to requested composition dimensions
- allocate native-resolution RGB buffers only when required
- add MOVIE_SIZE_NATIVE width and height sentinel
- preserve destination-sized rendering when dimensions are zero
- correct reported WebM audio sample depth
Configure VP8 decoding from source resolution and available processors while reserving capacity for engine and audio work on larger systems.

Prevent prolonged active-playback stalls from back-pressuring the shared demuxer by replacing stale decoded frames only after the compressed video queue reaches its safety threshold. Preserve ordered blocking during ordinary read-ahead, startup, and paused playback.

Report rate-limited underrun snapshots for PCM, compressed audio and video, decoded frames, and stale-frame replacements. Keep diagnostics off the normal playback path and preserve safe queue lifetime through audio-thread shutdown.

Retain direct target-size YUV conversion, eight-buffer PCM reserves, decoder preroll, and playback-aware producer backoff.
Snapshot decoded-frame queue occupancy before polling and process only that population during the current engine update.

Defer frames published concurrently until the next update. This prevents a decoder producing stale frames from continuously refilling the queue
and turning nonblocking catch-up into an unbounded main-thread stall.

Preserve retained-frame promotion, future-frame timing, reverse playback, and terminal-marker handling.
Preroll decoded video alongside PCM before publishing READY. Preserve one due frame per presentation opportunity, bound audio-master lateness, and report presentation or decoded-queue drops independently from compressed backlog recovery.
Treat a full decoded-frame queue as normal bounded read-ahead instead of replacing ordered frames whenever the compressed queue is populated. Keep exceptional audio-aware compressed recovery, remove obsolete decoded-drop diagnostics, and separate the initial video log block.
Rewrite playback comments as present-tense ownership, timing, and queue contracts. Remove tuning-history and compatibility-development wording, and place a blank line before WebM warnings.
@DCurrent DCurrent self-assigned this Aug 18, 2026
@DCurrent
DCurrent merged commit 218910c into master Aug 18, 2026
8 checks passed
@DCurrent
DCurrent deleted the webn-playback-refactor branch August 18, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant