Docs / OpenCanvas / Overview

OpenCanvas

The official music video of any song, playing full-screen behind your player and locked to the song. It jumps when the listener seeks, starts with the next track and never loops. It runs inside your app on Android and desktop JVM, with no server, no helper process and no API key.

What it does

A streaming app knows the song it is playing. OpenCanvas finds that song's official music video, checks that it really is the same recording, works out how the song's timeline maps onto the video's, and then keeps the picture on the right second for as long as the song plays.

How it works

Each step below feeds the next. The first picture can start before the map is finished, which is why a song never waits for the measurement.

Song startsTitle, artist and audio id.
Search and rankChannel, "official video", length, verified artist.
Video stream360p now, 720p or better a moment later.
VerifyRead two MP4 headers, about 0.3 MB each.
EnvelopesAAC frame sizes become a loudness curve.
AlignFFT cross-correlation, then Viterbi over 10 s windows.
MapOne offset per stretch of the song.
Sync policySeek, nudge or hide, then hand to your player.

Why frame sizes?

An AAC audio track is a sequence of frames, and the size of each frame follows the loudness and busyness of the music. The table of frame sizes sits in the file's header, so reading about 300 KB of each file gives a loudness-like curve for the song and for the video, without downloading or decoding any audio.

The two curves are aligned with FFT cross-correlation in 10-second windows, and a Viterbi pass picks one offset per window, charging for every change of offset. The candidate that lines up best wins, which is also what separates the official video from a lyric video, a live take or a re-upload.

Why two tiers?

YouTube serves a 360p stream almost instantly and a taller 720p or 1080p stream through a second client a moment later. Both are requested at once. The canvas starts on the first and is re-published with the sharper one.

Why ranged reads?

YouTube throttles an open-ended download of its adaptive streams to about 140 KiB/s, but serves bounded ranges at line rate. OpenCanvas starts with a small first range that grows, and answers a seek with a small range at the new position first, so playback never waits for a whole file.

Measured, not claimed

Everything here was measured on one Windows 11 machine on a home connection, with the final code, from an empty cache. The raw reports come from the manual tests in the repository.

Cold start, 16 songs in 6 languages. 16 of 16 aligned.
StageMinMedianp90Max
Video found, first stream playable451 ms550 ms643 ms688 ms
Song-to-video map ready596 ms954 ms1,139 ms1,688 ms
Same song, map already cached0 to 25 ms
Getting the same 720p file (18 MiB, 263 s).
Way of getting itFirst byteWhole file
yt-dlp, resolve the URL2.3 to 2.5 sn/a
yt-dlp, full downloadn/a3.9 to 4.0 s
Plain HTTP GET87 ms132 s (throttled)
OpenCanvas ranged reads27 ms1.8 s
Read this before quoting the numbersThe sync figures (96% of samples within 150 ms) come from an Android emulator, not a phone. They show that the policy works and what an under-powered device does. They are not a measurement of real hardware.

Use it in your app

Add the dependency, configure once at app start, then hand OpenCanvas the song that is playing.

build.gradle.kts
repositories {
    mavenCentral()
    maven { url = uri("https://jitpack.io") }
}
dependencies {
    implementation("com.github.Vijay-Janarthanan:OpenCanvas:1.0.0")
    // optional Compose player
    implementation("com.github.Vijay-Janarthanan.OpenCanvas:opencanvas-compose:1.0.0")
}
App start (excerpt)
OpenCanvas.configure(
    OpenCanvasConfig(
        cacheDirectory = File(context.filesDir, "opencanvas"), // measured maps live here
    )
)

The full guide, with every option and the Compose player, is in the repository README.