🎵 Projekt: Audio-Player

Erstelle deinen eigenen Web-Audio-Player mit Effekten

Projektübersicht

In diesem Projekt wirst du einen vollständigen Audio-Player mit Hilfe der Web Audio API erstellen. Der Player soll grundlegende Funktionen bieten sowie verschiedene Audio-Effekte implementieren.

💡 Ziel: Ein funktionsfähiger Audio-Player mit Wiedergabesteuerung und Effektparametern, der im Browser läuft.
Audio-Player Projekt - Architektur Darstellung der Architektur eines Web-Audio-Players mit verschiedenen Komponenten: Audio-Quelle, Effekte, Steuerung Audio-Player Audio-Quelle Datei / Stream Effekt-Chain Filter, Verzerrung, Hall Ausgabe Lautsprecher Steuerung Play, Pause, Lautstärke, Effekte
Architektur des Audio-Players: Audio-Quelle → Effekt-Chain → Ausgabe, mit separater Steuerungskomponente
💡 W3Schools Ressource: Für Informationen über Audio-Player-Implementierung mit HTML5 siehe HTML5 Audio Player auf W3Schools

Lernziele

Voraussetzungen

Projektstruktur

audio-player/
├── index.html
├── css/
│   └── style.css
├── js/
│   └── player.js
└── audio/
    └── beispiel.mp3

HTML-Grundgerüst

Erstelle die grundlegende HTML-Struktur für den Audio-Player:

<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <title>Audio-Player</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div class="player-container">
    <h1>Mein Audio-Player</h1>
    
    <!-- Audio-Datei-Auswahl -->
    <input type="file" id="audioFile" accept="audio/*">
    
    <!-- Wiedergabesteuerung -->
    <div class="controls">
      <button id="playBtn">▶</button>
      <button id="pauseBtn">⏸</button>
      <button id="stopBtn">⏹</button>
    </div>
    
    <!-- Fortschrittsanzeige -->
    <div class="progress-container">
      <input type="range" id="progressBar" min="0" max="100" value="0">
      <span id="timeDisplay">0:00 / 0:00</span>
    </div>
    
    <!-- Lautstärke -->
    <div class="volume-control">
      <label for="volumeSlider">Lautstärke:</label>
      <input type="range" id="volumeSlider" min="0" max="1" step="0.01" value="0.8">
    </div>
    
    <!-- Effekt-Steuerung -->
    <div class="effects-controls">
      <label for="reverbSlider">Hall:</label>
      <input type="range" id="reverbSlider" min="0" max="1" step="0.01" value="0">
      
      <label for="delaySlider">Verzögerung:</label>
      <input type="range" id="delaySlider" min="0" max="1" step="0.01" value="0">
    </div>
  </div>
  
  <script src="js/player.js"></script>
</body>
</html>

JavaScript-Implementierung

Implementiere die Hauptfunktionalitäten des Audio-Players:

// player.js
class AudioPlayer {
  constructor() {
    this.audioContext = null;
    this.audioSource = null;
    this.audioBuffer = null;
    this.isPlaying = false;
    this.startTime = 0;
    this.pauseTime = 0;
    
    // Effekt-Knoten
    this.gainNode = null;
    this.reverbNode = null;
    this.delayNode = null;
    
    this.initializeElements();
    this.setupEventListeners();
    this.initAudioContext();
  }
  
  initializeElements() {
    this.playBtn = document.getElementById('playBtn');
    this.pauseBtn = document.getElementById('pauseBtn');
    this.stopBtn = document.getElementById('stopBtn');
    this.progressBar = document.getElementById('progressBar');
    this.timeDisplay = document.getElementById('timeDisplay');
    this.volumeSlider = document.getElementById('volumeSlider');
    this.reverbSlider = document.getElementById('reverbSlider');
    this.delaySlider = document.getElementById('delaySlider');
    this.audioFileInput = document.getElementById('audioFile');
  }
  
  setupEventListeners() {
    this.playBtn.addEventListener('click', () => this.play());
    this.pauseBtn.addEventListener('click', () => this.pause());
    this.stopBtn.addEventListener('click', () => this.stop());
    this.volumeSlider.addEventListener('input', () => this.updateVolume());
    this.reverbSlider.addEventListener('input', () => this.updateReverb());
    this.delaySlider.addEventListener('input', () => this.updateDelay());
    this.audioFileInput.addEventListener('change', (e) => this.loadAudioFile(e));
  }
  
