> ## Content Index
> Fetch the complete content index at: https://blog.devutilites.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Cloudflare Stream a fondo: sube, codifica y reproduce vídeo sin montar nada
- URL: https://blog.devutilites.com/cloudflare-stream-a-fondo/
- Published: 2026-10-03T03:14:00.000Z
- Updated: 2026-10-03T03:14:00.000Z
- Description: Qué es Cloudflare Stream y cómo funciona: subida directa desde el navegador, codificación automática, reproductor y HLS/DASH, vídeos privados con tokens firmados y directos por RTMPS o SRT. Precios por minutos y un Worker con el binding de Stream probado en local.
- Author: Luis Collantes
- Tags: Cloudflare, Backend

Servir vídeo bien es más difícil de lo que parece. Un MP4 subido tal cual pesa demasiado para un móvil con mala cobertura y demasiado poco para una tele 4K; lo correcto es **codificarlo** en varias calidades, trocearlo en segmentos y servirlo con **streaming adaptativo** (HLS o DASH) para que el reproductor cambie de calidad según la conexión. Montarlo tú supone FFmpeg, colas de trabajo, almacenamiento, una CDN y un reproductor.

**Cloudflare Stream** lo hace todo con una API: subes el vídeo (o dejas que tus usuarios lo suban directamente), Stream lo codifica, lo guarda y lo sirve con su reproductor o con tus propias URL HLS/DASH. También sirve para **directos**. Y se paga solo por dos cosas: minutos guardados y minutos vistos.

En este artículo vemos cómo funciona, cómo se factura, cómo hacer privados los vídeos y un Worker que importa vídeos y genera enlaces firmados con el binding de Stream, probado en el simulador local de Wrangler.

🧪

**El Worker de este artículo se ha ejecutado** el 3 de octubre de 2026 con **Wrangler 4.147.0** y **Node.js 24.21.0** contra el **simulador local de Stream**, que admite parte de la API: importar desde URL, listar y generar tokens (de mentira). La subida directa no está soportada en local. **La codificación, la reproducción, los tokens reales y los directos no los he probado**: necesitan una cuenta con Stream. Precios y límites comprobados en la documentación oficial ese mismo día.

## Cómo funciona

