2020-12-26 15:03:12 +01:00
|
|
|
<template>
|
|
|
|
<div class="extension fade-in" :class="{hidden: !expanded}">
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-3">
|
|
|
|
</div>
|
2023-11-06 01:54:12 +01:00
|
|
|
<div class="col-6 buttons">
|
2020-12-26 15:03:12 +01:00
|
|
|
<div class="buttons">
|
2021-01-05 00:50:24 +01:00
|
|
|
<button @click="$emit('previous')" title="Play previous track" v-if="buttons_.previous">
|
2020-12-26 15:03:12 +01:00
|
|
|
<i class="icon fa fa-step-backward"></i>
|
|
|
|
</button>
|
2021-01-05 00:50:24 +01:00
|
|
|
<button @click="$emit('stop')" v-if="buttons_.stop && status.state !== 'stop'" title="Stop playback">
|
2020-12-26 15:03:12 +01:00
|
|
|
<i class="icon fa fa-stop"></i>
|
|
|
|
</button>
|
2021-01-05 00:50:24 +01:00
|
|
|
<button @click="$emit('next')" title="Play next track" v-if="buttons_.next">
|
2020-12-26 15:03:12 +01:00
|
|
|
<i class="icon fa fa-step-forward"></i>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-3">
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="row">
|
2023-11-06 01:54:12 +01:00
|
|
|
<VolumeSlider :value="status.volume" :range="volumeRange" :status="status"
|
|
|
|
@mute="$emit('mute')" @unmute="$emit('unmute')"
|
|
|
|
@set-volume="$emit('set-volume', $event)" />
|
|
|
|
|
|
|
|
<ExtraControls :status="status" :buttons="buttons_"
|
|
|
|
@consume="$emit('consume', !status.consume)"
|
|
|
|
@random="$emit('random', !status.random)"
|
|
|
|
@repeat="$emit('repeat', !status.repeat)" />
|
2020-12-26 15:03:12 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="row">
|
2023-11-06 01:54:12 +01:00
|
|
|
<ProgressBar :elapsed="elapsed" :duration="duration" :status="status" @seek="$emit('seek', $event)" />
|
2020-12-26 15:03:12 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="controls">
|
2023-11-05 23:45:37 +01:00
|
|
|
<div class="playback-controls until tablet col-2">
|
2023-11-06 21:22:35 +01:00
|
|
|
<PlayPauseButton :status="status" @play="$emit('play')" @pause="$emit('pause')" />
|
2020-12-26 15:03:12 +01:00
|
|
|
</div>
|
|
|
|
|
2023-11-06 01:54:12 +01:00
|
|
|
<div class="track-container col-s-9 col-m-9 col-l-3">
|
2020-12-26 15:03:12 +01:00
|
|
|
<div class="track-info" v-if="track && status?.state !== 'stop'">
|
2021-01-14 00:15:35 +01:00
|
|
|
<div class="title" v-if="status.state === 'play' || status.state === 'pause'">
|
|
|
|
<a :href="$route.fullPath" v-text="track.title?.length ? track.title : '[No Title]'"
|
2021-01-01 15:58:37 +01:00
|
|
|
@click.prevent="$emit('search', {artist: track.artist, album: track.album})" v-if="track.album"></a>
|
2021-01-14 00:15:35 +01:00
|
|
|
<a :href="track.url" v-text="track.title?.length ? track.title : '[No Title]'" v-else-if="track.url"></a>
|
|
|
|
<span v-text="track.title?.length ? track.title : '[No Title]' " v-else></span>
|
2020-12-26 15:03:12 +01:00
|
|
|
</div>
|
2021-01-14 00:15:35 +01:00
|
|
|
<div class="artist" v-if="track.artist?.length && (status.state === 'play' || status.state === 'pause')">
|
2021-01-01 15:58:37 +01:00
|
|
|
<a :href="$route.fullPath" v-text="track.artist" @click.prevent="$emit('search', {artist: track.artist})"></a>
|
2020-12-26 15:03:12 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2023-11-05 23:45:37 +01:00
|
|
|
<div class="playback-controls from desktop col-6">
|
2020-12-26 15:03:12 +01:00
|
|
|
<div class="row buttons">
|
2021-01-05 00:50:24 +01:00
|
|
|
<button @click="$emit('previous')" title="Play previous track" v-if="buttons_.previous">
|
2020-12-26 15:03:12 +01:00
|
|
|
<i class="icon fa fa-step-backward"></i>
|
|
|
|
</button>
|
2023-11-06 21:22:35 +01:00
|
|
|
<PlayPauseButton :status="status" @play="$emit('play')" @pause="$emit('pause')" />
|
2021-01-05 00:50:24 +01:00
|
|
|
<button @click="$emit('stop')" v-if="buttons_.stop && status.state !== 'stop'" title="Stop playback">
|
2020-12-26 15:03:12 +01:00
|
|
|
<i class="icon fa fa-stop"></i>
|
|
|
|
</button>
|
2021-01-05 00:50:24 +01:00
|
|
|
<button @click="$emit('next')" title="Play next track" v-if="buttons_.next">
|
2020-12-26 15:03:12 +01:00
|
|
|
<i class="icon fa fa-step-forward"></i>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="row">
|
2023-11-06 01:54:12 +01:00
|
|
|
<ProgressBar :elapsed="elapsed" :duration="duration" :status="status" @seek="$emit('seek', $event)" />
|
2020-12-26 15:03:12 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2023-11-06 01:54:12 +01:00
|
|
|
<div class="col-1 until tablet right-controls">
|
2020-12-26 15:03:12 +01:00
|
|
|
<button @click="expanded = !expanded" :title="expanded ? 'Show more controls' : 'Hide extra controls'">
|
|
|
|
<i class="fas" :class="[`fa-chevron-${expanded ? 'down' : 'up'}`]" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
2023-11-06 01:54:12 +01:00
|
|
|
<div class="col-3 from desktop right-controls">
|
|
|
|
<VolumeSlider :value="status.volume" :range="volumeRange" :status="status"
|
|
|
|
@mute="$emit('mute')" @unmute="$emit('unmute')"
|
|
|
|
@set-volume="$emit('set-volume', $event)" />
|
2020-12-26 15:03:12 +01:00
|
|
|
|
2023-11-06 01:54:12 +01:00
|
|
|
<ExtraControls :status="status" :buttons="buttons_"
|
|
|
|
@consume="$emit('consume', !status.consume)"
|
|
|
|
@random="$emit('random', !status.random)"
|
|
|
|
@repeat="$emit('repeat', !status.repeat)" />
|
2020-12-26 15:03:12 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
import Utils from "@/Utils"
|
|
|
|
import MediaUtils from "@/components/Media/Utils";
|
2023-11-06 01:54:12 +01:00
|
|
|
import ExtraControls from "./ExtraControls";
|
2023-11-06 21:22:35 +01:00
|
|
|
import PlayPauseButton from "./PlayPauseButton";
|
2023-11-06 01:54:12 +01:00
|
|
|
import ProgressBar from "./ProgressBar";
|
2023-11-05 23:45:37 +01:00
|
|
|
import VolumeSlider from "./VolumeSlider";
|
2020-12-26 15:03:12 +01:00
|
|
|
|
|
|
|
export default {
|
2023-11-06 21:22:35 +01:00
|
|
|
components: {ExtraControls, PlayPauseButton, ProgressBar, VolumeSlider},
|
2020-12-26 15:03:12 +01:00
|
|
|
mixins: [Utils, MediaUtils],
|
2023-11-05 23:45:37 +01:00
|
|
|
emits: [
|
|
|
|
'consume',
|
|
|
|
'mute',
|
|
|
|
'next',
|
|
|
|
'pause',
|
|
|
|
'play',
|
|
|
|
'previous',
|
|
|
|
'random',
|
|
|
|
'repeat',
|
|
|
|
'search',
|
|
|
|
'seek',
|
|
|
|
'set-volume',
|
|
|
|
'stop',
|
|
|
|
'unmute',
|
|
|
|
],
|
2020-12-26 15:03:12 +01:00
|
|
|
|
|
|
|
props: {
|
|
|
|
track: {
|
|
|
|
type: Object,
|
|
|
|
},
|
|
|
|
|
|
|
|
status: {
|
|
|
|
type: Object,
|
|
|
|
default: () => {},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Enabled playback buttons
|
|
|
|
buttons: {
|
|
|
|
type: Object,
|
|
|
|
default: () => {
|
|
|
|
return {
|
|
|
|
previous: true,
|
|
|
|
next: true,
|
|
|
|
stop: true,
|
|
|
|
consume: true,
|
|
|
|
random: true,
|
|
|
|
repeat: true,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
// Volume range
|
|
|
|
volumeRange: {
|
|
|
|
type: Array,
|
|
|
|
default: () => [0, 100],
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
data() {
|
2021-01-05 00:50:24 +01:00
|
|
|
const buttons = Object.keys(this.buttons)?.length ? this.buttons : {
|
|
|
|
previous: true,
|
|
|
|
next: true,
|
|
|
|
stop: true,
|
|
|
|
consume: true,
|
|
|
|
random: true,
|
|
|
|
repeat: true,
|
|
|
|
}
|
|
|
|
|
2020-12-26 15:03:12 +01:00
|
|
|
return {
|
|
|
|
expanded: false,
|
|
|
|
lastSync: 0,
|
2021-01-05 00:50:24 +01:00
|
|
|
elapsed: this.status?.elapsed || this.status?.position,
|
|
|
|
buttons_: buttons,
|
2020-12-26 15:03:12 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
duration() {
|
|
|
|
return this.status?.duration != null ? this.status.duration : this.track?.duration
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
getTime() {
|
|
|
|
return (new Date()).getTime() / 1000
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
mounted() {
|
|
|
|
const self = this
|
2021-01-02 14:33:01 +01:00
|
|
|
this.lastSync = this.getTime()
|
2020-12-26 15:03:12 +01:00
|
|
|
|
2021-01-02 14:33:01 +01:00
|
|
|
this.$watch(() => this.track, (track) => {
|
2020-12-26 15:03:12 +01:00
|
|
|
if (!track || self.status?.state !== 'play')
|
|
|
|
self.lastSync = this.getTime()
|
|
|
|
})
|
|
|
|
|
2021-01-02 14:33:01 +01:00
|
|
|
this.$watch(() => this.status, () => {
|
2020-12-26 15:03:12 +01:00
|
|
|
self.lastSync = this.getTime()
|
|
|
|
})
|
|
|
|
|
|
|
|
setInterval(() => {
|
2021-01-05 00:50:24 +01:00
|
|
|
if (self.status?.state !== 'stop') {
|
|
|
|
self.elapsed = (self.status?.elapsed || self.status?.position || 0)
|
|
|
|
if (self.status?.state === 'play')
|
|
|
|
self.elapsed += Math.round(this.getTime() - self.lastSync)
|
|
|
|
}
|
2020-12-26 15:03:12 +01:00
|
|
|
}, 1000)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
@import 'vars.scss';
|
|
|
|
|
|
|
|
button {
|
|
|
|
border: 0;
|
2021-02-21 18:10:15 +01:00
|
|
|
background: none;
|
|
|
|
|
2020-12-26 15:03:12 +01:00
|
|
|
&:hover {
|
|
|
|
border: 0;
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
color: $default-hover-fg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&.enabled {
|
|
|
|
color: $selected-fg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.extension {
|
|
|
|
box-shadow: $border-shadow-bottom;
|
|
|
|
flex-direction: column;
|
|
|
|
display: none;
|
|
|
|
overflow: hidden;
|
2023-11-06 01:54:12 +01:00
|
|
|
padding: .5em;
|
2020-12-26 15:03:12 +01:00
|
|
|
|
|
|
|
@include until($desktop) {
|
|
|
|
display: flex;
|
2023-11-06 01:54:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.extra-controls-container) {
|
|
|
|
@extend .pull-right;
|
|
|
|
flex: 1;
|
2020-12-26 15:03:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
.row {
|
|
|
|
display: flex;
|
|
|
|
}
|
|
|
|
|
|
|
|
.buttons {
|
2023-11-06 01:54:12 +01:00
|
|
|
display: flex;
|
2020-12-26 15:03:12 +01:00
|
|
|
justify-content: center;
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.controls {
|
|
|
|
width: 100%;
|
|
|
|
height: $media-ctrl-panel-height;
|
|
|
|
display: flex;
|
|
|
|
padding: 1em .5em;
|
|
|
|
overflow: hidden;
|
2023-11-05 23:45:37 +01:00
|
|
|
align-items: center;
|
2020-12-26 15:03:12 +01:00
|
|
|
|
|
|
|
.row {
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
}
|
|
|
|
|
|
|
|
.track-container {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: center;
|
|
|
|
margin-left: 0;
|
|
|
|
|
|
|
|
@include until($tablet) {
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
a {
|
|
|
|
color: initial;
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
color: $default-hover-fg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.artist, .title {
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
}
|
|
|
|
|
|
|
|
.artist {
|
|
|
|
opacity: 0.6;
|
|
|
|
letter-spacing: .04em;
|
|
|
|
}
|
|
|
|
|
|
|
|
.title {
|
|
|
|
font-weight: normal;
|
|
|
|
font-size: 1em;
|
|
|
|
letter-spacing: .05em;
|
|
|
|
margin-bottom: .25em;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.playback-controls {
|
|
|
|
.row {
|
|
|
|
justify-content: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.buttons {
|
2022-03-30 01:43:59 +02:00
|
|
|
margin-bottom: .5em;
|
2020-12-26 15:03:12 +01:00
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
button {
|
2022-03-30 01:43:59 +02:00
|
|
|
padding: 0.5em;
|
2020-12-26 15:03:12 +01:00
|
|
|
margin: 0 .75em;
|
|
|
|
|
|
|
|
.play-pause {
|
|
|
|
color: $play-btn-fg;
|
|
|
|
font-size: 1.75em;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
color: $default-hover-fg-2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-06 01:54:12 +01:00
|
|
|
.right-controls {
|
|
|
|
@extend .pull-right;
|
2020-12-26 15:03:12 +01:00
|
|
|
|
2023-11-06 01:54:12 +01:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
flex: 1;
|
2023-11-06 02:25:54 +01:00
|
|
|
align-items: flex-end;
|
2020-12-26 15:03:12 +01:00
|
|
|
|
|
|
|
button {
|
2022-03-30 01:43:59 +02:00
|
|
|
padding: 0.5em;
|
2020-12-26 15:03:12 +01:00
|
|
|
}
|
2023-11-06 01:54:12 +01:00
|
|
|
|
|
|
|
:deep(.extra-controls-container) {
|
|
|
|
@extend .pull-right;
|
|
|
|
}
|
2020-12-26 15:03:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
.seek-slider {
|
|
|
|
width: 75%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|