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.

feat: preload next queue item after 30 seconds

+56 -23
+7 -3
src/components/engine/audio/element.js
··· 230 230 * @type {Actions["supply"]} 231 231 */ 232 232 supply(args) { 233 - const existingSet = new Set(this.#items.value.map((a) => a.id)); 234 - const newSet = new Set(args.audio.map((a) => a.id)); 233 + const existingMap = new Map(this.#items.value.map((a) => [a.id, a])); 235 234 236 - if (newSet.difference(existingSet).size !== 0) { 235 + const hasNewIds = args.audio.some((a) => !existingMap.has(a.id)); 236 + const hasPreloadChanges = args.audio.some( 237 + (a) => existingMap.get(a.id)?.isPreload !== a.isPreload, 238 + ); 239 + 240 + if (hasNewIds || hasPreloadChanges) { 237 241 this.#items.value = args.audio; 238 242 } 239 243
+49 -20
src/components/orchestrator/queue-audio/element.js
··· 64 64 // 🛠️ 65 65 66 66 async monitorActiveQueueItem() { 67 - if (!this.audio) return; 68 - if (!this.input) return; 69 - if (!this.queue) return; 67 + const audio = this.audio; 68 + const input = this.input; 69 + const queue = this.queue; 70 70 71 - const activeItem = this.queue.now(); 71 + if (!audio) return; 72 + if (!input) return; 73 + if (!queue) return; 74 + 75 + const activeItem = queue.now(); 76 + const nextItem = queue.future()[0] ?? null; 77 + 78 + const tracks = this.output?.tracks.collection(); 72 79 const activeTrack = activeItem 73 - ? this.output?.tracks.collection().find((t) => t.id === activeItem.id) 80 + ? tracks?.find((t) => t.id === activeItem.id) 81 + : undefined; 82 + const nextTrack = nextItem 83 + ? tracks?.find((t) => t.id === nextItem.id) 74 84 : undefined; 85 + 75 86 if ((await this.isLeader()) === false) return; 76 87 77 - const isPlaying = untracked(this.audio.isPlaying); 88 + const isPlaying = untracked(audio.isPlaying); 78 89 79 - // Resolve URIs 90 + // Resolve active URI 80 91 const resolvedUri = activeTrack 81 - ? await this.input.resolve({ method: "GET", uri: activeTrack.uri }) 92 + ? await input.resolve({ method: "GET", uri: activeTrack.uri }) 82 93 : undefined; 83 94 84 95 if (resolvedUri && "stream" in resolvedUri) { ··· 88 99 const url = resolvedUri?.url; 89 100 90 101 // Check if we still need to render 91 - if (this.queue.now?.()?.id !== activeItem?.id) return; 102 + if (queue.now?.()?.id !== activeItem?.id) return; 92 103 93 - // Play new active queue item 104 + // Supply active track immediately 94 105 // TODO: Take URL expiration timestamp into account 95 - // TODO: Preload next queue item 96 - this.audio.supply({ 97 - audio: activeItem && url 98 - ? [{ 99 - id: activeItem.id, 100 - isPreload: false, 101 - url, 102 - }] 103 - // TODO: Keep preloads 104 - : [], 106 + const activeAudio = activeItem && url 107 + ? [{ id: activeItem.id, isPreload: false, url }] 108 + : []; 109 + audio.supply({ 110 + audio: activeAudio, 105 111 play: activeItem && isPlaying ? { audioId: activeItem.id } : undefined, 106 112 }); 113 + 114 + // Preload next track after a delay 115 + clearTimeout(this._preloadTimeout); 116 + if (!nextTrack) return; 117 + 118 + this._preloadTimeout = setTimeout(async () => { 119 + const resolvedNextUri = await input.resolve({ 120 + method: "GET", 121 + uri: nextTrack.uri, 122 + }); 123 + const nextUrl = resolvedNextUri && !("stream" in resolvedNextUri) 124 + ? resolvedNextUri.url 125 + : undefined; 126 + if (!nextUrl) return; 127 + 128 + audio.supply({ 129 + audio: [...activeAudio, { 130 + id: nextItem.id, 131 + isPreload: true, 132 + url: nextUrl, 133 + }], 134 + }); 135 + }, 30_000); 107 136 } 108 137 109 138 async monitorAudioEnd() {