Publicar en almacenamiento de objetos (S3, Azure Blob)
Available in: UI · API
Usa esta how-to para enviar la salida segmentada de un Live stream a almacenamiento de objetos en lugar de a un ingest de CDN. Lo cubren dos providers: S3 para cualquier bucket compatible S3 y Azure Blob Storage para un container de Azure. Ambos son Destinations normales — no hay formulario aparte ni entidad extra que crear.
Cuándo usar esto
Cuando quieres ser dueño del origen. El bucket guarda los manifests y segmentos que produce el Encoder, un CDN por delante sirve a la audiencia, y la factura de almacenamiento sustituye al contrato de ingest del CDN. Es también el camino más corto hacia un archivo que un reproductor puede leer directamente, y hacia un origen self-hosted en las instalaciones del cliente.
S3 no es específico de AWS. La misma superficie de subida firmada funciona contra AWS S3, MinIO, Ceph RGW, Wasabi, Backblaze B2, DigitalOcean Spaces y Cloudflare R2, incluidos endpoints en un puerto no estándar.
Prerrequisitos
- Un bucket o container ya creado. C21 Live Control no lo crea. Para S3, anota el endpoint regional, el nombre del bucket y el prefijo de clave bajo el que quieres que viva la salida; para Azure, el endpoint de la cuenta y el container.
- Credenciales acotadas a ese prefijo. Para S3, un Access Key ID y un Secret Access Key de una identidad que pueda escribir y borrar bajo el prefijo. Para Azure, un token SAS de container (
sr=c) con permiso de creación y escritura. - Un CDN por delante del bucket — recomendado para cualquier cosa que vaya a ver una audiencia. Su URL pública es la que va en Playback URL.
- Un Live stream configurado cuyo Encoding produzca salida HLS / TS, DASH o CMAF.
- Rol necesario. System Administrator para registrar el Destination; Operator (o System Administrator) para enlazarlo a un Live stream y emitir.
Restricciones a tener en cuenta
- Solo formatos segmentados. Los Destinations de almacenamiento de objetos aceptan
HLS / TS,DASHyCMAF. El provider no se ofrece en RTMP, Enhanced RTMP, SRT, STREAM, SDIOUT ni Record. - En S3 no hay campo de región, por diseño. La región de firma se deriva del endpoint y se corrige automáticamente — consulta Región (S3).
- No se soportan credenciales temporales STS. Usa claves de larga duración.
- No se soportan claves SSE-KMS por objeto. El cifrado por defecto a nivel de bucket (SSE-S3 o SSE-KMS) se aplica de forma transparente y no requiere nada aquí.
- La storage class es la del bucket. Usa una regla de ciclo de vida para transicionar los objetos a una clase más barata.
- El Encoder no purga Azure Blob Storage. Los segmentos antiguos permanecen en el container hasta que una regla de ciclo de vida de Azure los expire.
- DASH y CMAF dejan huérfanos los objetos de una sesión en cada reinicio del Encoder. Sus nombres de segmento llevan un timestamp de sesión, así que un reinicio empieza una sesión de nombres nueva y los objetos de la anterior se quedan atrás — nada en el producto los sigue después. Una regla de ciclo de vida sobre el prefijo es obligatoria, no opcional. HLS no se ve afectado: sus segmentos son un contador continuo bajo una ruta fija, así que la ventana sigue rodando entre reinicios.
Vía UI
Crear el Destination
Navega: Destinations → Add destination. Rellena Name, elige el Type (HLS / TS, DASH o CMAF) y pon Provider en S3 o Azure Blob Storage. El resto del formulario se reetiqueta según el provider.
Para S3:
| Etiqueta UI | Campo API | Valor |
|---|---|---|
| Endpoint and bucket | settings_common.urls.primary_server | Endpoint, bucket y prefijo de clave en una sola URL, por ejemplo https://s3.eu-west-1.amazonaws.com/my-bucket/live. |
| Stream name | settings_common.stream | Se añade al prefijo — pasa a ser el siguiente segmento de la clave. |
| Access Key ID | settings_common.username | El identificador de la clave de acceso. |
| Secret Access Key | settings_common.password | El secreto correspondiente. Se almacena cifrado; las lecturas devuelven el sentinel. |
| Playback URL | settings_common.urls.player | La URL del CDN que deben usar los reproductores. |
Para Azure Blob Storage:
| Etiqueta UI | Campo API | Valor |
|---|---|---|
| Primary server | settings_common.urls.primary_server | https://<account>.blob.core.windows.net/<container>/<prefix>. |
| Stream name | settings_common.stream | Se añade al prefijo, igual que arriba. |
| SAS token | settings_common.password | El SAS de container. Con o sin el ? inicial — se aceptan ambos. No hay usuario: el SAS es toda la autorización. |
| Playback URL | settings_common.urls.player | La URL del CDN que deben usar los reproductores. |
Acertar con la URL
Los dos estilos de direccionamiento de S3 se aceptan y no hace falta nada para distinguirlos:
path-style https://s3.eu-west-1.amazonaws.com/my-bucket/live
virtual-hosted-style https://my-bucket.s3.eu-west-1.amazonaws.com/live
self-hosted https://minio.example.com:9000/my-bucket/live
Con Stream name channel-1, la primera forma escribe my-bucket/live/channel-1/index.m3u8 y sus segmentos al lado.
Usa un endpoint regional. El global s3.amazonaws.com responde con una redirección a la región propia del bucket, y una petición redirigida ya no cuadra con su firma. El editor avisa de esto bajo el campo, y también de un endpoint que no sea https o que no lleve bucket en la ruta. Los avisos no bloquean el guardado.
Enlazar el Destination a un Live stream
Abre el Live stream de destino en el editor de Live streams y añade el nuevo Destination bajo Destinations — directamente o a través de un Destination group. Guarda.
Arrancar el Live stream
Arranca la emisión desde On air. El Destination pasa a activo en el indicador por Destination de la fila on-air, y los objetos empiezan a aparecer bajo el prefijo.
Vía API
Todas las llamadas usan la cabecera bearer estándar Authorization: Bearer <YOUR_API_TOKEN>.
| Acción | Método + path | operationId |
|---|---|---|
| Listar el catálogo de providers | GET /c21apiv2/publishing/providers | getPublishingProviders |
| Crear un Destination | POST /c21apiv2/publishings | addPublishing |
| Actualizar un Destination | PUT /c21apiv2/publishings/{publishingId} | updatePublishing |
curl -X POST "https://<your-host>/c21apiv2/publishings" \
-H "Authorization: Bearer <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Origin bucket — Channel 1",
"type": "HLS / TS",
"settings_common": {
"provider": "S3",
"stream": "channel-1",
"username": "<Access Key ID>",
"password": "<Secret Access Key>",
"urls": {
"primary_server": "https://s3.eu-west-1.amazonaws.com/my-bucket/live",
"player": "https://cdn.example.com/live/channel-1/index.m3u8"
}
}
}'
La variante Azure es la misma llamada con "provider": "Azure Blob Storage", la URL del container en primary_server, el token SAS en password y sin username.
Las credenciales son write-only. Toda ruta de lectura devuelve el sentinel •••••••• cuando hay un valor almacenado y una cadena vacía cuando no lo hay; reenviar el sentinel en una escritura preserva el valor almacenado, así que un PUT que solo cambia el stream name no necesita tener el secreto a mano.
Consulta API → Overview para el envelope estándar y Paginación y errores para el manejo de errores.
Región (S3)
SigV4 ata una firma a una región, y el Encoder la deduce en vez de pedir algo que el propio endpoint puede declarar. En este orden:
- La respuesta del propio endpoint. Un sondeo sin firmar sobre el bucket devuelve la región junto con su denegación, así que esto resuelve correctamente incluso con credenciales que solo puedan escribir objetos.
- El hostname, para los proveedores que la codifican en una etiqueta —
s3.<region>.amazonaws.com,s3.<region>.wasabisys.com,<region>.digitaloceanspaces.comy*.r2.cloudflarestorage.com(que firma comoauto). us-east-1, que es lo que acepta una implementación S3 sin región configurada.- Corrección. Si el almacenamiento rechaza la petición por cabecera de autorización malformada, su respuesta nombra la región que espera; el Encoder la adopta, registra la corrección y vuelve a firmar. Una región mal configurada cuesta por tanto una petición, no una caída.
La región en uso se reporta en el log del Encoder cuando arranca la publicación.
Preparar el bucket
Política IAM (AWS S3)
Dale al Destination su propia identidad, con nada más que los verbos que usa y acotada al prefijo que escribe. No reutilices una clave administrativa.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublishSegments",
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::my-bucket/live/*"
},
{
"Sid": "RegionDiscovery",
"Effect": "Allow",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}
En la práctica s3:DeleteObject no es opcional: sin él la ventana en directo no se purga nunca y el bucket crece mientras el canal siga emitiendo. Un borrado rechazado se reporta en el log del Encoder.
Acceso público
No se envía ninguna cabecera de ACL, así que los objetos son privados y cómo se sirven es una decisión del bucket:
- Recomendado: mantener el bucket privado y poner CloudFront por delante con Origin Access Control.
- Como alternativa, una política de bucket que conceda
s3:GetObjectsobre el prefijo.
Los buckets creados desde abril de 2023 tienen las ACL deshabilitadas, así que una ACL public-read sería rechazada de plano — por eso no se envía ninguna.
CORS
La reproducción en navegador necesita CORS en el bucket, o en el CDN. Una política mínima permite GET y HEAD desde el origen del reproductor.
Regla de ciclo de vida
La ventana en directo la mantiene el propio Encoder: según cada segmento sale de la ventana, se borra del bucket. Eso mantiene acotado un canal en marcha — medido en un canal de referencia en 81 objetos .ts estables, planos en el tiempo.
Aun así, una regla de ciclo de vida sobre el prefijo es obligatoria para DASH y CMAF, cuyos nombres de segmento por sesión dejan huérfanos los objetos de la sesión anterior en cada reinicio, y para Azure Blob Storage, que el Encoder no purga nunca. Expira el prefijo a los pocos días; además sale más barato que un borrado por objeto.
Qué lleva cada objeto
El almacenamiento de objetos conserva exactamente lo que declaró la subida y lo sirve así durante toda la vida del objeto, de modo que el tipo de medio y la caducidad de caché se fijan en el momento de subir:
| Objeto | Content-Type | Cache-Control |
|---|---|---|
.m3u8 | application/vnd.apple.mpegurl | no-cache, max-age=1 |
.mpd | application/dash+xml | no-cache, max-age=1 |
.ts | video/MP2T | public, max-age=31536000, immutable |
.m4s, .mp4, .cmfv | video/mp4 | public, max-age=31536000, immutable |
.cmfa | audio/mp4 | public, max-age=31536000, immutable |
.vtt | text/vtt | public, max-age=31536000, immutable |
Los manifests se reescriben en cada segmento y no deben cachearse. Los segmentos y los init segments son inmutables — sus nombres llevan el número de segmento o el timestamp de sesión — así que esa caducidad larga es lo que hace asequible la combinación bucket más CDN.
Coste
Cada segmento de cada rendition es una subida, y cada segmento que sale de la ventana es un borrado. Con segmentos de 2 s y cinco renditions eso son aproximadamente 9.000 subidas por hora y Destination, escalando linealmente con el número de renditions. En un hyperscaler son céntimos por hora, pero son recurrentes — dimensiónalo antes de apuntar un canal 24/7 a un bucket. Segmentos más largos lo reducen proporcionalmente.
Verificar
- Los objetos aparecen bajo
<prefijo>/<stream name>/en una o dos duraciones de segmento desde el arranque. - El número de objetos se estabiliza en vez de crecer sin límite: en un canal de referencia, 81 objetos
.tsen HLS y 351 por rendition en DASH, planos en el tiempo. - El manifest es accesible por la URL de CDN guardada en Playback URL, y un reproductor abierto sobre esa URL reproduce el live edge.
- Parar el Live stream detiene las subidas; los objetos ya escritos permanecen hasta que la regla de ciclo de vida los expire.
Resolución de problemas
El código de error del propio proveedor de almacenamiento se refleja en el log del Encoder, en vez de un estado HTTP a secas.
| Síntoma | Causa |
|---|---|
403 SignatureDoesNotMatch | Secret Access Key incorrecto. |
403 AccessDenied | La clave es válida; la política no permite escribir en ese prefijo. |
404 NoSuchBucket | Nombre de bucket incorrecto, o a la ruta de la URL le falta el segmento del bucket. |
| Una corrección de región registrada | Informativo: la región de firma se corrigió automáticamente en la primera petición. |
| El bucket crece y se registran fallos de borrado | A la política le falta s3:DeleteObject. |
Azure: todas las subidas devuelven 404 ResourceNotFound | El SAS llegó truncado, o tiene alcance de blob en vez de container (sr=c). |
| Los objetos llegan pero la reproducción falla | Revisa CORS en el bucket o en el CDN, y que Playback URL apunte al CDN. |
| La reproducción sirve un manifest obsoleto | Una caché por delante del bucket está ignorando el Cache-Control del manifest. |
Un SAS de container de Azure llega a unos 160 caracteres y su firma es lo último que lleva, así que cualquier truncamiento por el camino elimina en silencio justo la parte que autoriza la petición — y todas las subidas responden 404 ResourceNotFound. Pégalo entero.
FAQ
us-east-1, y si el almacenamiento rechaza la firma nombrando otra región, la adopta y vuelve a firmar. Una suposición errónea cuesta una petición.Real Time streaming hacia Dolby Millicast
Emite con latencia extremo a extremo inferior al segundo — Real time encoding en el Live stream, contribución RTMP o Enhanced RTMP y distribución WebRTC de Dolby Millicast.
Proteger un Live stream con multi-DRM
Registra un DRM provider y asócialo a un Publishing — desde la UI, desde la API o ambas.