fix(lavalink): delegate YouTube deciphering to a yt-cipher sidecar - #41
Merged
Conversation
With the TV User-Agent fix in place, playback got past the playability check and died one step later: Client [TVHTML5] failed: Must find sig function from script: /s/player/2574220e/player_embed.vflset/el_GR/base.js The plugin's regex-based extraction cannot find the signature function in the current player script, so a valid OAuth token and an accepted client still yield no stream. Upstream's standing answer to this error is a remote cipher server rather than a plugin fix - the maintainer says so directly in #225 - and the README documents the `remoteCipher` block for exactly this. Adds yt-cipher to compose.yml on the private network, wires `plugins.youtube.remoteCipher` at it, and shares a `YT_CIPHER_PASSWORD` between the two. `OVERRIDE_PLAYER_VARIANT: IAS` is what makes it work: upstream reports only the IAS variant is consistently usable, and `player_embed` - the variant the plugin asks for - is the one that fails. Verified end to end against this repository's own compose.yml and application.yml, with a throwaway password and no OAuth token at all: six unrelated videos (kJQP7kiw5Fk, lp0drugrSM4, 8CFh_-qtzeg, YC-QnlIEbJU, xcFbyuzd9S8, CcNo07Xp8aQ) all reach TrackStart and run without a TrackExceptionEvent, where the same stack without the sidecar fails all six at the signature stage. The six-video set is deliberate: #226 documents that dQw4w9WgXcQ passes even on broken builds, so validating against one video proves nothing. No healthcheck on the service: the image is a compiled Deno binary on distroless, with no shell, curl, nc or deno CLI to run a check with - verified by inspecting the image. Lavalink logs a cipher error per track if it is unreachable, and it is pinned only by the moving :master tag because the project publishes no versioned releases; both are noted in the compose comments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
#40 worked: the TV client now clears the playability check that YouTube's 2026-08-18 User-Agent change was failing. Playback moved one step further and died there instead:
OAuth applied, client accepted, formats listed — and then the plugin's regex-based extraction cannot find the signature function in the current player script, so there is no playable URL. This is the last step before audio.
Fix
Upstream's standing answer to this error is a remote cipher server, not a plugin fix. The maintainer says so directly in #225 ("the consensus is to use a remote cipher server"), and the plugin README documents the
remoteCipherblock for exactly this.This adds yt-cipher to
compose.ymlon the private network, pointsplugins.youtube.remoteCipherat it, and shares aYT_CIPHER_PASSWORDbetween the two sides.OVERRIDE_PLAYER_VARIANT: IASis the part that matters, not just running the service: upstream reports only the IAS player variant works consistently, andplayer_embed— the variant the plugin asks for, and the one named in the error above — is precisely the one that fails.Verification
Run against this repository's own
compose.ymlandapplication.yml, with a throwaway password and no OAuth token at all:Using remote cipher server with URL "http://yt-cipher:8001", resolving the sidecar by service name over the private network.player_ias.vflset/.../base.js— the IAS variant, not the embed one.kJQP7kiw5Fk,lp0drugrSM4,8CFh_-qtzeg,YC-QnlIEbJU,xcFbyuzd9S8,CcNo07Xp8aQ— all reachTrackStartEventand run with noTrackExceptionEvent. The same stack without the sidecar fails all six at the signature stage.The six-video set is deliberate rather than decorative: #226 documents that
dQw4w9WgXcQpasses even on broken builds, so validating against a single video proves nothing.Notes
/app/server, usernonroot, with no shell,curl,ncordenoCLI to run a check with (verified by inspecting the image). If it is unreachable, Lavalink logs a cipher error per track.:masteris a moving tag because the project publishes no versioned releases. Pin a digest if reproducible deploys matter more than picking up its fixes automatically.YT_CIPHER_PASSWORDis a shared secret between two containers on a private network, not a Google credential. Empty disables auth on the sidecar, which is survivable only because it is never published.Deploying
git pull && docker compose up -dSet
YT_CIPHER_PASSWORDin.envfirst (openssl rand -hex 32).up -dalone brings up the new service and recreates Lavalink for the config change; the bot is untouched.🤖 Generated with Claude Code