Migrate instance ID storage from localStorage to sessionStorage

Switch instance ID storage from localStorage to sessionStorage in useWebRenderer composable to ensure better session management and prevent data persistence across browser sessions.

Also update the DirectOggFlacSink implementation to improve backpressure handling, streamline the OGG chaining logic, and add proper Safari range header support for the stream endpoint.
This commit is contained in:
2026-02-28 14:10:38 +01:00
parent ca8702fd7f
commit de74c7b431
5 changed files with 187 additions and 271 deletions

View File

@@ -1,15 +1,17 @@
//! Handler HTTP GET /api/webrenderer/{id}/stream
//!
//! Sert le flux FLAC d'une instance WebRenderer via DirectFlacSink.
//! Sert le flux OGG-FLAC d'une instance WebRenderer.
//!
//! Reconnectable : appelé à chaque Play, crée un nouveau pipe + encodeur.
//! Safari fait systématiquement une requête Range: bytes=0-1 avant de jouer.
//! On répond 206 avec 2 octets factices pour satisfaire la sonde,
//! puis la vraie requête (sans Range) reçoit le stream persistant.
use axum::{
body::Body,
extract::{Path, State},
http::{
StatusCode,
header::{CACHE_CONTROL, CONNECTION, CONTENT_TYPE},
HeaderMap, StatusCode,
header::{CACHE_CONTROL, CONNECTION, CONTENT_TYPE, CONTENT_RANGE, CONTENT_LENGTH, ACCEPT_RANGES},
},
response::{IntoResponse, Response},
};
@@ -20,21 +22,31 @@ use tracing::info;
use crate::registry::RendererRegistry;
/// GET /api/webrenderer/{id}/stream
///
/// Crée un nouveau pipe FLAC à chaque connexion (chaque Play).
/// Le flux reste ouvert jusqu'à ce que le client se déconnecte (Stop).
/// Les morceaux s'enchaînent en gapless dans le même flux.
///
/// Safari envoie un Range header (bytes=0-) et exige Accept-Ranges: bytes.
/// On répond 206 Partial Content si un Range header est présent, sinon 200.
pub async fn stream_handler(
State(registry): State<Arc<RendererRegistry>>,
Path(instance_id): Path<String>,
headers: HeaderMap,
) -> impl IntoResponse {
info!(instance_id = %instance_id, "FLAC stream client connecting");
let handle = match registry.get_flac_handle(&instance_id) {
Some(h) => h,
// Détecter la sonde Range: bytes=0-1 de Safari
if let Some(range) = headers.get("range") {
if range.as_bytes() == b"bytes=0-1" {
info!(instance_id = %instance_id, "Safari range probe — responding 206");
return Response::builder()
.status(StatusCode::PARTIAL_CONTENT)
.header(CONTENT_TYPE, "audio/ogg; codecs=flac")
.header(ACCEPT_RANGES, "bytes")
.header(CONTENT_RANGE, "bytes 0-1/*")
.header(CONTENT_LENGTH, "2")
.body(Body::from(vec![0u8, 0u8]))
.unwrap()
.into_response();
}
}
let stream = match registry.get_stream(&instance_id) {
Some(s) => s,
None => {
return (
StatusCode::NOT_FOUND,
@@ -44,18 +56,14 @@ pub async fn stream_handler(
}
};
let stream = handle.connect().await;
info!(instance_id = %instance_id, "FLAC stream started");
// Toujours 200 OK pour un flux live de taille inconnue.
// Les navigateurs (Safari inclus) acceptent 200 pour l'audio streaming.
// Un Content-Range invalide (bytes 0-*/*) ferait rejeter le flux.
Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, "audio/ogg; codecs=flac")
.header(CACHE_CONTROL, "no-store, no-transform")
.header(CONNECTION, "keep-alive")
.header(ACCEPT_RANGES, "bytes")
.header("X-Content-Type-Options", "nosniff")
.body(Body::from_stream(ReaderStream::new(stream)))
.unwrap()