3.6 KiB
Streaming de audio
Objetivo
Reproducir el audio sin que los bytes pasen por la WebApi, pero con control de acceso. La WebApi solo emite un permiso corto; el reverse proxy sirve los bytes desde SeaweedFS.
Formato: fichero único AAC (.m4a) por HTTP range
El audio se transcodifica a un único audio.m4a (AAC, contenedor MP4 con +faststart para que el átomo moov quede al inicio y el seek por range funcione), dentro de la carpeta Tracks/ProcessedAudios/{AudioFolderName}/. No hay manifest ni segmentos: el reproductor descarga el fichero por HTTP range requests (modelo tipo Spotify para VOD musical).
Multi-bitrate no está implementado hoy; el camino para añadirlo es transcodificar varias calidades (audio_96.m4a, …) y que el cliente elija una al iniciar (sin ABR fluido a mitad de pista, igual que Spotify).
El acceso: stream-ticket de prefijo
Se autoriza el prefijo de la carpeta de la pista con un stream-ticket de corta vida, y el proxy lo valida en cada petición. Aunque hoy sea un único objeto, el ticket sigue acotado al prefijo Tracks/ProcessedAudios/<folder>/ (cubre el fichero actual y cualquier calidad futura).
Flujo
GET /tracks/{id}/stream(autenticado) → la WebApi autoriza y devuelve:El ticket lleva el claim{ "manifestUrl": "http://<gateway>/media/Tracks/ProcessedAudios/<folder>/audio.m4a", "ticket": "<JWT RS256>", "expiresInSeconds": 3600 }prefix = "Tracks/ProcessedAudios/<folder>/".- El reproductor abre la URL del audio añadiéndole
?t=<ticket>(o la cabeceraX-Stream-Ticket). - El StreamingGateway (YARP) valida el ticket y reenvía al filer; los bytes van SeaweedFS → cliente (con range/seek).
El ticket (RS256)
- Lo firma la WebApi con clave privada (
StreamTicketService); el gateway valida con la pública (TicketValidator). Así el gateway no puede emitir tickets, solo verificarlos. - Claims:
sub,prefix,aud = media-gateway,iss = musify-webapi,exp(TTL ~1h).
Por qué RS256 (asimétrica): separa responsabilidades — emisor (WebApi) y verificador (gateway) no comparten secreto; el gateway, aunque se comprometa, no puede crear permisos.
El gateway (validación + proxy)
TicketValidationMiddleware en cada petición a /media/**:
- Lee
?t=, valida firma/aud/iss/exp. - Comprueba que la clave pedida empieza por el
prefixdel ticket (StartsWith). Si no → 403. - Quita el
?t=y deja pasar a YARP, que reescribe/media/{key}→/buckets/webapi-storage/{key}del filer.
- Sin ticket → 401; fuera de prefijo → 403; ok → 200 (y 206 en range requests, así que el seek funciona).
- Pasa
Range/Accept-Rangesy expone esos headers por CORS.
¿Un ticket por canción?
Sí. Cada ticket está acotado a una carpeta (una pista). Para reproducir otra, se pide otro ticket. Es barato (la WebApi solo firma un JWT) y es lo más seguro: cada permiso abre solo lo que vas a reproducir.
- Si algún día molesta (muchísimos cambios de pista), el mismo mecanismo del claim
prefixpermite ampliar el alcance: a nivel de playlist o de toda la biblioteca (Tracks/ProcessedAudios/). Trade-off seguridad ↔ comodidad. Por defecto, por canción.
Pendiente / notas de seguridad
- La política de quién puede pedir el ticket es hoy "cualquier autenticado" (TODO: restringir con
UserHasTrack/visibilidad). - El check de prefijo es
StartsWithliteral; conviene endurecer rechazando claves con..(path traversal), aunque navegadores y ASP.NET normalizan... - El ticket viaja en la query (
?t=); el gateway lo elimina antes de reenviar (no llega a los logs del filer).