YesPlayMusic/src/utils/Player.js
2023-01-28 12:06:57 +08:00

959 lines
28 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 { getAlbum } from '@/api/album';
import { getArtist } from '@/api/artist';
import { trackScrobble, trackUpdateNowPlaying } from '@/api/lastfm';
import { fmTrash, personalFM } from '@/api/others';
import { getPlaylistDetail, intelligencePlaylist } from '@/api/playlist';
import { getMP3, getTrackDetail, scrobble } from '@/api/track';
import store from '@/store';
import { isAccountLoggedIn } from '@/utils/auth';
import { cacheTrackSource, getTrackSource } from '@/utils/db';
import { isCreateMpris, isCreateTray } from '@/utils/platform';
import { Howl, Howler } from 'howler';
import shuffle from 'lodash/shuffle';
import { decode as base642Buffer } from '@/utils/base64';
const PLAY_PAUSE_FADE_DURATION = 200;
/**
* @readonly
* @enum {string}
*/
const UNPLAYABLE_CONDITION = {
PLAY_NEXT_TRACK: 'playNextTrack',
PLAY_PREV_TRACK: 'playPrevTrack',
};
const electron =
process.env.IS_ELECTRON === true ? window.require('electron') : null;
const ipcRenderer =
process.env.IS_ELECTRON === true ? electron.ipcRenderer : null;
const delay = ms =>
new Promise(resolve => {
setTimeout(() => {
resolve('');
}, ms);
});
const excludeSaveKeys = [
'_playing',
'_personalFMLoading',
'_personalFMNextLoading',
];
function setTitle(track) {
document.title = track
? `${track.name} · ${track.ar[0].name} - YesPlayMusic`
: 'YesPlayMusic';
if (isCreateTray) {
ipcRenderer?.send('updateTrayTooltip', document.title);
}
store.commit('updateTitle', document.title);
}
function setTrayLikeState(isLiked) {
if (isCreateTray) {
ipcRenderer?.send('updateTrayLikeState', isLiked);
}
}
export default class {
constructor() {
// 播放器状态
this._playing = false; // 是否正在播放中
this._progress = 0; // 当前播放歌曲的进度
this._enabled = false; // 是否启用Player
this._repeatMode = 'off'; // off | on | one
this._shuffle = false; // true | false
this._reversed = false;
this._volume = 1; // 0 to 1
this._volumeBeforeMuted = 1; // 用于保存静音前的音量
this._personalFMLoading = false; // 是否正在私人FM中加载新的track
this._personalFMNextLoading = false; // 是否正在缓存私人FM的下一首歌曲
// 播放信息
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下一首歌曲信息为了快速加载下一首
/**
* The blob records for cleanup.
*
* @private
* @type {string[]}
*/
this.createdBlobRecords = [];
// howler (https://github.com/goldfire/howler.js)
this._howler = null;
Object.defineProperty(this, '_howler', {
enumerable: false,
});
// init
this._init();
window.yesplaymusic = {};
window.yesplaymusic.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;
}
this._repeatMode = mode;
}
get shuffle() {
return this._shuffle;
}
set shuffle(shuffle) {
if (this._isPersonalFM) return;
if (shuffle !== true && shuffle !== false) {
console.warn('shuffle: invalid args, must be Boolean');
return;
}
this._shuffle = shuffle;
if (shuffle) {
this._shuffleTheList();
}
}
get reversed() {
return this._reversed;
}
set reversed(reversed) {
if (this._isPersonalFM) return;
if (reversed !== true && reversed !== false) {
console.warn('reversed: invalid args, must be Boolean');
return;
}
console.log('changing reversed to:', reversed);
this._reversed = reversed;
}
get volume() {
return this._volume;
}
set volume(volume) {
this._volume = volume;
this._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 currentTrackID() {
return this._currentTrack?.id ?? 0;
}
get playlistSource() {
return this._playlistSource;
}
get playNextList() {
return this._playNextList;
}
get isPersonalFM() {
return this._isPersonalFM;
}
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();
this._howler?.volume(this.volume);
if (this._enabled) {
// 恢复当前播放歌曲
this._replaceCurrentTrack(this.currentTrackID, false).then(() => {
this._howler?.seek(localStorage.getItem('playerCurrentTrackTime') ?? 0);
}); // update audio source and init howler
this._initMediaSession();
}
this._setIntervals();
// 初始化私人FM
if (
this._personalFMTrack.id === 0 ||
this._personalFMNextTrack.id === 0 ||
this._personalFMTrack.id === this._personalFMNextTrack.id
) {
personalFM().then(result => {
this._personalFMTrack = result.data[0];
this._personalFMNextTrack = result.data[1];
return this._personalFMTrack;
});
}
}
_setPlaying(isPlaying) {
this._playing = isPlaying;
if (isCreateTray) {
ipcRenderer?.send('updateTrayPlayState', this._playing);
}
}
_setIntervals() {
// 同步播放进度
// TODO: 如果 _progress 在别的地方被改变了,
// 这个定时器会覆盖之前改变的值是bug
setInterval(() => {
if (this._howler === null) return;
this._progress = this._howler.seek();
localStorage.setItem('playerCurrentTrackTime', this._progress);
if (isCreateMpris) {
ipcRenderer?.send('playerCurrentTrackTime', this._progress);
}
}, 1000);
}
_getNextTrack() {
const next = this._reversed ? this.current - 1 : this.current + 1;
if (this._playNextList.length > 0) {
let trackID = this._playNextList.shift();
return [trackID, this.current];
}
// 循环模式开启,则重新播放当前模式下的相对的下一首
if (this.repeatMode === 'on') {
if (this._reversed && this.current === 0) {
// 倒序模式,当前歌曲是第一首,则重新播放列表最后一首
return [this.list[this.list.length - 1], this.list.length - 1];
} else if (this.list.length === this.current + 1) {
// 正序模式,当前歌曲是最后一首,则重新播放第一首
return [this.list[0], 0];
}
}
// 返回 [trackID, index]
return [this.list[next], next];
}
_getPrevTrack() {
const next = this._reversed ? this.current + 1 : this.current - 1;
// 循环模式开启,则重新播放当前模式下的相对的下一首
if (this.repeatMode === 'on') {
if (this._reversed && this.current === 0) {
// 倒序模式,当前歌曲是最后一首,则重新播放列表第一首
return [this.list[0], 0];
} else if (this.list.length === this.current + 1) {
// 正序模式,当前歌曲是第一首,则重新播放列表最后一首
return [this.list[this.list.length - 1], this.list.length - 1];
}
}
// 返回 [trackID, index]
return [this.list[next], next];
}
async _shuffleTheList(firstTrackID = this.currentTrackID) {
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(track, time, completed = false) {
console.debug(
`[debug][Player.js] scrobble track 👉 ${track.name} by ${track.ar[0].name} 👉 time:${time} completed: ${completed}`
);
const trackDuration = ~~(track.dt / 1000);
time = completed ? trackDuration : ~~time;
scrobble({
id: track.id,
sourceid: this.playlistSource.id,
time,
});
if (
store.state.lastfm.key !== undefined &&
(time >= trackDuration / 2 || time >= 240)
) {
const timestamp = ~~(new Date().getTime() / 1000) - time;
trackScrobble({
artist: track.ar[0].name,
track: track.name,
timestamp,
album: track.al.name,
trackNumber: track.no,
duration: trackDuration,
});
}
}
_playAudioSource(source, autoplay = true) {
Howler.unload();
this._howler = new Howl({
src: [source],
html5: true,
preload: true,
format: ['mp3', 'flac'],
onend: () => {
this._nextTrackCallback();
},
});
this._howler.on('loaderror', (_, errCode) => {
// https://developer.mozilla.org/en-US/docs/Web/API/MediaError/code
// code 3: MEDIA_ERR_DECODE
if (errCode === 3) {
this._playNextTrack(this._isPersonalFM);
} else {
const t = this.progress;
this._replaceCurrentTrackAudio(this.currentTrack, false, false).then(
replaced => {
// 如果 replaced 为 false代表当前的 track 已经不是这里想要替换的track
// 此时则不修改当前的歌曲进度
if (replaced) {
this._howler?.seek(t);
this.play();
}
}
);
}
});
if (autoplay) {
this.play();
if (this._currentTrack.name) {
setTitle(this._currentTrack);
}
setTrayLikeState(store.state.liked.songs.includes(this.currentTrack.id));
}
this.setOutputDevice();
}
_getAudioSourceBlobURL(data) {
// Create a new object URL.
const source = URL.createObjectURL(new Blob([data]));
// Clean up the previous object URLs since we've created a new one.
// Revoke object URLs can release the memory taken by a Blob,
// which occupied a large proportion of memory.
for (const url in this.createdBlobRecords) {
URL.revokeObjectURL(url);
}
// Then, we replace the createBlobRecords with new one with
// our newly created object URL.
this.createdBlobRecords = [source];
return source;
}
_getAudioSourceFromCache(id) {
return getTrackSource(id).then(t => {
if (!t) return null;
return this._getAudioSourceBlobURL(t.source);
});
}
_getAudioSourceFromNetease(track) {
if (isAccountLoggedIn()) {
return getMP3(track.id).then(result => {
if (!result.data[0]) return null;
if (!result.data[0].url) return null;
if (result.data[0].freeTrialInfo !== null) return null; // 跳过只能试听的歌曲
const source = result.data[0].url.replace(/^http:/, 'https:');
if (store.state.settings.automaticallyCacheSongs) {
cacheTrackSource(track, source, result.data[0].br);
}
return source;
});
} else {
return new Promise(resolve => {
resolve(`https://music.163.com/song/media/outer/url?id=${track.id}`);
});
}
}
async _getAudioSourceFromUnblockMusic(track) {
console.debug(`[debug][Player.js] _getAudioSourceFromUnblockMusic`);
if (
process.env.IS_ELECTRON !== true ||
store.state.settings.enableUnblockNeteaseMusic === false
) {
return null;
}
/**
*
* @param {string=} searchMode
* @returns {import("@unblockneteasemusic/rust-napi").SearchMode}
*/
const determineSearchMode = searchMode => {
/**
* FastFirst = 0
* OrderFirst = 1
*/
switch (searchMode) {
case 'fast-first':
return 0;
case 'order-first':
return 1;
default:
return 0;
}
};
/** @type {import("@unblockneteasemusic/rust-napi").RetrievedSongInfo | null} */
const retrieveSongInfo = await ipcRenderer.invoke(
'unblock-music',
store.state.settings.unmSource,
track,
/** @type {import("@unblockneteasemusic/rust-napi").Context} */({
enableFlac: store.state.settings.unmEnableFlac || null,
proxyUri: store.state.settings.unmProxyUri || null,
searchMode: determineSearchMode(store.state.settings.unmSearchMode),
config: {
'joox:cookie': store.state.settings.unmJooxCookie || null,
'qq:cookie': store.state.settings.unmQQCookie || null,
'ytdl:exe': store.state.settings.unmYtDlExe || null,
},
})
);
if (store.state.settings.automaticallyCacheSongs && retrieveSongInfo?.url) {
// 对于来自 bilibili 的音源
// retrieveSongInfo.url 是音频数据的base64编码
// 其他音源为实际url
const url =
retrieveSongInfo.source === 'bilibili'
? `data:application/octet-stream;base64,${retrieveSongInfo.url}`
: retrieveSongInfo.url;
cacheTrackSource(track, url, 128000, `unm:${retrieveSongInfo.source}`);
}
if (!retrieveSongInfo) {
return null;
}
if (retrieveSongInfo.source !== 'bilibili') {
return retrieveSongInfo.url;
}
const buffer = base642Buffer(retrieveSongInfo.url);
return this._getAudioSourceBlobURL(buffer);
}
_getAudioSource(track) {
return this._getAudioSourceFromCache(String(track.id))
.then(source => {
return source ?? this._getAudioSourceFromNetease(track);
})
.then(source => {
return source ?? this._getAudioSourceFromUnblockMusic(track);
});
}
_replaceCurrentTrack(
id,
autoplay = true,
ifUnplayableThen = UNPLAYABLE_CONDITION.PLAY_NEXT_TRACK
) {
if (autoplay && this._currentTrack.name) {
this._scrobble(this.currentTrack, this._howler?.seek());
}
return getTrackDetail(id).then(data => {
const track = data.songs[0];
this._currentTrack = track;
this._updateMediaSessionMetaData(track);
return this._replaceCurrentTrackAudio(
track,
autoplay,
true,
ifUnplayableThen
);
});
}
/**
* @returns 是否成功加载音频并使用加载完成的音频替换了howler实例
*/
_replaceCurrentTrackAudio(
track,
autoplay,
isCacheNextTrack,
ifUnplayableThen = UNPLAYABLE_CONDITION.PLAY_NEXT_TRACK
) {
return this._getAudioSource(track).then(source => {
if (source) {
let replaced = false;
if (track.id === this.currentTrackID) {
this._playAudioSource(source, autoplay);
replaced = true;
}
if (isCacheNextTrack) {
this._cacheNextTrack();
}
return replaced;
} else {
store.dispatch('showToast', `无法播放 ${track.name}`);
switch (ifUnplayableThen) {
case UNPLAYABLE_CONDITION.PLAY_NEXT_TRACK:
this._playNextTrack(this.isPersonalFM);
break;
case UNPLAYABLE_CONDITION.PLAY_PREV_TRACK:
this.playPrevTrack();
break;
default:
store.dispatch(
'showToast',
`undefined Unplayable condition: ${ifUnplayableThen}`
);
break;
}
return false;
}
});
}
_cacheNextTrack() {
let nextTrackID = this._isPersonalFM
? this._personalFMNextTrack?.id ?? 0
: this._getNextTrack()[0];
if (!nextTrackID) return;
if (this._personalFMTrack.id == nextTrackID) return;
getTrackDetail(nextTrackID).then(data => {
let track = data.songs[0];
this._getAudioSource(track);
});
}
_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(this.isPersonalFM);
});
navigator.mediaSession.setActionHandler('stop', () => {
this.pause();
});
navigator.mediaSession.setActionHandler('seekto', event => {
this.seek(event.seekTime);
this._updateMediaSessionPositionState();
});
navigator.mediaSession.setActionHandler('seekbackward', event => {
this.seek(this.seek() - (event.seekOffset || 10));
this._updateMediaSessionPositionState();
});
navigator.mediaSession.setActionHandler('seekforward', event => {
this.seek(this.seek() + (event.seekOffset || 10));
this._updateMediaSessionPositionState();
});
}
}
_updateMediaSessionMetaData(track) {
if ('mediaSession' in navigator === false) {
return;
}
let artists = track.ar.map(a => a.name);
const metadata = {
title: track.name,
artist: artists.join(','),
album: track.al.name,
artwork: [
{
src: track.al.picUrl + '?param=224y224',
type: 'image/jpg',
sizes: '224x224',
},
{
src: track.al.picUrl + '?param=512y512',
type: 'image/jpg',
sizes: '512x512',
},
],
length: this.currentTrackDuration,
trackId: this.current,
};
navigator.mediaSession.metadata = new window.MediaMetadata(metadata);
if (isCreateMpris) {
ipcRenderer?.send('metadata', metadata);
}
}
_updateMediaSessionPositionState() {
if ('mediaSession' in navigator === false) {
return;
}
if ('setPositionState' in navigator.mediaSession) {
navigator.mediaSession.setPositionState({
duration: ~~(this.currentTrack.dt / 1000),
playbackRate: 1.0,
position: this.seek(),
});
}
}
_nextTrackCallback() {
this._scrobble(this._currentTrack, 0, true);
if (!this.isPersonalFM && this.repeatMode === 'one') {
this._replaceCurrentTrack(this.currentTrackID);
} else {
this._playNextTrack(this.isPersonalFM);
}
}
_loadPersonalFMNextTrack() {
if (this._personalFMNextLoading) {
return [false, undefined];
}
this._personalFMNextLoading = true;
return personalFM()
.then(result => {
if (!result || !result.data) {
this._personalFMNextTrack = undefined;
} else {
this._personalFMNextTrack = result.data[0];
this._cacheNextTrack(); // cache next track
}
this._personalFMNextLoading = false;
return [true, this._personalFMNextTrack];
})
.catch(() => {
this._personalFMNextTrack = undefined;
this._personalFMNextLoading = false;
return [false, this._personalFMNextTrack];
});
}
_playDiscordPresence(track, seekTime = 0) {
if (
process.env.IS_ELECTRON !== true ||
store.state.settings.enableDiscordRichPresence === false
) {
return null;
}
let copyTrack = { ...track };
copyTrack.dt -= seekTime * 1000;
ipcRenderer?.send('playDiscordPresence', copyTrack);
}
_pauseDiscordPresence(track) {
if (
process.env.IS_ELECTRON !== true ||
store.state.settings.enableDiscordRichPresence === false
) {
return null;
}
ipcRenderer?.send('pauseDiscordPresence', track);
}
_playNextTrack(isPersonal) {
if (isPersonal) {
this.playNextFMTrack();
} else {
this.playNextTrack();
}
}
appendTrack(trackID) {
this.list.append(trackID);
}
playNextTrack() {
// TODO: 切换歌曲时增加加载中的状态
const [trackID, index] = this._getNextTrack();
if (trackID === undefined) {
this._howler?.stop();
this._setPlaying(false);
return false;
}
this.current = index;
this._replaceCurrentTrack(trackID);
return true;
}
async playNextFMTrack() {
if (this._personalFMLoading) {
return false;
}
this._isPersonalFM = true;
if (!this._personalFMNextTrack) {
this._personalFMLoading = true;
let result = null;
let retryCount = 5;
for (; retryCount >= 0; retryCount--) {
result = await personalFM().catch(() => null);
if (!result) {
this._personalFMLoading = false;
store.dispatch('showToast', 'personal fm timeout');
return false;
}
if (result.data?.length > 0) {
break;
} else if (retryCount > 0) {
await delay(1000);
}
}
this._personalFMLoading = false;
if (retryCount < 0) {
let content = '获取私人FM数据时重试次数过多请手动切换下一首';
store.dispatch('showToast', content);
console.log(content);
return false;
}
// 这里只能拿到一条数据
this._personalFMTrack = result.data[0];
} else {
if (this._personalFMNextTrack.id === this._personalFMTrack.id) {
return false;
}
this._personalFMTrack = this._personalFMNextTrack;
}
if (this._isPersonalFM) {
this._replaceCurrentTrack(this._personalFMTrack.id);
}
this._loadPersonalFMNextTrack();
return true;
}
playPrevTrack() {
const [trackID, index] = this._getPrevTrack();
if (trackID === undefined) return false;
this.current = index;
this._replaceCurrentTrack(
trackID,
true,
UNPLAYABLE_CONDITION.PLAY_PREV_TRACK
);
return true;
}
saveSelfToLocalStorage() {
let player = {};
for (let [key, value] of Object.entries(this)) {
if (excludeSaveKeys.includes(key)) continue;
player[key] = value;
}
localStorage.setItem('player', JSON.stringify(player));
}
pause() {
this._howler?.fade(this.volume, 0, PLAY_PAUSE_FADE_DURATION);
this._howler?.once('fade', () => {
this._howler?.pause();
this._setPlaying(false);
setTitle(null);
this._pauseDiscordPresence(this._currentTrack);
});
}
play() {
if (this._howler?.playing()) return;
this._howler?.play();
this._howler?.once('play', () => {
this._howler?.fade(0, this.volume, PLAY_PAUSE_FADE_DURATION);
this._setPlaying(true);
if (this._currentTrack.name) {
setTitle(this._currentTrack);
}
this._playDiscordPresence(this._currentTrack, this.seek());
if (store.state.lastfm.key !== undefined) {
trackUpdateNowPlaying({
artist: this.currentTrack.ar[0].name,
track: this.currentTrack.name,
album: this.currentTrack.al.name,
trackNumber: this.currentTrack.no,
duration: ~~(this.currentTrack.dt / 1000),
});
}
});
}
playOrPause() {
if (this._howler?.playing()) {
this.pause();
} else {
this.play();
}
}
seek(time = null) {
if (time !== null) {
this._howler?.seek(time);
if (this._playing)
this._playDiscordPresence(this._currentTrack, this.seek());
}
return this._howler === null ? 0 : this._howler.seek();
}
mute() {
if (this.volume === 0) {
this.volume = this._volumeBeforeMuted;
} else {
this._volumeBeforeMuted = this.volume;
this.volume = 0;
}
}
setOutputDevice() {
if (this._howler?._sounds.length <= 0 || !this._howler?._sounds[0]._node) {
return;
}
this._howler?._sounds[0]._node.setSinkId(store.state.settings.outputDevice);
}
replacePlaylist(
trackIDs,
playlistSourceID,
playlistSourceType,
autoPlayTrackID = 'first'
) {
this._isPersonalFM = false;
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) {
console.debug(
`[debug][Player.js] playPlaylistByID 👉 id:${id} trackID:${trackID} noCache:${noCache}`
);
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);
});
}
playTrackOnListByID(id, listName = 'default') {
if (listName === 'default') {
this._current = this._list.findIndex(t => t === id);
}
this._replaceCurrentTrack(id);
}
playIntelligenceListById(id, trackID = 'first', noCache = false) {
getPlaylistDetail(id, noCache).then(data => {
const randomId = Math.floor(
Math.random() * (data.playlist.trackIds.length + 1)
);
const songId = data.playlist.trackIds[randomId].id;
intelligencePlaylist({ id: songId, pid: id }).then(result => {
let trackIDs = result.data.map(t => t.id);
this.replacePlaylist(trackIDs, id, 'playlist', trackID);
});
});
}
addTrackToPlayNext(trackID, playNow = false) {
this._playNextList.push(trackID);
if (playNow) {
this.playNextTrack();
}
}
playPersonalFM() {
this._isPersonalFM = true;
if (!this._enabled) this._enabled = true;
if (this.currentTrackID !== this._personalFMTrack.id) {
this._replaceCurrentTrack(this._personalFMTrack.id, true);
} else {
this.playOrPause();
}
}
async moveToFMTrash() {
this._isPersonalFM = true;
let id = this._personalFMTrack.id;
if (await this.playNextFMTrack()) {
fmTrash(id);
}
}
sendSelfToIpcMain() {
if (process.env.IS_ELECTRON !== true) return false;
let liked = store.state.liked.songs.includes(this.currentTrack.id);
ipcRenderer?.send('player', {
playing: this.playing,
likedCurrentTrack: liked,
});
setTrayLikeState(liked);
}
switchRepeatMode() {
if (this._repeatMode === 'on') {
this.repeatMode = 'one';
} else if (this._repeatMode === 'one') {
this.repeatMode = 'off';
} else {
this.repeatMode = 'on';
}
if (isCreateMpris) {
ipcRenderer?.send('switchRepeatMode', this.repeatMode);
}
}
switchShuffle() {
this.shuffle = !this.shuffle;
if (isCreateMpris) {
ipcRenderer?.send('switchShuffle', this.shuffle);
}
}
switchReversed() {
this.reversed = !this.reversed;
}
clearPlayNextList() {
this._playNextList = [];
}
removeTrackFromQueue(index) {
this._playNextList.splice(index, 1);
}
}