diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue index dcb1c507e121441aa9712aaca403f8dcc5759861..521a66f8715d0fce86e569ea1c2daa41e20630f6 100644 --- a/front/src/components/audio/PlayButton.vue +++ b/front/src/components/audio/PlayButton.vue @@ -79,10 +79,14 @@ export default { return true } if (this.track) { - return this.track.is_playable + return this.track.uploads && this.track.uploads.length > 0 + } else if (this.artist) { + return this.albums.filter((a) => { + return a.is_playable === true + }).length > 0 } else if (this.tracks) { return this.tracks.filter((t) => { - return t.is_playable + return t.uploads && t.uploads.length > 0 }).length > 0 } return false @@ -139,7 +143,7 @@ export default { self.isLoading = false }, 250) return tracks.filter(e => { - return e.is_playable === true + return e.uploads && e.uploads.length > 0 }) }) }, diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index 1d622f33055ab9e7d0165fda1bc9781aa01da046..80c54bbb2d9ad0f41174894e1a13611fee28250d 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -4,6 +4,7 @@ <audio-track ref="currentAudio" v-if="currentTrack" + @errored="handleError" :is-current="true" :start-time="$store.state.player.currentTime" :autoplay="$store.state.player.playing" @@ -41,13 +42,13 @@ </div> </div> </div> - <div class="progress-area" v-if="currentTrack"> + <div class="progress-area" v-if="currentTrack && !errored"> <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"> + <div v-if="!isLoadingAudio" class="right floated four wide column"> <p class="timer total">{{durationFormatted}}</p> </div> </div> @@ -59,7 +60,18 @@ <div class="bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div> </div> </div> - + <div class="ui small warning message" v-if="currentTrack && errored"> + <div class="header"> + <translate>We cannot load this track</translate> + </div> + <p v-if="hasNext && playing && $store.state.player.errorCount < $store.state.player.maxConsecutiveErrors"> + <translate>The next track will play automatically in a few seconds...</translate> + <i class="loading spinner icon"></i> + </p> + <p> + <translate>You may have a connectivity issue.</translate> + </p> + </div> <div class="two wide column controls ui grid"> <a href @@ -299,6 +311,10 @@ export default { } let image = this.$refs.cover this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4) + }, + handleError ({sound, error}) { + this.$store.commit('player/isLoadingAudio', false) + this.$store.dispatch('player/trackErrored') } }, computed: { @@ -310,6 +326,7 @@ export default { looping: state => state.player.looping, duration: state => state.player.duration, bufferProgress: state => state.player.bufferProgress, + errored: state => state.player.errored, queue: state => state.queue }), ...mapGetters({ diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue index 32eb62722da88bddb40cdca68dae137059f18b59..c847d4de1a69f3f6cd1e6937e580d8ddf23332da 100644 --- a/front/src/components/audio/Track.vue +++ b/front/src/components/audio/Track.vue @@ -46,12 +46,17 @@ export default { onload: function () { self.$store.commit('player/isLoadingAudio', false) self.$store.commit('player/resetErrorCount') + self.$store.commit('player/errored', false) self.$store.commit('player/duration', self.sound.duration()) let node = self.sound._sounds[0]._node; node.addEventListener('progress', () => { self.updateBuffer(node) }) - } + }, + onloaderror: function (sound, error) { + console.log('Error while playing:', sound, error) + self.$emit('errored', {sound, error}) + }, }) if (this.autoplay) { self.$store.commit('player/isLoadingAudio', true) @@ -73,14 +78,23 @@ export default { looping: state => state.player.looping }), srcs: function () { - // let file = this.track.files[0] - // if (!file) { - // this.$store.dispatch('player/trackErrored') - // return [] - // } - let sources = [ - {type: 'mp3', url: this.$store.getters['instance/absoluteUrl'](this.track.listen_url)} - ] + let sources = this.track.uploads.map(u => { + return { + type: u.extension, + url: this.$store.getters['instance/absoluteUrl'](u.listen_url), + } + }) + // We always add a transcoded MP3 src at the end + // because transcoding is expensive, but we want browsers that do + // not support other codecs to be able to play it :) + sources.push({ + type: 'mp3', + url: url.updateQueryString( + this.$store.getters['instance/absoluteUrl'](this.track.listen_url), + 'to', + 'mp3' + ) + }) if (this.$store.state.auth.authenticated) { // we need to send the token directly in url // so authentication can be checked by the backend diff --git a/front/src/components/audio/track/Row.vue b/front/src/components/audio/track/Row.vue index b17cf117073190f767e82894de09d12106712d47..fd8b2daaf134473aff45cef895b104bc3a7f6ba7 100644 --- a/front/src/components/audio/track/Row.vue +++ b/front/src/components/audio/track/Row.vue @@ -34,8 +34,8 @@ {{ track.album.title }} </router-link> </td> - <td colspan="4" v-if="track.duration"> - {{ time.parse(track.duration) }} + <td colspan="4" v-if="track.uploads && track.uploads.length > 0"> + {{ time.parse(track.uploads[0].duration) }} </td> <td colspan="4" v-else> <translate>N/A</translate> diff --git a/front/src/components/library/Track.vue b/front/src/components/library/Track.vue index 483ff66739f582b3facd15237879c9321ae1c841..ddccda397352bc578999608489f153519f332fe8 100644 --- a/front/src/components/library/Track.vue +++ b/front/src/components/library/Track.vue @@ -44,13 +44,13 @@ <i class="external icon"></i> <translate>View on MusicBrainz</translate> </a> - <a v-if="track.is_playable" :href="downloadUrl" target="_blank" class="ui button"> + <a v-if="upload" :href="downloadUrl" target="_blank" class="ui button"> <i class="download icon"></i> <translate>Download</translate> </a> </div> </div> - <div class="ui vertical stripe center aligned segment"> + <div class="ui vertical stripe center aligned segment" v-if="upload"> <h2 class="ui header"><translate>Track information</translate></h2> <table class="ui very basic collapsing celled center aligned table"> <tbody> @@ -58,8 +58,8 @@ <td> <translate>Duration</translate> </td> - <td v-if="track.duration"> - {{ time.parse(track.duration) }} + <td v-if="upload.duration"> + {{ time.parse(upload.duration) }} </td> <td v-else> <translate>N/A</translate> @@ -69,8 +69,8 @@ <td> <translate>Size</translate> </td> - <td v-if="track.size"> - {{ track.size | humanSize }} + <td v-if="upload.size"> + {{ upload.size | humanSize }} </td> <td v-else> <translate>N/A</translate> @@ -80,8 +80,8 @@ <td> <translate>Bitrate</translate> </td> - <td v-if="track.bitrate"> - {{ track.bitrate | humanSize }}/s + <td v-if="upload.bitrate"> + {{ upload.bitrate | humanSize }}/s </td> <td v-else> <translate>N/A</translate> @@ -91,8 +91,8 @@ <td> <translate>Type</translate> </td> - <td v-if="track.mimetype"> - {{ track.mimetype }} + <td v-if="upload.extension"> + {{ upload.extension }} </td> <td v-else> <translate>N/A</translate> @@ -195,6 +195,11 @@ export default { title: this.$gettext('Track') } }, + upload () { + if (this.track.uploads) { + return this.track.uploads[0] + } + }, wikipediaUrl () { return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.track.title + ' ' + this.track.artist.name) }, @@ -204,7 +209,7 @@ export default { } }, downloadUrl () { - let u = this.$store.getters['instance/absoluteUrl'](this.track.listen_url) + let u = this.$store.getters['instance/absoluteUrl'](this.upload.listen_url) if (this.$store.state.auth.authenticated) { u = url.updateQueryString(u, 'jwt', encodeURI(this.$store.state.auth.token)) } diff --git a/front/src/store/index.js b/front/src/store/index.js index 051e89b39e11291f7b303056399faa56e8abed15..0b8eb3321c27b00d8c31498ad602de7f9617c49a 100644 --- a/front/src/store/index.js +++ b/front/src/store/index.js @@ -79,6 +79,7 @@ export default new Vuex.Store({ id: track.id, title: track.title, mbid: track.mbid, + uploads: track.uploads, listen_url: track.listen_url, album: { id: track.album.id, diff --git a/front/src/store/player.js b/front/src/store/player.js index 08492541ea6380645b16ab39402a3200e692c1a4..fac17368d10377b959fb98520f242eb2f77f4aa2 100644 --- a/front/src/store/player.js +++ b/front/src/store/player.js @@ -95,10 +95,19 @@ export default { incrementVolume ({commit, state}, value) { commit('volume', state.volume + value) }, - stop (context) { + stop ({commit}) { + commit('errored', false) + commit('resetErrorCount') }, - togglePlay ({commit, state}) { + togglePlay ({commit, state, dispatch}) { commit('playing', !state.playing) + if (state.errored && state.errorCount < state.maxConsecutiveErrors) { + setTimeout(() => { + if (state.playing) { + dispatch('queue/next', null, {root: true}) + } + }, 3000) + } }, trackListened ({commit, rootState}, track) { if (!rootState.auth.authenticated) { @@ -121,7 +130,13 @@ export default { trackErrored ({commit, dispatch, state}) { commit('errored', true) commit('incrementErrorCount') - dispatch('queue/next', null, {root: true}) + if (state.errorCount < state.maxConsecutiveErrors) { + setTimeout(() => { + if (state.playing) { + dispatch('queue/next', null, {root: true}) + } + }, 3000) + } }, updateProgress ({commit}, t) { commit('currentTime', t) diff --git a/front/src/store/queue.js b/front/src/store/queue.js index b6edb2242a3d09092baacf3b5b3af817bc9695dd..81403b11fa6d77d2e5332a77ed12ba3023d3f00e 100644 --- a/front/src/store/queue.js +++ b/front/src/store/queue.js @@ -142,7 +142,6 @@ export default { commit('ended', false) commit('player/currentTime', 0, {root: true}) commit('player/playing', true, {root: true}) - commit('player/errored', false, {root: true}) commit('currentIndex', index) if (state.tracks.length - index <= 2 && rootState.radios.running) { dispatch('radios/populateQueue', null, {root: true}) diff --git a/front/tests/unit/specs/store/queue.spec.js b/front/tests/unit/specs/store/queue.spec.js index 373f4938e034864d65db318ba82a550fdcc012c4..282a4f02633758d878f7a89106844b6981fcccfd 100644 --- a/front/tests/unit/specs/store/queue.spec.js +++ b/front/tests/unit/specs/store/queue.spec.js @@ -267,7 +267,6 @@ describe('store/queue', () => { { type: 'ended', payload: false }, { type: 'player/currentTime', payload: 0, options: {root: true} }, { type: 'player/playing', payload: true, options: {root: true} }, - { type: 'player/errored', payload: false, options: {root: true} }, { type: 'currentIndex', payload: 1 } ] }) @@ -281,7 +280,6 @@ describe('store/queue', () => { { type: 'ended', payload: false }, { type: 'player/currentTime', payload: 0, options: {root: true} }, { type: 'player/playing', payload: true, options: {root: true} }, - { type: 'player/errored', payload: false, options: {root: true} }, { type: 'currentIndex', payload: 1 } ] }) @@ -295,7 +293,6 @@ describe('store/queue', () => { { type: 'ended', payload: false }, { type: 'player/currentTime', payload: 0, options: {root: true} }, { type: 'player/playing', payload: true, options: {root: true} }, - { type: 'player/errored', payload: false, options: {root: true} }, { type: 'currentIndex', payload: 1 } ], expectedActions: [