From 97e7049333313adf816479bb78e2fcce3484b969 Mon Sep 17 00:00:00 2001 From: wvffle Date: Thu, 28 Jul 2022 23:45:53 +0000 Subject: [PATCH] Rewrite player logic This commit will bring: - Gapless play! (Fix #739) - Chunked queue shuffling - we play first track after first 50 queue items are shuffled, then we shuffle chunks of 50 queue items with each new animation frame. - We can now restore original queue order after shuffling! (Part of #1506) - Preloading whole tracks into LRU cache (Should fix #1812) - Preloading multiple tracks at once --- docker/nginx/conf.dev | 4 +- front/package.json | 2 + front/src/components/Queue.vue | 61 ++- .../src/components/audio/ChannelEntryCard.vue | 4 +- front/src/components/audio/PlayButton.vue | 2 +- front/src/components/audio/Player.vue | 110 +++--- front/src/components/audio/VolumeControl.vue | 16 +- .../components/audio/podcast/MobileRow.vue | 4 +- .../src/components/audio/track/MobileRow.vue | 4 +- front/src/components/audio/track/Row.vue | 4 +- front/src/composables/audio/usePlayOptions.ts | 33 +- front/src/composables/audio/usePlayer.ts | 265 ------------- front/src/composables/audio/useQueue.ts | 110 ++---- front/src/composables/audio/useSound.ts | 160 -------- front/src/composables/audio/useSoundCache.ts | 38 -- .../src/composables/audio/useTrackSources.ts | 18 +- .../composables/audio/useWebAudioPlayer.ts | 354 ++++++++++++++++++ front/src/init/mediaSession.ts | 11 +- front/src/init/sentry.ts | 20 +- front/src/main.ts | 1 + front/src/store/index.ts | 68 ++-- front/src/store/player.ts | 130 ++----- front/src/store/queue.ts | 231 ++++++++---- front/src/style/components/_player.scss | 3 +- .../src/style/components/_volume_control.scss | 4 +- front/src/types.ts | 1 + front/yarn.lock | 110 +++--- 27 files changed, 839 insertions(+), 929 deletions(-) delete mode 100644 front/src/composables/audio/usePlayer.ts delete mode 100644 front/src/composables/audio/useSound.ts delete mode 100644 front/src/composables/audio/useSoundCache.ts create mode 100644 front/src/composables/audio/useWebAudioPlayer.ts diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev index 5cd43b1e5..28964a529 100644 --- a/docker/nginx/conf.dev +++ b/docker/nginx/conf.dev @@ -69,12 +69,12 @@ http { text/x-component text/x-cross-domain-policy; - add_header Content-Security-Policy "default-src 'self' 'unsafe-eval'; connect-src https: 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Content-Security-Policy "connect-src https: wss: 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; add_header Referrer-Policy "strict-origin-when-cross-origin"; add_header X-Frame-Options "SAMEORIGIN" always; location /front/ { - add_header Content-Security-Policy "default-src 'self' 'unsafe-eval'; connect-src https: 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Content-Security-Policy "connect-src https: wss: 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; add_header Referrer-Policy "strict-origin-when-cross-origin"; add_header Service-Worker-Allowed "/"; # uncomment the following line and comment the proxy-pass one diff --git a/front/package.json b/front/package.json index 3b0b8e075..19cda8790 100644 --- a/front/package.json +++ b/front/package.json @@ -35,6 +35,7 @@ "howler": "2.2.3", "js-logger": "1.6.1", "lodash-es": "4.17.21", + "lru-cache": "^7.13.1", "mavon-editor": "^3.0.0-beta", "moment": "2.29.4", "qs": "6.11.0", @@ -42,6 +43,7 @@ "sanitize-html": "2.7.1", "sass": "1.54.0", "showdown": "2.1.0", + "standardized-audio-context": "^25.3.29", "text-clipper": "2.2.0", "tiptap-markdown": "^0.5.0", "transliteration": "2.3.5", diff --git a/front/src/components/Queue.vue b/front/src/components/Queue.vue index bf6b690bd..6887e9401 100644 --- a/front/src/components/Queue.vue +++ b/front/src/components/Queue.vue @@ -11,7 +11,7 @@ import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue' import { whenever, watchDebounced, useCurrentElement, useScrollLock } from '@vueuse/core' import { useGettext } from 'vue3-gettext' import useQueue from '~/composables/audio/useQueue' -import usePlayer from '~/composables/audio/usePlayer' +import useWebAudioPlayer from '~/composables/audio/useWebAudioPlayer' import VirtualList from '~/components/vui/list/VirtualList.vue' import QueueItem from '~/components/QueueItem.vue' @@ -24,33 +24,33 @@ const scrollLock = useScrollLock(document.body) const store = useStore() const { - playing, - loading: isLoadingAudio, - errored, - duration, - durationFormatted, - currentTimeFormatted, - progress, - bufferProgress, - currentTime, - pause, - resume -} = usePlayer() - -const { - currentTrack, - hasNext, isEmpty: emptyQueue, tracks, reorder, endsIn: timeLeft, - currentIndex, removeTrack, - clear, - next, - previous + clear } = useQueue() +const currentIndex = computed(() => store.state.queue.currentIndex) +const currentTrack = computed(() => store.state.queue.tracks[currentIndex.value]) +const hasNext = computed(() => store.getters['queue/hasNext']) +const durationFormatted = computed(() => time.parse(Math.floor(duration.value))) +const currentTimeFormatted = computed(() => time.parse(Math.floor(currentTime.value))) + +const { + play, + pause, + next, + previous, + playing, + errored, + progress, + duration, + time: currentTime, + loading: isLoadingAudio +} = useWebAudioPlayer() + const labels = computed(() => ({ queue: $pgettext('*/*/*', 'Queue'), duration: $pgettext('*/*/*', 'Duration'), @@ -105,13 +105,12 @@ router.beforeEach(() => store.commit('ui/queueFocused', null)) const progressBar = ref() const touchProgress = (event: MouseEvent) => { - const time = ((event.clientX - (event.target as Element).getBoundingClientRect().left) / progressBar.value.offsetWidth) * duration.value - currentTime.value = time + const percent = (event.clientX - ((event.target as Element).closest('.progress')?.getBoundingClientRect().left ?? 0)) / progressBar.value.offsetWidth + progress.value = percent * 100 } -const play = (index: unknown) => { - store.dispatch('queue/currentIndex', index as number) - resume() +const playIndex = (index: number) => { + store.state.queue.currentIndex = index } const getCover = (track: Track) => { @@ -255,12 +254,8 @@ const reorderTracks = async (from: number, to: number) => {
-
{ :title="labels.play" :aria-label="labels.play" class="control" - @click.prevent.stop="resume" + @click.prevent.stop="play" > @@ -395,7 +390,7 @@ const reorderTracks = async (from: number, to: number) => { :index="index" :source="item" :class="[...classList, currentIndex === index && 'active']" - @play="play" + @play="playIndex" @remove="removeTrack" /> diff --git a/front/src/components/audio/ChannelEntryCard.vue b/front/src/components/audio/ChannelEntryCard.vue index cbe9abeae..1e0dde091 100644 --- a/front/src/components/audio/ChannelEntryCard.vue +++ b/front/src/components/audio/ChannelEntryCard.vue @@ -4,7 +4,7 @@ import type { Cover, Track } from '~/types' import PlayButton from '~/components/audio/PlayButton.vue' import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue' import useQueue from '~/composables/audio/useQueue' -import usePlayer from '~/composables/audio/usePlayer' +import useWebAudioPlayer from '~/composables/audio/useWebAudioPlayer' import { computed } from 'vue' interface Props { @@ -16,7 +16,7 @@ interface Props { const props = defineProps() const { currentTrack } = useQueue() -const { playing } = usePlayer() +const { playing } = useWebAudioPlayer() const cover = computed(() => props.entry.cover ?? null) const duration = computed(() => props.entry.uploads.find(upload => upload.duration)?.duration ?? null) diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue index d2575cf6c..9dc98186b 100644 --- a/front/src/components/audio/PlayButton.vue +++ b/front/src/components/audio/PlayButton.vue @@ -155,7 +155,7 @@ const openMenu = () => { data-ref="enqueue" :disabled="!playable" :title="labels.addToQueue" - @click.stop.prevent="enqueue" + @click.stop.prevent="enqueue()" > Add to queue diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index cdda270b6..bf3ff4a69 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -1,5 +1,7 @@