Skip to main content
Portrait representing Abhishek Thakur
therealshek Abhishek Thakur (therealshek)
Abhishek Thakur · Software Engineer
Open to work · India / Remote
Desktop application

open-mpv

I built a Rust / GTK4 media viewer with bounded decode scheduling, generation-checked rendering, byte-budgeted caching, and explicit playback and file-write lifecycles.

Focus
Async scheduling · memory bounds · file safety
Built with
Rust · GTK4 · glycin · GStreamer
Status
Active · installable on Fedora
Ownership
Personal project

Engineering focus

The engineering problem is keeping GTK's main loop responsive while storage, image decoders, and video pipelines finish work at different times. A user can change selection, switch folders, or edit a source file before earlier operations complete. I built the scheduler, cache, and presentation rules around that changing state.

  • The first-frame scheduler caps active decoding at two jobs, including jobs that are still cancelling.
  • The image cache budgets additional decoded media by bytes and entry count; the displayed image has a separate exemption.
  • Generation checks and per-path query versions prevent old work from replacing current media or restoring invalid cache entries.
open-mpv displaying a photo in a GNOME window, with navigation and image controls along the bottom
The shipped GTK4 application. Implementation details below cover scheduling, cache consistency, playback state, and safe writes.

Architecture

  1. GTK presentation and background I/O

    GTK owns input and presentation. Folder enumeration and subtitle discovery each use one active worker and one replaceable pending request. A storage call already in progress keeps its worker slot until it returns, even after cancellation.

  2. Image scheduler and decoded cache

    glycin produces decoded images. The scheduler tracks the foreground request and up to two neighbors, shares in-flight work for the same path, and starts foreground work before speculation. A small LRU cache retains decoded neighbors within its configured budgets.

  3. Separate video playback state

    GStreamer handles streaming rather than populating the image cache. A playback model tracks the current session, seeks, rates, audio tracks, and subtitles. Rendering and transport state follow the video lifecycle instead of borrowing image-cache rules.

Engineering challenges

Cancellation must not create more work

Cancelling a decoder is a request, not proof that it has stopped. Releasing its slot immediately would let rapid navigation accumulate active decoders. I keep the slot until completion, allow at most one uncancelled speculative job, and promote a requested neighbor using the latest foreground token.

Read the implementation: loader/scheduler.rs

Stale results must not re-enter the cache

A slow decode or filesystem metadata query can finish after selection changes, rename, or deletion. Results carry generation or per-path version state. File events invalidate affected cached and pending work; superseded results cannot restore it. Current-image changes coalesce through a 100 ms quiet-period refresh.

Read the implementation: window/monitor.rs

Bound extra memory without excluding large images

The current image must remain viewable even if it exceeds the neighbor-cache budget. Additional decoded textures count against checked byte and entry limits; arithmetic overflow counts as over-budget. A zero neighbor budget disables retention, and folder replacement prunes old entries.

Read the implementation: loader.rs

Preserve files across an interrupted save

Image edits are staged in a temporary file in the source directory. The save path restores ownership, permissions, and user extended attributes, synchronizes the staged file, replaces the destination, and attempts directory synchronization. Trash Undo uses no-replace rename semantics, so it refuses to overwrite a recreated file.

Read the implementation: fileops.rs

Playback commands and pipeline observations can disagree

A seek or rate request can be pending while the pipeline still reports earlier state. I keep requested and accepted state separate in a session model, and tie delayed error handling to that session's generation. The controls can then track pending commands without treating them as completed pipeline changes.

Read the implementation: player/playback.rs

Tradeoffs and current limits

The two-job bound limits concurrent work, not the memory required to decode one large file. The displayed image is also exempt from the neighbor-cache budget. Those are deliberate boundaries, so I do not describe the app as having a fixed total memory ceiling.

Fedora 44 Workstation on GNOME, Wayland, and x86-64 is the supported environment. Arch / Omarchy packaging remains experimental. Video support depends on installed codecs and drivers. Cold-start and navigation timings in the requirements are targets, not published benchmark results.

Code and documentation