SDK

Tres paquetes. TypeScript-first. Funciona con React 19, Next.js, TanStack Start, y cualquier framework Node.js.

@devultur/core

La base. Configuración, media router, cliente R2, firma de URLs, y tipos TypeScript.

  • createMediaRouter — punto de entrada único, managed o self-hosted
  • video(), image(), file() — builders de tipo de media con validación
  • URLs pre-firmadas de upload via R2 (compatible con S3)
  • Firma de URLs HMAC-SHA256 via Web Crypto API
  • Funciona en Node.js, Cloudflare Workers, y edge runtimes

@devultur/react

Componentes React listos para usar: upload, reproducción, captions, y tracking de progreso.

  • UploadZone — drag-and-drop o headless, con progreso
  • VideoPlayer — HLS adaptive streaming via hls.js
  • CaptionSelector — cambio de captions multi-idioma
  • useProgress — progreso en tiempo real de uploads y transcodes
  • ProgressProvider — context provider para componentes anidados

@devultur/server

Handlers server-side para frameworks como Next.js, TanStack Start, y Express.

  • createMediaHandler — handler de requests agnóstico de framework
  • Adaptador Web para APIs estándar Request/Response
  • Verificación de firmas de webhooks
  • Generación server-side de URLs de upload
  • Compatible con middleware: funciona con cualquier capa de routing

Ejemplos

Patrones comunes para upload, reproducción, transcoding, y seguridad.

Configuración

lib/media.ts
import { createMediaRouter, video, image, file } from "@devultur/core";

export const media = createMediaRouter({
  // Managed mode: one API key, we handle the infra
  apiKey: process.env.DEVULTUR_API_KEY,

  video: video({
    maxSize: "2GB",
    locales: ["es-CO", "en"],
  }),
  image: image({ maxSize: "10MB" }),
  file:  file({ maxSize: "500MB" }),
});

Upload

components/lesson-upload.tsx
import { UploadZone } from "@devultur/react";
import { media } from "@/lib/media";

export function LessonUpload() {
  return (
    <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) => {
        // Trigger transcode after upload
        media.transcode({ key: result.key, preset: "hls-adaptive" });
      }}
    />
  );
}

Reproducción

components/lesson-player.tsx
import { VideoPlayer } from "@devultur/react";

export function LessonPlayer({ lesson }) {
  return (
    <VideoPlayer
      src={lesson.playlistUrl}
      captions={lesson.captions.map((c) => ({
        locale: c.locale,
        label: c.label,
        src: c.vttUrl,
      }))}
      onProgress={(time) => saveProgress(lesson.id, time)}
      initialTime={lesson.savedProgress}
    />
  );
}

Transcode, captions, y seguridad

api/process-video.ts
// Transcode a video to HLS
const job = await media.transcode({
  key: "videos/lesson-1.mp4",
  preset: "hls-adaptive",
  callbackUrl: "https://yourapp.com/api/webhooks/transcode",
});

// Request captions
const caption = await media.requestCaptions({
  key: "videos/lesson-1.mp4",
  locale: "es-CO",
});

// Issue a signed playback token
const token = await media.issueToken({
  key: "videos/lesson-1/playlist.m3u8",
  expiresIn: 7200,
});