Bevor wir darauf eingehen, wie Sie Ihrem Videoplayer Untertitel hinzufügen, gibt es einige Dinge, die wir zunächst erwähnen müssen, die Sie wissen sollten, bevor wir beginnen.
Untertitel und Übersetzungen sind nicht dasselbe: Sie haben unterschiedlich signifikante Zielgruppen und vermitteln verschiedene Informationen. Es wird empfohlen, sich über die Unterschiede zu informieren, wenn Sie sich nicht sicher sind, was sie sind. Sie werden jedoch technisch auf die gleiche Weise implementiert, sodass das Material in diesem Artikel für beide gilt.
Für diesen Artikel werden wir uns auf die angezeigten Textspuren als Übersetzungen beziehen, da ihr Inhalt für hörende Menschen gedacht ist, die Schwierigkeiten haben, die Sprache des Films zu verstehen, und nicht für gehörlose oder schwerhörige Personen.
HTML erlaubt es uns, mithilfe des <track> Elements Übersetzungen für ein Video anzugeben. Die verschiedenen Attribute dieses Elements ermöglichen es uns, Dinge wie die Art des Inhalts, den wir hinzufügen, die Sprache, in der er geschrieben ist, und natürlich einen Verweis auf die Textdatei, die die tatsächlichen Untertitel-Informationen enthält, anzugeben.
Die Dateien, die die tatsächlichen Untertiteldaten enthalten, sind Textdateien, die einem festgelegten Format folgen, in diesem Fall dem Web Video Text Tracks (WebVTT) Format.
Videodienste (wie die Blender Foundation) stellen Untertitel und Übersetzungen in einem Textformat mit ihren Videos bereit, aber sie sind normalerweise im SubRip Text (SRT) Format. Diese können leicht mit einem Online-Konverter in WebVTT konvertiert werden.
Dieser Abschnitt fasst die Änderungen zusammen, die am Code des vorherigen Artikels vorgenommen wurden, um die Hinzufügung von Untertiteln zum Video zu erleichtern. Wenn Sie daran nicht interessiert sind und direkt zu JavaScript und dem relevanteren CSS übergehen möchten, springen Sie zum Abschnitt Implementierung der Untertitel.
In diesem Beispiel verwenden wir ein anderes Video, Sintel, da es tatsächlich einige Sprache enthält und daher besser veranschaulicht, wie Untertitel funktionieren!
Wie oben erwähnt, müssen wir das neue HTML <track> Element nutzen, um unsere Untertiteldateien zum HTML-Video hinzuzufügen. Wir haben unsere Untertitel tatsächlich in drei verschiedenen Sprachen — Englisch, Deutsch und Spanisch — also werden wir alle drei relevanten VTT-Dateien referenzieren, indem wir <track> Elemente innerhalb unseres HTML <video> Elements hinzufügen:
Wie Sie sehen können, hat jedes <track> Element die folgenden Attribute gesetzt:
kind hat den Wert subtitles, was die Art des Inhalts angibt, den die Dateien enthalten.
label hat einen Wert, der angibt, für welche Sprache dieser Untertitelsatz ist — zum Beispiel English oder Deutsch — diese Labels erscheinen in der Benutzeroberfläche, damit der Benutzer leicht auswählen kann, welche Untertitelsprache er sehen möchte.
src ist eine gültige URL, die auf die jeweilige WebVTT-Untertiteldatei verweist.
srclang gibt an, in welcher Sprache der Inhalt jeder Untertiteldatei ist.
Das default Attribut ist auf dem englischen <track> Element gesetzt, wodurch dem Browser mitgeteilt wird, dass dies die Standard-Untertiteldatei ist, die verwendet werden soll, wenn die Untertitel aktiviert sind und der Benutzer keine spezielle Auswahl getroffen hat.
Neben dem Hinzufügen der <track> Elemente haben wir auch eine neue Schaltfläche hinzugefügt, um das Untertitelmenü zu steuern, das wir erstellen werden. Infolgedessen sehen die Videosteuerungen jetzt wie folgt aus:
Es gibt fast keine Änderung am CSS gegenüber der vorherigen Version, außer an einer Stelle, an der width: calc(100% / 6) durch width: calc(100% / 7) ersetzt wurde, um die neue Schaltfläche einzubeziehen. Wir setzen auch position: relative auf die Figure, damit das Untertitelmenü-Popup relativ dazu positioniert werden kann.
Es wird kein Bild für die Untertitel-Schaltfläche verwendet, daher wird es wie folgt gestaltet:
Es gibt auch andere CSS-Änderungen, die spezifisch für einige zusätzliche JavaScript-Implementierungen sind, aber diese werden an der entsprechenden Stelle unten erwähnt.
Vieles von dem, was wir tun, um auf die Video-Untertitel zuzugreifen, dreht sich um JavaScript. Ähnlich wie bei den Videosteuerungen gibt es, wenn ein Browser HTML-Video-Untertitel unterstützt, eine Schaltfläche innerhalb des nativen Steuerungssatzes, um darauf zuzugreifen. Da wir jedoch unsere eigenen Videosteuerungen definiert haben, ist diese Schaltfläche versteckt, und wir müssen unsere eigene definieren.
Browser unterscheiden sich darin, was sie unterstützen, daher werden wir versuchen, eine einheitlichere Benutzeroberfläche für jeden Browser zu schaffen, wo immer dies möglich ist. Mehr zu den Fragen der Browser-Kompatibilität weiter unten.
const videoContainer = document.getElementById("videoContainer");
const video = document.getElementById("video");
const videoControls = document.getElementById("video-controls");
const playPause = document.getElementById("play-pause");
const stop = document.getElementById("stop");
const mute = document.getElementById("mute");
const volInc = document.getElementById("vol-inc");
const volDec = document.getElementById("vol-dec");
const progress = document.getElementById("progress");
const fullscreen = document.getElementById("fs");
// Hide the default controls
video.controls = false;
// Display the user defined video controls
videoControls.setAttribute("data-state", "visible");
function changeButtonState(type) {
if (type === "play-pause") {
// Play/Pause button
if (video.paused || video.ended) {
playPause.setAttribute("data-state", "play");
} else {
playPause.setAttribute("data-state", "pause");
}
} else if (type === "mute") {
// Mute button
mute.setAttribute("data-state", video.muted ? "unmute" : "mute");
}
}
video.addEventListener("play", () => {
changeButtonState("play-pause");
});
video.addEventListener("pause", () => {
changeButtonState("play-pause");
});
stop.addEventListener("click", (e) => {
video.pause();
video.currentTime = 0;
progress.value = 0;
// Update the play/pause button's 'data-state' which allows the
// correct button image to be set via CSS
changeButtonState("play-pause");
});
mute.addEventListener("click", (e) => {
video.muted = !video.muted;
changeButtonState("mute");
});
playPause.addEventListener("click", (e) => {
if (video.paused || video.ended) {
video.play();
} else {
video.pause();
}
});
function checkVolume(dir) {
if (dir) {
const currentVolume = Math.floor(video.volume * 10) / 10;
if (dir === "+" && currentVolume < 1) {
video.volume += 0.1;
} else if (dir === "-" && currentVolume > 0) {
video.volume -= 0.1;
}
// If the volume has been turned off, also set it as muted
// Note: can only do this with the custom control set as when the 'volumechange' event is raised,
// there is no way to know if it was via a volume or a mute change
video.muted = currentVolume <= 0;
}
changeButtonState("mute");
}
function alterVolume(dir) {
checkVolume(dir);
}
volInc.addEventListener("click", (e) => {
alterVolume("+");
});
volDec.addEventListener("click", (e) => {
alterVolume("-");
});
video.addEventListener("volumechange", () => {
checkVolume();
});
progress.addEventListener("click", (e) => {
if (!Number.isFinite(video.duration)) return;
const rect = progress.getBoundingClientRect();
const pos = (e.pageX - rect.left) / progress.offsetWidth;
video.currentTime = pos * video.duration;
});
video.addEventListener("loadedmetadata", () => {
progress.setAttribute("max", video.duration);
});
video.addEventListener("timeupdate", () => {
if (!progress.getAttribute("max"))
progress.setAttribute("max", video.duration);
progress.value = video.currentTime;
});
if (!document?.fullscreenEnabled) {
fullscreen.style.display = "none";
}
fullscreen.addEventListener("click", (e) => {
if (document.fullscreenElement !== null) {
// The document is in fullscreen mode
document.exitFullscreen();
// Set the fullscreen button's 'data-state' which allows the
// correct button image to be set via CSS
fullscreen.setAttribute("data-state", "go-fullscreen");
} else {
// The document is not in fullscreen mode
videoContainer.requestFullscreen();
fullscreen.setAttribute("data-state", "cancel-fullscreen");
}
});
Außerdem schalten wir alle Untertitel zunächst aus, falls der Browser einige von ihnen standardmäßig einschaltet:
js
for (const track of video.textTracks) {
track.mode = "hidden";
}
Die Eigenschaft video.textTracks enthält ein Array von allen an das Video angehängten Textspuren. Wir durchlaufen jede einzelne und setzen ihren mode auf hidden.
Hinweis: Die WebVTT API gibt uns Zugriff auf alle Textspuren, die für ein HTML-Video mit dem <track> Element definiert sind.
Unser Ziel ist es, die zuvor hinzugefügte subtitles Schaltfläche zu verwenden, um ein Menü anzuzeigen, in dem Benutzer auswählen können, in welcher Sprache die Untertitel angezeigt werden sollen, oder sie ganz auszuschalten.
Wir haben die Schaltfläche hinzugefügt, aber bevor wir sie etwas tun lassen, müssen wir das zugehörige Menü erstellen. Dieses Menü wird dynamisch erstellt, sodass später Sprachen durch Bearbeiten der <track> Elemente im Video-Markup hinzugefügt oder entfernt werden können.
Alles, was wir tun müssen, ist durch die textTracks des Videos zu gehen, ihre Eigenschaften zu lesen und das Menü daraus zu erstellen:
js
const subtitleMenuButtons = [];
let subtitlesMenu;
if (video.textTracks) {
const df = document.createDocumentFragment();
subtitlesMenu = df.appendChild(document.createElement("ul"));
subtitlesMenu.className = "subtitles-menu";
subtitlesMenu.appendChild(createMenuItem("subtitles-off", "", "Off"));
for (const track of video.textTracks) {
subtitlesMenu.appendChild(
createMenuItem(
`subtitles-${track.language}`,
track.language,
track.label,
),
);
}
videoContainer.appendChild(subtitlesMenu);
}
Dieser Code erstellt ein documentFragment, das verwendet wird, um eine ungeordnete Liste zu halten, die unser Untertitelmenü enthält. Zunächst wird eine Option hinzugefügt, die es dem Benutzer ermöglicht, alle Untertitel auszuschalten, und dann werden Schaltflächen für jede Textspur hinzugefügt, wobei die Sprache und das Label von jeder gelesen wird.
Die Erstellung jedes Listenelements und jeder Schaltfläche erfolgt durch die createMenuItem() Funktion, die wie folgt definiert ist:
js
function createMenuItem(id, lang, label) {
const listItem = document.createElement("li");
const button = listItem.appendChild(document.createElement("button"));
button.setAttribute("id", id);
button.className = "subtitles-button";
if (lang.length > 0) button.setAttribute("lang", lang);
button.value = label;
button.setAttribute("data-state", "inactive");
button.appendChild(document.createTextNode(label));
button.addEventListener("click", (e) => {
// Set all buttons to inactive
subtitleMenuButtons.forEach((button) => {
button.setAttribute("data-state", "inactive");
});
// Find the language to activate
const lang = button.getAttribute("lang");
for (const track of video.textTracks) {
// For the 'subtitles-off' button, the first condition will never match so all will subtitles be turned off
if (track.language === lang) {
track.mode = "showing";
button.setAttribute("data-state", "active");
} else {
track.mode = "hidden";
}
}
subtitlesMenu.style.display = "none";
});
subtitleMenuButtons.push(button);
return listItem;
}
Diese Funktion erstellt die erforderlichen <li> und <button> Elemente und gibt sie zurück, damit sie der Untertitelmenüliste hinzugefügt werden können. Sie setzt auch die erforderlichen Ereignis-Listener auf der Schaltfläche, um den betreffenden Untertitelsatz ein- oder auszuschalten. Dies geschieht, indem das erforderliche mode Attribut der Untertitel auf showing gesetzt wird, und die anderen auf hidden gesetzt werden.
Sobald das Menü erstellt ist, wird es unten im videoContainer in das DOM eingefügt.
Das Menü ist anfänglich standardmäßig verborgen, sodass ein Ereignis-Listener zu unserer Untertitel-Schaltfläche hinzugefügt werden muss, um sie umzuschalten:
Eine der weniger bekannten und unterstützten Funktionen von WebVTT ist die Fähigkeit, die einzelnen Untertitel zu stylen (etwas, das als Text-Cues bezeichnet wird) über CSS.
Das ::cue Pseudo-Element ist der Schlüssel, um individuelle Texttrack-Cues für das Styling zu verwenden, da es zu jedem definierten Cue passt. Es gibt nur eine Handvoll CSS-Eigenschaften, die auf einen Text-Cue angewendet werden können: