diff --git a/changes/changelog.d/310.bugfix b/changes/changelog.d/310.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..fed21ded8978942b3586334e51eefabd999e258e --- /dev/null +++ b/changes/changelog.d/310.bugfix @@ -0,0 +1 @@ +Fixed current track restart/hiccup when shuffling queue, deleting track from queue or reordering (#310) diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index 5415e1b0e036e66b83cb72f4e8611b376550e15d..9eec6c0e2721f50ba9209474cabddf028ca4c163 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -116,8 +116,8 @@ </div> <div class="ui bottom attached tab" data-tab="queue"> <table class="ui compact inverted very basic fixed single line unstackable table"> - <draggable v-model="queue.tracks" element="tbody" @update="reorder"> - <tr @click="$store.dispatch('queue/currentIndex', index)" v-for="(track, index) in queue.tracks" :key="index" :class="[{'active': index === queue.currentIndex}]"> + <draggable v-model="tracks" element="tbody" @update="reorder"> + <tr @click="$store.dispatch('queue/currentIndex', index)" v-for="(track, index) in tracks" :key="index" :class="[{'active': index === queue.currentIndex}]"> <td class="right aligned">{{ index + 1}}</td> <td class="center aligned"> <img class="ui mini image" v-if="track.album.cover" :src="$store.getters['instance/absoluteUrl'](track.album.cover)"> @@ -176,6 +176,7 @@ export default { return { selectedTab: 'library', backend: backend, + tracksChangeBuffer: null, isCollapsed: true, fetchInterval: null } @@ -207,6 +208,14 @@ export default { return adminPermissions.filter(e => { return e }).length > 0 + }, + tracks: { + get () { + return this.$store.state.queue.tracks + }, + set (value) { + this.tracksChangeBuffer = value + } } }, methods: { @@ -219,7 +228,7 @@ export default { }, reorder: function (event) { this.$store.commit('queue/reorder', { - oldIndex: event.oldIndex, newIndex: event.newIndex}) + tracks: this.tracksChangeBuffer, oldIndex: event.oldIndex, newIndex: event.newIndex}) }, scrollToCurrent () { let current = $(this.$el).find('[data-tab="queue"] .active')[0] diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index 1cc27970b47aabc5571047713d1ee45e3f7b853a..8eecb232f67b770be9f28477c4a3380f56a3607d 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -1,16 +1,16 @@ <template> <div class="ui inverted segment player-wrapper" :style="style"> <div class="player"> - <audio-track - ref="currentAudio" - v-if="renderAudio && currentTrack" - :key="currentTrack.id" - :is-current="true" - :start-time="$store.state.player.currentTime" - :autoplay="$store.state.player.playing" - :track="currentTrack"> - </audio-track> - + <keep-alive> + <audio-track + ref="currentAudio" + v-if="renderAudio && currentTrack" + :is-current="true" + :start-time="$store.state.player.currentTime" + :autoplay="$store.state.player.playing" + :track="currentTrack"> + </audio-track> + </keep-alive> <div v-if="currentTrack" class="track-area ui unstackable items"> <div class="ui inverted item"> <div class="ui tiny image"> diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue index 2ded7cb0076f29b2f78956aae2ff8703fb2da9c8..9be38337717e8d024e3f711e019a960762d7c355 100644 --- a/front/src/components/audio/Track.vue +++ b/front/src/components/audio/Track.vue @@ -4,7 +4,7 @@ @error="errored" @loadeddata="loaded" @durationchange="updateDuration" - @timeupdate="updateProgress" + @timeupdate="updateProgressThrottled" @ended="ended" preload> <source @@ -30,6 +30,7 @@ export default { }, data () { return { + realTrack: this.track, sourceErrors: 0, isUpdatingTime: false } @@ -43,7 +44,7 @@ export default { looping: state => state.player.looping }), srcs: function () { - let file = this.track.files[0] + let file = this.realTrack.files[0] if (!file) { this.$store.dispatch('player/trackErrored') return [] @@ -61,6 +62,9 @@ export default { }) } return sources + }, + updateProgressThrottled () { + return _.throttle(this.updateProgress, 250) } }, methods: { @@ -100,30 +104,40 @@ export default { } } }, - updateProgress: _.throttle(function () { + updateProgress: function () { this.isUpdatingTime = true if (this.$refs.audio) { this.$store.dispatch('player/updateProgress', this.$refs.audio.currentTime) } - }, 250), + }, ended: function () { let onlyTrack = this.$store.state.queue.tracks.length === 1 if (this.looping === 1 || (onlyTrack && this.looping === 2)) { this.setCurrentTime(0) this.$refs.audio.play() } else { - this.$store.dispatch('player/trackEnded', this.track) + this.$store.dispatch('player/trackEnded', this.realTrack) } }, setCurrentTime (t) { if (t < 0 | t > this.duration) { return } - this.updateProgress(t) + if (t === this.$refs.audio.currentTime) { + return + } + if (t === 0) { + this.updateProgressThrottled.cancel() + } this.$refs.audio.currentTime = t } }, watch: { + track: _.debounce(function (newValue) { + this.realTrack = newValue + this.setCurrentTime(0) + this.$refs.audio.load() + }, 1000, {leading: true, trailing: true}), playing: function (newValue) { if (newValue === true) { this.$refs.audio.play() @@ -131,6 +145,11 @@ export default { this.$refs.audio.pause() } }, + '$store.state.queue.currentIndex' () { + if (this.$store.state.player.playing) { + this.$refs.audio.play() + } + }, volume: function (newValue) { this.$refs.audio.volume = newValue }, diff --git a/front/src/store/queue.js b/front/src/store/queue.js index 2d6c667b29ba5e87623f3cc60e7be31e0d5d3fc4..0435c867ee1137c308f997bd2442a330fa0ffcf8 100644 --- a/front/src/store/queue.js +++ b/front/src/store/queue.js @@ -31,9 +31,10 @@ export default { insert (state, {track, index}) { state.tracks.splice(index, 0, track) }, - reorder (state, {oldIndex, newIndex}) { + reorder (state, {tracks, oldIndex, newIndex}) { // called when the user uses drag / drop to reorder // tracks in queue + state.tracks = tracks if (oldIndex === state.currentIndex) { state.currentIndex = newIndex return @@ -102,7 +103,7 @@ export default { } if (current) { // we play next track, which now have the same index - dispatch('currentIndex', index) + commit('currentIndex', index) } if (state.currentIndex + 1 === state.tracks.length) { dispatch('radios/populateQueue', null, {root: true}) @@ -156,7 +157,6 @@ export default { let toKeep = state.tracks.slice(0, state.currentIndex + 1) let toShuffle = state.tracks.slice(state.currentIndex + 1) let shuffled = toKeep.concat(_.shuffle(toShuffle)) - commit('player/currentTime', 0, {root: true}) commit('tracks', []) let params = {tracks: shuffled} if (callback) { diff --git a/front/test/unit/specs/store/queue.spec.js b/front/test/unit/specs/store/queue.spec.js index 3a59117d54f8d833f0a35ffe63884981849030fd..cc2f04fa087714cd8a0fcb8afc03be87ab9688f9 100644 --- a/front/test/unit/specs/store/queue.spec.js +++ b/front/test/unit/specs/store/queue.spec.js @@ -169,11 +169,11 @@ describe('store/queue', () => { payload: 2, params: {state: {currentIndex: 2}}, expectedMutations: [ - { type: 'splice', payload: {start: 2, size: 1} } + { type: 'splice', payload: {start: 2, size: 1} }, + { type: 'currentIndex', payload: 2 } ], expectedActions: [ - { type: 'player/stop', payload: null, options: {root: true} }, - { type: 'currentIndex', payload: 2 } + { type: 'player/stop', payload: null, options: {root: true} } ] }, done) }) @@ -324,7 +324,6 @@ describe('store/queue', () => { action: store.actions.shuffle, params: {state: {currentIndex: 1, tracks: tracks}}, expectedMutations: [ - { type: 'player/currentTime', payload: 0, options: {root: true} }, { type: 'tracks', payload: [] } ], expectedActions: [ diff --git a/front/test/unit/specs/store/ui.spec.js b/front/test/unit/specs/store/ui.spec.js index adcfa87d8f34bd600f113fc4100c0c6318bc730e..ddce055a571e887a3b7bf3bcefdff901e033ea40 100644 --- a/front/test/unit/specs/store/ui.spec.js +++ b/front/test/unit/specs/store/ui.spec.js @@ -1,7 +1,5 @@ import store from '@/store/ui' -import { testAction } from '../../utils' - describe('store/ui', () => { describe('mutations', () => { it('addMessage', () => {