From f51007b807135ccbd479b30151fd3abf9194b374 Mon Sep 17 00:00:00 2001 From: wvffle Date: Fri, 28 Oct 2022 12:29:58 +0000 Subject: [PATCH] Fetch only new queue tracks from indexedDB --- front/src/composables/audio/player.ts | 15 +++-- front/src/composables/audio/queue.ts | 64 +++++++++++++------ front/src/composables/audio/tracks.ts | 13 ++-- front/src/composables/audio/usePlayOptions.ts | 16 +++-- 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/front/src/composables/audio/player.ts b/front/src/composables/audio/player.ts index 1964fd821..c9961c5fd 100644 --- a/front/src/composables/audio/player.ts +++ b/front/src/composables/audio/player.ts @@ -1,6 +1,6 @@ import { createGlobalState, tryOnMounted, useIntervalFn, useRafFn, useStorage, useTimeoutFn, whenever } from '@vueuse/core' +import { computed, nextTick, ref, watch, watchEffect, type Ref } from 'vue' import { useTracks } from '~/composables/audio/tracks' -import { computed, ref, watch, watchEffect, type Ref } from 'vue' import { setGain } from './audio-api' import { useQueue, currentIndex, currentTrack } from './queue' @@ -43,6 +43,12 @@ export const usePlayer = createGlobalState(() => { sound.pause() }) + const stop = async () => { + isPlaying.value = false + seekTo(0) + return nextTick() + } + // Create first track when we initalize the page // NOTE: We want to have it called only once, hence we're using createGlobalState const initializeFirstTrack = createGlobalState(() => tryOnMounted(() => { @@ -195,13 +201,14 @@ export const usePlayer = createGlobalState(() => { return sound.isErrored.value }) - const { start, stop } = useTimeoutFn(() => playNext(), 3000, { immediate: false }) - watch(currentIndex, stop) - whenever(errored, start) + const { start: startErrorTimeout, stop: stopErrorTimeout } = useTimeoutFn(() => playNext(), 3000, { immediate: false }) + watch(currentIndex, stopErrorTimeout) + whenever(errored, startErrorTimeout) return { initializeFirstTrack, isPlaying, + stop, volume, mute, looping, diff --git a/front/src/composables/audio/queue.ts b/front/src/composables/audio/queue.ts index 827f1a588..69da7e287 100644 --- a/front/src/composables/audio/queue.ts +++ b/front/src/composables/audio/queue.ts @@ -1,12 +1,12 @@ import type { Track, Upload } from '~/types' -import { computedAsync, createGlobalState, useNow, useStorage, useTimeAgo } from '@vueuse/core' -import { shuffle as shuffleArray, sum, uniq } from 'lodash-es' +import { createGlobalState, useNow, useStorage, useTimeAgo, whenever } from '@vueuse/core' +import { shuffle as shuffleArray, sum } from 'lodash-es' +import { computed, ref, shallowReactive, watchEffect } from 'vue' import { getMany, setMany } from 'idb-keyval' -import { computed, watchEffect } from 'vue' import { useClamp } from '@vueuse/math' -import { looping, LoopingMode, isPlaying } from '~/composables/audio/player' +import { looping, LoopingMode, isPlaying, usePlayer } from '~/composables/audio/player' import { useStore } from '~/store' import axios from 'axios' @@ -44,22 +44,45 @@ const shuffledIds = useStorage('queue:tracks:shuffled', [] as number[]) const isShuffled = computed(() => shuffledIds.value.length !== 0) -const tracksById = computedAsync(async () => { - const trackObjects = await getMany(uniq(tracks.value)) - return trackObjects.reduce((acc, track) => { - acc[track.id] = track - return acc - }, {}) as Record -}, {}) +const tracksById = shallowReactive(new Map()) +const fetchingTracks = ref(false) +watchEffect(async () => { + if (fetchingTracks.value) return -const queue = computed(() => { - const indexedTracks = tracksById.value + const allTracks = new Set(tracks.value) + const addedIds = new Set(allTracks) - if (isShuffled.value) { - return shuffledIds.value.map(id => indexedTracks[id]).filter(i => i) + for (const id of tracksById.keys()) { + if (allTracks.has(id)) { + // Track in queue, so remove it from the new ids set + addedIds.delete(id) + } else { + // Track removed from queue, so remove it from the object + tracksById.delete(id) + } } - return tracks.value.map(id => indexedTracks[id]).filter(i => i) + if (addedIds.size > 0) { + fetchingTracks.value = true + try { + const trackInfos: QueueTrack[] = await getMany([...addedIds]) + for (const track of trackInfos) { + tracksById.set(track.id, track) + } + } catch (error) { + console.error(error) + } finally { + fetchingTracks.value = false + } + } +}) + +const queue = computed(() => { + const ids = isShuffled.value + ? shuffledIds.value + : tracks.value + + return ids.map(id => tracksById.get(id)).filter((i): i is QueueTrack => !!i) }) // Current Index @@ -235,7 +258,11 @@ export const useQueue = createGlobalState(() => { })) // Clear + const clearRadio = ref(false) const clear = async () => { + const { stop } = usePlayer() + await stop() + clearRadio.value = true tracks.value.length = 0 } @@ -249,8 +276,9 @@ export const useQueue = createGlobalState(() => { } }) - watchEffect(() => { - if (tracks.value.length === 0) { + whenever(clearRadio, () => { + clearRadio.value = false + if (store.state.radios.running) { return store.dispatch('radios/stop') } }) diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 95b6d0383..cd0831831 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -1,13 +1,13 @@ import type { QueueTrack, QueueTrackSource } from '~/composables/audio/queue' import type { Sound } from '~/api/player' -import { soundImplementation } from '~/api/player' -import { createGlobalState, syncRef } from '@vueuse/core' -import { computed, ref, watch } from 'vue' +import { createGlobalState, syncRef, whenever } from '@vueuse/core' +import { computed, ref } from 'vue' -import { useQueue } from '~/composables/audio/queue' import { connectAudioSource } from '~/composables/audio/audio-api' import { usePlayer } from '~/composables/audio/player' +import { useQueue } from '~/composables/audio/queue' +import { soundImplementation } from '~/api/player' import useLRUCache from '~/composables/useLRUCache' import store from '~/store' @@ -112,8 +112,9 @@ export const useTracks = createGlobalState(() => { // NOTE: We want to have it called only once, hence we're using createGlobalState const initialize = createGlobalState(() => { - const { currentTrack: track, currentIndex } = useQueue() - watch(currentIndex, (index) => createTrack(index)) + const { currentIndex, currentTrack: track } = useQueue() + + whenever(track, () => createTrack(currentIndex.value)) syncRef(track, currentTrack, { direction: 'ltr' }) diff --git a/front/src/composables/audio/usePlayOptions.ts b/front/src/composables/audio/usePlayOptions.ts index 913b7820f..f1981c058 100644 --- a/front/src/composables/audio/usePlayOptions.ts +++ b/front/src/composables/audio/usePlayOptions.ts @@ -164,21 +164,25 @@ export default (props: PlayOptionsProps) => { const tracksToPlay = await getPlayableTracks() await addToQueue(...tracksToPlay) - const trackIndex = props.tracks?.findIndex(track => track.id === props.track?.id && track.position === props.track?.position) ?? 0 - await playTrack(trackIndex) + if (props.track && props.tracks?.length) { + const trackIndex = props.tracks?.findIndex(track => track.id === props.track?.id && track.position === props.track?.position) ?? 0 + await playTrack(trackIndex) + isPlaying.value = true + } else { + await playTrack(0, true) + isPlaying.value = true + } - isPlaying.value = true - playTrack(0, true) addMessage(tracksToPlay) } - const activateTrack = (track: Track, index: number) => { + const activateTrack = async (track: Track, index: number) => { // TODO (wvffle): Check if position checking did not break anything if (track.id === currentTrack.value?.id && track.position === currentTrack.value?.position) { isPlaying.value = true } - replacePlay() + return replacePlay() } return {