refactor: move all states+actions inside player.vue to Player.js

master
qier222 4 years ago
parent 5355caa4e4
commit fab0227ed3
No known key found for this signature in database
GPG Key ID: 9C85007ED905F14D

@ -46,7 +46,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["showLyrics"]), ...mapState(["showLyrics", "showLibraryDefault", "player"]),
isAccountLoggedIn() { isAccountLoggedIn() {
return isAccountLoggedIn(); return isAccountLoggedIn();
}, },
@ -62,18 +62,14 @@ export default {
); );
}, },
enablePlayer() { enablePlayer() {
return ( return this.player.enabled && this.$route.name !== "lastfmCallback";
this.$store.state.player.enabled &&
this.$route.name !== "lastfmCallback"
);
}, },
showNavbar() { showNavbar() {
return this.$route.name !== "lastfmCallback"; return this.$route.name !== "lastfmCallback";
}, },
}, },
created() { created() {
this.$store.state.settings.showLibraryDefault && this.showLibraryDefault && this.$router.push("/library");
this.$router.push("/library");
if (this.isElectron) { if (this.isElectron) {
ipcRenderer(this); ipcRenderer(this);
} }
@ -85,7 +81,7 @@ export default {
if (e.target.tagName === "INPUT") return false; if (e.target.tagName === "INPUT") return false;
if (this.$route.name === "mv") return false; if (this.$route.name === "mv") return false;
e.preventDefault(); e.preventDefault();
this.$refs.player.play(); this.player.play();
} }
}, },
}, },

