Skip to content

TabLeaderPlugin

If your app can be open in multiple tabs at the same time, you have probably seen two tabs fighting over audio. TabLeaderPlugin stops that by using the Web Locks API to elect one tab as the leader. Only the leader tab is allowed to play. When a second tab acquires the lock, the first tab loses the lock; the onLost action handler is registered but the involuntary-loss signal is not yet emitted in this release. If the leader tab is closed or navigated away, the browser hands the lock to the next waiting tab automatically.

This plugin is a direct re-export of the player core’s TabLeaderPlugin. The music player does not override any behaviour.

TypeScript
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { TabLeaderPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
import type { TabLeaderOptions } from '@nomercy-entertainment/nomercy-music-player/plugins';

Plugin id: 'tab-leader'

What it does

On use(), the plugin calls navigator.locks.request(key, callback). The browser gives the lock to the first tab that asks. That tab emits leader-acquired and is free to play. Every other tab queues behind it. When the leader releases the lock (or the tab closes), the browser fires the next tab’s callback and it emits leader-acquired in turn. In environments where navigator.locks is absent, the plugin emits unsupported and becomes a no-op.

Options

OptionTypeDefaultDescription
onLost'pause' | 'mute''pause'Action when this tab releases the lock voluntarily (releaseLock() / dispose()): 'pause' pauses, 'mute' mutes. Not yet triggered on the involuntary path where another tab steals the lock.
handoffOnVisiblebooleantrueRe-request the lock whenever the tab becomes visible via document.visibilitychange. The behaviour is active when this option is absent; set it explicitly to false to disable.
getLockKey() => stringundefinedFactory returning the lock key. When absent, the plugin falls back to the shared key 'nomercy-player-leader', which is global across all instances on the same origin. Return a per-player or per-session string to scope leadership more narrowly.

Events

EventPayloadDescription
leader-acquiredvoidFired when this tab successfully acquires the leader lock.
leader-releasedvoidFired when this tab voluntarily releases the lock via releaseLock() or on dispose.
unsupportedvoidFired when the Web Locks API is unavailable. The plugin is a no-op in this case.

Listen using the namespaced string form:

TypeScript
player.on('plugin:tab-leader:leader-acquired', () => {
console.log('This tab is now the leader');
});

player.on('plugin:tab-leader:unsupported', () => {
console.warn('Web Locks not available; tab coordination disabled');
});

Methods

isLeader()

TypeScript
isLeader(): boolean

Returns true when this tab currently holds the leader lock.

requestLock()

TypeScript
requestLock(): Promise<void>

Request the leader lock. The returned promise resolves when the lock is voluntarily released via releaseLock() or when the plugin disposes — not at acquire time. Calling while an election is already in progress returns the existing promise rather than starting a second election. Emits leader-acquired when the lock is granted.

releaseLock()

TypeScript
releaseLock(): void

Voluntarily release the leader lock so the next waiting tab can take over. Emits leader-released when this tab was the leader. No-ops when this tab is not the current leader.

requestLeadership()

TypeScript
requestLeadership(): Promise<boolean>

Backwards-compatible alias for requestLock(). Returns true when leadership is held after the attempt resolves, false otherwise.

releaseLeadership()

TypeScript
releaseLeadership(): void

Backwards-compatible alias for releaseLock().

Registration

Basic registration; the plugin calls requestLock() automatically on use():

TypeScript
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { TabLeaderPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

const player = nmMPlayer('main')
.addPlugin(TabLeaderPlugin)
.setup({ playlist: myTracks });

With options:

TypeScript
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { TabLeaderPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

const player = nmMPlayer('main')
.addPlugin(TabLeaderPlugin, {
onLost: 'pause',
handoffOnVisible: true,
getLockKey: () => `nomercy-leader-${location.hostname}`,
})
.setup({ playlist: myTracks });

const leaderPlugin = player.getPlugin(TabLeaderPlugin)!;

player.on('play', () => {
if (!leaderPlugin.isLeader()) {
void player.pause?.();
}
});

Browser support

Web Locks is supported in Chrome 69+, Firefox 96+, and Safari 15.4+. In environments without navigator.locks, the plugin emits unsupported and becomes a no-op. All tabs can play simultaneously without error in those environments.

See also