WIP: Rewrite queue
This commit is contained in:
parent
3bf7dd98a2
commit
c08e1fad94
|
@ -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",
|
||||
|
|
|
@ -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
|
|||
<div class="meta">
|
||||
<router-link
|
||||
class="discrete link"
|
||||
:to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}"
|
||||
:to="{name: 'library.artists.detail', params: {id: currentTrack.artist?.id }}"
|
||||
@click.stop.prevent=""
|
||||
>
|
||||
{{ currentTrack.artist.name }}
|
||||
{{ currentTrack.artist?.name }}
|
||||
</router-link>
|
||||
<template v-if="currentTrack.album">
|
||||
/
|
||||
|
@ -231,7 +229,7 @@ const currentTimeFormatted = computed(() => time.parse(Math.round(currentTime.va
|
|||
{{ currentTrack.title }}
|
||||
</strong>
|
||||
<div class="meta">
|
||||
{{ currentTrack.artist.name }}<template v-if="currentTrack.album">
|
||||
{{ currentTrack.artist?.name }}<template v-if="currentTrack.album">
|
||||
/ {{ currentTrack.album.title }}
|
||||
</template>
|
||||
</div>
|
||||
|
@ -264,7 +262,7 @@ const currentTimeFormatted = computed(() => time.parse(Math.round(currentTime.va
|
|||
:aria-label="labels.previous"
|
||||
:disabled="!hasPrevious"
|
||||
class="circular button control tablet-and-up"
|
||||
@click.prevent.stop="$store.dispatch('queue/previous')"
|
||||
@click.prevent.stop="playPrevious"
|
||||
>
|
||||
<i :class="['ui', 'large', {'disabled': !hasPrevious}, 'backward step', 'icon']" />
|
||||
</button>
|
||||
|
@ -291,7 +289,7 @@ const currentTimeFormatted = computed(() => time.parse(Math.round(currentTime.va
|
|||
:aria-label="labels.next"
|
||||
:disabled="!hasNext"
|
||||
class="circular button control"
|
||||
@click.prevent.stop="$store.dispatch('queue/next')"
|
||||
@click.prevent.stop="playNext"
|
||||
>
|
||||
<i :class="['ui', 'large', {'disabled': !hasNext}, 'forward step', 'icon']" />
|
||||
</button>
|
||||
|
@ -335,7 +333,7 @@ const currentTimeFormatted = computed(() => time.parse(Math.round(currentTime.va
|
|||
|
||||
<button
|
||||
class="circular control button"
|
||||
:disabled="queueIsEmpty || null"
|
||||
:disabled="tracks.length === 0"
|
||||
:title="labels.shuffle"
|
||||
:aria-label="labels.shuffle"
|
||||
@click.prevent.stop="shuffle()"
|
||||
|
@ -346,7 +344,7 @@ const currentTimeFormatted = computed(() => time.parse(Math.round(currentTime.va
|
|||
/>
|
||||
<i
|
||||
v-else
|
||||
:class="['ui', 'random', {'disabled': queueIsEmpty}, 'icon']"
|
||||
:class="['ui', 'random', {'disabled': tracks.length === 0}, 'icon']"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -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
|
|
@ -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<number, Promise<Sound>>()
|
||||
const soundCache = shallowReactive(new Map<number, Sound>())
|
||||
|
||||
|
@ -78,7 +65,13 @@ export const createSound = async (track: Track): Promise<Sound> => {
|
|||
|
||||
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<Sound> => {
|
|||
|
||||
// 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))
|
||||
|
|
|
@ -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 "*"
|
||||
|
||||
|
|
Loading…
Reference in New Issue