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.

All Zemer Cipher functionality depends on a one-time initialization call. ZemerCipher.initialize() sets up the shared OkHttpClient, loads the bundled player configs, schedules a background config refresh, and prepares CipherDeobfuscator. It must be called before any deobfuscation or PoToken calls.

Basic Initialization

Call ZemerCipher.initialize() in your Application.onCreate(). This is the recommended location because it runs before any Activity, Service, or ContentProvider starts.
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        ZemerCipher.initialize(context = applicationContext)
    }
}
Register your Application subclass in AndroidManifest.xml:
<application
    android:name=".MyApplication"
    ... >

Initialization Parameters

ZemerCipher.initialize() accepts three parameters:
ParameterTypeDefaultDescription
contextContextRequired. Pass applicationContext to avoid memory leaks.
proxyProxy?nullOptional java.net.Proxy. When set, all HTTP requests (player JS fetch, config fetch) route through this proxy. Can be changed at runtime via ZemerCipher.proxy.
debugLoggingBooleanfalseEnables verbose Timber logging. Recommended value: BuildConfig.DEBUG.
A full initialization with all parameters:
ZemerCipher.initialize(
    context = applicationContext,
    proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("proxy.example.com", 8080)),
    debugLogging = BuildConfig.DEBUG
)

What Initialization Does

Calling ZemerCipher.initialize() performs the following steps in order:
  • CipherDeobfuscator.initialize(context) — Stores the application context so CipherDeobfuscator can create a CipherWebView on demand without holding an Activity reference.
  • ZemerCipher.proxy = proxy — Stores the optional proxy reference. The shared OkHttpClient is built lazily on first use, so setting proxy here takes effect before any network request.
  • ZemerCipher.debugLogging = debugLogging — Enables or disables verbose Timber logging across the library.
  • PlayerConfigStore.initialize(context) — Loads the bundled player_configs.json asset (the offline default) and overlays the last-good cached remote copy from <filesDir>/cipher_cache/configs_remote.json, if present and valid.
  • PlayerConfigStore.scheduleStartupRefresh() — Fires a background coroutine that checks the 6-hour TTL. If the remote config is stale, it fetches a fresh copy from the zemer-cipher GitHub repository.
  • PlayerDatesStore.initialize(context) — Loads player date metadata used for internal bookkeeping.

Prewarming the WebView

After initialization, the CipherWebView is created lazily on the first call to deobfuscateStreamUrl. To eliminate this cold-start delay before the first playback request, call CipherDeobfuscator.prewarm() from a background coroutine:
// In a ViewModel or use-case, after ZemerCipher.initialize():
viewModelScope.launch(Dispatchers.IO) {
    CipherDeobfuscator.prewarm()
}
Prewarm fetches the player JS (from cache if available, from the network otherwise), extracts the signature and n-transform functions, and loads them into a WebView — all under the same internal mutex as normal deobfuscation, so it cannot race a real request. On failure, the WebView is created lazily on first use instead; prewarm never throws to the caller.

Changing Proxy at Runtime

The ZemerCipher.proxy property can be changed at any point after initialization. The shared OkHttpClient is rebuilt on the next request when the proxy reference changes (checked by reference equality):
// Switch to a SOCKS proxy at runtime:
ZemerCipher.proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", 1080))

// Remove proxy:
ZemerCipher.proxy = null
In-flight HTTP requests complete on the old client. Only new requests after the assignment use the rebuilt client.
Do not call ZemerCipher.initialize() more than once. Multiple calls rebuild the config store and reset the startup refresh timer, causing a redundant player JS fetch and clearing any in-memory state accumulated since the first initialization.

Build docs developers (and LLMs) love