Skip to content

wix-playground/music-trafficker

Repository files navigation

Music Trafficker — Disco Ball

A single-page 3D experience for the @MusicTrafficker YouTube channel, built on Wix Headless (Astro + React + three.js).

A giant mirror ball spins in a dark club. Its windows are live thumbnails of the channel's videos — click one and it flies out of the ball into a fullscreen player that starts the video from the beginning, with sound. Close it and the tile flies back into the ball.

Live site: https://music-traf-ab6ea66c-vytenisu.wix-site-host.com

Stack

  • Astro 5 + @wix/astro (Wix-managed hosting, server routes)
  • React 18 island with three.js via @react-three/fiber + @react-three/drei
  • Bloom/vignette via @react-three/postprocessing
  • YouTube IFrame embed for playback

How the YouTube feed works

src/pages/api/videos.ts is an Astro server route (GET /api/videos). No YouTube API key is used:

  1. Primary source — it fetches https://www.youtube.com/@MusicTrafficker/videos and parses the ytInitialData JSON embedded in the page. The /videos tab lists full videos only (Shorts live in a separate tab), includes durations, and returns ~30 items.
  2. Fallback — if parsing fails (YouTube markup change, consent wall), it falls back to the channel RSS feed (youtube.com/feeds/videos.xml?channel_id=…, latest ~15 items). Because RSS mixes in Shorts, each item is probed with a HEAD request to /shorts/<id> — a 200 there means it's a Short and it gets dropped.
  3. Anything shorter than 90 seconds is also filtered out, results are cached in memory for 1 hour, and the response carries Cache-Control: s-maxage=3600.

Tiles first load i.ytimg.com/vi/<id>/mqdefault.jpg thumbnails, then upgrade to animated previews; the fullscreen flyer upgrades to maxresdefault.jpg when available.

Animated tile previews

Every window on the ball plays its video, muted, at low resolution. Two sources, best available wins:

  1. Real motion (an_webp hover previews) — ~3 s of consecutive real frames at ~10 fps, the clips YouTube shows on thumbnail hover. Available for most (not all) videos; URLs come from the channel page and are proxied via GET /api/motion-preview?v=<id>. The client decodes the animated WebP with the ImageDecoder API (Chromium) into a 160×90-cell atlas and plays it at its natural framerate.
  2. Storyboard crossfade — fallback for videos without a hover preview and for browsers without ImageDecoder. Storyboard frames are ~2 s apart in the source, so instead of hard cuts (which read as sped-up or laggy) each frame holds ~1.3–2.2 s and dissolves into the next via a small crossfade shader.

Storyboard sprite sheets are the seek-preview images every video has: one 800×450 sheet is a 5×5 grid of 160×90 frames sampled across the whole video — a ready-made flipbook atlas.

  • The sheet URL (signed) comes from the watch page's playerStoryboardSpecRenderer.spec. Watch pages are bot-walled from datacenter IPs, so specs are resolved at build time from a developer machine into src/server/storyboard-snapshot.json (node scripts/resolve-storyboards.mjs); the runtime falls back to live resolution only for ids missing from the snapshot (e.g. videos published after the last deploy — those show static thumbnails until the next snapshot refresh).
  • GET /api/storyboard-image?v=<id> proxies the sheet bytes (i.ytimg.com sends no CORS headers, so the browser can't texture from it directly) with a long CDN cache.
  • The client loads each sheet as one texture and animates it by shifting UV offsets over the grid (~8–12 fps, staggered per video) — zero per-frame uploads or decoding.

Discussion thread ("What do you think of AI music?")

A button under the ball opens a translucent popup with a single discussion thread, newest messages first. It's backed by real Wix modules:

  • Wix Blog hosts one seeded post ("What do you think of AI music?"); Wix Comments stores the messages, keyed to that post (on fresh Blog V3 sites comments key on the post id — referenceId only exists on legacy-migrated posts).
  • GET /api/thread — public read (comments.listCommentsByResource, NEWEST_FIRST) + current-member info; POST /api/thread — member-gated (members.getCurrentMembercomments.createComment with a Ricos body).
  • Login/logout use the built-in @wix/astro routes (/api/auth/login, /api/auth/logout) — the Wix-hosted login page registers new members too. A chip in the top-right shows the state.
  • Wix Members Area app is installed so commenter names/avatars resolve.

Live feed auto-update

The server re-scrapes the channel every 15 minutes; the client re-polls /api/videos (every 10 s until all storyboards are attached, then every 5 minutes) and hot-swaps new videos into the ball without remounting the scene — a swap is deferred while a video is playing.

The 3D scene

src/components/disco/DiscoScene.tsx:

  • ~1,100 instanced mirror facets + ~82 "video window" planes distributed over latitude bands of the sphere (each of the ~30 videos appears 2–3 times).
  • Orbiting colored spotlights, an emissive-lightformer environment map for reflections, particle sparkles, bloom + vignette.
  • All animation is time-based (delta-driven), so it is framerate-independent.
  • Click → the tile's world transform is captured and a 16:9 "flyer" plane interpolates position/rotation/scale to a rect that exactly matches the letterboxed frame the fullscreen player will use, while the thumbnail un-crops from window aspect to full 16:9. When it arrives, the DOM overlay with the YouTube iframe fades in (autoplay=1&start=0, sound allowed because it follows a user gesture). Closing reverses the flight.
  • Drag to orbit the ball manually (OrbitControls, rotation only).

Develop

npm install --ignore-scripts   # sharp's native build is dev-only dead weight; skip it
npm run dev                    # wix dev → http://localhost:4321

Prefix commands with AI_AGENT=claude (or run interactively) when using the Wix CLI from an agent.

Build & deploy

npm run build     # wix build
npx wix release   # publishes to Wix hosting, prints the live URL

The site and hosting are managed by Wix; wix.config.json carries the site/app ids. .env.local (gitignored) holds the CLI-managed OAuth secret — never commit it.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors