Reel Player

Eine Komponente für einen Videoplayer im Vollbild im Stil von Instagram Reels und TikTok, gebaut mit @reelkit/react-reel-player.

Live-Demo ansehen →

Funktionen

Vertikal wischen
Touch, Ziehen, Tastatur, Mausrad
Video-Autoplay
Spielt, sobald sichtbar
Ton umschalten
Durchgehend auf iOS
Mehrere Medien
Verschachtelte horizontale Karussells
Position merken
Macht dort weiter, wo du warst
Frame-Erfassung
Überblendung vom Poster zum Video
Virtualisiert
Nur 3 Slides im DOM
Seitenverhältnis
9:16 am Desktop, mobil im Vollbild
Desktop-Navigation
Pfeil-Buttons
Generische Typen
Eigene Datenmodelle für Inhalte
Anpassbar
Render-Props für alles
Slide-Overlay
Autor, Likes, Beschreibung
URL-State
Links zum Teilen und Speichern

Installation

bash
npm install @reelkit/react-reel-player @reelkit/react lucide-react

Vergiss nicht, die Styles zu importieren:

typescript
import '@reelkit/react-reel-player/styles.css';
Icons

Die Standard-Steuerelemente nutzen lucide-react für die Icons. Willst du eine andere Icon-Bibliothek, bring mit renderControls und renderNavigation deine eigenen mit.

Schnellstart

Die Komponente ReelPlayerOverlay rendert ein Player-Overlay im Vollbild. Übergib ein Array von ContentItem-Objekten und steuere die Sichtbarkeit mit isOpen.

tsx
import { useState } from 'react';
import { ReelPlayerOverlay, type ContentItem } from '@reelkit/react-reel-player';
import '@reelkit/react-reel-player/styles.css';

const content: ContentItem[] = [
  {
    id: '1',
    media: [{
      id: 'v1',
      type: 'video',
      src: 'https://example.com/video.mp4',
      poster: 'https://example.com/poster.jpg',
      aspectRatio: 9 / 16,
    }],
    author: { name: 'John Doe', avatar: 'https://example.com/avatar.jpg' },
    likes: 1234,
    description: 'Amazing video!',
  },
];

function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Player</button>
      <ReelPlayerOverlay
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        content={content}
      />
    </>
  );
}
ReelPlayerPage.tsx
import { useState } from 'react';
import { ReelPlayerOverlay, type ContentItem } from '@reelkit/react-reel-player';
import '@reelkit/react-reel-player/styles.css';

const content: ContentItem[] = [
  {
    id: '1',
    media: [{
      id: 'v1',
      type: 'video',
      src: '/cdn/samples/videos/video-01.mp4',
      poster: '/cdn/samples/videos/video-poster-01.jpg',
      aspectRatio: 16 / 9,
    }],
    author: { name: 'Alex Johnson', avatar: '/cdn/samples/avatars/avatar-01.jpg' },
    likes: 1234,
    description: 'Amazing sunset vibes',
  },
  {
    id: '2',
    media: [{
      id: 'img1',
      type: 'image',
      src: '/cdn/samples/images/image-01.jpg',
      aspectRatio: 2 / 3,
    }],
    author: { name: 'Sarah Miller', avatar: '/cdn/samples/avatars/avatar-02.jpg' },
    likes: 5678,
    description: 'Nature at its finest',
  },
  {
    id: '3',
    media: [{
      id: 'v2',
      type: 'video',
      src: '/cdn/samples/videos/video-04.mp4',
      poster: '/cdn/samples/videos/video-poster-04.jpg',
      aspectRatio: 16 / 9,
    }],
    author: { name: 'James Wilson', avatar: '/cdn/samples/avatars/avatar-03.jpg' },
    likes: 3456,
    description: 'City life adventures',
  },
  {
    id: '4',
    media: [
      { id: 'img2', type: 'image', src: '/cdn/samples/images/image-02.jpg', aspectRatio: 2 / 3 },
      { id: 'img3', type: 'image', src: '/cdn/samples/images/image-03.jpg', aspectRatio: 3 / 4 },
    ],
    author: { name: 'Emma Davis', avatar: '/cdn/samples/avatars/avatar-04.jpg' },
    likes: 8901,
    description: 'Travel moments',
  },
  {
    id: '5',
    media: [{
      id: 'img4',
      type: 'image',
      src: '/cdn/samples/images/image-04.jpg',
      aspectRatio: 2 / 3,
    }],
    author: { name: 'Michael Brown', avatar: '/cdn/samples/avatars/avatar-05.jpg' },
    likes: 2345,
    description: 'Golden hour magic',
  },
  {
    id: '6',
    media: [{
      id: 'v3',
      type: 'video',
      src: '/cdn/samples/videos/video-05.mp4',
      poster: '/cdn/samples/videos/video-poster-05.jpg',
      aspectRatio: 16 / 9,
    }],
    author: { name: 'Alex Johnson', avatar: '/cdn/samples/avatars/avatar-01.jpg' },
    likes: 7890,
    description: 'Living the moment',
  },
];

export default function App() {
  const [isOpen, setIsOpen] = useState(false);
  const [initialIndex, setInitialIndex] = useState(0);

  return (
    <div style={{ padding: 16, background: '#0f172a', minHeight: '100vh' }}>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
        {content.map((item, i) => (
          <button
            key={item.id}
            onClick={() => { setInitialIndex(i); setIsOpen(true); }}
            style={{
              position: 'relative', aspectRatio: '9 / 16', borderRadius: 8,
              overflow: 'hidden', border: 'none', padding: 0, cursor: 'pointer',
              background: '#1e293b',
            }}
          >
            <img
              src={item.media[0].poster || item.media[0].src}
              style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
            />
          </button>
        ))}
      </div>

      <ReelPlayerOverlay
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        content={content}
        initialIndex={initialIndex}
      />
    </div>
  );
}

Den ganzen Ablauf, ein Raster aus Vorschaubildern, das den Player öffnet und die Zuschauer dorthin zurückbringt, wo sie aufgehört haben, zeigt das Rezept „Vom Raster zum Player“.

Anpassung

Generischer Inhaltstyp

Eigene Datentypen nutzt du, indem du BaseContentItem erweiterst:

tsx
import { ReelPlayerOverlay, type BaseContentItem } from '@reelkit/react-reel-player';

interface MyItem extends BaseContentItem {
  title: string;
  username: string;
}

const items: MyItem[] = [
  {
    id: '1',
    media: [{ id: 'v1', type: 'video', src: '/video.mp4', aspectRatio: 9/16 }],
    title: 'My Video',
    username: '@user',
  },
];

<ReelPlayerOverlay<MyItem>
  isOpen={isOpen}
  onClose={handleClose}
  content={items}
  renderSlideOverlay={(item) => (
    <div style={{ position: 'absolute', bottom: 16, left: 16, color: '#fff' }}>
      <strong>{item.username}</strong>
      <p>{item.title}</p>
    </div>
  )}
/>

Eigenes Slide-Overlay

Ersetze das eingebaute Slide-Overlay durch eigene Inhalte pro Slide:

tsx
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderSlideOverlay={(item, index, isActive) => (
    <div style={{
      position: 'absolute',
      bottom: 0,
      left: 0,
      right: 0,
      padding: 16,
      background: 'linear-gradient(transparent, rgba(0,0,0,0.8))',
      color: '#fff',
    }}>
      <h3>{item.author.name}</h3>
      <p>{item.description}</p>
      <span>Slide {index + 1} {isActive ? '(active)' : ''}</span>
    </div>
  )}
/>

Slides ohne Medien

Mit renderSlide fügst du eigene Inhalte ein (zum Beispiel CTA-Karten). Gib null zurück, um auf die Standarddarstellung zurückzufallen:

tsx
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderSlide={({ index, size }) => {
    // CTA card on last slide
    if (index === content.length - 1) {
      return (
        <div style={{
          width: size[0],
          height: size[1],
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          background: 'linear-gradient(135deg, #667eea, #764ba2)',
          color: '#fff',
        }}>
          <div style={{ textAlign: 'center' }}>
            <h2>Follow for more!</h2>
            <button>Subscribe</button>
          </div>
        </div>
      );
    }
    // Fall back to default MediaSlide + overlay
    return null;
  }}
/>

Eigene Steuerelemente

Setze wiederverwendbare Unterkomponenten mit deinen eigenen Ergänzungen zusammen:

tsx
import {
  ReelPlayerOverlay,
  CloseButton,
  SoundButton,
} from '@reelkit/react-reel-player';

<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderControls={({ onClose, content, activeIndex }) => (
    <>
      <CloseButton onClick={onClose} />
      <SoundButton />
      <button
        onClick={() => share(content[activeIndex])}
        style={{
          position: 'absolute',
          bottom: 60,
          right: 16,
          zIndex: 10,
        }}
      >
        Share
      </button>
    </>
  )}
/>

Eigene Timeline

Ersetze die eingebaute Wiedergabeleiste über renderTimeline durch deine eigene Oberfläche zum Spulen. Der Callback wird nur aufgerufen, wenn die Regeln des Overlays die Standardleiste rendern würden (dieselbe Logik aus dem Modus timeline und timelineMinDurationSeconds), du musst sie also nicht nachbauen. Verwende die Klasse .rk-reel-timeline auf deinem Wurzelelement wieder, um die bündige Position am unteren Rand, das Padding für den Safe Area und den Abstand zum Slide-Overlay auf Touch-Geräten zu übernehmen.

tsx
import { useRef, useEffect } from 'react';
import { ReelPlayerOverlay } from '@reelkit/react-reel-player';
import { Observe } from '@reelkit/react';

function CustomTimelineBar({ timelineState }) {
  const trackRef = useRef(null);
  useEffect(() => {
    if (!trackRef.current) return;
    // Pointer + keyboard scrub wiring, same as the built-in bar.
    return timelineState.bindInteractions(trackRef.current);
  }, [timelineState]);

  return (
    <div className="rk-reel-timeline" style={{ padding: '0 16px' }}>
      <Observe signals={[timelineState.progress, timelineState.currentTime]}>
        {() => (
          <div
            ref={trackRef}
            role="slider"
            aria-valuenow={timelineState.currentTime.value}
            style={{ height: 6, background: 'rgba(255,255,255,0.2)' }}
          >
            <div style={{
              width: `${timelineState.progress.value * 100}%`,
              height: '100%',
              background: 'linear-gradient(90deg, #6366f1, #ec4899)',
            }} />
          </div>
        )}
      </Observe>
    </div>
  );
}

<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  timeline="always"
  renderTimeline={({ timelineState }) => (
    <CustomTimelineBar timelineState={timelineState} />
  )}
/>

Eigene Navigation

tsx
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderNavigation={({ onPrev, onNext, activeIndex, count }) => (
    <div style={{ position: 'absolute', right: 16, top: '50%', transform: 'translateY(-50%)' }}>
      <button onClick={onPrev} disabled={activeIndex === 0}>Up</button>
      <span>{activeIndex + 1}/{count}</span>
      <button onClick={onNext} disabled={activeIndex === count - 1}>Down</button>
    </div>
  )}
/>

Eigene verschachtelte Navigation

Ersetze die Pfeile nach links und rechts in Slides mit mehreren Medien (horizontales Karussell) durch eine eigene Navigation:

tsx
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderNestedNavigation={({ onPrev, onNext, activeIndex, count }) => (
    <div style={{
      position: 'absolute',
      bottom: 48,
      left: 0,
      right: 0,
      display: 'flex',
      justifyContent: 'center',
      gap: 8,
      zIndex: 10,
    }}>
      <button onClick={onPrev} disabled={activeIndex === 0}>Prev</button>
      <span>{activeIndex + 1} / {count}</span>
      <button onClick={onNext} disabled={activeIndex === count - 1}>Next</button>
    </div>
  )}
/>

Eigene verschachtelte Slides

Passe einzelne Slides in Karussells mit mehreren Medien mit renderNestedSlide an. Mit props.defaultContent umschließt du das Standard-ImageSlide/VideoSlide, oder du ersetzt es ganz:

tsx
// Wrap default slides with rounded corners
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderNestedSlide={({ defaultContent }) => (
    <div style={{ borderRadius: 16, overflow: 'hidden' }}>
      {defaultContent}
    </div>
  )}
/>

// Fully custom nested slide for images
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderNestedSlide={({ item, size, isActive, slideKey, onVideoRef, defaultContent }) => {
    if (item.type === 'video') return defaultContent; // keep default video
    return (
      <ImageSlide
        src={item.src}
        size={size}
        imgStyle={{ objectFit: 'contain' }}
        style={{ backgroundColor: '#111' }}
      />
    );
  }}
/>

URL-State

Live-Demo ansehen →

ReelPlayerUrlOverlay ist eine eigene Komponente, deren Öffnungszustand in der Adressleiste liegt. Baue mit useOverlayUrlState aus @reelkit/react einen Controller und übergib ihn als controller: Der Player öffnet sich selbst, wenn der Parameter einen Slide nennt, und schließt, wenn er verschwindet. Links lassen sich teilen, und der Zurück-Button schließt den Player, statt die Seite zu verlassen.

Eingebaute Schlüssel

Slides kannst du mit einem eingebauten Schlüssel ansprechen – spreize urlIndexKey (nach Position) oder urlStableIdKey (nach einer stabilen id) in den Controller –, beide werden aus @reelkit/react erneut exportiert. Siehe den Leitfaden zum URL-State und die Core-API.

tsx
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/react';
import { ReelPlayerUrlOverlay } from '@reelkit/react-reel-player';
import { Link } from 'react-router-dom';

const reel = useOverlayUrlState({
  param: 'reel',
  ...urlIndexKey(() => content.length),
});

// Opening is a link — the overlay reads the URL and opens itself.
{content.map((item, i) => (
  <Link key={item.id} to={`?reel=${i}`}>
    <img src={getThumbnail(item)} />
  </Link>
))}

<ReelPlayerUrlOverlay controller={reel} content={content} />

Alle Optionen von useOverlayUrlState – param, adapter, codec, locator – stehen in der React-API-Referenz. Die Schritt-für-Schritt-Erklärung steht im React-Leitfaden.

  • Das Öffnen legt einen Eintrag im Verlauf an. Das Wischen durch den Feed ersetzt ihn, N Wischgesten fügen also keine Einträge hinzu, und ein Schritt zurück verlässt immer den Player. Zurück schließt; es blättert nicht durch die Slides.
  • Zurück schließt nur, wenn der Player innerhalb der App geöffnet wurde – der Link hat einen Eintrag angelegt. Ein geteilter Link, der direkt in einem neuen Tab geöffnet wird, hat keinen Verlauf hinter sich, daher verlässt Zurück im Browser die Seite; der Button ✕ oder Escape entfernt den Parameter an Ort und Stelle und bleibt.
  • Ein Deep Link ?reel=3 öffnet den Player beim Laden auf diesem Slide.
  • Ein Parameter, der keinen Slide nennt – ein veraltetes Lesezeichen, ein von Hand geänderter Wert –, wird aus der URL entfernt, statt die Adressleiste einen Slide behaupten zu lassen, der sich nicht öffnen lässt.
  • Standardmäßig spricht der Parameter nur den vertikalen Beitrag an (?reel=3). Mit einem Schlüssel für zwei Achsen trägt er auch den inneren Medienindex eines Karussells mit mehreren Medien – siehe unten.

Ein Schlüssel oder zwei: wähle die Tiefe der URL

Dasselbe ReelPlayerUrlOverlay bedient beide Formen; es unterscheidet zur Laufzeit anhand der Position des Controllers, daher gibt es kein Prop für den Modus. Den Schlüssel wählst du, wenn du den Controller baust:

SchlüsselFormatTrägt
urlIndexKey(…)?reel=3Nur den vertikalen Beitrag.
urlIndexTwoAxisKey(…)?reel=3.2Den Beitrag und den inneren Medienindex eines Karussells.

Die beiden Formate sind bewusst verschieden – ein Schlüssel für zwei Achsen hat immer einen Punkt (3.0, nie ein einfaches 3), sodass ein einfacher Link für eine Achse nicht als der andere decodiert wird. Wechselt eine App den Schlüssel, werden deshalb alle zuvor geteilten Links ungültig. Wähle eine Form und bleib dabei.

tsx
import { useOverlayUrlState, urlIndexTwoAxisKey } from '@reelkit/react';

const reel = useOverlayUrlState({
  param: 'reel',
  ...urlIndexTwoAxisKey({
    outerCount: () => content.length,
    innerCounts: () => content.map((post) => post.media.length),
  }),
});

// A link now names both axes: post 3, inner media 2.
<Link to="?reel=3.2">…</Link>
<ReelPlayerUrlOverlay controller={reel} content={content} />

App mit Router – übergib einen Adapter. Wer am Router vorbei history.pushState schreibt, lässt dessen Location veralten, und seine nächste Navigation verliert den Parameter:

tsx
import { useReactRouterUrlAdapter } from '@reelkit/react/react-router-url-adapter';

const adapter = useReactRouterUrlAdapter();
const reel = useOverlayUrlState({
  param: 'reel',
  adapter,
  ...urlIndexKey(() => content.length),
});

<ReelPlayerUrlOverlay controller={reel} content={content} />

Stabile Links. Der Index richtet sich nach der Position, ein gespeichertes ?reel=3 öffnet also einen anderen Beitrag, sobald der Feed umsortiert wird – für einen Feed ist das der Normalfall, nicht die Ausnahme. urlStableIdKey verwendet die stabile id jedes Beitrags als Schlüssel und durchsucht den aktuellen Feed – ein Aufruf deckt den üblichen Fall ab.

tsx
const reel = useOverlayUrlState({
  param: 'reel',
  ...urlStableIdKey({ items: () => content }),
});

Übergib hashCodec: base64UrlCodec, um die id in der URL als base64url zu codieren – eine umkehrbare Verschleierung, kein kryptografischer Hash.

Willst du ein anderes Feld als Schlüssel (einen slug) oder einen endlosen Feed mit locateAsync nachladen, baust du codec/locator selbst. Das sind zwei getrennte Aufgaben: codec schreibt die Identität in die URL, locator findet, wo diese Identität liegt.

