No edit summary |
No edit summary Tag: Reverted |
||
Line 6: | Line 6: | ||
<includeonly> | <includeonly> | ||
<html><audio | <html> | ||
< | <script> | ||
< | import lottieWeb from 'https://cdn.skypack.dev/lottie-web'; | ||
</audio> | |||
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); | |||
</script> | |||
<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>audio player ish</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="<!--{$mp3|escape:'urlpathinfo'}-->"></audio-player> | |||
<html> | <html> | ||
</includeonly> | </includeonly> |
Revision as of 09:07, 19 May 2023