YouTube’sDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ZemerTeam/zemer-cipher/llms.txt
Use this file to discover all available pages before exploring further.
WEB and WEB_REMIX clients require a proof-of-origin token (PoToken) to serve streams without truncation. Without a valid PoToken, the CDN serves only approximately the first 1 MiB of a stream before dropping the connection. PoTokenGenerator handles token generation by running the BotGuard JavaScript challenge inside an Android WebView, managing session reuse across videos, and falling back gracefully when WebView is unavailable.
Creating a Generator
PoTokenGenerator is not a singleton — instantiate one per use-case or player session and hold it for the lifetime of that scope:
PoTokenGenerator instance owns one PoTokenWebView internally. Creating multiple instances per session wastes WebView resources; sharing one instance across sessions is safe because the generator detects sessionId changes and recreates its WebView automatically.
Generating Tokens
CallgetWebClientPoToken with the current video ID and the visitorData value from the InnerTube /player or /browse response:
Token Bindings
PoTokenResult contains two tokens with distinct bindings and usage points:
-
playerRequestPoToken— Bound to the session (visitorData). Send this in the InnerTube/playerrequest body. It is generated once per session and reused across all videos in that session, so there is no need to callgetWebClientPoTokenjust to refresh this token. -
streamingDataPoToken— Bound to the video ID. Append this aspot=<token>to each individual stream URL. The CDN validates that the token is bound to the exact video being served. Using a session-bound token on a video URL causes the CDN to drop the connection after serving approximately the first 1 MiB (~45 seconds of audio at typical bitrates).
Session Management
PoTokenGenerator manages its internal WebView lifecycle automatically:
- The
PoTokenWebViewis created on first use and cached for the life of the generator. - The session PoToken (bound to
visitorData) is generated once at WebView creation time and cached internally. It is reused on every subsequent call with the samesessionId. - Per-video tokens are never cached — a fresh token is generated on every
getWebClientPoTokencall. - When
sessionIdchanges (e.g. the user switches accounts), the generator closes the old WebView and creates a new one automatically, generating a new session token for the newsessionId. - On WebView renderer death (OOM kill by the system), the generator detects the dead instance and recreates both the WebView and the session token on the next call.
Timeout and Fallback
Token generation is subject to an 8-second timeout:- A cold start (WebView spin-up + BotGuard JS load + session token generation) typically takes 2–5 seconds on a healthy device.
- If the total time exceeds 8 seconds — for example when the WebView renderer is killed by the OS under memory pressure —
getWebClientPoTokenreturnsnulland the WebView is closed and cleaned up. - A
nullreturn value means: the current system cannot produce a PoToken. Continue playback using a fallback client that does not require PoToken (such asANDROID_VRorIOS). - Warm-path calls (WebView already initialized, session already minted) complete in well under a second.
Error Handling
Two exceptions can surface fromgetWebClientPoToken:
-
BadWebViewException— The system WebView implementation is broken. This typically occurs on very old Chromium builds that cannot parse modern BotGuard JavaScript. When caught, the generator permanently marks itself as bad and returnsnullon all future calls for the current process lifetime. No retry is possible. -
PoTokenException— A JavaScript error occurred during token generation. The generator automatically retries once by recreating the WebView from scratch. If the second attempt also fails, the exception propagates to the caller.
TimeoutCancellationException) are caught internally and result in a null return.
getWebClientPoToken is a synchronous blocking call that uses runBlocking internally. Call it from a background thread or a dedicated Dispatchers.IO dispatcher — never from the main thread. It does not need to be called from a coroutine.