diff --git a/web-src/src/components/LyricsPane.vue b/web-src/src/components/LyricsPane.vue
index 225f6b0d..5c69baa9 100644
--- a/web-src/src/components/LyricsPane.vue
+++ b/web-src/src/components/LyricsPane.vue
@@ -1,33 +1,31 @@
-
-
-
+
-
-
- -
- {{ timedWord.text }}
-
-
-
-
- {{ item[0] }}
-
+
+
+
-
-
+
+ {{ item[0] }}
+
+
@@ -43,11 +41,8 @@ export default {
// Fired upon scrolling, thus disabling the auto scrolling for 5 seconds
this.scrollTimer = null
this.lastItemId = -1
- // Reactive
return {
- scroll: {},
- // Stops scrolling when a touch event is detected
- autoScroll: true
+ autoScrolling: true
}
},
computed: {
@@ -55,201 +50,156 @@ export default {
return this.player.state === 'play'
},
is_sync() {
- return this.lyricsArr.length && this.lyricsArr[0].length > 1
+ return this.lyrics.length && this.lyrics[0].length > 1
},
- lyricIndex() {
- /*
- * A dichotomous search is performed in
- * the time array to find the matching index.
- */
- const curTime = this.player.item_progress_ms / 1000
- const la = this.lyricsArr
- if (la.length && la[0].length === 1) {
- this.resetPosCache()
- // Bail out for non synchronized lyrics
+ verse_index() {
+ if (!this.is_sync) {
+ this.reset_scrolling()
return -1
}
- if (
- this.player.item_id != this.lastItemId ||
- (this.lastIndexValid() && la[this.lastIndex][1] > curTime)
- ) {
+ 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][1] > curTime
+ if (trackChanged || trackSeeked) {
// Reset the cache when the track has changed or has been rewind
- this.resetPosCache()
+ this.reset_scrolling()
}
// Check the cached value to avoid searching the times
if (this.lastIndex < la.length - 1 && la[this.lastIndex + 1][1] > curTime)
return this.lastIndex
if (this.lastIndex < la.length - 2 && la[this.lastIndex + 2][1] > curTime)
return this.lastIndex + 1
-
// Not found in the next 2 items, so start dichotomous search for the best time
- let i
- let start = 0
- let end = la.length - 1
+ let start = 0,
+ end = la.length - 1,
+ index
while (start <= end) {
- i = ((end + start) / 2) | 0
- if (la[i][1] <= curTime && la.length > i + 1 && la[i + 1][1] > curTime)
- break
- if (la[i][1] < curTime) start = i + 1
- else end = i - 1
+ index = (start + end) >> 1
+ if (
+ la[index][1] <= curTime &&
+ (la[index + 1]?.[1] > curTime || !la[index + 1])
+ ) {
+ return index
+ }
+ la[index][1] < curTime ? (start = index + 1) : (end = index - 1)
}
- return i
+ return -1
},
- lyricDuration() {
- // Ignore unsynchronized lyrics
- if (!this.lyricsArr.length || this.lyricsArr[0].length < 2) return 3600
- // The index is 0 before the first lyrics and until their end
- if (
- this.lyricIndex == -1 &&
- this.player.item_progress_ms / 1000 < this.lyricsArr[0][1]
- )
- return this.lyricsArr[0][1]
- return this.lyricIndex < this.lyricsArr.length - 1
- ? this.lyricsArr[this.lyricIndex + 1][1] -
- this.lyricsArr[this.lyricIndex][1]
- : 3600
- },
- lyricsArr() {
+ lyrics() {
return this.$store.getters.lyrics
},
player() {
return this.$store.state.player
- },
- splitLyric() {
- if (!this.lyricsArr.length || this.lyricIndex == -1) return {}
-
- // Split evenly the transition in the lyrics word (based on the word size / lyrics size)
- const lyric = this.lyricsArr[this.lyricIndex][0]
- const lyricDur = this.lyricDuration / lyric.length
-
- // Split lyrics into words
- const parsedWords = lyric.match(/\S+\s*/g)
- let duration = 0
- return parsedWords.map((w) => {
- const d = duration
- duration += (w.length + 1) * lyricDur
- return { delay: d, text: w }
- })
}
},
watch: {
- lyricIndex() {
- /*
- * Scroll current lyrics in the center of the view
- * unless manipulated by user
- */
- this.autoScroll && this._scrollToElement()
- this.lastIndex = this.lyricIndex
+ verse_index() {
+ this.autoScrolling && this.scroll_to_verse()
+ this.lastIndex = this.verse_index
}
},
methods: {
- lastIndexValid() {
- return this.lastIndex >= 0 && this.lastIndex < this.lyricsArr.length
- },
-
- resetPosCache() {
+ reset_scrolling() {
// Scroll to the start of the track lyrics in all cases
- if (this.player.item_id != this.lastItemId && this.$refs.lyricsWrapper)
- this.$refs.lyricsWrapper.scrollTo(0, 0)
-
+ if (this.player.item_id != this.lastItemId && this.$refs.lyrics) {
+ this.$refs.lyrics.scrollTo(0, 0)
+ }
this.lastItemId = this.player.item_id
this.lastIndex = -1
},
- startedScroll(e) {
+ start_scrolling(e) {
/*
* Distinguish scroll event triggered by a user or programmatically
* Programmatically triggered event are ignored
*/
if (!e.screenX || e.screenX == 0 || !e.screenY || e.screenY == 0) return
- this.autoScroll = false
- if (this.scrollTimer) clearTimeout(this.scrollTimer)
+ this.autoScrolling = false
+ if (this.scrollTimer) {
+ clearTimeout(this.scrollTimer)
+ }
const t = this
// Reenable automatic scrolling after 5 seconds
this.scrollTimer = setTimeout(() => {
- t.autoScroll = true
- }, 5000)
+ t.autoScrolling = true
+ }, 2000)
},
-
- _scrollToElement() {
- const scrollTouch = this.$refs.lyricsWrapper
- if (this.lyricIndex == -1) {
- scrollTouch.scrollTo(0, 0)
+ scroll_to_verse() {
+ const pane = this.$refs.lyrics
+ if (this.verse_index === -1) {
+ pane.scrollTo(0, 0)
return
}
-
- const currentLyric =
- scrollTouch.children[0].children[this.lyricIndex + 1], // Because of space item
- offsetToCenter = scrollTouch.offsetHeight >> 1
- if (!this.lyricsArr || !currentLyric) return
-
- const currOff = scrollTouch.scrollTop,
- destOff =
- currentLyric.offsetTop -
- offsetToCenter +
- (currentLyric.offsetHeight >> 1)
+ 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
*/
- scrollTouch.scrollBy({
- top: destOff - currOff,
+ pane.scrollBy({
+ top,
left: 0,
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][1] - verse[1]
+ }
+ const unitDuration = verseDuration / verse[0].length
+ console.log(`${unitDuration}`)
+ // Split verse into words
+ let duration = 0
+ return verse[0].match(/\S+\s*/g).map((word) => {
+ const d = duration
+ duration += word.length * unitDuration
+ return { duration: d, content: word }
+ })
}
}
}
diff --git a/web-src/src/mystyles.scss b/web-src/src/mystyles.scss
index 3dcc8137..0009a8b1 100644
--- a/web-src/src/mystyles.scss
+++ b/web-src/src/mystyles.scss
@@ -13,19 +13,6 @@
}
}
-.lyrics-wrapper .lyrics .gradient {
- color: $success;
-}
-
-.lyrics-wrapper .lyrics div {
- color: $black;
-}
-.lyrics-overlay {
- box-shadow:
- 0px 40px 40px 0px $white inset,
- 0px -40px 40px 0px $white inset;
-}
-
.progress-bar {
background-color: $info;
border-radius: 2px;
@@ -139,6 +126,9 @@ a.navbar-item {
border-radius: $radius-large;
max-height: calc(100vh - 26rem);
}
+ &.is-masked {
+ filter: blur(0.5rem) opacity(0.2);
+ }
}
}
diff --git a/web-src/src/pages/PageNowPlaying.vue b/web-src/src/pages/PageNowPlaying.vue
index a751b530..c66468bd 100644
--- a/web-src/src/pages/PageNowPlaying.vue
+++ b/web-src/src/pages/PageNowPlaying.vue
@@ -7,6 +7,7 @@
:artist="track.artist"
:album="track.album"
class="is-clickable fd-has-shadow fd-cover-big-image"
+ :class="{ 'is-masked': lyrics_visible }"
@click="open_dialog(track)"
/>