#28 (WIP) Fix some bugs and improve Russian TL

Closed
chayleaf wants to merge 7 commits from chayleaf/freezerpc:master into master
  1. +7
    -7
      app/client/src/locales/ru.json
  2. +91
    -41
      app/client/src/main.js
  3. +7
    -7
      app/client/src/views/FullscreenPlayer.vue

+ 7
- 7
app/client/src/locales/ru.json View File

@ -5,7 +5,7 @@
"Tracks": "Треки",
"Playlists": "Плейлисты",
"Albums": "Альбомы",
"Artists": "Артисты",
"Artists": "Исполнители",
"More": "Ещё",
"Settings": "Настройки",
"Downloads": "Загрузки",
@ -20,7 +20,7 @@
"Start downloading": "Начать загрузку",
"Cancel": "Отмена",
"Stream logging is disabled!": "Отправка статистики отключена!",
"Enable it in settings for history to work properly.": "Включите её в настройках для работы рекомендаций.",
"Enable it in settings for history to work properly.": "Включите её в настройках для работы истории.",
"History": "История",
"Create new playlist": "Новый плейлист",
"TRACKS": "Треки",
@ -48,7 +48,7 @@
"albums": "альбомы",
"Play top": "Играть популярные",
"Radio": "Радио",
"Show all albums": "Показать все",
"Show all albums": "Показать все альбомы",
"Show all singles": "Показать все синглы",
"Show more": "Ещё",
"Downloaded": "Загрузки",
@ -108,7 +108,7 @@
"Don't minimize to tray": "Не сворачивать в трей",
"Close on exit": "Закрывать при выходе",
"Settings saved!": "Настройки сохранены!",
"Available only in Electron version!": "Доступно только в версии на Electron!",
"Available only in Electron version!": "Доступно только в версии Electron!",
"Crossfade (ms)": "Кроссфейд (мс)",
"Select primary color": "Выберите основной цвет",
"Light theme": "Светлая тема",
@ -143,7 +143,7 @@
"Art Resolution": "Разрешение обложки",
"Public": "Публичный",
"Private": "Приватный",
"Collaborative": "Совместное",
"Collaborative": "Совместный",
"Edit playlist": "Изменить плейлист",
"Save": "Сохранить",
"Edit": "Редактировать",
@ -161,11 +161,11 @@
"Artists:": "Исполнители:",
"Yes": "Да",
"No": "Нет",
"Download Filename": "Скачать шаблон для названия",
"Download Filename": "Шаблон названия файла для скачивания",
"Language": "Язык",
"Background Image": "Фоновое изображение",
"Enter URL or absolute path. WARNING: Requires reload!": "Введите URL или полный путь. ВНИМАНИЕ: Требуется перезагрузка!",
"LGBT Mode": "Режим ЛГБТ",
"Native top bar": "Верхнюю панель",
"Native top bar": "Системная верхняя панель",
"Requires restart of Freezer!": "Требуется перезагрузка!"
}

+ 91
- 41
app/client/src/main.js View File

@ -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)


+ 7
- 7
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>
@ -74,9 +74,9 @@
<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-icon v-if='$root.repeatMode == 0 || !$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>
@ -315,11 +315,11 @@ export default {
},
//Repeat button click
repeatClick() {
if (this.$root.repeat == 2) {
this.$root.repeat = 0;
if (this.$root.repeatMode == 2) {
this.$root.repeatMode = 0;
return;
}
this.$root.repeat += 1;
this.$root.repeatMode += 1;
},
//Copy link
share() {


Loading…
Cancel
Save