Commit b757ca46 authored by Eliot Berriot's avatar Eliot Berriot 💬

See #272: updated front-end for transcoding and new API results, improved error handling in player

parent d3f8fb6c
...@@ -79,10 +79,14 @@ export default { ...@@ -79,10 +79,14 @@ export default {
return true return true
} }
if (this.track) { 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) { } else if (this.tracks) {
return this.tracks.filter((t) => { return this.tracks.filter((t) => {
return t.is_playable return t.uploads && t.uploads.length > 0
}).length > 0 }).length > 0
} }
return false return false
...@@ -139,7 +143,7 @@ export default { ...@@ -139,7 +143,7 @@ export default {
self.isLoading = false self.isLoading = false
}, 250) }, 250)
return tracks.filter(e => { return tracks.filter(e => {
return e.is_playable === true return e.uploads && e.uploads.length > 0
}) })
}) })
}, },
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<audio-track <audio-track
ref="currentAudio" ref="currentAudio"
v-if="currentTrack" v-if="currentTrack"
@errored="handleError"
:is-current="true" :is-current="true"
:start-time="$store.state.player.currentTime" :start-time="$store.state.player.currentTime"
:autoplay="$store.state.player.playing" :autoplay="$store.state.player.playing"
...@@ -41,13 +42,13 @@ ...@@ -41,13 +42,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="progress-area" v-if="currentTrack"> <div class="progress-area" v-if="currentTrack && !errored">
<div class="ui grid"> <div class="ui grid">
<div class="left floated four wide column"> <div class="left floated four wide column">
<p class="timer start" @click="updateProgress(0)">{{currentTimeFormatted}}</p> <p class="timer start" @click="updateProgress(0)">{{currentTimeFormatted}}</p>
</div> </div>
<div class="right floated four wide column"> <div v-if="!isLoadingAudio" class="right floated four wide column">
<p class="timer total">{{durationFormatted}}</p> <p class="timer total">{{durationFormatted}}</p>
</div> </div>
</div> </div>
...@@ -59,7 +60,18 @@ ...@@ -59,7 +60,18 @@
<div class="bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div> <div class="bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div>
</div> </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"> <div class="two wide column controls ui grid">
<a <a
href href
...@@ -299,6 +311,10 @@ export default { ...@@ -299,6 +311,10 @@ export default {
} }
let image = this.$refs.cover let image = this.$refs.cover
this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4) 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: { computed: {
...@@ -310,6 +326,7 @@ export default { ...@@ -310,6 +326,7 @@ export default {
looping: state => state.player.looping, looping: state => state.player.looping,
duration: state => state.player.duration, duration: state => state.player.duration,
bufferProgress: state => state.player.bufferProgress, bufferProgress: state => state.player.bufferProgress,
errored: state => state.player.errored,
queue: state => state.queue queue: state => state.queue
}), }),
...mapGetters({ ...mapGetters({
......
...@@ -46,12 +46,17 @@ export default { ...@@ -46,12 +46,17 @@ export default {
onload: function () { onload: function () {
self.$store.commit('player/isLoadingAudio', false) self.$store.commit('player/isLoadingAudio', false)
self.$store.commit('player/resetErrorCount') self.$store.commit('player/resetErrorCount')
self.$store.commit('player/errored', false)
self.$store.commit('player/duration', self.sound.duration()) self.$store.commit('player/duration', self.sound.duration())
let node = self.sound._sounds[0]._node; let node = self.sound._sounds[0]._node;
node.addEventListener('progress', () => { node.addEventListener('progress', () => {
self.updateBuffer(node) self.updateBuffer(node)
}) })
} },
onloaderror: function (sound, error) {
console.log('Error while playing:', sound, error)
self.$emit('errored', {sound, error})
},
}) })
if (this.autoplay) { if (this.autoplay) {
self.$store.commit('player/isLoadingAudio', true) self.$store.commit('player/isLoadingAudio', true)
...@@ -73,14 +78,23 @@ export default { ...@@ -73,14 +78,23 @@ export default {
looping: state => state.player.looping looping: state => state.player.looping
}), }),
srcs: function () { srcs: function () {
// let file = this.track.files[0] let sources = this.track.uploads.map(u => {
// if (!file) { return {
// this.$store.dispatch('player/trackErrored') type: u.extension,
// return [] url: this.$store.getters['instance/absoluteUrl'](u.listen_url),
// } }
let sources = [ })
{type: 'mp3', url: this.$store.getters['instance/absoluteUrl'](this.track.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) { if (this.$store.state.auth.authenticated) {
// we need to send the token directly in url // we need to send the token directly in url
// so authentication can be checked by the backend // so authentication can be checked by the backend
......
...@@ -34,8 +34,8 @@ ...@@ -34,8 +34,8 @@
{{ track.album.title }} {{ track.album.title }}
</router-link> </router-link>
</td> </td>
<td colspan="4" v-if="track.duration"> <td colspan="4" v-if="track.uploads && track.uploads.length > 0">
{{ time.parse(track.duration) }} {{ time.parse(track.uploads[0].duration) }}
</td> </td>
<td colspan="4" v-else> <td colspan="4" v-else>
<translate>N/A</translate> <translate>N/A</translate>
......
...@@ -44,13 +44,13 @@ ...@@ -44,13 +44,13 @@
<i class="external icon"></i> <i class="external icon"></i>
<translate>View on MusicBrainz</translate> <translate>View on MusicBrainz</translate>
</a> </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> <i class="download icon"></i>
<translate>Download</translate> <translate>Download</translate>
</a> </a>
</div> </div>
</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> <h2 class="ui header"><translate>Track information</translate></h2>
<table class="ui very basic collapsing celled center aligned table"> <table class="ui very basic collapsing celled center aligned table">
<tbody> <tbody>
...@@ -58,8 +58,8 @@ ...@@ -58,8 +58,8 @@
<td> <td>
<translate>Duration</translate> <translate>Duration</translate>
</td> </td>
<td v-if="track.duration"> <td v-if="upload.duration">
{{ time.parse(track.duration) }} {{ time.parse(upload.duration) }}
</td> </td>
<td v-else> <td v-else>
<translate>N/A</translate> <translate>N/A</translate>
...@@ -69,8 +69,8 @@ ...@@ -69,8 +69,8 @@
<td> <td>
<translate>Size</translate> <translate>Size</translate>
</td> </td>
<td v-if="track.size"> <td v-if="upload.size">
{{ track.size | humanSize }} {{ upload.size | humanSize }}
</td> </td>
<td v-else> <td v-else>
<translate>N/A</translate> <translate>N/A</translate>
...@@ -80,8 +80,8 @@ ...@@ -80,8 +80,8 @@
<td> <td>
<translate>Bitrate</translate> <translate>Bitrate</translate>
</td> </td>
<td v-if="track.bitrate"> <td v-if="upload.bitrate">
{{ track.bitrate | humanSize }}/s {{ upload.bitrate | humanSize }}/s
</td> </td>
<td v-else> <td v-else>
<translate>N/A</translate> <translate>N/A</translate>
...@@ -91,8 +91,8 @@ ...@@ -91,8 +91,8 @@
<td> <td>
<translate>Type</translate> <translate>Type</translate>
</td> </td>
<td v-if="track.mimetype"> <td v-if="upload.extension">
{{ track.mimetype }} {{ upload.extension }}
</td> </td>
<td v-else> <td v-else>
<translate>N/A</translate> <translate>N/A</translate>
...@@ -195,6 +195,11 @@ export default { ...@@ -195,6 +195,11 @@ export default {
title: this.$gettext('Track') title: this.$gettext('Track')
} }
}, },
upload () {
if (this.track.uploads) {
return this.track.uploads[0]
}
},
wikipediaUrl () { wikipediaUrl () {
return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.track.title + ' ' + this.track.artist.name) return 'https://en.wikipedia.org/w/index.php?search=' + encodeURI(this.track.title + ' ' + this.track.artist.name)
}, },
...@@ -204,7 +209,7 @@ export default { ...@@ -204,7 +209,7 @@ export default {
} }
}, },
downloadUrl () { 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) { if (this.$store.state.auth.authenticated) {
u = url.updateQueryString(u, 'jwt', encodeURI(this.$store.state.auth.token)) u = url.updateQueryString(u, 'jwt', encodeURI(this.$store.state.auth.token))
} }
......
...@@ -79,6 +79,7 @@ export default new Vuex.Store({ ...@@ -79,6 +79,7 @@ export default new Vuex.Store({
id: track.id, id: track.id,
title: track.title, title: track.title,
mbid: track.mbid, mbid: track.mbid,
uploads: track.uploads,
listen_url: track.listen_url, listen_url: track.listen_url,
album: { album: {
id: track.album.id, id: track.album.id,
......
...@@ -95,10 +95,19 @@ export default { ...@@ -95,10 +95,19 @@ export default {
incrementVolume ({commit, state}, value) { incrementVolume ({commit, state}, value) {
commit('volume', state.volume + 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) 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) { trackListened ({commit, rootState}, track) {
if (!rootState.auth.authenticated) { if (!rootState.auth.authenticated) {
...@@ -121,7 +130,13 @@ export default { ...@@ -121,7 +130,13 @@ export default {
trackErrored ({commit, dispatch, state}) { trackErrored ({commit, dispatch, state}) {
commit('errored', true) commit('errored', true)
commit('incrementErrorCount') 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) { updateProgress ({commit}, t) {
commit('currentTime', t) commit('currentTime', t)
......
...@@ -142,7 +142,6 @@ export default { ...@@ -142,7 +142,6 @@ export default {
commit('ended', false) commit('ended', false)
commit('player/currentTime', 0, {root: true}) commit('player/currentTime', 0, {root: true})
commit('player/playing', true, {root: true}) commit('player/playing', true, {root: true})
commit('player/errored', false, {root: true})
commit('currentIndex', index) commit('currentIndex', index)
if (state.tracks.length - index <= 2 && rootState.radios.running) { if (state.tracks.length - index <= 2 && rootState.radios.running) {
dispatch('radios/populateQueue', null, {root: true}) dispatch('radios/populateQueue', null, {root: true})
......
...@@ -267,7 +267,6 @@ describe('store/queue', () => { ...@@ -267,7 +267,6 @@ describe('store/queue', () => {
{ type: 'ended', payload: false }, { type: 'ended', payload: false },
{ type: 'player/currentTime', payload: 0, options: {root: true} }, { type: 'player/currentTime', payload: 0, options: {root: true} },
{ type: 'player/playing', payload: true, options: {root: true} }, { type: 'player/playing', payload: true, options: {root: true} },
{ type: 'player/errored', payload: false, options: {root: true} },
{ type: 'currentIndex', payload: 1 } { type: 'currentIndex', payload: 1 }
] ]
}) })
...@@ -281,7 +280,6 @@ describe('store/queue', () => { ...@@ -281,7 +280,6 @@ describe('store/queue', () => {
{ type: 'ended', payload: false }, { type: 'ended', payload: false },
{ type: 'player/currentTime', payload: 0, options: {root: true} }, { type: 'player/currentTime', payload: 0, options: {root: true} },
{ type: 'player/playing', payload: true, options: {root: true} }, { type: 'player/playing', payload: true, options: {root: true} },
{ type: 'player/errored', payload: false, options: {root: true} },
{ type: 'currentIndex', payload: 1 } { type: 'currentIndex', payload: 1 }
] ]
}) })
...@@ -295,7 +293,6 @@ describe('store/queue', () => { ...@@ -295,7 +293,6 @@ describe('store/queue', () => {
{ type: 'ended', payload: false }, { type: 'ended', payload: false },
{ type: 'player/currentTime', payload: 0, options: {root: true} }, { type: 'player/currentTime', payload: 0, options: {root: true} },
{ type: 'player/playing', payload: true, options: {root: true} }, { type: 'player/playing', payload: true, options: {root: true} },
{ type: 'player/errored', payload: false, options: {root: true} },
{ type: 'currentIndex', payload: 1 } { type: 'currentIndex', payload: 1 }
], ],
expectedActions: [ expectedActions: [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment