# 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 as `rss_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 feed `https://www.youtube.com/feeds/videos.xml?channel_id=UC…` (or `?playlist_id=…`). `GET /podcasts/search/?q=` answers with that one channel. - From then on the feed is refreshed like any other. It carries the latest 15 videos. **Shorts are left out.** There is no duration (`duration_seconds` 0) until a player knows it: `POST /podcasts/progress/save/` takes an optional `duration_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:`, `artwork_url` = the video's thumbnail, `description` = the video description as HTML (links, line breaks; timestamps like `00:00 Intro` work as usual). - Nothing on the server touches the sound of such an episode: no ID3 chapters, no transcripts, and `/podcasts/episodes//silences/` answers `{"ready": true, "spans": [], "error": "youtube"}` (skip silence is up to the app, as on Android anyway). ## 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.