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.
CipherDeobfuscator is the main entry point for resolving YouTube streaming URLs. It handles both signature deobfuscation (converting a signatureCipher query string into a valid stream URL) and the n-parameter transformation needed to prevent CDN throttling. Both operations execute inside an Android WebView that runs the real YouTube player JavaScript.
Prerequisites
ZemerCipher.initialize() must have been called before using any method on CipherDeobfuscator. All public methods are suspend functions — call them from a coroutine or a suspend context.
Getting the signatureTimestamp
Before making an InnerTube/player API call, retrieve the signatureTimestamp (sts) so the server signs the response with the same player version currently loaded in the WebView. Mismatching player versions cause the CDN to reject the deciphered URL with a 403.
signatureTimestamp() fetches (or reuses the cached) player JS before returning, so it may trigger a network request on first call or after cache expiry.
Deobfuscating a Signature Cipher
YouTube’sstreamingData.adaptiveFormats[n].signatureCipher is a URL-encoded query string of the form s=<obfuscated-sig>&sp=signature&url=<base-url>. Pass it directly to deobfuscateStreamUrl:
deobfuscateStreamUrl returns null on failure and never throws. Internally, on the first failure it invalidates the cached player JS, closes the WebView, and retries once with a freshly fetched player. If the retry also fails, it returns null.
Transforming the N-Parameter
After deobfuscating the signature, transform then= query parameter in the resulting URL to prevent CDN throttling. This is a separate step from signature deobfuscation:
transformNParamInUrl returns the original URL unchanged if no n= parameter is present, if the n-transform function was not extracted at WebView creation time, or if the transform itself fails. It never throws.
Full Playback Pipeline
Combine both steps into a single helper:Handling CDN Rejections (403)
A 403 on a correctly deciphered URL typically means the player config entry is stale or incorrect — for example, the sig call expression was valid JavaScript but computed the wrong output. The exception-retry path insidedeobfuscateStreamUrl cannot detect this because no exception is thrown; the rejected URL is the only signal.
Call onStreamRejected() when your HTTP client or ExoPlayer receives a 403 on a deciphered URL:
onStreamRejected() triggers PlayerConfigStore.refreshAfterStreamRejection(), which re-fetches the remote player_configs.json (rate-limited with its own 5-minute cooldown). If the remote config has been updated with a corrected entry, configChanged = true signals that the WebView will be rebuilt with the new config on the next decipher call, and retrying resolution is worthwhile.
Diagnostic: Last Used Player Hash
CipherDeobfuscator.lastUsedPlayerHash exposes the 8-hex player hash of the player JS currently loaded in the WebView:
null when no WebView has been created yet in the current process.
deobfuscateStreamUrl and transformNParamInUrl both acquire the same internal Mutex before accessing the WebView. They cannot run concurrently — calling both from different coroutines simultaneously for the same instance will cause the second call to suspend until the first completes.