YesPlayMusic/src/utils/Player.js
2021-01-05 22:17:47 +08:00

377 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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),
});
}
}