Use Cases (Actualizado: 2/6/2026)

Video player con Claude Code para cursos y sitios de medios

Construye un reproductor React con video nativo, controles personalizados, streaming, accesibilidad y checklist.

Video player con Claude Code para cursos y sitios de medios

Actualización de producción 2026: qué es un video player

Un video player es la interfaz que carga un archivo de vídeo o una transmisión, muestra el contenido y permite controlar reproducción, pausa, avance, volumen, subtítulos y velocidad. En la web, la base suele ser el elemento nativo <video>. Encima de esa base, React o cualquier otra capa de UI se comunica con HTMLMediaElement para leer y cambiar currentTime, duration, paused, volume, muted y playbackRate.

La definición importa porque un reproductor de producción no es solo un botón bonito. En un producto educativo, decide si el alumno puede retomar una lección, bajar la velocidad, leer subtítulos y completar el curso. En un sitio de medios, decide si el artículo carga rápido, si el lector móvil abandona y si el vídeo conduce a una newsletter, membresía o informe premium. En una demo SaaS, decide si la persona entiende el producto antes de hablar con ventas.

Para implementar con criterio, usa la referencia oficial de MDN sobre el elemento <video> y la API HTMLMediaElement. Claude Code puede ayudarte a escribir el componente, revisar eventos, proponer pruebas y encontrar problemas de accesibilidad, pero la decisión de producto sigue siendo humana: construye el reproductor mínimo que el lector necesita.

Este artículo se conecta con el audio player de Claude Code, la guía de accesibilidad con Claude Code y la guía de optimización de performance. Si el vídeo forma parte de un curso pagado, una operación de medios o un programa de capacitación, la llamada comercial puede llevar a training y consultoría.

<video> nativo, controles personalizados o streaming

La primera decisión no es el color de los controles. Es el modelo de reproducción. El reproductor nativo con <video controls> es adecuado para insertar un vídeo corto en un artículo o en documentación interna. Los controles personalizados son útiles cuando necesitas progreso de lección, capítulos, CTA, analítica, diseño de marca o reglas de membresía. El streaming aparece cuando el archivo es largo, el catálogo crece, hay usuarios en redes débiles o necesitas calidad adaptativa.

EnfoqueMejor usoNotas de producción
<video controls> nativoArtículos, documentación interna, landing pages simplesEs lo más rápido. El navegador maneja controles básicos, pero la marca y la analítica son limitadas.
Controles personalizados sobre HTMLMediaElementCursos, sitios de medios, demos SaaS, áreas de miembrosControlas UI, progreso, CTA y eventos. También asumes accesibilidad, errores y móviles.
HLS/DASH o plataforma de vídeoLecciones largas, eventos en vivo, catálogos grandes, contenido protegidoRequiere transcodificación, manifest, CDN, permisos y a veces una librería de player.

Para la mayoría de proyectos con Claude Code, empieza con un MP4 corto, una imagen poster, subtítulos WebVTT y preload="metadata". Añade controles personalizados cuando haya una razón concreta: búsqueda en transcripción, capítulos, progreso de curso, CTA para una descarga de Gumroad o experiencia de membresía. Pasa a streaming cuando un solo MP4 ya no cubra redes móviles, regiones o vídeos largos.

Tabla de arquitectura

CapaResponsabilidadQué pedir a Claude Code
Activos de vídeoMP4/WebM, poster, subtítulos, manifest de streamingRevisar URL, MIME type, CORS, caché, enlaces vencidos y fallback.
Elemento nativoCargar media, exponer eventos, duración, tiempo y erroresVerificar preload, playsInline, track y mensajes de error.
Control de estadoSincronizar tiempo, duración, reproducción, volumen, mute y velocidadEvitar estado inventado desde clics; usar eventos reales del media element.
UI de controlesBotones, barra de progreso, volumen, velocidad, subtítulosUsar button e input reales con etiquetas y foco estable.
PersistenciaPosición de reanudación, finalización, velocidad, silencioGuardar solo lo necesario y explicar la privacidad si hay usuario identificado.
AnalíticaInicio, 25%, 50%, 75%, finalización, errores, clics de CTAMedir decisiones de producto, no enviar ruido cada segundo.
PerformancePoster, CDN, lazy loading, bitrateEvitar layout shift, exceso de bytes iniciales y coste móvil innecesario.

Esta arquitectura facilita el mantenimiento. Puedes pedir a Claude Code que revise solo el orden de foco, solo los eventos de finalización o solo la carga inicial. También permite una salida de emergencia: si los controles personalizados fallan, puedes habilitar controles nativos mientras corriges el problema.

Use case reales

Use case 1: una lección pagada. El alumno pausa, vuelve más tarde, cambia de dispositivo y usa 1.25x. El player necesita subtítulos, reanudación, criterio de finalización y enlace a la siguiente lección. La finalización no debe dispararse al cargar la página, sino después de una parte significativa de visualización.

Use case 2: un artículo de medios con vídeo. El lector escanea el texto antes de reproducir. La poster debe ser ligera, el transcript debe estar cerca y el archivo no debe descargarse completo en el primer render. Al terminar, el CTA puede ser una newsletter, una membresía o un análisis relacionado.

Use case 3: una demo SaaS. Un visitante puede querer ver solo precios, API o una función específica. Los capítulos y los eventos de reproducción permiten mostrar una prueba gratuita, una consulta o documentación técnica según la intención real.

Use case 4: capacitación interna. Ventas, soporte, onboarding y compliance necesitan reproducción estable, subtítulos, SSO, mensajes de error y progreso visible para administradores. Aquí la confiabilidad pesa más que la animación decorativa.

Código React/TypeScript ejecutable

Este componente funciona en Vite, Next.js o una isla React de Astro. Usa <video> nativo, eventos de HTMLMediaElement y controles accesibles sin depender de una librería externa.

