Download quality fallback, shuffle, repeat, lastfm
This commit is contained in:
2
app/client/package-lock.json
generated
2
app/client/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "client",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -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>
|
||||
|
@ -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'>
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
Reference in New Issue
Block a user