diff --git a/CHANGELOG b/CHANGELOG index d9d896035c752ba4680a1ff268dd2178e8dfb854..aa2b67dfa748c9d0236c3ed78de7b681bac575ea 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ Changelog - Shortcuts: can now use the ``f`` shortcut to toggle the currently playing track as a favorite (#53) - Shortcuts: avoid collisions between shortcuts by using the exact modifier (#53) +- Player: Added looping controls and shortcuts (#52) +- Player: Added shuffling controls and shortcuts (#52) 0.2.6 (2017-12-15) diff --git a/front/src/audio/queue.js b/front/src/audio/queue.js index 8c69638e80ceb46adfdd182010ced55898b6850f..4273fb9a630dd098458b532cd85f5bdb96399aba 100644 --- a/front/src/audio/queue.js +++ b/front/src/audio/queue.js @@ -1,10 +1,12 @@ +import Vue from 'vue' +import _ from 'lodash' + import logger from '@/logging' import cache from '@/cache' import config from '@/config' import Audio from '@/audio' import backend from '@/audio/backend' import radios from '@/radios' -import Vue from 'vue' import url from '@/utils/url' import auth from '@/auth' @@ -17,6 +19,7 @@ class Queue { this.currentTrack = null this.ended = true this.state = { + looping: 0, // 0 -> no, 1 -> on track, 2 -> on queue volume: cache.get('volume', 0.5) } this.audio = { @@ -267,12 +270,22 @@ class Queue { handleAudioEnded (e) { this.recordListen(this.currentTrack) + if (this.state.looping === 1) { + // we loop on the same track + logger.default.info('Looping on the same track') + return this.play(this.currentIndex) + } if (this.currentIndex < this.tracks.length - 1) { logger.default.info('Audio track ended, playing next one') - this.next() + return this.next() } else { logger.default.info('We reached the end of the queue') - this.ended = true + if (this.state.looping === 2) { + logger.default.info('Going back to the beginning of the queue') + return this.play(0) + } else { + this.ended = true + } } } @@ -297,6 +310,21 @@ class Queue { } } + toggleLooping () { + if (this.state.looping > 1) { + this.state.looping = 0 + } else { + this.state.looping += 1 + } + } + + shuffle () { + let tracks = this.tracks + let shuffled = _.shuffle(tracks) + this.clean() + this.appendMany(shuffled) + } + } let queue = new Queue() diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index aff3e65bd5bc0999b16c15874f04e22b5bf9b166..c862660ad60ed0960cc5b0e66a2380810cdef9b7 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -39,21 +39,78 @@ </div> </div> - <div class="controls ui grid"> - <div class="volume-control four wide center aligned column"> - <input type="range" step="0.05" min="0" max="1" v-model="sliderVolume" /> + <div class="two wide column controls ui grid"> + <div + @click="queue.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="!queue.audio.state.playing" + @click="pauseOrPlay" + title="Play track" + class="two wide column control"> + <i :class="['ui', 'play', {'disabled': !queue.currentTrack}, 'big', 'icon']"></i> + </div> + <div + v-else + @click="pauseOrPlay" + title="Pause track" + class="two wide column control"> + <i :class="['ui', 'pause', {'disabled': !queue.currentTrack}, 'big', 'icon']"></i> + </div> + <div + @click="queue.next()" + title="Next track" + class="two wide column control" + :disabled="!hasNext"> + <i :class="['ui', {'disabled': !hasPrevious}, 'step', 'forward', 'big', 'icon']" ></i> + </div> + <div class="two wide column control volume-control"> <i title="Unmute" @click="queue.setVolume(1)" v-if="currentVolume === 0" class="volume off secondary icon"></i> <i title="Mute" @click="queue.setVolume(0)" v-else-if="currentVolume < 0.5" class="volume down secondary icon"></i> <i title="Mute" @click="queue.setVolume(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="queue.state.looping === 0" + @click="queue.state.looping = 1" + :disabled="!queue.currentTrack" + :class="['ui', {'disabled': !queue.currentTrack}, 'step', 'repeat', 'secondary', 'icon']"></i> + <i + title="Looping on a single track. Click to switch to whole queue looping." + v-if="queue.state.looping === 1" + @click="queue.state.looping = 2" + :disabled="!queue.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="queue.state.looping === 2" + @click="queue.state.looping = 0" + :disabled="!queue.currentTrack" + class="repeat orange secondary icon"> + </i> </div> - <div class="eight wide center aligned column"> - <i title="Previous track" @click="queue.previous()" :class="['ui', {'disabled': !hasPrevious}, 'step', 'backward', 'big', 'icon']" :disabled="!hasPrevious"></i> - <i title="Play track" v-if="!queue.audio.state.playing" :class="['ui', 'play', {'disabled': !queue.currentTrack}, 'big', 'icon']" @click="pauseOrPlay"></i> - <i title="Pause track" v-else :class="['ui', 'pause', {'disabled': !queue.currentTrack}, 'big', 'icon']" @click="pauseOrPlay"></i> - <i title="Next track" @click="queue.next()" :class="['ui', 'step', 'forward', {'disabled': !hasNext}, 'big', 'icon']" :disabled="!hasNext"></i> + <div + @click="queue.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="four wide center aligned column"> - <i title="Clear your queue" @click="queue.clean()" :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" :disabled="queue.tracks.length === 0"></i> + <div class="one wide column"></div> + <div + @click="queue.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 @@ -63,6 +120,8 @@ @keydown.ctrl.down.prevent.exact="queue.incrementVolume(-0.1)" @keydown.ctrl.up.prevent.exact="queue.incrementVolume(0.1)" @keydown.f.prevent.exact="favoriteTracks.toggle(queue.currentTrack.id)" + @keydown.l.prevent.exact="queue.toggleLooping" + @keydown.s.prevent.exact="queue.shuffle" /> </div> @@ -187,14 +246,32 @@ export default { .volume-control { position: relative; .icon { - margin: 0; + // margin: 0; } [type="range"] { - max-width: 75%; + max-width: 100%; position: absolute; bottom: 5px; left: 10%; cursor: pointer; + display: none; + } + &:hover { + [type="range"] { + display: block; + } + } +} + +.looping.control { + i { + position: relative; + } + .label { + position: absolute; + font-size: 0.7rem; + bottom: -0.7rem; + right: -0.7rem; } } .ui.feed.icon {