000
Decoding exactly one frame
at a time —
nothing more.
macOS · Windows · Linux · Android — MIT

LiveWall

A live wallpaper built around one constraint:
it should cost almost nothing when you aren't looking at it.

FIELD PROCEDURAL
DECODER ACTIVE
FRAMES IN FLIGHT 1
T 00:00.000
Scroll
504 KBmacOS binary
✦
12 MBidle memory
✦
0.0%CPU when covered
✦
2.9%of one core, 4K 10-bit HEVC
✦
102 KBAndroid APK
✦
459 KBLinux daemon
✦
0.991SSIM across the loop seam
✦
0dependencies
✦
00 / WHY

Most video wallpaper apps wrap a media player around whatever file you drop in, and leave it decoding whether or not the desktop is visible. That is why they show up in Activity Monitor and Task Manager, and why laptop fans spin. LiveWall takes the opposite approach — every design decision trades features for resources.

Typical player · desktop covered

still decoding ~30 frames aheadbusy

LiveWall · desktop covered

decoder destroyed, last frame composited0.0%
01 / TRY IT

Drag a window over the wallpaper.

This is the coverage gate, running live. The desktop is sampled on the same 64×40 grid the apps use. Leave less than 8% uncovered and the decoder is torn down after 0.4 s. It only comes back above 15%. The gap stops a window edge sitting on the line from flipping the decoder on and off.

Terminal — zsh
$ swift test
✔ 31 tests passed
$ ./tools/measure.sh 20
mem 12 MB · cpu 0.0%
Safari — Docs
DECODING · 24 fps
Decoder
Playing
Desktop uncovered
100%stop < 8% · resume > 15%
CPU · 1 core2.9%
Memory19 MB
Gate log
02 / The design

Five
decisions.

All four apps are one program written in four languages. These five decisions are what keep them one project.

01 — TEARDOWN

Visibility tears down. It does not pause.

When nothing can see the wallpaper, the decoder and its session are destroyed. The window server keeps showing the last frame for free, and playback resumes from the saved timestamp.

02 — COVERAGE

Partial coverage counts.

The OS reports a desktop with one visible corner as "visible". LiveWall measures the uncovered fraction on a 64×40 grid instead. It stops below 8% and resumes above 15%.

03 — ONE FRAME

One frame in flight.

Each tick pulls one decoded frame and hands it straight to the compositor. There's no read-ahead queue and no media clock. At 4K 10-bit, one frame is ~20 MB, so a read-ahead queue gets expensive fast.

04 — NATIVE FORMAT

Never ask for a format the decoder isn't producing.

Naming a pixel format costs a kernel round trip per frame. Not naming one cut copyNextSampleBuffer from 65 profiler samples to 4. LiveWall takes the decoder's native 4:2:0 output and converts it in the compositor, where it's free.

05 — IMPORT

Import is mandatory.

Every video is converted on import, and only the converted file is ever played. The playback path never sees an unknown codec, an oversized frame, 60 fps, an audio track or a B-frame. Your original file is never touched.

03 / MEASURED

Pixels are free. Bits are free. Frames are expensive.

Measured on an M3 Pro against the real playback path. Same clip each time, one variable changed per row. Hardware decode costs the same at any frame size. What's left is the app's own per-frame work, and that grows with frames per second and nothing else.

CPU, % of one corePixels relative to 720p
0more pixels → +0.4% CPU
0the frame rate (24 → 60 fps) → 7.78% CPU
0AVPlayer-style read-ahead vs 19 MB
04 / IN FLIGHT

The queue nobody needed.

Both pipelines decode the same clip. The top one buffers ahead like a media player does. The bottom one is LiveWall: one tick, one sample, shown immediately. Its buffer pool stays at the minimum.

Media playerread-ahead ≈ 30 frames · 0 MB resident
LiveWall1 frame in flight · 0 MB resident
05 / STOP SIGNALS

Hard stops, not slowdowns.

The assets have no B-frames, so every frame must be decoded whether it's shown or not. Lowering the tick rate would just play the clip in slow motion. So any one of these conditions stops playback, and the last frame stays on screen. Tap a card to trigger it.

all cleardecoder →running
06 / IMPORT

Unknown files are fixed once, at import.

Each problem below would cost something on every frame if the file were played as-is. Converting at import fixes it once. The conversion uses the system encoder, with no bundled ffmpeg. Bundling it would add 40–70 MB, plus a licensing question, to a 504 KB app.

Source problem · cost if played directly
Fixed at import
VP9 / AV1 / ProRes software decode, tens of % CPU
✓Transcoded to HEVC → media engine
Larger than the panel decodes pixels you can't see
✓Scaled to cover the display, never past 1:1
60 fps 2.5× the pump work
✓Capped, snapped to a divisor of the refresh rate
Audio track spins up an audio graph
✓Stripped
B-frames decoder needs a reorder buffer
✓Frame reordering disabled
moov atom at EOF full-file read before playback
✓Optimised for streaming
Preset
Ultra Light
size
960p
rate
20 fps
depth
8-bit
Preset
Balanced
size
1920p
rate
24 fps
depth
8-bit
Default for new imports
Native
size
your display
rate
24 fps
depth
10-bit
07 / PLATFORMS

One design, four native ports.

Each port uses its platform's own APIs: no cross-platform runtime, no web view, and nothing that runs in the background without a reason. Each README covers how the port stays cheap, what was measured, and what it gives up.

Menu bar

macOS

Swift · AppKit · AVFoundation · Metal
  • Sits at kCGDesktopWindowLevel
  • Gradient mode in CAGradientLayer, costing the app nothing
  • Thermal + Low Power gates
504 KBbinary · 12 MB idle
LiveWall-macos.zip ↓
Notification area

Windows

C++20 · Win32 · Direct3D 11 · Media Foundation
  • WorkerW parenting, with a fallback
  • HEVC Main10 → Main → H.264 ladder
  • No admin rights; writes only to HKCU
45tests · no GPU required
LiveWall.exe · 1.0.4 ↓
Daemon

Linux

C++ · X11 + Wayland · EGL · dlopen'd FFmpeg/VA-API
  • X11 backend measures coverage, even under Wayland
  • wlr-layer-shell for sway, Hyprland, KWin
  • Optional libraries loaded only at runtime
459 KBbinary · ldd checked in CI
livewall ↓
Live wallpaper

Android

Kotlin · WallpaperService · MediaCodec · GLES
  • onVisibilityChanged is the gate
  • Service is 280 lines, comments included
  • No AndroidX, no Compose, no media3
102 KBrelease APK · 0.00% hidden
LiveWall-android.apk ↓
macOS

Windows

08 / SAMPLES

Two loops, made by the app.

Both clips come from LiveWall's own procedural field, not from stock footage. They're original work under the repo's MIT licence, and you can regenerate them with tools/make-samples.sh.

aurora1920×1080 · 24 fps · 10 s
ember1920×1080 · 24 fps · 10 s
0

SSIM measured across the loop seam. 1.0 would mean the last frame and the first frame are identical. You won't see the cut.

09 / GET IT

Install it, then forget it's running.

Grab a build from Releases, or build from source. Then pick Add Video… from the menu.