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.

Zemer Cipher uses a single shared OkHttpClient for all network requests — player JS fetches, config refreshes, and any other HTTP calls. The client is built lazily and rebuilt only when the proxy configuration changes (checked by reference equality), ensuring connection pooling and TLS session reuse across all requests. Prior to this design, each subsystem built its own OkHttpClient per call, allocating a fresh dispatcher and connection pool every time and defeating TCP/TLS reuse entirely.

Setting a Proxy at Initialization

Pass a java.net.Proxy to ZemerCipher.initialize() to route all library network traffic through a proxy from the start:
val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("proxy.example.com", 8080))
ZemerCipher.initialize(
    context = applicationContext,
    proxy = proxy,
    debugLogging = BuildConfig.DEBUG
)
Both Proxy.Type.HTTP and Proxy.Type.SOCKS are supported — OkHttp handles both transparently.

Changing Proxy at Runtime

The ZemerCipher.proxy property can be reassigned at any time after initialization:
// Switch to SOCKS5:
ZemerCipher.proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", 1080))

// Disable proxy:
ZemerCipher.proxy = null
The OkHttpClient is rebuilt on the next HTTP request when the proxy reference changes. In-flight requests on the old client complete normally; only new requests issued after the assignment use the rebuilt client.

What Goes Through the Proxy

The following requests use the shared OkHttpClient and therefore route through the configured proxy:
  • YouTube iframe_api fetch — retrieves the current player hash from https://www.youtube.com/iframe_api
  • Player JS download — downloads https://www.youtube.com/s/player/{hash}/player_ias.vflset/en_GB/base.js (~2.8 MB, cached for 6 hours in <filesDir>/cipher_cache/)
  • Remote player_configs.json fetch — retrieves the config from https://raw.githubusercontent.com/ZemerTeam/zemer-cipher/master/library/src/main/assets/player_configs.json (6-hour TTL with ETag-based conditional requests for 304 responses)
The following traffic does not use the shared OkHttpClient:
  • Cipher WebView page-load network — the CipherWebView has blockNetworkLoads = true. All JavaScript runs against the locally injected player JS; the WebView itself makes no outbound network requests.
  • PoToken WebView page-load network — the PoTokenWebView also sets blockNetworkLoads = true, so no direct page or resource loads originate from its WebView renderer. However, the BotGuard HTTP calls (to youtube.com/api/jnn/v1/Create and GenerateIT) are made from Kotlin via the shared OkHttpClient — those requests do route through the configured proxy.

Network Failure Handling

Zemer Cipher is designed to degrade gracefully on network failures rather than throw exceptions to callers:
  • Player JS fetch failuredeobfuscateStreamUrl returns null. The caller should fall through to an alternate client (e.g. ANDROID_VR or IOS) that does not require cipher deobfuscation.
  • Config fetch failure — The previous in-memory and on-disk config table is kept unchanged. No exception propagates. If the failure was a pure network error (the server was never reached — no HTTP response), the cooldown timer is not armed, so the next unknown-hash trigger retries immediately rather than waiting out the 5-minute cooldown window. If the server responded with an error code (e.g. HTTP 404 or 500), the cooldown is armed normally.
  • Startup refresh failurePlayerConfigStore.scheduleStartupRefresh() catches and logs all exceptions via Timber without propagating them. A failed startup refresh has no effect on the current config table; the next startup will attempt the refresh again.

6-Hour Caching

Two separate caches live in <filesDir>/cipher_cache/: Player JS cache
FilePurpose
player_<hash>.jsThe full player JavaScript (~2.8 MB)
current_hash.txtThe current hash and a Unix timestamp (ms)
The TTL is 6 hours from the timestamp in current_hash.txt. On cache expiry or a forced invalidation (triggered by a decipher failure), all player_* files and current_hash.txt are deleted. Config cache files (configs_remote.*) are deliberately excluded from this purge so the ETag survives a decipher retry. Player configs cache
FilePurpose
configs_remote.jsonThe last-good remote player_configs.json body
configs_remote.metaLine 1: ETag header value. Line 2: last-fetch timestamp (ms).
The TTL is 6 hours from the timestamp in configs_remote.meta. On refresh, a conditional If-None-Match request is sent using the stored ETag; a 304 Not Modified response advances the timestamp without re-downloading the body. Both files are written atomically (temp file + rename) to prevent corrupt state from a process death mid-write.
The BotGuard HTTP calls made by PoTokenWebView during PoToken generation use the shared OkHttpClient and therefore route through the configured proxy. Both blockNetworkLoads = true WebViews (cipher and PoToken) have no direct WebView-layer outbound traffic, so there is no separate WebView-level proxy to configure.

Build docs developers (and LLMs) love