|
|
|
@ -73,7 +73,7 @@ new Vue({ |
|
|
|
track: null, |
|
|
|
audio: null, |
|
|
|
volume: 0.00, |
|
|
|
//0 = Stopped, 1 = Paused, 2 = Playing, 3 = Loading
|
|
|
|
//0 = Stopped, 1 = Paused, 2 = Playing, 3 = Loading, 4 = Starting playing
|
|
|
|
state: 0, |
|
|
|
loaders: 0, |
|
|
|
playbackInfo: {}, |
|
|
|
@ -88,7 +88,7 @@ new Vue({ |
|
|
|
}, |
|
|
|
|
|
|
|
//0 - normal, 1 - repeat list, 2 - repeat track
|
|
|
|
repeat: 0, |
|
|
|
repeatMode: 0, |
|
|
|
shuffled: false, |
|
|
|
|
|
|
|
//Library cache
|
|
|
|
@ -122,26 +122,42 @@ new Vue({ |
|
|
|
methods: { |
|
|
|
// PLAYBACK METHODS
|
|
|
|
isPlaying() { |
|
|
|
return this.state == 2; |
|
|
|
return this.state == 2 || this.state == 4; |
|
|
|
}, |
|
|
|
|
|
|
|
play() { |
|
|
|
if (!this.audio || this.state != 1) return; |
|
|
|
this.audio.play(); |
|
|
|
this.state = 2; |
|
|
|
async load() { |
|
|
|
let audio = this.audio; |
|
|
|
if (!audio || this.state != 1) return; |
|
|
|
this.state = 1; |
|
|
|
await audio.play(); |
|
|
|
audio.pause(); |
|
|
|
}, |
|
|
|
|
|
|
|
async play() { |
|
|
|
let audio = this.audio; |
|
|
|
if (!audio || this.state != 1) return; |
|
|
|
this.state = 4; |
|
|
|
await audio.play(); |
|
|
|
if (this.state == 1 || audio != this.audio) { |
|
|
|
audio.pause(); |
|
|
|
} else { |
|
|
|
this.state = 2 |
|
|
|
} |
|
|
|
}, |
|
|
|
pause() { |
|
|
|
if (!this.audio || this.state != 2) return; |
|
|
|
this.audio.pause(); |
|
|
|
if (!this.audio || !this.isPlaying()) return; |
|
|
|
if (this.state == 2) this.audio.pause(); |
|
|
|
this.state = 1; |
|
|
|
}, |
|
|
|
toggle() { |
|
|
|
async toggle() { |
|
|
|
if (this.isPlaying()) return this.pause(); |
|
|
|
this.play(); |
|
|
|
await this.play(); |
|
|
|
}, |
|
|
|
seek(t) { |
|
|
|
if (!this.audio || isNaN(t) || !t) return; |
|
|
|
if (!this.audio || isNaN(t) || (!t && t !== 0)) return; |
|
|
|
//ms -> s
|
|
|
|
if(t < 0) t = 0; |
|
|
|
if(t > this.duration()) t = this.duration(); |
|
|
|
this.audio.currentTime = (t / 1000); |
|
|
|
this.position = t; |
|
|
|
|
|
|
|
@ -169,7 +185,7 @@ new Vue({ |
|
|
|
if (index >= this.queue.data.length || index < 0) return; |
|
|
|
this.queue.index = index; |
|
|
|
await this.playTrack(this.queue.data[this.queue.index]); |
|
|
|
this.play(); |
|
|
|
await this.play(); |
|
|
|
this.savePlaybackInfo(); |
|
|
|
}, |
|
|
|
//Skip n tracks, can be negative
|
|
|
|
@ -209,6 +225,15 @@ new Vue({ |
|
|
|
this.skip(1); |
|
|
|
this.savePlaybackInfo(); |
|
|
|
}, |
|
|
|
|
|
|
|
skipPrev() { |
|
|
|
if (this.queue.index != 0 && (!this.position || this.position <= 10000)) |
|
|
|
this.skip(-1); |
|
|
|
else // Rewind to start if more than 10 seconds in, which is the default behavior for most players
|
|
|
|
this.seek(0); |
|
|
|
this.savePlaybackInfo(); |
|
|
|
}, |
|
|
|
|
|
|
|
toggleMute() { |
|
|
|
if (this.audio) this.audio.muted = !this.audio.muted; |
|
|
|
this.muted = !this.muted; |
|
|
|
@ -220,11 +245,13 @@ new Vue({ |
|
|
|
|
|
|
|
this.track = track; |
|
|
|
this.loaders++; |
|
|
|
this.state = 3; |
|
|
|
//Stop audio
|
|
|
|
let autoplay = (this.state == 2); |
|
|
|
if (this.audio) this.audio.pause(); |
|
|
|
if (this.audio) this.audio.currentTime = 0; |
|
|
|
let autoplay = (this.state == 2 || this.state == 4); |
|
|
|
if (this.audio) { |
|
|
|
this.pause(); |
|
|
|
this.audio.currentTime = 0; |
|
|
|
} |
|
|
|
this.state = 3; |
|
|
|
|
|
|
|
//Load track meta
|
|
|
|
let playbackInfo = await this.loadPlaybackInfo(track.streamUrl, track.duration); |
|
|
|
@ -251,7 +278,7 @@ new Vue({ |
|
|
|
this.audio = new Audio(url); |
|
|
|
this.configureAudio(); |
|
|
|
this.state = 1; |
|
|
|
if (autoplay) this.play(); |
|
|
|
if (autoplay) await this.play(); |
|
|
|
//MediaSession
|
|
|
|
this.updateMediaSession(); |
|
|
|
|
|
|
|
@ -266,7 +293,7 @@ new Vue({ |
|
|
|
|
|
|
|
//Gapless playback
|
|
|
|
if (this.position >= (this.duration() - (this.settings.crossfadeDuration + 7500)) && this.state == 2) { |
|
|
|
if (this.repeat != 2) |
|
|
|
if (this.repeatMode != 2) |
|
|
|
this.loadGapless(); |
|
|
|
} |
|
|
|
|
|
|
|
@ -276,7 +303,8 @@ new Vue({ |
|
|
|
let currentVolume = this.audio.volume; |
|
|
|
let oldAudio = this.audio; |
|
|
|
this.audio = this.gapless.audio; |
|
|
|
this.audio.play(); |
|
|
|
this.state = 1; |
|
|
|
await this.play(); |
|
|
|
|
|
|
|
//Update meta
|
|
|
|
this.playbackInfo = this.gapless.info; |
|
|
|
@ -297,7 +325,7 @@ new Vue({ |
|
|
|
await new Promise((res) => setTimeout(() => res(), 50)); |
|
|
|
} |
|
|
|
//Restore original volume
|
|
|
|
this.audio.voume = currentVolume; |
|
|
|
this.audio.volume = currentVolume; |
|
|
|
|
|
|
|
oldAudio.pause(); |
|
|
|
|
|
|
|
@ -321,15 +349,15 @@ new Vue({ |
|
|
|
if (this.gapless.crossfade) return; |
|
|
|
|
|
|
|
//Repeat track
|
|
|
|
if (this.repeat == 2) { |
|
|
|
if (this.repeatMode == 2) { |
|
|
|
this.seek(0); |
|
|
|
this.audio.play(); |
|
|
|
await this.audio.play(); |
|
|
|
this.updateState(); |
|
|
|
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.skip(-(this.queue.data.length - 1)); |
|
|
|
return; |
|
|
|
} |
|
|
|
@ -347,7 +375,24 @@ new Vue({ |
|
|
|
}, |
|
|
|
//Update media session with current track metadata
|
|
|
|
updateMediaSession() { |
|
|
|
if (!this.track || !('mediaSession' in navigator)) return; |
|
|
|
if (!('mediaSession' in navigator)) return; |
|
|
|
|
|
|
|
//Controls
|
|
|
|
navigator.mediaSession.setActionHandler('play', this.play); |
|
|
|
navigator.mediaSession.setActionHandler('pause', this.pause); |
|
|
|
navigator.mediaSession.setActionHandler('nexttrack', this.skipNext); |
|
|
|
navigator.mediaSession.setActionHandler('previoustrack', this.skipPrev); |
|
|
|
navigator.mediaSession.setActionHandler('seekbackward', details => { |
|
|
|
this.seek(this.position - details.seekOffset * 1000); |
|
|
|
}); |
|
|
|
navigator.mediaSession.setActionHandler('seekforward', details => { |
|
|
|
this.seek(this.position + details.seekOffset * 1000); |
|
|
|
}); |
|
|
|
navigator.mediaSession.setActionHandler('seekto', details => { |
|
|
|
this.seek(details.seekTime * 1000); |
|
|
|
}); |
|
|
|
|
|
|
|
if (!this.track) return; |
|
|
|
|
|
|
|
// eslint-disable-next-line no-undef
|
|
|
|
navigator.mediaSession.metadata = new MediaMetadata({ |
|
|
|
@ -359,11 +404,6 @@ new Vue({ |
|
|
|
{src: this.getImageUrl(this.track.albumArt, 512), sizes: '512x512', type: 'image/jpeg'} |
|
|
|
] |
|
|
|
}); |
|
|
|
//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)); |
|
|
|
}, |
|
|
|
//Get Deezer CDN image url
|
|
|
|
getImageUrl(img, size = 256) { |
|
|
|
@ -406,7 +446,7 @@ new Vue({ |
|
|
|
if (this.loaders != 0 || 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 { |
|
|
|
@ -466,7 +506,7 @@ new Vue({ |
|
|
|
queue: this.queue, |
|
|
|
position: this.position, |
|
|
|
track: this.track, |
|
|
|
repeat: this.repeat, |
|
|
|
repeatMode: this.repeatMode, |
|
|
|
shuffled: this.shuffled |
|
|
|
} |
|
|
|
await this.$axios.post('/playback', data); |
|
|
|
@ -503,12 +543,13 @@ 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, |
|
|
|
duration: this.duration(), |
|
|
|
@ -516,9 +557,17 @@ new Vue({ |
|
|
|
track: this.track |
|
|
|
}); |
|
|
|
|
|
|
|
if (('mediaSession' in navigator) && navigator.mediaSession.setPositionState && this.duration() != null && !isNaN(this.duration())) { |
|
|
|
navigator.mediaSession.setPositionState({ |
|
|
|
position: this.position / 1000, |
|
|
|
duration: this.duration() / 1000, |
|
|
|
playbackRate: 1, |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
//Update in electron
|
|
|
|
if (this.settings.electron) { |
|
|
|
ipcRenderer.send('playing', this.state == 2); |
|
|
|
ipcRenderer.send('playing', this.isPlaying()); |
|
|
|
} |
|
|
|
}, |
|
|
|
updateLanguage(l) { |
|
|
|
@ -574,10 +623,11 @@ 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.load(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
@ -603,14 +653,14 @@ new Vue({ |
|
|
|
}); |
|
|
|
|
|
|
|
//Control from electron
|
|
|
|
ipcRenderer.on('togglePlayback', () => { |
|
|
|
this.toggle(); |
|
|
|
ipcRenderer.on('togglePlayback', async () => { |
|
|
|
await this.toggle(); |
|
|
|
}); |
|
|
|
ipcRenderer.on('skipNext', () => { |
|
|
|
this.skip(1); |
|
|
|
this.skipNext(); |
|
|
|
}); |
|
|
|
ipcRenderer.on('skipPrev', () => { |
|
|
|
this.skip(-1); |
|
|
|
this.skipPrev(); |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
@ -685,14 +735,14 @@ new Vue({ |
|
|
|
}); |
|
|
|
|
|
|
|
//Keystrokes
|
|
|
|
document.addEventListener('keyup', (e) => { |
|
|
|
document.addEventListener('keyup', async (e) => { |
|
|
|
//Don't handle keystrokes in text fields
|
|
|
|
if (e.target.tagName == "INPUT") return; |
|
|
|
//Don't handle if specials
|
|
|
|
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return; |
|
|
|
|
|
|
|
//K toggle playback
|
|
|
|
if (e.code == "KeyK" || e.code == "Space") this.$root.toggle(); |
|
|
|
if (e.code == "KeyK" || e.code == "Space") await this.$root.toggle(); |
|
|
|
//L +10s (from YT)
|
|
|
|
if (e.code == "KeyL") this.$root.seek((this.position + 10000)); |
|
|
|
//J -10s (from YT)
|
|
|
|
|