Start · Sprachen · JavaScript · Referenz · HTMLMediaElement: encrypted

HTMLMediaElement: encrypted

Ereignis

Wird ausgelöst, wenn der Browser in den Mediendaten Initialisierungsdaten findet, die auf eine Verschlüsselung (DRM) hinweisen.

seit JavaScript Baseline: alle modernen Browser (EME) Kategorie: webapi

Signatur

element.addEventListener("encrypted", handler)

Beschreibung

Das Ereignis encrypted gehört zu den Encrypted Media Extensions (EME). Es wird an einem <video>- oder <audio>-Element ausgelöst, sobald der Browser beim Parsen des Medienstroms (z. B. einer pssh-Box in MP4/CENC oder in WebM-Headern) Initialisierungsdaten für eine Verschlüsselung findet. Das Ereignis ist vom Typ MediaEncryptedEvent, es bubbelt nicht und lässt sich nicht abbrechen.

Genutzt wird es als Startpunkt für den DRM-Ablauf: Im Handler wählt man über navigator.requestMediaKeySystemAccess() ein Schlüsselsystem aus (z. B. Widevine, PlayReady, FairPlay oder Clear Key), erzeugt MediaKeys, hängt sie mit setMediaKeys() an das Element und öffnet eine MediaKeySession. Mit event.initDataType und event.initData ruft man dann session.generateRequest() auf. Die Lizenzanfrage bekommt man über das Ereignis message der Session.

Das Ereignis wird entweder über addEventListener("encrypted", handler) oder über die Eigenschaft onencrypted registriert. Es kann mehrfach ausgelöst werden, etwa einmal pro Track oder bei Schlüsselwechseln. Deshalb sollte man doppelte Initialisierungsdaten abfangen.

Parameter

Name Typ Default Beschreibung
$type Pflicht string Der Ereignisname "encrypted".
$handler Pflicht (event: MediaEncryptedEvent) => void Callback, der ein MediaEncryptedEvent bekommt. Wichtige Eigenschaften sind initDataType (string, z. B. "cenc", "webm" oder "keyids") und initData (ArrayBuffer | null).
$options boolean | AddEventListenerOptions false Die üblichen Listener-Optionen, z. B. { once: true } oder { signal }.

Rückgabewert

Typ
void
Beschreibung
addEventListener gibt nichts zurück. Die Daten stehen im MediaEncryptedEvent.

Beispiele

Initialisierungsdaten per addEventListener protokollieren

const video = document.querySelector('video');

video.addEventListener('encrypted', (event) => {
  console.log('Typ der Init-Daten:', event.initDataType);
  console.log('Größe:', event.initData?.byteLength, 'Bytes');
});
Typ der Init-Daten: cenc Größe: 86 Bytes

Vollständiger Clear-Key-Ablauf mit onencrypted

const video = document.querySelector('video');

// Schlüssel-ID und Schlüssel als Base64url (nur für Demo/Tests)
const KEYS = { 'nrQFDeRLSAKTLifXUIPiZg': 'FmY0xnWCPCNaSpRG-tUuTQ' };

const config = [{
  initDataTypes: ['cenc', 'keyids'],
  videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }]
}];

video.onencrypted = async (event) => {
  if (!video.mediaKeys) {
    const access = await navigator.requestMediaKeySystemAccess('org.w3.clearkey', config);
    const mediaKeys = await access.createMediaKeys();
    await video.setMediaKeys(mediaKeys);
  }

  const session = video.mediaKeys.createSession();

  session.addEventListener('message', async (msgEvent) => {
    const request = JSON.parse(new TextDecoder().decode(msgEvent.message));
    const license = {
      keys: request.kids.map((kid) => ({ kty: 'oct', kid, k: KEYS[kid] })),
      type: 'temporary'
    };
    await session.update(new TextEncoder().encode(JSON.stringify(license)));
    console.log('Lizenz gesetzt');
  });

  await session.generateRequest(event.initDataType, event.initData);
};
Lizenz gesetzt

Doppelte Init-Daten ignorieren und Listener wieder entfernen

const video = document.querySelector('video');
const seen = new Set();

const toHex = (buf) => [...new Uint8Array(buf)]
  .map((b) => b.toString(16).padStart(2, '0')).join('');

const onEncrypted = (event) => {
  const key = `${event.initDataType}:${toHex(event.initData)}`;
  if (seen.has(key)) return; // gleiche Daten schon verarbeitet
  seen.add(key);
  console.log('Neue Init-Daten, Sitzungen offen:', seen.size);
};

video.addEventListener('encrypted', onEncrypted);

// Beim Wechsel der Quelle aufräumen
const resetPlayer = () => {
  video.removeEventListener('encrypted', onEncrypted);
  seen.clear();
};
Neue Init-Daten, Sitzungen offen: 1

// Wichtig · Fallstricke

  • Nur in sicherem Kontext: EME (requestMediaKeySystemAccess) funktioniert nur über HTTPS. In iframes, die nicht vom gleichen Ursprung stammen, braucht man zusätzlich die Berechtigungsrichtlinie encrypted-media, z. B. <iframe allow=\"encrypted-media\">.
  • Welche Schlüsselsysteme es gibt, hängt vom Browser ab: Chrome, Edge und Firefox bringen Widevine mit, Edge auch PlayReady, Safari FairPlay (com.apple.fps). org.w3.clearkey ist nicht wirklich geschützt und nur für Tests gedacht.
  • Safari: Ältere Safari-Versionen haben nur das Präfix-API (webkitneedkey, WebKitMediaKeys). Das standardisierte encrypted gibt es ab Safari 12.1.
  • initData kann null sein, und das Ereignis kann mehrfach auftreten (mehrere Tracks, Schlüsselrotation). Duplikate sollte man erkennen, sonst entstehen unnötige Sitzungen und Lizenzanfragen.
  • Bei Media Source Extensions (MSE) kommt das Ereignis erst, wenn das Init-Segment an den SourceBuffer angehängt wurde.
  • Übergeben Sie Lizenzschlüssel nie im Klartext im Client-Code (außer bei Clear-Key-Tests). Die Lizenz muss vom Lizenzserver mit Authentifizierung kommen.