Newer
Older
Eliot Berriot
committed
<template>
<div class="ui inverted segment player-wrapper" :style="style">
<div class="player">
<audio-track
ref="currentAudio"
Eliot Berriot
committed
v-if="renderAudio && currentTrack"
:key="(currentIndex, currentTrack.id)"
:is-current="true"
:start-time="$store.state.player.currentTime"
:autoplay="$store.state.player.playing"
:track="currentTrack">
</audio-track>
<div v-if="currentTrack" class="track-area ui unstackable items">
<div class="ui inverted item">
<div class="ui tiny image">
<img ref="cover" @load="updateBackground" v-if="currentTrack.album.cover" :src="Track.getCover(currentTrack)">
<img v-else src="../../assets/audio/default-cover.png">
Eliot Berriot
committed
</div>
<div class="middle aligned content">
<router-link class="small header discrete link track" :to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}">
{{ currentTrack.title }}
</router-link>
<div class="meta">
<router-link class="artist" :to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}">
{{ currentTrack.artist.name }}
</router-link> /
<router-link class="album" :to="{name: 'library.albums.detail', params: {id: currentTrack.album.id }}">
{{ currentTrack.album.title }}
</router-link>
</div>
<div class="description">
<track-favorite-icon :track="currentTrack"></track-favorite-icon>
</div>
Eliot Berriot
committed
</div>
</div>
</div>
<div class="progress-area" v-if="currentTrack">
<div class="ui grid">
<div class="left floated four wide column">
<p class="timer start" @click="updateProgress(0)">{{currentTimeFormatted}}</p>
</div>
Eliot Berriot
committed
<div class="right floated four wide column">
<p class="timer total">{{durationFormatted}}</p>
</div>
</div>
<div ref="progress" class="ui small orange inverted progress" @click="touchProgress">
<div class="bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div>
Eliot Berriot
committed
</div>
</div>
<div class="two wide column controls ui grid">
<div
title="Previous track"
class="two wide column control"
:disabled="!hasPrevious">
<i @click="previous" :class="['ui', {'disabled': !hasPrevious}, 'step', 'backward', 'big', 'icon']" ></i>
</div>
<div
v-if="!playing"
title="Play track"
class="two wide column control">
<i @click="togglePlay" :class="['ui', 'play', {'disabled': !currentTrack}, 'big', 'icon']"></i>
</div>
<div
v-else
title="Pause track"
class="two wide column control">
<i @click="togglePlay" :class="['ui', 'pause', {'disabled': !currentTrack}, 'big', 'icon']"></i>
</div>
<div
title="Next track"
class="two wide column control"
:disabled="!hasNext">
<i @click="next" :class="['ui', {'disabled': !hasNext}, 'step', 'forward', 'big', 'icon']" ></i>
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
</div>
<div class="two wide column control volume-control">
<i title="Unmute" @click="$store.commit('player/volume', 1)" v-if="volume === 0" class="volume off secondary icon"></i>
<i title="Mute" @click="$store.commit('player/volume', 0)" v-else-if="volume < 0.5" class="volume down secondary icon"></i>
<i title="Mute" @click="$store.commit('player/volume', 0)" v-else class="volume up secondary icon"></i>
<input type="range" step="0.05" min="0" max="1" v-model="sliderVolume" />
</div>
<div class="two wide column control looping">
<i
title="Looping disabled. Click to switch to single-track looping."
v-if="looping === 0"
@click="$store.commit('player/looping', 1)"
:disabled="!currentTrack"
:class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'secondary', 'icon']"></i>
<i
title="Looping on a single track. Click to switch to whole queue looping."
v-if="looping === 1"
@click="$store.commit('player/looping', 2)"
:disabled="!currentTrack"
class="repeat secondary icon">
<span class="ui circular tiny orange label">1</span>
</i>
<i
title="Looping on whole queue. Click to disable looping."
v-if="looping === 2"
@click="$store.commit('player/looping', 0)"
:disabled="!currentTrack"
class="repeat orange secondary icon">
</i>
</div>
<div
:disabled="queue.tracks.length === 0"
title="Shuffle your queue"
class="two wide column control">
<i @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</div>
<div class="one wide column"></div>
<div
:disabled="queue.tracks.length === 0"
title="Clear your queue"
class="two wide column control">
<i @click="clean()" :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</div>
Eliot Berriot
committed
</div>
<GlobalEvents
@keydown.space.prevent.exact="togglePlay"
@keydown.ctrl.left.prevent.exact="previous"
@keydown.ctrl.right.prevent.exact="next"
@keydown.ctrl.down.prevent.exact="$store.commit('player/incrementVolume', -0.1)"
@keydown.ctrl.up.prevent.exact="$store.commit('player/incrementVolume', 0.1)"
@keydown.f.prevent.exact="$store.dispatch('favorites/toggle', currentTrack.id)"
@keydown.l.prevent.exact="$store.commit('player/toggleLooping')"
@keydown.s.prevent.exact="shuffle"
/>
Eliot Berriot
committed
</div>
</div>
</template>
<script>
import {mapState, mapGetters, mapActions} from 'vuex'
import GlobalEvents from '@/components/utils/global-events'
import ColorThief from '@/vendor/color-thief'
Eliot Berriot
committed
import Track from '@/audio/track'
import AudioTrack from '@/components/audio/Track'
Eliot Berriot
committed
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
export default {
name: 'player',
components: {
GlobalEvents,
AudioTrack
Eliot Berriot
committed
},
data () {
let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]]
Eliot Berriot
committed
return {
Eliot Berriot
committed
renderAudio: true,
sliderVolume: this.volume,
Track: Track,
defaultAmbiantColors: defaultAmbiantColors,
ambiantColors: defaultAmbiantColors
Eliot Berriot
committed
}
},
mounted () {
// we trigger the watcher explicitely it does not work otherwise
this.sliderVolume = this.volume
Eliot Berriot
committed
},
methods: {
...mapActions({
togglePlay: 'player/togglePlay',
clean: 'queue/clean',
next: 'queue/next',
previous: 'queue/previous',
shuffle: 'queue/shuffle',
updateProgress: 'player/updateProgress'
}),
Eliot Berriot
committed
touchProgress (e) {
let time
let target = this.$refs.progress
time = e.layerX / target.offsetWidth * this.duration
this.$refs.currentAudio.setCurrentTime(time)
},
updateBackground () {
if (!this.currentTrack.album.cover) {
this.ambiantColors = this.defaultAmbiantColors
return
}
let image = this.$refs.cover
this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4)
Eliot Berriot
committed
}
},
computed: {
...mapState({
currentIndex: state => state.queue.currentIndex,
playing: state => state.player.playing,
volume: state => state.player.volume,
looping: state => state.player.looping,
duration: state => state.player.duration,
queue: state => state.queue
}),
...mapGetters({
currentTrack: 'queue/currentTrack',
hasNext: 'queue/hasNext',
hasPrevious: 'queue/hasPrevious',
durationFormatted: 'player/durationFormatted',
currentTimeFormatted: 'player/currentTimeFormatted',
progress: 'player/progress'
}),
style: function () {
let style = {
'background': this.ambiantGradiant
}
return style
},
ambiantGradiant: function () {
let indexConf = [
{orientation: 330, percent: 100, opacity: 0.7},
{orientation: 240, percent: 90, opacity: 0.7},
{orientation: 150, percent: 80, opacity: 0.7},
{orientation: 60, percent: 70, opacity: 0.7}
]
let gradients = this.ambiantColors.map((e, i) => {
let [r, g, b] = e
let conf = indexConf[i]
return `linear-gradient(${conf.orientation}deg, rgba(${r}, ${g}, ${b}, ${conf.opacity}) 10%, rgba(255, 255, 255, 0) ${conf.percent}%)`
}).join(', ')
return gradients
}
Eliot Berriot
committed
},
watch: {
currentTrack (newValue) {
Eliot Berriot
committed
if (!newValue || !newValue.album.cover) {
this.ambiantColors = this.defaultAmbiantColors
}
},
Eliot Berriot
committed
currentIndex (newValue, oldValue) {
if (newValue !== oldValue) {
// why this? to ensure the audio tag is deleted and fully
// rerendered, so we don't have any issues with cached position
// or whatever
this.renderAudio = false
this.$nextTick(() => {
this.renderAudio = true
})
}
},
volume (newValue) {
Eliot Berriot
committed
this.sliderVolume = newValue
},
sliderVolume (newValue) {
this.$store.commit('player/volume', newValue)
Eliot Berriot
committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.ui.progress {
margin: 0.5rem 0 1rem;
}
.progress {
cursor: pointer;
.bar {
min-width: 0 !important;
}
}
.ui.inverted.item > .content > .description {
color: rgba(255, 255, 255, 0.9) !important;
}
.ui.item {
.meta {
font-size: 90%;
line-height: 1.2
}
}
.timer.total {
text-align: right;
}
.timer.start {
cursor: pointer
}
.track-area {
.header, .meta, .artist, .album {
color: white !important;
}
}
.controls .icon.big {
cursor: pointer;
font-size: 2em !important;
}
.controls .icon {
cursor: pointer;
vertical-align: middle;
}
.secondary.icon {
font-size: 1.5em;
}
.progress-area .actions {
text-align: center;
}
.volume-control {
position: relative;
.icon {
Eliot Berriot
committed
}
[type="range"] {
Eliot Berriot
committed
position: absolute;
bottom: 5px;
left: 10%;
cursor: pointer;
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
input[type=range] {
-webkit-appearance: none;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
cursor: pointer;
background: white;
}
input[type=range]::-webkit-slider-thumb {
background: white;
cursor: pointer;
-webkit-appearance: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #white;
}
input[type=range]::-moz-range-track {
cursor: pointer;
background: white;
}
input[type=range]::-moz-range-thumb {
background: white;
cursor: pointer;
}
input[type=range]::-ms-track {
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: white;
}
input[type=range]::-ms-fill-upper {
background: white;
}
input[type=range]::-ms-thumb {
background: white;
cursor: pointer;
}
input[type=range]:focus::-ms-fill-lower {
background: white;
}
input[type=range]:focus::-ms-fill-upper {
background: #white;
}
&:hover {
[type="range"] {
display: block;
}
}
}
.looping.control {
i {
position: relative;
}
.label {
position: absolute;
font-size: 0.7rem;
bottom: -0.7rem;
right: -0.7rem;
Eliot Berriot
committed
}
}
.ui.feed.icon {
margin: 0;
}
</style>