Start · Sprachen · JavaScript · Referenz · HTMLMediaElement: canplay

HTMLMediaElement: canplay

Ereignis

Wird ausgelöst, sobald ein Audio- oder Videoelement mit der Wiedergabe beginnen kann, voraussichtlich aber noch einmal zum Nachpuffern anhalten muss.

seit JavaScript Baseline: alle Browser Kategorie: webapi

Signatur

mediaElement.addEventListener("canplay", handler)

Beschreibung

Das Ereignis canplay feuert an einem HTMLMediaElement (also an <audio> oder <video>), sobald der Browser genug Daten hat, um die Wiedergabe zu starten. Das entspricht einem readyState von mindestens HAVE_FUTURE_DATA (3). Der Browser schätzt dabei aber, dass die Daten nicht reichen, um das Medium ohne weiteres Puffern bis zum Ende abzuspielen. Wenn er glaubt, dass es ohne Unterbrechung klappt, feuert er zusätzlich canplaythrough.

Typische Anwendungsfälle:

  • Einen Lade-Spinner ausblenden und den Play-Button freischalten
  • Die Wiedergabe per Skript starten, sobald sie möglich ist
  • Ladezeiten messen, etwa die Zeit bis zum ersten abspielbaren Zustand

Du kannst das Ereignis mit addEventListener("canplay", handler) abonnieren oder über die Eigenschaft oncanplay setzen. Es lässt sich nicht abbrechen und steigt nicht im DOM auf (bubbles: false). Delegation über ein Elternelement klappt also nur in der Capture-Phase.

Das Ereignis kann mehrfach feuern, zum Beispiel nach einem Sprung (seeked), nach einem Pufferengpass (waiting) oder nach einem Wechsel der Quelle. Code, der nur einmal laufen soll, registrierst du deshalb mit { once: true }.

Parameter

Name Typ Default Beschreibung
$type Pflicht string Der Ereignisname "canplay".
$handler Pflicht (event: Event) => void Callback mit einem einfachen Event-Objekt. event.target ist das Medienelement.
$options boolean | AddEventListenerOptions false Optionen wie { once: true }, capture oder signal (AbortSignal) zum Entfernen des Listeners.

Rückgabewert

Typ
void
Beschreibung
addEventListener gibt nichts zurück. Der Handler bekommt ein Event ohne zusätzliche Eigenschaften.

Beispiele

Play-Button freischalten, sobald das Video abspielbar ist

const video = document.querySelector('#film');
const playBtn = document.querySelector('#play');
const spinner = document.querySelector('.spinner');

playBtn.disabled = true;

video.addEventListener('canplay', () => {
  spinner.hidden = true;
  playBtn.disabled = false;
  console.log('Bereit, readyState =', video.readyState);
}, { once: true });

playBtn.addEventListener('click', () => video.play());
Bereit, readyState = 3

Autoplay mit Fehlerbehandlung über oncanplay

const audio = new Audio('/media/podcast.mp3');

audio.oncanplay = async () => {
  try {
    await audio.play();
    console.log('Wiedergabe läuft');
  } catch (err) {
    // Autoplay-Richtlinie blockiert Start ohne Nutzerinteraktion
    console.warn('Autoplay blockiert:', err.name);
  }
  audio.oncanplay = null; // Handler entfernen
};
Autoplay blockiert: NotAllowedError

Ladezeit messen und Listener per AbortController entfernen

const video = document.querySelector('video');
const controller = new AbortController();
const start = performance.now();

video.addEventListener('canplay', () => {
  console.log(`Abspielbar nach ${Math.round(performance.now() - start)} ms`);
  controller.abort(); // entfernt alle Listener mit diesem Signal
}, { signal: controller.signal });

video.src = '/media/trailer.mp4';
video.load();
Abspielbar nach 412 ms

Promise-Helfer: auf abspielbaren Zustand warten

const waitForCanPlay = (media) => new Promise((resolve, reject) => {
  if (media.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) return resolve();
  media.addEventListener('canplay', () => resolve(), { once: true });
  media.addEventListener('error', () => reject(media.error), { once: true });
});

const video = document.querySelector('video');
await waitForCanPlay(video);
console.log('Video kann starten');
Video kann starten

// Wichtig · Fallstricke

  • Race Condition: Wenn das Medium schon aus dem Cache geladen ist, kann canplay gefeuert haben, bevor dein Listener registriert wird. Prüfe deshalb vorher media.readyState >= 3, wie im Promise-Helfer oben.
  • Mehrfaches Auslösen: Nach einem Sprung, nach Pufferengpässen oder nach einem Quellenwechsel feuert canplay erneut. Mit { once: true } vermeidest du doppelte Initialisierung.
  • preload="none": Dann lädt der Browser keine Daten, und canplay feuert erst nach play() oder load(). Auf Mobilgeräten wird preload oft ignoriert oder eingeschränkt.
  • Autoplay-Richtlinien: Auch nach canplay kann play() mit NotAllowedError abgelehnt werden, wenn der Nutzer noch nicht mit der Seite interagiert hat und der Ton nicht stummgeschaltet ist. Das zurückgegebene Promise solltest du immer auswerten.
  • Kein Aufsteigen im DOM: Das Ereignis bubbelt nicht. Für Delegation brauchst du capture: true.
  • Kompatibilität: Wird von allen modernen Browsern unterstützt. Bei canplaythrough weichen die Browser in ihren Schätzungen stärker voneinander ab.