#29 Refactor playback

Open
chayleaf wants to merge 5 commits from chayleaf/freezerpc:refactor-playback into master
  1. +3
    -3
      app/client/src/App.vue
  2. +2
    -2
      app/client/src/components/AlbumTile.vue
  3. +3
    -3
      app/client/src/components/LibraryHistory.vue
  4. +4
    -4
      app/client/src/components/LibraryTracks.vue
  5. +5
    -5
      app/client/src/components/Lyrics.vue
  6. +2
    -2
      app/client/src/components/PlaylistTile.vue
  7. +2
    -2
      app/client/src/components/SmartTrackList.vue
  8. +4
    -4
      app/client/src/components/TrackTile.vue
  9. +187
    -119
      app/client/src/main.js
  10. +5
    -5
      app/client/src/views/AlbumPage.vue
  11. +7
    -7
      app/client/src/views/ArtistPage.vue
  12. +8
    -18
      app/client/src/views/FullscreenPlayer.vue
  13. +2
    -2
      app/client/src/views/Library.vue
  14. +4
    -4
      app/client/src/views/PlaylistPage.vue
  15. +3
    -3
      app/client/src/views/Search.vue
  16. +2
    -2
      app/src/integrations.js

+ 3
- 3
app/client/src/App.vue View File

@ -255,7 +255,7 @@
<v-col cols='0' md='auto' class='d-none d-sm-none d-md-flex justify-center px-2' v-if='this.$root.track'>
<span class='text-subtitle-2'>
{{$duration($root.position)}} <span class='px-4'>{{qualityText}}</span>
{{$duration($root.position())}} <span class='px-4'>{{qualityText}}</span>
</span>
</v-col>
@ -562,8 +562,8 @@ export default {
this.volume = this.$root.volume;
},
//Update position
'$root.position'() {
this.position = (this.$root.position / this.$root.duration()) * 100;
'$root.positionVal'() {
this.position = (this.$root.position() / this.$root.duration()) * 100;
},
//Global snackbar
'$root.globalSnackbar'() {


+ 2
- 2
app/client/src/components/AlbumTile.vue View File

@ -134,8 +134,8 @@ export default {
source: 'album',
data: album.id
};
this.$root.replaceQueue(album.tracks);
this.$root.playIndex(0);
await this.$root.replaceQueue(album.tracks);
await this.$root.playIndex(0);
},
//Add to queue
async addQueue() {


+ 3
- 3
app/client/src/components/LibraryHistory.vue View File

@ -54,14 +54,14 @@ export default {
this.loading = false;
},
//Load as queue and play
play(index) {
async play(index) {
this.$root.queue.source = {
text: this.$t('History'),
source: 'history',
data: null
};
this.$root.replaceQueue(this.tracks);
this.$root.playIndex(index);
await this.$root.replaceQueue(this.tracks);
await this.$root.playIndex(index);
}
},
mounted() {


+ 4
- 4
app/client/src/components/LibraryTracks.vue View File

@ -112,13 +112,13 @@ export default {
source: 'playlist',
data: this.$root.profile.favoritesPlaylist
};
this.$root.replaceQueue(this.tracks);
this.$root.playIndex(this.tracks.findIndex(t => t.id == id));
await this.$root.replaceQueue(this.tracks);
await this.$root.playIndex(this.tracks.findIndex(t => t.id == id));
//Load all tracks
if (this.tracks.length < this.count) {
this.loadAll().then(() => {
this.$root.replaceQueue(this.tracks);
this.loadAll().then(async () => {
await this.$root.replaceQueue(this.tracks);
});
}
},


+ 5
- 5
app/client/src/components/Lyrics.vue View File

@ -71,19 +71,19 @@ export default {
},
//Wether current lyric is playing rn
playingNow(i) {
if (!this.$root.audio) return false;
if (!this.$root.playback || !this.$root.playback.audio) return false;
//First & last lyric check
if (i == this.lyrics.lyrics.length - 1) {
if (this.lyrics.lyrics[i].offset <= this.$root.position) return true;
if (this.lyrics.lyrics[i].offset <= this.$root.position()) return true;
return false;
}
if (this.$root.position >= this.lyrics.lyrics[i].offset && this.$root.position < this.lyrics.lyrics[i+1].offset) return true;
if (this.$root.position() >= this.lyrics.lyrics[i].offset && this.$root.position() < this.lyrics.lyrics[i+1].offset) return true;
return false;
},
//Get index of current lyric
currentLyric() {
if (!this.$root.audio) return 0;
if (!this.$root.playback || !this.$root.playback.audio) return 0;
return this.lyrics.lyrics.findIndex((l) => {
return this.playingNow(this.lyrics.lyrics.indexOf(l));
});
@ -118,7 +118,7 @@ export default {
this.load();
}
},
'$root.position'() {
'$root.positionVal'() {
this.scrollLyric();
}
}


+ 2
- 2
app/client/src/components/PlaylistTile.vue View File

@ -146,8 +146,8 @@ export default {
source: 'playlist',
data: playlist.id
};
this.$root.replaceQueue(playlist.tracks);
this.$root.playIndex(0);
await this.$root.replaceQueue(playlist.tracks);
await this.$root.playIndex(0);
//Load all tracks
if (playlist.tracks.length != playlist.trackCount) {


+ 2
- 2
app/client/src/components/SmartTrackList.vue View File

@ -40,8 +40,8 @@ export default {
source: 'smarttracklist',
data: this.stl.id
};
this.$root.replaceQueue(res.data);
this.$root.playIndex(0);
await this.$root.replaceQueue(res.data);
await this.$root.playIndex(0);
this.loading = false;
}


+ 4
- 4
app/client/src/components/TrackTile.vue View File

@ -198,8 +198,8 @@ export default {
},
methods: {
//Add track next to queue
playNext() {
this.$root.addTrackIndex(this.track, this.$root.queue.index+1);
async playNext() {
await this.$root.addTrackIndex(this.track, this.$root.queue.index + 1);
},
addQueue() {
this.$root.queue.data.push(this.track);
@ -250,8 +250,8 @@ export default {
source: 'trackmix',
data: this.track.id
};
this.$root.replaceQueue(res.data);
this.$root.playIndex(0);
await this.$root.replaceQueue(res.data);
await this.$root.playIndex(0);
},
//Copy link
share() {


+ 187
- 119
app/client/src/main.js View File

@ -69,15 +69,15 @@ new Vue({
downloads: {},
cancelSource: axios.CancelToken.source(),
//Player
track: null,
audio: null,
playback: null,
volume: 0.00,
//0 = Stopped, 1 = Paused, 2 = Playing, 3 = Loading
state: 0,
loaders: 0,
loading: false,
playbackInfo: {},
position: 0,
positionVal: 0,
muted: false,
//Gapless playback meta
gapless: {
@ -88,7 +88,7 @@ new Vue({
},
//0 - normal, 1 - repeat list, 2 - repeat track
repeat: 0,
repeatMode: 0,
shuffled: false,
//Library cache
@ -122,64 +122,110 @@ new Vue({
methods: {
// PLAYBACK METHODS
isPlaying() {
return this.state == 2;
return this.playback && this.playback.state >= 1;
},
//Repeat button click
async repeatClick() {
if (this.repeatMode == 2) {
if (this.playback) this.playback.audio.loop = false;
this.repeatMode = 0;
await this.savePlaybackInfo();
return;
}
this.repeatMode += 1;
if (this.repeatMode == 2) {
if (this.playback) this.playback.audio.loop = true;
}
await this.savePlaybackInfo();
},
play() {
if (!this.audio || this.state != 1) return;
this.audio.play();
this.state = 2;
async play(playback) {
playback = playback || this.playback;
if (!playback || playback.state != 0) return;
playback.state = 1;
if(playback.audio) {
if (playback.position) {
playback.audio.currentTime = playback.position / 1000;
playback.position = null;
}
await playback.audio.play();
if (playback.state == 0) playback.audio.pause();
else playback.state = 2;
}
},
pause() {
if (!this.audio || this.state != 2) return;
this.audio.pause();
this.state = 1;
pause(playback) {
playback = playback || this.playback;
if (!playback || playback.state == 0) return;
if (playback.state == 2) playback.audio.pause();
playback.state = 0;
},
toggle() {
if (this.isPlaying()) return this.pause();
this.play();
},
seek(t) {
if (!this.audio || isNaN(t) || !t) return;
if (isNaN(t) || (!t && t !== 0) || !this.playback) return;
if (!this.playback.audio) {
this.playback.position = t;
return;
}
//ms -> s
this.audio.currentTime = (t / 1000);
this.position = t;
this.playback.audio.currentTime = t / 1000;
this.positionVal = t;
this.updateState();
},
position() {
if (!this.playback) return 0;
if (!this.playback.audio) return this.playback.position;
return this.playback.audio.currentTime * 1000;
},
//Current track duration
duration() {
//Prevent 0 division
if (!this.audio) return 1;
return this.audio.duration * 1000;
if (!this.playback || !this.playback.audio || !this.playback.audio.duration)
return (this.track ? this.track.duration : 0) || 1;
return this.playback.audio.duration * 1000;
},
//Replace queue, has to make clone of data to not keep references
replaceQueue(newQueue) {
async replaceQueue(newQueue) {
this.queue.data = Object.assign([], newQueue);
await this.savePlaybackInfo();
},
//Add track to queue at index
addTrackIndex(track, index) {
async addTrackIndex(track, index) {
this.queue.data.splice(index, 0, track);
await this.savePlaybackInfo();
},
//Play at index in queue
async playIndex(index) {
if (index >= this.queue.data.length || index < 0) return;
this.queue.index = index;
await this.playTrack(this.queue.data[this.queue.index]);
this.play();
this.savePlaybackInfo();
await this.playTrack(this.queue.data[this.queue.index], true);
await this.savePlaybackInfo();
},
//Skip n tracks, can be negative
async skip(n) {
let newIndex = this.queue.index + n;
// Loop entire list
if (this.repeatMode == 1) {
newIndex %= this.queue.data.length;
while (newIndex < 0) newIndex += this.queue.data.length;
}
//Out of bounds
if (newIndex < 0 || newIndex >= this.queue.data.length) return;
await this.playIndex(newIndex);
if (newIndex >= this.queue.data.length) return;
// Just seek to track start if attempting to skip -1 on the first track
if (newIndex < 0) await this.seek(0);
else await this.playIndex(newIndex);
},
shuffle() {
async shuffle() {
if (!this.shuffled) {
//Save positions
for (let i=0; i<this.queue.data.length; i++)
@ -193,46 +239,68 @@ new Vue({
//Update index
this.queue.index = this.queue.data.findIndex(t => t.id == this.track.id);
this.shuffled = true;
await this.savePlaybackInfo();
return;
}
} else {
//Restore unshuffled queue
if (this.shuffled) {
this.queue.data.sort((a, b) => (a._position || 10000) - (b._position || 10000));
this.queue.index = this.queue.data.findIndex(t => t.id == this.track.id);
this.shuffled = false;
await this.savePlaybackInfo();
return;
}
},
//Skip wrapper
skipNext() {
this.skip(1);
this.savePlaybackInfo();
async skipNext() {
await this.skip(1);
await this.savePlaybackInfo();
},
async skipPrev() {
if (this.position() >= 10000) await this.seek(0);
else await this.skip(-1);
await this.savePlaybackInfo();
},
toggleMute() {
if (this.audio) this.audio.muted = !this.audio.muted;
if (this.playback && this.playback.audio) this.playback.audio.muted = !this.playback.audio.muted;
this.muted = !this.muted;
},
async playTrack(track) {
async playTrack(track, autoplay) {
if (!track || !track.streamUrl) return;
if (this.loading) {
this.cancelSource.cancel();
this.cancelSource = axios.CancelToken.source();
}
this.resetGapless();
this.track = track;
this.loaders++;
this.state = 3;
this.loading = true;
//Stop audio
let autoplay = (this.state == 2);
if (this.audio) this.audio.pause();
if (this.audio) this.audio.currentTime = 0;
if (this.playback && this.playback.audio) {
this.pause();
this.playback.audio.removeEventListener('timeupdate', this.onTimeUpdate);
this.playback.audio.removeEventListener('ended', this.onEnded);
}
this.playback = {
audio: null,
state: autoplay ? 1 : 0,
};
this.track = track;
this.positionVal = 0;
//Load track meta
let playbackInfo = await this.loadPlaybackInfo(track.streamUrl, track.duration);
let playbackInfo;
try {
playbackInfo = await this.loadPlaybackInfo(track.streamUrl, track.duration, this.cancelSource.token);
if (!playbackInfo) {
this.loaders--;
this.loading = false;
this.skipNext();
return;
}
} catch (err) { // canceled
return;
}
this.playbackInfo = playbackInfo;
//Stream URL
@ -242,41 +310,52 @@ new Vue({
else
url = this.playbackInfo.direct;
//Cancel loading
this.loaders--;
if (this.loaders > 0) {
return;
}
this.loading = false;
//Audio
this.audio = new Audio(url);
autoplay = this.playback.state >= 1;
let position = this.playback.position;
this.playback = {
state: 0,
audio: new Audio(url),
position,
};
this.configureAudio();
this.state = 1;
if (autoplay) this.play();
//MediaSession
this.updateMediaSession();
if (autoplay)
await this.play();
//Loads more tracks if end of list
this.loadSTL();
},
//Configure html audio element
configureAudio() {
//Listen position updates
this.audio.addEventListener('timeupdate', async () => {
this.position = this.audio.currentTime * 1000;
this.playback.audio.loop = this.repeatMode == 2;
this.playback.audio.muted = this.muted;
this.playback.audio.volume = this.volume * this.volume;
//Listen position updates
this.playback.audio.addEventListener('timeupdate', this.onTimeUpdate);
this.playback.audio.addEventListener('ended', this.onEnded);
},
async onTimeUpdate() {
this.positionVal = this.position();
//Gapless playback
if (this.position >= (this.duration() - (this.settings.crossfadeDuration + 7500)) && this.state == 2) {
if (this.repeat != 2)
if (this.position() >= (this.duration() - (this.settings.crossfadeDuration + 10000)) && this.isPlaying()) {
this.loadGapless();
}
//Crossfade
if (this.settings.crossfadeDuration > 0 && this.position >= (this.duration() - this.settings.crossfadeDuration) && this.state == 2 && this.gapless.audio && !this.gapless.crossfade && this.gapless.track) {
if (this.settings.crossfadeDuration > 0 && this.position() >= (this.duration() - this.settings.crossfadeDuration) && this.isPlaying() && this.gapless.audio && !this.gapless.crossfade && this.gapless.track) {
this.gapless.crossfade = true;
let currentVolume = this.audio.volume;
let oldAudio = this.audio;
this.audio = this.gapless.audio;
this.audio.play();
let oldPlayback = this.playback;
if (!oldPlayback) return;
let currentVolume = oldPlayback.audio.volume;
let playback = {
audio: this.gapless.audio,
state: 0, // start as paused
};
this.playback = playback;
await this.play(playback);
//Update meta
this.playbackInfo = this.gapless.info;
@ -286,64 +365,50 @@ new Vue({
this.configureAudio();
this.updateMediaSession();
this.audio.volume = 0.0;
playback.audio.volume = 0.0;
let volumeStep = currentVolume / (this.settings.crossfadeDuration / 50);
for (let i=0; i<(this.settings.crossfadeDuration / 50); i++) {
if ((oldAudio.volume - volumeStep) > 0)
oldAudio.volume -= volumeStep;
if ((this.audio.volume + volumeStep) >= 1.0 || (this.audio.volume + volumeStep) >= currentVolume)
for (let i = 0; i < this.settings.crossfadeDuration / 50; i++) {
oldPlayback.audio.volume = Math.max(oldPlayback.audio.volume - volumeStep, 0);
if (playback.audio.volume + volumeStep >= Math.min(1.0, currentVolume))
break;
this.audio.volume += volumeStep;
await new Promise((res) => setTimeout(() => res(), 50));
playback.audio.volume += volumeStep;
await new Promise(res => setTimeout(() => res(), 50));
}
//Restore original volume
this.audio.voume = currentVolume;
oldAudio.pause();
playback.audio.volume = currentVolume;
await this.pause(oldPlayback);
if (this.playback === playback) {
this.resetGapless();
this.updateState();
//Save
await this.savePlaybackInfo();
}
}
//Scrobble/LogListen
if (this.position >= this.duration() * 0.75) {
if (this.position() >= this.duration() * 0.75) {
this.logListen();
}
});
this.audio.muted = this.muted;
//Set volume
this.audio.volume = this.volume * this.volume;
this.audio.addEventListener('ended', async () => {
},
async onEnded() {
if (this.gapless.crossfade) return;
//Repeat track
if (this.repeat == 2) {
this.seek(0);
this.audio.play();
if (this.repeatMode == 2) {
this.updateState();
return;
}
//Repeat list
if (this.repeat == 1 && this.queue.index == this.queue.data.length - 1) {
this.skip(-(this.queue.data.length - 1));
return;
}
//End of queue
if (this.queue.index+1 == this.queue.data.length) {
this.state = 1;
if (this.repeatMode == 0 && this.queue.index + 1 == this.queue.data.length) {
return;
}
//Skip to next track
this.skip(1);
this.savePlaybackInfo();
});
},
//Update media session with current track metadata
updateMediaSession() {
@ -360,24 +425,27 @@ new Vue({
]
});
//Controls
navigator.mediaSession.setActionHandler('play', this.play);
navigator.mediaSession.setActionHandler('pause', this.pause);
navigator.mediaSession.setActionHandler('nexttrack', this.skipNext);
navigator.mediaSession.setActionHandler('previoustrack', () => this.skip(-1));
navigator.mediaSession.setActionHandler('play', async () => await this.play());
navigator.mediaSession.setActionHandler('pause', () => this.pause());
navigator.mediaSession.setActionHandler('nexttrack', () => this.skipNext());
navigator.mediaSession.setActionHandler('previoustrack', () => this.skipPrev());
},
//Get Deezer CDN image url
getImageUrl(img, size = 256) {
return `https://e-cdns-images.dzcdn.net/images/${img.type}/${img.hash}/${size}x${size}-000000-80-0-0.jpg`
},
async loadPlaybackInfo(streamUrl, duration) {
async loadPlaybackInfo(streamUrl, duration, cancelToken) {
//Get playback info
let quality = this.settings.streamQuality;
let infoUrl = `/streaminfo/${streamUrl}?q=${quality}`;
let res;
try {
res = await this.$axios.get(infoUrl);
} catch (_) {
res = await this.$axios.get(infoUrl, cancelToken ? { cancelToken } : { });
} catch (err) {
if (axios.isCancel(err)) {
throw err;
}
return null;
}
@ -403,10 +471,10 @@ new Vue({
},
//Load next track for gapless
async loadGapless() {
if (this.loaders != 0 || this.gapless.promise || this.gapless.audio || this.gapless.crossfade) return;
if (this.repeatMode == 2 || this.loading || this.gapless.promise || this.gapless.audio || this.gapless.crossfade) return;
//Repeat list
if (this.repeat == 1 && this.queue.index == this.queue.data.length - 1) {
if (this.repeatMode == 1 && this.queue.index == this.queue.data.length - 1) {
this.gapless.track = this.queue.data[0];
this.gapless.index = 0;
} else {
@ -446,7 +514,7 @@ new Vue({
if (data.data) {
this.queue.data = this.queue.data.concat(data.data);
}
this.savePlaybackInfo();
await this.savePlaybackInfo();
}
},
@ -464,9 +532,9 @@ new Vue({
async savePlaybackInfo() {
let data = {
queue: this.queue,
position: this.position,
position: this.position(),
track: this.track,
repeat: this.repeat,
repeatMode: this.repeatMode,
shuffled: this.shuffled
}
await this.$axios.post('/playback', data);
@ -503,22 +571,22 @@ new Vue({
//Send state update to integrations
async updateState() {
//Wait for duration
if (this.state == 2 && (this.duration() == null || isNaN(this.duration()))) {
if (this.isPlaying() && (this.duration() == null || isNaN(this.duration()))) {
setTimeout(() => {
this.updateState();
}, 500);
return;
}
this.$io.emit('stateChange', {
position: this.position,
position: this.position(),
duration: this.duration(),
state: this.state,
playing: this.isPlaying(),
track: this.track
});
//Update in electron
if (this.settings.electron) {
ipcRenderer.send('playing', this.state == 2);
ipcRenderer.send('playing', this.isPlaying());
}
},
updateLanguage(l) {
@ -574,10 +642,10 @@ 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.repeat) this.repeat = pd.data.repeat;
if (pd.data.repeatMode) this.repeatMode = pd.data.repeatMode;
if (pd.data.shuffled) this.shuffled = pd.data.shuffled;
this.playTrack(this.track).then(() => {
this.seek(pd.data.position);
this.playTrack(this.track).then(async () => {
await this.seek(pd.data.position);
});
}
@ -607,10 +675,10 @@ new Vue({
this.toggle();
});
ipcRenderer.on('skipNext', () => {
this.skip(1);
this.skipNext();
});
ipcRenderer.on('skipPrev', () => {
this.skip(-1);
this.skipPrev();
})
}
@ -694,13 +762,13 @@ new Vue({
//K toggle playback
if (e.code == "KeyK" || e.code == "Space") this.$root.toggle();
//L +10s (from YT)
if (e.code == "KeyL") this.$root.seek((this.position + 10000));
if (e.code == "KeyL") this.$root.seek(this.position() + 10000);
//J -10s (from YT)
if (e.code == "KeyJ") this.$root.seek((this.position - 10000));
if (e.code == "KeyJ") this.$root.seek(this.position() - 10000);
//-> +5s (from YT)
if (e.code == "ArrowRight") this.$root.seek((this.position + 5000));
if (e.code == "ArrowRight") this.$root.seek(this.position() + 5000);
//<- -5s (from YT)
if (e.code == "ArrowLeft") this.$root.seek((this.position - 5000));
if (e.code == "ArrowLeft") this.$root.seek(this.position() - 5000);
// ^ v - Volume
if (e.code == 'ArrowUp') {
if ((this.volume + 0.05) > 1) {
@ -727,8 +795,8 @@ new Vue({
},
//Update volume with curve
volume() {
if (this.audio)
this.audio.volume = this.volume * this.volume;
if (this.playback && this.playback.audio)
this.playback.audio.volume = this.volume * this.volume;
}
},


+ 5
- 5
app/client/src/views/AlbumPage.vue View File

@ -93,18 +93,18 @@ export default {
},
methods: {
//Load album and play at index
playTrack(index) {
async playTrack(index) {
this.$root.queue.source = {
text: this.album.title,
source: 'album',
data: this.album.id
};
this.$root.replaceQueue(this.album.tracks);
this.$root.playIndex(index);
await this.$root.replaceQueue(this.album.tracks);
await this.$root.playIndex(index);
},
//Play from beggining
play() {
this.playTrack(0);
async play() {
await this.playTrack(0);
},
//Add to library
async library() {


+ 7
- 7
app/client/src/views/ArtistPage.vue View File

@ -142,17 +142,17 @@ export default {
artistData: Object
},
methods: {
playIndex(index) {
async playIndex(index) {
this.$root.queue.source = {
text: this.artist.name,
source: 'top',
data: this.artist.id
};
this.$root.replaceQueue(this.artist.topTracks);
this.$root.playIndex(index);
await this.$root.replaceQueue(this.artist.topTracks);
await this.$root.playIndex(index);
},
play() {
this.playIndex(0);
async play() {
await this.playIndex(0);
},
//Add to library
async library() {
@ -212,8 +212,8 @@ export default {
source: 'radio',
data: this.artist.id
};
this.$root.replaceQueue(res.data);
this.$root.playIndex(0);
await this.$root.replaceQueue(res.data);
await this.$root.playIndex(0);
}
},
//On scroll load more albums


+ 8
- 18
app/client/src/views/FullscreenPlayer.vue View File

@ -51,7 +51,7 @@
<!-- Controls -->
<v-row no-gutters class='ma-4'>
<v-col>
<v-btn icon x-large @click='$root.skip(-1)'>
<v-btn icon x-large @click='$root.skipPrev'>
<v-icon size='42px'>mdi-skip-previous</v-icon>
</v-btn>
</v-col>
@ -73,10 +73,10 @@
<!-- Bottom actions -->
<div class='d-flex mx-2 mb-2'>
<v-btn icon @click='repeatClick'>
<v-icon v-if='$root.repeat == 0 || !$root.repeat'>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 icon @click='$root.repeatClick'>
<v-icon v-if='!$root.repeatMode'>mdi-repeat</v-icon>
<v-icon color='primary' v-if='$root.repeatMode == 1'>mdi-repeat</v-icon>
<v-icon color='primary' v-if='$root.repeatMode == 2'>mdi-repeat-once</v-icon>
</v-btn>
<v-btn icon @click='$root.shuffle()'>
<v-icon color='primary' v-if='$root.shuffled'>mdi-shuffle</v-icon>
@ -261,7 +261,7 @@ export default {
data() {
return {
//Position used in seconds, because of CPU usage
position: this.$root.position / 1000,
position: this.$root.position() / 1000,
seeking: false,
tab: null,
inLibrary: this.$root.track.library ? true : false,
@ -313,14 +313,6 @@ export default {
updateVolume(v) {
this.$root.volume = v;
},
//Repeat button click
repeatClick() {
if (this.$root.repeat == 2) {
this.$root.repeat = 0;
return;
}
this.$root.repeat += 1;
},
//Copy link
share() {
let copyElem = document.createElement('input');
@ -368,10 +360,8 @@ export default {
'$root.track'() {
this.inLibrary = this.$root.libraryTracks.includes(this.$root.track.id);
},
'$root.position'() {
if (!this.seeking) {
this.position = this.$root.position / 1000;
}
'$root.positionVal'() {
this.position = this.$root.position() / 1000;
},
//Force update queue
'$root.shuffled'() {


+ 2
- 2
app/client/src/views/Library.vue View File

@ -88,8 +88,8 @@ export default {
source: 'playlist',
data: 0
};
this.$root.replaceQueue(res.data);
this.$root.playIndex(0);
await this.$root.replaceQueue(res.data);
await this.$root.playIndex(0);
}
}
},


+ 4
- 4
app/client/src/views/PlaylistPage.vue View File

@ -159,13 +159,13 @@ export default {
source: 'playlist',
data: this.playlist.id
};
this.$root.replaceQueue(this.playlist.tracks);
this.$root.playIndex(index);
await this.$root.replaceQueue(this.playlist.tracks);
await this.$root.playIndex(index);
//Load rest of tracks on background
if (this.playlist.tracks.length < this.playlist.trackCount) {
this.loadAllTracks().then(() => {
this.$root.replaceQueue(this.playlist.tracks);
this.loadAllTracks().then(async () => {
await this.$root.replaceQueue(this.playlist.tracks);
});
}
},


+ 3
- 3
app/client/src/views/Search.vue View File

@ -128,14 +128,14 @@ export default {
});
},
//On click for track tile
playTrack(i) {
async playTrack(i) {
this.$root.queue.source = {
text: this.$t("Search"),
source: "search",
data: this.query
};
this.$root.replaceQueue(this.data.tracks);
this.$root.playIndex(i);
await this.$root.replaceQueue(this.data.tracks);
await this.$root.playIndex(i);
}
},
watch: {


+ 2
- 2
app/src/integrations.js View File

@ -104,7 +104,7 @@ class Integrations extends EventEmitter {
//Called when playback state changed
async updateState(data) {
if (this.discordReady) {
if (data.state == 2){
if (data.playing){
let richPresence = {
state: data.track.artistString,
details: data.track.title,
@ -112,7 +112,7 @@ class Integrations extends EventEmitter {
instance: true,
}
//Show timestamp only if playing
if (data.state == 2) {
if (data.playing) {
Object.assign(richPresence, {
startTimestamp: Date.now() - data.position,
endTimestamp: (Date.now() - data.position) + data.duration,


Loading…
Cancel
Save