<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)
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.