Skip to content

Instantly share code, notes, and snippets.

@hi-ogawa
Created August 19, 2026 15:07
Show Gist options
  • Select an option

  • Save hi-ogawa/a9a121859bdee7028d601350940b020d to your computer and use it in GitHub Desktop.

Select an option

Save hi-ogawa/a9a121859bdee7028d601350940b020d to your computer and use it in GitHub Desktop.
YouTube audio acquisition hardening investigation for youtube-audio-replacement

YouTube audio acquisition hardening investigation

Existing acquisition path

The extension creates a hidden credentialless https://www.youtube.com/embed/ iframe. A MAIN-world content script extracts VISITOR_DATA, calls /youtubei/v1/player as ANDROID_VR 1.65.10, selects a direct Opus audio format, and asks the extension background service worker to download Googlevideo in ranges.

Relevant source:

Production-path reproduction

A staged Playwright probe loaded the packaged extension and submitted video 7GU_VQfgMT0 through the generator UI. It observed:

  1. The extension service worker started.
  2. The hidden YouTube iframe loaded and announced RPC readiness.
  3. /youtubei/v1/player returned HTTP 200 with playabilityStatus.status = OK and 26 adaptive formats.
  4. The selected Android VR audio was itag 251 with a content length of 3,490,477 bytes.
  5. The production first range 0-3490476 returned HTTP 403 from Googlevideo with zero bytes.
  6. The generator displayed Proxy fetch failed: 403.

This places the observed production failure at Android VR media delivery, after successful iframe setup and player extraction.

Upstream yt-dlp history

Current yt-dlp upstream master was inspected after fetching current remote refs. Relevant changes include:

  • ff459e5fc reverted Android VR from 1.71.26 to 1.65.10 because newer versions may return SABR-only streams.
  • 1328586f7 added the JS-less VISIONOS 1.02 client.
  • 69ea20006 added Android VR PO-token policy after intermittent/selective enforcement appeared for non-HLS formats.
  • dae52d838 removed Android VR from defaults on 2026-08-18. Its source comment states that since 2026-08-17 all Android VR 1.65.10 formats, including live HLS and itag 18, were observed returning HTTP 403.

Current logged-out yt-dlp defaults use visionos,web; JS-less defaults use visionos.

Fetch-context comparison

Diagnostic RPC methods were run inside the extension's actual hidden credentialless YouTube iframe.

Using the Android VR itag 251 URL produced:

Context Range Status Bytes Response type
Extension background service worker 0-3490476 403 0 n/a
Hidden YouTube MAIN-world iframe 0-3490476 403 0 cors
Hidden YouTube MAIN-world iframe 0-65535 200 65,536 cors

The service-worker origin is therefore not the sole cause. The iframe can also read Googlevideo responses under CORS, so the background proxy is not required for the tested direct-fetch path.

Android VR range behavior

Consecutive 64 KiB ranges

  • The first fifteen ranges, covering 0-983039, returned HTTP 200.
  • Range 983040-1048575 returned HTTP 403 with zero bytes.
  • Refreshing the player metadata and retrying with the newly returned URL still returned 403.
  • No response redirected.
  • The final Googlevideo host remained unchanged.
  • Response type remained cors.
  • No Content-Range header was exposed.

Only 983,040 of 3,490,477 bytes could be assembled.

Aligned range attempts

Strategy Request Result
1 MiB 0-1048575 403 immediately
960 KiB 0-983039 200 with 983,040 bytes
960 KiB continuation 983040-1966079 403 with zero bytes

The accessible 960 KiB is a prefix, not a reusable segment size. Changing fetch context, chunk size, alignment, or refreshing the URL did not make Android VR fully downloadable.

The probe did not reproduce a spontaneous redirect. All recorded responses had redirected = false and retained the same Googlevideo host.

VisionOS complete-download verification

The same full hidden-frame test used yt-dlp's VisionOS identity:

clientName: VISIONOS
clientVersion: 1.02
client ID: 101
deviceModel: RealityDevice17,1
userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15
osName: visionOS
osVersion: 26.5.23O471

Results for itag 251:

  • Expected bytes: 3,490,477.
  • Received bytes: 3,490,477.
  • Every full 65,536-byte range returned HTTP 200.
  • The final range 3473408-3490476 returned 17,069 bytes.
  • No response redirected or changed host.
  • Every response was CORS-readable.
  • The assembled WebM decoded successfully with AudioContext.decodeAudioData.
  • Decoded duration was approximately 209.804 seconds.

This verifies metadata extraction, complete ranged media transfer, byte assembly, and audio decode through the real hidden extension iframe.

Separate player challenge observation

Some reduced crafted requests returned HTTP 200 with:

{
  "playabilityStatus": {
    "status": "LOGIN_REQUIRED",
    "reason": "Sign in to confirm you’re not a bot"
  }
}

Other fresh runs returned OK. This behavior was not quantified and is separate from the deterministic Android VR Googlevideo failure reproduced through the extension. No refresh strategy is established by this investigation.

The current parser reports videoDetails not found in player API response for this case, which obscures the actual playability reason.

Conclusions

  1. Hidden iframe creation and RPC are working.
  2. Android VR player extraction still returns direct formats for the test video.
  3. Android VR media delivery is restricted to an incomplete prefix and cannot produce a usable file.
  4. The behavior matches yt-dlp's August 2026 removal of Android VR from defaults.
  5. Moving the existing large fetch from the service worker into YouTube MAIN world does not fix Android VR.
  6. VisionOS downloads and decodes the complete file in the existing hidden iframe.
  7. The service-worker Googlevideo proxy is unnecessary for the verified VisionOS path.

Proposed implementation

  • Make VISIONOS the production player client.
  • Download media directly in the hidden YouTube MAIN-world iframe using 64 KiB ranges.
  • Remove the background proxyFetch RPC and base64 transfer.
  • Remove the Googlevideo host permission if no remaining feature requires it.
  • Do not retain Android VR as a media fallback because it produced only an incomplete prefix.
  • Parse and surface playabilityStatus.status and reason before expecting videoDetails.
  • Guard the ranged loop against zero-byte responses.
  • Replace the diagnostic probe with a concise extension E2E success test covering iframe readiness, player extraction, complete transfer, and usable output.

If VisionOS later becomes unusable, the larger fallback is yt-dlp's modern web path, which involves current client configuration, player JavaScript challenge solving, PO tokens, and potentially SABR. That is substantially more complex than the verified VisionOS direct-format path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment