[web] Improve the initialisation of stores

This commit is contained in:
Alain Nussbaumer
2025-05-17 00:24:58 +02:00
parent 6b5f4ff4d7
commit f91189d93b
11 changed files with 289 additions and 1094 deletions

View File

@@ -1,5 +1,5 @@
<template>
<button :class="{ 'is-dark': playerStore.lyrics }" @click="toggle">
<button :class="{ 'is-dark': playerStore.showLyrics }" @click="toggle">
<mdicon
class="icon"
:name="icon"
@@ -21,14 +21,14 @@ export default {
},
computed: {
icon() {
return this.playerStore.lyrics
return this.playerStore.showLyrics
? 'script-text-play'
: 'script-text-outline'
}
},
methods: {
toggle() {
this.playerStore.lyrics = !this.playerStore.lyrics
this.playerStore.showLyrics = !this.playerStore.showLyrics
}
}
}

View File

@@ -1,8 +1,12 @@
<template>
<div class="lyrics is-overlay">
<div v-for="(verse, index) in visibleLyrics" :key="index">
<div
class="lyrics is-overlay"
@wheel.prevent="onScroll"
@touchmove.prevent="onScroll"
>
<div v-for="(verse, index) in visibleVerses" :key="index">
<div v-if="verse">
<div v-if="index === 3" class="title is-5 my-5 lh-2">
<div v-if="index === MIDDLE_POSITION" class="title is-5 my-5 lh-2">
<span
v-for="(word, wordIndex) in verse.words"
:key="wordIndex"
@@ -19,36 +23,40 @@
<script>
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue'
const VISIBLE_VERSES = 7
const MIDDLE_POSITION = Math.floor(VISIBLE_VERSES / 2)
export default {
name: 'PaneLyrics',
setup() {
return { playerStore: usePlayerStore(), queueStore: useQueueStore() }
const VISIBLE_VERSES = 7
const MIDDLE_POSITION = Math.floor(VISIBLE_VERSES / 2)
const SCROLL_THRESHOLD = 10
return {
MIDDLE_POSITION,
playerStore: usePlayerStore(),
SCROLL_THRESHOLD,
VISIBLE_VERSES
}
},
data() {
return {
lastIndex: -1,
time: 0,
timerId: null,
lastProgress: 0,
lastUpdateTime: 0,
lyrics: []
lyrics: { synchronised: false, verses: [] },
scrollIndex: 0,
scrollDelta: 0,
time: 0,
timerId: null
}
},
computed: {
verseIndex() {
const currentTime = this.time
const { lyrics } = this
const { verses } = this.lyrics
let start = 0
let end = lyrics.length - 1
let end = verses.length - 1
while (start <= end) {
const mid = Math.floor((start + end) / 2)
const midTime = lyrics[mid].time
const nextTime = lyrics[mid + 1]?.time
const midTime = verses[mid].time
const nextTime = verses[mid + 1]?.time
if (midTime <= currentTime && (!nextTime || nextTime > currentTime)) {
return mid
} else if (midTime < currentTime) {
@@ -59,13 +67,13 @@ export default {
}
return -1
},
visibleLyrics() {
const current = this.verseIndex
const total = this.lyrics.length
return Array.from({ length: VISIBLE_VERSES }, (_, i) => {
const index = current - MIDDLE_POSITION + i
return index >= 0 && index < total ? this.lyrics[index] : null
})
visibleVerses() {
const { verses, synchronised } = this.lyrics
const index = synchronised ? this.verseIndex : this.scrollIndex
return Array.from(
{ length: this.VISIBLE_VERSES },
(_, i) => verses[index - this.MIDDLE_POSITION + i] ?? null
)
}
},
watch: {
@@ -78,7 +86,6 @@ export default {
}
},
'playerStore.item_progress_ms'(progress) {
this.lastProgress = progress
this.lastUpdateTime = Date.now()
if (!this.playerStore.isPlaying) {
this.time = progress
@@ -86,13 +93,10 @@ export default {
},
'playerStore.lyricsContent'() {
this.lyrics = this.parseLyrics()
},
verseIndex(index) {
this.lastIndex = index
}
},
mounted() {
this.lastProgress = this.playerStore.item_progress_ms
this.playerStore.initialise()
this.lastUpdateTime = Date.now()
this.lyrics = this.parseLyrics()
this.updateTime()
@@ -101,11 +105,24 @@ export default {
this.stopTimer()
},
methods: {
onScroll(event) {
if (this.verseIndex >= 0) {
return
}
this.scrollDelta += event.deltaY
if (Math.abs(this.scrollDelta) >= this.SCROLL_THRESHOLD) {
const newIndex = this.scrollIndex + Math.sign(this.scrollDelta)
if (newIndex >= -1 && newIndex < this.lyrics.verses.length) {
this.scrollIndex = newIndex
}
this.scrollDelta = 0
}
},
isWordHighlighted(word) {
return this.time >= word.start && this.time < word.end
},
parseLyrics() {
const verses = []
const lyrics = { synchronised: false, verses: [] }
const regex =
/(?:\[(?<minutes>\d+):(?<seconds>\d+)(?:\.(?<hundredths>\d+))?\])?\s*(?<text>\S.*\S)?\s*/u
this.playerStore.lyricsContent.split('\n').forEach((line) => {
@@ -117,25 +134,25 @@ export default {
const time =
(Number(minutes) * 60 + Number(`${seconds}.${hundredths ?? 0}`)) *
1000
verses.push({ text: verse, time })
lyrics.synchronised = !isNaN(time)
lyrics.verses.push({ text: verse, time })
}
}
})
verses.forEach((verse, index, lyrics) => {
const nextTime = lyrics[index + 1]?.time ?? verse.time + 3000
lyrics.verses.forEach((verse, index, verses) => {
const nextTime = verses[index + 1]?.time ?? verse.time + 3000
const totalDuration = nextTime - verse.time
const words = verse.text.match(/\S+\s*/gu) || []
const totalLength = words.reduce((sum, word) => sum + word.length, 0)
let currentTime = verse.time
verse.words = words.map((text) => {
const duration = totalDuration * (text.length / totalLength)
const start = currentTime
const end = start + duration
const end = start + totalDuration * (text.length / totalLength)
currentTime = end
return { text, start, end }
})
})
return verses
return lyrics
},
startTimer() {
if (this.timerId) {
@@ -150,13 +167,15 @@ export default {
}
},
tick() {
this.time = this.lastProgress + Date.now() - this.lastUpdateTime
this.time =
this.playerStore.item_progress_ms + Date.now() - this.lastUpdateTime
},
updateTime() {
this.lastUpdateTime = Date.now()
if (this.playerStore.isPlaying) {
this.startTimer()
} else {
this.time = this.lastProgress
this.time = this.playerStore.item_progress_ms
}
}
}