diff --git a/Blackboard/ToDiscuss/play_from_url.md b/Blackboard/ToDiscuss/play_from_url.md new file mode 100644 index 00000000..d6dd2f6a --- /dev/null +++ b/Blackboard/ToDiscuss/play_from_url.md @@ -0,0 +1,323 @@ +# Play From URL — source `UrlSource` + +Inspiré par BubbleUPnP : recevoir n'importe quelle URL (lien de partage Qobuz, +flux radio, playlist M3U, page web contenant de l'audio…) et la jouer +immédiatement sur le renderer actif. + +--- + +## Vision architecturale + +`UrlSource` est une **source musicale ordinaire** qui implémente `MusicSource`, +exactement comme Qobuz, RadioFrance ou RadioParadise. Elle apparaît dans le +drawer gauche au même titre que les autres sources du serveur PMO. + +Sa particularité : sa **barre de recherche est le champ URL**. L'utilisateur +colle ou tape une URL, appuie sur Entrée — la source résout l'URL et retourne +le contenu jouable comme un `BrowseResult` normal. + +``` +Drawer gauche + └─ PMO Music Server + ├─ Qobuz + ├─ Radio Paradise + ├─ Radio France + └─ URL / Partage ← nouvelle source + └─ [barre de recherche = champ URL] + └─ coller une URL + Entrée + └─ résolution → BrowseResult → queue + play +``` + +Avantages de cette approche : +- **Zéro nouvelle UI** : la barre de recherche existante du drawer gère tout +- **Zéro nouvel endpoint REST** : browse/search existants suffisent +- **Zéro cas particulier** dans le drawer ou le content directory handler +- `browse()` du container racine peut afficher un **historique** des URLs jouées + +--- + +## Trait `UrlHandler` (dans `pmosource`) + +Chaque source (et un handler générique) peut revendiquer les URLs qu'elle sait +résoudre. + +```rust +pub enum ResolvedContent { + /// Référence à un container d'une source existante + /// → la UrlSource délègue le browse à cette source + SourceContainer { + source_id: String, // "qobuz", "radiofrance", … + container_id: String, // "qobuz:album:l46fxnqnxp5vs" + }, + /// Liste de tracks (M3U, PLS, XSPF, RSS/podcast…) + Playlist { + title: Option, + items: Vec, + }, + /// Track unique ou flux continu + Track { + uri: String, + metadata: TrackMetadata, + }, + Stream { + uri: String, + metadata: StreamMetadata, + }, +} + +#[async_trait] +pub trait UrlHandler: Send + Sync { + fn name(&self) -> &str; + /// Priorité : plus grand = essayé en premier (défaut 50) + fn priority(&self) -> u8 { 50 } + /// Test rapide sans I/O (regex sur l'URL) + fn can_handle(&self, url: &str) -> bool; + /// Résolution effective (I/O autorisé) + async fn resolve(&self, url: &str) -> Result; +} +``` + +--- + +## Handlers spécifiques aux sources + +### `QobuzUrlHandler` (dans `pmoqobuz`) — priorité 90 + +URLs reconnues. Les IDs sont potentiellement alphanumériques pour tous les +types (pas seulement les albums) : + +| Forme d'URL | Exemple | +|---|---| +| `open.qobuz.com/album/` | `https://open.qobuz.com/album/l46fxnqnxp5vs` | +| `play.qobuz.com/album/` | `https://play.qobuz.com/album/l46fxnqnxp5vs` | +| `open.qobuz.com/track/` | `https://open.qobuz.com/track/48471123` | +| `open.qobuz.com/playlist/` | `https://open.qobuz.com/playlist/63246908` | +| `open.qobuz.com/artist/` | `https://open.qobuz.com/artist/125709` | + +Regex d'extraction : `[a-zA-Z0-9]+` pour tous les types sans exception. + +Résolution sans appel API — l'ID est directement mappé sur un container_id : + +``` +open.qobuz.com/album/l46fxnqnxp5vs + → ResolvedContent::SourceContainer { + source_id: "qobuz", + container_id: "qobuz:album:l46fxnqnxp5vs", + } +``` + +### `RadioFranceUrlHandler` (dans `pmoradiofrance`) — priorité 90 + +URLs `radiofrance.fr/*`, `francemusique.fr/*`, `fip.fr/*`, etc. +→ `ResolvedContent::Stream` + +### `RadioParadiseUrlHandler` (dans `pmoparadise`) — priorité 90 + +URLs `radioparadise.com/*` +→ `ResolvedContent::Stream` + +--- + +## Handler générique (dans `pmourlresolver`, nouveau crate) — priorité 10 + +Dernier recours. Pipeline interne : + +``` +URL + │ + ├─ Garde-fou SSRF : rejeter si IP résolue est privée/locale + │ (RFC-1918 : 10/8, 172.16/12, 192.168/16 ; loopback : 127/8, ::1 ; + │ link-local : 169.254/16, fe80::/10) + │ → aucun cas d'usage légitime pour une URL interne ici + │ + ├─ HEAD request → Content-Type audio/* ? + │ └─ → ResolvedContent::Stream / Track (URI directe) + │ + ├─ Extension ou Content-Type playlist ? + │ ├─ .m3u / .m3u8 / application/vnd.apple.mpegurl → parse M3U + │ ├─ .pls / audio/x-scpls → parse PLS + │ └─ .xspf / application/xspf+xml → parse XSPF + │ + ├─ application/rss+xml / application/xml ? + │ └─ → parse RSS, extraire les audio → Playlist + │ + └─ text/html ? + └─ GET + parse HTML + ├─