Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

Tạo playlist video/ âm thanh HTML5


Cơ chế playlist đơn giản

Sử dụng sự kiện ended của video/audio để chuyển sang media tiếp theo.

Ví dụ cơ bản: Playlist Video

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>HTML5 Video Playlist</title>
  <style>
    .player-container { max-width: 800px; margin: 2rem auto; }
    .video-wrapper { position: relative; width: 100%; aspect-ratio: 16/9; background: #000; border-radius: 8px; overflow: hidden; }
    video { width: 100%; height: 100%; display: block; }
    .playlist { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 1rem; }
    .playlist-item { 
      display: flex; align-items: center; gap: 1rem; 
      padding: 0.75rem; background: #f8f9fa; border-radius: 8px; cursor: pointer;
      transition: background 0.2s; border: 2px solid transparent;
    }
    .playlist-item:hover { background: #e9ecef; }
    .playlist-item.active { border-color: #3498db; background: #ebf5fb; }
    .playlist-item.playing::before { content: "▶ "; color: #3498db; }
    .thumb { width: 120px; height: 68px; object-fit: cover; border-radius: 4px; flex-shrink: 0; }
    .info { flex: 1; min-width: 0; }
    .title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .duration { font-size: 0.875rem; color: #666; }
  </style>
</head>
<body>
  <div class="player-container">
    <div class="video-wrapper">
      <video id="player" controls playsinline>
        <source src="" type="video/mp4">
        Trình duyệt không hỗ trợ video.
      </video>
    </div>
    
    <div class="playlist" id="playlist"></div>
  </div>

  <script>
    const playlistData = [
      { title: 'Video 1: Giới thiệu', src: 'video1.mp4', thumb: 'thumb1.jpg', duration: '10:30' },
      { title: 'Video 2: Tính năng', src: 'video2.mp4', thumb: 'thumb2.jpg', duration: '15:45' },
      { title: 'Video 3: Hướng dẫn', src: 'video3.mp4', thumb: 'thumb3.jpg', duration: '8:20' },
      { title: 'Video 4: Kết luận', src: 'video4.mp4', thumb: 'thumb4.jpg', duration: '5:10' },
    ];
    
    const player = document.getElementById('player');
    const source = player.querySelector('source');
    const playlistEl = document.getElementById('playlist');
    let currentIndex = 0;
    
    // Render playlist UI
    function renderPlaylist() {
      playlistEl.innerHTML = '';
      playlistData.forEach((item, index) => {
        const div = document.createElement('div');
        div.className = 'playlist-item' + (index === currentIndex ? ' active playing' : '');
        div.innerHTML = `
          <img class="thumb" src="${item.thumb}" alt="">
          <div class="info">
            <div class="title">${item.title}</div>
            <div class="duration">${item.duration}</div>
          </div>
        `;
        div.addEventListener('click', () => playVideo(index));
        playlistEl.appendChild(div);
      });
    }
    
    // Phát video tại index
    function playVideo(index) {
      currentIndex = index;
      const item = playlistData[index];
      source.src = item.src;
      player.load(); // Quan trọng: load lại source mới
      player.play().catch(e => console.log('Autoplay blocked:', e));
      renderPlaylist();
    }
    
    // Tự động chuyển video tiếp theo khi hết
    player.addEventListener('ended', () => {
      const nextIndex = (currentIndex + 1) % playlistData.length;
      playVideo(nextIndex);
    });
    
    // Keyboard navigation
    document.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowRight' || e.key === 'n') {
        playVideo((currentIndex + 1) % playlistData.length);
      } else if (e.key === 'ArrowLeft' || e.key === 'p') {
        playVideo((currentIndex - 1 + playlistData.length) % playlistData.length);
      }
    });
    
    // Khởi tạo
    renderPlaylist();
    playVideo(0);
  </script>
</body>
</html>

Playlist Audio (Music Player)

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>Audio Playlist</title>
  <style>
    .audio-player { max-width: 500px; margin: 2rem auto; padding: 1.5rem; border: 1px solid #ddd; border-radius: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
    .track-info { text-align: center; margin-bottom: 1rem; }
    .track-title { font-size: 1.25rem; font-weight: 600; }
    .track-artist { opacity: 0.8; font-size: 0.9rem; }
    .progress-container { margin: 1rem 0; }
    .progress-bar { width: 100%; height: 6px; background: rgba(255,255,255,0.3); border-radius: 3px; cursor: pointer; position: relative; }
    .progress-fill { height: 100%; background: white; border-radius: 3px; width: 0%; transition: width 0.1s linear; }
    .controls { display: flex; justify-content: center; align-items: center; gap: 1.5rem; }
    .controls button { background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; opacity: 0.8; transition: opacity 0.2s; }
    .controls button:hover { opacity: 1; }
    .controls button:disabled { opacity: 0.3; cursor: not-allowed; }
    .volume { display: flex; align-items: center; gap: 0.5rem; }
    .playlist { margin-top: 1.5rem; max-height: 200px; overflow-y: auto; }
    .playlist-item { display: flex; justify-content: space-between; padding: 0.5rem; border-radius: 4px; cursor: pointer; }
    .playlist-item:hover { background: rgba(255,255,255,0.1); }
    .playlist-item.active { background: rgba(255,255,255,0.2); }
  </style>
</head>
<body>
  <div class="audio-player">
    <audio id="audioPlayer" preload="metadata"></audio>
    
    <div class="track-info">
      <div class="track-title" id="trackTitle">Chọn bài hát</div>
      <div class="track-artist" id="trackArtist"></div>
    </div>
    
    <div class="progress-container">
      <div class="progress-bar" id="progressBar">
        <div class="progress-fill" id="progressFill"></div>
      </div>
      <div style="display:flex; justify-content:space-between; font-size:0.75rem; opacity:0.8; margin-top:0.25rem;">
        <span id="currentTime">0:00</span>
        <span id="duration">0:00</span>
      </div>
    </div>
    
    <div class="controls">
      <button id="prevBtn" onclick="prevTrack()" disabled>⏮</button>
      <button id="playPauseBtn" onclick="togglePlay()">▶</button>
      <button id="nextBtn" onclick="nextTrack()" disabled>⏭</button>
      <div class="volume">
        <span>🔊</span>
        <input type="range" id="volume" min="0" max="1" step="0.1" value="0.7" oninput="setVolume(this.value)">
      </div>
    </div>
    
    <div class="playlist" id="playlist"></div>
  </div>

  <script>
    const playlist = [
      { title: 'Bài 1', artist: 'Nghệ sĩ A', src: 'song1.mp3', cover: 'cover1.jpg' },
      { title: 'Bài 2', artist: 'Nghệ sĩ B', src: 'song2.mp3', cover: 'cover2.jpg' },
      { title: 'Bài 3', artist: 'Nghệ sĩ C', src: 'song3.mp3', cover: 'cover3.jpg' },
    ];
    
    const audio = document.getElementById('audioPlayer');
    const playlistEl = document.getElementById('playlist');
    let currentIndex = 0;
    let isPlaying = false;
    
    function renderPlaylist() {
      playlistEl.innerHTML = '';
      playlist.forEach((track, i) => {
        const div = document.createElement('div');
        div.className = 'playlist-item' + (i === currentIndex ? ' active' : '');
        div.innerHTML = `<span>${track.title}</span> <span>${track.artist}</span>`;
        div.onclick = () => loadTrack(i);
        playlistEl.appendChild(div);
      });
    }
    
    function loadTrack(index) {
      currentIndex = index;
      const track = playlist[index];
      audio.src = track.src;
      document.getElementById('trackTitle').textContent = track.title;
      document.getElementById('trackArtist').textContent = track.artist;
      renderPlaylist();
      audio.load();
      audio.play().then(() => {
        isPlaying = true;
        updatePlayPauseBtn();
      }).catch(e => console.log('Autoplay blocked'));
    }
    
    function togglePlay() {
      if (audio.paused) {
        audio.play();
        isPlaying = true;
      } else {
        audio.pause();
        isPlaying = false;
      }
      updatePlayPauseBtn();
    }
    
    function updatePlayPauseBtn() {
      document.getElementById('playPauseBtn').textContent = isPlaying ? '⏸' : '▶';
    }
    
    function nextTrack() {
      loadTrack((currentIndex + 1) % playlist.length);
    }
    
    function prevTrack() {
      loadTrack((currentIndex - 1 + playlist.length) % playlist.length);
    }
    
    function setVolume(val) {
      audio.volume = val;
    }
    
    // Progress bar
    audio.addEventListener('timeupdate', () => {
      const percent = (audio.currentTime / audio.duration) * 100 || 0;
      document.getElementById('progressFill').style.width = percent + '%';
      document.getElementById('currentTime').textContent = formatTime(audio.currentTime);
    });
    
    audio.addEventListener('loadedmetadata', () => {
      document.getElementById('duration').textContent = formatTime(audio.duration);
    });
    
    audio.addEventListener('ended', nextTrack);
    
    // Seek on progress bar click
    document.getElementById('progressBar').addEventListener('click', (e) => {
      const rect = e.target.getBoundingClientRect();
      const percent = (e.clientX - rect.left) / rect.width;
      audio.currentTime = percent * audio.duration;
    });
    
    function formatTime(sec) {
      if (isNaN(sec)) return '0:00';
      const m = Math.floor(sec / 60);
      const s = Math.floor(sec % 60).toString().padStart(2, '0');
      return `${m}:${s}`;
    }
    
    // Init
    renderPlaylist();
    audio.volume = 0.7;
  </script>
</body>
</html>

Mở rộng: Playlist động từ JSON

// Load playlist từ API
async function loadPlaylist() {
  const response = await fetch('/api/playlist');
  const data = await response.json();
  playlistData = data.tracks;
  renderPlaylist();
  playVideo(0);
}

Lưu ý quan trọng

  • player.load() bắt buộc sau khi đổi source.src
  • Xử lý play() promise rejection (autoplay policy)
  • Dọn dẹp event listeners khi destroy player
  • Preload strategy: metadata cho playlist, auto cho single video
  • Keyboard accessibility: Space, Arrow keys, Enter