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.

When YouTube rotates its player JS, the existing configs no longer match the new player hash. Adding a new entry to player_configs.json and pushing to the repository’s master branch fixes all deployed apps running Zemer Cipher within minutes — no APK release needed. Deployed apps fetch configs from the raw GitHub URL at runtime (6-hour TTL) and force-refresh the moment they encounter an unknown player hash.

When to Add a Config

The trigger for adding a new config entry is playback failure following a YouTube player rotation. Symptoms include:
  • HTTP 403 responses on stream URLs from WEB_REMIX or WEB clients
  • Audio or video quality degradation (streams cut off early or serve only partial content)
  • Timber log output: No hardcoded config for hash: <hash> — this is the most reliable indicator that a new player has been deployed and is not yet in the config table
Player rotations are frequent; each rotation has a unique 8-hex hash in the player JS URL (/s/player/{hash}/player_ias.vflset/).

Config Entry Format

Each entry in player_configs.json maps an 8-hex player hash to its deobfuscation parameters:
{
  "schemaVersion": 1,
  "players": {
    "<8-hex-hash>": {
      "sig": "<FuncName>(<int>,<int>,INPUT)",
      "nClass": "<2-8 char identifier>",
      "sts": <integer>,
      "aliases": ["<8-hex-md5-alias>"]
    }
  }
}
A concrete example from the bundled file:
"445213fb": { "sig": "mP(4,155,INPUT)", "nClass": "Yx", "sts": 20613, "aliases": ["d62bd338"] }
FieldDescriptionValidation
hash (key)8-hex player hash from the player JS URLMust be unique across all primary keys
sigSignature call expression. INPUT is substituted with the obfuscated signature at runtime.Must match ^[A-Za-z0-9$_]{1,8}\(\d+,\d+,INPUT\)$
nClassURL class name for the n-transform IIFE built from a local templateMust match ^[A-Za-z0-9$_]{1,8}$
stssignatureTimestamp integer sent in InnerTube /player requestsMust be a positive integer
aliasesOptional array of 8-hex MD5-of-first-10000-bytes fallback hashes for the same playerEach alias must be globally unique

Validating a New Entry

Before adding an entry, validate it against a live stream to confirm the CDN accepts the deciphered URL. From the zemer-app repository:
# In zemer-app repository:
node tests/validate-player-config.mjs <hash>
The script deciphers a real stream URL using the candidate config and verifies that the CDN returns HTTP 206 Partial Content. This is the only reliable ground truth — multiple (constantArg1, constantArg2) pairs can produce output that looks like a valid URL, but only the correct pair produces a URL the CDN accepts. The script prints a paste-ready JSON entry on success.

Adding an Entry

1

Identify the new player hash

Find the hash in Timber logs (No hardcoded config for hash: <hash>) or extract it from the YouTube iframe_api endpoint at https://www.youtube.com/iframe_api, which contains a path of the form /s/player/{hash}/player_ias.vflset/.
2

Run the validation script

Run node tests/validate-player-config.mjs <hash> in the zemer-app repository. This deciphers a live stream and confirms CDN acceptance with an HTTP 206 response. Copy the printed JSON entry.
3

Add the entry to player_configs.json

Open library/src/main/assets/player_configs.json and add the new entry inside the "players" object. Keep the file alphabetically or chronologically ordered by hash for readability.
4

Run unit tests

Run ./gradlew :library:testDebugUnitTest to catch duplicate primary hashes, duplicate aliases, or alias-equals-primary-hash collisions before pushing. A file rejected by the parser leaves all deployed apps on their previous config.
5

Push to master

Push to the master branch. Deployed apps performing a failure-triggered refresh resolve the unknown hash immediately; apps performing the routine startup refresh pick up the new entry within 6 hours.
6

Bump the submodule pointer in zemer-app

Update the zemer-cipher submodule pointer in zemer-app so the bundled APK default stays fresh and new installs work offline without waiting for a remote fetch.

Collision Rules

PlayerConfigParser is the validation boundary for all config data. It enforces strict uniqueness rules:
  • A duplicate primary hash (two entries with the same key) causes the entire file to be rejected.
  • A duplicate alias (two entries sharing an alias value) causes the entire file to be rejected.
  • An alias that equals another entry’s primary hash causes the entire file to be rejected.
When a file is rejected, deployed apps keep their last-good in-memory and on-disk table — they do not break. However, they also do not receive the new config entry until the collision is fixed and a corrected file is pushed to master. Fix collisions before pushing.

Schema Version

The schemaVersion field is currently 1. Older apps that receive a file with a higher schemaVersion automatically reject it and continue operating from their last-good config table — they do not break. Only bump schemaVersion for breaking shape changes (for example, renaming a required field or changing the type of an existing field). Adding a new optional field within the existing shape does not require a version bump.
Always run ./gradlew :library:testDebugUnitTest before pushing to master. A file with duplicate keys or aliases is rejected wholesale by PlayerConfigParser — all deployed apps that attempt a refresh while the bad file is live will be stuck on their previous config until a corrected file is pushed.

Build docs developers (and LLMs) love