a simple web player for subsonic tinysub.devins.page
subsonic navidrome javascript
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: loop

closes #7

intergrav 347057e5 011d7c5e

+39 -5
+7 -2
src/css/components.css
··· 270 270 } 271 271 272 272 #queue #queue-table .queue-favorite:hover { 273 - opacity: 0.7; 273 + opacity: 0.5; 274 274 } 275 275 276 276 #queue #queue-table .queue-favorite.favorited { ··· 288 288 background: var(--bg-tertiary); 289 289 } 290 290 291 - #actions #shuffle-btn { 291 + #actions #loop-btn { 292 292 margin-inline-start: auto; 293 + opacity: 0.5; 294 + } 295 + 296 + #actions #loop-btn.active { 297 + opacity: 1; 293 298 } 294 299 295 300 /* CONTEXT MENU */
+5
src/index.html
··· 17 17 id="icon-play-next" 18 18 src="static/famfamfam-silk/control_fastforward_blue.png" 19 19 /> 20 + <img id="icon-loop" src="static/famfamfam-silk/control_repeat.png" /> 20 21 <img id="icon-add" src="static/famfamfam-silk/add.png" /> 21 22 <img id="icon-favorite" src="static/famfamfam-silk/heart.png" /> 22 23 <img id="icon-move-up" src="static/famfamfam-silk/arrow_up.png" /> ··· 190 191 ><a href="https://tangled.org/devins.page/tinysub">tinysub</a> 191 192 1.5.0</span 192 193 > 194 + <button id="loop-btn" aria-label="loop"> 195 + <img src="static/famfamfam-silk/control_repeat.png" alt="loop" /> 196 + <span>loop</span> 197 + </button> 193 198 <button id="shuffle-btn" aria-label="shuffle"> 194 199 <img 195 200 src="static/famfamfam-silk/arrow_refresh_small.png"
+1
src/js/constants.js
··· 52 52 PROGRESS: "progress", 53 53 TIME_DISPLAY: "time-display", 54 54 SHUFFLE_BTN: "shuffle-btn", 55 + LOOP_BTN: "loop-btn", 55 56 CLEAR_BTN: "clear-btn", 56 57 SECTION_TOGGLE: "section-toggle", 57 58 };
+5
src/js/events.js
··· 245 245 } 246 246 }); 247 247 248 + ui.loopBtn.addEventListener("click", () => { 249 + state.loop = !state.loop; 250 + ui.loopBtn.classList.toggle("active", state.loop); 251 + }); 252 + 248 253 ui.clearBtn.addEventListener("click", () => { 249 254 clearQueue(); 250 255 resetPlayerUI();
+19 -3
src/js/player.js
··· 27 27 28 28 // move to next or previous track in queue 29 29 function navigateTrack(offset) { 30 - const newIndex = state.queueIndex + offset; 30 + let newIndex = state.queueIndex + offset; 31 + // loop back to start if at end and moving forward with loop enabled 32 + if (offset > 0 && newIndex >= state.queue.length && state.loop) { 33 + newIndex = 0; 34 + } 31 35 if (newIndex >= 0 && newIndex < state.queue.length) { 32 36 state.queueIndex = newIndex; 33 37 saveQueue(); 34 38 playTrack(state.queue[state.queueIndex]); 39 + } else if (offset > 0) { 40 + // skipping past end with loop off ends track 41 + endQueue(); 35 42 } 36 43 } 37 44 ··· 64 71 } 65 72 } 66 73 74 + // end queue playback and update UI 75 + function endQueue() { 76 + clearPlayerUI(); 77 + highlightCurrentTrack(); 78 + } 79 + 67 80 // clear player UI and reset to default state 68 81 function clearPlayerUI() { 69 82 ui.player.src = ""; ··· 90 103 if (state.queueIndex < state.queue.length) { 91 104 saveQueue(); 92 105 playTrack(state.queue[state.queueIndex]); 106 + } else if (state.loop) { 107 + state.queueIndex = 0; 108 + saveQueue(); 109 + playTrack(state.queue[0]); 93 110 } else { 94 - clearPlayerUI(); 95 - highlightCurrentTrack(); 111 + endQueue(); 96 112 } 97 113 } 98 114
+2
src/js/state.js
··· 6 6 queue: [], 7 7 queueIndex: -1, 8 8 favorites: new Set(), 9 + loop: false, 9 10 expanded: { 10 11 artists: false, 11 12 playlists: false, ··· 47 48 settingsBtn: document.getElementById("settings-btn"), 48 49 sectionToggles: document.querySelectorAll(`.${DOM_IDS.SECTION_TOGGLE}`), 49 50 shuffleBtn: document.getElementById(DOM_IDS.SHUFFLE_BTN), 51 + loopBtn: document.getElementById(DOM_IDS.LOOP_BTN), 50 52 clearBtn: document.getElementById(DOM_IDS.CLEAR_BTN), 51 53 };