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
7 changes: 4 additions & 3 deletions docs/media-showcase.md
Original file line number Diff line number Diff line change
Expand Up @@ -823,9 +823,10 @@ new peer connections. The supported public service contract freshly mints one-ho
credentials. The client keeps a conservative monotonic lifetime, charging
request/body time and a rounding margin; a wall-clock correction can only
shorten it. Renewal starts after two thirds of the remaining lifetime.
New peer connections use the latest credential. Relayed connections retire
shortly before their old credential expires so the SDK can reconnect while
the playing HLS instance and verified cache survive. Pause cancels renewal.
New peer connections use the latest credential. Connections using this viewer's
local relay allocation retire shortly before its old credential expires so the
SDK can reconnect while the playing HLS instance and verified cache survive.
Pause cancels renewal.
Diagnostics expose availability and expiry duration, never the credential.

An unconfigured public relay causes no request, timer or startup delay;
Expand Down
2 changes: 1 addition & 1 deletion site/live-media.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,6 @@
</main>
<!-- SHOWCASE_END -->
<footer class="site-footer"><div class="page-wrap"><div class="footer-inner"><a class="brand" href="/">Forge<span>Sworn</span></a><div class="footer-links"><a href="https://github.com/forgesworn">GitHub</a><a href="/live-media-benchmarks.html">Measured results</a><a href="https://github.com/sponsors/TheCryptoDonkey">Support the work ↗</a></div></div><p class="footer-note">Open-source media software, yours to run. The supplied mix is licensed for this demonstration; its media rights are separate from the MIT code. <a href="/media/dejavu-preview/NOTICE.txt">Media permission</a> · <a href="/llms.txt">For developers and LLMs</a></p></div></footer>
<script type="module" src="/media-lab.mjs?v=20261027-bounded-startup-window"></script>
<script type="module" src="/media-lab.mjs?v=20261027-local-turn-lease"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion site/media-lab.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,6 @@
</main>
<!-- SHOWCASE_END -->
<footer class="site-footer"><div class="page-wrap"><div class="footer-inner"><a class="brand" href="/">Forge<span>Sworn</span></a><div class="footer-links"><a href="https://github.com/forgesworn">GitHub</a><a href="/live-media-benchmarks.html">Measured results</a><a href="https://github.com/sponsors/TheCryptoDonkey">Support the work ↗</a></div></div><p class="footer-note">Open-source media software, yours to run. The supplied mix is licensed for this demonstration; its media rights are separate from the MIT code. <a href="/media/dejavu-preview/NOTICE.txt">Media permission</a> · <a href="/llms.txt">For developers and LLMs</a></p></div></footer>
<script type="module" src="/media-lab.mjs?v=20261027-bounded-startup-window"></script>
<script type="module" src="/media-lab.mjs?v=20261027-local-turn-lease"></script>
</body>
</html>
4 changes: 2 additions & 2 deletions site/media-lab.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ async function initialise() {
publicRoom?.stop(); publicRoom = null;
const pending = (async () => {
const [{ createListenerSession }, { PUBLIC_SWARM_SOURCE_SHA256, PUBLIC_SWARM_RELAYS }] = await Promise.all([
import('./media-listener-session.mjs?v=20261027-bounded-startup-window'), import('./media-public-swarm.mjs?v=20261027-bounded-startup-window'),
import('./media-listener-session.mjs?v=20261027-bounded-startup-window'), import('./media-public-swarm.mjs?v=20261027-local-turn-lease'),
]);
if (request !== publicRoomRequest || pageAbort.signal.aborted) return null;
const clock = createListenerSession({ room: `forgesworn-dejavu-listening-v${quality === 'full' ? 2 : 1}-${quality}`, source: PUBLIC_SWARM_SOURCE_SHA256,
Expand Down Expand Up @@ -1522,7 +1522,7 @@ async function initialise() {
if (player) {
let peer, setupEpoch;
try {
const { createPublicSwarm, prepareRecordedSource, createRecordedLive } = await import('./media-public-swarm.mjs?v=20261027-bounded-startup-window');
const { createPublicSwarm, prepareRecordedSource, createRecordedLive } = await import('./media-public-swarm.mjs?v=20261027-local-turn-lease');
if (!owns() || s.hls !== player) return;
// These two exports share the exact helper module/recording brand.
// A cold first room prepares locally before publishing its clock.
Expand Down
7 changes: 4 additions & 3 deletions site/media-public-swarm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -437,9 +437,10 @@ export function createPublicSwarm({ Hls, hls, video, playlistUrl, quality, signa
const renew = () => {
if (!enabled || stopped || this.connectionState === 'closed' || !record.identity) return;
const current = turn.leaseIdentity();
if (current && current !== record.identity && peerObservations.get(this)?.kind === 'relay') {
// Retire just this expiring transport. The SDK reconnects with
// the latest lease; the playing HLS instance and cache survive.
if (current && current !== record.identity && peerObservations.get(this)?.localCandidateType === 'relay') {
// Retire only this viewer's expiring local relay allocation.
// The SDK reconnects with the latest lease; the playing HLS
// instance and cache survive.
for (const channel of record.channels) { try { channel.close(); } catch {} }
return;
}
Expand Down
2 changes: 1 addition & 1 deletion site/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,6 @@ <h2 class="reveal">Pick a block and start</h2>
});
});
</script>
<script type="module" src="/media-lab.mjs?v=20261027-bounded-startup-window"></script>
<script type="module" src="/media-lab.mjs?v=20261027-local-turn-lease"></script>
</body>
</html>
70 changes: 67 additions & 3 deletions test/media-public-swarm.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ function lifecycleFixture(t, { turnCredentials, turnConfiguration, turnFactory,
close() { if (this.readyState === 3) return; this.readyState = 3; this.dispatchEvent(new Event('close')); }
}
class Connection extends EventTarget {
constructor(config) { super(); this.configuration = structuredClone(config); this.connectionState = 'connected'; this.iceConnectionState = 'connected'; connections.push(this); }
getStats() { return Promise.resolve(new Map()); }
createDataChannel() { return { send() {} }; }
constructor(config) { super(); this.configuration = structuredClone(config); this.connectionState = 'connected'; this.iceConnectionState = 'connected'; this.stats = new Map(); connections.push(this); }
getStats() { return Promise.resolve(this.stats); }
createDataChannel() { return { closeCalls: 0, send() {}, close() { this.closeCalls++; } }; }
close() { if (this.connectionState === 'closed') return; this.connectionState = 'closed'; this.dispatchEvent(new Event('connectionstatechange')); }
}
class Loader { load(context, config, callbacks) { callbacks.onSuccess({ data: new ArrayBuffer(16) }, {}, context, null); } }
Expand Down Expand Up @@ -290,6 +290,70 @@ test('native peer construction uses the current automatic lease and pause cancel
assert.equal(fixture.pendingTimers(), 0);
});

for (const { name, local, remote, kind, retired } of [
{ name: 'selected local relay', local: 'relay', remote: 'host', kind: 'relay', retired: true },
{ name: 'selected remote relay only', local: 'host', remote: 'relay', kind: 'relay', retired: false },
{ name: 'selected direct route', local: 'host', remote: 'srflx', kind: 'direct', retired: false },
{ name: 'unknown selected route', local: null, remote: null, kind: null, retired: false },
]) test(`automatic lease rotation retires only its local allocation: ${name}`, async t => {
let identity = 'initial-viewer';
const f = lifecycleFixture(t, { turnCredentials: {
async start() {}, stop() {},
servers: () => [{ urls: ['turns:relay.invalid:5349?transport=tcp'], username: identity, credential: 'fixture-only' }],
leaseIdentity: () => identity,
snapshot: () => ({ phase: 'ready', available: true, remainingMs: 90000, failures: 0 }),
} });
await f.adapter.setEnabled(true);
const pc = f.connections[0], channel = pc.createDataChannel('fixture');
if (local && remote) pc.stats = new Map([
['transport', { id: 'transport', type: 'transport', selectedCandidatePairId: 'selected' }],
['selected', { id: 'selected', type: 'candidate-pair', localCandidateId: 'local', remoteCandidateId: 'remote', bytesSent: 0, bytesReceived: 0 }],
['local', { id: 'local', type: 'local-candidate', candidateType: local }],
['remote', { id: 'remote', type: 'remote-candidate', candidateType: remote }],
]);
// Use the adapter's ordinary stats sampling and original renewal timer.
f.advance(750); await new Promise(resolve => setImmediate(resolve));
assert.equal(f.adapter.metrics().topology.connections[0].kind, kind, 'aggregate route reporting remains unchanged');
assert.equal(f.adapter.metrics().topology.connections[0].localCandidateType, local);
const loader = f.hls.config.fLoader, media = { ...f.video };
identity = 'renewed-viewer';
f.advance(30249);
assert.equal(channel.closeCalls, 0, 'the old lease is not retired before its scheduled boundary');
f.advance(1);
assert.equal(channel.closeCalls, Number(retired), 'only the selected local allocation belongs to the rotating lease');
assert.equal(f.cycles.length, 1); assert.equal(f.cycles[0].stopped, false);
assert.equal(f.hls.config.fLoader, loader); assert.deepEqual(f.video, media);
assert.equal(f.adapter.metrics().resources.heldBytes, 1024, 'transport retirement preserves the active media cache');
const next = new f.cycles[0].options.RTCPeerConnectionImpl({});
assert.equal(next.configuration.iceServers[1].username, 'renewed-viewer', 'a subsequent dial takes the current lease');
next.close();
await f.adapter.setEnabled(false);
assert.equal(f.pendingTimers(), 0, 'pause cancels all remaining sampling and lease timers');
});

test('pause cancels scheduled local relay retirement before a later lease rotation', async t => {
let identity = 'initial-viewer', stops = 0;
const f = lifecycleFixture(t, { turnCredentials: {
async start() {}, stop() { stops++; },
servers: () => [{ urls: ['turns:relay.invalid:5349?transport=tcp'], username: identity, credential: 'fixture-only' }],
leaseIdentity: () => identity,
snapshot: () => ({ phase: 'ready', available: true, remainingMs: 90000, failures: 0 }),
} });
await f.adapter.setEnabled(true);
const pc = f.connections[0], channel = pc.createDataChannel('fixture');
pc.stats = new Map([
['selected', { id: 'selected', type: 'candidate-pair', state: 'succeeded', nominated: true, localCandidateId: 'local', remoteCandidateId: 'remote' }],
['local', { id: 'local', type: 'local-candidate', candidateType: 'relay' }],
['remote', { id: 'remote', type: 'remote-candidate', candidateType: 'host' }],
]);
f.advance(750); await new Promise(resolve => setImmediate(resolve));
assert.equal(f.adapter.metrics().topology.connections[0].localCandidateType, 'relay');
await f.adapter.setEnabled(false);
identity = 'renewed-viewer'; f.advance(90000);
assert.equal(channel.closeCalls, 0, 'a retired adapter cannot run its previous lease timer');
assert.equal(stops, 1); assert.equal(pc.connectionState, 'closed'); assert.equal(f.pendingTimers(), 0);
});

test('a completed empty ICE gather reports no browser route without crediting peer delivery', async t => {
const f = lifecycleFixture(t);
await f.adapter.setEnabled(true);
Expand Down
Loading