Bug fixes, better lyrics, sorting
This commit is contained in:
parent
83860ff052
commit
80f6cbf870
Binary file not shown.
After Width: | Height: | Size: 370 B |
Binary file not shown.
After Width: | Height: | Size: 552 B |
Binary file not shown.
After Width: | Height: | Size: 479 B |
Binary file not shown.
After Width: | Height: | Size: 524 B |
|
@ -7,6 +7,7 @@ let tray;
|
||||||
let settings;
|
let settings;
|
||||||
|
|
||||||
let shouldExit = false;
|
let shouldExit = false;
|
||||||
|
let playing = false;
|
||||||
|
|
||||||
//Get path to asset
|
//Get path to asset
|
||||||
function assetPath(a) {
|
function assetPath(a) {
|
||||||
|
@ -74,26 +75,38 @@ async function createWindow() {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Thumbnail Toolbars
|
||||||
|
setThumbarButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create window
|
//Create window
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
//Create tray
|
//Create Tray
|
||||||
tray = new Tray(assetPath("icon-taskbar.png"));
|
tray = new Tray(assetPath("icon-taskbar.png"));
|
||||||
tray.on('double-click', () => win.show());
|
tray.on('double-click', () => win.show());
|
||||||
tray.on('click', () => win.show());
|
tray.on('click', () => win.show());
|
||||||
|
|
||||||
//Tray menu
|
setTray();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Update tray context menu
|
||||||
|
function setTray() {
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
label: 'Restore',
|
label: 'Restore',
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
click: () => win.show()
|
click: () => win.show()
|
||||||
},
|
},
|
||||||
|
playing ?
|
||||||
{
|
{
|
||||||
label: 'Play/Pause',
|
label: 'Pause',
|
||||||
|
type: 'normal',
|
||||||
|
click: () => win.webContents.send('togglePlayback')
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
label: 'Play',
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
click: () => win.webContents.send('togglePlayback')
|
click: () => win.webContents.send('togglePlayback')
|
||||||
},
|
},
|
||||||
|
@ -117,6 +130,42 @@ app.on('ready', async () => {
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
tray.setContextMenu(contextMenu);
|
tray.setContextMenu(contextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Thumbnail Toolbars (Windows)
|
||||||
|
function setThumbarButtons() {
|
||||||
|
win.setThumbarButtons([
|
||||||
|
{
|
||||||
|
tooltip: 'Skip Previous',
|
||||||
|
icon: assetPath('skip-previous.png'),
|
||||||
|
click: () => win.webContents.send('skipPrev')
|
||||||
|
},
|
||||||
|
//Play/Pause
|
||||||
|
playing ?
|
||||||
|
{
|
||||||
|
tooltip: 'Pause',
|
||||||
|
icon: assetPath('pause.png'),
|
||||||
|
click: () => win.webContents.send('togglePlayback')
|
||||||
|
} :
|
||||||
|
{
|
||||||
|
tooltip: 'Play',
|
||||||
|
icon: assetPath('play.png'),
|
||||||
|
click: () => win.webContents.send('togglePlayback')
|
||||||
|
},
|
||||||
|
//Skip next
|
||||||
|
{
|
||||||
|
tooltip: 'Skip Next',
|
||||||
|
icon: assetPath('skip-next.png'),
|
||||||
|
click: () => win.webContents.send('skipNext')
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Playing state change from UI
|
||||||
|
ipcMain.on('playing', (event, args) => {
|
||||||
|
playing = args;
|
||||||
|
setThumbarButtons();
|
||||||
|
setTray();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Update settings from ui
|
//Update settings from ui
|
||||||
|
|
|
@ -371,8 +371,8 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
// /search
|
// /search
|
||||||
document.addEventListener('keypress', (event) => {
|
document.addEventListener('keypress', (e) => {
|
||||||
if (event.keyCode != 47) return;
|
if (e.keyCode != 47) return;
|
||||||
this.$refs.searchBar.focus();
|
this.$refs.searchBar.focus();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.searchQuery.startsWith('/')) this.searchQuery = this.searchQuery.substring(1);
|
if (this.searchQuery.startsWith('/')) this.searchQuery = this.searchQuery.substring(1);
|
||||||
|
@ -394,6 +394,9 @@ export default {
|
||||||
if (this.$root.audio) this.$root.audio.volume = this.volume;
|
if (this.$root.audio) this.$root.audio.volume = this.volume;
|
||||||
this.$root.volume = this.volume;
|
this.$root.volume = this.volume;
|
||||||
},
|
},
|
||||||
|
'$root.volume'() {
|
||||||
|
this.volume = this.$root.volume;
|
||||||
|
},
|
||||||
//Update position
|
//Update position
|
||||||
'$root.position'() {
|
'$root.position'() {
|
||||||
this.position = (this.$root.position / this.$root.duration()) * 100;
|
this.position = (this.$root.position / this.$root.duration()) * 100;
|
||||||
|
|
|
@ -1,5 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<v-list :height='height' class='overflow-y-auto' v-scroll.self='scroll'>
|
<div v-scroll.self='scroll'>
|
||||||
|
<div class='px-4 pt-2 d-flex' style='max-height: 50px;'>
|
||||||
|
<div class='text-overline px-2 pt-1'>
|
||||||
|
{{count}} TRACKS.
|
||||||
|
</div>
|
||||||
|
<div style="max-width: 200px;" class='d-flex mx-2'>
|
||||||
|
<v-select class='px-2' dense solo :items='sortTypes' @change='sort' label='Sort By'>
|
||||||
|
</v-select>
|
||||||
|
</div>
|
||||||
|
<div class='px-2' @click='reverseSort'>
|
||||||
|
<v-btn icon>
|
||||||
|
<v-icon v-if='isReversed'>mdi-sort-reverse-variant</v-icon>
|
||||||
|
<v-icon v-if='!isReversed'>mdi-sort-variant</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-list :height='height' class='overflow-y-auto'>
|
||||||
<v-lazy
|
<v-lazy
|
||||||
v-for='(track, index) in tracks'
|
v-for='(track, index) in tracks'
|
||||||
:key='index + "t" + track.id'
|
:key='index + "t" + track.id'
|
||||||
|
@ -11,9 +28,8 @@
|
||||||
<div class='text-center' v-if='loading'>
|
<div class='text-center' v-if='loading'>
|
||||||
<v-progress-circular indeterminate></v-progress-circular>
|
<v-progress-circular indeterminate></v-progress-circular>
|
||||||
</div>
|
</div>
|
||||||
|
</v-list>
|
||||||
|
</div>
|
||||||
</v-list>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -28,7 +44,15 @@ export default {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
tracks: [],
|
tracks: [],
|
||||||
count: 0
|
count: 0,
|
||||||
|
sortTypes: [
|
||||||
|
'Date Added',
|
||||||
|
'Name (A-Z)',
|
||||||
|
'Artist (A-Z)',
|
||||||
|
'Album (A-Z)'
|
||||||
|
],
|
||||||
|
tracksUnsorted: null,
|
||||||
|
isReversed: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -88,8 +112,44 @@ export default {
|
||||||
this.$root.replaceQueue(this.tracks);
|
this.$root.replaceQueue(this.tracks);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
//Sort changed
|
||||||
|
async sort(type) {
|
||||||
|
let index = this.sortTypes.indexOf(type);
|
||||||
|
//Preload all tracks
|
||||||
|
if (this.tracks.length < this.count)
|
||||||
|
await this.loadAll();
|
||||||
|
//Copy original
|
||||||
|
if (!this.tracksUnsorted)
|
||||||
|
this.tracksUnsorted = JSON.parse(JSON.stringify(this.tracks));
|
||||||
|
|
||||||
|
//Using indexes, so it can be translated later
|
||||||
|
this.isReversed = false;
|
||||||
|
switch (index) {
|
||||||
|
//Default
|
||||||
|
case 0:
|
||||||
|
this.tracks = JSON.parse(JSON.stringify(this.tracksUnsorted));
|
||||||
|
break;
|
||||||
|
//Name
|
||||||
|
case 1:
|
||||||
|
this.tracks = this.tracks.sort((a, b) => {return a.title.localeCompare(b.title);});
|
||||||
|
break;
|
||||||
|
//Artist
|
||||||
|
case 2:
|
||||||
|
this.tracks = this.tracks.sort((a, b) => {return a.artistString.localeCompare(b.artistString);});
|
||||||
|
break;
|
||||||
|
//Album
|
||||||
|
case 3:
|
||||||
|
this.tracks = this.tracks.sort((a, b) => {return a.album.title.localeCompare(b.album.title);});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async reverseSort() {
|
||||||
|
//Preload tracks if not sorted yet
|
||||||
|
if (this.tracks.length < this.count)
|
||||||
|
await this.sort(0);
|
||||||
|
this.isReversed = !this.isReversed;
|
||||||
|
this.tracks.reverse();
|
||||||
},
|
},
|
||||||
removedTrack(index) {
|
removedTrack(index) {
|
||||||
this.tracks.splice(index, 1);
|
this.tracks.splice(index, 1);
|
||||||
|
|
|
@ -5,7 +5,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if='!loading && lyrics && lyrics.lyrics.length > 0' class='text-center'>
|
<div v-if='!loading && lyrics && lyrics.lyrics.length > 0' class='text-center'>
|
||||||
<div v-for='(lyric, index) in lyrics.lyrics' :key='lyric.offset' class='my-8 mx-4'>
|
<div
|
||||||
|
v-for='(lyric, index) in lyrics.lyrics'
|
||||||
|
:key='lyric.offset'
|
||||||
|
class='my-6 mx-4 pa-2 rounded'
|
||||||
|
:class='{"grey darken-3": playingNow(index)}'
|
||||||
|
@click='seekTo(index)'>
|
||||||
<span
|
<span
|
||||||
class='my-8'
|
class='my-8'
|
||||||
:class='{"text-h6 font-weight-regular": !playingNow(index), "text-h5 font-weight-bold": playingNow(index)}'
|
:class='{"text-h6 font-weight-regular": !playingNow(index), "text-h5 font-weight-bold": playingNow(index)}'
|
||||||
|
@ -27,7 +32,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error -->
|
<!-- Error -->
|
||||||
<div v-if='!loading && !lyrics && lyrics.text.length == 0 && lyrics.lyrics.length == 0' class='pa-4 text-center'>
|
<div v-if='!loading && (!lyrics || (lyrics.text.length == 0 && lyrics.lyrics.length == 0))' class='pa-4 text-center'>
|
||||||
<span class='red--text text-h5'>
|
<span class='red--text text-h5'>
|
||||||
Error loading lyrics or lyrics not found!
|
Error loading lyrics or lyrics not found!
|
||||||
</span>
|
</span>
|
||||||
|
@ -94,10 +99,15 @@ export default {
|
||||||
//Roughly middle
|
//Roughly middle
|
||||||
let offset = window.innerHeight / 2 - 500;
|
let offset = window.innerHeight / 2 - 500;
|
||||||
|
|
||||||
|
if (!this.$refs["l"+this.currentLyricIndex]) return;
|
||||||
this.$refs.content.scrollTo({
|
this.$refs.content.scrollTo({
|
||||||
top: this.$refs["l"+this.currentLyricIndex][0].offsetTop + offset,
|
top: this.$refs["l"+this.currentLyricIndex][0].offsetTop + offset,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
//Seek to lyric in song
|
||||||
|
seekTo(i) {
|
||||||
|
this.$root.seek(this.lyrics.lyrics[i].offset);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
@ -177,7 +177,7 @@ export default {
|
||||||
addLibrary() {
|
addLibrary() {
|
||||||
this.isLibrary = true;
|
this.isLibrary = true;
|
||||||
this.$root.libraryTracks.push(this.track.id);
|
this.$root.libraryTracks.push(this.track.id);
|
||||||
this.$axios.put(`/library/tracks?id=${this.track.id}`);
|
this.$axios.put(`/library/track?id=${this.track.id}`);
|
||||||
},
|
},
|
||||||
goAlbum() {
|
goAlbum() {
|
||||||
this.$emit('redirect')
|
this.$emit('redirect')
|
||||||
|
@ -196,7 +196,7 @@ export default {
|
||||||
async removeLibrary() {
|
async removeLibrary() {
|
||||||
this.isLibrary = false;
|
this.isLibrary = false;
|
||||||
this.$root.libraryTracks.splice(this.$root.libraryTracks.indexOf(this.track.id), 1);
|
this.$root.libraryTracks.splice(this.$root.libraryTracks.indexOf(this.track.id), 1);
|
||||||
await this.$axios.delete(`/library/tracks?id=${this.track.id}`);
|
await this.$axios.delete(`/library/track?id=${this.track.id}`);
|
||||||
this.$emit('remove');
|
this.$emit('remove');
|
||||||
},
|
},
|
||||||
//Remove from playlist
|
//Remove from playlist
|
||||||
|
|
|
@ -213,6 +213,8 @@ new Vue({
|
||||||
this.configureAudio();
|
this.configureAudio();
|
||||||
this.state = 1;
|
this.state = 1;
|
||||||
if (autoplay) this.play();
|
if (autoplay) this.play();
|
||||||
|
//MediaSession
|
||||||
|
this.updateMediaSession();
|
||||||
|
|
||||||
//Loads more tracks if end of list
|
//Loads more tracks if end of list
|
||||||
this.loadSTL();
|
this.loadSTL();
|
||||||
|
@ -275,6 +277,7 @@ new Vue({
|
||||||
//Play
|
//Play
|
||||||
this.state = 2;
|
this.state = 2;
|
||||||
this.audio.play();
|
this.audio.play();
|
||||||
|
this.updateMediaSession();
|
||||||
await this.savePlaybackInfo();
|
await this.savePlaybackInfo();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -282,7 +285,6 @@ new Vue({
|
||||||
this.skip(1);
|
this.skip(1);
|
||||||
this.savePlaybackInfo();
|
this.savePlaybackInfo();
|
||||||
});
|
});
|
||||||
this.updateMediaSession();
|
|
||||||
},
|
},
|
||||||
//Update media session with current track metadata
|
//Update media session with current track metadata
|
||||||
updateMediaSession() {
|
updateMediaSession() {
|
||||||
|
@ -417,6 +419,12 @@ new Vue({
|
||||||
state: this.state,
|
state: this.state,
|
||||||
track: this.track
|
track: this.track
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Update in electron
|
||||||
|
if (this.settings.electron) {
|
||||||
|
const {ipcRenderer} = window.require('electron');
|
||||||
|
ipcRenderer.send('playing', this.state == 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
|
@ -511,17 +519,40 @@ new Vue({
|
||||||
//Don't handle keystrokes in text fields
|
//Don't handle keystrokes in text fields
|
||||||
if (e.target.tagName == "INPUT") return;
|
if (e.target.tagName == "INPUT") return;
|
||||||
//K toggle playback
|
//K toggle playback
|
||||||
//e.keyCode === 32
|
if (e.code == "KeyK" || e.code == "Space") this.$root.toggle();
|
||||||
if (e.keyCode === 75 || e.keyCode === 107) this.$root.toggle();
|
|
||||||
//L +10s (from YT)
|
//L +10s (from YT)
|
||||||
if (e.keyCode === 108 || e.keyCode === 76) this.$root.seek((this.position + 10000));
|
if (e.code == "KeyL") this.$root.seek((this.position + 10000));
|
||||||
//J -10s (from YT)
|
//J -10s (from YT)
|
||||||
if (e.keyCode === 106 || e.keyCode === 74) 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));
|
||||||
|
//<- -5s (from YT)
|
||||||
|
if (e.code == "ArrowLeft") this.$root.seek((this.position - 5000));
|
||||||
|
// ^ v - Volume
|
||||||
|
if (e.code == 'ArrowUp' && this.audio) {
|
||||||
|
if ((this.audio.volume + 0.05) > 1) {
|
||||||
|
this.audio.volume = 1.00;
|
||||||
|
this.volume = 1.00;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.audio.volume += 0.05;
|
||||||
|
this.volume = this.audio.volume;
|
||||||
|
}
|
||||||
|
if (e.code == 'ArrowDown' && this.audio) {
|
||||||
|
if ((this.audio.volume - 0.05) < 0) {
|
||||||
|
this.audio.volume = 0.00;
|
||||||
|
this.volume = 0.00;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.audio.volume -= 0.05;
|
||||||
|
this.volume = this.audio.volume;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
//Watch state for integrations
|
//Watch state for integrations
|
||||||
state() {
|
state() {
|
||||||
|
this.updateMediaSession();
|
||||||
this.updateState();
|
this.updateState();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "freezer",
|
"name": "freezer",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "background.js",
|
"main": "background.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -192,7 +192,7 @@ class DeezerAPI {
|
||||||
url: `/stream/${info}?q=9`
|
url: `/stream/${info}?q=9`
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warning('Qualiy fallback: ' + e);
|
logger.warn('Qualiy fallback: ' + e);
|
||||||
//Fallback
|
//Fallback
|
||||||
//9 - FLAC
|
//9 - FLAC
|
||||||
//3 - MP3 320
|
//3 - MP3 320
|
||||||
|
|
|
@ -80,7 +80,7 @@ class Integrations extends EventEmitter {
|
||||||
//Always accept join requests
|
//Always accept join requests
|
||||||
this.discordRPC.subscribe('ACTIVITY_JOIN_REQUEST', (user) => {
|
this.discordRPC.subscribe('ACTIVITY_JOIN_REQUEST', (user) => {
|
||||||
this.discordRPC.sendJoinInvite(user.user).catch((e) => {
|
this.discordRPC.sendJoinInvite(user.user).catch((e) => {
|
||||||
logger.warning('Unable to accept Discord invite: ' + e);
|
logger.warn('Unable to accept Discord invite: ' + e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
//Joined
|
//Joined
|
||||||
|
@ -93,7 +93,6 @@ class Integrations extends EventEmitter {
|
||||||
});
|
});
|
||||||
//Connect to discord
|
//Connect to discord
|
||||||
this.discordRPC.login({clientId: CLIENTID}).catch(() => {
|
this.discordRPC.login({clientId: CLIENTID}).catch(() => {
|
||||||
logger.info('Error connecting to Discord!');
|
|
||||||
//Wait 5s to retry
|
//Wait 5s to retry
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.discordReady)
|
if (!this.discordReady)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "freezer",
|
"name": "freezer",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"description": "",
|
"description": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
|
|
Loading…
Reference in New Issue