Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion renderer/src/hooks/useMediaSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,15 @@ export function useMediaSession(playback: PlaybackState) {

const provider = () => getActiveProvider();
navigator.mediaSession.setActionHandler("play", () => { provider().play().catch(() => {}); });
navigator.mediaSession.setActionHandler("pause", () => { provider().pause().catch(() => {}); });
// On Windows, OS pause commands come from Teams joining a call and from
// Bluetooth headsets as well as the user, and would stop the shared
// speakers for everyone — so they're swallowed (a no-op handler, not null,
// which would let Chromium apply its default). See the SMTC handler in main.ts.
const ignoreOsPause = navigator.userAgent.includes("Windows");
navigator.mediaSession.setActionHandler("pause", () => {
if (ignoreOsPause) return;
provider().pause().catch(() => {});
});
navigator.mediaSession.setActionHandler("previoustrack", () => { provider().skipPrev().catch(() => {}); });
navigator.mediaSession.setActionHandler("nexttrack", () => { provider().skipNext().catch(() => {}); });
navigator.mediaSession.setActionHandler("seekto", (details) => {
Expand Down
42 changes: 7 additions & 35 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import WebSocket from 'ws';
import type { FetchRequest, FetchResponse } from './types';
import * as telemetry from './telemetry';
import * as smtc from './smtc';
import { isMicInUse } from './mic-in-use';

// Ensure the renderer's Media Session API (used to surface "now playing" to
// Windows System Media Transport Controls / macOS Now Playing) is wired up.
Expand All @@ -50,7 +49,13 @@ smtc.init((action) => {
wsSend({ namespace: 'playback', groupId, command: 'play' }, { allowTvPauseRestore: true, deviceFeedback: 'NONE' }).catch(() => {});
break;
case 'pause':
void handleSmtcPause();
// Deliberately ignored. Windows sends pause for more than the user
// pressing pause: Teams pauses every media session on joining a call,
// and Bluetooth headsets send pause when they connect, switch to call
// mode or come off. Any of those would stop the shared office speakers
// for everyone, so pausing is left to the app's own controls.
console.log('[smtc] ignoring pause command from Windows');
telemetry.event('SmtcPauseIgnored');
break;
case 'next':
wsSend({ namespace: 'playback', groupId, command: 'skipToNextTrack' }, {}).catch(() => {});
Expand All @@ -61,39 +66,6 @@ smtc.init((action) => {
}
}, { displayName: 'True Tunes', appPath: app.getAppPath(), isPackaged: app.isPackaged });

/** How long after an SMTC pause we keep watching for a call starting. */
const CALL_PAUSE_GRACE_MS = 4000;
const CALL_PAUSE_POLL_MS = 500;

/**
* Teams (and other call apps) pause every SMTC media session when you join a
* call. That's right for local audio, but here it would pause the office
* speakers for everyone. The pause event doesn't say who sent it, so we use
* "another app has the mic open" as the call signal: if it already is, the
* pause is ignored; if it opens within a few seconds of us pausing, we resume.
*/
async function handleSmtcPause(): Promise<void> {
if (await isMicInUse()) {
console.log('[smtc] ignoring pause: microphone in use (call in progress)');
telemetry.event('SmtcPauseIgnored', { reason: 'mic-in-use' });
return;
}
const sendPlayback = (command: 'play' | 'pause') => {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
wsSend({ namespace: 'playback', groupId: config.groupId, command }, { allowTvPauseRestore: true, deviceFeedback: 'NONE' }).catch(() => {});
};
sendPlayback('pause');
for (let waited = 0; waited < CALL_PAUSE_GRACE_MS; waited += CALL_PAUSE_POLL_MS) {
await new Promise((r) => setTimeout(r, CALL_PAUSE_POLL_MS));
if (await isMicInUse()) {
console.log('[smtc] call started right after pause, resuming playback');
telemetry.event('SmtcPauseIgnored', { reason: 'mic-opened-after' });
sendPlayback('play');
return;
}
}
}

// ─── Config ──────────────────────────────────────────────────────────────────

/** Music service credentials (persisted to userData/config.json) plus
Expand Down
45 changes: 0 additions & 45 deletions src/mic-in-use.test.ts

This file was deleted.

52 changes: 0 additions & 52 deletions src/mic-in-use.ts

This file was deleted.

Loading