tsx
const reel = useOverlayUrlState({
  param: 'reel',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => content.findIndex((x) => x.id === id),
    identify: (index) => content[index].id,
  },
});

Endlose Feeds. locate ist synchron und kann deshalb nur für bereits geladene Beiträge antworten – ein geteilter Link auf Beitrag 400 eines Feeds, der erst 20 geladen hat, geht ins Leere. locateAsync ist der Fallback und wird nur aufgerufen, wenn locate nichts findet.

Abkürzung

Du verwendest die id des Eintrags als Schlüssel? Spar dir den selbst gebauten codec und locator – übergib locateAsync direkt an urlStableIdKey({ items, locateAsync }) (es lädt bei einem Fehlschlag nach und gibt dann den Index zurück). Die ausführlichere Variante unten ist für ein anderes Feld als Schlüssel oder für volle Kontrolle.

tsx
const reel = useOverlayUrlState({
  param: 'reel',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => content.findIndex((x) => x.id === id),
    identify: (index) => content[index].id,
    locateAsync: async (id) => {
      const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
      if (!loaded) return null; // exhausted — link names no post
      setContent(loaded); // commit — the overlay renders from this state
      return loaded.findIndex((x) => x.id === id); // wherever it landed
    },
  },
});
  • Solange locateAsync läuft, bleibt der Player geschlossen und der Parameter unangetastet, damit der Deep Link das Laden übersteht. null oder eine Ablehnung entfernt den Parameter.
  • Eine Antwort, die eintrifft, nachdem die URL sich weiterbewegt hat, nach dem Schließen oder nach dem Unmount, wird verworfen – ein langsames Laden kann keinen Slide öffnen, nach dem niemand gefragt hat.
  • Während des Wartens wird nichts gerendert; den Ladezustand besitzt bereits die Seite, rendere also dein eigenes Skeleton.
  • Es gibt kein Timeout – der Player kann nicht wissen, wie lang der Feed ist. Antworte mit null, wenn die Paginierung erschöpft ist, sonst bleibt das Overlay auf unbestimmte Zeit geschlossen.

API-Referenz

Props von ReelPlayerOverlayProps

ReelPlayerOverlayProps<T>

ReelPlayerOverlayProps ist ReelPlayerOverlayBaseProps (die Props für Darstellung und Verhalten, die beide Overlays teilen) verbunden mit ReelPlayerControlledProps (isOpen und onClose). Typisiere mit dem Basistyp einen Wrapper, der eines der beiden Overlays rendert.

PropTypStandardBeschreibung
apiRefMutableRefObject<ReelApi>-Ref für den Zugriff auf die Reel-API
ariaLabelstring'Video player'Zugängliche Beschriftung der Dialog-Region; Screenreader lesen sie vor, wenn sich das Overlay öffnet
aspectRationumber9/16 (0.5625)Verhältnis von Breite zu Höhe des Player-Containers am Desktop. Bei 768px Breite und schmaler nutzt der Player immer den ganzen Viewport. Ein Wert von null, ein negativer oder ein nicht endlicher Wert fällt auf 9/16 zurück.
contentT[]PflichtArray der Inhaltseinträge (generisch, Standard ist ContentItem)
initialIndexnumber0Index des Slides zum Start
initialInnerIndexnumber0Innerer Medienindex, bei dem geöffnet wird, nur für den anfangs sichtbaren Beitrag – damit kann eine URL mit zwei Achsen per Deep Link auf ein bestimmtes Bild eines Beitrags mit mehreren Medien zeigen. Wird ignoriert, sobald der Player offen ist und der Nutzer navigiert.
isOpenbooleanPflichtSteuert die Sichtbarkeit des Overlays. Soll die URL steuern, ob der Player offen ist, greif stattdessen zum eigenen ReelPlayerUrlOverlay – siehe URL-State oben.
timeline'auto' | 'always' | 'never''auto'Strategie, wann die eingebaute Wiedergabeleiste erscheint. 'auto' rendert sie nur für Videos, die länger als timelineMinDurationSeconds sind; 'always' rendert sie immer, wenn der aktive Slide ein Video hat; 'never' schaltet die eingebaute Leiste ab (für einen komplett eigenen Ersatz nimm renderTimeline).
timelineMinDurationSecondsnumber30Mindestdauer des Videos (Sekunden), ab der timeline='auto' die eingebaute Leiste rendert. Kurze Clips in Schleife unter dieser Schwelle bekommen keine.
renderControls(props: ControlsRenderProps) => ReactNode-Eigene Steuerelemente, ersetzt die Standard-Buttons zum Schließen und für den Ton
renderError(props: { item: T; activeIndex: number }) => ReactNode-Eigene Fehleranzeige, ersetzt das Standard-Fehlericon
renderLoading(props: { item: T; activeIndex: number }) => ReactNode-Eigene Ladeanzeige, ersetzt den Standard-Wellenlader
renderNavigation(props: NavigationRenderProps) => ReactNode-Eigene Navigation, ersetzt die Standard-Pfeile nach oben und unten
renderNestedNavigation(props: NavigationRenderProps) => ReactNode-Eigene Navigation für den verschachtelten horizontalen Slider (Beiträge mit mehreren Medien), ersetzt die Standard-Pfeile nach links und rechts
renderNestedSlide(props: NestedSlideRenderProps) => ReactNode-Eigener Renderer für die Einträge des verschachtelten horizontalen Sliders. Mit props.defaultContent umschließt oder bettest du das Standard-ImageSlide/VideoSlide ein. Anders als bei renderSlide gilt null hier nicht als Rückfall.
renderSlide(props: SlideRenderProps) => ReactNode | null-Eigenes Rendern der Slides. Gib null zurück, um auf die Standarddarstellung zurückzufallen. Mit props.defaultContent umschließt oder bettest du den Standard-Slide ein.
renderSlideOverlay(item, index, isActive) => ReactNode-Eigenes Overlay pro Slide, ersetzt das Standard-SlideOverlay. Gib null zurück, um es auszublenden.
renderTimeline(props: TimelineRenderProps) => ReactNode-Eigene Wiedergabeleiste. Wird nur aufgerufen, wenn die Regeln die Standardleiste rendern würden (dieselbe Logik aus auto/always/never und timelineMinDurationSeconds). Mit props.defaultContent umschließt du das eingebaute <TimelineBar />; gib null zurück, um sie auszublenden.

