Skip to content

Tab Leader

TabLeaderPlugin solves a specific annoyance: you open your library in two browser tabs and both start playing at once. The plugin uses the browser’s Web Locks API to hold a named lock while a tab is playing. Only one tab holds the lock at a time. When a second tab requests it, the browser queues that request and hands the lock over the moment the first tab releases it or closes.

Reach for this plugin whenever you need to guarantee that only one player instance on the same origin is active at a time. If you want finer control, such as scoping leadership to a single player instance rather than the whole origin, pass a getLockKey factory in the options.

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

Plugin id: 'tab-leader'

What it does

On use() the plugin calls navigator.locks.request(key, callback). The browser hands the lock to the first tab that requests it. That tab emits 'leader-acquired' and is free to play. Other tabs queue behind it. When the leader tab closes or calls releaseLock(), the browser hands the lock to the next queued tab, which emits 'leader-acquired' in turn.

In environments without navigator.locks (Safari below 15.4) the plugin emits 'unsupported' and becomes a no-op. Playback is unaffected, it just does not enforce single-tab behaviour.

Options

OptionTypeDefaultDescription
onLost'pause' | 'mute''pause'Action taken when this tab loses the leader lock. 'pause' calls player.pause(). 'mute' calls player.mute() so audio stops but buffering continues.
handoffOnVisiblebooleantrueRe-request the lock whenever the tab becomes visible via document.visibilitychange. Set explicitly to false to disable; absence is treated as true.
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-acquired'voidThis tab now holds the lock and is the active player.
'leader-released'voidLock released voluntarily via releaseLock() or when the plugin disposes.
'unsupported'voidWeb Locks not available in this environment. The plugin is inert.

Methods

isLeader

TypeScript
plugin.isLeader(): boolean

Returns true when this tab currently holds the leader lock.

requestLock

TypeScript
plugin.requestLock(): Promise<void>

Queue a lock-acquire request. The returned promise resolves when the lock is released (via releaseLock(), plugin disposal, or tab close), not when it is first acquired. Calling while an election is already in progress returns the existing pending promise rather than starting a second one.

releaseLock

TypeScript
plugin.releaseLock(): void

Voluntarily release the leader lock. Emits 'leader-released' when this tab was the leader. The next queued tab acquires the lock immediately. No-ops if this tab is not currently the leader.

requestLeadership / releaseLeadership

TypeScript
plugin.requestLeadership(): Promise<boolean>
plugin.releaseLeadership(): void

Backwards-compatible aliases for requestLock() and releaseLock(). requestLeadership() resolves to true when leadership is held after the attempt, false otherwise. Prefer the non-alias names in new code.

Registration

TypeScript
import nmplayer from '@nomercy-entertainment/nomercy-video-player';
import { TabLeaderPlugin } from '@nomercy-entertainment/nomercy-video-player/plugins';

const player = nmplayer('player')
.addPlugin(TabLeaderPlugin, { onLost: 'pause' })
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Films',
playlist: [
{
id: 'sintel-2010',
url: '/Sintel.(2010)/Sintel.(2010).NoMercy.m3u8',
},
],
});

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

player.on('plugin:tab-leader:leader-released', () => {
console.log('this tab released the lock');
});

Scoping to a specific player

By default the lock key is global to the origin, so one tab plays at a time across all player instances on that origin. Pass getLockKey to scope it to a server ID, content item, or session:

TypeScript
const serverId = 'abc123';

player.addPlugin(TabLeaderPlugin, {
getLockKey: () => `nomercy-leader-${serverId}`,
});

Reading leader state

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

const leaderPlugin = player.getPlugin(TabLeaderPlugin);

if (leaderPlugin?.isLeader()) {
// safe to start playback
}

See also