HTML-Seite als Playlist

Angewiesen auf diverse Plattformen war ich gezwungen mich mit verschiedenen Playlist-Apps auseinanderzusetzen. Ich brauchte eine plattformübergreifende Lösung; warum nicht per Browser?
Natürlich muss es ein Browser sein, der das audio-Element unterstützt (https://caniuse.com/#feat=audio).

Gleich vorweg

Die im folgenden beschriebene HTML-Seite mit JavaScript ist nicht als Internet-Anwendung einzusetzen.
Es überhaupt nicht nötig, einen Webserver zu benutzen. Die Seite befindet sich z.B. auf einem Stick und wird im sogenannten Filezugriff aufgerufen, also die HTML-Datei direkt im Browser geöffnet. Das könnte in der Adresszeile des Browsers so aussehen:
file:///E:/playlist.html
- also nicht per http:// oder https://.

Die Playlist im Browser

So sieht die Playlist (playlist.html) im Browser aus.
Einzelne audio-Elemente sind in einer numerischen Liste (ol-Element) eingetragen.
Jedem audio-Element sind 3 Button zugeordnet (Los, Start, Stopp) zugeordnet. Die Überschrift nennt den jeweiligen Dateinamen des Audiofiles (ohne die Endung .mp3).
Ein Klick auf "Los!" (Button ganz oben) lässt alle Titel nacheinander abspielen.
"Stopp" hält den aktuellen Titel an, "Start" startet ihn erneut, aber von Beginn an. Die Button "Los" neben den Titeln lassen ebenfalls die Playlist abspielen, aber eben nur ab dem entsprechenden Titel. Die Bedienung der Controls der audio-Elemente bleibt dabei erhalten.

Der HTML-Code

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8">
    ...
    <script type="text/javascript" src="playlist.js"></script>
  </head>
<body>
  ...
  <button  id="startlist">Los!</button>
  <ol>
    <li>
      <h3>GypsyQueen</h3><
      <audio id="GypsyQueen" controls="controls">
        <source type="audio/mp3" src="GypsyQueen.mp3"></source>
      </audio>
      <button id="GypsyQueenplayrest">Los</button>
      <button id="GypsyQueenplay">Start</button>
      <button id="GypsyQueenstop">Stopp</button>
   </li>
   <li>... wie beim ersten li-Element, anstelle GypsyQueen hier HookinUp ... </li>
   <li>... wie beim ersten li-Element, anstelle GypsyQueen hier ICanHelp ... </li>
  </ol>  
  ...
</body>
</html>
Das Listing zeigt nur das Nötigste, was für das Funktionieren der Playlist erforderlich ist. Jeder weitere Code, der zur Gestaltung der Seite dient, ist denkbar. Ich halte mich dabei an diese Empfehlung: id-Attribut nur bei Elementen, auf die per JavaScript zugegriffen werden soll, CSS nur per Elementname, class- oder style-Attribut. So kann es keine Seiteneffekte geben.
Wir sehen hier audio-Elemente in den Listenelementen (li) einer numerischen Liste (ol) eingetragen.
Das Besondere ist die Namensgebung der id-Elemente, das wesentliche Unterscheidungsmerkmal li-Inhalte. Der Dateiname ohne Endung von z.B. GypsyQueen.mp3 ist auch Anfang der id-Namen der Elemente audio und button.
Nur beim Button am Anfang des HTML-Codes ist der id-Name ein anderer (startlist).
Zur Laufzeit (HTML-Seite wird im Browser aufgerufen) wird über JavaScript auf die Elemente mit id zugegriffen, um die Eventfunktionen anzubinden, die für die oben genannten Bedienungen erforderlich sind.
JavaScript wird nur an einer Stelle in den Code eingefügt, nämlich per Datei playlist.js im Header des HTML-Codes.

Der JavaScript-Code für die Playlist

Ein Array mit allen Audio-Elementen

  var alle = document.getElementsByTagName('audio');
Hier sammeln wir alle audio-Elemente ein. Dabei kommt es auf die richtige Reihenfolge an. Da das Dokument von Anfang bis Ende durchforstet wird, sollte die Reihenfolge dieselbe sein wie die Folge durch die Listenelemente vorgegeben wird. Bisher gab es diesbezüglich keine Überraschung.
Ganz klar, dass der Durchlauf dieses Arrays im weiteren Programm die Reihenfolge des Abspielens der Titel bestimmt.

Alle Titel nacheinander abspielen

  function playalle(alles, startidx)
  {
    if (alle.length > startidx)
    {
      let audioelement = alles[startidx];
      if (startidx > 0)
      {
        let oldidx = startidx - 1;
        let lastaudio = alles[oldidx];
        lastaudio.style.backgroundColor = '#cccccc';
      }
      let idx = startidx + 1;
      audioelement.addEventListener("ended", function() 
        { playalle(alles, idx); }, false);
      audioelement.style.backgroundColor = '#ff00ff';
      audioelement.play();
    }
  }
Die Funktion playalle() spielt genaugenommen nur einen einzigen Titel (blau gefärbte Codezeilen). dass trotzdem alle Titel nacheinander abgespielt werden können, bewirkt ein rekursiver Aufruf (rot gefärbte Codezeilen).
Betrachten wir zunächst die blauen Codezeilen. Der Parameter alles wird beim Aufruf der Funktion als Argument das Array alle erhalten (enthält alle Audioelemente). Der Parameter startidx wird eine Zahl erhalten.
Eine if-Anweisung überprüft (alle.length > startidx), ob die Zahl innerhalb der Anzahl der Arrayelemente liegt. Dann wird der Variablen audioelement das mit Zahl indexierte Audioelement aus dem Array übergeben.
Am Ende wird an audioelement die Methode play() aufgerufen, und wir hören die Musik
(audioelement ist ein HTMLMediaElement-Objekt, siehe https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement).
Im rot gefärbten Code wird die nächste Zahl berechnet und bei Auftreten des Events "ended" am Audioelement wird die Funktion playalle() erneut aufgerufen, aber eben mit der nächsten Zahl, also wird der nächste Titel gespielt.
Der Event "ended" feuert erst dann, wenn das Abspielen des Titels das Ende erreicht ist.
Bliebe noch zu klären, was es mit der Hintergrundfarbe (audioelement.style.backgroundColor) auf sich hat. Wird ein Titel gespielt, so wird das HTML-audio-Element eingefärbt ('#ff00ff') und der vorherige Titel erhält im rekursiven Aufruf seine ursprüngliche Hintergrundfarbe zurück ('#cccccc').

Die Buttons erhalten ihre Funktionalität

  function myplay(audioid, numid) {

    // Button Los neben Audio-Element
    document.getElementById(audioid+'playrest').addEventListener('click', 
      function() { 
        playalle(alle, numid); 
      }, false);

    // Button Start
    document.getElementById(audioid+'play').addEventListener('click', 
      function() { 
        document.getElementById(audioid).currentTime = 0;
        document.getElementById(audioid).play(); 
      }, false);

   // Button Stopp
   document.getElementById(audioid+'stop').addEventListener('click', 
    function() { 
      document.getElementById(audioid).pause(); 
    }, false);

   // Lautstärke für alle
   document.getElementById(audioid).volume = 1.0;

  }
Die Funktion myplay() verleiht den Button "Los", "Start "und "Stopp" neben einem bestimmten audio-Element ihre Funktionalität, indem die passenden Event-Listener angebunden werden.
Beim Aufruf der Funktion erhält der Parameter audioid die Id eines audio-Elements, im Beispiel ist das entweder 'GypsyQueen', 'HookinUp' oder 'ICanHelp'.
Und jetzt erkennt man, warum die Id-Werte jedes Button genau mit einer dieser Zeichenketten beginnen. Der Ausdruck audioid+'playrest' ist exakt die Id des Buttons "Los" neben dem gewünschten audio-Element. In gleicher Weise werden die anderen beiden Button erkannt.
Jeder Button erhält einen Listener auf den click-Event, der feuert, wenn der Anwender ein Button anklickt (oder antippt bei Touch-Screen).
Bei Klick auf "Los" würde playalle() aufgerufen und der gewünschte Titel zuerst gespielt, vorausgesetzt, der Parameter numid enthält des passenden numerischen Index.
Bei "Start" wird einfach nur die Methode play() am audio-Element aufgerufen, bei "Stopp" die Methode pause().
Bei "Start" wird vor dem Abspielen die Zeit zurückgesetzt (Eigenschaft currentTime = 0), das bewirkt in jedem Fall einen Start des Titels von Anfang an. Dieses Feature habe ich in mancher Playlist-App vermisst, ein angespielter Titel wurde meistens an der Stelle weitergespielt, an der er gestoppt wurde, und nicht von vorn.

Anbinden der EventListener an die Button

Hatten wir das nicht gerade besprochen? - Wir haben besprochen, wie es die Funktion myplay() bei einem audio-Element machen würde, jedoch passieren wird es erst bei Aufruf der Funktion.
  for (var i = 0; i < alle.length; i++)
  {
    var currid = alle[i].id;
    myplay(currid, i);
  }
Diese kleine Schleife durchläuft den Zahlenbereich des Arrays alle und ermittelt für jedes der audio-Elemente den Id-Wert (alle[i].id). Der Index i ist dazu der passende numerische Wert.
Nun kann bei jedem Schleifendurchlauf myplay() mit den passenden Argumenten aufgerufen werden. Nach Ablauf der Schleife hat jeder Button neben jedem audio-Element seine EventListener.
Musik hören wir immer noch nicht, aber der Anwender kann die Musik jetzt per Button-Klick starten, was ja das Ziel unseres Programms ist.

War da nicht noch was?

Genau! - am Anfang des HTML-Codes befindet sich das Button "Los!", das muss noch seine Funktionalität erhalten.
  if (document.getElementById('startlist'))
  {
    document.getElementById('startlist').addEventListener('click', function() { 
      playalle(alle, 0);
    }, false);
  } else
  {
    playalle(alle, 0);
  }
Das ist der letzte Programmteil in unserer JavaScript-Anwendung.
Zuerst wird getestet, ob der Button überhaupt vorhanden ist. Wenn ja, wird anhand der Id ('startlist') der Button gefunden und über den click-Event wird playalle(alle, 0); aufgerufen. Die Playlist wird also erst nach Klick auf Los abgespielt.
Und wenn der Button gar nicht vorhanden ist? - dann wird die Playlist sofort nach dem Laden der Seite gestartet.