From a910929132d161b5bb5b1feb8d5eef6857d16931 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Sun, 18 Feb 2018 14:31:52 +0100 Subject: [PATCH] Fix #59: Use color-thief for setting player colors based on track cover --- front/src/components/Sidebar.vue | 10 +- front/src/components/audio/Player.vue | 290 +++++++++++++++----------- 2 files changed, 166 insertions(+), 134 deletions(-) diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index df9eb00e..86ec5781 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -87,9 +87,7 @@ </div> </div> </div> - <div class="ui inverted segment player-wrapper"> - <player></player> - </div> + <player></player> </div> </template> @@ -150,6 +148,7 @@ export default { $sidebar-color: #1B1C1D; .sidebar { + background: $sidebar-color; @include media(">tablet") { display:flex; flex-direction:column; @@ -211,11 +210,6 @@ $sidebar-color: #1B1C1D; flex: 1; } -.player-wrapper { - border-top: 1px solid rgba(255, 255, 255, 0.1) !important; - background-color: rgb(46, 46, 46) !important; -} - .logo { cursor: pointer; display: inline-block; diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index 9388c268..e44a92d4 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -1,145 +1,147 @@ <template> - <div class="player"> - <audio-track - ref="currentAudio" - v-if="currentTrack" - :key="(currentIndex, currentTrack.id)" - :is-current="true" - :start-time="$store.state.player.currentTime" - :autoplay="$store.state.player.playing" - :track="currentTrack"> - </audio-track> + <div class="ui inverted segment player-wrapper" :style="style"> + <div class="player"> + <audio-track + ref="currentAudio" + v-if="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 v-if="currentTrack.album.cover" :src="Track.getCover(currentTrack)"> - <img v-else src="../../assets/audio/default-cover.png"> - </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 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"> </div> - <div class="description"> - <track-favorite-icon :track="currentTrack"></track-favorite-icon> + <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> </div> </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> + <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> - <div class="right floated four wide column"> - <p class="timer total">{{durationFormatted}}</p> + <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> </div> </div> - <div ref="progress" class="ui small orange inverted progress" @click="touchProgress"> - <div class="bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div> - </div> - </div> - <div class="two wide column controls ui grid"> - <div - @click="previous" - title="Previous track" - class="two wide column control" - :disabled="!hasPrevious"> - <i :class="['ui', {'disabled': !hasPrevious}, 'step', 'backward', 'big', 'icon']" ></i> - </div> - <div - v-if="!playing" - @click="togglePlay" - title="Play track" - class="two wide column control"> - <i :class="['ui', 'play', {'disabled': !currentTrack}, 'big', 'icon']"></i> - </div> - <div - v-else - @click="togglePlay" - title="Pause track" - class="two wide column control"> - <i :class="['ui', 'pause', {'disabled': !currentTrack}, 'big', 'icon']"></i> - </div> - <div - @click="next" - title="Next track" - class="two wide column control" - :disabled="!hasNext"> - <i :class="['ui', {'disabled': !hasNext}, 'step', 'forward', 'big', 'icon']" ></i> - </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 - @click="shuffle()" - :disabled="queue.tracks.length === 0" - title="Shuffle your queue" - class="two wide column control"> - <i :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> - </div> - <div class="one wide column"></div> - <div - @click="clean()" - :disabled="queue.tracks.length === 0" - title="Clear your queue" - class="two wide column control"> - <i :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> + <div class="two wide column controls ui grid"> + <div + @click="previous" + title="Previous track" + class="two wide column control" + :disabled="!hasPrevious"> + <i :class="['ui', {'disabled': !hasPrevious}, 'step', 'backward', 'big', 'icon']" ></i> + </div> + <div + v-if="!playing" + @click="togglePlay" + title="Play track" + class="two wide column control"> + <i :class="['ui', 'play', {'disabled': !currentTrack}, 'big', 'icon']"></i> + </div> + <div + v-else + @click="togglePlay" + title="Pause track" + class="two wide column control"> + <i :class="['ui', 'pause', {'disabled': !currentTrack}, 'big', 'icon']"></i> + </div> + <div + @click="next" + title="Next track" + class="two wide column control" + :disabled="!hasNext"> + <i :class="['ui', {'disabled': !hasNext}, 'step', 'forward', 'big', 'icon']" ></i> + </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 + @click="shuffle()" + :disabled="queue.tracks.length === 0" + title="Shuffle your queue" + class="two wide column control"> + <i :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> + </div> + <div class="one wide column"></div> + <div + @click="clean()" + :disabled="queue.tracks.length === 0" + title="Clear your queue" + class="two wide column control"> + <i :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> + </div> </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" + /> </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" - /> - </div> </template> <script> import {mapState, mapGetters, mapActions} from 'vuex' import GlobalEvents from '@/components/utils/global-events' +import ColorThief from '@/vendor/color-thief' import Track from '@/audio/track' import AudioTrack from '@/components/audio/Track' @@ -153,9 +155,12 @@ export default { AudioTrack }, data () { + let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]] return { sliderVolume: this.volume, - Track: Track + Track: Track, + defaultAmbiantColors: defaultAmbiantColors, + ambiantColors: defaultAmbiantColors } }, mounted () { @@ -177,6 +182,14 @@ export default { 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) } }, computed: { @@ -195,9 +208,34 @@ export default { 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 + } }, watch: { + currentTrack (newValue) { + if (!newValue) { + this.ambiantColors = this.defaultAmbiantColors + } + }, volume (newValue) { this.sliderVolume = newValue }, -- GitLab