refactor: player

This commit is contained in:
qier222 2021-01-05 22:17:47 +08:00
parent 0482e6a1ed
commit f6c36fbcac
22 changed files with 659 additions and 500 deletions

View File

@ -47,6 +47,7 @@
"howler": "^2.2.0",
"js-cookie": "^2.2.1",
"localforage": "^1.9.0",
"lodash": "^4.17.20",
"nprogress": "^0.2.0",
"pac-proxy-agent": "^4.1.0",
"plyr": "^3.6.2",

View File

@ -9,7 +9,7 @@
</main>
<transition name="slide-up">
<Player
v-if="this.$store.state.player.enable"
v-if="this.$store.state.player.enabled"
ref="player"
v-show="
['mv', 'loginUsername', 'login', 'loginAccount'].includes(

View File

@ -29,8 +29,6 @@
</template>
<script>
import { playAlbumByID, playPlaylistByID, playArtistByID } from "@/utils/play";
export default {
props: {
id: { type: Number, required: true },
@ -74,12 +72,13 @@ export default {
},
methods: {
play() {
const player = this.$store.state.player;
const playActions = {
album: playAlbumByID,
playlist: playPlaylistByID,
artist: playArtistByID,
album: player.playAlbumByID,
playlist: player.playPlaylistByID,
artist: player.playArtistByID,
};
playActions[this.type](this.id);
playActions[this.type].bind(player)(this.id);
},
goTo() {
this.$router.push({ name: this.type, params: { id: this.id } });

View File

@ -36,7 +36,6 @@
</span>
</div>
</div>
<!-- 账号登录才会显示 like 图标 -->
<div class="like-button">
<button-icon
@click.native="likeCurrentSong"
@ -60,9 +59,9 @@
<button-icon
class="play"
@click.native="play"
:title="$t(playing ? 'player.pause' : 'player.play')"
:title="$t(player.playing ? 'player.pause' : 'player.play')"
>
<svg-icon :iconClass="playing ? 'pause' : 'play'"
<svg-icon :iconClass="player.playing ? 'pause' : 'play'"
/></button-icon>
<button-icon @click.native="next" :title="$t('player.next')"
><svg-icon icon-class="next"
@ -77,15 +76,18 @@
/></button-icon>
<button-icon
:title="
player.repeat === 'one'
player.repeatMode === 'one'
? $t('player.repeatTrack')
: $t('player.repeat')
"
@click.native="repeat"
:class="{ active: player.repeat !== 'off' }"
:class="{ active: player.repeatMode !== 'off' }"
>
<svg-icon icon-class="repeat" v-show="player.repeat !== 'one'" />
<svg-icon icon-class="repeat-1" v-show="player.repeat === 'one'" />
<svg-icon icon-class="repeat" v-show="player.repeatMode !== 'one'" />
<svg-icon
icon-class="repeat-1"
v-show="player.repeatMode === 'one'"
/>
</button-icon>
<button-icon
@click.native="shuffle"
@ -94,7 +96,7 @@
><svg-icon icon-class="shuffle"
/></button-icon>
<div class="volume-control">
<button-icon :title="$t('player.mute')" @click.native="mute">
<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
@ -121,13 +123,11 @@
</template>
<script>
import { updateMediaSessionMetaData } from "@/utils/mediaSession";
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 { Howler } from "howler";
import ButtonIcon from "@/components/ButtonIcon.vue";
import VueSlider from "vue-slider-component";
@ -147,10 +147,7 @@ export default {
},
mounted() {
setInterval(() => {
// fix _id
if (this.howler && this.howler._sounds?.[0]?._id) {
this.progress = ~~this.howler.seek();
}
this.progress = ~~this.player.seek();
}, 1000);
if (isAccountLoggedIn()) {
userLikedSongsIDs(this.data.user.userId).then((data) => {
@ -159,7 +156,7 @@ export default {
}
},
computed: {
...mapState(["player", "howler", "settings", "liked", "data"]),
...mapState(["player", "settings", "liked", "data"]),
currentTrack() {
return this.player.currentTrack;
},
@ -168,92 +165,46 @@ export default {
return this.player.volume;
},
set(value) {
this.updatePlayerState({ key: "volume", value });
Howler.volume(value);
this.player.volume = value;
},
},
playing() {
if (this.howler) {
if (this.howler.state() === "loading") {
this.updatePlayerState({ key: "playing", value: true });
return true;
}
const status = this.howler.playing();
this.updatePlayerState({ key: "playing", value: status });
return status;
} else {
return false;
}
return this.player.playing;
},
progressMax() {
let max = ~~(this.currentTrack.dt / 1000);
let max = ~~(this.player.currentTrack.dt / 1000);
return max > 1 ? max - 1 : max;
},
},
methods: {
...mapMutations([
"turnOnShuffleMode",
"turnOffShuffleMode",
"updatePlayerState",
"updateRepeatStatus",
"updateLikedSongs",
]),
...mapActions([
"nextTrack",
"previousTrack",
"playTrackOnListByID",
"addNextTrackEvent",
"showToast",
]),
...mapMutations(["updateLikedSongs"]),
...mapActions(["showToast"]),
play() {
if (this.playing) {
this.howler.pause();
} else {
if (this.howler.state() === "unloaded") {
this.playTrackOnListByID(this.currentTrack.id);
}
this.howler.play();
if (this.howler._onend.length === 0) {
this.addNextTrackEvent();
updateMediaSessionMetaData(this.currentTrack);
}
}
this.player.playing ? this.player.pause() : this.player.play();
},
next() {
this.progress = 0;
this.nextTrack(true);
if (this.player.playNextTrack()) this.progress = 0;
},
previous() {
this.progress = 0;
this.previousTrack();
if (this.player.playPrevTrack()) this.progress = 0;
},
shuffle() {
if (this.player.shuffle === true) {
this.turnOffShuffleMode();
} else {
this.turnOnShuffleMode();
}
this.player.shuffle = !this.player.shuffle;
console.log(this.player);
},
repeat() {
if (this.player.repeat === "on") {
this.updateRepeatStatus("one");
} else if (this.player.repeat === "one") {
this.updateRepeatStatus("off");
console.log(this.player.repeatMode);
if (this.player.repeatMode === "on") {
this.player.repeatMode = "one";
} else if (this.player.repeatMode === "one") {
this.player.repeatMode = "off";
} else {
this.updateRepeatStatus("on");
}
},
mute() {
if (this.volume === 0) {
this.volume = this.oldVolume;
} else {
this.oldVolume = this.volume;
this.volume = 0;
this.player.repeatMode = "on";
}
},
setSeek() {
this.progress = this.$refs.progress.getValue();
this.howler.seek(this.$refs.progress.getValue());
this.player.seek(this.$refs.progress.getValue());
},
goToNextTracksPage() {
this.$route.name === "next"
@ -285,15 +236,20 @@ export default {
});
},
goToList() {
if (this.player.listInfo.id === this.data.likedSongPlaylistID)
if (this.player.playlistSource.id === this.data.likedSongPlaylistID)
this.$router.push({ path: "/library/liked-songs" });
else
this.$router.push({
path: "/" + this.player.listInfo.type + "/" + this.player.listInfo.id,
path:
"/" +
this.player.playlistSource.type +
"/" +
this.player.playlistSource.id,
});
},
goToAlbum() {
this.$router.push({ path: "/album/" + this.currentTrack.al.id });
if (this.player.currentTrack.al.id === 0) return;
this.$router.push({ path: "/album/" + this.player.currentTrack.al.id });
},
goToArtist(id) {
this.$router.push({ path: "/artist/" + id });

View File

@ -41,12 +41,6 @@
<script>
import { mapActions, mapMutations, mapState } from "vuex";
import { likeATrack } from "@/api/track";
import {
playPlaylistByID,
playAlbumByID,
playAList,
appendTrackToPlayerList,
} from "@/utils/play";
import { addOrRemoveTrackFromPlaylist } from "@/api/playlist";
import { isAccountLoggedIn } from "@/utils/auth";
@ -140,24 +134,32 @@ export default {
} else if (this.dbclickTrackFunc === "playTrackOnListByID") {
this.playTrackOnListByID(trackID);
} else if (this.dbclickTrackFunc === "playPlaylistByID") {
playPlaylistByID(this.id, trackID);
this.$store.state.player.playPlaylistByID(this.id, trackID);
}
},
playThisListDefault(trackID) {
if (this.type === "playlist") {
playPlaylistByID(this.id, trackID);
this.$store.state.player.playPlaylistByID(this.id, trackID);
} else if (this.type === "album") {
playAlbumByID(this.id, trackID);
this.$store.state.player.playAlbumByID(this.id, trackID);
} else if (this.type === "tracklist") {
let trackIDs = this.tracks.map((t) => t.id);
playAList(trackIDs, this.tracks[0].ar[0].id, "artist", trackID);
this.$store.state.player.replacePlaylist(
trackIDs,
this.id,
"artist",
trackID
);
}
},
play() {
appendTrackToPlayerList(this.clickTrack.id, true);
this.$store.state.player.addTrackToPlayNext(
this.rightClickedTrack.id,
true
);
},
playNext() {
appendTrackToPlayerList(this.clickTrack.id);
this.$store.state.player.addTrackToPlayNext(this.rightClickedTrack.id);
},
like() {
this.likeASong(this.rightClickedTrack.id);

View File

@ -62,9 +62,9 @@ export function createTouchBar(window) {
},
});
ipcMain.on("vuex-state", (e, { player, liked }) => {
playButton.label = player.playing === true ? "􀊆" : "􀊄";
likeButton.label = liked.songs.includes(player.currentTrack.id) ? "􀊵" : "􀊴";
ipcMain.on("player", (e, { playing, likedCurrentTrack }) => {
playButton.label = playing === true ? "􀊆" : "􀊄";
likeButton.label = likedCurrentTrack ? "􀊵" : "􀊴";
});
const touchBar = new TouchBar({

View File

@ -6,7 +6,6 @@ import store from "./store";
import i18n from "@/locale";
import "@/assets/icons";
import "@/utils/filters";
import { initMediaSession } from "@/utils/mediaSession";
import "./registerServiceWorker";
import { dailyTask } from "@/utils/common";
@ -21,8 +20,6 @@ Vue.use(VueAnalytics, {
Vue.config.productionTip = false;
initMediaSession();
if (process.env.VUE_APP_ENABLE_SENTRY === "true") {
Sentry.init({
dsn:

View File

@ -1,185 +1,4 @@
import { updateMediaSessionMetaData } from "@/utils/mediaSession";
import { getTrackDetail, scrobble, getMP3 as getMP3Api } from "@/api/track";
import { isAccountLoggedIn } from "@/utils/auth";
import { updateHttps } from "@/utils/common";
import localforage from "localforage";
import store from "@/store";
import { cacheTrack } from "@/utils/db";
const electron =
process.env.IS_ELECTRON === true ? window.require("electron") : null;
const ipcRenderer =
process.env.IS_ELECTRON === true ? electron.ipcRenderer : null;
export default {
switchTrack(
{ state, dispatch, commit },
{ id, sort = 0, autoplay = true, ifUnplayableThen = "nextTrack" }
) {
getTrackDetail(id).then((data) => {
let track = data.songs[0];
track.sort = sort;
// 获取当前的播放时间。初始化为 loading 状态时返回 howler 的实例而不是浮点数时间,比如 1.332
let time = state.howler ? state.howler.seek() : 0;
let currentTime = 0;
if (time === 0) {
// state.howler._duration 可以获得当前实例的播放时长
currentTime = 180;
}
if (time.toString() === "[object Object]") {
currentTime = 0;
}
if (time > 0) {
currentTime = time;
}
scrobble({
id: state.player.currentTrack.id,
sourceid: state.player.listInfo.id,
time: currentTime,
});
commit("updateCurrentTrack", track);
updateMediaSessionMetaData(track);
document.title = `${track.name} · ${track.ar[0].name} - YesPlayMusic`;
let unblockSongUrl = null;
if (track.playable === false) {
let res = undefined;
if (process.env.IS_ELECTRON === true) {
res = ipcRenderer.sendSync("unblock-music", track);
}
if (res?.url) {
unblockSongUrl = res.url;
} else {
dispatch(ifUnplayableThen);
return;
}
}
function commitMP3(mp3) {
commit("replaceMP3", { mp3, autoplay });
state.howler.once("end", () => {
dispatch("nextTrack");
});
}
function getMP3(id) {
return getMP3Api(id).then((data) => {
// 未知情况下会没有返回数据导致报错,增加防范逻辑
if (data.data[0]) {
const url = updateHttps(data.data[0].url);
commitMP3(url);
return url;
}
});
}
if (isAccountLoggedIn()) {
if (store.state.settings.automaticallyCacheSongs === true) {
let tracks = localforage.createInstance({
name: "tracks",
});
tracks
.getItem(`${track.id}`)
.then((t) => {
if (t !== null) {
const blob = new Blob([t.mp3]);
commitMP3(URL.createObjectURL(blob));
} else {
if (unblockSongUrl) {
commitMP3(unblockSongUrl);
cacheTrack(`${track.id}`, unblockSongUrl);
} else {
getMP3(track.id).then((url) => {
cacheTrack(`${track.id}`, url);
});
}
}
})
.catch((err) => {
console.log(err.messaeg);
if (unblockSongUrl) {
commitMP3(unblockSongUrl);
} else {
getMP3(track.id);
}
});
} else {
if (unblockSongUrl) {
commitMP3(unblockSongUrl);
} else {
getMP3(track.id);
}
}
} else {
commitMP3(
unblockSongUrl ||
`https://music.163.com/song/media/outer/url?id=${track.id}`
);
}
});
},
playFirstTrackOnList({ state, dispatch }) {
dispatch(
"switchTrack",
state.player.list.find((t) => t.sort === 0)
);
},
playTrackOnListByID({ state, commit, dispatch }, trackID) {
let track = state.player.list.find((t) => t.id === trackID);
dispatch("switchTrack", track);
if (state.player.shuffle) {
// 当随机模式开启时双击列表的一首歌进行播放此时要把这首歌的sort调到第一(0),这样用户就能随机播放完整的歌单
let otherTrack = state.player.list.find((t) => t.sort === 0);
commit("switchSortBetweenTwoTracks", {
trackID1: track.id,
trackID2: otherTrack.id,
});
}
},
nextTrack({ state, dispatch }, realNext = false) {
let nextTrack = state.player.list.find(
(track) => track.sort === state.player.currentTrack.sort + 1
);
if (state.player.repeat === "one" && realNext === false) {
nextTrack = state.player.currentTrack;
}
if (nextTrack === undefined) {
if (state.player.repeat !== "off") {
nextTrack = state.player.list.find((t) => t.sort === 0);
} else {
document.title = "YesPlayMusic";
return;
}
}
dispatch("switchTrack", nextTrack);
},
previousTrack({ state, dispatch }) {
let previousTrack = state.player.list.find(
(track) => track.sort === state.player.currentTrack.sort - 1
);
if (previousTrack == undefined) {
if (state.player.repeat !== "off") {
previousTrack = state.player.list.reduce((x, y) => (x > y ? x : y));
} else {
previousTrack = state.player.list.find((t) => t.sort === 0);
}
}
dispatch("switchTrack", {
id: previousTrack.id,
sort: previousTrack.sort,
ifUnplayableThen: "previousTrack",
});
},
addNextTrackEvent({ state, dispatch }) {
state.howler.once("end", () => {
dispatch("nextTrack");
});
},
showToast({ state, commit }, text) {
if (state.toast.timer !== null) {
clearTimeout(state.toast.timer);

View File

@ -3,23 +3,13 @@ import Vuex from "vuex";
import state from "./state";
import mutations from "./mutations";
import actions from "./actions";
import initLocalStorage from "./initLocalStorage";
import { Howler } from "howler";
import { changeAppearance } from "@/utils/common";
import updateApp from "@/utils/updateApp";
import pkg from "../../package.json";
import Player from "@/utils/Player";
// vuex 自定义插件
import { getBroadcastPlugin } from "./plugins/broadcast";
import saveToLocalStorage from "./plugins/localStorage";
if (localStorage.getItem("appVersion") === null) {
localStorage.setItem("player", JSON.stringify(initLocalStorage.player));
localStorage.setItem("settings", JSON.stringify(initLocalStorage.settings));
localStorage.setItem("data", JSON.stringify(initLocalStorage.data));
localStorage.setItem("appVersion", pkg.version);
window.location.reload();
}
updateApp();
Vue.use(Vuex);
@ -39,19 +29,6 @@ const options = {
const store = new Vuex.Store(options);
Howler.volume(store.state.player.volume);
// 防止软件第一次打开资源加载2次
Howler.autoUnlock = false;
const currentTrack = store.state?.player?.currentTrack;
if (currentTrack?.id) {
store.dispatch("switchTrack", {
id: currentTrack.id,
sort: currentTrack.sort,
autoplay: false,
});
}
if ([undefined, null].includes(store.state.settings.lang)) {
let lang = "en";
if (navigator.language.slice(0, 2) === "zh") lang = "zh-CN";
@ -69,4 +46,17 @@ window
}
});
let player = new Player();
player = new Proxy(player, {
set(target, prop, val) {
// console.log({ prop, val });
target[prop] = val;
if (prop === "_howler") return true;
target.saveSelfToLocalStorage();
target.sendSelfToIpcMain();
return true;
},
});
store.state.player = player;
export default store;

View File

@ -1,21 +1,7 @@
import { playlistCategories } from "@/utils/staticData";
let localStorage = {
player: {
enable: false,
show: true,
playing: false,
shuffle: false,
volume: 1,
repeat: "off", // on | off | one
currentTrack: {},
notShuffledList: [],
list: [],
listInfo: {
type: "",
id: "",
},
},
player: {},
settings: {
playlistCategories,
lang: null,

View File

@ -1,100 +1,7 @@
import { Howl, Howler } from "howler";
import { shuffleAList } from "@/utils/common";
export default {
updatePlayerState(state, { key, value }) {
state.player[key] = value;
},
updateCurrentTrack(state, track) {
state.player.currentTrack = track;
},
replaceMP3(state, { mp3, autoplay }) {
if (state.howler) {
Howler.unload();
}
state.howler = new Howl({
src: [mp3],
autoplay,
html5: true,
format: ["mp3", "flac"],
});
},
updatePlayerList(state, list) {
state.player.list = list;
if (state.player.enable !== true) state.player.enable = true;
},
updateListInfo(state, info) {
state.player.listInfo = info;
},
updateRepeatStatus(state, status) {
state.player.repeat = status;
},
appendTrackToPlayerList(state, { track, playNext = false }) {
let existTrack = state.player.list.find((t) => t.id === track.id);
if (
(existTrack === null || existTrack === undefined) &&
playNext === false
) {
state.player.list.push(track);
return;
}
// 把track加入到正在播放歌曲的下一首位置
state.player.list = state.player.list.map((t) => {
if (t.sort > state.player.currentTrack.sort) {
t.sort = t.sort + 1;
}
return t;
});
track.sort = state.player.currentTrack.sort + 1;
state.player.list.push(track);
},
turnOnShuffleMode(state) {
state.player.notShuffledList = JSON.parse(
JSON.stringify(state.player.list)
);
state.player.shuffle = true;
let newSorts = shuffleAList(
state.player.list.filter((t) => t.sort > state.player.currentTrack.sort)
);
state.player.list = state.player.list.map((track) => {
if (newSorts[track.id] !== undefined) track.sort = newSorts[track.id];
return track;
});
},
turnOffShuffleMode(state) {
state.player.shuffle = false;
state.player.list = JSON.parse(
JSON.stringify(state.player.notShuffledList)
);
state.player.currentTrack.sort = state.player.list.find(
(t) => t.id === state.player.currentTrack.id
).sort;
},
shuffleTheListBeforePlay(state) {
state.player.notShuffledList = JSON.parse(
JSON.stringify(state.player.list)
);
let newSorts = shuffleAList(state.player.list);
state.player.list = state.player.list.map((track) => {
track.sort = newSorts[track.id];
return track;
});
},
updateLikedSongs(state, trackIDs) {
state.liked.songs = trackIDs;
},
switchSortBetweenTwoTracks(state, { trackID1, trackID2 }) {
let t1 = state.player.list.find((t) => t.id === trackID1);
let t2 = state.player.list.find((t) => t.id === trackID2);
let sorts = [t1.sort, t2.sort];
state.player.list = state.player.list.map((t) => {
if (t.id === t1.id) t.sort = sorts[1];
if (t.id === t2.id) t.sort = sorts[0];
return t;
});
state.player.sendSelfToIpcMain();
},
changeLang(state, lang) {
state.settings.lang = lang;

View File

@ -1,7 +1,6 @@
export default (store) => {
store.subscribe((mutation, state) => {
// console.log(mutation);
localStorage.setItem("player", JSON.stringify(state.player));
localStorage.setItem("settings", JSON.stringify(state.settings));
localStorage.setItem("data", JSON.stringify(state.data));
});

View File

@ -1,3 +1,12 @@
import initLocalStorage from "./initLocalStorage";
import pkg from "../../package.json";
if (localStorage.getItem("appVersion") === null) {
localStorage.setItem("settings", JSON.stringify(initLocalStorage.settings));
localStorage.setItem("data", JSON.stringify(initLocalStorage.data));
localStorage.setItem("appVersion", pkg.version);
}
export default {
howler: null,
liked: {

377
src/utils/Player.js Normal file
View File

@ -0,0 +1,377 @@
import { getTrackDetail, scrobble, getMP3 } from "@/api/track";
import { shuffle } from "lodash";
import { Howler, Howl } from "howler";
import localforage from "localforage";
import { cacheTrack } from "@/utils/db";
import { getAlbum } from "@/api/album";
import { getPlaylistDetail } from "@/api/playlist";
import { getArtist } from "@/api/artist";
import store from "@/store";
const electron =
process.env.IS_ELECTRON === true ? window.require("electron") : null;
const ipcRenderer =
process.env.IS_ELECTRON === true ? electron.ipcRenderer : null;
export default class {
constructor() {
this._enabled = false;
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._howler = null;
Object.defineProperty(this, "_howler", {
enumerable: false,
});
this._init();
}
get repeatMode() {
return this._repeatMode;
}
set repeatMode(mode) {
if (!["off", "on", "one"].includes(mode)) {
console.warn("repeatMode: invalid args, must be 'on' | 'off' | 'one'");
return;
}
this._repeatMode = mode;
}
get shuffle() {
return this._shuffle;
}
set shuffle(shuffle) {
if (shuffle !== true && shuffle !== false) {
console.warn("shuffle: invalid args, must be Boolean");
return;
}
this._shuffle = shuffle;
if (shuffle) {
this._shuffleTheList();
}
}
get volume() {
return this._volume;
}
set volume(volume) {
this._volume = volume;
Howler.volume(volume);
}
get list() {
return this.shuffle ? this._shuffledList : this._list;
}
set list(list) {
this._list = list;
}
get current() {
return this.shuffle ? this._shuffledCurrent : this._current;
}
set current(current) {
if (this.shuffle) {
this._shuffledCurrent = current;
} else {
this._current = current;
}
}
get enabled() {
return this._enabled;
}
get playing() {
return this._playing;
}
get currentTrack() {
return this._currentTrack;
}
get playlistSource() {
return this._playlistSource;
}
get playNextList() {
return this._playNextList;
}
_init() {
Howler.autoUnlock = false;
this._loadSelfFromLocalStorage();
this._replaceCurrentTrack(this._currentTrack.id, false); // update audio source and init howler
this._initMediaSession();
}
_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];
}
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];
}
return [this.list[this.current - 1], this.current - 1];
}
async _shuffleTheList(firstTrackID = this._currentTrack.id) {
let list = this._list.filter((tid) => tid !== firstTrackID);
if (firstTrackID === "first") list = this._list;
this._shuffledList = shuffle(list);
if (firstTrackID !== "first") this._shuffledList.unshift(firstTrackID);
}
async _scrobble(complete = false) {
let time = this._howler.seek();
if (complete) {
time = ~~(this._currentTrack.dt / 100);
}
scrobble({
id: this._currentTrack.id,
sourceid: this.playlistSource.id,
time,
});
}
_playAudioSource(source, autoplay = true) {
Howler.unload();
this._howler = new Howl({
src: [source],
html5: true,
format: ["mp3", "flac"],
});
if (autoplay) this.play();
this._howler.once("end", () => {
this._nextTrackCallback();
});
}
_getAudioSourceFromCache(id) {
let tracks = localforage.createInstance({ name: "tracks" });
return tracks.getItem(id).then((t) => {
if (t === null) return null;
const source = URL.createObjectURL(new Blob([t.mp3]));
return source;
});
}
_getAudioSourceFromNetease(track) {
return getMP3(track.id).then((data) => {
if (!data.data[0]) return null;
if (data.data[0].freeTrialInfo !== null) return null; // 跳过只能试听的歌曲
const source = data.data[0].url.replace(/^http:/, "https:");
if (store.state.settings.automaticallyCacheSongs) {
cacheTrack(track.id, source);
}
return source;
});
}
_getAudioSourceFromUnblockMusic(track) {
if (process.env.IS_ELECTRON !== true) return null;
const source = ipcRenderer.sendSync("unblock-music", track);
return source?.url;
}
_getAudioSource(track) {
return this._getAudioSourceFromCache(String(track.id))
.then((source) => {
if (!source) return null;
return source;
})
.then((source) => {
if (source) return source;
return this._getAudioSourceFromNetease(track);
})
.then((source) => {
if (source) return source;
return this._getAudioSourceFromUnblockMusic(track);
});
}
_replaceCurrentTrack(
id,
autoplay = true,
ifUnplayableThen = "playNextTrack"
) {
return getTrackDetail(id).then((data) => {
let track = data.songs[0];
this._currentTrack = track;
this._updateMediaSessionMetaData(track);
document.title = `${track.name} · ${track.ar[0].name} - YesPlayMusic`;
this._getAudioSource(track).then((source) => {
if (source) {
this._playAudioSource(source, autoplay);
return source;
} else {
ifUnplayableThen === "playNextTrack"
? this.playNextTrack()
: this.playPrevTrack();
}
});
});
}
_loadSelfFromLocalStorage() {
const player = JSON.parse(localStorage.getItem("player"));
if (!player) return;
for (const [key, value] of Object.entries(player)) {
this[key] = value;
}
}
_initMediaSession() {
if ("mediaSession" in navigator) {
navigator.mediaSession.setActionHandler("play", () => {
this.play();
});
navigator.mediaSession.setActionHandler("pause", () => {
this.pause();
});
navigator.mediaSession.setActionHandler("previoustrack", () => {
this.playPrevTrack();
});
navigator.mediaSession.setActionHandler("nexttrack", () => {
this.playNextTrack();
});
navigator.mediaSession.setActionHandler("stop", () => {
this.pause();
});
}
}
_updateMediaSessionMetaData(track) {
if ("mediaSession" in navigator === false) {
return;
}
let artists = track.ar.map((a) => a.name);
navigator.mediaSession.metadata = new window.MediaMetadata({
title: track.name,
artist: artists.join(","),
album: track.al.name,
artwork: [
{
src: track.al.picUrl + "?param=512y512",
type: "image/jpg",
sizes: "512x512",
},
],
});
}
_nextTrackCallback() {
this._scrobble(true);
if (this.repeatMode === "one") {
this._howler.play();
} else {
this.playNextTrack();
}
}
currentTrackID() {
const { list, current } = this._getListAndCurrent();
return list[current];
}
appendTrack(trackID) {
this.list.append(trackID);
}
playNextTrack() {
// TODO: 切换歌曲时增加加载中的状态
const [trackID, index] = this._getNextTrack();
if (trackID === undefined) {
this._howler.stop();
return false;
}
this.current = index;
this._replaceCurrentTrack(trackID);
return true;
}
playPrevTrack() {
const [trackID, index] = this._getPrevTrack();
if (trackID === undefined) return false;
this.current = index;
this._replaceCurrentTrack(trackID, true, "playPrevTrack");
return true;
}
saveSelfToLocalStorage() {
let player = {};
for (let [key, value] of Object.entries(this)) {
if (key === "_playing") continue;
player[key] = value;
}
localStorage.setItem("player", JSON.stringify(player));
}
pause() {
this._howler.pause();
this._playing = false;
}
play() {
this._howler.play();
this._playing = true;
}
seek(time = null) {
if (time !== null) this._howler.seek(time);
return this._howler.seek();
}
mute() {
if (this.volume === 0) {
this.volume = this._volumeBeforeMuted;
} else {
this._volumeBeforeMuted = this.volume;
this.volume = 0;
}
}
replacePlaylist(
trackIDs,
playlistSourceID,
playlistSourceType,
autoPlayTrackID = "first"
) {
if (!this._enabled) this._enabled = true;
this.list = trackIDs;
this.current = 0;
this._playlistSource = {
type: playlistSourceType,
id: playlistSourceID,
};
if (this.shuffle) this._shuffleTheList(autoPlayTrackID);
if (autoPlayTrackID === "first") {
this._replaceCurrentTrack(this.list[0]);
} else {
this.current = trackIDs.indexOf(autoPlayTrackID);
this._replaceCurrentTrack(autoPlayTrackID);
}
}
playAlbumByID(id, trackID = "first") {
getAlbum(id).then((data) => {
let trackIDs = data.songs.map((t) => t.id);
this.replacePlaylist(trackIDs, id, "album", trackID);
});
}
playPlaylistByID(id, trackID = "first", noCache = false) {
getPlaylistDetail(id, noCache).then((data) => {
let trackIDs = data.playlist.trackIds.map((t) => t.id);
this.replacePlaylist(trackIDs, id, "playlist", trackID);
});
}
playArtistByID(id, trackID = "first") {
getArtist(id).then((data) => {
let trackIDs = data.hotSongs.map((t) => t.id);
this.replacePlaylist(trackIDs, id, "artist", trackID);
});
}
addTrackToPlayNext(trackID, playNow = false) {
this._playNextList.push(trackID);
if (playNow) this.playNextTrack();
}
sendSelfToIpcMain() {
if (process.env.IS_ELECTRON !== true) return false;
ipcRenderer.send("player", {
playing: this.playing,
likedCurrentTrack: store.state.liked.songs.includes(this.currentTrack.id),
});
}
}

View File

@ -1,53 +0,0 @@
import store from "@/store";
import { getAlbum } from "@/api/album";
import { getPlaylistDetail } from "@/api/playlist";
import { getArtist } from "@/api/artist";
export function playAList(list, id, type, trackID = "first") {
let filteredList = list.map((id, index) => {
return { sort: index, id };
});
store.commit("updatePlayerList", filteredList);
if (store.state.player.shuffle) store.commit("shuffleTheListBeforePlay");
if (trackID === "first") store.dispatch("playFirstTrackOnList");
else store.dispatch("playTrackOnListByID", trackID);
store.commit("updateListInfo", { type, id });
}
export function playAlbumByID(id, trackID = "first") {
getAlbum(id).then((data) => {
let trackIDs = data.songs.map((t) => t.id);
playAList(trackIDs, id, "album", trackID);
});
}
export function playPlaylistByID(id, trackID = "first", noCache = false) {
getPlaylistDetail(id, noCache).then((data) => {
let trackIDs = data.playlist.trackIds.map((t) => t.id);
playAList(trackIDs, id, "playlist", trackID);
});
}
export function playArtistByID(id, trackID = "first") {
getArtist(id).then((data) => {
let trackIDs = data.hotSongs.map((t) => t.id);
playAList(trackIDs, id, "artist", trackID);
});
}
export function appendTrackToPlayerList(trackID, playNext = false) {
let filteredTrack = {
sort: 0,
id: trackID,
};
store.commit("appendTrackToPlayerList", {
track: filteredTrack,
playNext,
});
if (playNext) {
store.dispatch("nextTrack", true);
}
}

View File

@ -24,8 +24,31 @@ const updateData = () => {
localStorage.setItem("data", JSON.stringify(data));
};
const updatePlayer = () => {
let parsedData = JSON.parse(localStorage.getItem("player"));
let appVersion = localStorage.getItem("appVersion");
if (appVersion === `"0.2.5"`) parsedData = {}; // 0.2.6版本重构了player
const data = {
_repeatMode: "off",
_shuffle: false,
_list: [],
_current: 0,
_playlistSource: {},
_volume: 1,
_volumeBeforeMuted: 1,
_currentTrack: {},
_playNextList: [],
_enabled: false,
_shuffledList: [],
_shuffledCurrent: 0,
...parsedData,
};
localStorage.setItem("player", JSON.stringify(data));
};
export default function () {
updateSetting();
updateData();
updatePlayer();
localStorage.setItem("appVersion", JSON.stringify(pkg.version));
}

View File

@ -124,7 +124,6 @@
import { mapMutations, mapActions, mapState } from "vuex";
import { getArtistAlbum } from "@/api/artist";
import { getTrackDetail } from "@/api/track";
import { playAlbumByID } from "@/utils/play";
import { getAlbum, albumDynamicDetail, likeAAlbum } from "@/api/album";
import { splitSoundtrackAlbumTitle, splitAlbumTitle } from "@/utils/common";
import NProgress from "nprogress";
@ -202,7 +201,7 @@ export default {
if (this.tracks.find((t) => t.playable !== false) === undefined) {
return;
}
playAlbumByID(id, trackID);
this.$store.state.player.playAlbumByID(id, trackID);
},
likeAlbum(toast = false) {
if (!isAccountLoggedIn()) {

View File

@ -112,7 +112,6 @@ import {
artistMv,
followAArtist,
} from "@/api/artist";
import { playAList } from "@/utils/play";
import { isAccountLoggedIn } from "@/utils/auth";
import NProgress from "nprogress";
@ -186,7 +185,12 @@ export default {
},
playPopularSongs(trackID = "first") {
let trackIDs = this.popularTracks.map((t) => t.id);
playAList(trackIDs, this.artist.id, "artist", trackID);
this.$store.state.player.replacePlaylist(
trackIDs,
this.artist.id,
"artist",
trackID
);
},
followArtist() {
if (!isAccountLoggedIn()) {

View File

@ -125,7 +125,6 @@ import {
import { randomNum, dailyTask } from "@/utils/common";
import { getPlaylistDetail } from "@/api/playlist";
import { isAccountLoggedIn } from "@/utils/auth";
import { playPlaylistByID } from "@/utils/play";
import NProgress from "nprogress";
import TrackList from "@/components/TrackList.vue";
@ -193,7 +192,11 @@ export default {
...mapActions(["showToast"]),
...mapMutations(["updateModal"]),
playLikedSongs() {
playPlaylistByID(this.playlists[0].id, "first", true);
this.$store.state.player.playPlaylistByID(
this.playlists[0].id,
"first",
true
);
},
updateCurrentTab(tab) {
if (!isAccountLoggedIn() && tab !== "playlists") {

View File

@ -182,7 +182,6 @@ import {
subscribePlaylist,
deletePlaylist,
} from "@/api/playlist";
import { playAList } from "@/utils/play";
import { getTrackDetail } from "@/api/track";
import { isAccountLoggedIn } from "@/utils/auth";
@ -335,7 +334,12 @@ export default {
...mapActions(["playFirstTrackOnList", "playTrackOnListByID", "showToast"]),
playPlaylistByID(trackID = "first") {
let trackIDs = this.playlist.trackIds.map((t) => t.id);
playAList(trackIDs, this.playlist.id, "playlist", trackID);
this.$store.state.player.replacePlaylist(
trackIDs,
this.playlist.id,
"playlist",
trackID
);
},
likePlaylist(toast = false) {
if (!isAccountLoggedIn()) {

View File

@ -105,7 +105,6 @@
<script>
import { mapState } from "vuex";
import NProgress from "nprogress";
import { appendTrackToPlayerList } from "@/utils/play";
import { search } from "@/api/others";
import Cover from "@/components/Cover.vue";
@ -148,7 +147,7 @@ export default {
},
playTrackInSearchResult(id) {
let track = this.tracks.find((t) => t.id === id);
appendTrackToPlayerList(track, true);
this.$store.state.player.appendTrackToPlayerList(track, true);
},
getData(keywords) {
search({ keywords: keywords, type: 1018 }).then((data) => {

170
yarn.lock
View File

@ -1381,6 +1381,11 @@
dependencies:
defer-to-connect "^1.0.1"
"@tokenizer/token@^0.1.0", "@tokenizer/token@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.1.1.tgz#f0d92c12f87079ddfd1b29f614758b9696bc29e3"
integrity sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.npm.taobao.org/@tootallnate/once/download/@tootallnate/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@ -1536,6 +1541,14 @@
resolved "https://registry.npm.taobao.org/@types/range-parser/download/@types/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha1-fuMwunyq+5gJC+zoal7kQRWQTCw=
"@types/readable-stream@^2.3.9":
version "2.3.9"
resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.9.tgz#40a8349e6ace3afd2dd1b6d8e9b02945de4566a9"
integrity sha512-sqsgQqFT7HmQz/V5jH1O0fvQQnXAJO46Gg9LRO/JPfjmVmGUlcx831TZZO3Y3HtWhIkzf3kTsNT0Z0kzIhIvZw==
dependencies:
"@types/node" "*"
safe-buffer "*"
"@types/semver@^7.3.1":
version "7.3.4"
resolved "https://registry.npm.taobao.org/@types/semver/download/@types/semver-7.3.4.tgz?cache=0&sync_timestamp=1600115372478&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fsemver%2Fdownload%2F%40types%2Fsemver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
@ -2765,6 +2778,19 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5:
escalade "^3.1.0"
node-releases "^1.1.61"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
@ -2775,7 +2801,12 @@ buffer-equal@0.0.1:
resolved "https://registry.npm.taobao.org/buffer-equal/download/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b"
integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=
buffer-from@^1.0.0:
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
buffer-from@^1.0.0, buffer-from@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
@ -2812,6 +2843,14 @@ buffer@^5.2.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buffer@^5.4.3:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
builder-util-runtime@8.7.2:
version "8.7.2"
resolved "https://registry.npm.taobao.org/builder-util-runtime/download/builder-util-runtime-8.7.2.tgz#d93afc71428a12789b437e13850e1fa7da956d72"
@ -3093,6 +3132,11 @@ chardet@^0.7.0:
resolved "https://registry.npm.taobao.org/chardet/download/chardet-0.7.0.tgz?cache=0&sync_timestamp=1594010664806&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchardet%2Fdownload%2Fchardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=
charenc@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
check-types@^8.0.3:
version "8.0.3"
resolved "https://registry.npm.taobao.org/check-types/download/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
@ -3529,7 +3573,7 @@ content-disposition@0.5.3:
dependencies:
safe-buffer "5.1.2"
content-type@~1.0.4:
content-type@^1.0.4, content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha1-4TjMdeBAxyexlm/l5fjJruJW/js=
@ -3690,6 +3734,11 @@ cross-spawn@^7.0.0:
shebang-command "^2.0.0"
which "^2.0.1"
crypt@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.npm.taobao.org/crypto-browserify/download/crypto-browserify-3.12.0.tgz?cache=0&sync_timestamp=1589682788096&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcrypto-browserify%2Fdownload%2Fcrypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@ -3947,7 +3996,7 @@ debug@^3.1.1, debug@^3.2.5:
dependencies:
ms "^2.1.1"
debug@^4.1.0, debug@^4.1.1:
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
@ -4144,6 +4193,11 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dijkstrajs@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b"
integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs=
dir-glob@^2.0.0, dir-glob@^2.2.2:
version "2.2.2"
resolved "https://registry.npm.taobao.org/dir-glob/download/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
@ -5114,6 +5168,16 @@ file-loader@^4.2.0:
loader-utils "^1.2.3"
schema-utils "^2.5.0"
file-type@^16.1.0:
version "16.1.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.1.0.tgz#1c8a4458b2103e07d2b49ae7f76384abafe86529"
integrity sha512-G4Klqf6tuprtG0pC4r9kni4Wv8XhAAsfHphVqsQGA+YiOlPAO40BZduDqKfv0RFsu9q9ZbFObWfwszY/NqhEZw==
dependencies:
readable-web-to-node-stream "^3.0.0"
strtok3 "^6.0.3"
token-types "^2.0.0"
typedarray-to-buffer "^3.1.5"
file-type@^9.0.0:
version "9.0.0"
resolved "https://registry.npm.taobao.org/file-type/download/file-type-9.0.0.tgz?cache=0&sync_timestamp=1603046153408&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffile-type%2Fdownload%2Ffile-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18"
@ -6261,10 +6325,10 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-buffer@^1.1.5:
is-buffer@^1.1.5, is-buffer@~1.1.6:
version "1.1.6"
resolved "https://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha1-76ouqdqg16suoTqXsritUf776L4=
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-callable@^1.1.4, is-callable@^1.2.0:
version "1.2.2"
@ -6575,6 +6639,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isarray@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@^4.0.6:
version "4.0.6"
resolved "https://registry.npm.taobao.org/isbinaryfile/download/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
@ -7121,7 +7190,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.npm.taobao.org/lodash.uniq/download/lodash.uniq-4.5.0.tgz?cache=0&sync_timestamp=1589682817275&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash.uniq%2Fdownload%2Flodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.3:
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3:
version "4.17.20"
resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.20.tgz?cache=0&sync_timestamp=1597335994883&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI=
@ -7225,6 +7294,15 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
md5@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
dependencies:
charenc "0.0.2"
crypt "0.0.2"
is-buffer "~1.1.6"
mdn-data@2.0.4:
version "2.0.4"
resolved "https://registry.npm.taobao.org/mdn-data/download/mdn-data-2.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmdn-data%2Fdownload%2Fmdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
@ -7240,6 +7318,11 @@ media-typer@0.3.0:
resolved "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
media-typer@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561"
integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==
memory-fs@^0.4.1:
version "0.4.1"
resolved "https://registry.npm.taobao.org/memory-fs/download/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@ -7549,6 +7632,18 @@ multicast-dns@^6.0.1:
dns-packet "^1.3.1"
thunky "^1.0.2"
music-metadata@^7.6.2:
version "7.6.2"
resolved "https://registry.yarnpkg.com/music-metadata/-/music-metadata-7.6.2.tgz#7c46aaa32f37d3f747d653006e24c3f0efd83dc6"
integrity sha512-yICd18bxEhphpUPwwzDe8wP3LSsbnKzPjuIDKAYpXgYVkMl2aZxvWx6qA59ccsVC0J5FeEhbRlWiAZ6IFZZYZg==
dependencies:
content-type "^1.0.4"
debug "^4.3.1"
file-type "^16.1.0"
media-typer "^1.1.0"
strtok3 "^6.0.6"
token-types "^2.0.0"
mute-stream@0.0.8:
version "0.0.8"
resolved "https://registry.npm.taobao.org/mute-stream/download/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
@ -8271,6 +8366,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
peek-readable@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-3.1.1.tgz#795c1429374f256e4b5ab6c584cecfd3a110024f"
integrity sha512-QHJag0oYYPVkx6rVPEgCLEUMo6VRYbV3GUrqy00lxXJBEIw9LhPCP5MQI6mEfahJO9KYUP8W8qD8kC0V9RyZFQ==
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
@ -8396,7 +8496,7 @@ pngjs-nozlib@^1.0.0:
resolved "https://registry.npm.taobao.org/pngjs-nozlib/download/pngjs-nozlib-1.0.0.tgz#9e64d602cfe9cce4d9d5997d0687429a73f0b7d7"
integrity sha1-nmTWAs/pzOTZ1Zl9BodCmnPwt9c=
pngjs@^3.0.0, pngjs@^3.3.3:
pngjs@^3.0.0, pngjs@^3.3.0, pngjs@^3.3.3:
version "3.4.0"
resolved "https://registry.npm.taobao.org/pngjs/download/pngjs-3.4.0.tgz?cache=0&sync_timestamp=1603551107900&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpngjs%2Fdownload%2Fpngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
integrity sha1-mcp9clll+2VYFOr2XzjxK72/VV8=
@ -8982,6 +9082,19 @@ q@^1.1.2:
resolved "https://registry.npm.taobao.org/q/download/q-1.5.1.tgz?cache=0&sync_timestamp=1589682817412&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fq%2Fdownload%2Fq-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
qrcode@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==
dependencies:
buffer "^5.4.3"
buffer-alloc "^1.2.0"
buffer-from "^1.1.1"
dijkstrajs "^1.0.1"
isarray "^2.0.1"
pngjs "^3.3.0"
yargs "^13.2.4"
qs@6.7.0:
version "6.7.0"
resolved "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@ -9140,6 +9253,14 @@ readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.1.1, readable
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-web-to-node-stream@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.1.tgz#3f619b1bc5dd73a4cfe5c5f9b4f6faba55dff845"
integrity sha512-4zDC6CvjUyusN7V0QLsXVB7pJCD9+vtrM9bYDRv6uBQ+SKfx36rp5AFNPRgh9auKRul/a1iFZJYXcCbwRL+SaA==
dependencies:
"@types/readable-stream" "^2.3.9"
readable-stream "^3.6.0"
readdirp@^2.2.1:
version "2.2.1"
resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@ -9472,16 +9593,16 @@ rxjs@^6.6.0:
dependencies:
tslib "^1.9.0"
safe-buffer@*, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz?cache=0&sync_timestamp=1589682795646&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz?cache=0&sync_timestamp=1589682795646&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.npm.taobao.org/safe-regex/download/safe-regex-1.1.0.tgz?cache=0&sync_timestamp=1589682757445&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-regex%2Fdownload%2Fsafe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@ -10227,6 +10348,15 @@ strip-json-comments@~2.0.1:
resolved "https://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz?cache=0&sync_timestamp=1594567532500&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-json-comments%2Fdownload%2Fstrip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
strtok3@^6.0.3, strtok3@^6.0.6:
version "6.0.6"
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.0.6.tgz#65ad16df313c8dfbf075ab0bcb1edd070002fcb3"
integrity sha512-fVxvAEKDwHFfbQO1yKxKBPfkWZyBr0Zf20UQ/mblbkAQe5h0Xdd2jDb3Mh7yRZd7LSItJ9JWgQWelpEmVoBe2g==
dependencies:
"@tokenizer/token" "^0.1.1"
"@types/debug" "^4.1.5"
peek-readable "^3.1.1"
stylehacks@^4.0.0:
version "4.0.3"
resolved "https://registry.npm.taobao.org/stylehacks/download/stylehacks-4.0.3.tgz?cache=0&sync_timestamp=1599672034713&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstylehacks%2Fdownload%2Fstylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
@ -10595,6 +10725,14 @@ toidentifier@1.0.0:
resolved "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=
token-types@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/token-types/-/token-types-2.0.0.tgz#b23618af744818299c6fbf125e0fdad98bab7e85"
integrity sha512-WWvu8sGK8/ZmGusekZJJ5NM6rRVTTDO7/bahz4NGiSDb/XsmdYBn6a1N/bymUHuWYTWeuLUg98wUzvE4jPdCZw==
dependencies:
"@tokenizer/token" "^0.1.0"
ieee754 "^1.1.13"
toposort@^1.0.0:
version "1.0.7"
resolved "https://registry.npm.taobao.org/toposort/download/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
@ -11718,10 +11856,10 @@ yargs-parser@^4.2.0:
dependencies:
camelcase "^3.0.0"
yargs@^13.3.2:
yargs@^13.2.4, yargs@^13.3.2:
version "13.3.2"
resolved "https://registry.npm.taobao.org/yargs/download/yargs-13.3.2.tgz?cache=0&sync_timestamp=1600660037884&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha1-rX/+/sGqWVZayRX4Lcyzipwxot0=
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
dependencies:
cliui "^5.0.0"
find-up "^3.0.0"