Repository navigation
Play the preferred audio language of YouTube web manifests (plain URLs from Safari) - #602
Open
midzdotdev wants to merge 2 commits into
Open
midzdotdev wants to merge 2 commits into
midzdotdev wants to merge 2 commits into
Conversation
A YouTube web page (e.g. Safari's AirPlay button) sends POST /play with a plain https Content-Location of a HLS master playlist, which is passed unchanged to playbin. That playlist has no EXT-X-MEDIA AUDIO renditions: each EXT-X-STREAM-INF variant stream is listed once per audio language (the original and its AI-dubbed versions), marked only by the YouTube-private attributes YT-EXT-AUDIO-CONTENT-ID and YT-EXT-XTAGS. The player picks the highest BANDWIDTH, which is often a dubbed language, and the -lang / $LANGUAGE preferences are never consulted. For urls https://[*.]googlevideo.com/.../manifest/hls_variant/..., UxPlay now fetches the playlist, keeps only the variant streams of one language, and plays the filtered copy from a file in the user runtime directory (removed at exit). The language is chosen as for YouTube app playlists: a strict match with the -lang list, else a match with the system languages, else the original language (acont=original in YT-EXT-XTAGS). Variant streams with no language have the original audio: they are kept only with the original language, and are all that is kept when nothing else is marked as the original. If the original language has several content ids (e.g. an audio description), only the original one is kept. The playlist is fetched in the main thread (not in on_video_play, which runs in the httpd thread), with a 5 second limit. If anything fails, the playlist is malformed (a variant stream without a uri, relative uris, more than 64 languages) or there is nothing to filter, the url is played unchanged as before. Other plain urls are not touched. Co-Authored-By: Claude Opus 5.5 <[email protected]>
tests/test_yt_web_manifest.c checks select_yt_web_manifest_language() with
synthetic master playlists (language choice, exact output, malformed
input); it can also filter a playlist in a file. Fetching the playlist
(errors, timeout) is not covered. The target is not built by default and
is not installed:
cmake --build [build_dir] --target test_yt_web_manifest
Co-Authored-By: Claude Opus 5.5 <[email protected]>
midzdotdev
marked this pull request as ready for review
October 13, 2026 07:09
Collaborator
|
So you have found (or got Claude to find?) how to intercept the m3u8 hls playlists that gstreamer fetches directly. The simplified strategy we used with the playlists pushed by the client to UxPlay was to edit the playlists to filter out all languages (and subtitle languages) except the one we actually want to render. (one audio language, 0 or 1 subtitle language) This was because it was difficult to get gstreamer to choose the right one, so we don't offer it a choice. Are you trying to to something similar with the gstreamer playbin -downloaded playlists? |
fduncanh
force-pushed
the
master
branch
6 times, most recently
from
October 22, 2026 21:13
a0da077 to
517cbb1
Compare
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.
Addresses #601.
What I saw: a YouTube video with AI-dubbed audio, sent with the AirPlay button in Safari (macOS, youtube.com), plays its French dub.
-langand$LANGUAGEmake no difference.Cause: Safari hands over a plain
https://manifest.googlevideo.com/api/manifest/hls_variant/...URL, which goes straight to playbin. That master playlist has noEXT-X-MEDIAaudio renditions: each resolution is listed once per audio language as a separate variant stream, marked only byYT-EXT-AUDIO-CONTENT-IDandYT-EXT-XTAGS(acont=original/acont=dubbed-auto,lang=...). The player takes the highestBANDWIDTH, which is a dub by a few bits per second. Details and numbers are in #601.What this does
select_yt_web_manifest_language()inlib/airplay_video.cfilters such a master playlist down to the variant streams of one audio language. It reusesstrict_match_language()/match_language(), so the choice follows the app path: a strict match with the-langlist, else a match with the system languages, else the language markedacont=original. Variant streams with no language attributes are the original audio: they are kept with the original language, and are all that is kept when nothing is marked as the original. It returns NULL ("play unchanged") when there is nothing to choose, and for anything malformed (a variant without a URI line, relative URIs, more than 64 languages).https://[*.]googlevideo.com/.../manifest/hls_variant/...,uxplay.cppfetches the playlist (video_renderer_fetch_text(): source element into appsink, 5 s overall limit, 4 MiB cap), filters it, writes the copy to a 0600 file in the user runtime directory and gives playbin itsfile://URI. The file is removed at exit. The fetch runs in the main thread before the renderer is rebuilt, not inon_video_play(), which runs in the httpd thread.-lang.About #581 /
hlspi#581 adds a pad probe for master playlists that GStreamer fetches itself. That is a better place for this than a second fetch and a temporary file: the language filter could be one more call inside the probe. It is not in master, so this PR is self-contained against master. If
hlspiis going to land, I would rather redo the wiring on top of the probe and dropvideo_renderer_fetch_text()and the temp file; the filter function and its test would carry over as they are. That is why this is a draft: tell me which base you want.Tests
The second commit adds
tests/test_yt_web_manifest.c(30 cases on synthetic playlists: language choice, one exact-output comparison, malformed input) as a target that is not built by default and not installed. It is a separate commit, so it is easy to drop.Validation
en-USoriginal + 6 untagged);-lang frkeeps the 6fr-FR.GST_DEBUG=hlsdemux2:6and the duration in/playback-info: unpatched master fetches a dubbed variant with no option, bare-langand-lang en; the patched build fetches the original withLANGUAGE=en_GB:en, and the French dub with-lang fr.YouTube web manifest: available audio languages fr-FR,de-DE,en-US (original: en-US), selected: en-US, then normal playback.Not tested / known gaps
/playthat arrives while the first manifest is being fetched wins, as it should, but is played unfiltered.uxplay.1and the-htext are unchanged.🤖 Generated with Claude Code