mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-27 15:45:56 -05:00
Revert "[web] Add lyrics player to the webinterface"
This reverts commit 98a844b409
.
This commit is contained in:
parent
e4c47c22b3
commit
a9e20abf06
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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)
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -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>
|
@ -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,
|
||||
|
@ -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>
|
@ -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,
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -578,8 +578,7 @@
|
||||
"shuffle-disabled": "Lire les pistes dans l’ordre",
|
||||
"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": {
|
||||
|
@ -578,8 +578,7 @@
|
||||
"shuffle-disabled": "按顺序播放曲目",
|
||||
"skip-backward": "播放上一首",
|
||||
"skip-forward": "播放下一首",
|
||||
"stop": "停止",
|
||||
"toggle-lyrics": "显示/隐藏歌词"
|
||||
"stop": "停止"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user