Props von ReelPlayerUrlOverlay

ReelPlayerUrlOverlayProps<T>

Nimmt jedes Prop für Darstellung und Verhalten von oben außer isOpen, das durch einen controller ersetzt wird: Der Typ ist ReelPlayerOverlayBaseProps verbunden mit ReelPlayerUrlControlledProps. initialIndex wird ignoriert – die Position des Controllers bestimmt den Slide, ein zusätzlich übergebener Wert wird also bei jedem Öffnen überschrieben.

PropTypStandardBeschreibung
controllerUrlStateControllerrequiredController aus useOverlayUrlState. Seine Position entscheidet, ob das Overlay offen ist und welchen Slide es zeigt; beim Slide-Wechsel und beim Schließen schreibt das Overlay über ihn zurück.

Callbacks

PropTypBeschreibung
onClose() => voidWird aufgerufen, wenn der Player schließt. Pflicht bei ReelPlayerOverlay (du steuerst, ob er offen ist, und musst das Schließen also selbst behandeln); optional bei ReelPlayerUrlOverlay, wo die URL das Schließen steuert – übergib ihn nur, um nach dem Schließen zu reagieren.
onSlideChange(index: number) => voidWird nach dem Slide-Wechsel aufgerufen
onInnerSlideChange(outerIndex: number, innerIndex: number) => voidWird aufgerufen, wenn sich der innere Medienindex des aktiven Beitrags ändert – bei der inneren Navigation in einem Beitrag mit mehreren Medien und beim Aktivieren eines äußeren Beitrags, dann mit dessen aktuellem inneren Index (0 für einen Beitrag mit nur einem Medium).

Props von Reel (durchgereicht)

Diese Props werden an die darunterliegende Komponente Reel weitergegeben. Ihr Typ ist ReelProxyProps, die Teilmenge der Props von Reel, die das Overlay durchreicht; typisiere damit einen Wrapper, der sie weitergibt.

PropTypStandardBeschreibung
enableNavKeysbooleantrueSchaltet die Tastaturnavigation ein
enableWheelbooleantrueSchaltet die Navigation mit dem Mausrad ein
loopbooleanfalseSchaltet die Endlosschleife ein
swipeDistanceFactornumber0.12Schwelle für das Wischen (0-1)
transitionDurationnumber300Dauer der Übergangsanimation (ms)
wheelDebounceMsnumber200Dauer des Debounce für das Mausrad (ms)

Typen

BaseContentItem

Der Typ für die generische Einschränkung. Erweitere ihn, um eigene Datentypen mit ReelPlayerOverlay zu nutzen.

typescript
interface BaseContentItem {
  id: string;
  media: MediaItem[];
}

ContentItem

typescript
interface ContentItem extends BaseContentItem {
  author: {
    name: string;
    avatar: string;
  };
  likes: number;
  description: string;
}

MediaItem

typescript
interface MediaItem {
  id: string;
  type: 'image' | 'video';
  src: string;
  poster?: string;
  aspectRatio: number;
}

MediaType

typescript
type MediaType = 'image' | 'video';

ControlsRenderProps<T>

typescript
interface ControlsRenderProps<T extends BaseContentItem> {
  onClose: () => void;
  soundState: SoundState;
  activeIndex: number;
  content: T[];
}
typescript
interface NavigationRenderProps {
  onPrev: () => void;
  onNext: () => void;
  activeIndex: number;
  count: number;
}

SlideRenderProps<T>

typescript
interface SlideRenderProps<T extends BaseContentItem> {
  item: T;
  index: number;
  size: [number, number];
  isActive: boolean;
  slideKey: string;
  onVideoRef?: (ref: HTMLVideoElement | null) => void;
  innerSliderRef: MutableRefObject<ReelApi | null>;
  onActiveMediaTypeChange?: (type: 'image' | 'video') => void;
  renderNestedNavigation?: (props: NavigationRenderProps) => ReactNode;
  enableWheel?: boolean;
  defaultContent: ReactNode;
}

NestedSlideRenderProps

typescript
interface NestedSlideRenderProps {
  item: MediaItem;
  index: number;
  size: [number, number];
  isActive: boolean;
  isInnerActive: boolean;
  slideKey: string;
  onVideoRef?: (ref: HTMLVideoElement | null) => void;
  defaultContent: ReactNode;
}

SlideOverlayProps

typescript
interface SlideOverlayProps {
  author?: { name: string; avatar: string };
  description?: string;
  likes?: number;
}

ImageSlideProps

typescript
interface ImageSlideProps {
  src: string;
  size: [number, number];
  className?: string;
  style?: CSSProperties;
  imgClassName?: string;
  imgStyle?: CSSProperties;
}

VideoSlideProps

typescript
interface VideoSlideProps {
  src: string;
  poster?: string;
  aspectRatio: number;
  size: [number, number];
  isActive: boolean;
  isInnerActive?: boolean;   // default: true
  slideKey: string;
  onVideoRef?: (ref: HTMLVideoElement | null) => void;
  className?: string;
  style?: CSSProperties;
}

CloseButtonProps

typescript
interface CloseButtonProps {
  onClick: () => void;
  className?: string;
  style?: React.CSSProperties;
}

SoundButtonProps

typescript
interface SoundButtonProps {
  disabled?: boolean;
  className?: string;
  style?: React.CSSProperties;
}

TimelineBarProps

typescript
interface TimelineBarProps {
  className?: string;
  style?: React.CSSProperties;
}

TimelineRenderProps<T>

typescript
interface TimelineRenderProps<T extends BaseContentItem> {
  item: T;
  activeIndex: number;
  timelineState: TimelineController;
  defaultContent: ReactNode;
}

Unterkomponenten

Wiederverwendbare Bausteine, exportiert zum Zusammensetzen in eigenen Render-Props:

