Custom Audio Player
@ -34,8 +34,7 @@ function filewalker (dir, done) {
|
||||
|
||||
// If directory, execute a recursive call
|
||||
if (stat && stat.isDirectory()) {
|
||||
// Add directory to array [comment if you need to remove the directories from the array]
|
||||
results.push(file)
|
||||
//results.push(file)
|
||||
|
||||
filewalker(file, function (err, res) {
|
||||
if (err) return done(err)
|
||||
|
10
public/icon/pause.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 438.536 438.536" style="enable-background:new 0 0 438.536 438.536;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M164.453,0H18.276C13.324,0,9.041,1.807,5.425,5.424C1.808,9.04,0.001,13.322,0.001,18.271v401.991 c0,4.948,1.807,9.233,5.424,12.847c3.619,3.617,7.902,5.428,12.851,5.428h146.181c4.949,0,9.231-1.811,12.847-5.428 c3.617-3.613,5.424-7.898,5.424-12.847V18.271c0-4.952-1.807-9.231-5.428-12.847C173.685,1.807,169.402,0,164.453,0z" fill="#FFFFFF"/>
|
||||
<path d="M433.113,5.424C429.496,1.807,425.215,0,420.267,0H274.086c-4.949,0-9.237,1.807-12.847,5.424 c-3.621,3.615-5.432,7.898-5.432,12.847v401.991c0,4.948,1.811,9.233,5.432,12.847c3.609,3.617,7.897,5.428,12.847,5.428h146.181 c4.948,0,9.229-1.811,12.847-5.428c3.614-3.613,5.421-7.898,5.421-12.847V18.271C438.534,13.319,436.73,9.04,433.113,5.424z" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
9
public/icon/play-next.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 306 306" style="enable-background:new 0 0 306 306;" xml:space="preserve">
|
||||
<g>
|
||||
<g id="skip-next">
|
||||
<path d="M0,306l216.75-153L0,0V306z M255,0v306h51V0H255z" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 503 B |
10
public/icon/play-prev.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 306 306" style="enable-background:new 0 0 306 306;" xml:space="preserve">
|
||||
<g>
|
||||
<g id="skip-previous">
|
||||
<rect width="51" height="306" fill="#FFFFFF"/>
|
||||
<polygon points="89.25,153 306,306 306,0 " fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 543 B |
3
public/icon/play.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 41.999 41.999" style="enable-background:new 0 0 41.999 41.999;" xml:space="preserve" width="512px" height="512px">
|
||||
<path d="M36.068,20.176l-29-20C6.761-0.035,6.363-0.057,6.035,0.114C5.706,0.287,5.5,0.627,5.5,0.999v40 c0,0.372,0.206,0.713,0.535,0.886c0.146,0.076,0.306,0.114,0.465,0.114c0.199,0,0.397-0.06,0.568-0.177l29-20 c0.271-0.187,0.432-0.494,0.432-0.823S36.338,20.363,36.068,20.176z" fill="#FFFFFF"/></svg>
|
After Width: | Height: | Size: 597 B |
7
public/icon/volume-low.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 347.182 347.182" style="enable-background:new 0 0 347.182 347.182;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M277.808,5.426C274.187,1.809,269.902,0,264.954,0c-4.945,0-9.233,1.809-12.847,5.426L157.034,100.5H82.233 c-4.952,0-9.235,1.809-12.851,5.424c-3.617,3.621-5.426,7.901-5.426,12.85v109.634c0,4.948,1.809,9.232,5.426,12.847 c3.619,3.617,7.902,5.428,12.851,5.428h74.798l95.07,95.077c3.62,3.61,7.904,5.421,12.847,5.421c4.955,0,9.236-1.811,12.854-5.421 c3.613-3.617,5.424-7.901,5.424-12.847V18.276C283.225,13.328,281.421,9.042,277.808,5.426z" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 882 B |
40
public/icon/volume-off.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 461.55 461.55" style="enable-background:new 0 0 461.55 461.55;" xml:space="preserve">
|
||||
<g>
|
||||
<g id="volume-off">
|
||||
<path d="M345.525,229.5c0-45.9-25.5-84.15-63.75-102v56.1l63.75,63.75C345.525,239.7,345.525,234.6,345.525,229.5z M409.275,229.5 c0,22.95-5.1,45.9-12.75,66.3l38.25,38.25c17.85-30.6,25.5-68.85,25.5-107.1c0-109.65-76.5-201.45-178.5-224.4V56.1 C355.725,81.6,409.275,147.9,409.275,229.5z M34.425,0L1.275,33.15L121.125,153H1.275v153h102l127.5,127.5V262.65L340.425,372.3 c-17.851,12.75-35.7,22.95-58.65,30.601v53.55c35.7-7.65,66.3-22.95,94.35-45.9l51,51l33.15-33.149l-229.5-229.5L34.425,0z M230.775,25.5l-53.55,53.55l53.55,53.55V25.5z" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
9
public/icon/volume.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 459 459" style="enable-background:new 0 0 459 459;" xml:space="preserve">
|
||||
<g>
|
||||
<g id="volume-up">
|
||||
<path d="M0,153v153h102l127.5,127.5v-408L102,153H0z M344.25,229.5c0-45.9-25.5-84.15-63.75-102v204 C318.75,313.65,344.25,275.4,344.25,229.5z M280.5,5.1v53.55C354.45,81.6,408,147.899,408,229.5S354.45,377.4,280.5,400.35V453.9 C382.5,430.949,459,339.15,459,229.5C459,119.85,382.5,28.049,280.5,5.1z" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 746 B |
@ -73,9 +73,84 @@ tr:nth-child(even) {
|
||||
background-color: #112635;
|
||||
cursor: pointer;
|
||||
}
|
||||
.paging.btn.inner {
|
||||
background-color: #102331;
|
||||
}
|
||||
.paging.bg {
|
||||
background-color: #0e202c;
|
||||
}
|
||||
.player-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 40px;
|
||||
background-color: #0c2233;
|
||||
}
|
||||
.player-controls .grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.player-controls span {
|
||||
display: block;
|
||||
}
|
||||
.player-controls .icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
background-size: 60%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin: 5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
.player-controls .timestamp {
|
||||
margin-top: 0.9em;
|
||||
font-size: 0.8em;
|
||||
padding: 0 5px;
|
||||
min-width: 8em;
|
||||
text-align: center;
|
||||
}
|
||||
.play-btn {
|
||||
background-image: url('icon/play.svg');
|
||||
}
|
||||
.pause-btn {
|
||||
background-image: url('icon/pause.svg');
|
||||
}
|
||||
.mute-btn {
|
||||
background-image: url('icon/volume.svg');
|
||||
}
|
||||
.mute1-btn {
|
||||
background-image: url('icon/volume-low.svg');
|
||||
}
|
||||
.muted-btn {
|
||||
background-image: url('icon/volume-off.svg');
|
||||
}
|
||||
.next-btn {
|
||||
background-image: url('icon/play-next.svg');
|
||||
}
|
||||
.prev-btn {
|
||||
background-image: url('icon/play-prev.svg');
|
||||
}
|
||||
.volume {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.volume .volume-bar {
|
||||
width: 5em;
|
||||
}
|
||||
.volume .volume-bar .seek-inner {
|
||||
width: 100%;
|
||||
}
|
||||
.seek-container {
|
||||
height: 8px;
|
||||
width: 100%;
|
||||
margin: 16px 5px 0 5px;
|
||||
background-color: #00131b;
|
||||
cursor: pointer;
|
||||
}
|
||||
.seek-inner {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background-color: #00b7ff;
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
tr td:nth-child(1), th:nth-child(1) {
|
||||
display: none;
|
||||
@ -100,4 +175,7 @@ tr:nth-child(even) {
|
||||
.allowance {
|
||||
margin-bottom: 100px !important;
|
||||
}
|
||||
.volume .volume-bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,32 @@
|
||||
</div>
|
||||
<div class="pages flex-row">
|
||||
<div class="paging btn" id="jump-first" onclick="showTracks(1)"><<</div>
|
||||
<div class="paging btn" id="jump-prev" onclick="showTracks(pageNum - 1)"><</div>
|
||||
<div class="paging bg" id="pagenum">0 / 0</div>
|
||||
<div class="paging btn" id="jump-next" onclick="showTracks(pageNum + 1)">></div>
|
||||
<div class="paging btn inner" id="jump-prev" onclick="showTracks(pageNum - 1)"><</div>
|
||||
<div class="paging bg" id="pagenum">0 / 0</div>
|
||||
<div class="paging btn inner" id="jump-next" onclick="showTracks(pageNum + 1)">></div>
|
||||
<div class="paging btn" id="jump-last" onclick="showTracks(pages)">>></div>
|
||||
</div>
|
||||
<div class="player flex-row">
|
||||
<div id="playing">Nothing playing</div>
|
||||
<audio id="player"></audio>
|
||||
<div class="player-controls">
|
||||
<span class="play-btn ">
|
||||
<span class="prev-btn flex-col icon" id="player-prev"></span>
|
||||
<span class="play-btn flex-col icon" id="player-play"></span>
|
||||
<span class="next-btn flex-col icon" id="player-next"></span>
|
||||
<span class="timestamp flex-col" id="player-ts">0:00 / 0:00</span>
|
||||
<div class="seek-bar seek-container flex-col grow" id="player-seekbar">
|
||||
<div class="seek-inner"></div>
|
||||
</div>
|
||||
<div class="volume flex-col">
|
||||
<span class="mute-btn flex-col icon" id="player-mute"></span>
|
||||
<div class="volume-bar seek-container flex-col" id="player-volbar">
|
||||
<div class="seek-inner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
<script type="text/javascript" src="player.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,6 +3,9 @@ var input = document.querySelector('#search')
|
||||
var audio = document.querySelector('#player')
|
||||
var playing = document.querySelector('#playing')
|
||||
|
||||
var next = document.querySelector('#player-next')
|
||||
var prev = document.querySelector('#player-prev')
|
||||
|
||||
var tableHead = '<tr> \
|
||||
<th class="small">#</th> \
|
||||
<th>Track</th> \
|
||||
@ -60,7 +63,6 @@ function httpGet (url, callback) {
|
||||
}
|
||||
|
||||
function shortTitle (title, artist) {
|
||||
title = title.replace(/&/g, '&')
|
||||
if (!artist) return title
|
||||
return artist + ' - ' + title
|
||||
}
|
||||
@ -118,6 +120,7 @@ function search (query) {
|
||||
}
|
||||
|
||||
function play (id) {
|
||||
if (id < 1) return
|
||||
httpGet('/api/track/' + id).then(function (data) {
|
||||
let title = shortTitle(data.title, data.artist)
|
||||
playing.innerHTML = title
|
||||
@ -140,6 +143,14 @@ audio.addEventListener('ended', function (e) {
|
||||
play(nowPlaying + 1)
|
||||
})
|
||||
|
||||
next.addEventListener('click', function (e) {
|
||||
play(nowPlaying + 1)
|
||||
})
|
||||
|
||||
prev.addEventListener('click', function (e) {
|
||||
play(nowPlaying - 1)
|
||||
})
|
||||
|
||||
showTracks(1)
|
||||
|
||||
window.play = play
|
||||
|
183
public/player.js
Normal file
@ -0,0 +1,183 @@
|
||||
(function () {
|
||||
var audio = document.querySelector('#player')
|
||||
|
||||
var play = document.querySelector('#player-play')
|
||||
|
||||
var ts = document.querySelector('#player-ts')
|
||||
var seek = document.querySelector('#player-seekbar')
|
||||
var seekBar = document.querySelector('#player-seekbar .seek-inner')
|
||||
|
||||
var mute = document.querySelector('#player-mute')
|
||||
var vol = document.querySelector('#player-volbar')
|
||||
var volBar = document.querySelector('#player-volbar .seek-inner')
|
||||
|
||||
// Seconds into HH:MM:SS
|
||||
function toHHMMSS (numbr) {
|
||||
let secNum = parseInt(numbr, 10) // don't forget the second param
|
||||
let hours = Math.floor(secNum / 3600)
|
||||
let minutes = Math.floor((secNum - (hours * 3600)) / 60)
|
||||
let seconds = secNum - (hours * 3600) - (minutes * 60)
|
||||
|
||||
if (hours < 10) hours = '0' + hours
|
||||
if (minutes < 10) minutes = '0' + minutes
|
||||
if (seconds < 10) seconds = '0' + seconds
|
||||
|
||||
let time = ''
|
||||
if (parseInt(hours) > 0) {
|
||||
time = hours + ':' + minutes + ':' + seconds
|
||||
} else {
|
||||
time = minutes + ':' + seconds
|
||||
}
|
||||
return time
|
||||
}
|
||||
|
||||
function relMouseCoords (e) {
|
||||
let x
|
||||
let y
|
||||
|
||||
if (e.changedTouches) {
|
||||
let touch = e.changedTouches[0]
|
||||
if (touch) {
|
||||
e.pageX = touch.pageX
|
||||
e.pageY = touch.pageY
|
||||
}
|
||||
}
|
||||
|
||||
if (e.pageX || e.pageY) {
|
||||
x = e.pageX
|
||||
y = e.pageY
|
||||
} else {
|
||||
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
|
||||
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||
}
|
||||
|
||||
x -= this.offsetLeft
|
||||
y -= this.offsetTop
|
||||
|
||||
return {x: x, y: y}
|
||||
}
|
||||
|
||||
function updateSeeker() {
|
||||
if (isNaN(audio.duration) || audio.duration === 0) return
|
||||
|
||||
ts.innerHTML = toHHMMSS(audio.currentTime) + ' / ' + toHHMMSS(audio.duration)
|
||||
|
||||
let progress = 100 * audio.currentTime / audio.duration
|
||||
|
||||
seekBar.style.width = progress + '%'
|
||||
}
|
||||
|
||||
function updateVolume() {
|
||||
volBar.style.width = (100 * audio.volume) + '%'
|
||||
|
||||
if (audio.muted) {
|
||||
mute.className = 'muted-btn flex-col icon'
|
||||
} else if (audio.volume > 0.5) {
|
||||
mute.className = 'mute-btn flex-col icon'
|
||||
} else {
|
||||
mute.className = 'mute1-btn flex-col icon'
|
||||
}
|
||||
}
|
||||
|
||||
function seekProperty (prop, max, e) {
|
||||
if (isNaN(max) || max === 0) return
|
||||
|
||||
let pos = relMouseCoords.call(this, e)
|
||||
let clickPercent = pos.x / this.offsetWidth
|
||||
|
||||
if (max == 1) {
|
||||
audio[prop] = clickPercent
|
||||
return
|
||||
}
|
||||
|
||||
audio[prop] = Math.floor(max * clickPercent)
|
||||
}
|
||||
|
||||
function setClass (elem, int, rep) {
|
||||
let classString = elem.className
|
||||
|
||||
if (classString.indexOf(int) !== -1) {
|
||||
classString = classString.replace(int, rep)
|
||||
} else if (classString.indexOf(rep) !== -1) {
|
||||
return
|
||||
}
|
||||
|
||||
elem.className = classString
|
||||
}
|
||||
|
||||
function togglePlayButton () {
|
||||
if (audio.paused) {
|
||||
setClass(play, 'pause-btn', 'play-btn')
|
||||
} else {
|
||||
setClass(play, 'play-btn', 'pause-btn')
|
||||
}
|
||||
}
|
||||
|
||||
function registerSeekBar (element, v, mv) {
|
||||
let seeking = false
|
||||
let moved = false
|
||||
|
||||
element.addEventListener('mousedown', function (e) {
|
||||
seeking = true
|
||||
moved = false
|
||||
}, false)
|
||||
|
||||
element.addEventListener('mousemove', function (e) {
|
||||
if (!seeking) return
|
||||
moved = true
|
||||
|
||||
if (v === 'currentTime') {
|
||||
audio.seeking = true
|
||||
}
|
||||
|
||||
seekProperty.call(element, v, (mv ? audio[mv] : 1), e)
|
||||
})
|
||||
|
||||
element.addEventListener('mouseup', function (e) {
|
||||
if (!seeking) return
|
||||
seeking = false
|
||||
|
||||
if (moved) {
|
||||
if (v === 'currentTime') {
|
||||
audio.seeking = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
seekProperty.call(element, v, (mv ? audio[mv] : 1), e)
|
||||
})
|
||||
|
||||
element.addEventListener('mouseleave', function (e) {
|
||||
if (!seeking) return
|
||||
seeking = false
|
||||
|
||||
updateSeeker()
|
||||
})
|
||||
}
|
||||
|
||||
audio.addEventListener('durationchange', updateSeeker, false)
|
||||
audio.addEventListener('volumechange', updateVolume, false)
|
||||
audio.addEventListener('timeupdate', updateSeeker, false)
|
||||
|
||||
audio.addEventListener('play', togglePlayButton, false)
|
||||
audio.addEventListener('pause', togglePlayButton, false)
|
||||
audio.addEventListener('stop', togglePlayButton, false)
|
||||
|
||||
play.addEventListener('click', function (e) {
|
||||
if (audio.paused) {
|
||||
audio.play()
|
||||
} else {
|
||||
audio.pause()
|
||||
}
|
||||
}, false)
|
||||
|
||||
mute.addEventListener('click', function (e) {
|
||||
audio.muted = !audio.muted
|
||||
updateVolume()
|
||||
}, false)
|
||||
|
||||
registerSeekBar(seek, 'currentTime', 'duration')
|
||||
registerSeekBar(vol, 'volume')
|
||||
|
||||
updateVolume()
|
||||
})()
|