Jonathan Sykes a171cae417 Add ad-free YouTube player built on zero-native
Desktop YouTube player with no login and no ads. Extracts direct
video/audio streams with yt-dlp and plays them in a native window via
zero-native (Zig + system WebView) for a tiny footprint vs Electron.

- Dual-stream engine: muted video synced with separate audio track for
  high quality without ffmpeg muxing; progressive fallback
- On-device playlists, watch history, audio-only mode
- Full custom controls: seek, volume, speed, quality, fullscreen, queue
- Zig bridge handlers spawn yt-dlp and return slimmed JSON to the web UI
- Cinematic dark UI: Bricolage/Hanken/JetBrains Mono, vermilion accent
2026-06-13 23:33:16 +08:00

YT Player

A lightweight, ad-free YouTube player with on-device playlists. No login, no tracking, no ads — it extracts direct video/audio streams with yt-dlp and plays them in a native window.

Built on zero-native (Zig + the system WebView) instead of Electron, so the binary is tiny — no bundled Chromium or Node runtime.

Features

  • 🔍 Search YouTube without an account
  • 🚫 No ads — plays the raw stream, never the YouTube player
  • 📺 High quality via a dual-stream engine: a muted video track synced with a separate audio track (1080p+) with no ffmpeg muxing required; progressive formats are used as a fallback
  • 🎵 Audio-only mode — great for music, saves bandwidth
  • 📂 On-device playlists — create, rename, delete, add/remove videos. Stored locally as JSON; nothing leaves your machine
  • 🕘 Watch history
  • ⏯ Full controls: seek, volume, playback speed, quality switching, fullscreen, next/prev, and keyboard shortcuts (space, ←/→, f, m)

Architecture

frontend/            Static web UI (no framework, no build step)
  index.html
  styles.css
  app.js             Player engine + UI; calls window.zero.invoke(...)
src/
  main.zig           App definition + bridge handler registration
  bridge.zig         Native handlers: spawn yt-dlp, slim its JSON, local store
scripts/
  setup-ytdlp.js     Downloads the standalone yt-dlp binary into ./bin
bin/                 yt-dlp lands here (gitignored)
app.zon              App manifest (window, engine, permissions, frontend dir)

The web UI talks to the Zig side over the zero-native bridge:

window.zero.invoke(...) Native handler Returns
yt.search { query } ytSearch { ok, results:[…] }
yt.streams { videoId } ytStreams { ok, data:{ meta, audioUrl, qualities[] } }
store.load {} storeLoad playlists / history / settings
store.save { data } storeSave { ok }

Because the bridge is size-limited, the Zig handlers parse yt-dlp's large JSON and return only the compact fields the UI needs.

Prerequisites

  • Zig (0.14.x recommended) — https://ziglang.org/download/
  • zero-native CLI: npm install -g zero-native
  • Node.js (only to run the yt-dlp downloader script)
  • No system yt-dlp needed — the setup script bundles it. (On Linux/macOS the bundled build is self-contained; Python is not required.)

Setup & run

# 1. Download the yt-dlp binary into ./bin
npm run setup            # or: node scripts/setup-ytdlp.js

# 2. Generate the zero-native build files for your installed version
#    (build.zig, build.zig.zon, src/runner.zig). Run this in a scratch dir and
#    copy the generated build.zig / build.zig.zon next to this project, OR run
#    init here and keep your files:
zero-native init ytplayer --frontend none

# 3. Merge: keep THIS repo's frontend/, src/main.zig, src/bridge.zig and app.zon.
#    (src/main.zig shows exactly how the handlers are registered — fold that into
#    the generated App if the scaffold differs.)

# 4. Build & launch the native window
zig build run

To refresh yt-dlp later (YouTube changes often): npm run update-ytdlp.

Notes & caveats

  • zero-native is pre-1.0. The bridge handler signature used here follows its documented contract (fn(context, invocation, output) anyerror![]const u8). If your installed version exposes the Invocation type or BridgeDispatcher fields slightly differently, only the wiring in src/main.zig and the payloadField helper need adjusting — the handler logic is self-contained.
  • Stream URLs from yt-dlp are IP-locked and expire after a few hours; the app re-fetches them each time you play a video, so this is transparent.
  • This is for personal use. Respect YouTube's Terms of Service and the rights of content creators.

License

MIT

Description
No description provided
Readme 1,018 MiB
Languages
JavaScript 67.3%
CSS 17.7%
Zig 4.9%
Rust 4.9%
HTML 3.5%
Other 1.7%