CloseButton

Eigenständiger Button zum Schließen im Standardstil des Players. Zur Verwendung in renderControls.

tsx
import { CloseButton } from '@reelkit/react-reel-player';

<CloseButton onClick={onClose} />
<CloseButton onClick={onClose} className="my-close-btn" style={{ top: 24, right: 24 }} />

SoundButton

Eigenständiger Schalter für den Ton. Muss in einem SoundProvider liegen (den ReelPlayerOverlay automatisch bereitstellt).

tsx
import { SoundButton } from '@reelkit/react-reel-player';

<SoundButton />
<SoundButton disabled className="my-sound-btn" />

TimelineBar

Standardleiste zum Spulen der Wiedergabe. Liest aus dem nächsten TimelineProvider (der in ReelPlayerOverlay automatisch gemountet ist) und rendert die Spur, die gepufferten Bereiche, die Fortschrittsfüllung und den Griff zum Spulen. Gestalte sie über die Custom Properties --rk-reel-timeline-*, oder ersetze sie über renderTimeline.

tsx
import { TimelineBar } from '@reelkit/react-reel-player';

// Inside renderTimeline — wrap or augment the default bar:
<ReelPlayerOverlay
  renderTimeline={({ defaultContent }) => (
    <>
      <MyTimecode />
      {defaultContent}
    </>
  )}
/>

// Or render standalone inside a custom TimelineProvider tree:
<TimelineBar className="my-timeline" />

SlideOverlay

Das Standard-Overlay mit Verlauf, das Autor, Beschreibung und Likes zeigt. Wird automatisch gerendert, wenn der Inhalt die nötigen Felder hat. Mit renderSlideOverlay ersetzt oder versteckst du es.

tsx
import { SlideOverlay } from '@reelkit/react-reel-player';

<SlideOverlay
  author={{ name: 'John', avatar: '/avatar.jpg' }}
  description="Amazing content"
  likes={12500}
/>

ImageSlide

Bild-Slide mit Lazy Loading und standardmäßig object-fit: cover. Zur Verwendung in renderSlide, um eigene Bild-Slides mit deinen eigenen Styles zusammenzusetzen.

tsx
import { ImageSlide } from '@reelkit/react-reel-player';

// Default usage
<ImageSlide src="/photo.jpg" size={[400, 700]} />

// Custom styles
<ImageSlide
  src="/photo.jpg"
  size={[400, 700]}
  className="my-image-slide"
  style={{ backgroundColor: '#1a1a1a', borderRadius: 12 }}
  imgStyle={{ objectFit: 'contain' }}
/>

VideoSlide

Video-Slide mit gemeinsamem <video>-Element für durchgehenden Ton auf iOS, Poster-Frames, Positionsgedächtnis und Ladeanzeige. Muss in einem SoundProvider liegen (den ReelPlayerOverlay automatisch bereitstellt).

tsx
import { VideoSlide } from '@reelkit/react-reel-player';

<VideoSlide
  src="/video.mp4"
  poster="/thumb.jpg"
  aspectRatio={9 / 16}
  size={[400, 700]}
  isActive={true}
  slideKey="slide-1"
  style={{ borderRadius: 12 }}
/>
Eigene Slides zusammensetzen

Nutze renderSlide mit ImageSlide / VideoSlide, um das Rendern der Medien anzupassen und dabei das ganze eingebaute Verhalten zu behalten (Autoplay, Poster-Erfassung, Ton-Synchronisierung).

tsx
import {
  ReelPlayerOverlay,
  ImageSlide,
  VideoSlide,
} from '@reelkit/react-reel-player';

<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={handleClose}
  content={content}
  renderSlide={({ item, size, isActive, slideKey, onVideoRef }) => {
    const media = item.media[0];
    if (media.type === 'image') {
      return (
        <ImageSlide
          src={media.src}
          size={size}
          imgStyle={{ objectFit: 'contain' }}
          style={{ backgroundColor: '#111' }}
        />
      );
    }
    if (media.type === 'video') {
      return (
        <VideoSlide
          src={media.src}
          poster={media.poster}
          aspectRatio={media.aspectRatio}
          size={size}
          isActive={isActive}
          slideKey={slideKey}
          onVideoRef={onVideoRef}
          style={{ borderRadius: 16 }}
        />
      );
    }
    return null; // fallback to default
  }}
/>

Laden von Inhalten und Fehlerbehandlung

Der Player verfolgt Lade- und Fehlerzustände pro Slide. Während Inhalte laden, erscheint ein Wellenlader; bei defekten Medien ein Fehlericon. Fehlerhafte URLs werden zwischengespeichert, sodass ein erneuter Besuch den Fehler sofort zeigt, ohne es noch einmal zu versuchen.

Callbacks für den Lebenszyklus

Wenn du renderSlide nutzt, rufst du diese Callbacks auf, um die Ladeanzeige zu steuern:

CallbackWann aufrufen
onReadyDas Bild ist geladen oder das Video spielt. Setzt Lade- und Fehlerzustand zurück.
onWaitingDas Video puffert mitten in der Wiedergabe. Zeigt die Ladeanzeige.
onErrorDer Inhalt ließ sich nicht laden. Zeigt das Fehler-Overlay und merkt sich die URL als defekt.
tsx
// Inside renderSlide — wire callbacks to your custom media
renderSlide={({ item, size, isActive, onReady, onWaiting, onError }) => (
  <div style={{ width: size[0], height: size[1] }}>
    {item.media[0].type === 'image' ? (
      <img
        src={item.media[0].src}
        onLoad={onReady}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'cover' }}
      />
    ) : (
      <video
        src={item.media[0].src}
        autoPlay={isActive}
        onCanPlay={onReady}
        onWaiting={onWaiting}
        onError={onError}
        style={{ width: '100%', height: '100%', objectFit: 'cover' }}
      />
    )}
  </div>
)}

Eigene Oberfläche für Laden und Fehler

Ersetze den Standard-Wellenlader und das Fehlericon durch eigene Komponenten:

tsx
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  content={content}
  renderLoading={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: '#fff', fontSize: 14,
    }}>
      Loading slide {activeIndex + 1}...
    </div>
  )}
  renderError={({ item, activeIndex }) => (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 10,
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      gap: 12, color: 'rgba(255,255,255,0.5)',
    }}>
      <span style={{ fontSize: 48 }}>!</span>
      <span>Failed to load media</span>
    </div>
  )}
/>

Timeline

Das Overlay rendert eine eingebaute Wiedergabeleiste über dem aktiven Video. Das Prop timeline vom Typ TimelineMode steuert, wann sie erscheint:

  • 'auto' (Standard): rendert, wenn das aktive Medium ein Video ist, dessen Dauer timelineMinDurationSeconds überschreitet (Standard 30). Funktioniert für Slides mit einem einzelnen Video und für Karussells mit mehreren Medien; die Leiste folgt dem aktiven verschachtelten Eintrag und verschwindet bei Bildern.
  • 'always': rendert immer, wenn der aktive Slide ein Video hat.
  • 'never': rendert nie. Eine eigene Leiste baust du über renderTimeline.
tsx
<ReelPlayerOverlay
  isOpen={isOpen}
  onClose={close}
  content={items}
  timeline="auto"
  timelineMinDurationSeconds={30}
/>

Gestalte sie über die CSS Custom Properties --rk-reel-timeline-* (Höhe, Farben, Größe des Griffs). Für eine komplett eigene Leiste zum Spulen, einen Timecode oder eine Fortschrittsanzeige nutzt du renderTimeline; der Callback bekommt einen timelineState, hinter dem der zugrunde liegende TimelineController steht.

Eine Komponente, die im Overlay gerendert wird, kann denselben Controller selbst lesen: useTimelineState() holt ihn aus dem nächsten TimelineProvider (den das Overlay mountet) und wirft außerhalb davon einen Fehler; useTimelineStateOptional() gibt dort stattdessen null zurück, für Komponenten, die auch außerhalb des Players gerendert werden.

Ton-Context

Für eigene Implementierungen kannst du auf den Zustand des Tons zugreifen:

tsx
import { SoundProvider, useSoundState } from '@reelkit/react';

// ReelPlayerOverlay wraps itself in a SoundProvider.
// Access sound state inside custom controls:
function CustomControls() {
  const soundState = useSoundState();

  return (
    <button onClick={soundState.toggle}>
      {soundState.muted.value ? 'Unmute' : 'Mute'}
    </button>
  );
}

CSS-Klassen

Alle CSS-Klassen sind einfache Klassen (keine CSS Modules), du kannst sie also mit spezifischeren Selektoren in einem Stylesheet ansprechen, das nach @reelkit/react-reel-player/styles.css geladen wird. Für Änderungen an Farbe, Größe und z-index nimm lieber die CSS Custom Properties aus dem Abschnitt Theming unten – genau dafür sind sie gemacht.

KlasseKomponenteBeschreibung
.rk-reel-overlayOverlayFester Hintergrund im Vollbild (Hintergrund, z-index)
.rk-reel-containerOverlayContainer des Players (Position, Overflow)
.rk-reel-loaderOverlayOverlay mit der Wellen-Ladeanimation
.rk-reel-media-errorOverlayOverlay für den Fehlerzustand (zentriertes Icon + Text)
.rk-reel-media-error-textOverlayText der Fehlermeldung
.rk-reel-buttonControlsGemeinsamer runder Icon-Button (Schließen, Ton, Navigationspfeile)
.rk-reel-close-btnControlsButton zum Schließen
.rk-reel-sound-btnControlsButton zum Umschalten des Tons
.rk-reel-nav-arrowsNavigationContainer der Pfeile, nur am Desktop (versteckt bei 768px Breite und schmaler)
.rk-reel-nav-buttonNavigationEinzelner Navigationspfeil für vorher/weiter
.rk-reel-slide-wrapperSlideHülle um Medium + Overlay
.rk-reel-slide-overlaySlideOverlayContainer des Overlays mit Verlauf
.rk-reel-slide-overlay-authorSlideOverlayZeile mit dem Autor (Avatar + Name)
.rk-reel-slide-overlay-avatarSlideOverlayAvatarbild des Autors
.rk-reel-slide-overlay-nameSlideOverlayText mit dem Namen des Autors
.rk-reel-slide-overlay-descriptionSlideOverlayText der Beschreibung
.rk-reel-slide-overlay-likesSlideOverlayZeile mit den Likes (Herz + Anzahl)
.rk-reel-video-containerVideoSlideHülle des Videos (Hintergrund, Overflow)
.rk-reel-video-elementVideoSlideDas Element <video>
.rk-reel-video-posterVideoSlidePosterbild (blendet bei der Wiedergabe aus)
.rk-reel-video-poster.rk-visibleVideoSlideZustands-Modifier auf dem Poster, solange das Video pausiert oder lädt
.rk-reel-nested-indicatorNestedSliderPunkt-Paginierung unter Slides mit mehreren Medien (Position je nach Desktop oder Touch)
.rk-reel-nested-navNestedSliderPfeile des horizontalen Karussells (versteckt bei 768px Breite und schmaler)
.rk-reel-nested-nav-nextNestedSliderPosition des verschachtelten Pfeils weiter
.rk-reel-nested-nav-prevNestedSliderPosition des verschachtelten Pfeils zurück
.rk-reel-timelineTimelineBarHülle der Leiste zum Spulen. Auf den Wurzelelementen eines eigenen `renderTimeline` wiederverwenden, um die bündige Position am unteren Rand, das Padding für den Safe Area und den Abstand zum Slide-Overlay auf Touch-Geräten zu übernehmen.
.rk-reel-timeline-trackTimelineBarSpur (noch nicht gespielter Bereich)
.rk-reel-timeline-bufferedTimelineBarEbene der gepufferten Abschnitte
.rk-reel-timeline-fillTimelineBarFüllung für den gespielten Fortschritt
.rk-reel-timeline-cursorTimelineBarGriff zum Spulen (schwebt über der Spur)

Theming

