feat: optimize CPU and memory usage

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2346>
environments/review-docs-nginx-aqlin2/deployments/17397
Kasper Seweryn 2023-01-31 22:31:38 +01:00 zatwierdzone przez Marge
rodzic ed8b71257d
commit 77e920672d
15 zmienionych plików z 999 dodań i 105 usunięć

Wyświetl plik

@ -58,7 +58,7 @@
},
"devDependencies": {
"@intlify/eslint-plugin-vue-i18n": "2.0.0",
"@intlify/vite-plugin-vue-i18n": "6.0.3",
"@intlify/unplugin-vue-i18n": "^0.8.1",
"@types/diff": "5.0.2",
"@types/dompurify": "2.4.0",
"@types/howler": "2.2.7",
@ -73,6 +73,7 @@
"@vitejs/plugin-vue": "4.0.0",
"@vitest/coverage-c8": "0.25.8",
"@vue/compiler-sfc": "3.2.45",
"@vue/devtools": "^6.5.0",
"@vue/eslint-config-standard": "8.0.1",
"@vue/eslint-config-typescript": "11.0.2",
"@vue/test-utils": "2.2.7",

Wyświetl plik

@ -11,8 +11,8 @@ export interface SoundSource {
}
export interface Sound {
preload(): void | Promise<void>
dispose(): void
preload(): Promise<void>
dispose(): Promise<void>
readonly audioNode: IAudioNode<IAudioContext>
readonly isErrored: Ref<boolean>
@ -23,11 +23,11 @@ export interface Sound {
readonly buffered: number
looping: boolean
pause(): void | Promise<void>
play(): void | Promise<void>
pause(): Promise<void>
play(): Promise<void>
seekTo(seconds: number): void | Promise<void>
seekBy(seconds: number): void | Promise<void>
seekTo(seconds: number): Promise<void>
seekBy(seconds: number): Promise<void>
onSoundLoop: EventHookOn<Sound>
onSoundEnd: EventHookOn<Sound>
@ -95,19 +95,22 @@ export class HTMLSound implements Sound {
this.isLoaded.value = this.#audio.readyState >= 2
})
useEventListener(this.#audio, 'error', () => {
useEventListener(this.#audio, 'error', (err) => {
console.error('>> AUDIO ERRORED', err, this.__track?.title)
this.isErrored.value = true
this.isLoaded.value = true
})
}
preload () {
async preload () {
this.isErrored.value = false
console.log('CALLING PRELOAD ON', this.__track?.title)
this.#audio.load()
}
dispose () {
async dispose () {
this.audioNode.disconnect()
this.#audio.pause()
// Cancel any request downloading the source
this.#audio.src = ''

Wyświetl plik

@ -2,7 +2,7 @@
import type { QueueItemSource } from '~/types'
import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset, useStorage } from '@vueuse/core'
import { nextTick, ref, computed, watchEffect, onMounted } from 'vue'
import { nextTick, ref, computed, watchEffect, watch, defineAsyncComponent } from 'vue'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
@ -17,11 +17,12 @@ import time from '~/utils/time'
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
import PlayerControls from '~/components/audio/PlayerControls.vue'
import MilkDrop from '~/components/audio/visualizer/MilkDrop.vue'
import VirtualList from '~/components/vui/list/VirtualList.vue'
import QueueItem from '~/components/QueueItem.vue'
const MilkDrop = defineAsyncComponent(() => import('~/components/audio/visualizer/MilkDrop.vue'))
const {
isPlaying,
currentTime,
@ -41,7 +42,7 @@ const {
dequeue,
playTrack,
reorder,
endsIn: timeLeft,
endsIn,
clear
} = useQueue()
@ -92,16 +93,6 @@ const scrollToCurrent = (behavior: ScrollBehavior = 'smooth') => {
watchDebounced(currentTrack, () => scrollToCurrent(), { debounce: 100 })
const scrollLoop = () => {
const visible = [...(list.value?.scroller.$_views.values() ?? [])].map(item => item.nr.index)
if (!visible.includes(currentIndex.value)) {
list.value?.scrollToIndex(currentIndex.value)
requestAnimationFrame(scrollLoop)
}
}
onMounted(scrollLoop)
whenever(
() => queue.value.length === 0,
() => store.commit('ui/queueFocused', null),
@ -117,6 +108,13 @@ const touchProgress = (event: MouseEvent) => {
seekTo(time)
}
const animated = ref(false)
watch(currentTrack, async track => {
animated.value = false
await nextTick()
animated.value = true
})
const play = async (index: number) => {
isPlaying.value = true
return playTrack(index)
@ -357,8 +355,13 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
:style="{ 'transform': `translateX(${bufferProgress - 100}%)` }"
/>
<div
class="position bar"
:style="{ 'transform': `translateX(calc(${progress}% - 100%)` }"
:class="['position bar', { animated }]"
:style="{
animationDuration: duration + 's',
animationPlayState: isPlaying
? 'running'
: 'paused'
}"
/>
</div>
</div>
@ -415,12 +418,11 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
<div class="sub header">
<div>
{{ $t('components.Queue.meta.queuePosition', {index: currentIndex +1, length: queue.length}) }}
<template v-if="!$store.state.radios.running">
<span class="middle ellipses symbol" />
<span :title="labels.duration">
{{ timeLeft }}
</span>
</template>
<span class="middle pipe symbol" />
{{ $t('components.Queue.meta.end') }}
<span :title="labels.duration">
{{ endsIn }}
</span>
</div>
</div>
</div>
@ -433,8 +435,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
:component="QueueItem"
:size="50"
@reorder="reorderTracks"
@visible="scrollToCurrent('auto')"
@hidden="scrollLoop"
@visible="list.scrollToIndex(currentIndex, 'center')"
>
<template #default="{ index, item, classlist }">
<queue-item

Wyświetl plik

@ -150,7 +150,6 @@ const hideArtist = () => {
/>
<div
class="position bar"
:style="{ 'transform': `translateX(${progress - 100}%)` }"
/>
<div
class="seek bar"

Wyświetl plik

@ -2,7 +2,7 @@
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
import { computed } from 'vue'
import { computed, ref } from 'vue'
import usePlayOptions from '~/composables/audio/usePlayOptions'
@ -23,8 +23,6 @@ interface Props extends PlayOptionsProps {
showPosition?: boolean
displayActions?: boolean
hover: boolean
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
tracks: Track[]
isPlayable?: boolean
@ -57,12 +55,15 @@ const { isPlaying, loading } = usePlayer()
const { currentTrack } = useQueue()
const active = computed(() => props.track.id === currentTrack.value?.id && props.track.position === currentTrack.value?.position)
const hover = ref(false)
</script>
<template>
<div
:class="[{ active }, 'track-row row']"
@dblclick="activateTrack(track, index)"
@mousemove="hover = true"
@mouseout="hover = false"
>
<div
class="actions one wide left floated column"

Wyświetl plik

@ -1,7 +1,6 @@
<script setup lang="ts">
import type { Track } from '~/types'
import { useElementByPoint, useMouse } from '@vueuse/core'
import { useI18n } from 'vue-i18n'
import { clone, uniqBy } from 'lodash-es'
import { ref, computed } from 'vue'
@ -71,15 +70,6 @@ const props = withDefaults(defineProps<Props>(), {
unique: true
})
const { x, y } = useMouse({ type: 'client' })
const { element } = useElementByPoint({ x, y })
const hover = computed(() => {
const row = element.value?.closest('.track-row') ?? null
return row && allTracks.value.find(track => {
return `${track.id}` === row.getAttribute('data-track-id') && `${track.position}` === row.getAttribute('data-track-position')
})
})
const currentPage = ref(props.page)
const totalTracks = ref(props.total)
const fetchDataUrl = ref(props.nextUrl)
@ -235,8 +225,6 @@ const updatePage = (page: number) => {
<track-row
v-for="(track, index) in allTracks"
:key="`${track.id} ${track.position}`"
:data-track-id="track.id"
:data-track-position="track.position"
:track="track"
:index="index"
:tracks="allTracks"
@ -247,7 +235,6 @@ const updatePage = (page: number) => {
:display-actions="displayActions"
:show-duration="showDuration"
:is-podcast="isPodcast"
:hover="hover === track"
/>
</div>
<div

Wyświetl plik

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useMouse, useCurrentElement, useRafFn, useElementByPoint } from '@vueuse/core'
import { ref, watchEffect, reactive } from 'vue'
import { ref, watchEffect, reactive, watch } from 'vue'
// @ts-expect-error no typings
import { RecycleScroller } from 'vue-virtual-scroller'
@ -9,7 +9,6 @@ import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
interface Events {
(e: 'reorder', from: number, to: number): void
(e: 'visible'): void
(e: 'hidden'): void
}
interface Props {
@ -97,7 +96,19 @@ const cleanup = () => {
const scrollDirection = ref()
const containerSize = reactive({ bottom: 0, top: 0 })
const { x, y: screenY } = useMouse({ type: 'client' })
const { element: hoveredElement } = useElementByPoint({ x, y: screenY })
const { element: hoveredElement, pause: dragTrackPause, resume: dragTrackStart } = useElementByPoint({ x, y: screenY })
dragTrackPause()
// Disable element lookup
watch(draggedItem, (dragging) => {
if (dragging) {
dragTrackStart()
return
}
dragTrackPause()
}, { immediate: true })
// Find current index and position on both desktop and mobile devices
watchEffect(() => {
@ -138,23 +149,35 @@ const resize = () => {
containerSize.bottom = element.offsetHeight + containerSize.top
}
// Scrolling when item held near top/bottom border
let lastDate = +new Date()
const { resume, pause } = useRafFn(() => {
const now = +new Date()
const delta = now - lastDate
const direction = scrollDirection.value
if (direction && el.value?.children[0] && !isTouch.value) {
el.value.children[0].scrollTop += 200 / delta * (direction === 'up' ? -1 : 1)
el.value.children[0].scrollTop += 200 / (now - lastDate) * (direction === 'up' ? -1 : 1)
}
lastDate = now
}, { immediate: false })
const virtualList = ref()
const scrollToIndex = (index: number, block: 'center' | 'start' | 'end' = 'start') => {
if (!virtualList.value) return
const position = block === 'start'
? index * props.size
: block === 'end'
? (index + 1) * props.size - virtualList.value.$el.offsetHeight
: (index + 0.5) * props.size - virtualList.value.$el.offsetHeight / 2
virtualList.value.scrollToPosition(position)
}
defineExpose({
scrollToIndex: (index: number) => virtualList.value?.scrollToItem(index),
scroller: virtualList,
scrollToIndex,
cleanup
})
</script>
@ -173,7 +196,6 @@ defineExpose({
@touchmove="onTouchmove"
@resize="resize"
@visible="emit('visible')"
@hidden="emit('hidden')"
>
<template #before>
<slot name="header" />

Wyświetl plik

@ -168,15 +168,6 @@ export const usePlayer = createGlobalState(() => {
// Progress
const progress = ref(0)
useRafFn(() => {
const sound = currentSound.value
if (!sound) {
progress.value = 0
return
}
progress.value = sound.currentTime / sound.duration * 100
})
// Loading
const loading = computed(() => {

Wyświetl plik

@ -296,7 +296,6 @@ export const useQueue = createGlobalState(() => {
}
// Ends in
const now = useNow()
const endsIn = useTimeAgo(computed(() => {
const seconds = sum(
queue.value
@ -304,10 +303,12 @@ export const useQueue = createGlobalState(() => {
.map((track) => track.sources[0]?.duration ?? 0)
)
const date = new Date(now.value)
const date = new Date()
date.setSeconds(date.getSeconds() + seconds)
return date
}))
}), {
updateInterval: 0
})
// Clear
const clearRadio = ref(false)

Wyświetl plik

@ -20,7 +20,7 @@ const AUDIO_ELEMENT = document.createElement('audio')
const soundPromises = new Map<number, Promise<Sound>>()
const soundCache = useLRUCache<number, Sound>({
max: 10,
max: 3,
dispose: (sound) => sound.dispose()
})

Wyświetl plik

@ -192,7 +192,8 @@
"queuePosition": "Track {index} of {length}",
"startTime": "00:00",
"unknownArtist": "Unknown Artist",
"unknownAlbum": "Unknown Album"
"unknownAlbum": "Unknown Album",
"end": "End"
}
},
"RemoteSearchForm": {

Wyświetl plik

@ -2,6 +2,7 @@ import type { InitModule } from '~/types'
import store, { key } from '~/store'
import router from '~/router'
import devtools from '@vue/devtools'
import { createApp, defineAsyncComponent, h } from 'vue'
@ -13,6 +14,10 @@ import '~/api'
// NOTE: Set the theme as fast as possible
useTheme()
if (import.meta.env.MODE === 'development') {
devtools.connect(/* host, port */)
}
const logger = useLogger()
logger.info('Loading environment:', import.meta.env.MODE)
logger.debug('Environment variables:', import.meta.env)
@ -32,6 +37,8 @@ const app = createApp({
}
})
app.config.performance = false
app.use(router)
app.use(store, key)

Wyświetl plik

@ -171,6 +171,18 @@
.ui.progress:not(.indeterminate)
.bar.position:not(.buffer) {
background: var(--vibrant-color);
will-change: transform;
&.animated {
@keyframes progress {
0% { transform: translate3d(-100%, 0, 0) }
100% { transform: translate3d(0, 0, 0) }
}
animation-name: progress;
animation-timing-function: linear;
}
}
.indicating.progress .bar {

Wyświetl plik

@ -1,7 +1,6 @@
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import Inspector from 'vite-plugin-vue-inspector'
import VueI18n from '@intlify/vite-plugin-vue-i18n'
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
import { VitePWA } from 'vite-plugin-pwa'
import { resolve } from 'path'
@ -21,11 +20,6 @@ export default defineConfig(({ mode }) => ({
include: resolve(__dirname, './src/locales/**')
}),
// https://github.com/webfansplz/vite-plugin-vue-inspector
Inspector({
toggleComboKey: 'alt-shift-d'
}),
// https://github.com/antfu/vite-plugin-pwa
VitePWA({
strategies: 'injectManifest',

Plik diff jest za duży Load Diff