Skip to main content

Documentation 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.

YouTube’s 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:
// Create once and hold in a ViewModel or service:
private val poTokenGenerator = PoTokenGenerator()
Each 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

Call getWebClientPoToken with the current video ID and the visitorData value from the InnerTube /player or /browse response:
// visitorData comes from the InnerTube /player or /browse response
val result: PoTokenResult? = poTokenGenerator.getWebClientPoToken(
    videoId = videoId,
    sessionId = visitorData
)

if (result != null) {
    // Use result.playerRequestPoToken in the /player InnerTube request body
    // Use result.streamingDataPoToken appended to the stream URL as pot=<token>
    val streamUrlWithPot = "$streamUrl&pot=${result.streamingDataPoToken}"
}
// If result is null, proceed without PoToken (use non-web fallback client)

Token Bindings

PoTokenResult contains two tokens with distinct bindings and usage points:
  • playerRequestPoToken — Bound to the session (visitorData). Send this in the InnerTube /player request body. It is generated once per session and reused across all videos in that session, so there is no need to call getWebClientPoToken just to refresh this token.
  • streamingDataPoToken — Bound to the video ID. Append this as pot=<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 PoTokenWebView is 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 same sessionId.
  • Per-video tokens are never cached — a fresh token is generated on every getWebClientPoToken call.
  • When sessionId changes (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 new sessionId.
  • 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 — getWebClientPoToken returns null and the WebView is closed and cleaned up.
  • A null return value means: the current system cannot produce a PoToken. Continue playback using a fallback client that does not require PoToken (such as ANDROID_VR or IOS).
  • Warm-path calls (WebView already initialized, session already minted) complete in well under a second.

Error Handling

Two exceptions can surface from getWebClientPoToken:
  • 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 returns null on 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.
All other exceptions (including 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.
Create one PoTokenGenerator per active player session. If the user signs out and signs in with a different account (new visitorData), pass the new visitorData as sessionId on the next call — the generator detects the change and handles WebView recreation automatically without any additional code on your side.

Build docs developers (and LLMs) love