Jede Farbe, Größe, jeder z-index und jeder Übergang liegt in einer CSS Custom Property. Überschreibe eine oder mehrere auf :root (oder einem beliebigen Vorfahren des Overlays), um das Theme zu ändern, ohne den Quellcode der Komponente anzufassen.

TokenStandardSteuert
--rk-reel-overlay-bg#000Farbe des Hintergrunds im Vollbild
--rk-reel-overlay-z1000z-index des Overlays
--rk-reel-button-bgrgba(0, 0, 0, 0.5)Hintergrund des runden Standard-Buttons
--rk-reel-button-bg-hoverrgba(255, 255, 255, 0.1)Hintergrund der Navigationspfeile (und Basis für den Hover-Zustand)
--rk-reel-button-bg-hover-strongrgba(255, 255, 255, 0.2)Hintergrund der Navigationspfeile beim Hover
--rk-reel-button-fg#fffFarbe der Button-Icons
--rk-reel-button-size44pxBreite / Höhe der Buttons
--rk-reel-button-radius50%border-radius der Buttons
--rk-reel-ui-z10z-index von Schließen / Ton / Navigation
--rk-reel-edge-padding16pxAbstand zum Rand für Schließen / Ton / Navigationspfeile
--rk-reel-nav-gap8pxAbstand zwischen den übereinanderliegenden Navigationspfeilen
--rk-reel-transition0.2sDauer des Hover-Übergangs
--rk-reel-loader-colorrgba(255, 255, 255, 0.12)Farbe des Verlaufs im Wellenlader
--rk-reel-loader-duration1.8sDauer der Animation des Wellenladers
--rk-reel-error-fgrgba(255, 255, 255, 0.4)Farbe von Fehlericon und -text
--rk-reel-error-text-size13pxSchriftgröße der Fehlermeldung
--rk-reel-slide-overlay-bglinear-gradient(transparent, rgba(0, 0, 0, 0.7))Verlauf hinter der Bildunterschrift
--rk-reel-slide-overlay-padding48px 16px 16pxInnenabstand der Bildunterschrift
--rk-reel-slide-overlay-name-color#fffFarbe des Autorennamens
--rk-reel-slide-overlay-description-colorrgba(255, 255, 255, 0.9)Farbe des Beschreibungstexts
--rk-reel-slide-overlay-likes-colorrgba(255, 255, 255, 0.8)Textfarbe der Zeile mit den Likes
--rk-reel-video-bg#000Hintergrund der Letterbox hinter <video>
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)Hintergrund der verschachtelten Pfeile
--rk-reel-nested-button-bg-hoverrgba(255, 255, 255, 0.2)Hintergrund der verschachtelten Pfeile beim Hover
--rk-reel-nested-button-size36pxGröße der verschachtelten Pfeile
--rk-reel-nested-edge-padding12pxAbstand der verschachtelten Pfeile zum Rand
--rk-reel-timeline-trackrgba(255, 255, 255, 0.22)Hintergrund der Spur (noch nicht gespielter Bereich)
--rk-reel-timeline-bufferedrgba(255, 255, 255, 0.4)Farbe der gepufferten Abschnitte
--rk-reel-timeline-fill#fffFarbe der Füllung für den gespielten Fortschritt
--rk-reel-timeline-cursor#fffFarbe des Griffs zum Spulen
--rk-reel-timeline-height3pxHöhe der Spur in Ruhe
--rk-reel-timeline-height-active6pxHöhe der Spur bei Hover / Fokus / Spulen
--rk-reel-timeline-cursor-width10pxBreite des Griffs in Ruhe
--rk-reel-timeline-cursor-width-active14pxBreite des Griffs beim Spulen
--rk-reel-timeline-cursor-height24pxHöhe des Griffs in Ruhe
--rk-reel-timeline-cursor-height-active32pxHöhe des Griffs beim Spulen
--rk-reel-timeline-hitbox16pxZusätzliche Trefferfläche für den Zeiger über der Spur
--rk-reel-timeline-transition0.15s ease-outAnimation für das Wachsen und Schrumpfen von Spur und Griff
--rk-reel-timeline-z11z-index der Timeline (über der Standardebene der UI)

Füge das Snippet unten in ein Stylesheet ein, das nach @reelkit/react-reel-player/styles.css geladen wird.

css
/* Brand the reel-player overlay */
:root {
  --rk-reel-overlay-bg: #0f172a;
  --rk-reel-button-bg: rgba(99, 102, 241, 0.65);
  --rk-reel-button-bg-hover-strong: rgba(168, 85, 247, 0.85);
  --rk-reel-edge-padding: 24px;
  --rk-reel-button-size: 52px;

  /* Timeline bar: brand-matched, beefier on desktop */
  --rk-reel-timeline-track: rgba(99, 102, 241, 0.25);
  --rk-reel-timeline-buffered: rgba(168, 85, 247, 0.45);
  --rk-reel-timeline-fill: #a855f7;
  --rk-reel-timeline-cursor: #a855f7;
  --rk-reel-timeline-height: 4px;
  --rk-reel-timeline-height-active: 8px;
  --rk-reel-timeline-cursor-width-active: 18px;
  --rk-reel-timeline-transition: 0.2s ease-out;
}

Barrierefreiheit

Das Wurzelelement des Overlays ist ein modaler Dialog (role="dialog", aria-modal="true"). Mit ariaLabel änderst du die Ansage für Screenreader; standardmäßig lautet sie "Video player". Jeder Slide trägt role="group", aria-roledescription="slide" und ein aria-label="Slide N of M", sodass beim Wischen die Position in der Abfolge angesagt wird.

Das Overlay fängt beim Öffnen den Fokus ein und gibt ihn beim Schließen an den Auslöser zurück. Tab und Shift+Tab wandern durch die fokussierbaren Elemente darin; Fokus, der entkommt (Klick nach außen, Fokus aus dem Code), wird zurückgeholt. Umgesetzt mit captureFocusForReturn und createFocusTrap aus @reelkit/core.

Tastenkürzel

TasteAktion
ArrowUpVorheriger Slide
ArrowDownNächster Slide
ArrowLeftVorheriges Medium (im verschachtelten Slider)
ArrowRightNächstes Medium (im verschachtelten Slider)
EscapePlayer schließen