@ -9,17 +9,16 @@
@click.stop @click.stop
> >
<vue-slider <vue-slider
v-model="progress" v-model="player.progress"
:min="0" :min="0"
:max="progressMax" :max="player.currentTrackDuration"
:interval="1" :interval="1"
:drag-on-click="true" :drag-on-click="true"
:duration="0" :duration="0"
:dotSize="12" :dot-size="12"
:height="2" :height="2"
:tooltipFormatter="formatTrackTime" :tooltip-formatter="formatTrackTime"
@drag-end="setSeek" @drag-end="player.seek"
ref="progress"
></vue-slider> ></vue-slider>
</div> </div>
<div class="controls"> <div class="controls">
@ -46,16 +45,16 @@
</div> </div>
<div class="like-button"> <div class="like-button">
<button-icon <button-icon
@click.native="likeCurrentSong"
:title="$t('player.like')" :title="$t('player.like')"
@click.native="likeASong(player.currentTrack.id)"
> >
<svg-icon <svg-icon
v-show="!player.isCurrentTrackLiked"
icon-class="heart" icon-class="heart"
v-show="!liked.songs.includes(currentTrack.id)"
></svg-icon> ></svg-icon>
<svg-icon <svg-icon
v-show="player.isCurrentTrackLiked"
icon-class="heart-solid" icon-class="heart-solid"
v-show="liked.songs.includes(currentTrack.id)"
></svg-icon> ></svg-icon>
</button-icon> </button-icon>
</div> </div>
@ -67,24 +66,26 @@
<div class="container" @click.stop> <div class="container" @click.stop>
<button-icon <button-icon
v-show="!player.isPersonalFM" v-show="!player.isPersonalFM"
@click.native="previous"
:title="$t('player.previous')" :title="$t('player.previous')"
@click.native="player.playPrevTrack"
><svg-icon icon-class="previous" ><svg-icon icon-class="previous"
/></button-icon> /></button-icon>
<button-icon <button-icon
v-show="player.isPersonalFM" v-show="player.isPersonalFM"
@click.native="moveToFMTrash"
title="不喜欢" title="不喜欢"
@click.native="player.moveToFMTrash"
><svg-icon icon-class="thumbs-down" ><svg-icon icon-class="thumbs-down"
/></button-icon> /></button-icon>
<button-icon <button-icon
class="play" class="play"
@click.native="play"
:title="$t(player.playing ? 'player.pause' : 'player.play')" :title="$t(player.playing ? 'player.pause' : 'player.play')"
@click.native="player.playOrPause"
> >
<svg-icon :iconClass="player.playing ? 'pause' : 'play'" <svg-icon :icon-class="player.playing ? 'pause' : 'play'"
/></button-icon> /></button-icon>
<button-icon @click.native="next" :title="$t('player.next')" <button-icon
:title="$t('player.next')"
@click.native="player.playNextTrack"
><svg-icon icon-class="next" ><svg-icon icon-class="next"
/></button-icon> /></button-icon>
</div> </div>
@ -94,48 +95,48 @@
<div class="blank"></div> <div class="blank"></div>
<div class="container" @click.stop> <div class="container" @click.stop>
<button-icon <button-icon
@click.native="goToNextTracksPage"
:title="$t('player.nextUp')" :title="$t('player.nextUp')"
:class="{ :class="{
active: this.$route.name === 'next', active: this.$route.name === 'next',
disabled: player.isPersonalFM, disabled: player.isPersonalFM,
}" }"
@click.native="goToNextTracksPage"
><svg-icon icon-class="list" ><svg-icon icon-class="list"
/></button-icon> /></button-icon>
<button-icon <button-icon
:class="{
active: player.repeatMode !== 'off',
disabled: player.isPersonalFM,
}"
:title=" :title="
player.repeatMode === 'one' player.repeatMode === 'one'
? $t('player.repeatTrack') ? $t('player.repeatTrack')
: $t('player.repeat') : $t('player.repeat')
" "
@click.native="repeat" @click.native="player.switchRepeatMode"
:class="{
active: player.repeatMode !== 'off',
disabled: player.isPersonalFM,
}"
> >
<svg-icon <svg-icon
icon-class="repeat"
v-show="player.repeatMode !== 'one'" v-show="player.repeatMode !== 'one'"
icon-class="repeat"
/> />
<svg-icon <svg-icon
icon-class="repeat-1"
v-show="player.repeatMode === 'one'" v-show="player.repeatMode === 'one'"
icon-class="repeat-1"
/> />
</button-icon> </button-icon>
<button-icon <button-icon
@click.native="shuffle"
:class="{ active: player.shuffle, disabled: player.isPersonalFM }" :class="{ active: player.shuffle, disabled: player.isPersonalFM }"
:title="$t('player.shuffle')" :title="$t('player.shuffle')"
@click.native="player.switchShuffle"
><svg-icon icon-class="shuffle" ><svg-icon icon-class="shuffle"
/></button-icon> /></button-icon>
<div class="volume-control"> <div class="volume-control">
<button-icon :title="$t('player.mute')" @click.native="player.mute"> <button-icon :title="$t('player.mute')" @click.native="player.mute">
<svg-icon icon-class="volume" v-show="volume > 0.5" /> <svg-icon v-show="volume > 0.5" icon-class="volume" />
<svg-icon icon-class="volume-mute" v-show="volume === 0" /> <svg-icon v-show="volume === 0" icon-class="volume-mute" />
<svg-icon <svg-icon
icon-class="volume-half"
v-show="volume <= 0.5 && volume !== 0" v-show="volume <= 0.5 && volume !== 0"
icon-class="volume-half"
/> />
</button-icon> </button-icon>
<div class="volume-bar"> <div class="volume-bar">
@ -147,7 +148,7 @@
:drag-on-click="true" :drag-on-click="true"
:duration="0" :duration="0"
:tooltip="`none`" :tooltip="`none`"
:dotSize="12" :dot-size="12"
></vue-slider> ></vue-slider>
</div> </div>
</div> </div>
@ -167,9 +168,6 @@
<script> <script>
import { mapState, mapMutations, mapActions } from "vuex"; import { mapState, mapMutations, mapActions } from "vuex";
import { isAccountLoggedIn } from "@/utils/auth";
import { userLikedSongsIDs } from "@/api/user";
import { likeATrack } from "@/api/track";
import "@/assets/css/slider.css"; import "@/assets/css/slider.css";
import ButtonIcon from "@/components/ButtonIcon.vue"; import ButtonIcon from "@/components/ButtonIcon.vue";
@ -181,24 +179,8 @@ export default {
ButtonIcon, ButtonIcon,
VueSlider, VueSlider,
}, },
data() {
return {
interval: null,
progress: 0,
};
},
mounted() {
setInterval(() => {
this.progress = this.player.seek();
}, 1000);
if (isAccountLoggedIn()) {
userLikedSongsIDs(this.data.user.userId).then((data) => {
this.updateLikedSongs(data.ids);
});
}
},
computed: { computed: {
...mapState(["player", "settings", "liked", "data"]), ...mapState(["player", "settings", "data"]),
currentTrack() { currentTrack() {
return this.player.currentTrack; return this.player.currentTrack;
}, },
@ -213,13 +195,6 @@ export default {
playing() { playing() {
return this.player.playing; return this.player.playing;
}, },
progressMax() {
let max = ~~(this.player.currentTrack.dt / 1000);
return max > 1 ? max - 1 : max;
},
isCurrentTrackLiked() {
return this.liked.songs.includes(this.currentTrack.id);
},
audioSource() { audioSource() {
return this.player._howler?._src.includes("kuwo.cn") return this.player._howler?._src.includes("kuwo.cn")
? "音源来自酷我音乐" ? "音源来自酷我音乐"
@ -227,38 +202,8 @@ export default {
}, },
}, },
methods: { methods: {
...mapMutations(["updateLikedSongs", "toggleLyrics"]), ...mapMutations(["toggleLyrics"]),
...mapActions(["showToast"]), ...mapActions(["showToast", "likeASong"]),
play() {
this.player.playing ? this.player.pause() : this.player.play();
},
next() {
if (this.player.playNextTrack()) this.progress = 0;
},
previous() {
if (this.player.playPrevTrack()) this.progress = 0;
},
shuffle() {
if (this.player.isPersonalFM) return;
this.player.shuffle = !this.player.shuffle;
},
repeat() {
if (this.player.isPersonalFM) return;
if (this.player.repeatMode === "on") {
this.player.repeatMode = "one";
} else if (this.player.repeatMode === "one") {
this.player.repeatMode = "off";
} else {
this.player.repeatMode = "on";
}
},
setSeek() {
this.progress = this.$refs.progress.getValue();
this.player.seek(this.$refs.progress.getValue());
},
setProgress(value) {
this.progress = value;
},
goToNextTracksPage() { goToNextTracksPage() {
if (this.player.isPersonalFM) return; if (this.player.isPersonalFM) return;
this.$route.name === "next" this.$route.name === "next"
@ -271,27 +216,6 @@ export default {
let sec = (~~(value % 60)).toString().padStart(2, "0"); let sec = (~~(value % 60)).toString().padStart(2, "0");
return `${min}:${sec}`; return `${min}:${sec}`;
}, },
likeCurrentSong() {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
return;
}
let id = this.currentTrack.id;
let like = true;
if (this.liked.songs.includes(id)) like = false;
likeATrack({ id, like }).then(() => {
if (like === false) {
this.updateLikedSongs(this.liked.songs.filter((d) => d !== id));
} else {
let newLikeSongs = this.liked.songs;
newLikeSongs.push(id);
this.updateLikedSongs(newLikeSongs);
}
});
},
moveToFMTrash() {
this.player.moveToFMTrash();
},
goToList() { goToList() {
if (this.player.playlistSource.id === this.data.likedSongPlaylistID) { if (this.player.playlistSource.id === this.data.likedSongPlaylistID) {
this.$router.push({ path: "/library/liked-songs" }); this.$router.push({ path: "/library/liked-songs" });

@ -1,3 +1,7 @@
import store from "@/store";
const player = store.state.player;
export function ipcRenderer(vueInstance) { export function ipcRenderer(vueInstance) {
const self = vueInstance; const self = vueInstance;
// 添加专有的类名 // 添加专有的类名
@ -25,46 +29,44 @@ export function ipcRenderer(vueInstance) {
}); });
ipcRenderer.on("play", () => { ipcRenderer.on("play", () => {
self.$refs.player.play(); player.playOrPause();
}); });
ipcRenderer.on("next", () => { ipcRenderer.on("next", () => {
console.log("touchBar:next"); player.playNextTrack();
self.$refs.player.next();
}); });
ipcRenderer.on("previous", () => { ipcRenderer.on("previous", () => {
self.$refs.player.previous(); player.playPrevTrack();
}); });
ipcRenderer.on("increaseVolume", () => { ipcRenderer.on("increaseVolume", () => {
if (self.$refs.player.volume + 0.1 >= 1) { if (player.volume + 0.1 >= 1) {
return (self.$refs.player.volume = 1); return (player.volume = 1);
} }
self.$refs.player.volume += 0.1; player.volume += 0.1;
}); });
ipcRenderer.on("decreaseVolume", () => { ipcRenderer.on("decreaseVolume", () => {
if (self.$refs.player.volume - 0.1 <= 0) { if (player.volume - 0.1 <= 0) {
return (self.$refs.player.volume = 0); return (player.volume = 0);
} }
self.$refs.player.volume -= 0.1; player.volume -= 0.1;
}); });
ipcRenderer.on("like", () => { ipcRenderer.on("like", () => {
self.$refs.player.likeCurrentSong(); store.dispatch("likeASong", player.currentTrack.id);
}); });
ipcRenderer.on("repeat", () => { ipcRenderer.on("repeat", () => {
self.$refs.player.repeat(); player.switchRepeatMode();
}); });
ipcRenderer.on("shuffle", () => { ipcRenderer.on("shuffle", () => {
self.$refs.player.shuffle(); player.switchShuffle();
}); });
ipcRenderer.on("routerGo", (event, where) => { ipcRenderer.on("routerGo", (event, where) => {
console.log(where);
self.$refs.navbar.go(where); self.$refs.navbar.go(where);
}); });

@ -1,3 +1,7 @@
// import store, { state, dispatch, commit } from "@/store";
import { isAccountLoggedIn } from "@/utils/auth";
import { likeATrack } from "@/api/track";
export default { export default {
showToast({ state, commit }, text) { showToast({ state, commit }, text) {
if (state.toast.timer !== null) { if (state.toast.timer !== null) {
@ -16,4 +20,24 @@ export default {
}, 3200), }, 3200),
}); });
}, },
likeASong({ state, commit, dispatch }, id) {
if (!isAccountLoggedIn()) {
dispatch("showToast", "此操作需要登录网易云账号");
return;
}
let like = true;
if (state.liked.songs.includes(id)) like = false;
likeATrack({ id, like }).then(() => {
if (like === false) {
commit(
"updateLikedSongs",
state.liked.songs.filter((d) => d !== id)
);
} else {
let newLikeSongs = state.liked.songs;
newLikeSongs.push(id);
commit("updateLikedSongs", newLikeSongs);
}
});
},
}; };

@ -17,35 +17,47 @@ const ipcRenderer =
export default class { export default class {
constructor() { constructor() {
this._enabled = false; // 播放器状态
this._playing = false; // 是否正在播放中
this._progress = 0; // 当前播放歌曲的进度
this._enabled = false; // 是否启用Player
this._repeatMode = "off"; // off | on | one this._repeatMode = "off"; // off | on | one
this._shuffle = false; // true | false this._shuffle = false; // true | false
this._volume = 1; // 0 to 1 this._volume = 1; // 0 to 1
this._volumeBeforeMuted = 1; // 用于保存静音前的音量 this._volumeBeforeMuted = 1; // 用于保存静音前的音量
this._list = [];
this._current = 0; // current track index // 播放信息
this._shuffledList = []; this._list = []; // 播放列表
this._shuffledCurrent = 0; this._current = 0; // 当前播放歌曲在播放列表里的index
this._playlistSource = { type: "album", id: 123 }; this._shuffledList = []; // 被随机打乱的播放列表,随机播放模式下会使用此播放列表
this._currentTrack = { id: 86827685 }; this._shuffledCurrent = 0; // 当前播放歌曲在随机列表里面的index
this._playlistSource = { type: "album", id: 123 }; // 当前播放列表的信息
this._currentTrack = { id: 86827685 }; // 当前播放歌曲的详细信息
this._playNextList = []; // 当这个list不为空时会优先播放这个list的歌 this._playNextList = []; // 当这个list不为空时会优先播放这个list的歌
this._playing = false; this._isPersonalFM = false; // 是否是私人FM模式
this._isPersonalFM = false; this._personalFMTrack = { id: 0 }; // 私人FM当前歌曲
this._personalFMTrack = { id: 0 }; this._personalFMNextTrack = { id: 0 }; // 私人FM下一首歌曲信息为了快速加载下一首
this._personalFMNextTrack = { id: 0 };
// howler (https://github.com/goldfire/howler.js)
this._howler = null; this._howler = null;
Object.defineProperty(this, "_howler", { Object.defineProperty(this, "_howler", {
enumerable: false, enumerable: false,
}); });
// init
this._init(); this._init();
// for debug
if (process.env.NODE_ENV === "development") {
window.player = this;
}
} }
get repeatMode() { get repeatMode() {
return this._repeatMode; return this._repeatMode;
} }
set repeatMode(mode) { set repeatMode(mode) {
if (this._isPersonalFM) return;
if (!["off", "on", "one"].includes(mode)) { if (!["off", "on", "one"].includes(mode)) {
console.warn("repeatMode: invalid args, must be 'on' | 'off' | 'one'"); console.warn("repeatMode: invalid args, must be 'on' | 'off' | 'one'");
return; return;
@ -56,6 +68,7 @@ export default class {
return this._shuffle; return this._shuffle;
} }
set shuffle(shuffle) { set shuffle(shuffle) {
if (this._isPersonalFM) return;
if (shuffle !== true && shuffle !== false) { if (shuffle !== true && shuffle !== false) {
console.warn("shuffle: invalid args, must be Boolean"); console.warn("shuffle: invalid args, must be Boolean");
return; return;
@ -109,12 +122,31 @@ export default class {
get personalFMTrack() { get personalFMTrack() {
return this._personalFMTrack; return this._personalFMTrack;
} }
get currentTrackDuration() {
const trackDuration = this._currentTrack.dt || 1000;
let duration = ~~(trackDuration / 1000);
return duration > 1 ? duration - 1 : duration;
}
get progress() {
return this._progress;
}
set progress(value) {
if (this._howler) {
this._howler.seek(value);
}
}
get isCurrentTrackLiked() {
return store.state.liked.songs.includes(this.currentTrack.id);
}
_init() { _init() {
this._loadSelfFromLocalStorage();
Howler.autoUnlock = false; Howler.autoUnlock = false;
Howler.usingWebAudio = true; Howler.usingWebAudio = true;
this._loadSelfFromLocalStorage(); Howler.volume(this.volume);
if (this._enabled) { if (this._enabled) {
// 恢复当前播放歌曲
this._replaceCurrentTrack(this._currentTrack.id, false).then(() => { this._replaceCurrentTrack(this._currentTrack.id, false).then(() => {
this._howler?.seek(localStorage.getItem("playerCurrentTrackTime") ?? 0); this._howler?.seek(localStorage.getItem("playerCurrentTrackTime") ?? 0);
setInterval( setInterval(
@ -127,10 +159,11 @@ export default class {
); );
}); // update audio source and init howler }); // update audio source and init howler
this._initMediaSession(); this._initMediaSession();
this._setIntervals();
} }
Howler.volume(this.volume);
// 初始化私人FM
if (this._personalFMTrack.id === 0 || this._personalFMNextTrack.id === 0) { if (this._personalFMTrack.id === 0 || this._personalFMNextTrack.id === 0) {
// init fm
personalFM().then((result) => { personalFM().then((result) => {
this._personalFMTrack = result.data[0]; this._personalFMTrack = result.data[0];
this._personalFMNextTrack = result.data[1]; this._personalFMNextTrack = result.data[1];
@ -138,23 +171,34 @@ export default class {
}); });
} }
} }
_setIntervals() {
// 同步播放进度
// TODO: 如果 _progress 在别的地方被改变了这个定时器会覆盖之前改变的值是bug
setInterval(() => {
this._progress = this._howler === null ? 0 : this._howler.seek();
}, 1000);
}
_getNextTrack() { _getNextTrack() {
// 返回 [trackID, index]
if (this._playNextList.length > 0) { if (this._playNextList.length > 0) {
let trackID = this._playNextList.shift(); let trackID = this._playNextList.shift();
return [trackID, this.current]; return [trackID, this.current];
} }
// 当歌曲是列表最后一首 && 循环模式开启
if (this.list.length === this.current + 1 && this.repeatMode === "on") { if (this.list.length === this.current + 1 && this.repeatMode === "on") {
// 当歌曲是列表最后一首 && 循环模式开启
return [this.list[0], 0]; return [this.list[0], 0];
} }
// 返回 [trackID, index]
return [this.list[this.current + 1], this.current + 1]; return [this.list[this.current + 1], this.current + 1];
} }
_getPrevTrack() { _getPrevTrack() {
// 当歌曲是列表第一首 && 循环模式开启
if (this.current === 0 && this.repeatMode === "on") { if (this.current === 0 && this.repeatMode === "on") {
// 当歌曲是列表第一首 && 循环模式开启
return [this.list[this.list.length - 1], this.list.length - 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[this.current - 1], this.current - 1];
} }
async _shuffleTheList(firstTrackID = this._currentTrack.id) { async _shuffleTheList(firstTrackID = this._currentTrack.id) {
@ -401,7 +445,7 @@ export default class {
this.list.append(trackID); this.list.append(trackID);
} }
playNextTrack(isFM = false) { playNextTrack(isFM = false) {
if (this._isPersonalFM || isFM) { if (this._isPersonalFM || isFM === true) {
this._isPersonalFM = true; this._isPersonalFM = true;
this._personalFMTrack = this._personalFMNextTrack; this._personalFMTrack = this._personalFMNextTrack;
this._replaceCurrentTrack(this._personalFMTrack.id); this._replaceCurrentTrack(this._personalFMTrack.id);
@ -565,4 +609,17 @@ export default class {
likedCurrentTrack: store.state.liked.songs.includes(this.currentTrack.id), likedCurrentTrack: store.state.liked.songs.includes(this.currentTrack.id),
}); });
} }
switchRepeatMode() {
if (this._repeatMode === "on") {
this.repeatMode = "one";
} else if (this._repeatMode === "one") {
this.repeatMode = "off";
} else {
this.repeatMode = "on";
}
}
switchShuffle() {
this.shuffle = !this.shuffle;
}
} }

@ -2,10 +2,10 @@
<transition name="slide-up"> <transition name="slide-up">
<div class="lyrics-page" :class="{ 'no-lyric': noLyric }"> <div class="lyrics-page" :class="{ 'no-lyric': noLyric }">
<div <div
v-if="this.$store.state.settings.showLyricsDynamicBackground" v-if="settings.showLyricsDynamicBackground"
class="dynamic-background" class="dynamic-background"
> >
<div v-show="this.$store.state.showLyrics"> <div v-show="showLyrics">
<div <div
class="top-right" class="top-right"
:style="{ backgroundImage: `url(${imageUrl})` }" :style="{ backgroundImage: `url(${imageUrl})` }"
@ -55,11 +55,11 @@
<div class="buttons"> <div class="buttons">
<button-icon <button-icon
:title="$t('player.like')" :title="$t('player.like')"
@click.native="playerRef.likeCurrentSong" @click.native="likeASong(player.currentTrack.id)"
> >
<svg-icon <svg-icon
:icon-class=" :icon-class="
playerRef.isCurrentTrackLiked ? 'heart-solid' : 'heart' player.isCurrentTrackLiked ? 'heart-solid' : 'heart'
" "
/> />
</button-icon> </button-icon>
@ -69,80 +69,77 @@
</div> </div>
</div> </div>
<div class="progress-bar"> <div class="progress-bar">
<span>{{ formatTrackTime(progress) || "0:00" }}</span> <span>{{ formatTrackTime(player.progress) || "0:00" }}</span>
<div class="slider"> <div class="slider">
<vue-slider <vue-slider
v-model="progress" v-model="player.progress"
:min="0" :min="0"
:max="progressMax" :max="player.currentTrackDuration"
:interval="1" :interval="1"
:drag-on-click="true" :drag-on-click="true"
:duration="0" :duration="0"
:dotSize="12" :dot-size="12"
:height="2" :height="2"
:tooltipFormatter="formatTrackTime" :tooltip-formatter="formatTrackTime"
@drag-end="setSeek" @drag-end="player.seek"
ref="progress"
></vue-slider> ></vue-slider>
</div> </div>
<span>{{ formatTrackTime(progressMax) }}</span> <span>{{ formatTrackTime(player.currentTrackDuration) }}</span>
</div> </div>
<div class="media-controls"> <div class="media-controls">
<button-icon <button-icon
v-show="!player.isPersonalFM" v-show="!player.isPersonalFM"
@click.native="playerRef.repeat"
:title=" :title="
player.repeatMode === 'one' player.repeatMode === 'one'
? $t('player.repeatTrack') ? $t('player.repeatTrack')
: $t('player.repeat') : $t('player.repeat')
" "
:class="{ active: player.repeatMode !== 'off' }" :class="{ active: player.repeatMode !== 'off' }"
@click.native="player.switchRepeatMode"
> >
<svg-icon <svg-icon
icon-class="repeat"
v-show="player.repeatMode !== 'one'" v-show="player.repeatMode !== 'one'"
icon-class="repeat"
/> />
<svg-icon <svg-icon
icon-class="repeat-1"
v-show="player.repeatMode === 'one'" v-show="player.repeatMode === 'one'"
icon-class="repeat-1"
/> />
</button-icon> </button-icon>
<div class="middle"> <div class="middle">
<button-icon <button-icon
v-show="!player.isPersonalFM" v-show="!player.isPersonalFM"
@click.native="playerRef.previous"
:title="$t('player.previous')" :title="$t('player.previous')"
@click.native="player.playPrevTrack"
> >
<svg-icon icon-class="previous" /> <svg-icon icon-class="previous" />
</button-icon> </button-icon>
<button-icon <button-icon
v-show="player.isPersonalFM" v-show="player.isPersonalFM"
@click.native="moveToFMTrash"
title="不喜欢" title="不喜欢"
@click.native="player.moveToFMTrash"
> >
<svg-icon icon-class="thumbs-down" /> <svg-icon icon-class="thumbs-down" />
</button-icon> </button-icon>
<button-icon <button-icon
id="play" id="play"
@click.native="playerRef.play"
:title="$t(player.playing ? 'player.pause' : 'player.play')" :title="$t(player.playing ? 'player.pause' : 'player.play')"
@click.native="player.playOrPause"
> >
<svg-icon <svg-icon :icon-class="player.playing ? 'pause' : 'play'" />
:icon-class="playerRef.playing ? 'pause' : 'play'"
/>
</button-icon> </button-icon>
<button-icon <button-icon
@click.native="playerRef.next"
:title="$t('player.next')" :title="$t('player.next')"
@click.native="player.playNextTrack"
> >
<svg-icon icon-class="next" /> <svg-icon icon-class="next" />
</button-icon> </button-icon>
</div> </div>
<button-icon <button-icon
v-show="!player.isPersonalFM" v-show="!player.isPersonalFM"
@click.native="playerRef.shuffle"
:title="$t('player.shuffle')" :title="$t('player.shuffle')"
:class="{ active: player.shuffle }" :class="{ active: player.shuffle }"
@click.native="player.switchShuffle"
> >
<svg-icon icon-class="shuffle" /> <svg-icon icon-class="shuffle" />
</button-icon> </button-icon>
@ -153,21 +150,22 @@
<div class="right-side"> <div class="right-side">
<transition name="slide-fade"> <transition name="slide-fade">
<div <div
v-show="!noLyric"
ref="lyricsContainer"
class="lyrics-container" class="lyrics-container"
:style="lyricFontSize" :style="lyricFontSize"
ref="lyricsContainer"
v-show="!noLyric"
> >
<div class="line" id="line-1"></div> <div id="line-1" class="line"></div>
<div <div
v-for="(line, index) in lyricWithTranslation"
:id="`line${index}`"
:key="index"
class="line" class="line"
:class="{ :class="{
highlight: highlightLyricIndex === index, highlight: highlightLyricIndex === index,
}" }"
v-for="(line, index) in lyricWithTranslation"
:key="index"
:id="`line${index}`"
@click="clickLyricLine(line.time)" @click="clickLyricLine(line.time)"
@dblclick="clickLyricLine(line.time, true)"
><span v-html="formatLine(line)"></span ><span v-html="formatLine(line)"></span
></div> ></div>
</div> </div>
@ -186,7 +184,7 @@
// The lyrics page of Apple Music is so gorgeous, so I copy the design. // The lyrics page of Apple Music is so gorgeous, so I copy the design.
// Some of the codes are from https://github.com/sl1673495/vue-netease-music // Some of the codes are from https://github.com/sl1673495/vue-netease-music
import { mapState, mapMutations } from "vuex"; import { mapState, mapMutations, mapActions } from "vuex";
import VueSlider from "vue-slider-component"; import VueSlider from "vue-slider-component";
import { formatTrackTime } from "@/utils/common"; import { formatTrackTime } from "@/utils/common";
import { getLyric } from "@/api/track"; import { getLyric } from "@/api/track";
@ -209,24 +207,13 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["player"]), ...mapState(["player", "settings", "showLyrics"]),
currentTrack() { currentTrack() {
return this.player.currentTrack; return this.player.currentTrack;
}, },
imageUrl() { imageUrl() {
return this.player.currentTrack?.al?.picUrl + "?param=1024y1024"; return this.player.currentTrack?.al?.picUrl + "?param=1024y1024";
}, },
progress: {
get() {
return this.playerRef.progress;
},
set(value) {
this.playerRef.setProgress(value);
},
},
progressMax() {
return this.playerRef.progressMax;
},
lyricWithTranslation() { lyricWithTranslation() {
let ret = []; let ret = [];
// //
@ -266,9 +253,6 @@ export default {
playerRef() { playerRef() {
return this.$parent.$refs.player ? this.$parent.$refs.player : {}; return this.$parent.$refs.player ? this.$parent.$refs.player : {};
}, },
showLyrics() {
return this.$store.state.showLyrics;
},
noLyric() { noLyric() {
return this.lyric.length == 0; return this.lyric.length == 0;
}, },
@ -301,6 +285,7 @@ export default {
}, },
methods: { methods: {
...mapMutations(["toggleLyrics"]), ...mapMutations(["toggleLyrics"]),
...mapActions(["likeASong"]),
getLyric() { getLyric() {
if (!this.currentTrack.id) return; if (!this.currentTrack.id) return;
return getLyric(this.currentTrack.id).then((data) => { return getLyric(this.currentTrack.id).then((data) => {
@ -319,18 +304,13 @@ export default {
formatTrackTime(value) { formatTrackTime(value) {
return formatTrackTime(value); return formatTrackTime(value);
}, },
setSeek() { clickLyricLine(value, startPlay = false) {
let value = this.$refs.progress.getValue(); // TODO:
this.playerRef.setProgress(value);
this.playerRef.player.seek(value);
},
seek(value) {
this.playerRef.setProgress(value);
this.playerRef.player.seek(value);
},
clickLyricLine(value) {
if (window.getSelection().toString().length === 0) { if (window.getSelection().toString().length === 0) {
this.seek(value); this.player.seek(value);
}
if (startPlay === true) {
this.player.play();
} }
}, },
setLyricsInterval() { setLyricsInterval() {
@ -476,7 +456,6 @@ export default {
} }
.svg-icon { .svg-icon {
opacity: 0.58;
height: 18px; height: 18px;
width: 18px; width: 18px;
} }

Loading…
Cancel
Save