A Pen by Thupten N Chakrishar on CodePen.
Created
February 17, 2023 11:34
-
-
Save vajradog/38df2ec057e4e9b2a23d9b0bf67ab3e3 to your computer and use it in GitHub Desktop.
xxaGYGj
This file contains hidden or 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
<!-- | |
Audio player courtesy of Ivan | |
https://codepen.io/k-ivan/pen/pJMLmJ | |
--> | |
<div class="audio-player"> | |
<div class="player-head"> | |
<div class="player-image"> | |
<img src="https://images.unsplash.com/photo-1542438408-abb260104ef3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1935&q=80" alt="" /> | |
</div> | |
<div class="player-close-icon"> | |
<i class="ion-ios-close"></i> | |
</div> | |
<div class="player-description"> | |
<h2>Start</h2> | |
<p>An introduction a really great audio experience</p> | |
</div> | |
</div> | |
<div class="player-controls"> | |
<div class="ap" id="ap"> | |
<div class="ap__inner"> | |
<div class="ap__item ap__item--playback"> | |
<button class="ap__controls ap__controls--prev"> | |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#333" width="24" height="24" viewBox="0 0 24 24"> | |
<path d="M9.516 12l8.484-6v12zM6 6h2.016v12h-2.016v-12z"></path> | |
</svg> | |
</button> | |
<button class="ap__controls ap__controls--toggle"> | |
<svg class="icon-play" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#333" width="36" height="36" viewBox="0 0 36 36" data-play="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z" data-pause="M 12,26 16.33,26 16.33,10 12,10 z M 20.66,26 25,26 25,10 20.66,10 z"> | |
<path d="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z"></path> | |
</svg> | |
</button> | |
<button class="ap__controls ap__controls--next"> | |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#333" width="24" height="24" viewBox="0 0 24 24"> | |
<path d="M15.984 6h2.016v12h-2.016v-12zM6 18v-12l8.484 6z"></path> | |
</svg> | |
</button> | |
<div class="ap__controls volume-container"> | |
<button class="volume-btn"> | |
<svg class="icon-volume-on" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#333" width="24" height="24" viewBox="0 0 24 24"> | |
<path d="M14.016 3.234q3.047 0.656 5.016 3.117t1.969 5.648-1.969 5.648-5.016 3.117v-2.063q2.203-0.656 3.586-2.484t1.383-4.219-1.383-4.219-3.586-2.484v-2.063zM16.5 12q0 2.813-2.484 4.031v-8.063q2.484 1.219 2.484 4.031zM3 9h3.984l5.016-5.016v16.031l-5.016-5.016h-3.984v-6z"></path> | |
</svg> | |
<svg class="icon-volume-off" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#333" width="24" height="24" viewBox="0 0 24 24"> | |
<path d="M12 3.984v4.219l-2.109-2.109zM4.266 3l16.734 16.734-1.266 1.266-2.063-2.063q-1.734 1.359-3.656 1.828v-2.063q1.172-0.328 2.25-1.172l-4.266-4.266v6.75l-5.016-5.016h-3.984v-6h4.734l-4.734-4.734zM18.984 12q0-2.391-1.383-4.219t-3.586-2.484v-2.063q3.047 0.656 5.016 3.117t1.969 5.648q0 2.25-1.031 4.172l-1.5-1.547q0.516-1.266 0.516-2.625zM16.5 12q0 0.422-0.047 0.609l-2.438-2.438v-2.203q2.484 1.219 2.484 4.031z"></path> | |
</svg> | |
</button> | |
<div class="volume"> | |
<div class="volume__track"> | |
<div class="volume__bar"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="ap__item ap__item--track"> | |
<div class="track"> | |
<div class="track__title">Queue is empty</div> | |
<div class="track__time"> | |
<span class="track__time--current">--</span> | |
<span> / </span> | |
<span class="track__time--duration">--</span> | |
</div> | |
<div class="progress-container"> | |
<div class="progress"> | |
<div class="progress__bar"></div> | |
<div class="progress__preload"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="ap__item ap__item--settings"> | |
<button class="ap__controls ap__controls--repeat"> | |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#333" width="24" height="24" viewBox="0 0 24 24"> | |
<path d="M17.016 17.016v-4.031h1.969v6h-12v3l-3.984-3.984 3.984-3.984v3h10.031zM6.984 6.984v4.031h-1.969v-6h12v-3l3.984 3.984-3.984 3.984v-3h-10.031z"></path> | |
</svg> | |
</button> | |
<button class="ap__controls ap__controls--playlist"> | |
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#333" width="24" height="24" viewBox="0 0 24 24"> | |
<path d="M17.016 12.984l4.969 3-4.969 3v-6zM2.016 15v-2.016h12.984v2.016h-12.984zM18.984 5.016v1.969h-16.969v-1.969h16.969zM18.984 9v2.016h-16.969v-2.016h16.969z"></path> | |
</svg> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> |
This file contains hidden or 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
(function(window, undefined) { | |
'use strict'; | |
var AudioPlayer = (function() { | |
// Player vars! | |
var | |
docTitle = document.title, | |
player = document.getElementById('ap'), | |
playBtn, | |
playSvg, | |
playSvgPath, | |
prevBtn, | |
nextBtn, | |
plBtn, | |
repeatBtn, | |
volumeBtn, | |
progressBar, | |
preloadBar, | |
curTime, | |
durTime, | |
trackTitle, | |
audio, | |
index = 0, | |
playList, | |
volumeBar, | |
wheelVolumeValue = 0, | |
volumeLength, | |
repeating = false, | |
seeking = false, | |
rightClick = false, | |
apActive = false, | |
// playlist vars | |
pl, | |
plUl, | |
plLi, | |
tplList = | |
'<li class="pl-list" data-track="{count}">'+ | |
'<div class="pl-list__track">'+ | |
'<div class="pl-list__icon"></div>'+ | |
'<div class="pl-list__eq">'+ | |
'<div class="eq">'+ | |
'<div class="eq__bar"></div>'+ | |
'<div class="eq__bar"></div>'+ | |
'<div class="eq__bar"></div>'+ | |
'<div class="eq__bar"></div>'+ | |
'</div>'+ | |
'</div>'+ | |
'</div>'+ | |
'<div class="pl-list__title">{title}</div>'+ | |
'<button class="pl-list__download">'+ | |
'<svg xmlns="http://www.w3.org/2000/svg"fill="#000000" height="20" viewBox="0 0 448 420" >'+ | |
'<polygon points="216.1,389 161.4,335.2 149.7,346.6 224.2,420 298.7,346.6 286.9,335.2 232.1,389 232.1,282 216.1,282 "/>'+ | |
'<path d="M367.3,118.4c0-1.2,0.2-2.3,0.2-3.5C367.5,51.4,314.6,0,250.7,0c-46.1,0-85.8,26.8-104.4,65.4c-8.1-4-17.1-6.3-26.8-6.3 c-29.6,0-54.1,23.3-58.9,51.3C25.4,122.4,0,153.8,0,192.8C0,241.9,40.1,282,89.6,282h70.5h21h35V147h16v135h134 c45,0,81.9-37.2,81.9-82.1S412.6,118.5,367.3,118.4z"/>'+ | |
'</svg>'+ | |
'</button>'+ | |
'</li>', | |
// settings | |
settings = { | |
volume : 0.7, | |
changeDocTitle: true, | |
confirmClose : true, | |
autoPlay : false, | |
buffered : true, | |
notification : true, | |
playList : [] | |
}; | |
function init(options) { | |
if(!('classList' in document.documentElement)) { | |
return false; | |
} | |
if(apActive || player === null) { | |
return 'Player already init'; | |
} | |
settings = extend(settings, options); | |
// get player elements | |
playBtn = player.querySelector('.ap__controls--toggle'); | |
playSvg = playBtn.querySelector('.icon-play'); | |
playSvgPath = playSvg.querySelector('path'); | |
prevBtn = player.querySelector('.ap__controls--prev'); | |
nextBtn = player.querySelector('.ap__controls--next'); | |
repeatBtn = player.querySelector('.ap__controls--repeat'); | |
volumeBtn = player.querySelector('.volume-btn'); | |
plBtn = player.querySelector('.ap__controls--playlist'); | |
curTime = player.querySelector('.track__time--current'); | |
durTime = player.querySelector('.track__time--duration'); | |
trackTitle = player.querySelector('.track__title'); | |
progressBar = player.querySelector('.progress__bar'); | |
preloadBar = player.querySelector('.progress__preload'); | |
volumeBar = player.querySelector('.volume__bar'); | |
playList = settings.playList; | |
playBtn.addEventListener('click', playToggle, false); | |
volumeBtn.addEventListener('click', volumeToggle, false); | |
repeatBtn.addEventListener('click', repeatToggle, false); | |
progressBar.closest('.progress-container').addEventListener('mousedown', handlerBar, false); | |
progressBar.closest('.progress-container').addEventListener('mousemove', seek, false); | |
document.documentElement.addEventListener('mouseup', seekingFalse, false); | |
volumeBar.closest('.volume').addEventListener('mousedown', handlerVol, false); | |
volumeBar.closest('.volume').addEventListener('mousemove', setVolume); | |
volumeBar.closest('.volume').addEventListener(wheel(), setVolume, false); | |
document.documentElement.addEventListener('mouseup', seekingFalse, false); | |
prevBtn.addEventListener('click', prev, false); | |
nextBtn.addEventListener('click', next, false); | |
apActive = true; | |
// Create playlist | |
renderPL(); | |
plBtn.addEventListener('click', plToggle, false); | |
// Create audio object | |
audio = new Audio(); | |
audio.volume = settings.volume; | |
audio.preload = 'auto'; | |
audio.addEventListener('error', errorHandler, false); | |
audio.addEventListener('timeupdate', timeUpdate, false); | |
audio.addEventListener('ended', doEnd, false); | |
volumeBar.style.height = audio.volume * 100 + '%'; | |
volumeLength = volumeBar.css('height'); | |
if(settings.confirmClose) { | |
window.addEventListener("beforeunload", beforeUnload, false); | |
} | |
if(isEmptyList()) { | |
return false; | |
} | |
audio.src = playList[index].file; | |
trackTitle.innerHTML = playList[index].title; | |
if(settings.autoPlay) { | |
audio.play(); | |
playBtn.classList.add('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-pause')); | |
plLi[index].classList.add('pl-list--current'); | |
notify(playList[index].title, { | |
icon: playList[index].icon, | |
body: 'Now playing' | |
}); | |
} | |
} | |
function changeDocumentTitle(title) { | |
if(settings.changeDocTitle) { | |
if(title) { | |
document.title = title; | |
} | |
else { | |
document.title = docTitle; | |
} | |
} | |
} | |
function beforeUnload(evt) { | |
if(!audio.paused) { | |
var message = 'Music still playing'; | |
evt.returnValue = message; | |
return message; | |
} | |
} | |
function errorHandler(evt) { | |
if(isEmptyList()) { | |
return; | |
} | |
var mediaError = { | |
'1': 'MEDIA_ERR_ABORTED', | |
'2': 'MEDIA_ERR_NETWORK', | |
'3': 'MEDIA_ERR_DECODE', | |
'4': 'MEDIA_ERR_SRC_NOT_SUPPORTED' | |
}; | |
audio.pause(); | |
curTime.innerHTML = '--'; | |
durTime.innerHTML = '--'; | |
progressBar.style.width = 0; | |
preloadBar.style.width = 0; | |
playBtn.classList.remove('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-play')); | |
plLi[index] && plLi[index].classList.remove('pl-list--current'); | |
changeDocumentTitle(); | |
throw new Error('Houston we have a problem: ' + mediaError[evt.target.error.code]); | |
} | |
/** | |
* UPDATE PL | |
*/ | |
function updatePL(addList) { | |
if(!apActive) { | |
return 'Player is not yet initialized'; | |
} | |
if(!Array.isArray(addList)) { | |
return; | |
} | |
if(addList.length === 0) { | |
return; | |
} | |
var count = playList.length; | |
var html = []; | |
playList.push.apply(playList, addList); | |
addList.forEach(function(item) { | |
html.push( | |
tplList.replace('{count}', count++).replace('{title}', item.title) | |
); | |
}); | |
// If exist empty message | |
if(plUl.querySelector('.pl-list--empty')) { | |
plUl.removeChild( pl.querySelector('.pl-list--empty') ); | |
audio.src = playList[index].file; | |
trackTitle.innerHTML = playList[index].title; | |
} | |
// Add song into playlist | |
plUl.insertAdjacentHTML('beforeEnd', html.join('')); | |
plLi = pl.querySelectorAll('li'); | |
} | |
/** | |
* PlayList methods | |
*/ | |
function renderPL() { | |
var html = []; | |
playList.forEach(function(item, i) { | |
html.push( | |
tplList.replace('{count}', i).replace('{title}', item.title) | |
); | |
}); | |
pl = create('div', { | |
'className': 'pl-container', | |
'id': 'pl', | |
'innerHTML': '<ul class="pl-ul">' + (!isEmptyList() ? html.join('') : '<li class="pl-list--empty">PlayList is empty</li>') + '</ul>' | |
}); | |
player.parentNode.insertBefore(pl, player.nextSibling); | |
plUl = pl.querySelector('.pl-ul'); | |
plLi = plUl.querySelectorAll('li'); | |
pl.addEventListener('click', listHandler, false); | |
} | |
function listHandler(evt) { | |
evt.preventDefault(); | |
if(evt.target.matches('.pl-list__title') || evt.target.matches('.pl-list__track') || evt.target.matches('.pl-list__icon') || evt.target.matches('.pl-list__eq') || evt.target.matches('.eq')) { | |
var current = parseInt(evt.target.closest('.pl-list').getAttribute('data-track'), 10); | |
if(index !== current) { | |
index = current; | |
play(current); | |
} | |
else { | |
playToggle(); | |
} | |
} else { | |
if(!!evt.target.closest('.pl-list__remove')) { | |
var parentEl = evt.target.closest('.pl-list'); | |
var isDel = parseInt(parentEl.getAttribute('data-track'), 10); | |
playList.splice(isDel, 1); | |
parentEl.closest('.pl-ul').removeChild(parentEl); | |
plLi = pl.querySelectorAll('li'); | |
[].forEach.call(plLi, function(el, i) { | |
el.setAttribute('data-track', i); | |
}); | |
if(!audio.paused) { | |
if(isDel === index) { | |
play(index); | |
} | |
} | |
else { | |
if(isEmptyList()) { | |
clearAll(); | |
} | |
else { | |
if(isDel === index) { | |
if(isDel > playList.length - 1) { | |
index -= 1; | |
} | |
audio.src = playList[index].file; | |
trackTitle.innerHTML = playList[index].title; | |
progressBar.style.width = 0; | |
} | |
} | |
} | |
if(isDel < index) { | |
index--; | |
} | |
} | |
} | |
} | |
function plActive() { | |
if(audio.paused) { | |
plLi[index].classList.remove('pl-list--current'); | |
return; | |
} | |
var current = index; | |
for(var i = 0, len = plLi.length; len > i; i++) { | |
plLi[i].classList.remove('pl-list--current'); | |
} | |
plLi[current].classList.add('pl-list--current'); | |
} | |
/** | |
* Player methods | |
*/ | |
function play(currentIndex) { | |
if(isEmptyList()) { | |
return clearAll(); | |
} | |
index = (currentIndex + playList.length) % playList.length; | |
audio.src = playList[index].file; | |
trackTitle.innerHTML = playList[index].title; | |
// Change document title | |
changeDocumentTitle(playList[index].title); | |
// Audio play | |
audio.play(); | |
// Show notification | |
notify(playList[index].title, { | |
icon: playList[index].icon, | |
body: 'Now playing', | |
tag: 'music-player' | |
}); | |
// Toggle play button | |
playBtn.classList.add('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-pause')); | |
// Set active song playlist | |
plActive(); | |
} | |
function prev() { | |
play(index - 1); | |
} | |
function next() { | |
play(index + 1); | |
} | |
function isEmptyList() { | |
return playList.length === 0; | |
} | |
function clearAll() { | |
audio.pause(); | |
audio.src = ''; | |
trackTitle.innerHTML = 'queue is empty'; | |
curTime.innerHTML = '--'; | |
durTime.innerHTML = '--'; | |
progressBar.style.width = 0; | |
preloadBar.style.width = 0; | |
playBtn.classList.remove('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-play')); | |
if(!plUl.querySelector('.pl-list--empty')) { | |
plUl.innerHTML = '<li class="pl-list--empty">PlayList is empty</li>'; | |
} | |
changeDocumentTitle(); | |
} | |
function playToggle() { | |
if(isEmptyList()) { | |
return; | |
} | |
if(audio.paused) { | |
if(audio.currentTime === 0) { | |
notify(playList[index].title, { | |
icon: playList[index].icon, | |
body: 'Now playing' | |
}); | |
} | |
changeDocumentTitle(playList[index].title); | |
audio.play(); | |
playBtn.classList.add('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-pause')); | |
} | |
else { | |
changeDocumentTitle(); | |
audio.pause(); | |
playBtn.classList.remove('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-play')); | |
} | |
plActive(); | |
} | |
function volumeToggle() { | |
if(audio.muted) { | |
if(parseInt(volumeLength, 10) === 0) { | |
volumeBar.style.height = settings.volume * 100 + '%'; | |
audio.volume = settings.volume; | |
} | |
else { | |
volumeBar.style.height = volumeLength; | |
} | |
audio.muted = false; | |
volumeBtn.classList.remove('has-muted'); | |
} | |
else { | |
audio.muted = true; | |
volumeBar.style.height = 0; | |
volumeBtn.classList.add('has-muted'); | |
} | |
} | |
function repeatToggle() { | |
if(repeatBtn.classList.contains('is-active')) { | |
repeating = false; | |
repeatBtn.classList.remove('is-active'); | |
} | |
else { | |
repeating = true; | |
repeatBtn.classList.add('is-active'); | |
} | |
} | |
function plToggle() { | |
plBtn.classList.toggle('is-active'); | |
pl.classList.toggle('h-show'); | |
} | |
function timeUpdate() { | |
if(audio.readyState === 0) return; | |
var barlength = Math.round(audio.currentTime * (100 / audio.duration)); | |
progressBar.style.width = barlength + '%'; | |
var | |
curMins = Math.floor(audio.currentTime / 60), | |
curSecs = Math.floor(audio.currentTime - curMins * 60), | |
mins = Math.floor(audio.duration / 60), | |
secs = Math.floor(audio.duration - mins * 60); | |
(curSecs < 10) && (curSecs = '0' + curSecs); | |
(secs < 10) && (secs = '0' + secs); | |
curTime.innerHTML = curMins + ':' + curSecs; | |
durTime.innerHTML = mins + ':' + secs; | |
if(settings.buffered) { | |
var buffered = audio.buffered; | |
if(buffered.length) { | |
var loaded = Math.round(100 * buffered.end(0) / audio.duration); | |
preloadBar.style.width = loaded + '%'; | |
} | |
} | |
} | |
/** | |
* TODO shuffle | |
*/ | |
function shuffle() { | |
if(shuffle) { | |
index = Math.round(Math.random() * playList.length); | |
} | |
} | |
function doEnd() { | |
if(index === playList.length - 1) { | |
if(!repeating) { | |
audio.pause(); | |
plActive(); | |
playBtn.classList.remove('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-play')); | |
return; | |
} | |
else { | |
play(0); | |
} | |
} | |
else { | |
play(index + 1); | |
} | |
} | |
function moveBar(evt, el, dir) { | |
var value; | |
if(dir === 'horizontal') { | |
value = Math.round( ((evt.clientX - el.offset().left) + window.pageXOffset) * 100 / el.parentNode.offsetWidth); | |
el.style.width = value + '%'; | |
return value; | |
} | |
else { | |
if(evt.type === wheel()) { | |
value = parseInt(volumeLength, 10); | |
var delta = evt.deltaY || evt.detail || -evt.wheelDelta; | |
value = (delta > 0) ? value - 10 : value + 10; | |
} | |
else { | |
var offset = (el.offset().top + el.offsetHeight) - window.pageYOffset; | |
value = Math.round((offset - evt.clientY)); | |
} | |
if(value > 100) value = wheelVolumeValue = 100; | |
if(value < 0) value = wheelVolumeValue = 0; | |
volumeBar.style.height = value + '%'; | |
return value; | |
} | |
} | |
function handlerBar(evt) { | |
rightClick = (evt.which === 3) ? true : false; | |
seeking = true; | |
seek(evt); | |
} | |
function handlerVol(evt) { | |
rightClick = (evt.which === 3) ? true : false; | |
seeking = true; | |
setVolume(evt); | |
} | |
function seek(evt) { | |
if(seeking && rightClick === false && audio.readyState !== 0) { | |
var value = moveBar(evt, progressBar, 'horizontal'); | |
audio.currentTime = audio.duration * (value / 100); | |
} | |
} | |
function seekingFalse() { | |
seeking = false; | |
} | |
function setVolume(evt) { | |
evt.preventDefault(); | |
volumeLength = volumeBar.css('height'); | |
if(seeking && rightClick === false || evt.type === wheel()) { | |
var value = moveBar(evt, volumeBar.parentNode, 'vertical') / 100; | |
if(value <= 0) { | |
audio.volume = 0; | |
audio.muted = true; | |
volumeBtn.classList.add('has-muted'); | |
} | |
else { | |
if(audio.muted) audio.muted = false; | |
audio.volume = value; | |
volumeBtn.classList.remove('has-muted'); | |
} | |
} | |
} | |
function notify(title, attr) { | |
if(!settings.notification) { | |
return; | |
} | |
if(window.Notification === undefined) { | |
return; | |
} | |
attr.tag = 'AP music player'; | |
window.Notification.requestPermission(function(access) { | |
if(access === 'granted') { | |
var notice = new Notification(title.substr(0, 110), attr); | |
setTimeout(notice.close.bind(notice), 5000); | |
} | |
}); | |
} | |
/* Destroy method. Clear All */ | |
function destroy() { | |
if(!apActive) return; | |
if(settings.confirmClose) { | |
window.removeEventListener('beforeunload', beforeUnload, false); | |
} | |
playBtn.removeEventListener('click', playToggle, false); | |
volumeBtn.removeEventListener('click', volumeToggle, false); | |
repeatBtn.removeEventListener('click', repeatToggle, false); | |
plBtn.removeEventListener('click', plToggle, false); | |
progressBar.closest('.progress-container').removeEventListener('mousedown', handlerBar, false); | |
progressBar.closest('.progress-container').removeEventListener('mousemove', seek, false); | |
document.documentElement.removeEventListener('mouseup', seekingFalse, false); | |
volumeBar.closest('.volume').removeEventListener('mousedown', handlerVol, false); | |
volumeBar.closest('.volume').removeEventListener('mousemove', setVolume); | |
volumeBar.closest('.volume').removeEventListener(wheel(), setVolume); | |
document.documentElement.removeEventListener('mouseup', seekingFalse, false); | |
prevBtn.removeEventListener('click', prev, false); | |
nextBtn.removeEventListener('click', next, false); | |
audio.removeEventListener('error', errorHandler, false); | |
audio.removeEventListener('timeupdate', timeUpdate, false); | |
audio.removeEventListener('ended', doEnd, false); | |
// Playlist | |
pl.removeEventListener('click', listHandler, false); | |
pl.parentNode.removeChild(pl); | |
audio.pause(); | |
apActive = false; | |
index = 0; | |
playBtn.classList.remove('is-playing'); | |
playSvgPath.setAttribute('d', playSvg.getAttribute('data-play')); | |
volumeBtn.classList.remove('has-muted'); | |
plBtn.classList.remove('is-active'); | |
repeatBtn.classList.remove('is-active'); | |
// Remove player from the DOM if necessary | |
// player.parentNode.removeChild(player); | |
} | |
/** | |
* Helpers | |
*/ | |
function wheel() { | |
var wheel; | |
if ('onwheel' in document) { | |
wheel = 'wheel'; | |
} else if ('onmousewheel' in document) { | |
wheel = 'mousewheel'; | |
} else { | |
wheel = 'MozMousePixelScroll'; | |
} | |
return wheel; | |
} | |
function extend(defaults, options) { | |
for(var name in options) { | |
if(defaults.hasOwnProperty(name)) { | |
defaults[name] = options[name]; | |
} | |
} | |
return defaults; | |
} | |
function create(el, attr) { | |
var element = document.createElement(el); | |
if(attr) { | |
for(var name in attr) { | |
if(element[name] !== undefined) { | |
element[name] = attr[name]; | |
} | |
} | |
} | |
return element; | |
} | |
function getTrack(index) { | |
return playList[index]; | |
} | |
Element.prototype.offset = function() { | |
var el = this.getBoundingClientRect(), | |
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft, | |
scrollTop = window.pageYOffset || document.documentElement.scrollTop; | |
return { | |
top: el.top + scrollTop, | |
left: el.left + scrollLeft | |
}; | |
}; | |
Element.prototype.css = function(attr) { | |
if(typeof attr === 'string') { | |
return getComputedStyle(this, '')[attr]; | |
} | |
else if(typeof attr === 'object') { | |
for(var name in attr) { | |
if(this.style[name] !== undefined) { | |
this.style[name] = attr[name]; | |
} | |
} | |
} | |
}; | |
// matches polyfill | |
window.Element && function(ElementPrototype) { | |
ElementPrototype.matches = ElementPrototype.matches || | |
ElementPrototype.matchesSelector || | |
ElementPrototype.webkitMatchesSelector || | |
ElementPrototype.msMatchesSelector || | |
function(selector) { | |
var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1; | |
while (nodes[++i] && nodes[i] != node); | |
return !!nodes[i]; | |
}; | |
}(Element.prototype); | |
// closest polyfill | |
window.Element && function(ElementPrototype) { | |
ElementPrototype.closest = ElementPrototype.closest || | |
function(selector) { | |
var el = this; | |
while (el.matches && !el.matches(selector)) el = el.parentNode; | |
return el.matches ? el : null; | |
}; | |
}(Element.prototype); | |
/** | |
* Public methods | |
*/ | |
return { | |
init: init, | |
update: updatePL, | |
destroy: destroy, | |
getTrack: getTrack | |
}; | |
})(); | |
window.AP = AudioPlayer; | |
})(window); | |
// TEST: image for web notifications | |
var iconImage = 'http://funkyimg.com/i/21pX5.png'; | |
AP.init({ | |
playList: [ | |
{'icon': iconImage, 'title': 'Hitman', 'file': 'http://incompetech.com/music/royalty-free/mp3-royaltyfree/Hitman.mp3'}, | |
{'icon': iconImage, 'title': 'Forever Believe', 'file': 'https://a.clyp.it/zbh0qeyo.mp3'}, | |
{'icon': iconImage, 'title': 'Drifting', 'file': 'https://a.clyp.it/bthbgqcs.mp3'}, | |
{'icon': iconImage, 'title': 'Clap Along (Lorem ipsum dolor sit amet, consectetur adipisicing.)', 'file': 'https://a.clyp.it/lygki3hx.mp3'}, | |
{'icon': iconImage, 'title': 'Pop Tune', 'file': 'https://a.clyp.it/enddsv44.mp3'} | |
] | |
}); | |
// TEST: update playlist | |
// document.getElementById('addSongs').addEventListener('click', function(e) { | |
// e.preventDefault(); | |
// AP.update([ | |
// {'icon': iconImage, 'title': 'Drifting', 'file': 'https://a.clyp.it/bthbgqcs.mp3'}, | |
// {'icon': iconImage, 'title': 'Clap Along (Lorem ipsum dolor sit amet, consectetur adipisicing.)', 'file': 'https://a.clyp.it/lygki3hx.mp3'}, | |
// {'icon': iconImage, 'title': 'Pop Tune', 'file': 'https://a.clyp.it/enddsv44.mp3'} | |
// ]); | |
// }) | |
$(document).ready(function(){ | |
$(".pl-list__download").on("click", function(){ | |
var trackPlaying = $(this).closest(".pl-list"); | |
console.log(AP.getTrack(trackPlaying.attr("data-track"))); | |
}); | |
}); |
This file contains hidden or 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
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> |
This file contains hidden or 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
// $primaryColor: steelblue; | |
$primaryColor: #1ec279; | |
$apBG: #f2f2f2; | |
$plBG: #fff; | |
$plCurrentBG: $primaryColor; | |
$plColorIcon: $primaryColor; | |
$plCurrentColor: #fff; | |
$plHoverColor: #f6f6f6; | |
$iconColor: #777; | |
$iconColorActive: $primaryColor; | |
$iconBGActive: $primaryColor; | |
$barBG: #ddd; | |
$progressBG: $primaryColor; | |
$font-family: inherit; | |
$font-size: 16px; | |
$apHeight: 50px; | |
$apMaxWidth: 1440px; | |
$barSize: 3px; | |
$apZ: 99999; | |
$volZ: 88888; | |
$plZ: 77777; | |
*, | |
*::before, | |
*::after { | |
box-sizing: border-box | |
} | |
.audio-player { | |
display: flex; | |
flex-direction: column; | |
width: 95%; | |
max-width: 450px; | |
max-height: 95vh; | |
margin: 10px auto 0 auto; | |
overflow: hidden; | |
border-radius: 5px; | |
box-shadow: 0 0 40px rgba(0,0,0,0.2); | |
.player-head { | |
position: relative; | |
max-width: 100%; | |
max-height: 12rem; | |
min-height: 12rem; | |
overflow: hidden; | |
.player-image { | |
max-width: 100%; | |
z-index: 10; | |
img { | |
position: relative; | |
width: 110%; | |
left: -5%; | |
margin-top: -10%; | |
display: block; | |
filter: blur(15px); | |
filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='15'); | |
} | |
} | |
.player-close-icon { | |
position: absolute; | |
top: 0.4rem; | |
right: 0.6rem; | |
transition: all 0.8s ease; | |
z-index: 25; | |
i { | |
color: white; | |
font-size: 2.3rem; | |
cursor: pointer; | |
} | |
.ion-ios-play { | |
margin-left: 0.2rem; | |
} | |
} | |
.player-description { | |
position: absolute; | |
top: 0; | |
left: 0; | |
padding: 1rem; | |
width: 100%; | |
z-index: 20; | |
h2 { | |
color: white; | |
padding: 0; | |
margin: 0; | |
font-weight: bold; | |
} | |
p { | |
color: white; | |
padding: 0; | |
margin: 0.5rem 0; | |
font-weight: 100; | |
} | |
} | |
} | |
.player-controls { | |
display: flex; | |
flex-direction: column; | |
.ap { | |
position: relative; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
width: 100%; | |
min-width: 250px; | |
font-family: $font-family; | |
font-size: $font-size; | |
user-select: none; | |
border-top: 1px solid #ccc; | |
background: $apBG; | |
box-shadow: 0 -1px 10px rgba(0,0,0,.1); | |
z-index: $apZ; | |
} | |
.ap__inner { | |
display: flex; | |
max-width: $apMaxWidth; | |
margin: auto; | |
height: auto; | |
flex-wrap: wrap; | |
} | |
.ap__item { | |
display: flex; | |
flex: 1; | |
justify-content: center; | |
align-items: center; | |
} | |
.ap__item--playback, .ap__item--settings { | |
flex: 1 1 50%; | |
order: 1; | |
} | |
.ap__item--playback > .ap__controls, | |
.ap__item--settings > .ap__controls { | |
flex: 1; | |
} | |
.ap__item--settings { | |
display: none; | |
} | |
@keyframes fs { | |
0% { | |
opacity: 0; | |
transform: scale(.5); | |
} | |
100% { | |
opacity: 1; | |
transform: scale(1); | |
} | |
} | |
// track bar | |
.ap__item--track { | |
flex: 1 1 100%; | |
margin-bottom: 10px; | |
padding: 0 20px; | |
order: 1; | |
} | |
.track { | |
position: relative; | |
width: 100%; | |
align-self: flex-start; | |
padding: 5px 0 0; | |
} | |
.track__title { | |
position: absolute; | |
width: 100%; | |
overflow: hidden; | |
padding-right: 80px; | |
text-align: left; | |
white-space: nowrap; | |
text-overflow: ellipsis; | |
} | |
.track__time { | |
position: absolute; | |
top: 5px; | |
right: 0; | |
} | |
.progress-container { | |
position: relative; | |
padding: 7px 0; | |
margin-top: 15px; | |
overflow: hidden; | |
cursor: pointer; | |
&:hover .progress__bar:after { | |
opacity: 1; | |
} | |
} | |
.progress { | |
height: $barSize; | |
border-radius: $barSize; | |
background: $barBG; | |
} | |
.progress__bar, | |
.progress__preload { | |
position: absolute; | |
width: 0; | |
height: $barSize; | |
border-radius: $barSize 0 0 $barSize; | |
} | |
.progress__bar { | |
background: $progressBG; | |
z-index: 1; | |
&:after { | |
position: absolute; | |
top: 0; | |
right: -10px; | |
width: 10px; | |
height: 10px; | |
margin-top: -3px; | |
content: ''; | |
border-radius: 6px; | |
background: $primaryColor; | |
opacity: 0; | |
transition: opacity .3s ease; | |
} | |
} | |
.progress__preload { | |
background: darken($barBG, 10%); | |
z-index: 0; | |
} | |
// Controls | |
.ap__controls, | |
.ap button { | |
margin: 0; | |
padding: 0; | |
border: 0; | |
outline: 0; | |
background: transparent; | |
position: relative; | |
display: block; | |
height: $apHeight; | |
text-align: center; | |
cursor: pointer; | |
transition: background .3s ease; | |
&:active { | |
background: rgba(0,0,0,.1); | |
} | |
&:hover { | |
opacity: 1; | |
} | |
} | |
.icon-play > path { | |
transition: all .3s ease; | |
} | |
.is-playing { | |
.icon-play { | |
fill: $iconColorActive; | |
} | |
} | |
// Volume | |
.volume-btn { | |
display: block; | |
text-align: center; | |
width: 100%; | |
} | |
.volume { | |
position: absolute; | |
left: 50%; | |
bottom: (#{$apHeight - 5px}); | |
width: 40px; | |
margin-left: -20px; | |
height: 120px; | |
opacity: 0; | |
visibility: hidden; | |
transform: translateY(10px); | |
transition: all .3s cubic-bezier(0.17, 0.72, 0.26, 1.23); | |
background: $apBG; | |
border: 1px solid #ccc; | |
border-radius: 1px; | |
z-index: $volZ; | |
&::before, | |
&::after { | |
content: ''; | |
position: absolute; | |
bottom: -12px; | |
border: 7px solid transparent; | |
border-top: 7px solid $apBG; | |
left: 50%; | |
margin-left: -7px; | |
} | |
&::after { | |
bottom: -14px; | |
z-index: -1; | |
border-top: 7px solid #ccc; | |
} | |
} | |
.volume-container:hover .volume { | |
opacity: 1; | |
transform: translateY(0); | |
visibility: visible; | |
} | |
.volume__track { | |
position: relative; | |
display: block; | |
width: $barSize; | |
height: 100px; | |
margin: 10px auto; | |
background: $barBG; | |
border-radius: $barSize; | |
overflow: hidden; | |
} | |
.volume__bar { | |
position: absolute; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: $progressBG; | |
height: 50%; | |
} | |
.icon-volume-off { | |
display: none; | |
} | |
.has-muted { | |
.icon-volume-on { | |
display: none; | |
} | |
.icon-volume-off { | |
display: inline; | |
opacity: .7; | |
} | |
} | |
.ap__controls.is-active { | |
> svg { | |
fill: $iconColorActive; | |
filter: drop-shadow(0 0 3px rgba($iconColorActive, .4)); | |
} | |
} | |
/*----------------------- | |
Playlist Player - PL | |
------------------------*/ | |
.pl-container { | |
display: block; | |
position: relative; | |
top: 0; | |
right: 0; | |
bottom: $apHeight; | |
left: 0; | |
overflow: auto; | |
border-radius: 0px 0px 5px 5px; | |
font-family: $font-family; | |
font-size: $font-size; | |
background: $plBG; | |
z-index: $plZ; | |
} | |
.pl-ul { | |
width: 100%; | |
margin: 0 auto; | |
padding: 0 0 0 0; | |
} | |
.pl-list { | |
display: flex; | |
align-items: center; | |
// transition: background .2s ease; | |
height: 60px; | |
line-height: 60px; | |
overflow: hidden; | |
svg { | |
fill: #555; | |
} | |
} | |
.pl-list + .pl-list { | |
border-top: 1px solid #eee; | |
} | |
.pl-list:not(.pl-list--current):hover { | |
background: $plHoverColor; | |
} | |
.pl-list__track, | |
.pl-list__download { | |
flex: 0 50px; | |
text-align: center; | |
} | |
.pl-list__track { | |
cursor: pointer; | |
display: flex; | |
align-content: center; | |
justify-content: center; | |
} | |
.pl-list__icon { | |
display: inline-block; | |
align-self: center center; | |
width: 0; | |
height: 0; | |
border-top: 7px solid transparent; | |
border-bottom: 7px solid transparent; | |
border-left: 11px solid #555; | |
} | |
.pl-list__title { | |
overflow: hidden; | |
padding-right: 10px; | |
cursor: pointer; | |
text-align: left; | |
white-space: nowrap; | |
text-overflow: ellipsis; | |
flex: 1; | |
} | |
.pl-list__download { | |
height: 100%; | |
background: transparent; | |
border: 0; | |
outline: 0; | |
cursor: pointer; | |
transition: opacity .2s ease; | |
display: flex; | |
justify-content: center; | |
} | |
.pl-list__download > svg { | |
width: 20px; | |
height: 20px; | |
align-self: center; | |
} | |
.pl-list__eq { | |
display: none; | |
} | |
.pl-list--current { | |
background: $plCurrentBG; | |
color: $plCurrentColor; | |
} | |
.pl-list--current { | |
svg { | |
fill: $plCurrentColor; | |
} | |
.pl-list__eq { | |
display: block; | |
} | |
.pl-list__icon { | |
display: none; | |
} | |
} | |
.pl-list:hover .pl-list__download, | |
.pl-list--current .pl-list__download { | |
opacity: 1; | |
} | |
.pl-list--current .pl-list__download:hover { | |
background: darken($plCurrentBG, 5%); | |
} | |
.pl-list--empty { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
font-size: 2rem; | |
transform: translate(-50%, -50%); | |
letter-spacing: 2px; | |
color: #ccc; | |
} | |
@keyframes eq { | |
0% { height: 3px; } | |
50% { height: 15px; } | |
100% { height: 3px; } | |
} | |
.eq { | |
display: flex; | |
width: 20px; | |
height: 15px; | |
margin: 0 auto; | |
justify-content: space-between; | |
align-items: flex-end; | |
} | |
.eq__bar { | |
width: 4px; | |
background: $plCurrentColor; | |
filter: drop-shadow(0 0 5px $plCurrentColor); | |
} | |
.eq__bar:nth-child(2) { | |
animation: eq 1.2s ease-in-out infinite 0s; | |
} | |
.eq__bar:nth-child(4) { | |
animation: eq 1.2s ease-in-out infinite .2s; | |
} | |
.eq__bar:nth-child(1) { | |
animation: eq 1.2s ease-in-out infinite .4s; | |
} | |
.eq__bar:nth-child(3) { | |
animation: eq 1.2s ease-in-out infinite .6s; | |
} | |
.h-hide { | |
display: none; | |
} | |
.h-show { | |
display: block; | |
} | |
} | |
} |
This file contains hidden or 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
<link href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment