diff --git a/front/package.json b/front/package.json
index 0b7a0cf88..3d719468c 100644
--- a/front/package.json
+++ b/front/package.json
@@ -22,9 +22,10 @@
"@sentry/tracing": "7.17.2",
"@sentry/vue": "7.17.2",
"@vue/runtime-core": "3.2.41",
- "@vueuse/core": "9.3.0",
- "@vueuse/integrations": "9.3.0",
- "@vueuse/router": "9.3.0",
+ "@vueuse/core": "9.3.1",
+ "@vueuse/integrations": "9.3.1",
+ "@vueuse/math": "9.3.1",
+ "@vueuse/router": "9.3.1",
"axios": "0.27.2",
"axios-auth-refresh": "3.3.4",
"diff": "5.1.0",
diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue
index 776e158b1..c2d9a324f 100644
--- a/front/src/components/audio/Player.vue
+++ b/front/src/components/audio/Player.vue
@@ -16,13 +16,24 @@ import {
loading as isLoadingAudio
} from '~/composables/audio/player'
+import {
+ hasPrevious,
+ playPrevious,
+ hasNext,
+ playNext,
+ tracks,
+ currentIndex,
+ currentTrack,
+ isShuffling,
+ shuffle
+} from '~/composables/audio/queue'
+
import { useMouse, useWindowSize } from '@vueuse/core'
import { useGettext } from 'vue3-gettext'
import { computed, ref } from 'vue'
import { useStore } from '~/store'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
-import useQueue from '~/composables/audio/useQueue'
import time from '~/utils/time'
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
@@ -36,19 +47,6 @@ const toggleMobilePlayer = () => {
store.commit('ui/queueFocused', ['queue', 'player'].includes(store.state.ui.queueFocused as string) ? null : 'player')
}
-const {
- isShuffling,
- shuffle,
- previous,
- isEmpty: queueIsEmpty,
- hasNext,
- hasPrevious,
- currentTrack,
- currentIndex,
- tracks,
- next
-} = useQueue()
-
// Key binds
onKeyboardShortcut('e', toggleMobilePlayer)
onKeyboardShortcut('p', () => { isPlaying.value = !isPlaying.value })
@@ -67,8 +65,8 @@ onKeyboardShortcut(['shift', 'right'], () => seekBy(30), true)
onKeyboardShortcut('left', () => seekBy(-5), true)
onKeyboardShortcut(['shift', 'left'], () => seekBy(-30), true)
-onKeyboardShortcut(['ctrl', 'shift', 'left'], previous, true)
-onKeyboardShortcut(['ctrl', 'shift', 'right'], next, true)
+onKeyboardShortcut(['ctrl', 'shift', 'left'], playPrevious, true)
+onKeyboardShortcut(['ctrl', 'shift', 'right'], playNext, true)
const labels = computed(() => ({
audioPlayer: $pgettext('Sidebar/Player/Hidden text', 'Media player'),
@@ -188,10 +186,10 @@ const currentTimeFormatted = computed(() => time.parse(Math.round(currentTime.va
diff --git a/front/src/composables/audio/queue.ts b/front/src/composables/audio/queue.ts
new file mode 100644
index 000000000..2b76841e3
--- /dev/null
+++ b/front/src/composables/audio/queue.ts
@@ -0,0 +1,59 @@
+import type { Track } from '~/types'
+
+import { isPlaying, looping, LoopingMode } from '~/composables/audio/player'
+import { currentSound } from '~/composables/audio/tracks'
+import { toReactive, useStorage } from '@vueuse/core'
+import { useClamp } from '@vueuse/math'
+import { computed, ref } from 'vue'
+
+// Queue
+export const tracks = toReactive(useStorage('queue:tracks', [] as Track[]))
+
+// Current Index
+export const currentIndex = useClamp(useStorage('queue:index', 0), 0, () => tracks.length)
+export const currentTrack = computed(() => tracks[currentIndex.value])
+
+// Play track
+export const playTrack = async (trackIndex: number, force = false) => {
+ if (isPlaying.value) currentSound.value?.pause()
+
+ if (force && currentIndex.value === trackIndex) {
+ currentSound.value?.seekTo(0)
+ if (isPlaying.value) currentSound.value?.play()
+ return
+ }
+
+ currentIndex.value = trackIndex
+ if (isPlaying.value) currentSound.value?.play()
+}
+
+// Previous track
+export const hasPrevious = computed(() => looping.value === LoopingMode.LoopQueue || currentIndex.value !== 0)
+export const playPrevious = async () => {
+ // Loop entire queue / change track to the next one
+ if (looping.value === LoopingMode.LoopQueue && currentIndex.value === 0) {
+ // Loop track programmatically if it is the only track in the queue
+ if (tracks.length === 1) return playTrack(currentIndex.value, true)
+ return playTrack(tracks.length - 1)
+ }
+
+ return playTrack(currentIndex.value - 1)
+}
+
+// Next track
+export const hasNext = computed(() => looping.value === LoopingMode.LoopQueue || currentIndex.value !== tracks.length - 1)
+export const playNext = async () => {
+ // Loop entire queue / change track to the next one
+ if (looping.value === LoopingMode.LoopQueue && currentIndex.value === tracks.length - 1) {
+ // Loop track programmatically if it is the only track in the queue
+ if (tracks.length === 1) return playTrack(currentIndex.value, true)
+ return playTrack(0)
+ }
+
+ return playTrack(currentIndex.value + 1)
+}
+
+// Shuffle
+export const isShuffling = ref(false)
+// TODO: Shuffle
+export const shuffle = () => undefined
diff --git a/front/src/composables/audio/tracks.ts b/front/src/composables/audio/tracks.ts
index fc5d62ac7..49247f371 100644
--- a/front/src/composables/audio/tracks.ts
+++ b/front/src/composables/audio/tracks.ts
@@ -5,27 +5,14 @@ import { connectAudioSource } from '~/composables/audio/audio-api'
import { isPlaying } from '~/composables/audio/player'
import { soundImplementation } from '~/api/player'
import { computed, shallowReactive } from 'vue'
-
-import useQueue from '~/composables/audio/useQueue'
+import { playNext, tracks, currentIndex } from '~/composables/audio/queue'
import store from '~/store'
import axios from 'axios'
-const createOnEndHandler = (sound: Sound) => () => {
- console.log('TRACK ENDED, PLAYING NEXT')
- createTrack(currentIndex.value + 1)
-
- // NOTE: We push it to the end of the job queue
- setTimeout(() => {
- store.dispatch('queue/next')
- }, 0)
-}
-
const ALLOWED_PLAY_TYPES: (CanPlayTypeResult | undefined)[] = ['maybe', 'probably']
const AUDIO_ELEMENT = document.createElement('audio')
-const { tracks, currentIndex } = useQueue()
-
const soundPromises = new Map>()
const soundCache = shallowReactive(new Map())
@@ -78,7 +65,13 @@ export const createSound = async (track: Track): Promise => {
const SoundImplementation = soundImplementation.value
const sound = new SoundImplementation(sources)
- sound.onSoundEnd(createOnEndHandler(sound))
+ sound.onSoundEnd(() => {
+ console.log('TRACK ENDED, PLAYING NEXT')
+ createTrack(currentIndex.value + 1)
+
+ // NOTE: We push it to the end of the job queue
+ setTimeout(playNext, 0)
+ })
soundCache.set(track.id, sound)
soundPromises.delete(track.id)
@@ -92,10 +85,10 @@ export const createSound = async (track: Track): Promise => {
// Create track from queue
export const createTrack = async (index: number) => {
- if (tracks.value.length <= index || index === -1) return
+ if (tracks.length <= index || index === -1) return
console.log('LOADING TRACK')
- const track = tracks.value[index]
+ const track = tracks[index]
if (!soundPromises.has(track.id) && !soundCache.has(track.id)) {
// TODO (wvffle): Resolve race condition - is it still here after adding soundPromises?
console.log('NO TRACK IN CACHE, CREATING')
@@ -112,12 +105,12 @@ export const createTrack = async (index: number) => {
}
// NOTE: Preload next track
- if (index === currentIndex.value && index + 1 < tracks.value.length) {
+ if (index === currentIndex.value && index + 1 < tracks.length) {
setTimeout(async () => {
- const sound = await createSound(tracks.value[index + 1])
+ const sound = await createSound(tracks[index + 1])
await sound.preload()
}, 100)
}
}
-export const currentSound = computed(() => soundCache.get(tracks.value[currentIndex.value]?.id ?? -1))
+export const currentSound = computed(() => soundCache.get(tracks[currentIndex.value]?.id ?? -1))
diff --git a/front/yarn.lock b/front/yarn.lock
index f210544a8..e59af5bdf 100644
--- a/front/yarn.lock
+++ b/front/yarn.lock
@@ -1516,10 +1516,10 @@
resolved "https://registry.yarnpkg.com/@earltp/vue-virtual-scroller/-/vue-virtual-scroller-1.0.1.tgz#75116ef9b091457a654d92ff0688e991b3cd9e8a"
integrity sha512-7UsmP2JALnkfWlheuWRDywuBUTLJcVPE86X5ogA3djUmYFybE6qximgQ7OgyJnrKLteWR7+1Cp0GUXHhdDKaDQ==
-"@types/web-bluetooth@^0.0.15":
- version "0.0.15"
- resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz#d60330046a6ed8a13b4a53df3813c44942ebdf72"
- integrity sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA==
+"@types/web-bluetooth@^0.0.16":
+ version "0.0.16"
+ resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
+ integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
"@typescript-eslint/eslint-plugin@5.41.0", "@typescript-eslint/eslint-plugin@^5.0.0":
version "5.41.0"
@@ -1888,42 +1888,50 @@
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz#4a61dbd29783d01ddab504276dcf0c2b6988654f"
integrity sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg==
-"@vueuse/core@9.3.0":
- version "9.3.0"
- resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.3.0.tgz#74d855bd19cb5eadd2edb30c871918fac881e8b8"
- integrity sha512-64Rna8IQDWpdrJxgitDg7yv1yTp41ZmvV8zlLEylK4QQLWAhz1OFGZDPZ8bU4lwcGgbEJ2sGi2jrdNh4LttUSQ==
+"@vueuse/core@9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.3.1.tgz#47bc65da9c705fef7b3beca4af764491f0479647"
+ integrity sha512-xriyD+v3D2ObH/UtnkEl+1sbcLBVHNaZaLi/rqoNEe/B92hggDEFQIGXoQUjdRzYOjASHSezf9uCDtmd7LeWyA==
dependencies:
- "@types/web-bluetooth" "^0.0.15"
- "@vueuse/metadata" "9.3.0"
- "@vueuse/shared" "9.3.0"
+ "@types/web-bluetooth" "^0.0.16"
+ "@vueuse/metadata" "9.3.1"
+ "@vueuse/shared" "9.3.1"
vue-demi "*"
-"@vueuse/integrations@9.3.0":
- version "9.3.0"
- resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.3.0.tgz#d08f0b96335746242e0a979d58964f374eed303a"
- integrity sha512-KkJpC97VioZUpSw7rvgnqoLgTztLlLLGdYp6WQKn69cJiItsJVSRZrmI+X9YVxPBzuLvRymYZfp0RMyISVFHTw==
+"@vueuse/integrations@9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.3.1.tgz#c339e1a862d328629eb3daefd7c38cccd3d18e91"
+ integrity sha512-ydVHxJpLZrO9WbRXs1mNBbgEby8ERkiQumR2LebYVtY/1AFEn+mQ8MkL9pvcXwd9mrQSms+wzssu760n7DaDAw==
dependencies:
- "@vueuse/core" "9.3.0"
- "@vueuse/shared" "9.3.0"
+ "@vueuse/core" "9.3.1"
+ "@vueuse/shared" "9.3.1"
vue-demi "*"
-"@vueuse/metadata@9.3.0":
- version "9.3.0"
- resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.3.0.tgz#c107fe77a577e1f221536cd1b291039c0c7c4bce"
- integrity sha512-GnnfjbzIPJIh9ngL9s9oGU1+Hx/h5/KFqTfJykzh/1xjaHkedV9g0MASpdmPZIP+ynNhKAcEfA6g5i8KXwtoMA==
-
-"@vueuse/router@9.3.0":
- version "9.3.0"
- resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-9.3.0.tgz#731b2e88718f8ca13202e527864686a122ccc68f"
- integrity sha512-UFN2MFciprH21oYsAgNHeDJ4Bd86HpRm9gximSN8j6h4fc2aa62fvfhprfHqdTxYAcgcGkMwcc9TO75jOvr8gg==
+"@vueuse/math@9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/math/-/math-9.3.1.tgz#547c6932ff0a7433e7a10bd6879a29b8adf07542"
+ integrity sha512-6ojNHKlhs57NViEzTOQMAOZ0QpBxRalx4kFgIV5M+5DHAwi2AAwZpYwtI+PKKHrIvfdB3fx+pkz7pcjnh9GBDQ==
dependencies:
- "@vueuse/shared" "9.3.0"
+ "@vueuse/shared" "9.3.1"
vue-demi "*"
-"@vueuse/shared@9.3.0":
- version "9.3.0"
- resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.3.0.tgz#40fc138ba4e379c894075830aa2e15404aaa8a5b"
- integrity sha512-caGUWLY0DpPC6l31KxeUy6vPVNA0yKxx81jFYLoMpyP6cF84FG5Dkf69DfSUqL57wX8JcUkJDMnQaQIZPWFEQQ==
+"@vueuse/metadata@9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.3.1.tgz#4e04d76df1e16f1aede28590c8b7a113ea3d3a7e"
+ integrity sha512-G1BPhtx3OHaL/y4OZBofh6Xt02G1VA9PuOO8nac9sTKMkMqfyez5VfkF3D9GUjSRNO7cVWyH4rceeGXfr2wdMg==
+
+"@vueuse/router@9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-9.3.1.tgz#67d90df3b0d1bdca23eaf69b1a40fc72283a4658"
+ integrity sha512-iyxyGNAeK8y4xaKjidnTI2H6eFiCiHypjoKPDEE8kdfESoxjegigp/DYfrbFt3IIeAHXPvBMq11m9IFuZ+LyLw==
+ dependencies:
+ "@vueuse/shared" "9.3.1"
+ vue-demi "*"
+
+"@vueuse/shared@9.3.1":
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.3.1.tgz#78c6cf41d8b75f05460e18a80819bf8164b597a5"
+ integrity sha512-YFu3qcnVeu0S2L4XdQJtBpDcjz6xwqHZtTv/XRhu66/yge1XVhxskUcc7VZbX52xF9A34V6KCfwncP9YDqYFiw==
dependencies:
vue-demi "*"