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,
});