diff --git a/package.json b/package.json index 7d0ce91..8e24d7f 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "core-js": "^3.6.5", "crypto-js": "^4.0.0", "dayjs": "^1.8.36", + "discord-rich-presence": "^0.0.8", + "discord-rpc": "^3.2.0", "electron": "^12.0.0", "electron-builder": "^22.10.5", "electron-context-menu": "^2.3.0", diff --git a/src/electron/ipcMain.js b/src/electron/ipcMain.js index c6007fc..c86d091 100644 --- a/src/electron/ipcMain.js +++ b/src/electron/ipcMain.js @@ -1,5 +1,6 @@ import { app, ipcMain, dialog } from "electron"; import match from "@njzy/unblockneteasemusic"; +const client = require("discord-rich-presence")("818936529484906596"); export function initIpcMain(win, store) { ipcMain.on("unblock-music", (event, track) => { @@ -61,4 +62,29 @@ export function initIpcMain(win, store) { ipcMain.on("settings", (event, options) => { store.set("settings", options); }); + + ipcMain.on("playDiscordPresence", (event, track) => { + client.updatePresence({ + details: track.name + " - " + track.ar.map((ar) => ar.name).join(","), + state: track.al.name, + endTimestamp: Date.now() + track.dt, + largeImageKey: "logo", + largeImageText: "YesPlayMusic", + smallImageKey: "play", + smallImageText: "Playing", + instance: true, + }); + }); + + ipcMain.on("pauseDiscordPresence", (event, track) => { + client.updatePresence({ + details: track.name + " - " + track.ar.map((ar) => ar.name).join(","), + state: track.al.name, + largeImageKey: "logo", + largeImageText: "YesPlayMusic", + smallImageKey: "pause", + smallImageText: "Pause", + instance: true, + }); + }); } diff --git a/src/locale/lang/en.js b/src/locale/lang/en.js index e3735a4..19cea7f 100644 --- a/src/locale/lang/en.js +++ b/src/locale/lang/en.js @@ -146,6 +146,7 @@ export default { showGitHubIcon: "Show GitHub icon", showUnavailableSongInGreyStyle: "Show unavailable song in grey style", showPlaylistsByAppleMusic: "Show playlists by Apple Music", + enableDiscordRichPresence: "Enable Discord Rich Presence", }, contextMenu: { play: "Play", diff --git a/src/locale/lang/zh-CN.js b/src/locale/lang/zh-CN.js index 6383e91..4fc031e 100644 --- a/src/locale/lang/zh-CN.js +++ b/src/locale/lang/zh-CN.js @@ -147,6 +147,7 @@ export default { showGitHubIcon: "显示 GitHub 图标", showUnavailableSongInGreyStyle: "显示不可播放的歌曲为灰色", showPlaylistsByAppleMusic: "首页显示来自 Apple Music 的歌单", + enableDiscordRichPresence: "启用 Discord Rich Presence", }, contextMenu: { play: "播放", diff --git a/src/store/initLocalStorage.js b/src/store/initLocalStorage.js index fcce7a3..1debf57 100644 --- a/src/store/initLocalStorage.js +++ b/src/store/initLocalStorage.js @@ -16,6 +16,7 @@ let localStorage = { nyancatStyle: false, showLyricsTranslation: true, minimizeToTray: false, + enableDiscordRichPresence: false, }, data: { user: {}, diff --git a/src/utils/Player.js b/src/utils/Player.js index 1e515be..b486c40 100644 --- a/src/utils/Player.js +++ b/src/utils/Player.js @@ -278,6 +278,15 @@ export default class { }); 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(); }); } } @@ -299,6 +308,18 @@ export default class { ], }); } + _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(true); if (this.repeatMode === "one") { @@ -313,6 +334,26 @@ export default class { return 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); + } currentTrackID() { const { list, current } = this._getListAndCurrent(); @@ -361,12 +402,14 @@ export default class { this._howler.pause(); this._playing = false; document.title = "YesPlayMusic"; + this._pauseDiscordPresence(this._currentTrack); } play() { if (this._howler.playing()) return; this._howler.play(); this._playing = true; document.title = `${this._currentTrack.name} · ${this._currentTrack.ar[0].name} - YesPlayMusic`; + this._playDiscordPresence(this._currentTrack, this.seek()); } playOrPause() { if (this._howler.playing()) { @@ -376,7 +419,11 @@ export default class { } } seek(time = null) { - if (time !== null) this._howler.seek(time); + 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() { @@ -388,7 +435,9 @@ export default class { } } setOutputDevice() { - if (this._howler._sounds.length <= 0) return; + if (this._howler._sounds.length <= 0 || !this._howler._sounds[0]._node) { + return; + } this._howler._sounds[0]._node.setSinkId(store.state.settings.outputDevice); } diff --git a/src/views/settings.vue b/src/views/settings.vue index 9ff755f..a9d97a9 100644 --- a/src/views/settings.vue +++ b/src/views/settings.vue @@ -74,7 +74,7 @@ -