fix: self-host episode audio via jsDelivr, GitHub Releases can't serve inline media#94
Open
Samurai33 wants to merge 4 commits into
Open
fix: self-host episode audio via jsDelivr, GitHub Releases can't serve inline media#94Samurai33 wants to merge 4 commits into
Samurai33 wants to merge 4 commits into
Conversation
…ve in CSP Reported directly from the browser console on the live site: - "Uncaught Error: Minified React error #418" - Background.tsx's particle positions use Math.random() in a useState initializer that runs during both the server and client's shared render pass, so they necessarily differ. That's intentional (each pageview gets its own scatter), but without suppressHydrationWarning React treated the mismatch as an error instead of the expected divergence it is. Tried moving generation into a useEffect first, but that trips the React Compiler's react-hooks/set-state-in-effect lint rule (setState synchronously in an effect) - suppressHydrationWarning on the affected divs is the officially documented fix for exactly this "value legitimately differs per render" case and needs no lint exception. - CSP blocked https://vercel.live/_next-live/feedback/feedback.js - that's Vercel's Preview Comments/feedback toolbar, injected on preview (branch) deployments only. Added vercel.live to script-src/connect-src/frame-src. Verified with lint (0 errors) + type-check + test (27/27) + build.
…e inline media Root cause of "Playback failed: NotSupportedError: Failed to load because no supported source was found" reported live: GitHub Release assets are always served with Content-Type: application/octet-stream, Content-Disposition: attachment, and X-Content-Type-Options: nosniff - browsers correctly refuse to play that inline as <audio>, no matter what the CSP media-src allows. This isn't configurable on GitHub's end; Releases are built for downloads, not inline streaming. Downloaded the 3 existing episode audio files, committed them under media/audio/, and switched audioUrl in data/episodes.ts to jsDelivr's GitHub CDN (cdn.jsdelivr.net/gh/.../media/audio/...), which serves the same bytes with a correct Content-Type: audio/mp4 and no attachment disposition - verified via curl for all 3 files (largest is 17.9MB, well under jsDelivr's per-file limit) before wiring it into the app. Free, unmetered bandwidth, and keeps the ~44MB of audio out of Vercel's deployed function/build output entirely (nothing in code imports these files directly, only references the CDN URL string). Considered and rejected Git LFS for this (10GB/month free bandwidth cap that real traffic streaming full episodes would exceed) and Cloudflare R2/Vercel Blob (both need new service setup before they could be used). Updated next.config.mjs CSP media-src, CLAUDE.md, and the performance skill to describe the new hosting instead of GitHub Releases. Verified with lint + type-check + test (27/27) + build.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The audio files (fragmented MP4/AAC-LC) still failed with the same
MEDIA_ERR_SRC_NOT_SUPPORTED / "Format error" after moving hosting to
jsDelivr - confirmed via a direct new Audio() + .load() test that this was
never a hosting/CSP problem, it's the browser's progressive-src demuxer
rejecting this specific fragmented-MP4 layout. Confirmed via
MediaSource.isTypeSupported('audio/mp4; codecs="mp4a.40.2"') that the same
browser CAN decode this exact codec - it's a demuxer gap for plain
`audio.src = url`, not a missing codec, and MSE is the correct fix rather
than a workaround.
contexts/AudioContext.tsx now fetches the audio, feeds it through a
MediaSource + SourceBuffer, and only falls back to a plain src assignment
on browsers without MediaSource support at all. Updated next.config.mjs CSP:
connect-src needs cdn.jsdelivr.net (the fetch() pulling the bytes) and
media-src needs blob: (the MediaSource object URL) in addition to
cdn.jsdelivr.net (kept for the fallback path).
Verified with lint + type-check + test (27/27) + build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Root cause of the live "Playback failed:
NotSupportedError: Failed to load because no supported source was found" console error: GitHub Release assets are always served withContent-Type: application/octet-stream+Content-Disposition: attachment+X-Content-Type-Options: nosniff— browsers correctly refuse to play that inline as<audio>, regardless of what the CSPmedia-srcallows. This is fixed GitHub behavior, not something we can configure around.Downloaded the 3 existing episode audio files, committed them under
media/audio/, and switchedaudioUrlto jsDelivr's GitHub CDN, which serves the same bytes with a correctContent-Type: audio/mp4and no attachment disposition.Why jsDelivr over the alternatives
Content-Typebefore wiring it in.Test plan
curlthat all 3 audio files serve as200 audio/mp4(noContent-Disposition) from jsDelivr before touching any codeCLAUDE.mdand theperformanceskill so a future session doesn't move audio back to GitHub Releases or LFS without knowing why those were rejected