![Esquema de Cloudflare Stream: el vídeo entra por subida directa desde el navegador, por URL o por la API; Stream lo codifica en varias calidades H.264 y lo guarda; se reproduce con el reproductor de Stream o con manifiestos HLS y DASH; los directos entran por RTMPS o SRT](https://blog.devutilites.com/content/images/2026/10/cf-stream-esquema.png)

Subes un original; Stream genera las calidades, las guarda y las sirve con streaming adaptativo. Diagrama: devutilites.

1. **Subida.** Hay cuatro vías: el panel, una URL de la que Stream descarga el vídeo, la API (formulario simple para ficheros de menos de 200 MB y protocolo **tus**, con reanudación, para los grandes) y la **subida directa** (*Direct Creator Upload*): tu servidor pide una URL de un solo uso y el navegador del usuario sube el fichero allí, sin pasar por tu servidor ni ver tu token de API. El máximo es **30 GB** por fichero.
2. **Codificación.** Stream codifica en **H.264** con varias calidades, de **360p a 1080p**, para streaming adaptativo. La ingesta y la codificación son **gratis**.
3. **Reproducción.** Con el reproductor de Stream (un `<iframe>`) o con tu propio reproductor usando los manifiestos HLS o DASH. Cada vídeo tiene una URL del tipo `https://customer-<CÓDIGO>.cloudflarestream.com/<ID>/iframe`.
4. **Directos.** Creas una entrada en directo y emites con OBS o similar por **RTMPS** o **SRT**. Las emisiones pueden grabarse y quedan como vídeos normales. Hay también entrada y salida por **WebRTC**.

## Precios

Stream factura solo dos cosas, igual para vídeo bajo demanda y para directos:

| Concepto               | Precio                                     | Cómo se cuenta                                             |
| ---------------------- | ------------------------------------------ | ---------------------------------------------------------- |
| Minutos almacenados    | 5 $ por cada 1000 minutos al mes (prepago) | Duración de los originales y las grabaciones, no su tamaño |
| Minutos entregados     | 1 $ por cada 1000 minutos (por uso)        | Segmentos de vídeo servidos a los espectadores             |
| Ingesta y codificación | Gratis                                     | —                                                          |
| Transferencia de datos | Incluida en los minutos entregados         | Sin cargos de salida                                       |

Algunos detalles de la documentación que conviene saber:

- El almacenamiento es **prepago**: compras bloques de 1000 minutos. Si te quedas sin espacio, no puedes subir más vídeos ni empezar directos hasta comprar más o borrar.
- **No cuentan**: las calidades que genera Stream, los vídeos con error, los borrados ni las descargas MP4 generadas.
- Una subida directa **reserva** su `maxDurationSeconds` hasta que se completa o caduca el enlace.
- La entrega se cuenta por **segmentos pedidos**, de 4 segundos en los vídeos subidos. La precarga del reproductor cuenta; lo que el navegador reproduce de su caché, no.
- Ejemplo oficial: dos personas que ven 30 minutos cada una son 60 minutos entregados, **0,06 $**. Un directo sin espectadores no cuesta entrega, pero su grabación sí ocupa almacenamiento.

⚠️

**La facturación de WebRTC empieza el 15 de octubre de 2026.** La documentación de precios avisa de que Stream Live por WebRTC pasa a disponibilidad general y su entrega se facturará desde esa fecha.

## Vídeos privados

Por defecto, **cualquiera que tenga el id de un vídeo puede verlo**. Para restringirlo, marca el vídeo con `requireSignedURLs: true`: dejan de funcionar todos los enlaces públicos y solo se puede reproducir con un **token firmado** en lugar del id. Hay tres formas de generar tokens:

- El endpoint **`/token`** de la API: devuelve un token que **caduca en una hora**. Recomendado solo para pruebas o menos de 1000 tokens al día, porque cada token es una llamada a la API.
- Una **clave de firma** propia: generas los tokens tú, sin llamar a Cloudflare. Es lo recomendado con mucho tráfico.
- El **binding de Stream** en un Worker, con `video(id).generateToken()`, sin gestionar claves.

También puedes limitar en qué dominios se puede incrustar un vídeo con `allowedOrigins`.

## El código

El binding de Stream para Workers permite subir desde URL, crear subidas directas, listar y gestionar vídeos, subtítulos, descargas y marcas de agua sin exponer un token de API.

```json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "stream-lab",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-01",
  "stream": { "binding": "STREAM" },
  // Subdominio de entrega de tu cuenta: customer-<CODE>.cloudflarestream.com
  "vars": { "STREAM_HOST": "customer-abc123.cloudflarestream.com" }
}
```

El Worker tiene tres rutas: crear una subida directa para un usuario, importar un vídeo que ya está en otra URL, y devolver una página con el reproductor de un vídeo privado y su token:

```typescript
export default {
  async fetch(request, env): Promise<Response> {
    const url = new URL(request.url);

    // 1. El usuario (ya autenticado en tu app) pide permiso para subir un vídeo
    if (url.pathname === "/subida" && request.method === "POST") {
      const subida = await env.STREAM.createDirectUpload({
        maxDurationSeconds: 600, // reserva 10 min de almacenamiento hasta que se suba
        expiry: new Date(Date.now() + 30 * 60_000).toISOString(), // enlace válido 30 min
        requireSignedURLs: true, // el vídeo será privado
        meta: { usuario: "luis" },
      });
      // El navegador sube el fichero (< 200 MB) directamente a uploadURL, sin pasar por el Worker
      return Response.json({ id: subida.id, uploadURL: subida.uploadURL });
    }

    // 2. Importar un vídeo que ya está en otra URL (S3, tu CMS...)
    if (url.pathname === "/importar" && request.method === "POST") {
      const { origen } = await request.json<{ origen: string }>();
      const video = await env.STREAM.upload(origen, { requireSignedURLs: true, meta: { origen } });
      return Response.json({ id: video.id, estado: video.status.state, firmado: video.requireSignedURLs });
    }

    // 3. Reproducir un vídeo privado: token firmado (caduca en 1 hora)
    const m = url.pathname.match(/^\/ver\/([\w-]{1,64})$/);
    if (m) {
      const token = await env.STREAM.video(m[1]).generateToken();
      const iframe = `https://${env.STREAM_HOST}/${token}/iframe`;
      return new Response(
        `<!doctype html><meta charset="utf-8"><title>Vídeo</title>
<iframe src="${iframe}" style="border:none;width:100%;aspect-ratio:16/9"
  allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture" allowfullscreen></iframe>`,
        { headers: { "content-type": "text/html; charset=utf-8" } },
      );
    }

    return new Response("No encontrado", { status: 404 });
  },
} satisfies ExportedHandler<Env>;
```

### En el simulador local

Wrangler incluye un simulador de Stream. Para probarlo generé un vídeo de 3 segundos con FFmpeg y lo serví desde mi máquina en `http://127.0.0.1:8899/prueba.mp4`:

```text
$ curl -s -X POST localhost:8811/importar -d '{"origen":"http://127.0.0.1:8899/prueba.mp4"}'
{"id":"19c93d21-20d3-4d39-aa61-26cb905b3183","estado":"ready","firmado":true}
$ curl -s localhost:8811/ver/19c93d21-20d3-4d39-aa61-26cb905b3183 | grep -o 'src="[^"]*"'
src="https://customer-abc123.cloudflarestream.com/eyJzdWIiOiIxOWM5M2QyMS0yMGQzLTRkMzktYWE2MS0yNmNiOTA1YjMxODMiLCJraWQiOiJsb2NhbC1tb2RlLWtleSIsImV4cCI6MTc5MTAwMzc3MH0=/iframe"
$ curl -s -X POST localhost:8811/subida
Error: createDirectUpload is not supported in local mode (HTTP 500)
```

El simulador **descarga el vídeo** de la URL, lo marca como listo y le asigna un id. `generateToken()` devuelve un token, pero es un JSON en base64 con `"kid":"local-mode-key"` que solo sirve para que el código funcione: el real lo firma Cloudflare. Y la subida directa responde con un error claro: `createDirectUpload is not supported in local mode`. Para probar esa parte necesitas `wrangler dev --remote` o desplegar.

La subida directa por API, sin Worker, sería así (sin probar):

```bash
curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/stream/direct_upload \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  --data '{"maxDurationSeconds": 600}'
```

La respuesta trae un `uploadURL` de un solo uso al que el navegador envía el fichero (hasta 200 MB por este método; para más, tus).

## Errores comunes (y cómo evitarlos)

- **Dar por hecho que los vídeos son privados.** No lo son. Usa `requireSignedURLs` en todo lo que no sea público.
- **Generar un token por visita con `/token`.** Está limitado; con tráfico real, usa una clave de firma o el binding.
- **Subidas directas con `maxDurationSeconds` enorme.** Reservan almacenamiento hasta que se completan o caducan. Ajusta la duración y la caducidad.
- **Usar el formulario para ficheros grandes.** Por encima de 200 MB usa tus, que además permite reanudar.
- **Olvidar la precarga.** El reproductor descarga segmentos por adelantado y cuentan como minutos entregados.
- **Quedarse sin almacenamiento prepago.** Las subidas fallan. Vigila el uso en el panel.
- **Esperar 4K.** Stream codifica hasta 1080p.

## Cuándo usar Stream (y cuándo no)

**Úsalo** cuando tu aplicación necesita vídeo de verdad: cursos, contenido de usuarios, vídeo de producto, directos, todo lo que deba verse bien en cualquier conexión sin que montes una cadena de codificación. El precio por minutos es fácil de prever.

**No lo necesitas** para un vídeo corto de fondo en tu portada (un MP4 optimizado en R2 basta), ni si necesitas resoluciones por encima de 1080p o códecs específicos. Para transformar vídeos cortos al vuelo (recortes, miniaturas) existe además **Media Transformations**, que comparte facturación con las transformaciones de [Images](https://blog.devutilites.com/cloudflare-images-a-fondo/).

## Sigue leyendo

- [Cloudflare a fondo](https://blog.devutilites.com/cloudflare-a-fondo/): la visión general de la plataforma.
- [Cloudflare Images a fondo](https://blog.devutilites.com/cloudflare-images-a-fondo/): lo mismo, pero para imágenes.
- [Cloudflare R2 a fondo](https://blog.devutilites.com/cloudflare-r2-a-fondo/): para ficheros de vídeo que no necesitan streaming.
- [Cloudflare Workers a fondo](https://blog.devutilites.com/cloudflare-workers-a-fondo/): el runtime del binding.