import { useRef, useState, type ChangeEvent } from "react";

type CaptionTrack = {
  src: string;
  srcLang: string;
  label: string;
  default?: boolean;
};

type VideoPlayerProps = {
  src: string;
  title: string;
  poster?: string;
  captions?: CaptionTrack[];
};

function formatTime(value: number) {
  if (!Number.isFinite(value)) return "0:00";
  const minutes = Math.floor(value / 60);
  const seconds = Math.floor(value % 60).toString().padStart(2, "0");
  return `${minutes}:${seconds}`;
}

export function VideoPlayer({ src, title, poster, captions = [] }: VideoPlayerProps) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [media, setMedia] = useState({
    current: 0,
    duration: 0,
    playing: false,
    volume: 0.8,
    rate: 1,
    error: "",
  });

  function patch(next: Partial<typeof media>) {
    setMedia((current) => ({ ...current, ...next }));
  }

  async function togglePlay() {
    const video = videoRef.current;
    if (!video) return;
    if (video.paused) {
      try {
        await video.play();
        patch({ playing: true, error: "" });
      } catch {
        patch({ error: "Playback was blocked. Try again from the play button." });
      }
    } else {
      video.pause();
      patch({ playing: false });
    }
  }

  function seek(event: ChangeEvent<HTMLInputElement>) {
    const video = videoRef.current;
    if (!video) return;
    const nextTime = Number(event.target.value);
    video.currentTime = nextTime;
    patch({ current: nextTime });
  }

  function changeVolume(event: ChangeEvent<HTMLInputElement>) {
    const video = videoRef.current;
    if (!video) return;
    const volume = Number(event.target.value);
    video.volume = volume;
    video.muted = volume === 0;
    patch({ volume });
  }

  function changeRate(event: ChangeEvent<HTMLSelectElement>) {
    const video = videoRef.current;
    if (!video) return;
    const rate = Number(event.target.value);
    video.playbackRate = rate;
    patch({ rate });
  }

  return (
    <section className="video-player" aria-label={`${title} video player`}>
      <video
        ref={videoRef}
        poster={poster}
        preload="metadata"
        playsInline
        onLoadedMetadata={(event) => patch({ duration: event.currentTarget.duration })}
        onTimeUpdate={(event) => patch({ current: event.currentTarget.currentTime })}
        onPlay={() => patch({ playing: true })}
        onPause={() => patch({ playing: false })}
        onVolumeChange={(event) => patch({ volume: event.currentTarget.muted ? 0 : event.currentTarget.volume })}
        onError={() => patch({ error: "The video could not be loaded." })}
      >
        <source src={src} type={src.endsWith(".webm") ? "video/webm" : "video/mp4"} />
        {captions.map((track) => (
          <track key={track.src} kind="captions" src={track.src} srcLang={track.srcLang} label={track.label} default={track.default} />
        ))}
        Your browser does not support the video element.
      </video>

      <div role="group" aria-label="Video controls">
        <button type="button" onClick={togglePlay} aria-pressed={media.playing}>
          {media.playing ? "Pause" : "Play"}
        </button>
        <input type="range" min="0" max={media.duration || 0} step="0.1" value={media.duration ? media.current : 0} onChange={seek} aria-label="Seek video" />
        <output>{formatTime(media.current)} / {formatTime(media.duration)}</output>
        <input type="range" min="0" max="1" step="0.05" value={media.volume} onChange={changeVolume} aria-label="Volume" />
        <select value={media.rate} onChange={changeRate} aria-label="Playback speed">
          {[0.75, 1, 1.25, 1.5, 2].map((rate) => (
            <option key={rate} value={rate}>{rate}x</option>
          ))}
        </select>
      </div>

      {media.error ? <p role="alert">{media.error}</p> : null}
    </section>
  );
}

Pitfall de accesibilidad y performance

El pitfall principal es ocultar los controles nativos y no reemplazar su comportamiento. Un botón de reproducción debe ser un button, no un div con onClick. La barra de progreso debe aceptar teclado. Los subtítulos y el texto alternativo no son opcionales cuando el vídeo contiene la explicación principal.

El segundo pitfall es la carga excesiva. Una portada con varios vídeos no debe descargar todos los MP4 completos. Usa poster con dimensiones estables, preload="metadata" para vídeos secundarios, CDN y archivos comprimidos. Para lecciones largas, prepara varias calidades o usa una plataforma administrada.

La analítica también puede fallar por exceso. Registra inicio, 25%, 50%, 75%, finalización, error y CTA. Si envías un evento por segundo, tendrás coste y ruido, pero no necesariamente mejores decisiones.

Checklist de lanzamiento y CTA

  • Probar teclado, táctil, mouse y etiquetas para lectores de pantalla.
  • Añadir subtítulos o transcript para cursos, medios y demos.
  • Validar playsInline, rotación y redes lentas en móvil.
  • Fijar dimensiones de poster y revisar CLS.
  • Evitar preload="auto" salvo que el vídeo sea la experiencia principal.
  • Probar URL vencida, subtítulo faltante, autoplay bloqueado y error de CDN.
  • Medir inicio, progreso, finalización, error y clic de CTA.
  • Mantener fallback a controles nativos durante la primera semana.

Como CTA de monetización, un vídeo gratuito puede llevar a una descarga de Gumroad, a una suscripción, a un curso completo o a training y consultoría. La forma práctica de trabajar con Claude Code es construir primero el reproductor mínimo, pedir revisión de accesibilidad, pedir revisión de performance y luego conectar eventos de negocio. Así el player deja de ser un adorno y se convierte en infraestructura para aprendizaje, medios y ventas.

#Claude Code #video player #React #accessibility #media UI
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.