feat(core): add Discord Rich Presence integration

Introduce optional Discord activity updates across config, runtime, tests, and docs so playback context appears in Discord without destabilizing app lifecycle. Tune default refresh cadence to reduce pause/resume lag during real sessions.
This commit is contained in:
2026-02-22 17:25:55 -08:00
parent edfe6640ac
commit f1dc418e2d
17 changed files with 238 additions and 346 deletions

View File

@@ -427,6 +427,7 @@ let jellyfinPlayQuitOnDisconnectArmed = false;
const JELLYFIN_LANG_PREF = 'ja,jp,jpn,japanese,en,eng,english,enUS,en-US';
const JELLYFIN_TICKS_PER_SECOND = 10_000_000;
const JELLYFIN_REMOTE_PROGRESS_INTERVAL_MS = 3000;
const DISCORD_PRESENCE_APP_ID = '1475264834730856619';
const JELLYFIN_MPV_CONNECT_TIMEOUT_MS = 3000;
const JELLYFIN_MPV_AUTO_LAUNCH_TIMEOUT_MS = 20000;
const MPV_JELLYFIN_DEFAULT_ARGS = [
@@ -585,19 +586,38 @@ const appState = createAppState({
texthookerPort: DEFAULT_TEXTHOOKER_PORT,
});
const discordPresenceSessionStartedAtMs = Date.now();
let discordPresenceMediaDurationSec: number | null = null;
function refreshDiscordPresenceMediaDuration(): void {
const client = appState.mpvClient;
if (!client || !client.connected) return;
void client
.requestProperty('duration')
.then((value) => {
const numeric = Number(value);
discordPresenceMediaDurationSec = Number.isFinite(numeric) && numeric > 0 ? numeric : null;
})
.catch(() => {
discordPresenceMediaDurationSec = null;
});
}
function publishDiscordPresence(): void {
refreshDiscordPresenceMediaDuration();
appState.discordPresenceService?.publish({
mediaTitle: appState.currentMediaTitle,
mediaPath: appState.currentMediaPath,
subtitleText: appState.currentSubText,
currentTimeSec: appState.mpvClient?.currentTimePos ?? null,
mediaDurationSec:
discordPresenceMediaDurationSec ?? anilistMediaGuessRuntimeState.mediaDurationSec,
paused: appState.playbackPaused,
connected: Boolean(appState.mpvClient?.connected),
sessionStartedAtMs: discordPresenceSessionStartedAtMs,
});
}
function createDiscordRpcClient(clientId: string) {
function createDiscordRpcClient() {
const discordRpc = require('discord-rpc') as {
Client: new (opts: { transport: 'ipc' }) => {
login: (opts: { clientId: string }) => Promise<void>;
@@ -609,7 +629,7 @@ function createDiscordRpcClient(clientId: string) {
const client = new discordRpc.Client({ transport: 'ipc' });
return {
login: () => client.login({ clientId }),
login: () => client.login({ clientId: DISCORD_PRESENCE_APP_ID }),
setActivity: (activity: unknown) =>
client.setActivity(activity as unknown as Record<string, unknown>),
clearActivity: () => client.clearActivity(),
@@ -620,7 +640,7 @@ function createDiscordRpcClient(clientId: string) {
async function initializeDiscordPresenceService(): Promise<void> {
appState.discordPresenceService = createDiscordPresenceService({
config: getResolvedConfig().discordPresence,
createClient: (clientId) => createDiscordRpcClient(clientId),
createClient: () => createDiscordRpcClient(),
logDebug: (message, meta) => logger.debug(message, meta),
});
await appState.discordPresenceService.start();