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 @@ -
+
{{ $t("settings.lyricFontSize.text") }}
@@ -231,6 +231,24 @@
+
+
+
+ {{ $t("settings.enableDiscordRichPresence") }}
+
+
+
+ + +
+
+
🐈️ 🏳️‍🌈
@@ -428,6 +446,17 @@ export default { }); }, }, + enableDiscordRichPresence: { + get() { + return this.settings.enableDiscordRichPresence; + }, + set(value) { + this.$store.commit("updateSettings", { + key: "enableDiscordRichPresence", + value, + }); + }, + }, }, methods: { getAllOutputDevices() { diff --git a/yarn.lock b/yarn.lock index 8bdd901..36b3dca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2657,10 +2657,10 @@ binaryextensions@^4.15.0: resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-4.15.0.tgz#c63a502e0078ff1b0e9b00a9f74d3c2b0f8bd32e" integrity sha512-MkUl3szxXolQ2scI1PM14WOT951KnaTNJ0eMKg7WzOI4kvSxyNo/Cygx4LOBNhwyINhAuSQpJW1rYD9aBSxGaw== -bindings@^1.5.0: +bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + resolved "https://registry.npm.taobao.org/bindings/download/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha1-EDU8npRTNLwFEabZCzj7x8nFBN8= dependencies: file-uri-to-path "1.0.0" @@ -4298,6 +4298,22 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +discord-rich-presence@^0.0.8: + version "0.0.8" + resolved "https://registry.npm.taobao.org/discord-rich-presence/download/discord-rich-presence-0.0.8.tgz#7a2b41ff87a278e8a2c8835cd91c9890d6b9fbdd" + integrity sha1-eitB/4eieOiiyINc2RyYkNa5+90= + dependencies: + discord-rpc "github:discordjs/rpc" + +discord-rpc@^3.2.0, "discord-rpc@github:discordjs/rpc": + version "3.2.0" + resolved "https://codeload.github.com/discordjs/rpc/tar.gz/32f1b9f8c47cf82ab87a83391ca9a9115513f01e" + dependencies: + node-fetch "^2.6.1" + ws "^7.3.1" + optionalDependencies: + register-scheme "github:devsnek/node-register-scheme" + dmg-builder@22.10.5: version "22.10.5" resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.10.5.tgz#65a33c106ead5a350c7de8997c546559bd6e0e7c" @@ -7909,10 +7925,15 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-addon-api@^1.6.3: +node-addon-api@^1.3.0, node-addon-api@^1.6.3: version "1.7.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" - integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== + resolved "https://registry.npm.taobao.org/node-addon-api/download/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" + integrity sha1-PfMLlXILU8JOWZSLSVMrZiRE9U0= + +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.npm.taobao.org/node-fetch/download/node-fetch-2.6.1.tgz?cache=0&sync_timestamp=1599309179354&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-fetch%2Fdownload%2Fnode-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI= node-forge@^0.10.0: version "0.10.0" @@ -9580,6 +9601,13 @@ regexpu-core@^4.7.1: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.2.0" +"register-scheme@github:devsnek/node-register-scheme": + version "0.0.2" + resolved "https://codeload.github.com/devsnek/node-register-scheme/tar.gz/e7cc9a63a1f512565da44cb57316d9fb10750e17" + dependencies: + bindings "^1.3.0" + node-addon-api "^1.3.0" + register-service-worker@^1.7.1: version "1.7.2" resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.7.2.tgz#6516983e1ef790a98c4225af1216bc80941a4bd2" @@ -12052,6 +12080,11 @@ ws@^6.0.0, ws@^6.2.1: dependencies: async-limiter "~1.0.0" +ws@^7.3.1: + version "7.4.4" + resolved "https://registry.npm.taobao.org/ws/download/ws-7.4.4.tgz?cache=0&sync_timestamp=1615063746103&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fws%2Fdownload%2Fws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" + integrity sha1-ODvJdCyyAikskHfOq29gR7F/LVk= + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"