feat: add reversed mode & improvements to lyrics animation (#1226)

* feat(reversed-mode): add reversed mode & improvements to lyrics animation

* feat(lyrics-animation): improve `:active` style

* fix: extra white space after artist in lyrics view

* refactor: remove unused function

* feat: slightly add duration of transition to improve replication from Apple Music style
This commit is contained in:
GalvinGao 2022-01-14 15:30:19 +08:00 committed by GitHub
parent 3ea5446fcc
commit 42f3da9b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 27 deletions

View File

@ -0,0 +1,2 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sort-amount-up" class="svg-inline--fa fa-sort-amount-up fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M304 416h-64a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h64a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h48v304a16 16 0 0 0 16 16h32a16 16 0 0 0 16-16V160h48c14.21 0 21.38-17.24 11.31-27.31l-80-96a16 16 0 0 0-22.62 0l-80 96C-5.35 142.74 1.77 160 16 160zm416 0H240a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-64 128H240a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM496 32H240a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h256a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"></path></svg>

After

Width:  |  Height:  |  Size: 784 B

View File

@ -132,6 +132,13 @@
@click.native="player.switchShuffle"
><svg-icon icon-class="shuffle"
/></button-icon>
<button-icon
v-if="settings.enableReversedMode"
:class="{ active: player.reversed, disabled: player.isPersonalFM }"
:title="$t('player.reversed')"
@click.native="player.switchReversed"
><svg-icon icon-class="sort-up"
/></button-icon>
<div class="volume-control">
<button-icon :title="$t('player.mute')" @click.native="player.mute">
<svg-icon v-show="volume > 0.5" icon-class="volume" />

View File

@ -100,6 +100,7 @@ export default {
repeat: 'Repeat',
repeatTrack: 'Repeat Track',
shuffle: 'Shuffle',
reversed: 'Reversed',
play: 'Play',
pause: 'Pause',
mute: 'Mute',
@ -154,8 +155,9 @@ export default {
showPlaylistsByAppleMusic: 'Show playlists by Apple Music',
enableDiscordRichPresence: 'Enable Discord Rich Presence',
enableGlobalShortcut: 'Enable Global Shortcut',
showLibraryDefault: 'Show library default',
subTitleDefault: 'Sub title alia default',
showLibraryDefault: 'Show Library after App Launched',
subTitleDefault: 'Show Alias for Subtitle by default',
enableReversedMode: 'Enable Reversed Mode (Experimental)',
lyricsBackground: {
text: 'Show Lyrics Background',
off: 'Off',

View File

@ -101,6 +101,7 @@ export default {
repeat: '循环播放',
repeatTrack: '单曲循环',
shuffle: '随机播放',
reversed: '倒序播放',
play: '播放',
pause: '暂停',
mute: '静音',
@ -157,6 +158,7 @@ export default {
enableGlobalShortcut: '启用全局快捷键',
showLibraryDefault: '启动后显示音乐库',
subTitleDefault: '副标题使用别名',
enableReversedMode: '启用倒序播放功能 (实验性功能)',
lyricsBackground: {
text: '显示歌词背景',
off: '关闭',

View File

@ -97,6 +97,7 @@ export default {
repeat: '循環播放',
repeatTrack: '單曲循環',
shuffle: '隨機播放',
reversed: '倒序播放',
play: '播放',
pause: '暫停',
mute: '靜音',
@ -154,6 +155,7 @@ export default {
enableGlobalShortcut: '啟用全域快捷鍵',
showLibraryDefault: '啟動後顯示音樂庫',
subTitleDefault: '副標題使用別名',
enableReversedMode: '啟用倒序播放功能 (實驗性功能)',
lyricsBackground: {
text: '顯示歌詞背景',
off: '關閉',

View File

@ -19,6 +19,7 @@ let localStorage = {
enableUnblockNeteaseMusic: true,
automaticallyCacheSongs: true,
cacheLimit: 8192,
enableReversedMode: false,
nyancatStyle: false,
showLyricsTranslation: true,
lyricsBackground: true,

View File

@ -28,6 +28,7 @@ export default class {
this._enabled = false; // 是否启用Player
this._repeatMode = 'off'; // off | on | one
this._shuffle = false; // true | false
this._reversed = false;
this._volume = 1; // 0 to 1
this._volumeBeforeMuted = 1; // 用于保存静音前的音量
this._personalFMLoading = false; // 是否正在私人FM中加载新的track
@ -91,6 +92,18 @@ export default class {
this._shuffleTheList();
}
}
get reversed() {
return this._reversed;
}
set reversed(reversed) {
if (this._isPersonalFM) return;
if (reversed !== true && reversed !== false) {
console.warn('reversed: invalid args, must be Boolean');
return;
}
console.log('changing reversed to:', reversed);
this._reversed = reversed;
}
get volume() {
return this._volume;
}
@ -191,27 +204,43 @@ export default class {
}, 1000);
}
_getNextTrack() {
const next = this._reversed ? this.current - 1 : this.current + 1;
if (this._playNextList.length > 0) {
let trackID = this._playNextList.shift();
return [trackID, this.current];
}
// 当歌曲是列表最后一首 && 循环模式开启
if (this.list.length === this.current + 1 && this.repeatMode === 'on') {
return [this.list[0], 0];
// 循环模式开启,则重新播放当前模式下的相对的下一首
if (this.repeatMode === 'on') {
if (this._reversed && this.current === 0) {
// 倒序模式,当前歌曲是第一首,则重新播放列表最后一首
return [this.list[this.list.length - 1], this.list.length - 1];
} else if (this.list.length === this.current + 1) {
// 正序模式,当前歌曲是最后一首,则重新播放第一首
return [this.list[0], 0];
}
}
// 返回 [trackID, index]
return [this.list[this.current + 1], this.current + 1];
return [this.list[next], next];
}
_getPrevTrack() {
// 当歌曲是列表第一首 && 循环模式开启
if (this.current === 0 && this.repeatMode === 'on') {
return [this.list[this.list.length - 1], this.list.length - 1];
const next = this._reversed ? this.current + 1 : this.current - 1;
// 循环模式开启,则重新播放当前模式下的相对的下一首
if (this.repeatMode === 'on') {
if (this._reversed && this.current === 0) {
// 倒序模式,当前歌曲是最后一首,则重新播放列表第一首
return [this.list[0], 0];
} else if (this.list.length === this.current + 1) {
// 正序模式,当前歌曲是第一首,则重新播放列表最后一首
return [this.list[this.list.length - 1], this.list.length - 1];
}
}
// 返回 [trackID, index]
return [this.list[this.current - 1], this.current - 1];
return [this.list[next], next];
}
async _shuffleTheList(firstTrackID = this._currentTrack.id) {
let list = this._list.filter(tid => tid !== firstTrackID);
@ -726,6 +755,9 @@ export default class {
switchShuffle() {
this.shuffle = !this.shuffle;
}
switchReversed() {
this.reversed = !this.reversed;
}
clearPlayNextList() {
this._playNextList = [];

View File

@ -59,8 +59,8 @@
<router-link
:to="`/artist/${artist.id}`"
@click.native="toggleLyrics"
>{{ artist.name }}
</router-link>
>{{ artist.name }}</router-link
>
<span v-if="album.id !== 0">
-
<router-link
@ -187,8 +187,18 @@
}"
@click="clickLyricLine(line.time)"
@dblclick="clickLyricLine(line.time, true)"
><span v-html="formatLine(line)"></span
></div>
>
<span v-if="line.contents[0]">{{ line.contents[0] }}</span>
<br />
<span
v-if="
line.contents[1] &&
$store.state.settings.showLyricsTranslation
"
class="translation"
>{{ line.contents[1] }}</span
>
</div>
</div>
</transition>
</div>
@ -400,16 +410,6 @@ export default {
}
}, 50);
},
formatLine(line) {
const showLyricsTranslation = this.$store.state.settings
.showLyricsTranslation;
if (showLyricsTranslation && line.contents[1]) {
return `<span>${line.contents[0]}<br/>${line.contents[1]}</span>`;
} else if (line.contents[0] !== undefined) {
return `<span>${line.contents[0]}</span>`;
}
return 'unknown';
},
moveToFMTrash() {
this.player.moveToFMTrash();
},
@ -673,17 +673,28 @@ export default {
scrollbar-width: none; // firefox
.line {
padding: 18px;
transition: 0.2s;
margin: 2px 0;
padding: 12px 18px;
transition: 0.5s;
border-radius: 12px;
&:hover {
background: var(--color-secondary-bg-for-transparent);
}
&:active {
transform: scale(0.95);
}
span {
opacity: 0.28;
cursor: default;
font-size: 1em;
transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
span.translation {
opacity: 0.2;
font-size: 0.95em;
}
}
@ -691,9 +702,19 @@ export default {
background: unset;
}
.translation {
margin-top: 0.1em;
}
.highlight span {
opacity: 0.98;
transition: 0.5s;
display: inline-block;
font-size: 1.25em;
}
.highlight span.translation {
opacity: 0.65;
font-size: 1.1em;
}
}
@ -755,6 +776,12 @@ export default {
}
}
@media screen and (min-width: 1200px) {
.right-side .lyrics-container {
max-width: 600px;
}
}
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.4s;

View File

@ -384,6 +384,23 @@
</div>
</div>
<div class="item">
<div class="left">
<div class="title">{{ $t('settings.enableReversedMode') }}</div>
</div>
<div class="right">
<div class="toggle">
<input
id="enable-reversed-mode"
v-model="enableReversedMode"
type="checkbox"
name="enable-reversed-mode"
/>
<label for="enable-reversed-mode"></label>
</div>
</div>
</div>
<div class="item">
<div class="left">
<div class="title" style="transform: scaleX(-1)">🐈 🏳🌈</div>
@ -805,6 +822,21 @@ export default {
});
},
},
enableReversedMode: {
get() {
if (this.settings.enableReversedMode === undefined) return false;
return this.settings.enableReversedMode;
},
set(value) {
this.$store.commit('updateSettings', {
key: 'enableReversedMode',
value,
});
if (value === false) {
this.$store.state.player.reversed = false;
}
},
},
enableGlobalShortcut: {
get() {
return this.settings.enableGlobalShortcut;