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

This commit is contained in:
qier222 2021-04-25 16:53:53 +08:00
parent 5355caa4e4
commit fab0227ed3
No known key found for this signature in database
GPG Key ID: 9C85007ED905F14D
6 changed files with 187 additions and 205 deletions

View File

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

View File

@ -9,17 +9,16 @@
@click.stop
>
<vue-slider
v-model="progress"
v-model="player.progress"
:min="0"
:max="progressMax"
:max="player.currentTrackDuration"
:interval="1"
:drag-on-click="true"
:duration="0"
:dotSize="12"
:dot-size="12"
:height="2"
:tooltipFormatter="formatTrackTime"
@drag-end="setSeek"
ref="progress"
:tooltip-formatter="formatTrackTime"
@drag-end="player.seek"
></vue-slider>
</div>
<div class="controls">
@ -46,16 +45,16 @@
</div>
<div class="like-button">
<button-icon
@click.native="likeCurrentSong"
:title="$t('player.like')"
@click.native="likeASong(player.currentTrack.id)"
>
<svg-icon
v-show="!player.isCurrentTrackLiked"
icon-class="heart"
v-show="!liked.songs.includes(currentTrack.id)"
></svg-icon>
<svg-icon
v-show="player.isCurrentTrackLiked"
icon-class="heart-solid"
v-show="liked.songs.includes(currentTrack.id)"
></svg-icon>
</button-icon>
</div>
@ -67,24 +66,26 @@
<div class="container" @click.stop>
<button-icon
v-show="!player.isPersonalFM"
@click.native="previous"
:title="$t('player.previous')"
@click.native="player.playPrevTrack"
><svg-icon icon-class="previous"
/></button-icon>
<button-icon
v-show="player.isPersonalFM"
@click.native="moveToFMTrash"
title="不喜欢"
@click.native="player.moveToFMTrash"
><svg-icon icon-class="thumbs-down"
/></button-icon>
<button-icon
class="play"
@click.native="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 @click.native="next" :title="$t('player.next')"
<button-icon
:title="$t('player.next')"
@click.native="player.playNextTrack"
><svg-icon icon-class="next"
/></button-icon>
</div>
@ -94,48 +95,48 @@
<div class="blank"></div>
<div class="container" @click.stop>
<button-icon
@click.native="goToNextTracksPage"
:title="$t('player.nextUp')"
:class="{
active: this.$route.name === 'next',
disabled: player.isPersonalFM,
}"
@click.native="goToNextTracksPage"
><svg-icon icon-class="list"
/></button-icon>
<button-icon
:class="{
active: player.repeatMode !== 'off',
disabled: player.isPersonalFM,
}"
:title="
player.repeatMode === 'one'
? $t('player.repeatTrack')
: $t('player.repeat')
"
@click.native="repeat"
:class="{
active: player.repeatMode !== 'off',
disabled: player.isPersonalFM,
}"
@click.native="player.switchRepeatMode"
>
<svg-icon
icon-class="repeat"
v-show="player.repeatMode !== 'one'"
icon-class="repeat"
/>
<svg-icon
icon-class="repeat-1"
v-show="player.repeatMode === 'one'"
icon-class="repeat-1"
/>
</button-icon>
<button-icon
@click.native="shuffle"
:class="{ active: player.shuffle, disabled: player.isPersonalFM }"
:title="$t('player.shuffle')"
@click.native="player.switchShuffle"
><svg-icon icon-class="shuffle"
/></button-icon>
<div class="volume-control">
<button-icon :title="$t('player.mute')" @click.native="player.mute">
<svg-icon icon-class="volume" v-show="volume > 0.5" />
<svg-icon icon-class="volume-mute" v-show="volume === 0" />
<svg-icon v-show="volume > 0.5" icon-class="volume" />
<svg-icon v-show="volume === 0" icon-class="volume-mute" />
<svg-icon
icon-class="volume-half"
v-show="volume <= 0.5 && volume !== 0"
icon-class="volume-half"
/>
</button-icon>
<div class="volume-bar">
@ -147,7 +148,7 @@
:drag-on-click="true"
:duration="0"
:tooltip="`none`"
:dotSize="12"
:dot-size="12"
></vue-slider>
</div>
</div>
@ -167,9 +168,6 @@
<script>
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 ButtonIcon from "@/components/ButtonIcon.vue";
@ -181,24 +179,8 @@ export default {
ButtonIcon,
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: {
...mapState(["player", "settings", "liked", "data"]),
...mapState(["player", "settings", "data"]),
currentTrack() {
return this.player.currentTrack;
},
@ -213,13 +195,6 @@ export default {
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() {
return this.player._howler?._src.includes("kuwo.cn")
? "音源来自酷我音乐"
@ -227,38 +202,8 @@ export default {
},
},
methods: {
...mapMutations(["updateLikedSongs", "toggleLyrics"]),
...mapActions(["showToast"]),
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;
},
...mapMutations(["toggleLyrics"]),
...mapActions(["showToast", "likeASong"]),
goToNextTracksPage() {
if (this.player.isPersonalFM) return;
this.$route.name === "next"
@ -271,27 +216,6 @@ export default {
let sec = (~~(value % 60)).toString().padStart(2, "0");
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() {
if (this.player.playlistSource.id === this.data.likedSongPlaylistID) {
this.$router.push({ path: "/library/liked-songs" });

View File

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

View File

@ -1,3 +1,7 @@
// import store, { state, dispatch, commit } from "@/store";
import { isAccountLoggedIn } from "@/utils/auth";
import { likeATrack } from "@/api/track";
export default {
showToast({ state, commit }, text) {
if (state.toast.timer !== null) {
@ -16,4 +20,24 @@ export default {
}, 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);
}
});
},
};

View File

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

View File

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