- YouTube antwortet auf Kanal-Feeds derzeit bei etwa zwei von drei Abrufen grundlos mit 404 (Kanäle zeigten „lädt nicht: HTTP 404“): ein YouTube-Feed wird bis zu 12-mal im Abstand von 0,25 s gefragt. Tests dazu. - Erscheinungsbild „Automatisch“: in der Android-App folgt die WebView nicht dem System, sondern dem (dunklen) App-Theme – die Seite fragt dort DioraNative.systemScheme() und hört __dioraSystemScheme bei einem Wechsel. sw.js: diora-v129. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.7 KiB
YouTube channels as podcasts
A YouTube channel (or playlist) can be subscribed to like a podcast. Only its sound is played, and the server never downloads or streams a video or its sound: each app finds the audio stream itself.
Server (diora-web, podcasts/youtube.py)
-
POST /podcasts/feeds/add/takes a YouTube address asrss_url: a channel (/@name,/channel/UC…,/c/…,/user/…), a playlist (?list=…), a video (subscribes its channel) or the feed itself. The server reads the channel page once (feed address, name, picture) and stores the feedhttps://www.youtube.com/feeds/videos.xml?channel_id=UC…(or?playlist_id=…).GET /podcasts/search/?q=<YouTube address>answers with that one channel. -
From then on the feed is refreshed like any other — except that YouTube answers its channel feeds with a 404 (or 5xx) now and then for no reason (October 2026: two requests in three), so a YouTube feed is asked up to 12 times, 0.25 s apart, before its refresh counts as failed (
YOUTUBE_FEED_TRIESinpodcasts/views.py). It carries the latest 15 videos. Shorts are left out. There is no duration (duration_seconds0) until a player knows it:POST /podcasts/progress/save/takes an optionalduration_seconds(number, 1 s … 7 days), stored only while the episode has none. -
An episode from such a feed has
audio_url=https://www.youtube.com/watch?v=<11-character id>. This is the marker: a client recognises a YouTube episode by this prefix;guid=yt:video:<id>,artwork_url= the video's thumbnail,description= the video description as HTML (links, line breaks; timestamps like00:00 Introwork as usual).
-
Nothing on the server touches the sound of such an episode: no ID3 chapters, no transcripts, and
/podcasts/episodes/<id>/silences/answers{"ready": true, "spans": [], "error": "youtube"}(skip silence is up to the app, as on Android anyway). -
POST /podcasts/feeds/<id>/move/ {"rss_url": …}gives an existing subscription a new address and keeps everything (settings, episodes, places, played and favourite marks, queue). Moved to a YouTube address, every episode whose file is named after its video (…/<11-character id>.mp3, as podsync-like servers name them) becomes that video (guidyt:video:<id>,audio_urlitswatch?v=page), so the channel's feed finds it again instead of adding it twice. Used to move channels that came through such a server (it downloaded the sound on the server) to the channel itself.
Apps
A client that sees a watch?v= address resolves it itself, on the device,
to an audio-only stream (no video track), and plays or downloads that.
| Client | How |
|---|---|
| Android | NewPipeExtractor: the original audio track, progressive, opus before m4a, ≤ 160 kbit/s; resolved when the stream is opened and once more on a 403; downloads in 10 MB ranges (in one go YouTube throttles to ~50 KB/s) |
| Desktop | yt-dlp in the main process (-f bestaudio), Electron itself as its JavaScript runtime; yt-dlp is fetched from its GitHub release on first use and kept up to date (src/main/youtube.js) |
| Browser | can't (YouTube doesn't let a web page read its pages, and a stream address only works for the device that asked for it): the episode is listed but says it plays in the app |
Stream addresses are bound to the device's IP address and run out after a few hours: resolve shortly before playing, and again when the player reports an error (podcasts-web.js reloads after a media error anyway).
Progress, queue, played state etc. work as for any episode: they belong to the episode on the server, not to the stream address.