  initAudioContext() {
    this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
    
    // Initialisiere Effekt-Knoten
    this.gainNode = this.audioContext.createGain();
    this.gainNode.gain.value = this.volumeSlider.value;
    
    // Reverb (vereinfacht mit Convolver)
    this.reverbNode = this.audioContext.createConvolver();
    
    // Delay
    this.delayNode = this.audioContext.createDelay(1.0);
    const feedback = this.audioContext.createGain();
    feedback.gain.value = 0.3;
    
    // Verbindungen: Quelle -> Effekte -> Ausgabe
    this.gainNode.connect(this.delayNode);
    this.delayNode.connect(feedback);
    feedback.connect(this.delayNode);
    this.delayNode.connect(this.reverbNode);
    this.reverbNode.connect(this.audioContext.destination);
  }
  
  async loadAudioFile(event) {
    const file = event.target.files[0];
    if (!file) return;
    
    const arrayBuffer = await file.arrayBuffer();
    this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
    this.updateTimeDisplay();
  }
  
  play() {
    if (!this.audioBuffer) return;
    
    if (this.audioContext.state === 'suspended') {
      this.audioContext.resume();
    }
    
    // Stoppe vorherige Wiedergabe
    if (this.audioSource) {
      this.audioSource.disconnect();
    }
    
    // Erstelle neue Quelle
    this.audioSource = this.audioContext.createBufferSource();
    this.audioSource.buffer = this.audioBuffer;
    this.audioSource.connect(this.gainNode);
    
    // Starte Wiedergabe
    const startTime = this.audioContext.currentTime;
    this.audioSource.start(0, this.pauseTime);
    this.isPlaying = true;
    this.startTime = startTime - this.pauseTime;
    
    // Starte Fortschrittsaktualisierung
    this.updateProgress();
  }
  
  pause() {
    if (!this.isPlaying) return;
    
    this.audioSource.stop();
    this.pauseTime = this.audioContext.currentTime - this.startTime;
    this.isPlaying = false;
  }
  
  stop() {
    if (this.audioSource) {
      this.audioSource.stop();
      this.audioSource.disconnect();
      this.audioSource = null;
    }
    this.isPlaying = false;
    this.pauseTime = 0;
    this.startTime = 0;
    this.progressBar.value = 0;
    this.updateTimeDisplay();
  }
  
  updateVolume() {
    if (this.gainNode) {
      this.gainNode.gain.value = this.volumeSlider.value;
    }
  }
  
  updateReverb() {
    // Vereinfachte Implementierung - in der Realität müsste ein Impulsantwort geladen werden
    // Für dieses Beispiel simulieren wir den Effekt mit einem Gain-Node
    if (this.reverbNode) {
      this.reverbNode.gain.value = this.reverbSlider.value;
    }
  }
  
  updateDelay() {
    if (this.delayNode) {
      this.delayNode.delayTime.value = this.delaySlider.value * 0.5; // Max 500ms
    }
  }
  
  updateProgress() {
    if (!this.isPlaying) return;
    
    const currentTime = this.audioContext.currentTime - this.startTime;
    const duration = this.audioBuffer ? this.audioBuffer.duration : 0;
    
    if (duration > 0) {
      const progress = (currentTime / duration) * 100;
      this.progressBar.value = Math.min(progress, 100);
      this.updateTimeDisplay(currentTime, duration);
    }
    
    if (currentTime < duration) {
      requestAnimationFrame(() => this.updateProgress());
    } else {
      this.isPlaying = false;
      this.pauseTime = 0;
    }
  }
  
  updateTimeDisplay(currentTime = 0, duration = 0) {
    if (!this.audioBuffer) {
      duration = this.audioBuffer.duration;
    }
    
    const formatTime = (seconds) => {
      const mins = Math.floor(seconds / 60);
      const secs = Math.floor(seconds % 60);
      return `${mins}:${secs.toString().padStart(2, '0')}`;
    };
    
    this.timeDisplay.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
  }
}

