Download quality fallback, shuffle, repeat, lastfm

This commit is contained in:
exttex
2020-09-07 19:12:45 +02:00
parent a77d8437bd
commit 27b55a4876
15 changed files with 246 additions and 715 deletions

View File

@ -1,6 +1,6 @@
{
"name": "client",
"version": "0.1.0",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -6,7 +6,21 @@
</div>
<v-list v-if='!loading'>
<v-lazy v-for='(track, index) in tracks' :key='track.id' max-height='100'>
<v-list-item v-if='!$root.settings.logListen'>
<v-list-item-avatar>
<v-icon class='yellow--text'>mdi-alert</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>
Streaming logging is disabled!
</v-list-item-title>
<v-list-item-subtitle>
Enable it in settings for history to work properly.
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-lazy v-for='(track, index) in tracks' :key='track.id + "INDEX" + index.toString()' max-height='100'>
<TrackTile :track='track' @click='play(index)'></TrackTile>
</v-lazy>
</v-list>

View File

@ -9,6 +9,12 @@
>{{track.title}}<span v-if='track.explicit' class='red--text text-overline pl-2'>E</span></v-list-item-title>
<v-list-item-subtitle>{{track.artistString}}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<!-- Duration -->
<div class='text-caption mx-2'>
{{$duration(track.duration)}}
</div>
</v-list-item-action>
<v-list-item-action>
<!-- Quick add/remoev to library -->
<v-btn @click.stop='addLibrary' icon v-if='!isLibrary'>

View File

@ -16,9 +16,15 @@ let axiosInstance = axios.create({
Vue.prototype.$axios = axiosInstance;
//Duration formatter
Vue.prototype.$duration = (s) => {
let pad = (n, z = 2) => ('00' + n).slice(-z);
return ((s%3.6e6)/6e4 | 0) + ':' + pad((s%6e4)/1000|0);
Vue.prototype.$duration = (ms) => {
if (isNaN(ms) || ms < 1) return '0:00';
let s = Math.floor(ms / 1000);
let hours = Math.floor(s / 3600);
s %= 3600;
let min = Math.floor(s / 60);
let sec = s % 60;
if (hours == 0) return `${min}:${sec.toString().padStart(2, '0')}`;
return `${hours}:${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
};
//Abbrevation
@ -82,6 +88,11 @@ new Vue({
track: null
},
//Repeat & Shuffle
//0 - normal, 1 - repeat list, 2 - repeat track
repeat: 0,
shuffle: false,
//Library cache
libraryTracks: [],
@ -109,8 +120,6 @@ new Vue({
if (!this.audio || this.state != 1) return;
this.audio.play();
this.state = 2;
this.logListen();
},
pause() {
if (!this.audio || this.state != 2) return;
@ -200,13 +209,41 @@ new Vue({
//Gapless playback
if (this.position >= (this.duration() - 5000) && this.state == 2) {
this.loadGapless();
if (!this.shuffle && this.repeat != 2)
this.loadGapless();
}
//Scrobble/LogListen
if (this.position >= this.duration() * 0.75) {
this.logListen();
}
});
this.audio.muted = this.muted;
this.audio.volume = this.volume;
this.audio.addEventListener('ended', async () => {
//Shuffle
if (this.shuffle) {
let index = Math.round(Math.random()*this.queue.data.length) - this.queue.index;
this.skip(index);
this.savePlaybackInfo();
return;
}
//Repeat track
if (this.repeat == 2) {
this.seek(0);
this.audio.play();
return;
}
//Repeat list
if (this.queue.index == this.queue.data.length - 1) {
this.skip(-(this.queue.data.length - 1));
return;
}
//Load gapless
if (this.gapless.promise || this.gapless.audio) {
this.state = 3;
@ -222,7 +259,6 @@ new Vue({
//Play
this.state = 2;
this.audio.play();
this.logListen();
await this.savePlaybackInfo();
return;
}
@ -309,7 +345,9 @@ new Vue({
let data = {
queue: this.queue,
position: this.position,
track: this.track
track: this.track,
shuffle: this.shuffle,
repeat: this.repeat
}
await this.$axios.post('/playback', data);
},
@ -336,12 +374,11 @@ new Vue({
//Log song listened to deezer, only if allowed
async logListen() {
if (!this.settings.logListen) return;
if (this.logListenId == this.track.id) return;
if (!this.track || !this.track.id) return;
this.logListenId = this.track.id;
await this.$axios.put(`/log/${this.track.id}`);
await this.$axios.post(`/log`, this.track);
}
},
async created() {
@ -357,6 +394,8 @@ new Vue({
if (pd.data != {}) {
if (pd.data.queue) this.queue = pd.data.queue;
if (pd.data.track) this.track = pd.data.track;
if (pd.data.shuffle) this.shuffle = pd.data.shuffle;
if (pd.data.repeat) this.repeat = pd.data.repeat;
this.playTrack(this.track).then(() => {
this.seek(pd.data.position);
});

View File

@ -41,13 +41,18 @@
<h1 class='mt-2'>Tracks</h1>
<v-list avatar v-if='album.tracks.length > 0'>
<TrackTile
v-for='(track, index) in album.tracks'
:key='track.id'
:track='track'
@click='playTrack(index)'
>
</TrackTile>
<div v-for='(track, index) in album.tracks' :key='track.id'>
<!-- Disk split -->
<div
v-if='index == 0 || track.diskNumber != album.tracks[index-1].diskNumber'
class='mx-4 text-subtitle-1'
>
Disk {{track.diskNumber}}
</div>
<TrackTile :track='track' @click='playTrack(index)'></TrackTile>
</div>
</v-list>
<DownloadDialog :tracks='album.tracks' v-if='downloadDialog' @close='downloadDialog = false'></DownloadDialog>

View File

@ -33,7 +33,7 @@
min='0'
step='1'
:max='this.$root.duration() / 1000'
@click='seekEvent'
@click.prevent.stop='seekEvent'
@start='seeking = true'
@end='seek'
:value='position'
@ -54,12 +54,14 @@
<v-icon size='42px'>mdi-skip-previous</v-icon>
</v-btn>
</v-col>
<v-col>
<v-btn icon x-large @click='$root.toggle()'>
<v-icon size='56px' v-if='!$root.isPlaying()'>mdi-play</v-icon>
<v-icon size='56px' v-if='$root.isPlaying()'>mdi-pause</v-icon>
</v-btn>
</v-col>
<v-col>
<v-btn icon x-large @click='$root.skip(1)'>
<v-icon size='42px'>mdi-skip-next</v-icon>
@ -68,8 +70,18 @@
</v-row>
<!-- Bottom actions -->
<div class='d-flex my-1 mx-8 '>
<div class='d-flex my-1 mx-2 '>
<v-btn icon @click='repeatClick'>
<v-icon v-if='$root.repeat == 0'>mdi-repeat</v-icon>
<v-icon color='primary' v-if='$root.repeat == 1'>mdi-repeat</v-icon>
<v-icon color='primary' v-if='$root.repeat == 2'>mdi-repeat-once</v-icon>
</v-btn>
<v-btn icon @click='$root.shuffle = !$root.shuffle'>
<v-icon v-if='!$root.shuffle'>mdi-shuffle</v-icon>
<v-icon v-if='$root.shuffle' color='primary'>mdi-shuffle</v-icon>
</v-btn>
<v-btn icon @click='addLibrary'>
<v-icon v-if='!inLibrary'>mdi-heart</v-icon>
<v-icon v-if='inLibrary'>mdi-heart-remove</v-icon>
@ -271,6 +283,14 @@ export default {
//Save volume
updateVolume(v) {
this.$root.volume = v;
},
//Repeat button click
repeatClick() {
if (this.$root.repeat == 2) {
this.$root.repeat = 0;
return;
}
this.$root.repeat += 1;
}
},
mounted() {

View File

@ -67,6 +67,13 @@
<v-list-item-subtitle>This allows listening history, flow and recommendations to work properly.</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- LastFM -->
<v-list-item @click='connectLastFM' v-if='!$root.settings.lastFM'>
<v-list-item-content>
<v-list-item-title>Login with LastFM</v-list-item-title>
<v-list-item-subtitle>Connect your LastFM account to allow scrobbling.</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- Minimize to tray -->
<v-list-item v-if='$root.settings.electron'>
@ -166,6 +173,11 @@ export default {
this.$root.settings.arl = null;
await this.$root.saveSettings();
location.reload();
},
//Redirect to lastfm login
async connectLastFM() {
let res = await this.$axios.get('/lastfm');
window.location.replace(res.data.url);
}
},
mounted() {