Multimedia/Playlists und mehr
Mittlerweile können alle Browser Audio- und Video-Dateien direkt im Browser ohne Plugins abspielen. Die Darstellung des browsereigenen Players mit controls bietet dem Nutzer ein vertrautes Bild, da dieser im Normalfall nur mit einem Browser im Netz unterwegs ist.
Trotzdem kann es attraktiv sein, Bedienelemente nach eigenen Wünschen zu programmieren und zu gestalten.[1] In diesem Tutorial zeigen wir, wie das geht und wie man mehrere Hörproben zu einer Playlist zusammenstellt und nacheinander abspielen kann.
Inhaltsverzeichnis
Audioplayer - selbstgemacht
Im letzten Kapitel wurde gezeigt, wie man mit dem controls-Attribut den browsereigenen Player einblendet. Wenn man ihn im Seiteninspektor untersucht, findet man mehrere browserspezifische Pseudoelemente, deren Styling aber nicht empfehlenswert ist.[2]
Einfacher ist es, das controls-Attribut nach dem Laden zu entfernen und durch einen customized audioplayer zu ersetzen:
<audio id="audio_with_controls"
controls
src="https://wiki.selfhtml.org/local/Europahymne.mp3"
type="audio/mp3"
>
Ihr Browser kann dieses Tondokument nicht wiedergeben.<br>
Es enthält eine Aufführung der Europahymne.
Sie können es unter
<a href="https://wiki.selfhtml.org/local/Europahymne.mp3">diesem Link</a>
abrufen.
</audio>
<script>
document.addEventListener('DOMContentLoaded', function () {
const audio = document.querySelector('audio');
function createPlayer() {
audio.removeAttribute('controls');
audio.insertAdjacentHTML('afterend', `
<div id="audio-player">
<svg viewBox="-49 -49 99 99"><circle r="45" id="background"/><circle r="45" id="duration"/></svg>
<label for="play-pause-icon">Audio Player</label>
<button id="play-pause-icon" class="play" aria-label="Play audio" aria-pressed="false"></button>
</div>
`);
}
</script>
Sobald die Webseite geladen ist, wird das controls-Attribut mit removeAttribute entfernt und mit insertAdjacentHTML ein HTML-Player eingefügt. Dieser besteht …
- aus einem SVG für den Fortschrittsverlauf. Dabei wurde die viewBox so angelegt, dass die beiden Kreise ihrem Mittelpunkt am Ursprung haben.
- aus einem Button, der ebenfalls im div zentriert wird. Das ist mit place-self: center mittlerweile kein Problem mehr.
- Das aria-label-Attribut dient als Erklärung[3]
- über aria-pressed wird der Zustand (state) angezeigt.
Funktionalität mit JavaScript
function createPlayer() {
audio.removeAttribute('controls');
audio.insertAdjacentHTML('afterend', `
...
`);
const playPauseButton = document.querySelector('#play-pause-icon');
playPauseButton.addEventListener('click', function () {
const isPlaying = playPauseButton.getAttribute('aria-pressed') === 'true';
if (!isPlaying) {
audio.play();
playPauseButton.setAttribute('aria-pressed', 'true');
playPauseButton.setAttribute('aria-label', 'Pause audio');
} else {
audio.pause();
playPauseButton.setAttribute('aria-pressed', 'false');
playPauseButton.setAttribute('aria-label', 'Play audio');
}
});
}
Der Button erhält nun einen EventListener, der in einer anonymen Funktion überprüft, ob der Button angeklickt wird. Dabei wird mit getAttribute der Zustand des vorher erwähnten aria-pressed-Attributs untersucht und dann entweder audio.play();
oder audio.pause();
aufgerufen. play()
und pause()
sind Methoden des HTMLMediaElement-Objekts, die im MDN näher beschrieben werden.[4]
Das Aussehen des Buttons wird nicht über Klassen, sondern über die schon vorhandenen Aria-Attribute mit dem Attributwert-Selektor gesteuert:
[aria-pressed=false] {
background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 160'%3E%3Cpath d='M35,10 l90,70 -90,70z' style='fill:%23c82f04;stroke:%23c82f04; stroke-width:19;stroke-linejoin:round;'/%3E%3C/svg%3E");
}
[aria-pressed=true] {
background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 160'%3E%3Crect x='25' y='0' width='45' height='160' rx='10' style='fill:%23c82f04;'/%3E%3Crect x='90' y='0' width='45' height='160' rx='10' style='fill:%23c82f04;'/%3E%3C/svg%3E");
}
Fortschrittsanzeige und Gesamtspielzeit
Die browsereigenen Player haben sowohl eine Zeitangabe der aktuellen und der Gesamtlaufzeit, als auch einen Balken zur Visualisierung.
Wir wollen anstelle eines progress-Elements zur Visualisierung einen Kreis nehmen, dessen Randlinie animiert wird.
#duration {
fill: none;
stroke: steelBlue;
stroke-width: 3;
stroke-dasharray: 282;
stroke-dashoffset: 282;
rotate: -0.25turn;
}
Randlinien werden mit stroke eingefärbt und können mit stroke-dasharray eine Strichelung erhalten. Diese gestrichelte Randlinie kann mit stroke-dashoffset so verschoben werden, dass sie anfangs gar nicht sichtbar ist.
Damit diese Animation oben startet, wird der Kreis mit rotate: -0.25turn; um eine Vierteldrehung nach links gedreht.
Unser JavaScript wird nun erweitert:
const currentTimeDisplay = document.querySelector('#current-time');
const durationTimeDisplay = document.querySelector('#duration-time');
const circleLength = 2 * Math.PI * parseFloat(durationCircle.getAttribute('r'));
...
audio.addEventListener('loadedmetadata', function () {
if (audio.duration) {
durationTimeDisplay.textContent = formatTime(audio.duration);
}
});
audio.addEventListener('timeupdate', function () {
if (audio.duration) {
const progress = audio.currentTime / audio.duration;
const dashOffset = circleLength * (1 - progress);
durationCircle.style.strokeDashoffset = dashOffset;
currentTimeDisplay.textContent = formatTime(audio.currentTime);
}
});
audio.addEventListener('ended', function () {
durationCircle.style.strokeDashoffset = circleLength;
playPauseButton.setAttribute('aria-pressed', 'false');
playPauseButton.setAttribute('aria-label', 'Play audio');
currentTimeDisplay.textContent = '0:00';
});
Das loadedmetadata-Event feuert, wenn die Metadaten der Medienressource geladen werden. Mit der duration
-Eigenschaft wird die Gesamtdauer ausgelesen und mit durationTimeDisplay.textContent
in das dazugehörige span eingetragen.
Das timeupdate-Event feuert, wenn das currentTime-Attribut geäöndert wird. Dann wird sowohl das stroke-dashoffset des Kreislinie, also auch der textContent von currentTimeDisplay
geändert.
Sobald das ended-Event beim Ende des Abspielens feuert, werden Kreislinie und Spielzeit wieder zurückgesetzt.
In einer Komfortversion könnte man einen dritten Kreis anlegen, der den Ladefortschritt anzeigt.
Mamma Lauder
In der jetzigen Form hat der Player keine Lautstärkeregelung, sondern übernimmt diese von den Einstellungen des Betriebssystems. Allerdings könnte man ohne Probleme die volume-Eigenschaft[5] setzen und durch das volumechange-Event beobachten.[6]
Erstellen und Wiedergabe von Playlists
Mit JavaScript ist es möglich mehrere Audio-Dateien in einer Wiedergabeliste (Playlist) zusammenzufassen und dann beliebig abzuspielen, zu wiederholen oder anzubieten. Sobald ein Titel endet, kann man auf das im letzten Abschnitt eingeführte ended-Event lauschen und einen neuen Titel aus der Liste laden und abspielen.
Dabei ist es clientseitigem JavaScript aber nicht möglich, Ordner im Dateisystem des Benutzers auf abspielfähige Audio-Dateien zu durchsuchen. Deshalb werden wir eine Liste von Audio-Titeln auf unserer Webseite verwenden. (Im „normalen“ Leben hat niemand mehr Musik als Dateien auf der Festplatte zuhause - geschweige denn als physische Tonträger, alles streamt aus der Cloud heraus, bis es das eben mal nicht mehr tut.)
Der Audioplayer aus dem ersten Teil des Tutorials dient - leicht verändert - als Grundlage unseres neuen Projekts:
<audio id="audio_with_controls"
controls
src="https://wiki.selfhtml.org/local/Europahymne.mp3"
type="audio/mp3"
>
Ihr Browser kann dieses Tondokument nicht wiedergeben.<br>
Es enthält eine Aufführung der Europahymne.
Sie können es unter
<a href="https://wiki.selfhtml.org/local/Europahymne.mp3">diesem Link</a>
abrufen.
</audio>
<ul class="playlist">
<li><a href="https://upload.wikimedia.org/wikipedia/commons/6/62/Pachelbel%27s_Canon.ogg">Kanon in D-Dur, Pachelbel</a></li>
<li><a href="https://upload.wikimedia.org/wikipedia/commons/c/c2/Toccata_and_Fugue_in_D_Minor_%28ISRC_USUAN1100350%29.mp3">Toccata und Fuge in D-Moll, Bach</a></li>
<li><a href="https://upload.wikimedia.org/wikipedia/commons/2/24/Mozart_-_Eine_kleine_Nachtmusik_-_1._Allegro.ogg">Eine kleine Nachtmusik, Mozart</a></li>
<li><a href="https://upload.wikimedia.org/wikipedia/commons/9/9d/Daniel_Bautista_-_Beethoven_-_Para_Elisa.ogg">Für Elise, Beethoven</a></li>
<li><a href="https://upload.wikimedia.org/wikipedia/commons/f/fa/Vivaldi_-_Four_Seasons_2_Summer_mvt_3_Presto_-_John_Harrison_violin.oga">Vier Jahreszeiten Sommer, Vivaldi</a></li>
<li><a href="https://upload.wikimedia.org/wikipedia/commons/e/e2/Mozart%2C_The_Marriage_of_Figaro_%28overture%29.ogg">Hochzeit des Figaro - Overtüre, Mozart</a></li>
<li><a href="https://upload.wikimedia.org/wikipedia/commons/e/ec/Korobeiniki-wansti-tobefree.mp3">Korobeiniki, Nikolai Alexejewitsch Nekrassow (1821 - 1878) </a></li>
</ul>
Zusätzlich zum Play/Pause-Button benötigen wir nun noch einen Vor-, bzw. Zurück-Button. Um Nutzern Orientierung zu geben, sollte die Liste weiterhin angezeigt werden, wobei der aktuell gespielte Titel gekennzeichnet werden sollte:
const playlistSource = document.querySelector('ul.playlist');
const playlist = [];
// Loop through all list items and extract data
playlistSource.querySelectorAll('li a').forEach(link => {
const item = {
source: link.getAttribute('href'),
title: link.textContent.trim()
};
playlist.push(item);
});
console.log(JSON.stringify(playlist, null, 2));
const audio = document.querySelector('#audio_with_controls');
const titleDisplay = document.querySelector('#titel');
let currentIndex = 0;
function loadTrack(index) {
if (index >= 0 && index < playlist.length) {
const track = playlist[index];
audio.src = track.source;
titleDisplay.textContent = track.title;
audio.play();
}
}
function playNext() {
currentIndex++;
if (currentIndex >= playlist.length) {
currentIndex = 0; // Loop back to the first track
}
loadTrack(currentIndex);
}
function playPrevious() {
currentIndex--;
if (currentIndex < 0) {
currentIndex = playlist.length - 1; // Loop to the last track
}
loadTrack(currentIndex);
}
audio.addEventListener('ended', playNext);
document.querySelector('#next').addEventListener('click', playNext);
document.querySelector('#back').addEventListener('click', playPrevious);
loadTrack(currentIndex);
Metadaten
Bereits im vorigen Kapitel wurden Mediendateien mit ZIP-Ordnern verglichen, die neben der Musik noch weitere Metadaten enthalten. Sobald das loadedmetadata-Event feuert, kann man diese auslesen und zu unserem JSON-Objekt hinzufügen:
audio.addEventListener('loadedmetadata', function () {
console.log('Duration:', audio.duration);
console.log('Buffered:', audio.buffered);
});
video.addEventListener('loadedmetadata', function () {
console.log('Dimensions:', video.videoWidth, 'x', video.videoHeight);
});
Siehe auch
Felix Riesterer und ich hatten ein Projekt, in dem Soundschnipsel zu immer wieder anderen mündlich gestellten Aufgaben aneinandergefügt wurden. So etwas geht heute einfacher mit Web Speech.
Weblinks
- ↑ Video and Audio APIs (developer.mozilla.org)
- ↑ -webkit- Pseudoelemente zum Stylen der Audio-Controls
Chromium-Browser unterstützen mehrere Pseudoelemente, mit denen einzelne Teile der Audiocontrols per CSS umgestaltet werden können. Die Einrückung in der Liste entspricht der Struktur des Shadow DOM, das Chrome zur Darstellung der Steuerelemente erzeugt:
Vorsicht: Diese Liste enthält Interna von Chrome. Sie können sich nicht darauf verlassen, dass diese Liste inhaltlich stabil bleibt.::-webkit-media-controls ::-webkit-media-controls-overlay-enclosure ::-webkit-media-controls-enclosure ::-webkit-media-controls-panel ::-webkit-media-controls-play-button ::-webkit-media-controls-current-time-display ::-webkit-media-controls-time-remaining-display ::-webkit-media-controls-timeline ::-webkit-media-controls-volume-control-container ::-webkit-media-controls-volume-control-hover-background ::-webkit-media-controls-volume-slider ::-webkit-media-controls-mute-button
- ↑ Accessible multimedia (developer.mozilla.org)
- ↑ HTMLMediaElement/play (developer.mozilla.org)
- ↑ HTMLMediaElement.volume (MDN)
- ↑ Get and Set Volume with JavaScript (David Walsh)