Qué incluye

Seis capacidades, un SDK. Sube, transcodifica, distribuye, subtitula, reproduce y protege tu media.

01

Upload

Tus usuarios suben. Nosotros nos encargamos del resto.

URLs pre-firmadas directo desde Cloudflare R2, los archivos nunca tocan tu servidor. Tracking de progreso, validación de tipo y límites de tamaño por tipo de media incluidos.

  • URLs pre-firmadas PUT generadas por archivo con expiración configurable
  • Componente React UploadZone con drag-and-drop, o completamente headless
  • Progreso en tiempo real via hook useProgress y ProgressProvider
  • Validación de archivos: tipo, tamaño y dimensiones verificados antes del upload
  • Uploads multipart para archivos grandes con chunking automático
upload-zone.tsx
import { UploadZone } from "@devultur/react";

<UploadZone
  accept="video/*"
  maxSize="2GB"
  onUploadUrl={async (file) => {
    const { url, key } = await media.createUploadUrl({
      filename: file.name,
      contentType: file.type,
    });
    return { url, key };
  }}
  onComplete={(result) => {
    console.log("Uploaded:", result.key);
  }}
/>
02

Transcode

Video crudo entra, streaming HLS sale.

FFmpeg corre en máquinas Fly.io por segundo. Sube un video, recibe un playlist HLS. Adaptive bitrate, auto-thumbnails, y notificaciones webhook cuando el job termina.

  • HLS single-bitrate o adaptativo con presets configurables
  • Facturación por segundo en Fly.io Machines (sin costos idle)
  • Thumbnail auto-generado en timestamp configurable
  • Callback webhook cuando el transcode completa o falla
  • 720p (Free), 1080p (Starter), 4K (Pro) resolución máxima
transcode.ts
const job = await media.transcode({
  key: "videos/lesson-1.mp4",
  preset: "hls-adaptive",
  callbackUrl: "https://yourapp.com/api/webhooks",
});

// Poll status
const status = await media.getTranscodeStatus(job.id);
// { state: "completed", playlist: "videos/lesson-1/playlist.m3u8" }
03

Deliver

Cero egress. En serio.

Cloudflare R2 almacena tus archivos con $0 en fees de egress. Cada archivo tiene una URL firmada con expiración configurable. Range requests y CDN caching funcionan sin configuración.

  • Cloudflare R2 storage: $0 bandwidth, siempre
  • URLs firmadas HMAC-SHA256 con expiración configurable
  • Soporte de range requests para video seeking
  • CDN caching via la red global de Cloudflare
  • Custom domain opcional (tier Pro)
deliver.ts
const url = await media.getMediaUrl({
  key: "videos/lesson-1/playlist.m3u8",
  expiresIn: 3600, // 1 hour
});

// Signed URL with HMAC verification
// https://media.devultur.com/v1/media/videos/lesson-1/playlist.m3u8
//   ?expires=1719000000&signature=abc123...
04

Captions

Transcripción en cualquier idioma, automáticamente.

Envía un video, recibe archivos VTT con timestamps por oración. Groq Whisper transcribe, Mistral traduce. Todo automático.

  • Speech-to-text via Groq Whisper (228x realtime)
  • Output VTT almacenado junto al video
  • Transcripción multi-idioma (99+ idiomas)
  • Traducciones automáticas via Mistral
  • Progreso en tiempo real via webhooks
captions.ts
const caption = await media.requestCaptions({
  key: "videos/lesson-1.mp4",
  locale: "es-CO",
});

// Check status
const status = await media.getCaptionsStatus(caption.id);

// Download VTT
const vtt = await media.getCaptionsVtt(caption.id);
05

Player

Agrega un video player con un componente.

hls.js por debajo, estilizado por defecto o completamente headless. Atajos de teclado, selector de captions, y reanudar desde última posición incluidos.

  • HLS adaptive streaming via hls.js
  • Selector de captions con soporte multi-idioma
  • Reanudar desde última posición con callback onProgress
  • Atajos de teclado (espacio, flechas, f para pantalla completa)
  • Estilizado por defecto, o renderiza tu propio UI en modo headless
player.tsx
import { VideoPlayer } from "@devultur/react";

<VideoPlayer
  src={playlistUrl}
  captions={[
    { locale: "es-CO", label: "Español", src: spanishVtt },
    { locale: "en", label: "English", src: englishVtt },
  ]}
  onProgress={(time) => saveProgress(lessonId, time)}
  initialTime={savedProgress}
/>
06

Security

Tokens JWT vinculados a la sesión.

Cuatro niveles de protección de contenido, desde URLs firmadas hasta segmentos HLS encriptados con AES-128. Los tokens se vinculan a IP y huella de sesión con expiración configurable.

  • Nivel 1: URLs firmadas HMAC-SHA256 con expiración
  • Nivel 2: Tokens JWT con vinculación por IP y huella de sesión
  • Nivel 3: JWT + URLs de segmentos de corta duración (Pro)
  • Nivel 4: Segmentos HLS encriptados AES-128 (Pro)
  • Expiración configurable por tipo de token
security.ts
const token = await media.issueToken({
  key: "videos/lesson-1/playlist.m3u8",
  expiresIn: 7200,
  claims: {
    userId: user.id,
    ip: request.ip,
  },
});

// Token is verified by the Worker on each segment request