blankify

You've seen this music player a thousand times but never knew what's behind it #musicplayer #coding

Source Code

index.html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Now Playing</title>
  <link rel="stylesheet" href="styles.css"/>
</head>
<body>
  <main class="container">
    <div class="card">
      <div class="glow"></div>
      <div class="artwork"></div>
      <div class="info">
        <span class="song">Midnight Overdrive</span>
        <span class="artist">Neon Echoes</span>
      </div>
      <div class="progress-bar">
        <div class="progress" style="width: 52%"></div>
      </div>
      <div class="times">
        <span class="time">1:47</span>
        <span class="time">3:24</span>
      </div>
      <div class="controls">
        <button class="btn">⏮</button>
        <button class="btn play">▶</button>
        <button class="btn">⏭</button>
      </div>
    </div>
  </main>
  <script src="player.js"></script>
</body>
</html>
styles.css
body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background:
    radial-gradient(ellipse at 30% 50%, rgba(232, 67, 147, .12), transparent 50%),
    radial-gradient(ellipse at 70% 50%, rgba(108, 92, 231, .12), transparent 50%),
    #0a0a0f;
  font-family: system-ui, -apple-system, sans-serif;
}

.card {
  position: relative;
  width: 320px;
  padding: 24px;
  border-radius: 24px;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .08);
  display: flex;
  flex-direction: column;
  gap: 20px;

  .glow {
    position: absolute;
    inset: -40px;
    z-index: -1;
    border-radius: 60px;
    background: radial-gradient(circle, rgba(232, 67, 147, .25), rgba(108, 92, 231, .2), transparent 70%);
    filter: blur(40px);
  }

  .artwork {
    aspect-ratio: 1;
    border-radius: 16px;
    background: linear-gradient(135deg, #e84393, #6c5ce7 40%, #0984e3 70%, #00cec9);
    box-shadow: 0 8px 32px rgba(232, 67, 147, .3);
  }

  .song {
    display: block;
    font-size: 18px;
    font-weight: 700;
    color: #f5f5f7;
  }

  .artist {
    display: block;
    font-size: 14px;
    color: rgba(255, 255, 255, .45);
  }
}

.progress-bar {
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .1);

  .progress {
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, #e84393, #6c5ce7);
    transition: width 0.3s linear;
  }
}

.times {
  display: flex;
  justify-content: space-between;
  margin-top: -14px;

  .time {
    font-size: 12px;
    color: rgba(255, 255, 255, .3);
  }
}

.controls {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 28px;

  .btn {
    background: none;
    border: none;
    color: rgba(255, 255, 255, .6);
    font-size: 20px;
    padding: 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .play {
    width: 52px;
    height: 52px;
    border-radius: 50%;
    background: #f5f5f7;
    color: #0a0a0f;
    font-size: 22px;
    box-shadow: 0 4px 20px rgba(255, 255, 255, .15);
  }
}
player.js
class MusicPlayer {
  static REWIND_THRESHOLD = 3;

  #audio   = new Audio();
  #tracks  = [];
  #current = 0;
  #playing = false;

  #dom = {
    progress : document.querySelector('.progress'),
    play     : document.querySelector('.play'),
    prev     : document.querySelectorAll('.btn')[0],
    next     : document.querySelectorAll('.btn')[2],
    elapsed  : document.querySelectorAll('.time')[0],
    duration : document.querySelectorAll('.time')[1],
    song     : document.querySelector('.song'),
    artist   : document.querySelector('.artist'),
  };

  constructor(tracks) {
    this.#tracks = tracks;
    this.#bind();
    this.load(0);
  }

  get track() { return this.#tracks[this.#current]; }

  static parseName(path) {
    const name  = path.split('/').pop().replace(/\.[^.]+$/, '');
    const delim = name.indexOf(' - ');

    return delim === -1
      ? { artist: 'Unknown Artist', song: name }
      : { artist: name.slice(0, delim), song: name.slice(delim + 3) };
  }

  static formatTime(seconds) {
    if (!seconds || !isFinite(seconds)) return '0:00';

    const min = Math.floor(seconds / 60);
    const sec = String(Math.floor(seconds % 60)).padStart(2, '0');

    return `${min}:${sec}`;
  }

  load(index) {
    this.#current = ((index % this.#tracks.length) + this.#tracks.length) % this.#tracks.length;

    const { artist, song } = MusicPlayer.parseName(this.track);

    this.#audio.src              = this.track;
    this.#dom.artist.textContent = artist;
    this.#dom.song.textContent   = song;
    this.#dom.progress.style.width  = '0%';
    this.#dom.elapsed.textContent   = '0:00';
    this.#dom.duration.textContent  = '0:00';
  }

  toggle() {
    this.#playing ? this.#audio.pause() : this.#audio.play();
    this.#playing = !this.#playing;
    this.#dom.play.textContent = this.#playing ? '⏸' : '▶';
  }

  prev() {
    if (this.#audio.currentTime > MusicPlayer.REWIND_THRESHOLD) {
      this.#audio.currentTime = 0;
      return;
    }

    this.load(this.#current - 1);
    if (this.#playing) this.#audio.play();
  }

  next() {
    this.load(this.#current + 1);
    if (this.#playing) this.#audio.play();
  }

  #updateProgress() {
    const { currentTime, duration } = this.#audio;

    this.#dom.progress.style.width = `${(currentTime / duration * 100)}%`;
    this.#dom.elapsed.textContent  = MusicPlayer.formatTime(currentTime);
  }

  #bind() {
    this.#dom.play.addEventListener('click', () => this.toggle());
    this.#dom.prev.addEventListener('click', () => this.prev());
    this.#dom.next.addEventListener('click', () => this.next());

    this.#audio.addEventListener('loadedmetadata', () => {
      this.#dom.duration.textContent = MusicPlayer.formatTime(this.#audio.duration);
    });

    this.#audio.addEventListener('timeupdate', () => this.#updateProgress());
    this.#audio.addEventListener('ended',      () => this.next());
  }
}

// ── Init ────────────────────────────────────────────────────────────
// Add your files to the music/ folder.
// Format: "Artist - Song Name.mp3"

const player = new MusicPlayer([
  'music/Giraffe Squad - Wait For Me.mp3',
  'music/Gameboy Tetris, nublu, Cartoon, Jéja - Biology.mp3',
]);