Einen eigenen Video-Player mit Claude Code bauen
Einen produktionsreifen Video-Player für React mit Claude Code: Barrierefreiheit, Auslieferung und Monetarisierung.
Upgrade 2026: Was ein Video-Player überhaupt ist
Ein Video-Player ist eine UI, die eine Videodatei oder einen Stream lädt und dem Leser Bedienung für Wiedergabe, Pause, Suchen, Lautstärke, Untertitel und Geschwindigkeit gibt. Im Web liegt meist das native <video>-Element als Fundament darunter, und aus JavaScript liest und aktualisiert man currentTime, duration, paused, volume, muted und playbackRate des HTMLMediaElement.
Diese Definition steht bewusst am Anfang, denn ein Video-Player ist keine bloße Dekoration. In Lernprodukten entscheidet er direkt über die Abschlussquote: ob Teilnehmer mitten im Video wieder einsteigen können, ob Untertitel beim Verstehen helfen und ob man die Geschwindigkeit zum Wiederholen senken kann. Auf Medienseiten zählt, ob er die erste Darstellung des Artikels nicht stört, ob er auch über das Mobilfunknetz nicht zu lange warten lässt und ob nach dem Ansehen ein natürlicher Übergang zum nächsten Artikel oder zur Registrierung entsteht. Bei einer SaaS-Produktdemo wird das Wiedergabeerlebnis selbst zum Vertrauenssignal.
Sieh dir bei der Umsetzung die Referenz zum <video>-Element und die HTMLMediaElement-API bei MDN an. Claude Code eignet sich gut dafür, Komponente, Tests, Barrierefreiheits-Review und Messereignisse gemeinsam aufzuräumen. Welche Erfahrung du schützen willst, entscheidet aber die Produktseite. Beginne mit dem kleinstmöglichen Player, den deine Leser brauchen.
Als verwandte Umsetzungen lohnt der Blick auf den Audio-Player mit Claude Code für reinen Ton, auf Barrierefreiheit mit Claude Code für Tastaturbedienung und Untertitel und auf die Performance-Optimierung mit Claude Code für die Ladezeit. Wenn aus Lern- oder Medien-Pfaden Umsatz werden soll, setze die Trainings- und Beratungsseite als CTA und gehe von dort zu Implementierungs-Review und Betriebsdesign über.
Natives <video>, eigene Steuerung und Streaming im Vergleich
Zuerst zu entscheiden ist nicht das Aussehen der Buttons, sondern welche Wiedergabeform du wählst. Natives <video controls> ist am schnellsten veröffentlicht, und der Browser kümmert sich um Tastaturbedienung und grundlegende Untertitel. Brauchst du dagegen Lernverwaltung, gespeicherten Fortschritt, CTAs für Mitglieder, Kapitel oder eigene Messung, ist eine eigene Steuerung über das HTMLMediaElement passender. Ist das Video lang, gibt es viele Zuschauer oder große Unterschiede bei Land und Netz, kommen HLS, DASH oder Streaming über einen Video-Dienst hinzu.
| Form | Geeignet für | Worauf in Produktion achten |
|---|---|---|
Natives <video controls> | Kurze Einbettung im Artikel, interne Doku, einfache Landingpage | Schnell umgesetzt. Marke und detaillierte Messung sind begrenzt, aber die Grundbedienung ist zuverlässig. |
Eigene Steuerung auf HTMLMediaElement | Lernprodukt, Medienseite, SaaS-Demo, Video für Mitglieder | UI, Wiedereinstieg, CTA-Anzeige und Analytics steuerbar. Dafür trägst du Verantwortung für Barrierefreiheit und Fehlerbehandlung. |
| HLS/DASH oder Video-Plattform | Lange Kurse, große Kataloge, Live, geschützte Videos | Transcoding, Manifest, CDN, Bitrate, Autorisierung und Player-Bibliothek müssen entworfen werden. |
Für eine erste Umsetzung mit Claude Code ist es realistisch, mit einem kurzen MP4, preload="metadata", einem Poster-Bild und einer Untertiteldatei zu starten. Sobald Kapitellinks, Untertitelsuche, Abschlussquote, Anzeige für Gumroad-Käufer oder eine SSO-Anbindung nötig werden, machst du den Player individuell, und sobald eine einzelne MP4-Datei nicht mehr reicht, wechselst du zu Streaming.
Architektur-Tabelle
| Schicht | Aufgabe | Was Claude Code prüfen soll |
|---|---|---|
| Asset-Verwaltung | MP4/WebM, Poster-Bild, Untertitel und Streaming-Manifest bereitstellen | URL, MIME-Type, CORS, befristete URLs und Alternativtext prüfen. |
| Natives Medien-Element | Wiedergabe, Laden, Zeit, Untertitel und Fehler dem Browser überlassen | preload, playsInline, track, Fallback-Text und Event-Abos prüfen. |
| Zustandsverwaltung | currentTime, duration, paused, muted, volume und Tempo in React spiegeln | Zustand nicht raten, sondern aus Media-Events synchronisieren. |
| Bedien-UI | Wiedergabe, Suchleiste, Lautstärke, Tempo, Untertitel und Vollbild anbieten | button und input nutzen, Tastaturbedienung und Labels erhalten. |
| Fortbestehender Zustand | Wiedereinstiegsposition, Abschluss, Tempo und Stummschaltung speichern | Speicherumfang minimieren, auf öffentlichen Seiten Datenschutz erklären. |
| Messung | Start, 25/50/75 %, Abschluss, Fehler und CTA-Klick erfassen | Keine Events pro Sekunde senden, nur nützliche Kennzahlen behalten. |
| Performance | Poster, CDN, Lazy Loading und Bitrate justieren | CLS, initiale Übertragung, Mobilnetz und Cache-Header prüfen. |
Mit dieser Aufteilung werden auch die Aufträge an Claude Code konkret. „Prüfe nur die Barrierefreiheit der Suchleiste“, „Korrigiere nur Größe und Ladestrategie des Posters“ oder „Teste, dass das Abschluss-Event nicht doppelt gesendet wird“ – so klein schneidbar verhinderst du, dass eine UI-Korrektur die Auslieferung oder das Analytics-Design mitreißt.
Reale Anwendungsfälle
Anwendungsfall 1 ist die Lektion eines kostenpflichtigen Kurses. Teilnehmer verlassen den Platz, kommen auf einem anderen Gerät zurück und wiederholen mit 1,25-facher Geschwindigkeit. Wichtiger als hübsche Animationen sind Untertitel, Wiedereinstiegsposition, Abschlussbedingung und der Weg zur nächsten Lektion. Das Abschluss-Event wird nicht beim Seitenaufruf gesendet, sondern wenn ein bestimmter Anteil angesehen wurde.
Anwendungsfall 2 ist ein eingebettetes Video im Medienartikel. Leser entscheiden beim Lesen, ob sie das Video ansehen. Stelle Poster-Bild und Transkript daneben und lade das Video selbst erst, wenn es gebraucht wird. Wer das Video zu Ende sieht, bekommt einen Pfad zu verwandten Artikeln, Newsletter, Registrierung oder einem kostenpflichtigen Report.
Anwendungsfall 3 ist die SaaS-Produktdemo. Funktionsbezogene Kapitel, ein Pfad zur Preisseite, ein Link zur API-Doku und ein Kontakt-Button, je nach Wiedergabeposition angepasst – so wird aus einem bloßen Video eine Erklär-UI vor dem Verkaufsgespräch. Wichtiger als der vollständige Abschluss ist, welches Kapitel gesehen und welcher CTA geklickt wurde.
Anwendungsfall 4 sind internes Training und Support. Wenn du Verkaufsgespräche, Onboarding, Compliance und Support-Beispiele als Video aufbereitest, zählen stabile Wiedergabe unter SSO, Untertitel, Audit-Logs und eine Anleitung im Fehlerfall. Statt eines auffälligen Designs wird es wertvoll, mit dem nötigen Minimum zu sehen, wer wie weit gekommen ist.
Kopierbarer React-/TypeScript-Code
Die folgende Komponente ist eine Minimalvariante, die du direkt in Vite, Next.js oder eine React-Island in Astro setzen kannst. Sie hängt von keiner externen Player-Bibliothek ab und nutzt das native <video> sowie HTMLMediaElement-Events.
import { useEffect, useRef, useState, type ChangeEvent } from "react";
type CaptionTrack = {
src: string;
srcLang: string;
label: string;
default?: boolean;
};
type ProductionVideoPlayerProps = {
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 ProductionVideoPlayer({
src,
title,
poster,
captions = [],
}: ProductionVideoPlayerProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(0.8);
const [rate, setRate] = useState(1);
const [error, setError] = useState("");
useEffect(() => {
const video = videoRef.current;
if (!video) return;
const syncTime = () => setCurrentTime(video.currentTime);
const syncDuration = () => {
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
};
const syncPlayState = () => setIsPlaying(!video.paused);
const syncVolume = () => setVolume(video.muted ? 0 : video.volume);
video.addEventListener("timeupdate", syncTime);
video.addEventListener("loadedmetadata", syncDuration);
video.addEventListener("durationchange", syncDuration);
video.addEventListener("play", syncPlayState);
video.addEventListener("pause", syncPlayState);
video.addEventListener("volumechange", syncVolume);
return () => {
video.removeEventListener("timeupdate", syncTime);
video.removeEventListener("loadedmetadata", syncDuration);
video.removeEventListener("durationchange", syncDuration);
video.removeEventListener("play", syncPlayState);
video.removeEventListener("pause", syncPlayState);
video.removeEventListener("volumechange", syncVolume);
};
}, []);
async function togglePlay() {
const video = videoRef.current;
if (!video) return;
if (video.paused) {
try {
await video.play();
setError("");
} catch {
setError("Playback was blocked. Tap play again or check browser settings.");
}
} else {
video.pause();
}
}
function seek(event: ChangeEvent<HTMLInputElement>) {
const video = videoRef.current;
if (!video) return;
const nextTime = Number(event.target.value);
video.currentTime = nextTime;
setCurrentTime(nextTime);
}
function changeVolume(event: ChangeEvent<HTMLInputElement>) {
const video = videoRef.current;
if (!video) return;
const nextVolume = Number(event.target.value);
video.volume = nextVolume;
video.muted = nextVolume === 0;
setVolume(nextVolume);
}
function changeRate(event: ChangeEvent<HTMLSelectElement>) {
const video = videoRef.current;
if (!video) return;
const nextRate = Number(event.target.value);
video.playbackRate = nextRate;
setRate(nextRate);
}
function toggleMute() {
const video = videoRef.current;
if (!video) return;
video.muted = !video.muted;
}
return (
<section className="production-video-player" aria-label={`${title} video player`}>
<video ref={videoRef} poster={poster} preload="metadata" playsInline>
<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-label={isPlaying ? "Pause video" : "Play video"}>
{isPlaying ? "Pause" : "Play"}
</button>
<label>
<span>Seek</span>
<input
type="range"
min="0"
max={duration || 0}
step="0.1"
value={duration ? currentTime : 0}
onChange={seek}
aria-valuetext={`${formatTime(currentTime)} of ${formatTime(duration)}`}
/>
</label>
<output>
{formatTime(currentTime)} / {formatTime(duration)}
</output>
<button type="button" onClick={toggleMute} aria-label={volume === 0 ? "Unmute video" : "Mute video"}>
{volume === 0 ? "Unmute" : "Mute"}
</button>
<label>
<span>Volume</span>
<input type="range" min="0" max="1" step="0.05" value={volume} onChange={changeVolume} />
</label>
<label>
<span>Speed</span>
<select value={rate} onChange={changeRate}>
{[0.75, 1, 1.25, 1.5, 2].map((speed) => (
<option key={speed} value={speed}>
{speed}x
</option>
))}
</select>
</label>
</div>
{error ? <p role="alert">{error}</p> : null}
</section>
);
}
Fallstricke bei Barrierefreiheit und Performance
Der größte Fallstrick ist ein Zustand, in dem die native Steuerung versteckt, die gleichwertige Bedienbarkeit aber nicht zurückgegeben wurde. Eine Wiedergabe-Taste aus einem div, eine Suchleiste ohne Label, keine Tempoänderung per Tastatur, fehlende Untertitel – solche Umsetzungen sehen vielleicht gut aus, taugen aber nicht für Lernprodukte. Grundregel: Buttons als echtes button, das Suchen als input type="range" und Fehler über role="alert" mitteilen.
Bei der Performance ist ein Design gefährlich, das in einer Artikelliste oder auf einer Landingpage mehrere Videos gleichzeitig lädt. Gib dem Poster-Bild feste Breite und Höhe, setze optional ansehbare Videos auf preload="metadata" und liefere das Video selbst über ein CDN aus. Verteilst du einen langen Kurs nur als eine riesige MP4, bist du bei Mobilnetz, Auslandsnutzung und Abbruch zwischendurch überall im Nachteil.
Auch Fehler bei der Messung sind häufig. Statt Events im Sekundentakt zu senden, sind Start, 25 %, 50 %, 75 %, Abschluss, Fehler und CTA-Klick für Verbesserungen nützlicher. Bei einem Lerndienst solltest du sehen, „welche Lektion eine niedrige Abschlussquote hat“, auf einer Medienseite, „ob nach dem Ansehen eine Registrierung folgte“.
Rollout-Checkliste
- Labels für Tastatur, Touch, Maus und Screenreader geprüft.
- Untertitel oder Transkript bereitgestellt und wichtige Informationen nicht nur ins Video gesperrt.
- Auf Mobil wirkt
playsInlineund es gibt keinen ungewollten Vollbildwechsel. - Seitenverhältnis und Größe des Poster-Bilds fixiert, kein CLS ausgelöst.
- Kein
preload="auto"an Videos, die für die erste Darstellung nicht nötig sind. - Abgelaufene URLs, fehlende Untertitel, langsames Netz und Autoplay-Block getestet.
- Messnamen für Start, Fortschritt, Abschluss, Fehler und CTA-Klick festgelegt.
- Vorgehen vorbereitet, um bei kaputter eigener Steuerung auf native
controlszurückzufallen.
Verwendungsbeispiel
<VideoPlayer
src="/videos/demo.mp4"
poster="/images/poster.jpg"
subtitles={[
{ src: '/subtitles/de.vtt', label: 'Deutsch', lang: 'de' },
{ src: '/subtitles/en.vtt', label: 'English', lang: 'en' },
]}
/>
Monetarisierungspfad und Prüfnotiz
Ein Video-Player kann auch ein Pfad sein, der von kostenlosen Artikeln zu kostenpflichtigem Lernmaterial oder Beratung führt. Natürlich ist ein Ablauf, bei dem du eine kostenlose Vorschau in den Artikel stellst und dem Leser, der mehr verstanden hat, ein Arbeitsblatt, ein Gumroad-Produkt, einen Kurs oder Training und Beratung anbietest. Kein aufdringlicher Verkauf, sondern die Positionierung: „Wer diese Video-UI auf eigenes Lernmaterial, internes Training oder eine Medienseite zuschneiden möchte, ist hier richtig.“
Prüfnotiz: Beim praktischen Test mit diesem Aufbau war es stabiler, isPlaying auf der React-Seite aus den play- und pause-Events zu synchronisieren, statt es beim Buttonklick zu raten. Weil video.play() an der Autoplay-Beschränkung des Browsers scheitern kann, muss man den Leser per try/catch zu einer erneuten Bedienung auffordern. Wenn du Claude Code prüfen lässt, zeig zuerst die drei Punkte „Wird der Zustand aus Media-Events synchronisiert?“, „Lässt sich alles per Tastatur bedienen?“ und „Wird beim ersten Laden nicht das Video selbst heruntergeladen?“ – dann führen die Korrekturen direkt zur Praxis.
Zur Formaten und Encoding von Web-Videos liefern die MDN Web Docs ausführliche Informationen.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Berechtigungs-Checkliste, bevor Claude Code eine Kundenseite bearbeitet
Ein Agenturrahmen für sichere KI-Edits an Landingpages, Formularen und Kundenseiten.
SaaS-Supporttickets mit Claude Code in reproduzierbare Bugs verwandeln
Ein Support-Workflow, der vage Meldungen in Repro-Schritte, Belege und Entwicklernotizen übersetzt.
Alte Obsidian-Notizen in ein Claude-Code-Briefing verwandeln: die 10-Minuten-Routine
Sortiere Obsidian-Notizen in 10 Minuten in Fakten, Entscheidungen und offene Punkte – als Briefing, mit dem Claude Code sofort loslegt.