Revert "[web] Add lyrics player to the webinterface"

This reverts commit 98a844b409.
This commit is contained in:
ejurgensen 2023-10-21 22:41:45 +02:00
parent e4c47c22b3
commit a9e20abf06
15 changed files with 25 additions and 352 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -297,22 +297,12 @@ export default {
update_player_status() {
webapi.player_status().then(({ data }) => {
this.$store.commit(types.UPDATE_PLAYER_STATUS, data)
this.update_lyrics()
})
},
update_queue() {
webapi.queue().then(({ data }) => {
this.$store.commit(types.UPDATE_QUEUE, data)
this.update_lyrics()
})
},
update_lyrics() {
let track = this.$store.state.queue.items.filter(e => e.id == this.$store.state.player.item_id)
if (track.length >= 1)
webapi.library_track(track[0].track_id).then(({ data }) => {
this.$store.commit(types.UPDATE_LYRICS, data)
})
},

View File

@ -1,188 +0,0 @@
<template>
<div
class="lyrics-wrapper"
ref="lyricsWrapper"
@touchstart="autoScroll = false"
@touchend="autoScroll = true"
v-on:scroll.passive="startedScroll"
v-on:wheel.passive="startedScroll"
>
<div class="lyrics">
<p
v-for="(item, key) in lyricsArr"
:class="key == lyricIndex && is_sync && 'gradient'"
>
{{ item[0] }}
</p>
</div>
</div>
</template>
<script>
export default {
name: "lyrics",
data() {
// Non reactive
// Used as a cache to speed up finding the lyric index in the array for the current time
this.lastIndex = 0;
// Fired upon scrolling, that's disabling the auto scrolling for 5s
this.scrollTimer = null;
this.lastItemId = -1;
// Reactive
return {
scroll: {},
// lineHeight: 42,
autoScroll: true, // stop scroll to element when touch
};
},
computed: {
player() {
return this.$store.state.player
},
is_sync() {
return this.lyricsArr.length && this.lyricsArr[0].length > 1;
},
lyricIndex() {
// We have to perform a dichotomic search in the time array to find the index that's matching
const curTime = this.player.item_progress_ms / 1000;
const la = this.lyricsArr;
if (la.length && la[0].length === 1) return 0; // Bail out for non synchronized lyrics
if (this.player.item_id != this.lastItemId
|| this.lastIndex < la.length && la[this.lastIndex][1] > curTime) {
// Song changed or time scrolled back, let's reset the cache
this.lastItemId = this.player.item_id;
this.lastIndex = 0;
}
// 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 dichotomic search for the best time
let i;
let start = 0,
end = la.length - 1;
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;
}
return i;
},
lyricDuration() {
// Ignore unsynchronized lyrics.
if (!this.lyricsArr.length || this.lyricsArr[0].length < 2) return 3600;
// The index is 0 before the first lyric until the end of the first lyric
if (!this.lyricIndex && 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() {
return this.$store.getters.lyrics;
},
},
watch: {
lyricIndex() {
// Scroll current lyric in the center of the view unless user manipulated
this.autoScroll && this._scrollToElement();
this.lastIndex = this.lyricIndex;
},
},
methods: {
startedScroll(e) {
// Ugly trick to check if a scroll event comes from the user or from JS
if (!e.screenX || e.screenX == 0 || !e.screenY || e.screenY == 0) return; // Programmatically triggered event are ignored here
this.autoScroll = false;
if (this.scrollTimer) clearTimeout(this.scrollTimer);
let t = this;
// Re-enable automatic scrolling after 5s
this.scrollTimer = setTimeout(function () {
t.autoScroll = true;
}, 5000);
},
_scrollToElement() {
let scrollTouch = this.$refs.lyricsWrapper,
currentLyric = scrollTouch.children[0].children[this.lyricIndex],
offsetToCenter = scrollTouch.offsetHeight >> 1;
if (!this.lyricsArr || !currentLyric) return;
let currOff = scrollTouch.scrollTop,
destOff = currentLyric.offsetTop - offsetToCenter;
// Using scrollBy ensure that scrolling will happen
// even if the element is visible before scrolling
scrollTouch.scrollBy({
top: destOff - currOff,
left: 0,
behavior: "smooth",
});
// Then prepare the animated gradient too
currentLyric.style.animationDuration = this.lyricDuration + "s";
},
},
};
</script>
<style scoped>
.lyrics-wrapper {
position: absolute;
top: -1rem;
left: calc(50% - 40vw);
right: calc(50% - 40vw);
bottom: 0;
max-height: calc(100% - 9rem);
overflow: auto;
/* Glass effect */
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
border: 1px solid rgba(0, 0, 0, 0.3);
}
.lyrics-wrapper .lyrics {
display: flex;
align-items: center;
flex-direction: column;
}
.lyrics-wrapper .lyrics .gradient {
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: bold;
font-size: 120%;
animation: slide-right 1 linear;
background-size: 200% 100%;
background-image: -webkit-linear-gradient(
left,
#080 50%,
#000 50%
);
}
@keyframes slide-right {
0% {
background-position: 100% 0%;
}
100% {
background-position: 0% 0%;
}
}
.lyrics-wrapper .lyrics p {
line-height: 3rem;
text-align: center;
font-size: 1rem;
color: #000;
}
</style>

View File

@ -107,8 +107,7 @@
</div>
</div>
<div class="navbar-brand is-flex-grow-1">
<div class="navbar-item is-expanded is-justify-content-left is-no-basis">
<navbar-item-link :to="{ name: 'queue' }" exact>
<navbar-item-link :to="{ name: 'queue' }" exact class="mr-auto">
<mdicon class="icon" name="playlist-play" size="24" />
</navbar-item-link>
<navbar-item-link
@ -127,8 +126,6 @@
/>
</div>
</navbar-item-link>
</div>
<div class="navbar-item is-expanded is-justify-content-center is-no-basis">
<player-button-previous
v-if="is_now_playing_page"
class="navbar-item px-2"
@ -156,15 +153,8 @@
class="navbar-item px-2"
:icon_size="24"
/>
</div>
<div class="navbar-item is-expanded is-justify-content-right is-no-basis">
<player-button-lyrics
v-if="is_now_playing_page"
class="navbar-item"
:icon_size="24"
/>
<a
class="navbar-item"
class="navbar-item ml-auto"
@click="show_player_menu = !show_player_menu"
>
<mdicon
@ -173,7 +163,6 @@
/>
</a>
</div>
</div>
<!-- Player menu for mobile and tablet -->
<div
class="navbar-menu is-hidden-desktop"
@ -279,7 +268,6 @@ import { mdiCancel } from '@mdi/js'
import NavbarItemLink from './NavbarItemLink.vue'
import NavbarItemOutput from './NavbarItemOutput.vue'
import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue'
import PlayerButtonLyrics from '@/components/PlayerButtonLyrics.vue'
import PlayerButtonNext from '@/components/PlayerButtonNext.vue'
import PlayerButtonPlayPause from '@/components/PlayerButtonPlayPause.vue'
import PlayerButtonPrevious from '@/components/PlayerButtonPrevious.vue'
@ -296,7 +284,6 @@ export default {
NavbarItemLink,
NavbarItemOutput,
PlayerButtonConsume,
PlayerButtonLyrics,
PlayerButtonNext,
PlayerButtonPlayPause,
PlayerButtonPrevious,

View File

@ -1,45 +0,0 @@
<template>
<a :class="{ 'is-active': is_active }" @click="toggle_lyrics">
<mdicon
v-if="!is_active"
name="script-text-outline"
:size="icon_size"
:title="$t('player.button.toggle-lyrics')"
/>
<mdicon
v-if="is_active"
name="script-text-play"
:size="icon_size"
:title="$t('player.button.toggle-lyrics')"
/>
</a>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'PlayerButtonLyrics',
props: {
icon_size: {
type: Number,
default: 16
}
},
computed: {
is_active() {
return this.$store.getters.lyrics_pane;
}
},
methods: {
toggle_lyrics() {
this.$store.state.lyrics.lyrics_pane = !this.$store.state.lyrics.lyrics_pane;
}
}
}
</script>
<style></style>

View File

@ -45,8 +45,6 @@ import {
mdiRepeatOnce,
mdiRewind10,
mdiRss,
mdiScriptTextOutline,
mdiScriptTextPlay,
mdiServer,
mdiShuffle,
mdiShuffleDisabled,
@ -109,8 +107,6 @@ export const icons = {
mdiRewind10,
mdiRss,
mdiServer,
mdiScriptTextOutline,
mdiScriptTextPlay,
mdiShuffle,
mdiShuffleDisabled,
mdiSkipBackward,

View File

@ -578,8 +578,7 @@
"shuffle-disabled": "Tracks in Reihenfolge wiedergeben",
"skip-backward": "Zum vorherigen Track springen",
"skip-forward": "Zum nächsten Track springen",
"stop": "Wiedergabe stoppen",
"toggle-lyrics": "Liedtexte anzeigen/verbergen"
"stop": "Wiedergabe stoppen"
}
},
"setting": {

View File

@ -578,8 +578,7 @@
"shuffle-disabled": "Play tracks in order",
"skip-backward": "Skip to previous track",
"skip-forward": "Skip to next track",
"stop": "Stop",
"toggle-lyrics": "Toggle lyrics"
"stop": "Stop"
}
},
"setting": {

View File

@ -578,8 +578,7 @@
"shuffle-disabled": "Lire les pistes dans lordre",
"skip-backward": "Reculer à la piste précédente",
"skip-forward": "Avancer à la piste suivante",
"stop": "Arrêter la lecture",
"toggle-lyrics": "Voir/Cacher les paroles"
"stop": "Arrêter la lecture"
}
},
"setting": {

View File

@ -578,8 +578,7 @@
"shuffle-disabled": "按顺序播放曲目",
"skip-backward": "播放上一首",
"skip-forward": "播放下一首",
"stop": "停止",
"toggle-lyrics": "显示/隐藏歌词"
"stop": "停止"
}
},
"setting": {

View File

@ -3,27 +3,6 @@
@import 'bulma/bulma.sass';
@import 'bulma-switch';
.is-no-basis {
flex-basis: 0;
}
/* Fix bulma issue for a group under a navbar-brand container (contrary to documentation) */
.navbar.is-white .navbar-brand > .navbar-item *:focus, .navbar.is-white .navbar-brand > .navbar-item *:hover, .navbar.is-white .navbar-brand > .navbar-item *.is-active {
color: $black;
}
.navbar.is-dark .navbar-brand > .navbar-item:focus, .navbar.is-dark .navbar-brand > .navbar-item:hover, .navbar.is-dark .navbar-brand > .navbar-item.is-active
.navbar.is-dark .navbar-brand > .navbar-item *:focus, .navbar.is-dark .navbar-brand > .navbar-item *:hover, .navbar.is-dark .navbar-brand > .navbar-item *.is-active {
background-color: $black-ter;;
color: $white;
}
.navbar.is-dark .navbar-brand .navbar-item,
.navbar.is-white.is-dark .navbar-brand .navbar-item,
{
color: $white;
}
.progress-bar {
background-color: $info;
border-radius: 2px;

View File

@ -9,7 +9,6 @@
class="is-clickable fd-has-shadow fd-cover-big-image"
@click="open_dialog(track)"
/>
<lyrics v-if="lyrics_visible" />
<control-slider
v-model:value="track_progress"
class="mt-5"
@ -57,7 +56,6 @@
import * as types from '@/store/mutation_types'
import ControlSlider from '@/components/ControlSlider.vue'
import CoverArtwork from '@/components/CoverArtwork.vue'
import Lyrics from '@/components/Lyrics.vue'
import { mdiCancel } from '@mdi/js'
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
import webapi from '@/webapi'
@ -69,7 +67,6 @@ export default {
components: {
ControlSlider,
CoverArtwork,
Lyrics,
ModalDialogQueueItem
},
@ -85,10 +82,6 @@ export default {
},
computed: {
lyrics_visible() {
return this.$store.getters.lyrics_pane;
},
is_live() {
return this.track.length_ms === 0
},

View File

@ -31,12 +31,7 @@ export default createStore({
volume: 0,
item_id: 0,
item_length_ms: 0,
item_progress_ms: 0,
},
lyrics: {
lyrics_pane: false,
lyrics_id: -1,
lyrics: []
item_progress_ms: 0
},
queue: {
version: 0,
@ -75,14 +70,6 @@ export default createStore({
},
getters: {
lyrics_pane: (state) => {
return state.lyrics.lyrics_pane
},
lyrics: (state) => {
return state.lyrics.lyrics
},
now_playing: (state) => {
const item = state.queue.items.find(function (item) {
return item.id === state.player.item_id
@ -201,26 +188,6 @@ export default createStore({
[types.UPDATE_QUEUE](state, queue) {
state.queue = queue
},
[types.UPDATE_LYRICS](state, lyrics) {
// Parse from .LRC or text format to synchronized lyrics
function parse(lyrics) {
let lyricsObj = [];
let tempArr = lyrics.split("\n");
const regex = /(\[(\d+):(\d+)(?:\.\d+)?\] ?)?(.*)/;
tempArr.forEach((item) => {
let matches = regex.exec(item);
if (matches !== null && matches[4].length) {
let obj = [matches[4]];
if (matches[2] != null && matches[3] != null)
obj.push(parseInt(matches[2], 10) * 60 + parseInt(matches[3], 10));
lyricsObj.push(obj);
}
});
return lyricsObj;
}
state.lyrics.lyrics = "lyrics" in lyrics ? parse(lyrics.lyrics) : ""
},
[types.UPDATE_LASTFM](state, lastfm) {
state.lastfm = lastfm
},

View File

@ -8,7 +8,6 @@ export const UPDATE_LIBRARY_RSS_COUNT = 'UPDATE_LIBRARY_RSS_COUNT'
export const UPDATE_OUTPUTS = 'UPDATE_OUTPUTS'
export const UPDATE_PLAYER_STATUS = 'UPDATE_PLAYER_STATUS'
export const UPDATE_QUEUE = 'UPDATE_QUEUE'
export const UPDATE_LYRICS = 'UPDATE_LYRICS'
export const UPDATE_LASTFM = 'UPDATE_LASTFM'
export const UPDATE_SPOTIFY = 'UPDATE_SPOTIFY'
export const UPDATE_PAIRING = 'UPDATE_PAIRING'