diff --git a/.gitpod.yml b/.gitpod.yml index e27625a4c..f06e4dc6a 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -84,7 +84,7 @@ ports: vscode: extensions: - - lukashass.volar + - Vue.volar - ms-python.python - ms-toolsai.jupyter - ms-toolsai.jupyter-keymap diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..26d404c11 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "Vue.volar" + ] +} diff --git a/front/package.json b/front/package.json index 002f87de9..c8ddff07d 100644 --- a/front/package.json +++ b/front/package.json @@ -36,6 +36,7 @@ "idb-keyval": "^6.2.0", "js-logger": "1.6.1", "lodash-es": "4.17.21", + "lru-cache": "^7.14.0", "moment": "2.29.4", "qs": "6.11.0", "showdown": "2.1.0", diff --git a/front/src/composables/audio/player.ts b/front/src/composables/audio/player.ts index e6ef9e76d..87a99c12f 100644 --- a/front/src/composables/audio/player.ts +++ b/front/src/composables/audio/player.ts @@ -8,12 +8,11 @@ import store from '~/store' import axios from 'axios' export const isPlaying = ref(false) - -watch(isPlaying, (playing) => { +watchEffect(() => { const sound = currentSound.value if (!sound) return - if (playing) { + if (isPlaying.value) { sound.play() return } diff --git a/front/src/composables/audio/queue.ts b/front/src/composables/audio/queue.ts index fc22edffd..6f851e198 100644 --- a/front/src/composables/audio/queue.ts +++ b/front/src/composables/audio/queue.ts @@ -85,12 +85,19 @@ const createQueueTrack = async (track: Track): Promise => { } // Adding tracks -export const enqueue = async (...newTracks: Track[]) => { +export const enqueueAt = async (index: number, ...newTracks: Track[]) => { const queueTracks = await Promise.all(newTracks.map(createQueueTrack)) await setMany(queueTracks.map(track => [track.id, track])) const ids = queueTracks.map(track => track.id) - tracks.value.push(...ids) + + if (index >= tracks.value.length) { + // we simply push to the end + tracks.value.push(...ids) + } else { + // we insert the track at given position + tracks.value.splice(index, 0, ...ids) + } // Shuffle new tracks if (isShuffled.value) { @@ -98,6 +105,10 @@ export const enqueue = async (...newTracks: Track[]) => { } } +export const enqueue = async (...newTracks: Track[]) => { + return enqueueAt(tracks.value.length, ...newTracks) +} + // Removing tracks export const dequeue = async (index: number) => { if (currentIndex.value === index) { @@ -131,7 +142,6 @@ export const playTrack = async (trackIndex: number, force = false) => { } currentIndex.value = trackIndex - if (isPlaying.value) currentSound.value?.play() } // Previous track @@ -198,6 +208,15 @@ export const shuffle = () => { shuffledIds.value = shuffleArray(tracks.value) } +export const reshuffleUpcomingTracks = () => { + // TODO: Test if needed to add 1 to currentIndex + const listenedTracks = shuffledIds.value.slice(0, currentIndex.value) + const upcomingTracks = shuffledIds.value.slice(currentIndex.value) + + listenedTracks.push(...shuffleArray(upcomingTracks)) + shuffledIds.value = listenedTracks +} + // Ends in const now = useNow() export const endsIn = useTimeAgo(computed(() => { diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts index 32b7fc378..11c561ba2 100644 --- a/front/src/composables/audio/tracks.ts +++ b/front/src/composables/audio/tracks.ts @@ -2,18 +2,20 @@ import type { QueueTrack, QueueTrackSource } from '~/composables/audio/queue' import type { Sound } from '~/api/player' import { soundImplementation } from '~/api/player' -import { computed, shallowReactive } from 'vue' +import { computed, watchEffect } from 'vue' import { playNext, queue, currentTrack, currentIndex } from '~/composables/audio/queue' import { connectAudioSource } from '~/composables/audio/audio-api' import { isPlaying } from '~/composables/audio/player' + +import useLRUCache from '~/composables/useLRUCache' import store from '~/store' const ALLOWED_PLAY_TYPES: (CanPlayTypeResult | undefined)[] = ['maybe', 'probably'] const AUDIO_ELEMENT = document.createElement('audio') const soundPromises = new Map>() -const soundCache = shallowReactive(new Map()) +const soundCache = useLRUCache({ max: 10 }) const getTrackSources = (track: QueueTrack): QueueTrackSource[] => { const sources: QueueTrackSource[] = track.sources @@ -60,7 +62,6 @@ export const createSound = async (track: QueueTrack): Promise => { // NOTE: We push it to the end of the job queue setTimeout(playNext, 0) }) - soundCache.set(track.id, sound) soundPromises.delete(track.id) return sound @@ -74,7 +75,7 @@ export const createSound = async (track: QueueTrack): Promise => { // Create track from queue export const createTrack = async (index: number) => { if (queue.value.length <= index || index === -1) return - console.log('LOADING TRACK') + console.log('LOADING TRACK', index) const track = queue.value[index] if (!soundPromises.has(track.id) && !soundCache.has(track.id)) { @@ -88,7 +89,7 @@ export const createTrack = async (index: number) => { sound.audioNode.disconnect() connectAudioSource(sound.audioNode) - if (isPlaying.value) { + if (isPlaying.value && index === currentIndex.value) { await sound.play() } @@ -101,4 +102,6 @@ export const createTrack = async (index: number) => { } } +watchEffect(async () => createTrack(currentIndex.value)) + export const currentSound = computed(() => soundCache.get(currentTrack.value?.id ?? -1)) diff --git a/front/src/composables/audio/usePlayOptions.ts b/front/src/composables/audio/usePlayOptions.ts index 018e53db3..5142249e3 100644 --- a/front/src/composables/audio/usePlayOptions.ts +++ b/front/src/composables/audio/usePlayOptions.ts @@ -9,7 +9,7 @@ import { useStore } from '~/store' import axios from 'axios' import jQuery from 'jquery' -import { enqueue as addToQueue, currentTrack } from '~/composables/audio/queue' +import { enqueue as addToQueue, currentTrack, playNext, currentIndex, enqueueAt, queue } from '~/composables/audio/queue' import { isPlaying } from '~/composables/audio/player' export interface PlayOptionsProps { @@ -143,11 +143,11 @@ export default (props: PlayOptionsProps) => { const tracks = await getPlayableTracks() - const wasEmpty = store.state.queue.tracks.length === 0 - await store.dispatch('queue/appendMany', { tracks, index: store.state.queue.currentIndex + 1 }) + const wasEmpty = queue.value.length === 0 + await enqueueAt(currentIndex.value + 1, ...tracks) if (next && !wasEmpty) { - await store.dispatch('queue/next') + await playNext() isPlaying.value = true } diff --git a/front/src/composables/useLRUCache.ts b/front/src/composables/useLRUCache.ts new file mode 100644 index 000000000..14c4de962 --- /dev/null +++ b/front/src/composables/useLRUCache.ts @@ -0,0 +1,11 @@ +import LRU from 'lru-cache' +import { reactive } from 'vue' + +export default (options: LRU.Options) => { + const cache = new LRU(options) + + // @ts-expect-error keyMap is used internally so it is not defined in the types + cache.keyMap = reactive(cache.keyMap) + + return cache +} diff --git a/front/yarn.lock b/front/yarn.lock index e4b5b351a..b97e176ca 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -4120,6 +4120,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.0.tgz#21be64954a4680e303a09e9468f880b98a0b3c7f" + integrity sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ== + magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"