A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

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

Don't start the app without the service worker

+141 -104
+141 -64
src/Javascript/index.js
··· 25 25 location.href = location.href.replace("http://", "https://") 26 26 } 27 27 28 + // Service worker 29 + if ("serviceWorker" in navigator) { 30 + navigator.serviceWorker.register("service-worker.js").then( 31 + initialise, 32 + () => failure( 33 + location.protocol === "https:" 34 + ? "Failed to start service worker." 35 + : "Failed to start service worker, try using HTTPS." 36 + ) 37 + ) 38 + } else { 39 + failure("We'll need a more modern browser to liven the place up a bit.") 40 + } 28 41 29 42 43 + 30 44 // 🍱 31 45 32 46 33 - const app = Elm.UI.init({ 34 - node: document.getElementById("elm"), 35 - flags: { 36 - darkMode: preferredColorScheme().matches, 37 - initialTime: Date.now(), 38 - isOnline: navigator.onLine, 39 - upgrade: viableForUpgrade(), 40 - viewport: { 41 - height: window.innerHeight, 42 - width: window.innerWidth 47 + let app 48 + let wire = {} 49 + 50 + 51 + function initialise() { 52 + app = Elm.UI.init({ 53 + node: document.getElementById("elm"), 54 + flags: { 55 + darkMode: preferredColorScheme().matches, 56 + initialTime: Date.now(), 57 + isOnline: navigator.onLine, 58 + upgrade: viableForUpgrade(), 59 + viewport: { 60 + height: window.innerHeight, 61 + width: window.innerWidth 62 + } 43 63 } 44 - } 45 - }) 64 + }) 65 + 66 + self.app = app 67 + 68 + // ⚡️ 69 + wire.brain() 70 + wire.audio() 71 + wire.backdrop() 72 + wire.clipboard() 73 + wire.covers() 74 + } 75 + 46 76 77 + function failure(text) { 78 + const note = document.createElement("div") 47 79 48 - self.app = app 80 + note.className = "flex flex-col font-body items-center h-screen italic justify-center leading-relaxed px-4 text-center text-base text-white" 81 + note.innerHTML = ` 82 + <a class="block logo mb-5" href="../"> 83 + <img src="../images/diffuse-light.svg" /> 84 + </a> 85 + 86 + <p class="opacity-60"> 87 + ${text} 88 + </p> 89 + ` 90 + 91 + document.body.appendChild(note) 92 + 93 + // Remove loader 94 + const elm = document.querySelector("#elm") 95 + 96 + elm && elm.parentNode.removeChild(elm) 97 + } 49 98 50 99 51 100 52 101 // Brain 53 102 // ===== 54 103 55 - const brain = new Worker( 56 - "brain.js#appHref=" + 57 - encodeURIComponent(window.location.href) 58 - ) 104 + let brain 59 105 106 + wire.brain = () => { 107 + brain = new Worker( 108 + "brain.js#appHref=" + 109 + encodeURIComponent(window.location.href) 110 + ) 60 111 61 - app.ports.toBrain.subscribe(thing => { 62 - brain.postMessage(thing) 63 - }) 112 + brain.onmessage = event => { 113 + if (event.data.action) return handleAction(event.data.action, event.data.data) 114 + if (event.data.tag) return app.ports.fromAlien.send(event.data) 115 + } 64 116 65 - 66 - brain.onmessage = event => { 67 - if (event.data.action) return handleAction(event.data.action, event.data.data) 68 - if (event.data.tag) return app.ports.fromAlien.send(event.data) 117 + app.ports.toBrain.subscribe(a => brain.postMessage(a)) 69 118 } 70 119 71 120 ··· 78 127 // Audio 79 128 // ----- 80 129 81 - const orchestrion = { 82 - activeQueueItem: null, 83 - audio: null, 84 - app: app, 85 - repeat: false 86 - } 130 + let orchestrion 131 + 132 + 133 + wire.audio = () => { 134 + orchestrion = { 135 + activeQueueItem: null, 136 + audio: null, 137 + app: app, 138 + repeat: false 139 + } 87 140 141 + audioEngine.setup(orchestrion) 88 142 89 - audioEngine.setup(orchestrion) 143 + app.ports.activeQueueItemChanged.subscribe(activeQueueItemChanged) 144 + app.ports.adjustEqualizerSetting.subscribe(adjustEqualizerSetting) 145 + app.ports.pause.subscribe(pause) 146 + app.ports.play.subscribe(play) 147 + app.ports.preloadAudio.subscribe(preloadAudio()) 148 + app.ports.seek.subscribe(seek) 149 + app.ports.setRepeat.subscribe(setRepeat) 150 + } 90 151 91 152 92 - app.ports.activeQueueItemChanged.subscribe(item => { 153 + function activeQueueItemChanged(item) { 93 154 if (orchestrion.activeQueueItem && item && item.trackId === orchestrion.activeQueueItem.trackId) { 94 155 orchestrion.audio.currentTime = 0 95 156 return ··· 115 176 app.ports.setAudioIsPlaying.send(false) 116 177 app.ports.setAudioPosition.send(0) 117 178 } 118 - }) 179 + } 119 180 120 181 121 - app.ports.adjustEqualizerSetting.subscribe(e => { 182 + function adjustEqualizerSetting(e) { 122 183 audioEngine.adjustEqualizerSetting(e.knob, e.value) 123 - }) 184 + } 124 185 125 186 126 - app.ports.pause.subscribe(_ => { 187 + function pause(_) { 127 188 if (orchestrion.audio) orchestrion.audio.pause() 128 - }) 189 + } 129 190 130 191 131 - app.ports.play.subscribe(_ => { 192 + function play(_) { 132 193 if (orchestrion.audio) orchestrion.audio.play() 133 - }) 194 + } 134 195 135 196 136 - app.ports.preloadAudio.subscribe(debounce(item => { 137 - // Wait 15 seconds to preload something. 138 - // This is particularly useful when quickly shifting through tracks, 139 - // or when moving things around in the queue. 140 - (audioEngine.usesSingleAudioNode() || item.isCached) 141 - ? false 142 - : audioEngine.preloadAudioElement(orchestrion, item) 143 - }, 15000)) 197 + function preloadAudio() { 198 + return debounce(item => { 199 + // Wait 15 seconds to preload something. 200 + // This is particularly useful when quickly shifting through tracks, 201 + // or when moving things around in the queue. 202 + (audioEngine.usesSingleAudioNode() || item.isCached) 203 + ? false 204 + : audioEngine.preloadAudioElement(orchestrion, item) 205 + }, 15000) 206 + } 144 207 145 208 146 - app.ports.seek.subscribe(percentage => { 209 + function seek(percentage) { 147 210 audioEngine.seek(orchestrion.audio, percentage) 148 - }) 211 + } 149 212 150 213 151 - app.ports.setRepeat.subscribe(repeat => { 214 + function setRepeat(repeat) { 152 215 orchestrion.repeat = repeat 153 - }) 216 + } 154 217 155 218 156 219 157 220 // Backdrop 158 221 // -------- 159 222 223 + wire.backdrop = () => { 224 + app.ports.pickAverageBackgroundColor.subscribe(pickAverageBackgroundColor) 225 + } 226 + 227 + 160 228 function averageColorOfImage(img) { 161 229 const canvas = document.createElement("canvas") 162 230 const ctx = canvas.getContext("2d") ··· 182 250 } 183 251 184 252 185 - app.ports.pickAverageBackgroundColor.subscribe(src => { 253 + function pickAverageBackgroundColor(src) { 186 254 const img = document.querySelector(`img[src$="${src}"]`) 187 255 188 256 if (img) { 189 257 const avgColor = averageColorOfImage(img) 190 258 app.ports.setAverageBackgroundColor.send(avgColor) 191 259 } 192 - }) 260 + } 193 261 194 262 195 263 196 264 // Clipboard 197 265 // --------- 198 266 199 - app.ports.copyToClipboard.subscribe(text => { 267 + wire.clipboard = () => { 268 + app.ports.copyToClipboard.subscribe(copyToClipboard) 269 + } 270 + 271 + 272 + function copyToClipboard(text) { 200 273 201 274 // Insert a textarea element 202 275 const el = document.createElement("textarea") ··· 226 299 document.getSelection().addRange(selected) 227 300 } 228 301 229 - }) 302 + } 230 303 231 304 232 305 ··· 236 309 const loadingCovers = {} 237 310 238 311 239 - app.ports.loadAlbumCovers.subscribe( 240 - debounce(loadAlbumCovers, 500) 241 - ) 312 + wire.covers = () => { 313 + app.ports.loadAlbumCovers.subscribe( 314 + debounce(loadAlbumCovers, 500) 315 + ) 316 + 317 + db.keys().then(cachedCovers) 318 + } 242 319 243 320 244 321 function loadAlbumCovers() { ··· 286 363 287 364 288 365 // Send a dictionary of the cached covers to the app. 289 - db.keys().then(keys => { 366 + function cachedCovers(keys) { 290 367 const cacheKeys = keys.filter( 291 368 k => k.startsWith("coverCache.") 292 369 ) ··· 309 386 app.ports.insertCoverCache.send(cache) 310 387 setTimeout(loadAlbumCovers, 500) 311 388 }) 312 - }) 389 + } 313 390 314 391 315 392 ··· 489 566 }) 490 567 491 568 492 - // navigator.mediaSession.setActionHandler("seekto", event => { 493 - // const audio = orchestrion.audio 494 - // if (audio) audio.currentTime = event.seekTime 495 - // }) 569 + navigator.mediaSession.setActionHandler("seekto", event => { 570 + const audio = orchestrion.audio 571 + if (audio) audio.currentTime = event.seekTime 572 + }) 496 573 497 574 } 498 575
-40
src/Static/Html/Application.html
··· 70 70 <script src="ui.elm.js"></script> 71 71 <script src="ui.js"></script> 72 72 73 - <!-- Service worker --> 74 - <script> 75 - if ("serviceWorker" in navigator) { 76 - navigator.serviceWorker.register("service-worker.js") 77 - } 78 - </script> 79 - 80 - <!-- 81 - 82 - YE OLDEN BROWSER 83 - ================ 84 - 85 - This `if` condition will be run for older browsers. 86 - 87 - --> 88 - <script> 89 - if (!self.app || !self.context) { 90 - // Add note 91 - const note = document.createElement("div") 92 - 93 - note.className = "flex flex-col font-body items-center h-screen italic justify-center leading-relaxed px-4 text-center text-base text-white" 94 - note.innerHTML = ` 95 - \<a class="block logo mb-5" href="../"> 96 - \<img src="../images/diffuse-light.svg" /> 97 - \</a> 98 - 99 - \<p class="opacity-60"> 100 - We'll need a more modern browser to liven the place up a bit. 101 - \</p> 102 - ` 103 - 104 - document.body.appendChild(note) 105 - 106 - // Remove loader 107 - const elm = document.querySelector("#elm") 108 - 109 - elm && elm.parentNode.removeChild(elm) 110 - } 111 - </script> 112 - 113 73 114 74 </body> 115 75 </html>