From e87811c554b58b2912ad28ae186811b7824954cf Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Sat, 25 Mar 2023 11:48:14 +0100 Subject: [PATCH 01/73] chore(docker): bump py3-cryptography to version 38.0.3-r1 Part-of: --- api/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index c22de6d87..de1e058cd 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -33,7 +33,7 @@ RUN set -eux; \ openssl-dev \ postgresql-dev \ zlib-dev \ - py3-cryptography=38.0.3-r0 \ + py3-cryptography=38.0.3-r1 \ py3-lxml=4.9.2-r0 \ py3-pillow=9.3.0-r0 \ py3-psycopg2=2.9.5-r0 \ @@ -92,7 +92,7 @@ RUN set -eux; \ libpq \ libxml2 \ libxslt \ - py3-cryptography=38.0.3-r0 \ + py3-cryptography=38.0.3-r1 \ py3-lxml=4.9.2-r0 \ py3-pillow=9.3.0-r0 \ py3-psycopg2=2.9.5-r0 \ From 026196d69814137b4eef64e9e1e6af2bce990b78 Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Sat, 28 Jan 2023 21:23:46 +0100 Subject: [PATCH 02/73] feat: ensure next track is always preloaded Part-of: --- front/package.json | 8 ++-- front/src/composables/audio/tracks.ts | 32 ++++++++------ front/yarn.lock | 60 +++++++++++++-------------- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/front/package.json b/front/package.json index e0509e5bd..14f069829 100644 --- a/front/package.json +++ b/front/package.json @@ -20,10 +20,10 @@ "@sentry/tracing": "7.27.0", "@sentry/vue": "7.27.0", "@vue/runtime-core": "3.2.45", - "@vueuse/core": "9.6.0", - "@vueuse/integrations": "9.6.0", - "@vueuse/math": "9.6.0", - "@vueuse/router": "9.6.0", + "@vueuse/core": "9.11.1", + "@vueuse/integrations": "9.11.1", + "@vueuse/math": "9.11.1", + "@vueuse/router": "9.11.1", "axios": "1.2.3", "axios-auth-refresh": "3.3.6", "butterchurn": "3.0.0-beta.4", diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index e29235522..195df4ae9 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -3,7 +3,7 @@ import type { Track, Upload } from '~/types' import type { Sound } from '~/api/player' import { createGlobalState, syncRef, useTimeoutFn, whenever } from '@vueuse/core' -import { computed, ref } from 'vue' +import { computed, ref, watchEffect } from 'vue' import { connectAudioSource } from '~/composables/audio/audio-api' import { usePlayer } from '~/composables/audio/player' @@ -110,7 +110,8 @@ export const useTracks = createGlobalState(() => { } // Preload next track - const { start: startPreloadTimeout, stop: stopPreloadTimeout } = useTimeoutFn(async (index) => { + const { start: startPreloadTimeout } = useTimeoutFn(async (index) => { + console.log('@@@@', index) const { queue } = useQueue() const sound = await createSound(queue.value[index as number]) await sound.preload() @@ -118,8 +119,6 @@ export const useTracks = createGlobalState(() => { // Create track from queue const createTrack = async (index: number) => { - stopPreloadTimeout() - const { queue, currentIndex, playNext, hasNext } = useQueue() if (queue.value.length <= index || index === -1) return console.log('LOADING TRACK', index) @@ -148,24 +147,33 @@ export const useTracks = createGlobalState(() => { if (isPlaying.value && index === currentIndex.value) { await sound.play() } - - // NOTE: Preload next track - if (index === currentIndex.value && index + 1 < queue.value.length) { - // @ts-expect-error vueuse is wrongly typed? - startPreloadTimeout(index + 1) - } } - + const currentTrack = ref() // NOTE: We want to have it called only once, hence we're using createGlobalState const initialize = createGlobalState(() => { - const { currentIndex, currentTrack: track } = useQueue() + const { currentIndex, currentTrack: track, queue, hasNext } = useQueue() whenever(track, () => { createTrack(currentIndex.value) }, { immediate: true }) + let lastTrack: QueueTrack + watchEffect(async () => { + if (!hasNext.value) return + + const nextTrack = queue.value[currentIndex.value + 1] + if (lastTrack === nextTrack) return + lastTrack = nextTrack + + // NOTE: Preload next track + // Calling this function clears previous timeout and starts a new one. + // Since this watchEffect fires whenever currentIndex / nextTrack changes, it will automatically cleanup previous preload. + // @ts-expect-error vueuse is wrongly typed? + startPreloadTimeout(currentIndex.value + 1) + }) + syncRef(track, currentTrack, { direction: 'ltr' }) diff --git a/front/yarn.lock b/front/yarn.lock index 7ad272d6c..794ba2c9c 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -2181,50 +2181,50 @@ resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz#4a61dbd29783d01ddab504276dcf0c2b6988654f" integrity sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg== -"@vueuse/core@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.6.0.tgz#de1d4730849cdbe28a9ebcf6cad167a700919603" - integrity sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A== +"@vueuse/core@9.11.1": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.11.1.tgz#1552aef67144220da7e6b797372a194a9c7ff52e" + integrity sha512-E/cizD1w9ILkq4axYjZrXLkKaBfzloaby2n3NMjUfd6yI/jkfTVgc6iwy/Cw2e++Ld4LphGbO+3MhzizvwUslQ== dependencies: "@types/web-bluetooth" "^0.0.16" - "@vueuse/metadata" "9.6.0" - "@vueuse/shared" "9.6.0" + "@vueuse/metadata" "9.11.1" + "@vueuse/shared" "9.11.1" vue-demi "*" -"@vueuse/integrations@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.6.0.tgz#e2c6a5e1770b189a643d9c5ed65592c486db6254" - integrity sha512-+rs2OWY/3spxoAGQMnlHQpxf8ErAYf4D1bT0aXaPnxphmtYgexm6KIjTFpBbcQnHwVi1g2ET1SJoQL16yDrgWA== +"@vueuse/integrations@9.11.1": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.11.1.tgz#ddcb69210d406992c74f32936443e16575a290e6" + integrity sha512-1VBT1U0ScI0GmJn+i7RvyCX5P+Dh04yxHurN3RniYPCFOJ8mCKSqJlzSA5aQ94UIK0ZKI2RyEGS8FY0WAteixw== dependencies: - "@vueuse/core" "9.6.0" - "@vueuse/shared" "9.6.0" + "@vueuse/core" "9.11.1" + "@vueuse/shared" "9.11.1" vue-demi "*" -"@vueuse/math@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@vueuse/math/-/math-9.6.0.tgz#7baa1e92bd7be362a749673078b918e702ca2dea" - integrity sha512-bYTFZjTcJwgqPdY7SaxS/J5mrWy1QaESxD5HK5UhXY49DUYf4jjZxXjMVBObM/A46jQgRolqSEQDOXrrpEfEww== +"@vueuse/math@9.11.1": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@vueuse/math/-/math-9.11.1.tgz#b574099f9c36729803b95d76191d6196133fcc82" + integrity sha512-EE0QCHexD91lleJuNFtcYAuJw08j9Hl/DytBFZSx+Piug86qhbHfcr6eHzawvZb+Yc2pHLPkUVSErsvf0d+Vkw== dependencies: - "@vueuse/shared" "9.6.0" + "@vueuse/shared" "9.11.1" vue-demi "*" -"@vueuse/metadata@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.6.0.tgz#b0a73277538cebef5d477983f74fdd2aa21ce5f9" - integrity sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w== +"@vueuse/metadata@9.11.1": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.11.1.tgz#f4e5fd9cb421c5a02373d034a0ce53538b370518" + integrity sha512-ABjkrG+VXggNhjfGyw5e/sekxTZfXTwjrYXkkWQmQ7Biyv+Gq9UD6IDNfeGvQZEINI0Qzw6nfuO2UFCd3hlrxQ== -"@vueuse/router@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-9.6.0.tgz#2827ea2238e7937ee6ff3acb32dc8feddafe6200" - integrity sha512-3TIZPX5smlimSNlTm+K3ESRTkA2VBHnwMintNrw4Z+WK5bh1UAh7lcBQluiGg3LJjkrMXYfuO7IPdU+a8NRnFA== +"@vueuse/router@9.11.1": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-9.11.1.tgz#cdf32998a8646b0fda47e52cb9b89301d392766c" + integrity sha512-nGmPnBZCcErEDpN2FXLcbdsHKSyRr10y+u2HzzJeDj3IutZjlCEysEOdDzlfjgUssNWao6CeSIb3sRMFZpWRsA== dependencies: - "@vueuse/shared" "9.6.0" + "@vueuse/shared" "9.11.1" vue-demi "*" -"@vueuse/shared@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.6.0.tgz#ce2e0e8124c6bdb1e270fc213e334ccc71dcb951" - integrity sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ== +"@vueuse/shared@9.11.1": + version "9.11.1" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.11.1.tgz#c76805c9d86da109b132529b4745d7d706106e7f" + integrity sha512-UTZYGAjT96hWn4buf4wstZbeheBVNcKPQuej6qpoSkjF1atdaeCD6kqm9uGL2waHfisSgH9mq0qCRiBOk5C/2w== dependencies: vue-demi "*" From d3b346341554d4bea36c9cba8759110fdb4790e5 Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Sat, 28 Jan 2023 23:45:15 +0100 Subject: [PATCH 03/73] fix: #2053 Part-of: --- front/src/composables/audio/queue.ts | 15 ++++++++++++--- front/src/composables/audio/tracks.ts | 3 +-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/front/src/composables/audio/queue.ts b/front/src/composables/audio/queue.ts index 4caf8d1d8..377628847 100644 --- a/front/src/composables/audio/queue.ts +++ b/front/src/composables/audio/queue.ts @@ -174,7 +174,12 @@ export const useQueue = createGlobalState(() => { await playNext(true) } - tracks.value.splice(index, 1) + if (isShuffled.value) { + tracks.value.splice(tracks.value.indexOf(shuffledIds.value[index]), 1) + shuffledIds.value.splice(index, 1) + } else { + tracks.value.splice(index, 1) + } if (index <= currentIndex.value) { currentIndex.value -= 1 @@ -239,8 +244,12 @@ export const useQueue = createGlobalState(() => { // Reorder const reorder = (from: number, to: number) => { - const [id] = tracks.value.splice(from, 1) - tracks.value.splice(to, 0, id) + const list = isShuffled.value + ? shuffledIds + : tracks + + const [id] = list.value.splice(from, 1) + list.value.splice(to, 0, id) const current = currentIndex.value if (current === from) { diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 195df4ae9..1e35b627e 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -111,7 +111,6 @@ export const useTracks = createGlobalState(() => { // Preload next track const { start: startPreloadTimeout } = useTimeoutFn(async (index) => { - console.log('@@@@', index) const { queue } = useQueue() const sound = await createSound(queue.value[index as number]) await sound.preload() @@ -170,7 +169,7 @@ export const useTracks = createGlobalState(() => { // NOTE: Preload next track // Calling this function clears previous timeout and starts a new one. // Since this watchEffect fires whenever currentIndex / nextTrack changes, it will automatically cleanup previous preload. - // @ts-expect-error vueuse is wrongly typed? + // @ts-expect-error vueuse is wrongly typed: https://github.com/vueuse/vueuse/issues/2691 startPreloadTimeout(currentIndex.value + 1) }) From b21edbb64d8618d1701695ad925cdd644188394f Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Sun, 29 Jan 2023 00:04:09 +0100 Subject: [PATCH 04/73] fix: #2061 Part-of: --- front/src/composables/audio/player.ts | 9 ++++++++- front/src/composables/audio/tracks.ts | 25 ++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/front/src/composables/audio/player.ts b/front/src/composables/audio/player.ts index e69dc5d56..a149130bc 100644 --- a/front/src/composables/audio/player.ts +++ b/front/src/composables/audio/player.ts @@ -192,7 +192,14 @@ export const usePlayer = createGlobalState(() => { return sound.isErrored.value }) - const { start: startErrorTimeout, stop: stopErrorTimeout } = useTimeoutFn(() => playNext(), 3000, { immediate: false }) + const { start: startErrorTimeout, stop: stopErrorTimeout } = useTimeoutFn(() => { + if (looping.value !== LoopingMode.LoopTrack) { + return playNext() + } + + isPlaying.value = false + }, 3000, { immediate: false }) + watch(currentIndex, stopErrorTimeout) whenever(errored, startErrorTimeout) diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 1e35b627e..13fc845db 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -109,6 +109,18 @@ export const useTracks = createGlobalState(() => { return soundPromise } + // Skip when errored + const { start: soundUnplayable, stop: abortSoundUnplayableTimeout } = useTimeoutFn(() => { + const { isPlaying, looping, LoopingMode } = usePlayer() + const { playNext } = useQueue() + + if (looping.value !== LoopingMode.LoopTrack) { + return playNext() + } + + isPlaying.value = false + }, 3000, { immediate: false }) + // Preload next track const { start: startPreloadTimeout } = useTimeoutFn(async (index) => { const { queue } = useQueue() @@ -118,7 +130,9 @@ export const useTracks = createGlobalState(() => { // Create track from queue const createTrack = async (index: number) => { - const { queue, currentIndex, playNext, hasNext } = useQueue() + abortSoundUnplayableTimeout() + + const { queue, currentIndex } = useQueue() if (queue.value.length <= index || index === -1) return console.log('LOADING TRACK', index) @@ -126,14 +140,7 @@ export const useTracks = createGlobalState(() => { const sound = await createSound(track) if (!sound.playable) { - setTimeout(() => { - if (hasNext.value && index !== queue.value.length - 1) { - return playNext(true) - } - - const { isPlaying } = usePlayer() - isPlaying.value = false - }, 3000) + soundUnplayable() return } From 536fa25f0e5eb9e74726e6d79acea97670c0b8f9 Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Sun, 29 Jan 2023 10:41:59 +0100 Subject: [PATCH 05/73] fix: abort preload if next track is unavailable Part-of: --- front/src/composables/audio/tracks.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 13fc845db..7c24f5560 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -122,7 +122,7 @@ export const useTracks = createGlobalState(() => { }, 3000, { immediate: false }) // Preload next track - const { start: startPreloadTimeout } = useTimeoutFn(async (index) => { + const { start: startPreloadTimeout, stop: abortPreload } = useTimeoutFn(async (index) => { const { queue } = useQueue() const sound = await createSound(queue.value[index as number]) await sound.preload() @@ -167,15 +167,15 @@ export const useTracks = createGlobalState(() => { let lastTrack: QueueTrack watchEffect(async () => { + abortPreload() + if (!hasNext.value) return const nextTrack = queue.value[currentIndex.value + 1] - if (lastTrack === nextTrack) return + if (nextTrack && lastTrack === nextTrack) return lastTrack = nextTrack // NOTE: Preload next track - // Calling this function clears previous timeout and starts a new one. - // Since this watchEffect fires whenever currentIndex / nextTrack changes, it will automatically cleanup previous preload. // @ts-expect-error vueuse is wrongly typed: https://github.com/vueuse/vueuse/issues/2691 startPreloadTimeout(currentIndex.value + 1) }) From 7105f5e48275e0c13680bc43cb5218847a7826e4 Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Sun, 29 Jan 2023 12:10:02 +0100 Subject: [PATCH 06/73] fix: ensure preloaded track exists Part-of: --- front/src/api/player.ts | 2 ++ front/src/composables/audio/tracks.ts | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/front/src/api/player.ts b/front/src/api/player.ts index e5108254c..98b620759 100644 --- a/front/src/api/player.ts +++ b/front/src/api/player.ts @@ -69,6 +69,8 @@ export class HTMLSound implements Sound { this.#audio.src = source this.#audio.preload = 'auto' + console.log('CREATED SOUND INSTANCE', this) + useEventListener(this.#audio, 'ended', () => this.#soundEndEventHook.trigger(this)) useEventListener(this.#audio, 'timeupdate', () => { if (this.#audio.currentTime === 0) { diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 7c24f5560..7087de1a4 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -122,9 +122,8 @@ export const useTracks = createGlobalState(() => { }, 3000, { immediate: false }) // Preload next track - const { start: startPreloadTimeout, stop: abortPreload } = useTimeoutFn(async (index) => { - const { queue } = useQueue() - const sound = await createSound(queue.value[index as number]) + const { start: preload, stop: abortPreload } = useTimeoutFn(async (track: QueueTrack) => { + const sound = await createSound(track) await sound.preload() }, 100, { immediate: false }) @@ -172,12 +171,12 @@ export const useTracks = createGlobalState(() => { if (!hasNext.value) return const nextTrack = queue.value[currentIndex.value + 1] - if (nextTrack && lastTrack === nextTrack) return + if (!nextTrack || lastTrack === nextTrack) return lastTrack = nextTrack // NOTE: Preload next track // @ts-expect-error vueuse is wrongly typed: https://github.com/vueuse/vueuse/issues/2691 - startPreloadTimeout(currentIndex.value + 1) + preload(nextTrack) }) syncRef(track, currentTrack, { From 206a154a87e54aed876b73baadfd40fa49255dcc Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Sun, 29 Jan 2023 12:31:58 +0100 Subject: [PATCH 07/73] fix: update vueuse to remove @ts-expect-error comment Part-of: --- front/package.json | 8 ++-- front/src/composables/audio/tracks.ts | 1 - front/yarn.lock | 60 +++++++++++++-------------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/front/package.json b/front/package.json index 14f069829..b3fda3f52 100644 --- a/front/package.json +++ b/front/package.json @@ -20,10 +20,10 @@ "@sentry/tracing": "7.27.0", "@sentry/vue": "7.27.0", "@vue/runtime-core": "3.2.45", - "@vueuse/core": "9.11.1", - "@vueuse/integrations": "9.11.1", - "@vueuse/math": "9.11.1", - "@vueuse/router": "9.11.1", + "@vueuse/core": "9.12.0", + "@vueuse/integrations": "9.12.0", + "@vueuse/math": "9.12.0", + "@vueuse/router": "9.12.0", "axios": "1.2.3", "axios-auth-refresh": "3.3.6", "butterchurn": "3.0.0-beta.4", diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 7087de1a4..09d2da72e 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -175,7 +175,6 @@ export const useTracks = createGlobalState(() => { lastTrack = nextTrack // NOTE: Preload next track - // @ts-expect-error vueuse is wrongly typed: https://github.com/vueuse/vueuse/issues/2691 preload(nextTrack) }) diff --git a/front/yarn.lock b/front/yarn.lock index 794ba2c9c..7a10d69e0 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -2181,50 +2181,50 @@ resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz#4a61dbd29783d01ddab504276dcf0c2b6988654f" integrity sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg== -"@vueuse/core@9.11.1": - version "9.11.1" - resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.11.1.tgz#1552aef67144220da7e6b797372a194a9c7ff52e" - integrity sha512-E/cizD1w9ILkq4axYjZrXLkKaBfzloaby2n3NMjUfd6yI/jkfTVgc6iwy/Cw2e++Ld4LphGbO+3MhzizvwUslQ== +"@vueuse/core@9.12.0": + version "9.12.0" + resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.12.0.tgz#e5b20f901e081c7ae5fe0e5f3af217929034eefe" + integrity sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg== dependencies: "@types/web-bluetooth" "^0.0.16" - "@vueuse/metadata" "9.11.1" - "@vueuse/shared" "9.11.1" + "@vueuse/metadata" "9.12.0" + "@vueuse/shared" "9.12.0" vue-demi "*" -"@vueuse/integrations@9.11.1": - version "9.11.1" - resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.11.1.tgz#ddcb69210d406992c74f32936443e16575a290e6" - integrity sha512-1VBT1U0ScI0GmJn+i7RvyCX5P+Dh04yxHurN3RniYPCFOJ8mCKSqJlzSA5aQ94UIK0ZKI2RyEGS8FY0WAteixw== +"@vueuse/integrations@9.12.0": + version "9.12.0" + resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.12.0.tgz#bd28cbab8afccc96b122bb44750b24b6d1b1496a" + integrity sha512-bu0hOQAqg7A8S33RHpr49LuzVQJ4tK4oyimEfhPFGUVqmz/MMcwPH8Lde+MbVXvfYh2hrtwNv9S38pCmonRx4w== dependencies: - "@vueuse/core" "9.11.1" - "@vueuse/shared" "9.11.1" + "@vueuse/core" "9.12.0" + "@vueuse/shared" "9.12.0" vue-demi "*" -"@vueuse/math@9.11.1": - version "9.11.1" - resolved "https://registry.yarnpkg.com/@vueuse/math/-/math-9.11.1.tgz#b574099f9c36729803b95d76191d6196133fcc82" - integrity sha512-EE0QCHexD91lleJuNFtcYAuJw08j9Hl/DytBFZSx+Piug86qhbHfcr6eHzawvZb+Yc2pHLPkUVSErsvf0d+Vkw== +"@vueuse/math@9.12.0": + version "9.12.0" + resolved "https://registry.yarnpkg.com/@vueuse/math/-/math-9.12.0.tgz#d7233840a7f6a1cc50357df02224745d9976f4a7" + integrity sha512-i4N67Ib+FXh/cdwK1J7hijSy8QXBKGqy492SvVBd6UjPPeCSIkkd4veO2Lsj0gMeRfObFsVdY0+1jW+kTRcJMw== dependencies: - "@vueuse/shared" "9.11.1" + "@vueuse/shared" "9.12.0" vue-demi "*" -"@vueuse/metadata@9.11.1": - version "9.11.1" - resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.11.1.tgz#f4e5fd9cb421c5a02373d034a0ce53538b370518" - integrity sha512-ABjkrG+VXggNhjfGyw5e/sekxTZfXTwjrYXkkWQmQ7Biyv+Gq9UD6IDNfeGvQZEINI0Qzw6nfuO2UFCd3hlrxQ== +"@vueuse/metadata@9.12.0": + version "9.12.0" + resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.12.0.tgz#19a0fefcba6a66a2382af10a7a67ebad6eec1f27" + integrity sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ== -"@vueuse/router@9.11.1": - version "9.11.1" - resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-9.11.1.tgz#cdf32998a8646b0fda47e52cb9b89301d392766c" - integrity sha512-nGmPnBZCcErEDpN2FXLcbdsHKSyRr10y+u2HzzJeDj3IutZjlCEysEOdDzlfjgUssNWao6CeSIb3sRMFZpWRsA== +"@vueuse/router@9.12.0": + version "9.12.0" + resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-9.12.0.tgz#985759be6fb4a55609942b300e62c4023a47612e" + integrity sha512-I3TaWrsxEdANrgJjNEeRTbOMGS+uYjB5zHoyayhFgk1SY1ytDw51tgXr6n4n9fVqglorvhKdX5rFDvBAbeQ+Xw== dependencies: - "@vueuse/shared" "9.11.1" + "@vueuse/shared" "9.12.0" vue-demi "*" -"@vueuse/shared@9.11.1": - version "9.11.1" - resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.11.1.tgz#c76805c9d86da109b132529b4745d7d706106e7f" - integrity sha512-UTZYGAjT96hWn4buf4wstZbeheBVNcKPQuej6qpoSkjF1atdaeCD6kqm9uGL2waHfisSgH9mq0qCRiBOk5C/2w== +"@vueuse/shared@9.12.0": + version "9.12.0" + resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.12.0.tgz#e6597da80084cba8fc3d6545f4c2fa9817b80428" + integrity sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ== dependencies: vue-demi "*" From bb946c3cedb74831fb7ec416256474aa43e4c89e Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Sun, 29 Jan 2023 19:52:58 +0100 Subject: [PATCH 08/73] feat: dispose sound instances when removed from LRU cache Part-of: --- front/src/api/player.ts | 5 +++++ front/src/composables/audio/tracks.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/front/src/api/player.ts b/front/src/api/player.ts index 98b620759..3145e75ef 100644 --- a/front/src/api/player.ts +++ b/front/src/api/player.ts @@ -90,11 +90,16 @@ export class HTMLSound implements Sound { } preload () { + console.log('CALLING PRELOAD ON', this) this.#audio.load() } dispose () { this.audioNode.disconnect() + + // Cancel any request downloading the source + this.#audio.src = '' + this.#audio.load() } async play () { diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 09d2da72e..01e1965f3 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -19,7 +19,10 @@ const ALLOWED_PLAY_TYPES: (CanPlayTypeResult | undefined)[] = ['maybe', 'probabl const AUDIO_ELEMENT = document.createElement('audio') const soundPromises = new Map>() -const soundCache = useLRUCache({ max: 10 }) +const soundCache = useLRUCache({ + max: 10, + dispose: (sound) => sound.dispose() +}) export const fetchTrackSources = async (id: number): Promise => { const { uploads } = await axios.get(`tracks/${id}/`) @@ -124,6 +127,7 @@ export const useTracks = createGlobalState(() => { // Preload next track const { start: preload, stop: abortPreload } = useTimeoutFn(async (track: QueueTrack) => { const sound = await createSound(track) + sound.__track = track await sound.preload() }, 100, { immediate: false }) @@ -153,7 +157,7 @@ export const useTracks = createGlobalState(() => { await sound.play() } } - + const currentTrack = ref() // NOTE: We want to have it called only once, hence we're using createGlobalState From 05416be7d1798fd2e0136f4c89bf173dcc7a47aa Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Mon, 30 Jan 2023 13:54:03 +0100 Subject: [PATCH 09/73] feat: remove media cache Part-of: --- front/src/serviceWorker.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/front/src/serviceWorker.ts b/front/src/serviceWorker.ts index 9fe63365b..9534c50eb 100644 --- a/front/src/serviceWorker.ts +++ b/front/src/serviceWorker.ts @@ -1,7 +1,7 @@ /// import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching' -import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies' +import { NetworkFirst } from 'workbox-strategies' import { ExpirationPlugin } from 'workbox-expiration' import { registerRoute } from 'workbox-routing' import { clientsClaim } from 'workbox-core' @@ -39,13 +39,6 @@ registerRoute(({ url }) => { ] })) -// NOTE: Stale-While-Revalidate cache for album covers -// We're serving from cache if available and making a request -// in the background to update the cache for next request -registerRoute(({ url }) => { - return url.pathname.startsWith('/media') -}, new StaleWhileRevalidate()) - // Precache all assets and add routes for them // https://developer.chrome.com/docs/workbox/reference/workbox-precaching/#method-precacheAndRoute precacheAndRoute(self.__WB_MANIFEST) From ed8b71257d9b7256543d5d77e926c1391b2ba49f Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Mon, 30 Jan 2023 14:57:46 +0100 Subject: [PATCH 10/73] fix: audio playback pause if errored before played Part-of: --- front/src/api/player.ts | 16 ++++++++++++++-- front/src/composables/audio/tracks.ts | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/front/src/api/player.ts b/front/src/api/player.ts index 3145e75ef..bac347f53 100644 --- a/front/src/api/player.ts +++ b/front/src/api/player.ts @@ -78,6 +78,18 @@ export class HTMLSound implements Sound { } }) + useEventListener(this.#audio, 'waiting', () => { + console.log('>> AUDIO WAITING', this.__track?.title) + }) + + useEventListener(this.#audio, 'playing', () => { + console.log('>> AUDIO PLAYING', this.__track?.title) + }) + + useEventListener(this.#audio, 'stalled', () => { + console.log('>> AUDIO STALLED', this.__track?.title) + }) + useEventListener(this.#audio, 'loadeddata', () => { // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState this.isLoaded.value = this.#audio.readyState >= 2 @@ -90,7 +102,7 @@ export class HTMLSound implements Sound { } preload () { - console.log('CALLING PRELOAD ON', this) + console.log('CALLING PRELOAD ON', this.__track?.title) this.#audio.load() } @@ -119,7 +131,7 @@ export class HTMLSound implements Sound { } get playable () { - return this.#audio.src !== '' + return this.#audio.src !== '' || this.isErrored.value } get duration () { diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 01e1965f3..c3a44749c 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -147,7 +147,7 @@ export const useTracks = createGlobalState(() => { return } - console.log('CONNECTING NODE') + console.log('CONNECTING NODE', sound) sound.audioNode.disconnect() connectAudioSource(sound.audioNode) From 77e920672d5ce842f7e393d83fd0ea09f932b364 Mon Sep 17 00:00:00 2001 From: Kasper Seweryn Date: Tue, 31 Jan 2023 22:31:38 +0100 Subject: [PATCH 11/73] feat: optimize CPU and memory usage Part-of: --- front/package.json | 3 +- front/src/api/player.ts | 21 +- front/src/components/Queue.vue | 47 +- front/src/components/audio/Player.vue | 1 - front/src/components/audio/track/Row.vue | 7 +- front/src/components/audio/track/Table.vue | 13 - front/src/components/vui/list/VirtualList.vue | 36 +- front/src/composables/audio/player.ts | 9 - front/src/composables/audio/queue.ts | 7 +- front/src/composables/audio/tracks.ts | 2 +- front/src/locales/en_US.json | 3 +- front/src/main.ts | 7 + front/src/style/components/_queue.scss | 12 + front/vite.config.ts | 8 +- front/yarn.lock | 928 +++++++++++++++++- 15 files changed, 999 insertions(+), 105 deletions(-) diff --git a/front/package.json b/front/package.json index b3fda3f52..022d6f0ea 100644 --- a/front/package.json +++ b/front/package.json @@ -58,7 +58,7 @@ }, "devDependencies": { "@intlify/eslint-plugin-vue-i18n": "2.0.0", - "@intlify/vite-plugin-vue-i18n": "6.0.3", + "@intlify/unplugin-vue-i18n": "^0.8.1", "@types/diff": "5.0.2", "@types/dompurify": "2.4.0", "@types/howler": "2.2.7", @@ -73,6 +73,7 @@ "@vitejs/plugin-vue": "4.0.0", "@vitest/coverage-c8": "0.25.8", "@vue/compiler-sfc": "3.2.45", + "@vue/devtools": "^6.5.0", "@vue/eslint-config-standard": "8.0.1", "@vue/eslint-config-typescript": "11.0.2", "@vue/test-utils": "2.2.7", diff --git a/front/src/api/player.ts b/front/src/api/player.ts index bac347f53..abfb37663 100644 --- a/front/src/api/player.ts +++ b/front/src/api/player.ts @@ -11,8 +11,8 @@ export interface SoundSource { } export interface Sound { - preload(): void | Promise - dispose(): void + preload(): Promise + dispose(): Promise readonly audioNode: IAudioNode readonly isErrored: Ref @@ -23,11 +23,11 @@ export interface Sound { readonly buffered: number looping: boolean - pause(): void | Promise - play(): void | Promise + pause(): Promise + play(): Promise - seekTo(seconds: number): void | Promise - seekBy(seconds: number): void | Promise + seekTo(seconds: number): Promise + seekBy(seconds: number): Promise onSoundLoop: EventHookOn onSoundEnd: EventHookOn @@ -95,19 +95,22 @@ export class HTMLSound implements Sound { this.isLoaded.value = this.#audio.readyState >= 2 }) - useEventListener(this.#audio, 'error', () => { + useEventListener(this.#audio, 'error', (err) => { + console.error('>> AUDIO ERRORED', err, this.__track?.title) this.isErrored.value = true this.isLoaded.value = true }) } - preload () { + async preload () { + this.isErrored.value = false console.log('CALLING PRELOAD ON', this.__track?.title) this.#audio.load() } - dispose () { + async dispose () { this.audioNode.disconnect() + this.#audio.pause() // Cancel any request downloading the source this.#audio.src = '' diff --git a/front/src/components/Queue.vue b/front/src/components/Queue.vue index 0fa7379ec..2d2428137 100644 --- a/front/src/components/Queue.vue +++ b/front/src/components/Queue.vue @@ -2,7 +2,7 @@ import type { QueueItemSource } from '~/types' import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset, useStorage } from '@vueuse/core' -import { nextTick, ref, computed, watchEffect, onMounted } from 'vue' +import { nextTick, ref, computed, watchEffect, watch, defineAsyncComponent } from 'vue' import { useFocusTrap } from '@vueuse/integrations/useFocusTrap' import { useRouter } from 'vue-router' import { useI18n } from 'vue-i18n' @@ -17,11 +17,12 @@ import time from '~/utils/time' import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue' import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue' import PlayerControls from '~/components/audio/PlayerControls.vue' -import MilkDrop from '~/components/audio/visualizer/MilkDrop.vue' import VirtualList from '~/components/vui/list/VirtualList.vue' import QueueItem from '~/components/QueueItem.vue' +const MilkDrop = defineAsyncComponent(() => import('~/components/audio/visualizer/MilkDrop.vue')) + const { isPlaying, currentTime, @@ -41,7 +42,7 @@ const { dequeue, playTrack, reorder, - endsIn: timeLeft, + endsIn, clear } = useQueue() @@ -92,16 +93,6 @@ const scrollToCurrent = (behavior: ScrollBehavior = 'smooth') => { watchDebounced(currentTrack, () => scrollToCurrent(), { debounce: 100 }) -const scrollLoop = () => { - const visible = [...(list.value?.scroller.$_views.values() ?? [])].map(item => item.nr.index) - if (!visible.includes(currentIndex.value)) { - list.value?.scrollToIndex(currentIndex.value) - requestAnimationFrame(scrollLoop) - } -} - -onMounted(scrollLoop) - whenever( () => queue.value.length === 0, () => store.commit('ui/queueFocused', null), @@ -117,6 +108,13 @@ const touchProgress = (event: MouseEvent) => { seekTo(time) } +const animated = ref(false) +watch(currentTrack, async track => { + animated.value = false + await nextTick() + animated.value = true +}) + const play = async (index: number) => { isPlaying.value = true return playTrack(index) @@ -357,8 +355,13 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART) :style="{ 'transform': `translateX(${bufferProgress - 100}%)` }" />
@@ -415,12 +418,11 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
{{ $t('components.Queue.meta.queuePosition', {index: currentIndex +1, length: queue.length}) }} - + + {{ $t('components.Queue.meta.end') }} + + {{ endsIn }} +
@@ -433,8 +435,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART) :component="QueueItem" :size="50" @reorder="reorderTracks" - @visible="scrollToCurrent('auto')" - @hidden="scrollLoop" + @visible="list.scrollToIndex(currentIndex, 'center')" >