Cette mise à jour implémente le streaming audio côté serveur pour WebRenderer, en remplaçant l'ancienne architecture basée sur WebSocket par une nouvelle approche utilisant des endpoints HTTP. Les modifications incluent l'ajout de dépendances audio, la création de nouveaux modules pour le pipeline audio, la gestion des registres et des flux, ainsi que la modification des handlers UPnP pour utiliser le nouveau pipeline. Le frontend a également été mis à jour pour utiliser un seul élément <audio> pointant vers le nouveau endpoint de streaming.
203 lines
7.7 KiB
Markdown
203 lines
7.7 KiB
Markdown
# WebRenderer : Streaming Audio Côté Serveur
|
|
|
|
## Problème actuel
|
|
|
|
Le webrenderer actuel délègue la lecture audio au navigateur : le serveur envoie une URL
|
|
de fichier via WebSocket (`SetUri`), et le navigateur charge cette URL dans un élément
|
|
`<audio>`. Cette approche a plusieurs limitations :
|
|
|
|
- Les URLs sont internes (IP locale + port) → inaccessibles depuis l'extérieur
|
|
- Les fichiers sur partage Samba ont des chemins locaux → jamais accessibles au navigateur
|
|
- La `base_url` doit être configurée statiquement → pas de solution propre local/externe
|
|
|
|
## Solution proposée : flux HTTP serveur
|
|
|
|
Le serveur génère un flux audio continu par instance de webrenderer, servi sur un endpoint
|
|
HTTP dédié. Le navigateur n'écoute que ce flux — une URL fixe, toujours accessible.
|
|
|
|
```
|
|
Avant : ControlPoint → SetAVTransportURI(url_interne) → WebSocket → Browser(<audio src=url_interne>)
|
|
Après : ControlPoint → SetAVTransportURI(url_interne) → Serveur(ouvre+stream) → Browser(<audio src=/api/webrenderer/{id}/stream>)
|
|
```
|
|
|
|
## Architecture cible
|
|
|
|
### Cycle de vie d'une instance
|
|
|
|
```
|
|
1. Navigateur ouvre la page
|
|
↓
|
|
2. POST /api/webrenderer/register {instance_id, user_agent}
|
|
↓
|
|
3. Serveur crée le device UPnP + pipeline audio
|
|
Annonce SSDP → ControlPoints découvrent le renderer
|
|
Répond : { stream_url }
|
|
↓
|
|
4. Navigateur ouvre GET /api/webrenderer/{id}/stream (flux FLAC)
|
|
↓
|
|
5. ControlPoint → SetAVTransportURI + Play → pipeline démarre
|
|
Navigateur écoute le flux FLAC en continu
|
|
SSE global existant → métadonnées et état vers l'interface
|
|
↓
|
|
6. Navigateur ferme la page → flux FLAC se coupe
|
|
Serveur détecte → SSDP byebye → pipeline stoppé
|
|
Device UPnP retiré
|
|
```
|
|
|
|
Le lecteur web est complètement invisible — l'interface est pilotée par le SSE global
|
|
existant du ControlPoint. Le WebSocket est supprimé. Pas de SSE dédié au webrenderer.
|
|
|
|
### Endpoints HTTP
|
|
|
|
```
|
|
POST /api/webrenderer/register
|
|
Body: { instance_id, user_agent }
|
|
Réponse: { stream_url }
|
|
|
|
GET /api/webrenderer/{id}/stream
|
|
Content-Type: audio/flac
|
|
Cache-Control: no-store, no-transform
|
|
[Flux FLAC continu — déconnexion = fin de session]
|
|
|
|
DELETE /api/webrenderer/{id}
|
|
Désenregistrement explicite (optionnel, fallback sur coupure du flux)
|
|
```
|
|
|
|
La `stream_url` est une URL relative (`/api/webrenderer/{id}/stream`) — le navigateur
|
|
la résout lui-même, toujours correcte en local et via proxy externe, sans reconstruction
|
|
depuis les headers `X-Forwarded-*`.
|
|
|
|
### Composants nécessaires
|
|
|
|
#### 1. Pipeline audio par instance
|
|
|
|
Chaque instance possède :
|
|
|
|
- Un **`StreamingFlacSink`** — infrastructure existante dans `pmoaudio-ext`
|
|
- Un **`StreamHandle`** — exposé via l'endpoint `/stream`
|
|
- Un canal de contrôle **`PipelineControl`** — alimenté par les actions UPnP
|
|
|
|
Le pipeline est créé au `POST /register` et détruit à la coupure du flux FLAC.
|
|
|
|
#### 2. Enregistrement et création du device UPnP
|
|
|
|
```
|
|
POST /api/webrenderer/register
|
|
→ créer DeviceInstance UPnP (même factory qu'aujourd'hui)
|
|
→ annoncer via SSDP (nouveau : aujourd'hui pas de SSDP pour le webrenderer)
|
|
→ créer StreamingFlacSink + pipeline
|
|
→ enregistrer dans le RendererRegistry
|
|
→ retourner stream_url
|
|
```
|
|
|
|
L'`instance_id` vient du `localStorage` du navigateur — stable entre les reloads,
|
|
garantit que le même renderer UPnP est retrouvé à la reconnexion.
|
|
|
|
#### 3. Modification de `SetAVTransportURI`
|
|
|
|
Au lieu d'envoyer l'URL au navigateur, le handler UPnP :
|
|
|
|
1. Reçoit l'URI source (fichier cache, Samba, URL externe...)
|
|
2. Envoie `PipelineControl::LoadUri(uri)` au pipeline de l'instance
|
|
3. Le pipeline ouvre la source côté serveur et alimente le `StreamingFlacSink`
|
|
4. Le navigateur reçoit un event SSE `state_changed: Transitioning` puis `Playing`
|
|
|
|
#### 4. Gestion des transitions (gapless)
|
|
|
|
Le `StreamingFlacSink` diffuse un flux FLAC continu. À la frontière de piste, le pipeline
|
|
enchaîne les sources sans interruption du flux HTTP.
|
|
|
|
`SetNextAVTransportURI` → `PipelineControl::LoadNextUri(uri)` → pré-chargé dans le pipeline
|
|
→ transition seamless, le navigateur ne recharge pas l'URL.
|
|
|
|
#### 5. Métadonnées et état
|
|
|
|
Tout passe par le SSE existant — titre, artiste, artwork, position, état de lecture.
|
|
Pas de nouveau mécanisme nécessaire.
|
|
|
|
#### 6. Seek
|
|
|
|
Flux HTTP live → pas de Range requests.
|
|
|
|
Pour les fichiers (non-live) :
|
|
- `PipelineControl::Seek(position_sec)` → pipeline repart depuis la nouvelle position
|
|
- Légère interruption du flux FLAC (rebuffering navigateur ~1s) — acceptable
|
|
|
|
#### 7. Sources supportées
|
|
|
|
Le pipeline réutilise `pmoaudio-ext` et `pmoflac`. La seule source actuellement
|
|
déclarée dans `pmoaudio-ext` fonctionne à partir d'une `pmoplaylist` — c'est le
|
|
modèle à suivre pour construire dans `source_loader.rs` une source ad-hoc capable
|
|
d'ouvrir des URIs arbitraires (URL HTTP externe, fichier local/Samba) qui ne passent
|
|
pas par le cache.
|
|
|
|
### État partagé par instance
|
|
|
|
```rust
|
|
pub struct WebRendererServerState {
|
|
pub playback_state: PlaybackState,
|
|
pub current_uri: Option<String>,
|
|
pub volume: u16,
|
|
pub mute: bool,
|
|
pub stream_handle: SharedStreamHandle, // Handle vers le flux FLAC
|
|
pub pipeline_tx: mpsc::Sender<PipelineControl>, // Contrôle du pipeline
|
|
}
|
|
|
|
pub enum PipelineControl {
|
|
LoadUri(String),
|
|
LoadNextUri(String),
|
|
Play,
|
|
Pause,
|
|
Stop,
|
|
Seek(f64),
|
|
SetVolume(u16),
|
|
}
|
|
```
|
|
|
|
## Fichiers à créer / modifier
|
|
|
|
### Nouveaux fichiers
|
|
|
|
| Fichier | Rôle |
|
|
|---------|------|
|
|
| `pmowebrenderer/src/stream.rs` | Handler HTTP du flux FLAC |
|
|
| `pmowebrenderer/src/pipeline.rs` | Pipeline audio serveur par instance |
|
|
| `pmowebrenderer/src/source_loader.rs` | Ouverture des sources (cache, HTTP, fichier local) |
|
|
| `pmowebrenderer/src/register.rs` | Handler `POST /register` + `DELETE /{id}` |
|
|
|
|
### Fichiers à modifier
|
|
|
|
| Fichier | Modification |
|
|
|---------|--------------|
|
|
| `pmowebrenderer/src/state.rs` | Ajouter `stream_handle` et `pipeline_tx` |
|
|
| `pmowebrenderer/src/handlers.rs` | `set_uri_handler` → `PipelineControl::LoadUri` |
|
|
| `pmowebrenderer/src/websocket.rs` | **Supprimer** — remplacé par `register.rs` |
|
|
| `pmowebrenderer/src/config.rs` | Enregistrer les nouvelles routes, supprimer WS |
|
|
| `pmowebrenderer/src/messages.rs` | **Supprimer** — plus de WebSocket |
|
|
| `pmowebrenderer/src/session.rs` | Adapter : session liée au flux FLAC, pas au WS |
|
|
| `pmoapp/webapp/src/` | Remplacer WS par `POST /register` + `<audio src=stream_url>` |
|
|
|
|
## Infrastructure existante réutilisable
|
|
|
|
Tout le travail difficile est déjà fait :
|
|
|
|
- **`StreamingFlacSink`** (`pmoaudio-ext/src/sinks/streaming_flac_sink.rs`) — broadcast multi-clients, gestion backpressure, ICY metadata
|
|
- **`timed_broadcast`** (`pmoaudio-ext/src/sinks/timed_broadcast.rs`) — diffusion multi-clients avec pacing
|
|
- **`pmoflac`** — décodage/encodage FLAC temps réel
|
|
- Pattern HTTP streaming (`pmomediaserver/src/paradise_streaming.rs`) — exemple complet à suivre
|
|
|
|
## Questions ouvertes
|
|
|
|
1. **Volume côté serveur ou navigateur** ? Actuellement le navigateur gère le volume.
|
|
Côté serveur, on pourrait appliquer un gain DSP dans le pipeline.
|
|
|
|
3. **Plusieurs onglets simultanés** : chaque onglet a son propre pipeline et flux ?
|
|
Ou partage d'un flux broadcast si même contenu ? (Complexe, probablement un pipeline par instance.)
|
|
|
|
4. **Reconnexion** : si le navigateur se reconnecte (reload), le pipeline continue de
|
|
tourner ? Ou on le stoppe et recrée ? La `StreamHandle` permet plusieurs souscripteurs,
|
|
donc la reconnexion peut se faire sans interruption.
|
|
|
|
5. **Latence** : le pipeline serveur introduit une latence (buffering + encodage).
|
|
À mesurer et potentiellement configurer via `max_lead_seconds`.
|