mirror of
https://github.com/owntone/owntone-server.git
synced 2025-03-30 17:23:44 -04:00
[web] Make the lyrics pane code more readable
This commit is contained in:
parent
553d35ef82
commit
b7ad3c8d45
@ -7,24 +7,24 @@
|
|||||||
@scroll.passive="start_scrolling"
|
@scroll.passive="start_scrolling"
|
||||||
@wheel.passive="start_scrolling"
|
@wheel.passive="start_scrolling"
|
||||||
>
|
>
|
||||||
<template v-for="(item, index) in lyrics" :key="item">
|
<template v-for="(verse, index) in lyrics" :key="index">
|
||||||
<div
|
<div
|
||||||
v-if="is_sync && index == verse_index"
|
v-if="index === verse_index"
|
||||||
:class="{ 'is-highlighted': is_playing }"
|
:class="{ 'is-highlighted': is_playing }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-for="word in split_verse(index)"
|
v-for="(word, index) in verse.words"
|
||||||
:key="word.duration"
|
:key="index"
|
||||||
class="has-text-weight-bold is-size-5"
|
class="has-text-weight-bold is-size-5"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
:style="{ animationDuration: word.duration + 's' }"
|
:style="{ 'animation-duration': `${word.delay}s` }"
|
||||||
v-text="word.content"
|
v-text="word.text"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ item.text }}
|
{{ verse.text }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -40,7 +40,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
this.lastIndex = -1
|
this.lastIndex = -1
|
||||||
// Fired upon scrolling, thus disabling the auto scrolling for 5 seconds
|
// Fired upon scrolling, thus disabling the auto scrolling for 5 seconds
|
||||||
this.scrollTimer = null
|
this.scrollingTimer = null
|
||||||
this.lastItemId = -1
|
this.lastItemId = -1
|
||||||
return {
|
return {
|
||||||
autoScrolling: true
|
autoScrolling: true
|
||||||
@ -50,49 +50,69 @@ export default {
|
|||||||
is_playing() {
|
is_playing() {
|
||||||
return this.player.state === 'play'
|
return this.player.state === 'play'
|
||||||
},
|
},
|
||||||
is_sync() {
|
|
||||||
return this.lyrics.length && this.lyrics[0].time
|
|
||||||
},
|
|
||||||
verse_index() {
|
verse_index() {
|
||||||
if (!this.is_sync) {
|
if (this.lyrics.length && this.lyrics[0].time) {
|
||||||
|
const curTime = this.player.item_progress_ms / 1000,
|
||||||
|
la = this.lyrics,
|
||||||
|
trackChanged = this.player.item_id !== this.lastItemId,
|
||||||
|
trackSeeked =
|
||||||
|
this.lastIndex >= 0 &&
|
||||||
|
this.lastIndex < la.length &&
|
||||||
|
la[this.lastIndex].time > curTime
|
||||||
|
if (trackChanged || trackSeeked) {
|
||||||
|
// Reset the cache when the track has changed or has been rewind
|
||||||
|
this.reset_scrolling()
|
||||||
|
}
|
||||||
|
// Check the cached value to avoid searching the times
|
||||||
|
if (
|
||||||
|
this.lastIndex < la.length - 1 &&
|
||||||
|
la[this.lastIndex + 1].time > curTime
|
||||||
|
)
|
||||||
|
return this.lastIndex
|
||||||
|
if (
|
||||||
|
this.lastIndex < la.length - 2 &&
|
||||||
|
la[this.lastIndex + 2].time > curTime
|
||||||
|
)
|
||||||
|
return this.lastIndex + 1
|
||||||
|
// Not found in the next 2 items, so start searching for the best time
|
||||||
|
return la.findLastIndex((verse) => verse.time <= curTime)
|
||||||
|
} else {
|
||||||
this.reset_scrolling()
|
this.reset_scrolling()
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
const curTime = this.player.item_progress_ms / 1000,
|
|
||||||
la = this.lyrics,
|
|
||||||
trackChanged = this.player.item_id !== this.lastItemId,
|
|
||||||
trackSeeked =
|
|
||||||
this.lastIndex >= 0 &&
|
|
||||||
this.lastIndex < la.length &&
|
|
||||||
la[this.lastIndex].time > curTime
|
|
||||||
if (trackChanged || trackSeeked) {
|
|
||||||
// Reset the cache when the track has changed or has been rewind
|
|
||||||
this.reset_scrolling()
|
|
||||||
}
|
|
||||||
// Check the cached value to avoid searching the times
|
|
||||||
if (this.lastIndex < la.length - 1 && la[this.lastIndex + 1].time > curTime)
|
|
||||||
return this.lastIndex
|
|
||||||
if (this.lastIndex < la.length - 2 && la[this.lastIndex + 2].time > curTime)
|
|
||||||
return this.lastIndex + 1
|
|
||||||
// Not found in the next 2 items, so start searching for the best time
|
|
||||||
return la.findLastIndex((verse) => verse.time <= curTime)
|
|
||||||
},
|
},
|
||||||
lyrics() {
|
lyrics() {
|
||||||
const raw = this.$store.getters.lyrics
|
const raw = this.$store.getters.lyrics
|
||||||
const parsed = []
|
const parsed = []
|
||||||
if (raw) {
|
if (raw) {
|
||||||
|
// Parse the lyrics
|
||||||
const regex = /(\[(\d+):(\d+)(?:\.\d+)?\] ?)?(.*)/
|
const regex = /(\[(\d+):(\d+)(?:\.\d+)?\] ?)?(.*)/
|
||||||
raw.split('\n').forEach((item) => {
|
raw.split('\n').forEach((item, index) => {
|
||||||
const matches = regex.exec(item)
|
const matches = regex.exec(item)
|
||||||
if (matches && matches[4]) {
|
if (matches && matches[4]) {
|
||||||
const verse = {}
|
const verse = {
|
||||||
verse.text = matches[4]
|
text: matches[4],
|
||||||
if (matches[2] && matches[3]) {
|
time: matches[2] * 60 + matches[3] * 1
|
||||||
verse.time = matches[2] * 60 + matches[3] * 1
|
|
||||||
}
|
}
|
||||||
parsed.push(verse)
|
parsed.push(verse)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// Split the verses into words
|
||||||
|
parsed.forEach((verse, index, lyrics) => {
|
||||||
|
const duration =
|
||||||
|
index < lyrics.length - 1 ? lyrics[index + 1].time - verse.time : 3
|
||||||
|
const unitDuration = duration / verse.text.length
|
||||||
|
let delay = 0
|
||||||
|
verse.words = verse.text.match(/\S+\s*/g).map((text) => {
|
||||||
|
const duration = text.length * unitDuration
|
||||||
|
delay += duration
|
||||||
|
return {
|
||||||
|
duration,
|
||||||
|
delay,
|
||||||
|
text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return parsed
|
return parsed
|
||||||
},
|
},
|
||||||
@ -108,7 +128,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
reset_scrolling() {
|
reset_scrolling() {
|
||||||
// Scroll to the start of the track lyrics in all cases
|
// Scroll to the start of the lyrics in all cases
|
||||||
if (this.player.item_id != this.lastItemId && this.$refs.lyrics) {
|
if (this.player.item_id != this.lastItemId && this.$refs.lyrics) {
|
||||||
this.$refs.lyrics.scrollTo(0, 0)
|
this.$refs.lyrics.scrollTo(0, 0)
|
||||||
}
|
}
|
||||||
@ -116,20 +136,15 @@ export default {
|
|||||||
this.lastIndex = -1
|
this.lastIndex = -1
|
||||||
},
|
},
|
||||||
start_scrolling(e) {
|
start_scrolling(e) {
|
||||||
/*
|
// Consider only user events
|
||||||
* Distinguish scroll event triggered by a user or programmatically
|
if (e.screenX || e.screenX != 0 || e.screenY || e.screenY != 0) {
|
||||||
* Programmatically triggered event are ignored
|
this.autoScrolling = false
|
||||||
*/
|
if (this.scrollingTimer) {
|
||||||
if (!e.screenX || e.screenX == 0 || !e.screenY || e.screenY == 0) return
|
clearTimeout(this.scrollingTimer)
|
||||||
this.autoScrolling = false
|
}
|
||||||
if (this.scrollTimer) {
|
// Reenable automatic scrolling after 2 seconds
|
||||||
clearTimeout(this.scrollTimer)
|
this.scrollingTimer = setTimeout((this.autoScrolling = true), 2000)
|
||||||
}
|
}
|
||||||
const t = this
|
|
||||||
// Reenable automatic scrolling after 5 seconds
|
|
||||||
this.scrollTimer = setTimeout(() => {
|
|
||||||
t.autoScrolling = true
|
|
||||||
}, 2000)
|
|
||||||
},
|
},
|
||||||
scroll_to_verse() {
|
scroll_to_verse() {
|
||||||
const pane = this.$refs.lyrics
|
const pane = this.$refs.lyrics
|
||||||
@ -138,37 +153,15 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const currentVerse = pane.children[this.verse_index]
|
const currentVerse = pane.children[this.verse_index]
|
||||||
const offsetToCenter = pane.offsetHeight >> 1
|
|
||||||
if (!this.lyrics || !currentVerse) return
|
|
||||||
const top =
|
|
||||||
currentVerse.offsetTop -
|
|
||||||
offsetToCenter +
|
|
||||||
(currentVerse.offsetHeight >> 1) -
|
|
||||||
pane.scrollTop
|
|
||||||
/*
|
|
||||||
* Using scrollBy ensures the scrolling will happen
|
|
||||||
* even if the element is visible before scrolling
|
|
||||||
*/
|
|
||||||
pane.scrollBy({
|
pane.scrollBy({
|
||||||
top,
|
top:
|
||||||
|
currentVerse.offsetTop -
|
||||||
|
(pane.offsetHeight >> 1) +
|
||||||
|
(currentVerse.offsetHeight >> 1) -
|
||||||
|
pane.scrollTop,
|
||||||
left: 0,
|
left: 0,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
},
|
|
||||||
split_verse(index) {
|
|
||||||
const verse = this.lyrics[index]
|
|
||||||
let verseDuration = 3 // Default duration for a verse
|
|
||||||
if (index < this.lyrics.length - 1) {
|
|
||||||
verseDuration = this.lyrics[index + 1].time - verse.time
|
|
||||||
}
|
|
||||||
const unitDuration = verseDuration / verse.text.length
|
|
||||||
// Split verse into words
|
|
||||||
let duration = 0
|
|
||||||
return verse.text.match(/\S+\s*/g).map((word) => {
|
|
||||||
const d = duration
|
|
||||||
duration += word.length * unitDuration
|
|
||||||
return { duration: d, content: word }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user