A Pen by MohammadTaseenKhan on CodePen.
Created
August 8, 2022 07:50
-
-
Save MohammadTaseenKhan/20cf8ca71df9360e42021f0f400985d7 to your computer and use it in GitHub Desktop.
Custom Audio Player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<style> | |
button { | |
padding: 0; | |
border: 0; | |
background: transparent; | |
cursor: pointer; | |
outline: none; | |
width: 40px; | |
height: 40px; | |
float: left; | |
} | |
#audio-player-container { | |
position: relative; | |
margin: 100px 2.5% auto 2.5%; | |
width: 95%; | |
max-width: 500px; | |
height: 132px; | |
background: #fff; | |
font-family: Arial, Helvetica, sans-serif; | |
--seek-before-width: 0%; | |
--volume-before-width: 100%; | |
--buffered-width: 0%; | |
letter-spacing: -0.5px; | |
} | |
#audio-player-container::before { | |
position: absolute; | |
content: ''; | |
width: calc(100% + 4px); | |
height: calc(100% + 4px); | |
left: -2px; | |
top: -2px; | |
background: linear-gradient(to left, #007db5, #ff8a00); | |
z-index: -1; | |
} | |
p { | |
position: absolute; | |
top: -18px; | |
right: 5%; | |
padding: 0 5px; | |
margin: 0; | |
font-size: 28px; | |
background: #fff; | |
} | |
#play-icon { | |
margin: 20px 2.5% 10px 2.5%; | |
} | |
path { | |
stroke: #007db5; | |
} | |
.time { | |
display: inline-block; | |
width: 37px; | |
text-align: center; | |
font-size: 20px; | |
margin: 28.5px 0 18.5px 0; | |
float: left; | |
} | |
output { | |
display: inline-block; | |
width: 32px; | |
text-align: center; | |
font-size: 20px; | |
margin: 10px 2.5% 0 5%; | |
float: left; | |
clear: left; | |
} | |
#volume-slider { | |
margin: 10px 2.5%; | |
width: 58%; | |
} | |
#volume-slider::-webkit-slider-runnable-track { | |
background: rgba(0, 125, 181, 0.6); | |
} | |
#volume-slider::-moz-range-track { | |
background: rgba(0, 125, 181, 0.6); | |
} | |
#volume-slider::-ms-fill-upper { | |
background: rgba(0, 125, 181, 0.6); | |
} | |
#volume-slider::before { | |
width: var(--volume-before-width); | |
} | |
#mute-icon { | |
margin: 0 2.5%; | |
} | |
input[type="range"] { | |
position: relative; | |
-webkit-appearance: none; | |
width: 48%; | |
margin: 0; | |
padding: 0; | |
height: 19px; | |
margin: 30px 2.5% 20px 2.5%; | |
float: left; | |
outline: none; | |
} | |
input[type="range"]::-webkit-slider-runnable-track { | |
width: 100%; | |
height: 3px; | |
cursor: pointer; | |
background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width)); | |
} | |
input[type="range"]::before { | |
position: absolute; | |
content: ""; | |
top: 8px; | |
left: 0; | |
width: var(--seek-before-width); | |
height: 3px; | |
background-color: #007db5; | |
cursor: pointer; | |
} | |
input[type="range"]::-webkit-slider-thumb { | |
position: relative; | |
-webkit-appearance: none; | |
box-sizing: content-box; | |
border: 1px solid #007db5; | |
height: 15px; | |
width: 15px; | |
border-radius: 50%; | |
background-color: #fff; | |
cursor: pointer; | |
margin: -7px 0 0 0; | |
} | |
input[type="range"]:active::-webkit-slider-thumb { | |
transform: scale(1.2); | |
background: #007db5; | |
} | |
input[type="range"]::-moz-range-track { | |
width: 100%; | |
height: 3px; | |
cursor: pointer; | |
background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width)); | |
} | |
input[type="range"]::-moz-range-progress { | |
background-color: #007db5; | |
} | |
input[type="range"]::-moz-focus-outer { | |
border: 0; | |
} | |
input[type="range"]::-moz-range-thumb { | |
box-sizing: content-box; | |
border: 1px solid #007db5; | |
height: 15px; | |
width: 15px; | |
border-radius: 50%; | |
background-color: #fff; | |
cursor: pointer; | |
} | |
input[type="range"]:active::-moz-range-thumb { | |
transform: scale(1.2); | |
background: #007db5; | |
} | |
input[type="range"]::-ms-track { | |
width: 100%; | |
height: 3px; | |
cursor: pointer; | |
background: transparent; | |
border: solid transparent; | |
color: transparent; | |
} | |
input[type="range"]::-ms-fill-lower { | |
background-color: #007db5; | |
} | |
input[type="range"]::-ms-fill-upper { | |
background: linear-gradient(to right, rgba(0, 125, 181, 0.6) var(--buffered-width), rgba(0, 125, 181, 0.2) var(--buffered-width)); | |
} | |
input[type="range"]::-ms-thumb { | |
box-sizing: content-box; | |
border: 1px solid #007db5; | |
height: 15px; | |
width: 15px; | |
border-radius: 50%; | |
background-color: #fff; | |
cursor: pointer; | |
} | |
input[type="range"]:active::-ms-thumb { | |
transform: scale(1.2); | |
background: #007db5; | |
} | |
</style> | |
<div id="audio-player-container"> | |
<audio src="" preload="metadata" loop></audio> | |
<p>Play Audio</p> | |
<button id="play-icon"></button> | |
<span id="current-time" class="time">0:00</span> | |
<input type="range" id="seek-slider" max="100" value="0"> | |
<span id="duration" class="time">0:00</span> | |
<output id="volume-output">100</output> | |
<input type="range" id="volume-slider" max="100" value="100"> | |
<button id="mute-icon"></button> | |
</div> | |
</template> | |
<audio-player data-src="https://audio-hub.vercel.app/Sigma%20Rule%20Song.mp3"></audio-player> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import lottieWeb from "https://cdn.skypack.dev/lottie-web"; | |
class AudioPlayer extends HTMLElement { | |
constructor() { | |
super(); | |
const template = document.querySelector("template"); | |
const templateContent = template.content; | |
const shadow = this.attachShadow({ mode: "open" }); | |
shadow.appendChild(templateContent.cloneNode(true)); | |
} | |
connectedCallback() { | |
everything(this); | |
} | |
} | |
const everything = function (element) { | |
const shadow = element.shadowRoot; | |
const audioPlayerContainer = shadow.getElementById("audio-player-container"); | |
const playIconContainer = shadow.getElementById("play-icon"); | |
const seekSlider = shadow.getElementById("seek-slider"); | |
const volumeSlider = shadow.getElementById("volume-slider"); | |
const muteIconContainer = shadow.getElementById("mute-icon"); | |
const audio = shadow.querySelector("audio"); | |
const durationContainer = shadow.getElementById("duration"); | |
const currentTimeContainer = shadow.getElementById("current-time"); | |
const outputContainer = shadow.getElementById("volume-output"); | |
let playState = "play"; | |
let muteState = "unmute"; | |
let raf = null; | |
audio.src = element.getAttribute("data-src"); | |
const playAnimation = lottieWeb.loadAnimation({ | |
container: playIconContainer, | |
path: | |
"https://maxst.icons8.com/vue-static/landings/animated-icons/icons/pause/pause.json", | |
renderer: "svg", | |
loop: false, | |
autoplay: false, | |
name: "Play Animation" | |
}); | |
const muteAnimation = lottieWeb.loadAnimation({ | |
container: muteIconContainer, | |
path: | |
"https://maxst.icons8.com/vue-static/landings/animated-icons/icons/mute/mute.json", | |
renderer: "svg", | |
loop: false, | |
autoplay: false, | |
name: "Mute Animation" | |
}); | |
playAnimation.goToAndStop(14, true); | |
const whilePlaying = () => { | |
seekSlider.value = Math.floor(audio.currentTime); | |
currentTimeContainer.textContent = calculateTime(seekSlider.value); | |
audioPlayerContainer.style.setProperty( | |
"--seek-before-width", | |
`${(seekSlider.value / seekSlider.max) * 100}%` | |
); | |
raf = requestAnimationFrame(whilePlaying); | |
}; | |
const showRangeProgress = (rangeInput) => { | |
if (rangeInput === seekSlider) | |
audioPlayerContainer.style.setProperty( | |
"--seek-before-width", | |
(rangeInput.value / rangeInput.max) * 100 + "%" | |
); | |
else | |
audioPlayerContainer.style.setProperty( | |
"--volume-before-width", | |
(rangeInput.value / rangeInput.max) * 100 + "%" | |
); | |
}; | |
const calculateTime = (secs) => { | |
const minutes = Math.floor(secs / 60); | |
const seconds = Math.floor(secs % 60); | |
const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`; | |
return `${minutes}:${returnedSeconds}`; | |
}; | |
const displayDuration = () => { | |
durationContainer.textContent = calculateTime(audio.duration); | |
}; | |
const setSliderMax = () => { | |
seekSlider.max = Math.floor(audio.duration); | |
}; | |
const displayBufferedAmount = () => { | |
const bufferedAmount = Math.floor( | |
audio.buffered.end(audio.buffered.length - 1) | |
); | |
audioPlayerContainer.style.setProperty( | |
"--buffered-width", | |
`${(bufferedAmount / seekSlider.max) * 100}%` | |
); | |
}; | |
if (audio.readyState > 0) { | |
displayDuration(); | |
setSliderMax(); | |
displayBufferedAmount(); | |
} else { | |
audio.addEventListener("loadedmetadata", () => { | |
displayDuration(); | |
setSliderMax(); | |
displayBufferedAmount(); | |
}); | |
} | |
playIconContainer.addEventListener("click", () => { | |
if (playState === "play") { | |
audio.play(); | |
playAnimation.playSegments([14, 27], true); | |
requestAnimationFrame(whilePlaying); | |
playState = "pause"; | |
} else { | |
audio.pause(); | |
playAnimation.playSegments([0, 14], true); | |
cancelAnimationFrame(raf); | |
playState = "play"; | |
} | |
}); | |
muteIconContainer.addEventListener("click", () => { | |
if (muteState === "unmute") { | |
muteAnimation.playSegments([0, 15], true); | |
audio.muted = true; | |
muteState = "mute"; | |
} else { | |
muteAnimation.playSegments([15, 25], true); | |
audio.muted = false; | |
muteState = "unmute"; | |
} | |
}); | |
audio.addEventListener("progress", displayBufferedAmount); | |
seekSlider.addEventListener("input", (e) => { | |
showRangeProgress(e.target); | |
currentTimeContainer.textContent = calculateTime(seekSlider.value); | |
if (!audio.paused) { | |
cancelAnimationFrame(raf); | |
} | |
}); | |
seekSlider.addEventListener("change", () => { | |
audio.currentTime = seekSlider.value; | |
if (!audio.paused) { | |
requestAnimationFrame(whilePlaying); | |
} | |
}); | |
volumeSlider.addEventListener("input", (e) => { | |
const value = e.target.value; | |
showRangeProgress(e.target); | |
outputContainer.textContent = value; | |
audio.volume = value / 100; | |
}); | |
if ("mediaSession" in navigator) { | |
navigator.mediaSession.metadata = new MediaMetadata({ | |
title: "Komorebi", | |
artist: "Anitek", | |
album: "MainStay", | |
artwork: [ | |
{ | |
src: "https://assets.codepen.io/4358584/1.300.jpg", | |
sizes: "96x96", | |
type: "image/png" | |
}, | |
{ | |
src: "https://assets.codepen.io/4358584/1.300.jpg", | |
sizes: "128x128", | |
type: "image/png" | |
}, | |
{ | |
src: "https://assets.codepen.io/4358584/1.300.jpg", | |
sizes: "192x192", | |
type: "image/png" | |
}, | |
{ | |
src: "https://assets.codepen.io/4358584/1.300.jpg", | |
sizes: "256x256", | |
type: "image/png" | |
}, | |
{ | |
src: "https://assets.codepen.io/4358584/1.300.jpg", | |
sizes: "384x384", | |
type: "image/png" | |
}, | |
{ | |
src: "https://assets.codepen.io/4358584/1.300.jpg", | |
sizes: "512x512", | |
type: "image/png" | |
} | |
] | |
}); | |
navigator.mediaSession.setActionHandler("play", () => { | |
if (playState === "play") { | |
audio.play(); | |
playAnimation.playSegments([14, 27], true); | |
requestAnimationFrame(whilePlaying); | |
playState = "pause"; | |
} else { | |
audio.pause(); | |
playAnimation.playSegments([0, 14], true); | |
cancelAnimationFrame(raf); | |
playState = "play"; | |
} | |
}); | |
navigator.mediaSession.setActionHandler("pause", () => { | |
if (playState === "play") { | |
audio.play(); | |
playAnimation.playSegments([14, 27], true); | |
requestAnimationFrame(whilePlaying); | |
playState = "pause"; | |
} else { | |
audio.pause(); | |
playAnimation.playSegments([0, 14], true); | |
cancelAnimationFrame(raf); | |
playState = "play"; | |
} | |
}); | |
navigator.mediaSession.setActionHandler("seekbackward", (details) => { | |
audio.currentTime = audio.currentTime - (details.seekOffset || 10); | |
}); | |
navigator.mediaSession.setActionHandler("seekforward", (details) => { | |
audio.currentTime = audio.currentTime + (details.seekOffset || 10); | |
}); | |
navigator.mediaSession.setActionHandler("seekto", (details) => { | |
if (details.fastSeek && "fastSeek" in audio) { | |
audio.fastSeek(details.seekTime); | |
return; | |
} | |
audio.currentTime = details.seekTime; | |
}); | |
navigator.mediaSession.setActionHandler("stop", () => { | |
audio.currentTime = 0; | |
seekSlider.value = 0; | |
audioPlayerContainer.style.setProperty("--seek-before-width", "0%"); | |
currentTimeContainer.textContent = "0:00"; | |
if (playState === "pause") { | |
playAnimation.playSegments([0, 14], true); | |
cancelAnimationFrame(raf); | |
playState = "play"; | |
} | |
}); | |
} | |
}; | |
customElements.define("audio-player", AudioPlayer); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment