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.

CipherDeobfuscator is the object responsible for resolving obfuscated YouTube stream URLs. It fetches the player JavaScript, extracts the signature and n-transform functions, and executes them inside an Android WebView (referred to as the cipher WebView). The cipher WebView is the primary deciphering path — fallback clients are unreliable and should be treated as last resorts. Package: com.zemer.cipher All methods are suspend unless otherwise noted.
deobfuscateStreamUrl, transformNParamInUrl, and prewarm all share a single Mutex. They serialize — concurrent callers queue rather than execute in parallel. This is required because the underlying CipherWebView has single-shot continuation slots that cannot be used concurrently.

Initialization

initialize

fun initialize(context: Context)
Stores the application context used when creating the cipher WebView. This is called internally by ZemerCipher.initialize() — you do not need to call it directly.
context
Context
required
Application context. applicationContext is stored internally to avoid leaks.

Properties

lastUsedPlayerHash: String?

val lastUsedPlayerHash: String?
Read-only. The 8-character hex hash of the player_ias JavaScript last used to decipher a web stream (signature or n-param). Returns null if no web stream has been deciphered in the current process lifetime. This is a diagnostic property — it is surfaced in debug UIs to help identify which player generation is active. Direct-URL clients (e.g. ANDROID_VR, IOS) never run the cipher, so this value only reflects the last web stream.

Functions

signatureTimestamp

suspend fun signatureTimestamp(): Int?
Returns the signatureTimestamp (sts) embedded in the player JavaScript that the cipher WebView is currently using (or will use after a lazy fetch). This value must be included in the signatureTimestamp field of InnerTube /player requests. During A/B player rollouts, different sources can resolve different player versions. Sending the sts from this function guarantees that the signature the /player endpoint mints is decipherable by the cipher WebView’s loaded player — mismatching versions causes the CDN to 403 the resulting URL.
return
Int?
The signatureTimestamp integer, or null if the player JavaScript could not be fetched.

prewarm

suspend fun prewarm()
Pre-creates the cipher WebView in the background so that the first call to deobfuscateStreamUrl incurs no cold-start delay. This involves fetching player JavaScript (~2.8 MB), extracting function names, and loading the JS into a WebView. Execution is guarded by the same mutex as deobfuscateStreamUrl — it will not race an in-flight deobfuscation request. On any failure (including renderer death), the error is swallowed and the WebView is created lazily on the next real request. Safe to call at any point after ZemerCipher.initialize().

deobfuscateStreamUrl

suspend fun deobfuscateStreamUrl(signatureCipher: String, videoId: String): String?
Parses a signatureCipher query string, deobfuscates the signature using the player JavaScript WebView, and returns the fully resolved stream URL.
signatureCipher
String
required
The full signatureCipher query string from a streaming format object, in the form s=<obfuscated_sig>&sp=<sig_param_name>&url=<base_url>. All three fields are percent-encoded.
videoId
String
required
The YouTube video ID. Used for logging and diagnostics only — it is not sent over the network.
return
String?
The resolved stream URL with the deobfuscated signature appended as a query parameter (e.g. &signature=<sig>), or null on failure.
Retry behavior: On the first failure, the player JS cache is invalidated and the cipher WebView is discarded. The function then retries once with freshly fetched player JavaScript. If the retry also fails, null is returned. Renderer death: If a CipherRendererGoneException is raised (the sandboxed WebView renderer was OOM-killed), the function returns null immediately without retrying. The dead WebView is discarded, and RendererRecoveryPolicy opens a short backoff window to avoid stalling subsequent songs on a doomed 2.8 MB player-JS re-parse under continued memory pressure. This function never throws, except for CancellationException from coroutine cancellation.

transformNParamInUrl

suspend fun transformNParamInUrl(url: String): String
Transforms the n= parameter in a YouTube streaming URL to prevent CDN throttling and 403 errors. Uses the n-transform function discovered from the player JavaScript when the cipher WebView was initialized.
url
String
required
A YouTube streaming URL. This is typically the output of deobfuscateStreamUrl, but any streaming URL with an n= parameter is valid.
return
String
The URL with the n= parameter replaced by its transformed value. The original URL is returned unchanged if:
  • No n= parameter is present in the URL
  • The n-transform function was not successfully discovered when the cipher WebView was initialized
  • The transform fails for any reason (exceptions are caught and logged)

onStreamRejected

suspend fun onStreamRejected(): Boolean
Call this when the CDN returns a 403 on a stream URL that was produced by deobfuscateStreamUrl. A 403 after successful deobfuscation typically indicates a stale or incorrect player config (e.g. the wrong signature function was selected due to an outdated remote config or a legacy-regex false positive). This kind of failure is invisible to deobfuscateStreamUrl’s own retry logic because no exception is thrown during deciphering — the bad signature is only detected when the CDN rejects it. Internally, this triggers PlayerConfigStore.refreshAfterStreamRejection(), a rate-limited single-flight fetch of the remote player config table. If the config table changes (i.e. its epoch advances), the next call to deobfuscateStreamUrl will rebuild the cipher WebView with the corrected config, recovering the WEB client without requiring an app restart.
return
Boolean
true if the remote config table changed (epoch advanced), indicating that retrying stream resolution with a fresh WebView may succeed. false if the config is unchanged or the refresh was rate-limited.

Build docs developers (and LLMs) love