add looping options

adds individul track loop count (indicated by a badge on the play button) and user loop toggle button (between none, track, and playlist)
This commit is contained in:
torcado194 2022-09-18 15:14:44 -04:00
parent 05c8b548ce
commit 2b055c1806
3 changed files with 166 additions and 35 deletions

View file

@ -8,6 +8,9 @@
<link rel="stylesheet" href="src/reset.css"> <link rel="stylesheet" href="src/reset.css">
<link rel="stylesheet" href="src/common.css"> <link rel="stylesheet" href="src/common.css">
<link rel="stylesheet" href="src/fonts.css"> <link rel="stylesheet" href="src/fonts.css">
<!-- <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@600&text=0123456789&display=swap" rel="stylesheet"> -->
</head> </head>
<style> <style>
:root { :root {
@ -24,6 +27,14 @@
--font: sans-serif; --font: sans-serif;
} }
@font-face {
font-family: 'Rubik';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('src/fonts/rubikNumbers-edit.woff2') format('woff2');
}
@keyframes pulse { @keyframes pulse {
0% { 0% {
opacity: 0.5; opacity: 0.5;
@ -306,6 +317,24 @@
font-size: 40px; font-size: 40px;
animation: pulse 1.0s infinite; animation: pulse 1.0s infinite;
} }
#bigButton.looped:before {
content: attr(loopcount);
display: block;
width: 20px;
height: 20px;
position: absolute;
top: -6px;
right: -6px;
color: var(--secondaryColor);
background-color: var(--primaryColor);
border: 2px solid var(--backgroundColor);
border-radius: 20px;
font-family: 'Rubik';
font-weight: 600;
font-size: 14px;
text-align: center;
letter-spacing: -1px;
}
#controls { #controls {
display: flex; display: flex;
@ -400,7 +429,7 @@
margin: auto -2px; margin: auto -2px;
} }
#controls #nextTrack, #controls #prevTrack { #controls #nextTrack, #controls #prevTrack, #controls #loopSwitch {
border: none; border: none;
margin-right: 20px; margin-right: 20px;
flex: 0 0; flex: 0 0;
@ -410,6 +439,9 @@
align-self: start; align-self: start;
cursor: pointer; cursor: pointer;
} }
#controls #loopSwitch {
opacity: 0.5;
}
#controls .spacer { #controls .spacer {
flex: 1 1; flex: 1 1;
@ -590,6 +622,28 @@
font-size: 20px; font-size: 20px;
} }
.track.looped button:before {
content: attr(loopcount);
display: block;
width: 15px;
height: 15px;
position: absolute;
top: -6px;
right: -6px;
color: var(--secondaryColor);
background-color: var(--primaryColor);
border: 2px solid var(--backgroundColor);
border-radius: 20px;
font-family: 'Rubik';
font-weight: 600;
font-size: 10px;
text-align: center;
letter-spacing: -1px;
}
.track.active.looped button:before {
border-color: var(--secondaryColor);;
}
.track .title { .track .title {
word-break: break-word; word-break: break-word;
} }
@ -714,6 +768,7 @@
</button> </button>
<button id="prevTrack" class="icon-previous"></button> <button id="prevTrack" class="icon-previous"></button>
<button id="nextTrack" class="icon-next"></button> <button id="nextTrack" class="icon-next"></button>
<button id="loopSwitch" class="icon-loop" title="loop off"></button>
<div class="spacer"></div> <div class="spacer"></div>
<div id="volumeContainer"> <div id="volumeContainer">
<div id="volumeTrackContainer"> <div id="volumeTrackContainer">
@ -746,6 +801,7 @@
let scrubberEl = document.getElementById('scrubber'); let scrubberEl = document.getElementById('scrubber');
let nextTrackEl = document.getElementById('nextTrack'); let nextTrackEl = document.getElementById('nextTrack');
let prevTrackEl = document.getElementById('prevTrack'); let prevTrackEl = document.getElementById('prevTrack');
let loopSwitchEl = document.getElementById('loopSwitch');
let titleEl = document.getElementById('title'); let titleEl = document.getElementById('title');
let titleContainerEl = document.getElementById('titleContainer'); let titleContainerEl = document.getElementById('titleContainer');
@ -772,6 +828,7 @@
let loadedFirst = false; let loadedFirst = false;
let autoPlay = false; let autoPlay = false;
let loopMode = "none";
let config = {}; let config = {};
@ -948,6 +1005,7 @@
</div>`); </div>`);
let trackEl = tracksEl.lastChild; let trackEl = tracksEl.lastChild;
entry.trackEl = trackEl; entry.trackEl = trackEl;
let buttonEl = trackEl.querySelector('button');
if(entry.feature){ if(entry.feature){
featureIndex = i; featureIndex = i;
} }
@ -973,8 +1031,14 @@
if(!entry.previewFade && entry.previewFade !== false){ if(!entry.previewFade && entry.previewFade !== false){
entry.previewFade = true; //default true entry.previewFade = true; //default true
} }
if(typeof entry.loopCount === 'number' && entry.loopCount > 0){
entry.remainingLoops = entry.loopCount;
trackEl.classList.add('looped');
buttonEl.setAttribute('title', 'loop count');
updateLoops(entry);
}
if(!entry.locked){ if(!entry.locked){
trackEl.querySelector('button').onclick = () => { buttonEl.onclick = () => {
loadedFirst = true; loadedFirst = true;
autoPlay = true; autoPlay = true;
playEntry(entry); playEntry(entry);
@ -1036,8 +1100,8 @@
entry.error = true; entry.error = true;
entry.errorMessage = e.target.error; entry.errorMessage = e.target.error;
removeLoading(entry, true); removeLoading(entry, true);
trackEl.querySelector('button').classList.add('error', 'icon-warning'); buttonEl.classList.add('error', 'icon-warning');
trackEl.querySelector('button').setAttribute('title', 'error loading file'); buttonEl.setAttribute('title', 'error loading file');
}); });
loaderEl.addEventListener('canplay', e => { loaderEl.addEventListener('canplay', e => {
removeLoading(entry, true); removeLoading(entry, true);
@ -1255,6 +1319,9 @@
prevTrackEl.addEventListener('click', e => { prevTrackEl.addEventListener('click', e => {
prevTrack(); prevTrack();
}); });
loopSwitchEl.addEventListener('click', e => {
loopSwitch();
});
function updateTrackPreview(){ function updateTrackPreview(){
if(currentEntry.needsDuration){ if(currentEntry.needsDuration){
@ -1347,22 +1414,31 @@
return !entry.error && !entry.locked; return !entry.error && !entry.locked;
} }
function nextTrack(loop=true){ function nextTrack(manual=true){
let idx = media.indexOf(currentEntry)+1; if(loopMode === 'track'){
if(!loop && idx >= media.length){ loadEntry(currentEntry);
return; } else if(!manual && currentEntry.remainingLoops > 0){
} currentEntry.remainingLoops--;
let entry = media[mod(idx, media.length)]; updateLoops(currentEntry);
let i = 0; loadEntry(currentEntry);
while(!canPlay(entry) && i < media.length){ } else {
i++; let loop = manual || loopMode === "playlist";
idx++; let idx = media.indexOf(currentEntry)+1;
if(!loop && idx >= media.length){ if(!loop && idx >= media.length){
return; return;
} }
entry = media[mod(idx, media.length)]; let entry = media[mod(idx, media.length)];
let i = 0;
while(!canPlay(entry) && i < media.length){
i++;
idx++;
if(!loop && idx >= media.length){
return;
}
entry = media[mod(idx, media.length)];
}
loadEntry(entry);
} }
loadEntry(entry);
} }
function prevTrack(){ function prevTrack(){
if(playerEl.currentTime > (currentEntry.previewStart - currentEntry.audioStart) + 5){ if(playerEl.currentTime > (currentEntry.previewStart - currentEntry.audioStart) + 5){
@ -1379,6 +1455,24 @@
loadEntry(entry); loadEntry(entry);
} }
} }
function loopSwitch(){
loopSwitchEl.classList.remove('icon-loop', 'icon-loop-1');
loopSwitchEl.style.opacity = 1;
if(loopMode === "none"){
loopMode = "playlist";
loopSwitchEl.classList.add('icon-loop');
loopSwitchEl.setAttribute('title', 'loop playlist');
} else if(loopMode === "playlist"){
loopMode = "track";
loopSwitchEl.classList.add('icon-loop-1');
loopSwitchEl.setAttribute('title', 'loop track');
} else {
loopMode = "none";
loopSwitchEl.classList.add('icon-loop');
loopSwitchEl.style.opacity = 0.5;
loopSwitchEl.setAttribute('title', 'loop off');
}
}
function setLoading(entry, delay=150){ function setLoading(entry, delay=150){
if(entry.loading){ if(entry.loading){
@ -1406,6 +1500,25 @@
entry.loading = false; entry.loading = false;
} }
function updateLoops(entry){
if(entry.loopCount > 0){
let buttonEl = entry.trackEl.querySelector('button');
buttonEl.setAttribute('loopcount', entry.remainingLoops);
// buttonEl.setAttribute('title', 'loop count');
}
if(entry === currentEntry){
if(entry.loopCount > 0){
bigButtonEl.setAttribute('loopcount', entry.remainingLoops);
} else {
bigButtonEl.removeAttribute('loopcount');
}
}
}
function resetLoops(entry){
entry.remainingLoops = entry.loopCount;
updateLoops(entry);
}
function loadEntry(entry){ function loadEntry(entry){
if(entry.error){ if(entry.error){
@ -1414,11 +1527,15 @@
if(entry.locked){ if(entry.locked){
return; return;
} }
media.forEach(entry => {
entry.trackEl.classList.remove('active');
entry.trackEl.querySelector('button').classList.remove('pause');
});
currentEntry = entry; currentEntry = entry;
media.forEach(thisEntry => {
thisEntry.trackEl.classList.remove('active');
thisEntry.trackEl.querySelector('button').classList.remove('pause');
if(thisEntry !== entry){
resetLoops(thisEntry);
}
updateLoops(entry);
});
if(!currentEntry.duration){ if(!currentEntry.duration){
currentEntry.duration = 999; currentEntry.duration = 999;
currentEntry.needsDuration = true; currentEntry.needsDuration = true;
@ -1430,6 +1547,11 @@
} else { } else {
scrubberTrackContainerEl.classList.remove('preview'); scrubberTrackContainerEl.classList.remove('preview');
} }
if(entry.loopCount > 0){
bigButtonEl.classList.add('looped');
} else {
bigButtonEl.classList.remove('looped');
}
Array.from(mediaContainerEl.children).forEach(el => { Array.from(mediaContainerEl.children).forEach(el => {
el.classList.remove('active'); el.classList.remove('active');
}); });

View file

@ -1,10 +1,10 @@
@font-face { @font-face {
font-family: 'icons'; font-family: 'icons';
src: url('fonts/icons.eot?llsbwi'); src: url('fonts/icons.eot?vz49dm');
src: url('fonts/icons.eot?llsbwi#iefix') format('embedded-opentype'), src: url('fonts/icons.eot?vz49dm#iefix') format('embedded-opentype'),
url('fonts/icons.ttf?llsbwi') format('truetype'), url('fonts/icons.ttf?vz49dm') format('truetype'),
url('fonts/icons.woff?llsbwi') format('woff'), url('fonts/icons.woff?vz49dm') format('woff'),
url('fonts/icons.svg?llsbwi#icons') format('svg'); url('fonts/icons.svg?vz49dm#icons') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: block; font-display: block;
@ -26,6 +26,24 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-plus-big:before {
content: "\2b";
}
.icon-drop-big:before {
content: "\2b07";
}
.icon-loading-big:before {
content: "\25cc";
}
.icon-loop-1:before {
content: "\1f502";
}
.icon-loop:before {
content: "\1f501";
}
.icon-play-loop:before {
content: "\1f503";
}
.icon-duration-preview:before { .icon-duration-preview:before {
content: "\1f55b"; content: "\1f55b";
} }
@ -36,7 +54,7 @@
content: "\23f2"; content: "\23f2";
} }
.icon-end:before { .icon-end:before {
content: "\e900"; content: "\23f5";
} }
.icon-start:before { .icon-start:before {
content: "\23f4"; content: "\23f4";
@ -149,12 +167,3 @@
.icon-play:before { .icon-play:before {
content: "\25b6"; content: "\25b6";
} }
.icon-plus-big:before {
content: "\2b";
}
.icon-drop-big:before {
content: "\2b07";
}
.icon-loading-big:before {
content: "\25cc";
}

Binary file not shown.