diff --git a/CHANGELOG b/CHANGELOG index d9d896035c752ba4680a1ff268dd2178e8dfb854..2f6cbe48bca3afd1cc77de5dd01a7d77f660ef12 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ 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) 0.2.6 (2017-12-15) diff --git a/front/src/audio/queue.js b/front/src/audio/queue.js index 8c69638e80ceb46adfdd182010ced55898b6850f..bb3f0f9055fafa10deac458b7806d9f857ef1a8f 100644 --- a/front/src/audio/queue.js +++ b/front/src/audio/queue.js @@ -17,6 +17,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 +268,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 +308,14 @@ class Queue { } } + toggleLooping () { + if (this.state.looping > 1) { + this.state.looping = 0 + } else { + this.state.looping += 1 + } + } + } let queue = new Queue() diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index aff3e65bd5bc0999b16c15874f04e22b5bf9b166..8cdb261c535023f50dc218b1712aeb3b099489d2 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -39,21 +39,71 @@ </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="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 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="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="three 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 +113,7 @@ @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" /> </div> @@ -187,14 +238,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 {