Public
Full-bleed shuffled YouTube stream of the Airwolf_C64 channel
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

Airwolf C64 Player

A full-bleed, edge-to-edge web player that shuffle-streams the @Airwolf_C64 ("C64!") channel's 33k+ demoscene uploads — built for an always-on Google TV Streamer display.

Open the endpoint and it autoplays a uniformly-random 50-video playlist you can skip through with the TV remote.

How it works

Video IDs are harvested into SQLite by a cron job using the cheap playlistItems endpoint (1 quota unit per 50 videos). Each page load then pulls a uniformly-random sample straight from the database — zero YouTube API quota per request.

Rendering mermaid diagram...

Files

FileTypeRole
main.tshttpServes the player; pulls 50 random IDs from SQLite (no API call)
harvest.tsintervalCron (every 6h): pages uploads into SQLite, resumes via a cursor
db.tsscriptSQLite schema + insert / random-select helpers
playlist.tsscriptYouTube API helpers (playlistItems paginator, search fallback), Fisher-Yates shuffle
player.htmlfilePure full-bleed 100vw/100vh iframe (undistorted) with __FIRST_VIDEO_ID__ / __REST_VIDEO_IDS__ placeholders

Setup

Requires a YouTube Data API v3 key in the YOUTUBE_API_KEY environment variable (used only by the cron and the cold-start fallback — never on a normal load).

šŸ‘‰ Add YOUTUBE_API_KEY here: https://www.val.town/x/cgio/airwolf-c64-player/environment-variables?key=YOUTUBE_API_KEY

Notes

  • Cold start: if the cache is empty, main.ts falls back to a live search query so it never serves a blank screen. Once the cron has run, the cache serves everything.
  • Cursor: harvest.ts stores its playlist page token in blob storage and resumes each run, walking the whole catalog and looping back to catch new uploads.
  • Autoplay: mute=0 means a browser/TV may require one interaction before audio starts.
  • Age-restricted filtering: harvest.ts requests part=snippet,contentDetails and skips any item flagged ytRating === "ytAgeRestricted" (or whose snippet text mentions age restriction) so it never enters the SQLite cache.
  • Pitch-black bleed + outline reset (white hairlines): player.html paints html/body and the iframe solid #000000 and applies an outline/border/box-shadow: none !important + -webkit-tap-highlight-color: transparent reset. This is what kills the original white sub-pixel hairlines — any fractional rendering on a high-res TV now bleeds into black instead of white, and no native focus ring contributes a stray edge.
  • Faithful, undistorted framing (source artifacts left as-is): the iframe is a plain 100vw/100vh box — no scale/crop, no distortion. Some uploads show faint gray vertical seams and/or horizontal bands at the picture edges. These were verified to be baked into the source video files — they reproduce identically on youtube.com (mobile and fullscreen), not just here, so no parent CSS, container, or display setting can be responsible. They're emulator/capture-pipeline artifacts (the uploader's recording region not landing on a clean pixel boundary, plus genuine C64 raster-border effects) frozen into the H.264 frames, and they sit at different positions per video. We deliberately do not crop or mask them: doing so would only work per-clip, would butcher the legitimate raster-border art that is the demoscene aesthetic, and would misrepresent the productions. An always-on demoscene shrine shows the archive faithfully — seams and all.