<div class="player">
  <div class="player-track-meta">
    <p>Outfoxing the Fox</p>
    <p><span>Kevin MacLeod</span></p>
  </div>
  <div class="player-controls">
    <button class="player-play-btn" 
            role="button" 
            aria-label="Play"
            data-playing="false"
            >
      <div class="player-icon-play">
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>play</title><polygon class="icon-play" points="19.05 12 6 3.36 6 20.64 19.05 12"/><rect class="icon-container" width="24" height="24"/></svg>
        </div>
      
      <div class="player-icon-pause hidden">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>pause</title><g><rect  class="icon-pause" x="6" y="3.26" width="4" height="17.48"/><rect class="icon-pause" x="14" y="3.26" width="4" height="17.48"/></g><rect class="icon-container" width="24" height="24"/></svg>
      </div>
    </button>
    <div class="player-timeline">
      <span class="player-time player-time-current">00:00</span>
      <div class="player-progress">
        <div class="player-progress-filled"></div>
      </div>
      <span class="player-time player-time-duration">00:00</span>
    </div>
    <div class="player-volume-container">
      	<input type="range" id="volume" min="0" max="1" value="1" step="0.01" class="player-volume" />
    </div>
  </div>
  
  <audio src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/858/outfoxing.mp3" crossorigin="anonymous" ></audio>
</div>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');

body {
  background: url("https://assets.codepen.io/210284/music-bg_1.jpg") center center;
  background-size: cover;
  color: #1f2937;
  font-family: 'Inter', sans-serif;
}

.hidden {
  display: none;
}

.player {
  max-width: 500px;
  margin: 7rem auto;
  background: white;
  padding: 36px 32px 24px 32px;
  border-radius: 14px;
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}

.player-track-meta {
  text-align: center;
}

.player-track-meta p {
  margin: 0;
  font-size: 20px;
  color: #0e0e0e;
  font-weight: 700;
}

.player-track-meta span {
  font-size: 16px;
  font-weight: 400;
  padding: 0 2px;
  position: relative;
  top: 1px;
  color: #a3a3a3;
}

.player-controls {
  display: flex;
  align-items: center;
}

.player-play-btn {
  background: transparent;
  border: none;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 36px;
  height: 36px;
}

.icon-container {
  fill: transparent;
  stroke: none;
}

.player-play-btn:hover {
  fill: #444444;
}

.player-play-btn svg { 
  color: #0e0e0e;
  position: relative;
  left: 0.5px;
  width: 36px;
  height: 36px;
  display: block;
}

.player-play-btn:hover svg {
  color: #ffffff;
}

.player-timeline {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  padding-left: 10px;
}

.player-progress {
  display: flex;
  postion: relative;
  height: 6px;
  background: #a3a3a3;
  border-radius: 25px;
  margin: 0 5px;
  flex: 10;
  flex-basis: 100%;
  overflow: hidden;
}

.player-progress-filled {
  height: 6px;
  background: #0e0e0e;
  flex: 0;
  flex-basis: 0%;
  border-radius: 25px;
}

.player-time {
  padding: 2px 5px;
}

.player-volume-container {
  width: 15%;
}
.player-volume {
  height: 28px;
  -webkit-appearance: none;
  margin: 10px 0;
  width: 100%;
  background: transparent;
}

.player-volume:focus {
  outline: none;
}

.player-volume::-webkit-slider-runnable-track {
  width: 100%;
  height: 6px;
  cursor: pointer;
  animate: 0.2s;
  background: #0e0e0e;
  border-radius: 10px;
}

.player-volume::-webkit-slider-thumb {
  height: 16px;
  width: 16px;
  border-radius: 100px;
  border: none;
  background: #0e0e0e;
  cursor: pointer;
  -webkit-appearance: none;
  margin-top: -4px;
}

.player-volume:focus::-webkit-slider-runnable-track {
  background: #0e0e0e;
}

.player-volume::-moz-range-track {
  width: 100%;
  height: 6px;
  cursor: pointer;
  animate: 0.2s;
  background: #0e0e0e;
  border-radius: 10px;
}

.player-volume::-moz-range-thumb {
  height: 16px;
  width: 16px;
  border-radius: 100px;
  border: none;
  background: #0e0e0e;
  cursor: pointer;
  margin-top: -4px;
}