// Initialisiere den Player sobald die Seite geladen ist
document.addEventListener('DOMContentLoaded', () => {
  new AudioPlayer();
});

CSS-Styling

Erstelle ein ansprechendes Styling für den Audio-Player:

/* style.css */
.player-container {
  max-width: 600px;
  margin: 2rem auto;
  padding: 2rem;
  background: var(--card-bg);
  border-radius: 16px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.08);
  border: 1px solid var(--border-light);
}

.player-container h1 {
  text-align: center;
  color: var(--h1-color);
  margin-bottom: 1.5rem;
}

.controls {
  display: flex;
  justify-content: center;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.controls button {
  padding: 0.8rem 1.5rem;
  font-size: 1.2rem;
  border-radius: 8px;
  cursor: pointer;
  background-color: var(--primary);
  color: white;
  border: none;
}

.controls button:hover {
  background-color: var(--primary-light);
}

.progress-container {
  margin-bottom: 1.5rem;
}

#progressBar {
  width: 100%;
  margin-bottom: 0.5rem;
}

#timeDisplay {
  display: block;
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.9rem;
}

.volume-control, .effects-controls {
  margin-bottom: 1.5rem;
}

.volume-control label, .effects-controls label {
  display: block;
  margin-bottom: 0.5rem;
  color: var(--text);
}

.effects-controls {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.effects-controls label {
  margin-bottom: 0.25rem;
}

input[type="range"] {
  width: 100%;
  margin-bottom: 1rem;
}

Erweiterte Funktionen

Playlist-Funktionalität

Erweitere den Player um Playlist-Unterstützung:

Visualisierung

Füge eine Audio-Visualisierung hinzu:

// Füge dies zur Klasse hinzu
initVisualizer() {
  this.analyser = this.audioContext.createAnalyser();
  this.analyser.fftSize = 2048;
  
  // Verbinde nach dem Gain-Node
  this.gainNode.connect(this.analyser);
  this.analyser.connect(this.audioContext.destination);
  
  // Canvas für Visualisierung
  this.canvas = document.createElement('canvas');
  this.canvas.width = 600;
  this.canvas.height = 200;
  document.querySelector('.player-container').appendChild(this.canvas);
  this.canvasCtx = this.canvas.getContext('2d');
  
  this.drawVisualizer();
}

drawVisualizer() {
  if (!this.analyser) return;
  
  const bufferLength = this.analyser.frequencyBinCount;
  const dataArray = new Uint8Array(bufferLength);
  
  const draw = () => {
    requestAnimationFrame(draw);
    
    this.analyser.getByteTimeDomainData(dataArray);
    
    this.canvasCtx.fillStyle = 'rgb(0, 0, 0)';
    this.canvasCtx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    
    this.canvasCtx.lineWidth = 2;
    this.canvasCtx.strokeStyle = '#00bcd4';
    this.canvasCtx.beginPath();
    
    const sliceWidth = this.canvas.width * 1.0 / bufferLength;
    let x = 0;
    
    for (let i = 0; i < bufferLength; i++) {
      const v = dataArray[i] / 128.0;
      const y = v * this.canvas.height / 2;
      
      if (i === 0) {
        this.canvasCtx.moveTo(x, y);
      } else {
        this.canvasCtx.lineTo(x, y);
      }
      
      x += sliceWidth;
    }
    
    this.canvasCtx.lineTo(this.canvas.width, this.canvas.height / 2);
    this.canvasCtx.stroke();
  };
  
  draw();
}

Test und Debugging

Testfälle

Debugging-Tipps

Deployment

Um den Audio-Player online verfügbar zu machen:

  1. Stelle sicher, dass alle Dateien korrekt organisiert sind
  2. Teste die Anwendung lokal mit einem Webserver
  3. Lade die Dateien auf einen Webserver hoch
  4. Stelle sicher, dass die Audio-Dateien korrekt bereitgestellt werden
  5. Teste die Online-Version auf verschiedenen Geräten
⚠️ Hinweis: Einige Browser blockieren Audio-Wiedergabe, bis der Nutzer eine Interaktion durchgeführt hat (z.B. Klick auf Play-Button).

Erweiterungsideen

Glückwunsch! Du hast einen vollständigen Audio-Player erstellt!

→ Zurück zum Hauptmenü