.player-volume::-ms-track {
  width: 100%;
  height: 6px;
  cursor: pointer;
  animate: 0.2s;
  background: #0e0e0e;
  border-radius: 10px;
}

.player-volume::-ms-fill-lower {
  background: #0e0e0e;
  border-radius: 10px;
}

.player-volume::-ms-fill-upper {
  background: #0e0e0e;
  border-radius: 10px;
}

.player-volume::-ms-thumb {
  margin-top: 1px;
  height: 15px;
  width: 15px;
  border-radius: 5px;
  border: none;
  background: #0e0e0e;
  cursor: pointer;
}

.player-volume:focus::-ms-fill-lower {
  background: #38bdf8;
}

.player-volume:focus::-ms-fill-upper {
  background: #38bdf8;
}
// load sound via <audio tag
const audioElement = document.querySelector("audio");
const audioCtx = new AudioContext();
const track = audioCtx.createMediaElementSource(audioElement);

// Player controls and attributes
const playButton = document.querySelector(".player-play-btn");
const playIcon = playButton.querySelector(".player-icon-play");
const pauseIcon = playButton.querySelector(".player-icon-pause");
const progress = document.querySelector(".player-progress");
const progressFilled = document.querySelector(".player-progress-filled");
const playerCurrentTime = document.querySelector(".player-time-current");
const playerDuration = document.querySelector(".player-time-duration");
const volumeControl = document.querySelector(".player-volume")

window.addEventListener("load", () => {
  // Set times after page load
  setTimes();

  // Update progress bar and time values as audio plays
  audioElement.addEventListener("timeupdate", () => {
    progressUpdate();
    setTimes();
  });

  // Play button toggle
  playButton.addEventListener("click", () => {
    // check if context is in suspended state (autoplay policy)
    // By default browsers won't allow you to autoplay audio.
    // You can overide by finding the AudioContext state and resuming it after a user interaction like a "click" event.
    if (audioCtx.state === "suspended") {
      audioCtx.resume();
    }

    // Play or pause track depending on state
    if (playButton.dataset.playing === "false") {
      audioElement.play();

      playButton.dataset.playing = "true";
      playIcon.classList.add("hidden");
      pauseIcon.classList.remove("hidden");
    } else if (playButton.dataset.playing === "true") {
      audioElement.pause();
      playButton.dataset.playing = "false";
      pauseIcon.classList.add("hidden");
      playIcon.classList.remove("hidden");
    }
  });

  // if the track ends reset the player
  audioElement.addEventListener("ended", () => {
    playButton.dataset.playing = "false";
    pauseIcon.classList.add("hidden");
    playIcon.classList.remove("hidden");
    progressFilled.style.flexBasis = "0%";
    audioElement.currentTime = 0;
    audioElement.duration = audioElement.duration;
  });

  // Bridge the gap between gainNode and AudioContext so we can manipulate volume (gain)
  const gainNode = audioCtx.createGain();
  const volumeControl = document.querySelector(".player-volume");
  volumeControl.addEventListener("change", () => {
    gainNode.gain.value = volumeControl.value;
  });

  track.connect(gainNode).connect(audioCtx.destination);

  // Display currentTime and duration properties in real time
  function setTimes() {
    playerCurrentTime.textContent = new Date(audioElement.currentTime * 1000)
      .toISOString()
      .substr(11, 8);
    playerDuration.textContent = new Date(audioElement.duration * 1000)
      .toISOString()
      .substr(11, 8);
  }

  // Update player timeline progress visually
  function progressUpdate() {
    const percent = (audioElement.currentTime / audioElement.duration) * 100;
    progressFilled.style.flexBasis = `${percent}%`;
  }

  // Scrub player timeline to skip forward and back on click for easier UX
  let mousedown = false;

  function scrub(event) {
    const scrubTime =
      (event.offsetX / progress.offsetWidth) * audioElement.duration;
    audioElement.currentTime = scrubTime;
  }

  progress.addEventListener("click", scrub);
  progress.addEventListener("mousemove", (e) => mousedown && scrub(e));
  progress.addEventListener("mousedown", () => (mousedown = true));
  progress.addEventListener("mouseup", () => (mousedown = false));

  // Track credit: Outfoxing the Fox by Kevin MacLeod under Creative Commons

}, false)

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.