@mebius-io/web 0.4.8 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +19 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +19 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -346,6 +346,9 @@ var WhepViewTransport = class {
|
|
|
346
346
|
};
|
|
347
347
|
|
|
348
348
|
// src/internal/scale-view-transport.ts
|
|
349
|
+
function retryWarmupNotFound(cfg, retryCount, res, retry) {
|
|
350
|
+
return retry || retryCount < (cfg?.maxNumRetry ?? 0) && res?.code === 404;
|
|
351
|
+
}
|
|
349
352
|
var HlsViewTransport = class {
|
|
350
353
|
/**
|
|
351
354
|
* deliveryPath, when given, is a gateway-relative path from the gateway's own
|
|
@@ -393,7 +396,22 @@ var HlsViewTransport = class {
|
|
|
393
396
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
394
397
|
return;
|
|
395
398
|
}
|
|
396
|
-
const hls = new Hls({
|
|
399
|
+
const hls = new Hls({
|
|
400
|
+
maxLiveSyncPlaybackRate: 1.1,
|
|
401
|
+
manifestLoadPolicy: {
|
|
402
|
+
default: {
|
|
403
|
+
maxTimeToFirstByteMs: 1e4,
|
|
404
|
+
maxLoadTimeMs: 2e4,
|
|
405
|
+
timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 },
|
|
406
|
+
errorRetry: {
|
|
407
|
+
maxNumRetry: 5,
|
|
408
|
+
retryDelayMs: 500,
|
|
409
|
+
maxRetryDelayMs: 2e3,
|
|
410
|
+
shouldRetry: (cfg, retryCount, _isTimeout, res, retry) => retryWarmupNotFound(cfg, retryCount, res, retry)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
397
415
|
this.hls = hls;
|
|
398
416
|
hls.on(Hls.Events.ERROR, (_evt, data) => {
|
|
399
417
|
if (data.fatal) this.bufferingCb?.();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/events.ts","../src/internal/view-target.ts","../src/internal/webrtc-util.ts","../src/internal/publish-transport.ts","../src/internal/autoplay.ts","../src/internal/ll-view-transport.ts","../src/internal/scale-view-transport.ts","../src/internal/balanced-view-transport.ts","../src/internal/transport.ts","../src/internal/telemetry.ts","../src/broadcaster.ts","../src/internal/freeze-clock.ts","../src/player.ts","../src/internal/signaling.ts","../src/internal/token.ts","../src/client.ts","../src/mebius.ts"],"sourcesContent":["/**\n * @mebius-io/web — Mebius Client SDK for the web.\n *\n * Public surface only. Everything under `internal/` is private and is never\n * re-exported here.\n */\nexport { Mebius } from \"./mebius.js\";\nexport { MebiusClient } from \"./client.js\";\nexport { MebiusBroadcaster } from \"./broadcaster.js\";\nexport { MebiusPlayer } from \"./player.js\";\nexport { MebiusError, mebiusError } from \"./errors.js\";\n\nexport type {\n ClientEventMap,\n BroadcasterEventMap,\n PlayerEventMap,\n} from \"./events.js\";\n\nexport type {\n MebiusInitOptions,\n MebiusConnectOptions,\n MebiusDelivery,\n BroadcasterOptions,\n PlayerOptions,\n PlaybackMode,\n ViewTarget,\n MediaConstraint,\n BroadcastStats,\n PlaybackStats,\n MebiusErrorCode,\n} from \"./types.js\";\n","import type { MebiusErrorCode } from \"./types.js\";\n\n/**\n * The single error type the SDK surfaces. Always uses Mebius terminology —\n * never raw transport/protocol wording.\n *\n * Inspect {@link MebiusError.code} to decide how to recover (for example,\n * refresh the token on `\"TOKEN_EXPIRED\"`).\n */\nexport class MebiusError extends Error {\n readonly code: MebiusErrorCode;\n /** The underlying cause, if any. Useful for logging, opaque to the contract. */\n readonly cause?: unknown;\n\n constructor(code: MebiusErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = \"MebiusError\";\n this.code = code;\n this.cause = cause;\n Object.setPrototypeOf(this, MebiusError.prototype);\n }\n}\n\n/** Human-readable default messages, kept in Mebius terms. */\nconst DEFAULT_MESSAGES: Record<MebiusErrorCode, string> = {\n TOKEN_EXPIRED: \"Your Mebius token has expired. Mint a fresh token and reconnect.\",\n PERMISSION_DENIED: \"Camera/microphone permission was denied by the user or browser.\",\n CONNECTION_FAILED: \"Could not establish a connection to the Mebius gateway.\",\n NOT_CONNECTED: \"Not connected to Mebius. Call connect() before using this client.\",\n STREAM_NOT_FOUND: \"The requested stream could not be found on the Mebius gateway.\",\n UNKNOWN: \"An unexpected Mebius error occurred.\",\n};\n\n/** Create a {@link MebiusError}, falling back to a sensible default message. */\nexport function mebiusError(\n code: MebiusErrorCode,\n message?: string,\n cause?: unknown,\n): MebiusError {\n return new MebiusError(code, message ?? DEFAULT_MESSAGES[code], cause);\n}\n","import type { MebiusError } from \"./errors.js\";\nimport type { BroadcastStats, PlaybackStats } from \"./types.js\";\n\n// NOTE: these are `type` aliases (not interfaces) so they satisfy the\n// `Record<string, unknown>` constraint on TypedEmitter — TS only treats object\n// type aliases (not augmentable interfaces) as having an implicit index\n// signature.\n\n/** Event payloads emitted by {@link MebiusClient}. */\nexport type ClientEventMap = {\n connected: void;\n disconnected: { reason?: string };\n error: MebiusError;\n};\n\n/** Event payloads emitted by a broadcaster. */\nexport type BroadcasterEventMap = {\n started: { streamId: string };\n stopped: void;\n stats: BroadcastStats;\n};\n\n/** Event payloads emitted by a player. */\nexport type PlayerEventMap = {\n playing: { streamId: string };\n buffering: void;\n ended: void;\n stats: PlaybackStats;\n};\n\ntype Listener<T> = (payload: T) => void;\n\n/**\n * A tiny strongly-typed event emitter. `EventMap` maps each event name to its\n * payload type, so `on(\"started\", cb)` infers `cb`'s argument automatically.\n */\nexport class TypedEmitter<EventMap extends Record<string, unknown>> {\n private readonly listeners = new Map<keyof EventMap, Set<Listener<unknown>>>();\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof EventMap>(event: K, cb: Listener<EventMap[K]>): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(cb as Listener<unknown>);\n return () => this.off(event, cb);\n }\n\n /** Unsubscribe a previously-registered listener. */\n off<K extends keyof EventMap>(event: K, cb: Listener<EventMap[K]>): void {\n this.listeners.get(event)?.delete(cb as Listener<unknown>);\n }\n\n /** Emit an event to all listeners. Internal use. */\n protected emit<K extends keyof EventMap>(event: K, payload: EventMap[K]): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const cb of [...set]) (cb as Listener<EventMap[K]>)(payload);\n }\n\n /** Remove every listener. Internal use during teardown. */\n protected removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","/** INTERNAL — resolve a public {@link ViewTarget} to a real `<video>` element. */\nimport { mebiusError } from \"../errors.js\";\nimport type { ViewTarget } from \"../types.js\";\n\nexport function resolveVideoElement(target: ViewTarget): HTMLVideoElement {\n if (typeof target !== \"string\") {\n if (target instanceof HTMLVideoElement) return target;\n throw mebiusError(\"UNKNOWN\", \"View target must be a <video> element or a CSS selector.\");\n }\n const el = document.querySelector(target);\n if (!el) {\n throw mebiusError(\"UNKNOWN\", `No element matches the selector \"${target}\".`);\n }\n if (!(el instanceof HTMLVideoElement)) {\n throw mebiusError(\"UNKNOWN\", `Selector \"${target}\" did not resolve to a <video> element.`);\n }\n return el;\n}\n","/** INTERNAL — small WebRTC helpers shared by publish/view transports. */\n\n/**\n * Wait until ICE gathering completes (or a short timeout elapses) so the SDP\n * we send already contains candidates. Keeps the gateway exchange to a single\n * round-trip.\n */\nexport function waitForIceGathering(pc: RTCPeerConnection, timeoutMs = 2000): Promise<void> {\n if (pc.iceGatheringState === \"complete\") return Promise.resolve();\n return new Promise((resolve) => {\n const done = () => {\n pc.removeEventListener(\"icegatheringstatechange\", check);\n clearTimeout(timer);\n resolve();\n };\n const check = () => {\n if (pc.iceGatheringState === \"complete\") done();\n };\n const timer = setTimeout(done, timeoutMs);\n pc.addEventListener(\"icegatheringstatechange\", check);\n });\n}\n\n/** Default ICE configuration. The gateway may also relay; STUN aids direct paths. */\nexport const DEFAULT_RTC_CONFIG: RTCConfiguration = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n};\n","/**\n * INTERNAL — publish transport (WHIP over the gateway).\n *\n * Sends a locally-captured MediaStream to the Mebius gateway via a standard\n * WHIP offer/answer exchange. Hidden from the public API.\n */\nimport type { BroadcastStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { PublishTransport } from \"./transport.js\";\nimport { DEFAULT_RTC_CONFIG, waitForIceGathering } from \"./webrtc-util.js\";\n\n/**\n * Offer H264 ahead of everything else for the outgoing video track.\n *\n * Chrome negotiates VP8 by default, and VP8 is a dead end for every viewer who\n * is not on WebRTC: the server's HLS/FLV muxers cannot carry it, so they drop\n * the video track and publish an audio-only stream (\"skipping track (VP8)\").\n * The broadcast looks perfect to the publisher and has no picture for anyone\n * watching over HLS, FLV, or the CDN.\n *\n * H264 is what every one of those paths speaks, and negotiating it here means\n * the server never has to transcode — which would cost far more latency than\n * anything else in this SDK.\n *\n * Best-effort by design: `setCodecPreferences` is unavailable on older Safari,\n * and a browser without an H264 encoder has nothing to reorder. Both cases fall\n * through to the default negotiation rather than failing the broadcast.\n */\nfunction preferH264(pc: RTCPeerConnection): void {\n const caps = RTCRtpSender.getCapabilities?.(\"video\");\n if (!caps?.codecs) return;\n const h264 = caps.codecs.filter((c) => c.mimeType.toLowerCase() === \"video/h264\");\n if (h264.length === 0) return;\n const rest = caps.codecs.filter((c) => c.mimeType.toLowerCase() !== \"video/h264\");\n for (const tr of pc.getTransceivers()) {\n if (tr.sender.track?.kind !== \"video\") continue;\n try {\n tr.setCodecPreferences?.([...h264, ...rest]);\n } catch {\n // A browser that rejects the list negotiates its own way; still better\n // than no video for CDN viewers on the browsers that accept it.\n }\n }\n}\n\nexport class WhipPublishTransport implements PublishTransport {\n private pc: RTCPeerConnection | null = null;\n private resourceUrl: string | null = null;\n\n constructor(private readonly signaling: SignalingClient) {}\n\n async start(streamId: string, stream: MediaStream): Promise<void> {\n const pc = new RTCPeerConnection(DEFAULT_RTC_CONFIG);\n this.pc = pc;\n\n for (const track of stream.getTracks()) {\n pc.addTrack(track, stream);\n }\n preferH264(pc);\n\n const offer = await pc.createOffer();\n await pc.setLocalDescription(offer);\n await waitForIceGathering(pc);\n\n const localSdp = pc.localDescription?.sdp;\n if (!localSdp) throw mebiusError(\"CONNECTION_FAILED\", \"Failed to create a local session.\");\n\n const { answer, resourceUrl } = await this.signaling.exchangeSession(\n \"publish\",\n streamId,\n localSdp,\n );\n this.resourceUrl = resourceUrl;\n await pc.setRemoteDescription({ type: \"answer\", sdp: answer });\n }\n\n async replaceVideoTrack(track: MediaStreamTrack | null): Promise<void> {\n const sender = this.pc?.getSenders().find((s) => s.track?.kind === \"video\");\n if (sender) await sender.replaceTrack(track);\n }\n\n async stop(): Promise<void> {\n await this.signaling.deleteResource(this.resourceUrl);\n this.resourceUrl = null;\n this.pc?.getSenders().forEach((s) => s.track?.stop());\n this.pc?.close();\n this.pc = null;\n }\n\n /** Bytes sent and packet counters at the previous getStats() call. */\n private lastOutbound: { bytes: number; atMs: number } | null = null;\n\n /**\n * Live broadcast statistics.\n *\n * Two corrections over the obvious reading of RTCStats:\n *\n * `bitrateKbps` is the delta of `outbound-rtp.bytesSent`, not\n * `availableOutgoingBitrate`. The latter is the congestion controller's\n * ESTIMATE of headroom, so a broadcaster on a fast link reported several\n * megabits while actually sending a fraction of that — the dashboard's\n * \"bitrate adherence\" score was measuring the network, not the encoder.\n *\n * `packetLossPct` comes from the receiver's report (`remote-inbound-rtp`),\n * which is the only place that knows what did not arrive. It was never\n * reported at all, and publishQualityScore treats a missing value as zero\n * loss — so every publisher scored full marks on a fifth of the rubric no\n * matter how bad the uplink was.\n */\n async getStats(): Promise<BroadcastStats | null> {\n if (!this.pc) return null;\n const report = await this.pc.getStats();\n\n let framesPerSecond: number | undefined;\n let rttMs: number | undefined;\n let packetLossPct: number | undefined;\n let bytesSent: number | undefined;\n let packetsSent: number | undefined;\n let packetsLost: number | undefined;\n\n report.forEach((stat) => {\n if (stat.type === \"outbound-rtp\" && !stat.isRemote) {\n if (typeof stat.framesPerSecond === \"number\") framesPerSecond = stat.framesPerSecond;\n if (typeof stat.bytesSent === \"number\") bytesSent = (bytesSent ?? 0) + stat.bytesSent;\n if (typeof stat.packetsSent === \"number\") packetsSent = (packetsSent ?? 0) + stat.packetsSent;\n }\n if (stat.type === \"remote-inbound-rtp\") {\n if (typeof stat.packetsLost === \"number\") packetsLost = (packetsLost ?? 0) + stat.packetsLost;\n // The receiver's RTT is more accurate than the candidate pair's when\n // both are present, so it wins; the candidate pair fills in below.\n if (typeof stat.roundTripTime === \"number\") rttMs = Math.round(stat.roundTripTime * 1000);\n }\n if (stat.type === \"candidate-pair\" && stat.state === \"succeeded\") {\n if (rttMs == null && typeof stat.currentRoundTripTime === \"number\") {\n rttMs = Math.round(stat.currentRoundTripTime * 1000);\n }\n }\n });\n\n let bitrateKbps: number | undefined;\n const atMs = Date.now();\n if (bytesSent != null) {\n const prev = this.lastOutbound;\n if (prev && atMs > prev.atMs && bytesSent >= prev.bytes) {\n bitrateKbps = Math.round(((bytesSent - prev.bytes) * 8) / 1000 / ((atMs - prev.atMs) / 1000));\n }\n this.lastOutbound = { bytes: bytesSent, atMs };\n }\n\n if (packetsLost != null && packetsSent != null && packetsSent > 0) {\n packetLossPct = Math.max(0, Math.min(100, (packetsLost / packetsSent) * 100));\n }\n\n return { bitrateKbps, framesPerSecond, rttMs, packetLossPct };\n }\n}\n","/**\n * INTERNAL — getting a `<video>` element to actually render.\n *\n * Every transport used to call `video.play().catch(() => undefined)`. That hides\n * the single most common playback failure in a browser: Chrome and Safari refuse\n * to start playback WITH AUDIO unless the page has sticky user activation. The\n * promise rejects with NotAllowedError, the element stays on frame zero, and the\n * SDK reports nothing — so a stream that is serving perfectly looks dead, the\n * first-frame watchdog times out on every route in turn, and the whole thing\n * surfaces as \"no fallback, black video\" even though the fallback ran.\n *\n * A click is not enough on its own: activation is consumed and expires, and a\n * play() that happens after `await fetchToken()` and `await import(\"hls.js\")` is\n * often too late.\n *\n * Muted playback is always allowed. So: try with sound, and if the policy\n * refuses, mute and try again — a muted picture the viewer can unmute beats a\n * black rectangle. The caller learns which happened instead of nothing at all.\n */\n\nexport interface PlayOutcome {\n /** True when playback started only because we muted the element. */\n mutedByPolicy: boolean;\n}\n\n/** Is this the browser's autoplay policy talking, rather than a real failure? */\nfunction isAutoplayBlocked(e: unknown): boolean {\n // Chrome/Safari/Firefox all use NotAllowedError here. The name check is the\n // portable signal; message text is not.\n return typeof e === \"object\" && e !== null && (e as { name?: string }).name === \"NotAllowedError\";\n}\n\n/**\n * Start playback, retrying muted if the autoplay policy blocks sound.\n *\n * Rethrows anything that is NOT a policy refusal: an unsupported source or a\n * decode failure is exactly what the player's route-fallback exists for, and\n * swallowing it made a dead route indistinguishable from a slow one.\n */\nexport async function playWithAutoplayFallback(video: HTMLVideoElement): Promise<PlayOutcome> {\n // Set both here rather than relying on the app's markup: inline playback on\n // iOS is refused outright without playsInline, whatever the policy says.\n video.playsInline = true;\n try {\n await video.play();\n return { mutedByPolicy: false };\n } catch (e) {\n if (!isAutoplayBlocked(e) || video.muted) throw e;\n video.muted = true;\n await video.play();\n return { mutedByPolicy: true };\n }\n}\n\n/**\n * Return a video element to a clean state before a different transport attaches\n * to it.\n *\n * `srcObject` and `src` are not interchangeable slots: while srcObject is set,\n * the element IGNORES src entirely. So a real-time route that attached a\n * MediaStream and then failed would silently block the HLS route behind it from\n * ever showing a frame — the picture stays black while every log says the\n * playlist loaded fine.\n */\nexport function resetVideoElement(video: HTMLVideoElement): void {\n video.srcObject = null;\n video.removeAttribute(\"src\");\n video.load();\n}\n","/**\n * INTERNAL — low-latency view transport (WHEP over the gateway).\n *\n * Pulls a remote stream from the Mebius gateway via a standard WHEP exchange\n * and renders it into a video element. Hidden from the public API.\n */\nimport type { PlaybackStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { ViewTransport } from \"./transport.js\";\nimport { DEFAULT_RTC_CONFIG, waitForIceGathering } from \"./webrtc-util.js\";\nimport { playWithAutoplayFallback, resetVideoElement } from \"./autoplay.js\";\n\nexport class WhepViewTransport implements ViewTransport {\n readonly kind = \"whep\" as const;\n\n private pc: RTCPeerConnection | null = null;\n private resourceUrl: string | null = null;\n private endedCb: (() => void) | null = null;\n private bufferingCb: (() => void) | null = null;\n\n /** Element this route attached a MediaStream to, so stop() can release it. */\n private video: HTMLVideoElement | null = null;\n\n constructor(private readonly signaling: SignalingClient) {}\n\n onEnded(cb: () => void): void {\n this.endedCb = cb;\n }\n\n onBuffering(cb: () => void): void {\n this.bufferingCb = cb;\n }\n\n async start(streamId: string, video: HTMLVideoElement): Promise<void> {\n this.video = video;\n const pc = new RTCPeerConnection(DEFAULT_RTC_CONFIG);\n this.pc = pc;\n const remote = new MediaStream();\n\n pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n pc.addTransceiver(\"audio\", { direction: \"recvonly\" });\n\n pc.ontrack = (ev) => {\n remote.addTrack(ev.track);\n video.srcObject = remote;\n // Muted retry rather than giving up: \"left to the app\" meant a viewer saw a\n // black rectangle and the app was told nothing.\n void playWithAutoplayFallback(video)\n .then((o) => {\n this.mutedByPolicy = o.mutedByPolicy;\n })\n .catch(() => {\n /* a genuine failure here surfaces as no first frame, which the player's\n route watchdog already handles */\n });\n };\n pc.onconnectionstatechange = () => {\n if (pc.connectionState === \"disconnected\" || pc.connectionState === \"failed\") {\n this.bufferingCb?.();\n }\n if (pc.connectionState === \"closed\") this.endedCb?.();\n };\n\n const offer = await pc.createOffer();\n await pc.setLocalDescription(offer);\n await waitForIceGathering(pc);\n\n const localSdp = pc.localDescription?.sdp;\n if (!localSdp) throw mebiusError(\"CONNECTION_FAILED\", \"Failed to create a local session.\");\n\n const { answer, resourceUrl } = await this.signaling.exchangeSession(\n \"view\",\n streamId,\n localSdp,\n );\n this.resourceUrl = resourceUrl;\n await pc.setRemoteDescription({ type: \"answer\", sdp: answer });\n }\n\n async stop(): Promise<void> {\n await this.signaling.deleteResource(this.resourceUrl);\n this.resourceUrl = null;\n this.pc?.close();\n this.pc = null;\n // Release the element's MediaStream. While srcObject is set the element\n // ignores `src`, so leaving it behind stops the next route (HLS/FLV) from\n // ever rendering — a black picture with a perfectly healthy playlist.\n if (this.video) resetVideoElement(this.video);\n this.video = null;\n }\n\n /** True when playback only started because the element had to be muted. */\n mutedByPolicy = false;\n\n async getStats(): Promise<PlaybackStats | null> {\n if (!this.pc) return null;\n const report = await this.pc.getStats();\n let bitrateKbps = 0;\n let framesPerSecond = 0;\n let latencyMs: number | undefined;\n report.forEach((stat) => {\n if (stat.type === \"inbound-rtp\") {\n if (typeof stat.framesPerSecond === \"number\") framesPerSecond = stat.framesPerSecond;\n if (typeof stat.jitter === \"number\") latencyMs = Math.round(stat.jitter * 1000);\n }\n if (stat.type === \"candidate-pair\" && stat.state === \"succeeded\") {\n if (typeof stat.availableIncomingBitrate === \"number\") {\n bitrateKbps = Math.round(stat.availableIncomingBitrate / 1000);\n }\n }\n });\n return { bitrateKbps, framesPerSecond, latencyMs };\n }\n}\n","/**\n * INTERNAL — scale view transport (HLS via hls.js / native).\n *\n * For large-audience playback Mebius delivers an HLS playlist from the\n * gateway. hls.js is loaded lazily; Safari plays the playlist natively.\n * Hidden from the public API.\n */\nimport type { PlaybackStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { ViewTransport } from \"./transport.js\";\nimport { playWithAutoplayFallback } from \"./autoplay.js\";\n\n// Loaded on demand so it never weighs down low-latency-only apps.\ntype HlsModule = typeof import(\"hls.js\");\ntype HlsInstance = import(\"hls.js\").default;\n\nexport class HlsViewTransport implements ViewTransport {\n readonly kind = \"hls\" as const;\n\n private hls: HlsInstance | null = null;\n private video: HTMLVideoElement | null = null;\n private endedCb: (() => void) | null = null;\n private bufferingCb: (() => void) | null = null;\n /** Aborts this attempt's element listeners; see start(). */\n private listeners: AbortController | null = null;\n\n /**\n * deliveryPath, when given, is a gateway-relative path from the gateway's own\n * delivery list — that is how a CDN-backed playlist gets used instead of the\n * origin one. Without it this falls back to the origin playlist, which is\n * still correct, just served from our own bandwidth.\n */\n constructor(\n private readonly signaling: SignalingClient,\n private readonly deliveryPath?: string,\n ) {}\n\n onEnded(cb: () => void): void {\n this.endedCb = cb;\n }\n\n onBuffering(cb: () => void): void {\n this.bufferingCb = cb;\n }\n\n async start(streamId: string, video: HTMLVideoElement): Promise<void> {\n this.video = video;\n const url = this.deliveryPath\n ? this.signaling.deliveryUrl(this.deliveryPath)\n : this.signaling.scalePlaylistUrl(streamId);\n\n // Same reason as the FLV route: every candidate shares one element, so a\n // failed attempt's listeners must not outlive it.\n this.listeners = new AbortController();\n const { signal } = this.listeners;\n video.addEventListener(\"ended\", () => this.endedCb?.(), { signal });\n video.addEventListener(\"waiting\", () => this.bufferingCb?.(), { signal });\n\n // Media Source first, native second — NOT the other way round.\n //\n // The check used to be `if (video.canPlayType(\"application/vnd.apple.mpegurl\"))`,\n // and Chromium answers \"maybe\" to that. \"maybe\" is a legal answer meaning\n // \"I might, ask me again with codecs\", and Chromium says it while being\n // unable to play a playlist at all — so this route assigned video.src and\n // died with \"NotSupportedError: Failed to load because no supported source\n // was found\", on the browser most viewers use. Asking the library whether\n // it can drive this browser is a direct question with a direct answer;\n // canPlayType is neither.\n let Hls: HlsModule[\"default\"] | null = null;\n try {\n Hls = (await import(\"hls.js\")).default;\n } catch {\n // Bundling or network problem. Native is then the only chance, and Safari\n // is exactly the browser where it works.\n Hls = null;\n }\n\n if (!Hls?.isSupported()) {\n if (!video.canPlayType(\"application/vnd.apple.mpegurl\")) {\n throw mebiusError(\"CONNECTION_FAILED\", \"Scale playback is not supported in this browser.\");\n }\n video.src = url;\n this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;\n return;\n }\n\n // No forced lowLatencyMode: it is for LL-HLS playlists (EXT-X-PART), and\n // asserting it against an ordinary playlist makes hls.js wait for parts that\n // never arrive. hls.js turns it on by itself when the playlist advertises it.\n //\n // maxLiveSyncPlaybackRate lets hls.js catch up by playing fast when it has\n // drifted behind the live edge. Default is 1 — no catching up ever, so every\n // stall becomes permanent added latency for the rest of the session.\n //\n // 1.1, not 1.5. Catch-up is a pitch shift on the audio, and 1.5 is a 50%\n // one: viewers hear it as chipmunk speech, which is worse than the latency it\n // buys back. It also fights itself on an LL-HLS playlist, where the target is\n // PART-HOLD-BACK (0.5s here) — any network jitter reads as \"behind\", so the\n // player spends the session alternating between sprinting and starving.\n // 1.1 is inaudible and still recovers a 2s drift in ~20s.\n // Everything else is left at hls.js defaults on purpose: it already reads the\n // server's own HOLD-BACK / PART-HOLD-BACK target from the playlist, and a\n // number guessed here would only override a value the server measured.\n const hls = new Hls({ maxLiveSyncPlaybackRate: 1.1 });\n this.hls = hls;\n hls.on(Hls.Events.ERROR, (_evt, data) => {\n if (data.fatal) this.bufferingCb?.();\n });\n hls.loadSource(url);\n hls.attachMedia(video);\n this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;\n }\n\n /** True when playback only started because the element had to be muted. */\n mutedByPolicy = false;\n\n async stop(): Promise<void> {\n this.listeners?.abort();\n this.listeners = null;\n this.hls?.destroy();\n this.hls = null;\n if (this.video) {\n this.video.removeAttribute(\"src\");\n this.video.load();\n }\n this.video = null;\n }\n\n async getStats(): Promise<PlaybackStats | null> {\n if (!this.video) return null;\n const level = this.hls?.levels?.[this.hls.currentLevel];\n return {\n bitrateKbps: level ? Math.round(level.bitrate / 1000) : 0,\n framesPerSecond: 0,\n };\n }\n}\n","/**\n * INTERNAL — balanced view transport (HTTP-FLV via flv.js).\n *\n * For typical one-to-many web viewers Mebius pulls an HTTP-FLV stream and feeds\n * it through flv.js (Media Source Extensions). Latency is ~1-3s — lower than\n * HLS, higher than the WebRTC pull — and it scales over a CDN edge.\n *\n * History worth keeping: 0.2.0 deleted this file on the reading that the mode\n * was \"unserved\", because the gateway had no route for it. The route simply had\n * not been built yet — production web playback has always been HTTP-FLV. The\n * gateway now serves it, so this comes back, with one change: the URL is no\n * longer derived from a hardcoded path. It comes from the gateway's own\n * delivery list, so the gateway can move or re-point that path without an SDK\n * release.\n *\n * Browser-only: flv.js needs MSE, which iOS Safari lacks — createViewCandidates\n * is what keeps this off platforms that cannot play it. flv.js is a BUNDLED\n * dependency, never a peer dependency: an integrator must never have to type a\n * transport library's name to make Mebius work.\n *\n * Hidden from the public API; the public surface speaks only of the\n * `\"balanced\"` playback mode.\n */\nimport type { PlaybackStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { ViewTransport } from \"./transport.js\";\nimport { playWithAutoplayFallback } from \"./autoplay.js\";\n\ntype FlvModule = typeof import(\"flv.js\");\ntype FlvPlayer = ReturnType<FlvModule[\"default\"][\"createPlayer\"]>;\n\n/**\n * flv.js tuned for live rather than for its VOD defaults.\n *\n * The defaults cost us both things this route exists to provide:\n * - `enableStashBuffer: true` with a 384KB initial stash holds bytes in the IO\n * layer before any of them reach MSE. At a webcam's ~400kbps that is ~8s of\n * nothing on screen — past the player's first-frame watchdog, so a perfectly\n * healthy stream got dropped as \"delivered no video\", and when it did start,\n * the stash was pure added delay.\n * - `lazyLoad: true` aborts the HTTP connection once 3 minutes are buffered.\n * For live that reconnect is a fresh 302 through the gateway and a fresh\n * stall for the viewer, buying nothing.\n * The cleanup pair keeps the SourceBuffer from growing without bound over a long\n * watch; `reuseRedirectedURL` keeps a reconnect on the signed CDN URL we were\n * already handed instead of re-running the gateway redirect.\n */\nconst LIVE_FLV_CONFIG = {\n enableStashBuffer: false,\n stashInitialSize: 128,\n lazyLoad: false,\n autoCleanupSourceBuffer: true,\n autoCleanupMaxBackwardDuration: 30,\n autoCleanupMinBackwardDuration: 10,\n reuseRedirectedURL: true,\n} as const;\n\n/**\n * Seconds behind the newest buffered byte before we skip forward.\n *\n * flv.js does not chase the live edge (that is mpegts.js). Without this, every\n * stall the network hands us is permanent latency: the player resumes where it\n * paused and stays that far behind for the rest of the session, so a viewer who\n * hit two stalls is minutes behind by the end. Bounded at 2s — below that the\n * skip is more visible than the delay it removes.\n */\nconst MAX_DRIFT_S = 2;\n/** Where to land when skipping: short of the edge, or we starve immediately. */\nconst EDGE_MARGIN_S = 0.4;\n\n/**\n * How long the first attempt gets to produce a frame before the audio-less\n * retry. Short on purpose: this path only exists for a stream that is already\n * broken, and every millisecond here is join delay for the viewer.\n */\nconst AUDIO_RETRY_MS = 2500;\n\n/**\n * Resolves true when media has landed but playback still cannot start.\n *\n * Both halves matter. `currentTime === 0` alone is also what a slow first\n * segment looks like, and treating that as the audio-lie case drops audio for\n * the rest of the session on nothing worse than a cold CDN edge — observed\n * once, as a stream that played perfectly and silently. Buffered data with a\n * clock that will not move is the actual signature: the demuxer fed the\n * SourceBuffer and the element is still waiting for a track that never arrives.\n */\nfunction stalledWithData(video: HTMLVideoElement, ms: number): Promise<boolean> {\n if (video.currentTime > 0) return Promise.resolve(false);\n return new Promise((resolve) => {\n const done = (stalled: boolean) => {\n clearTimeout(timer);\n video.removeEventListener(\"timeupdate\", onTime);\n resolve(stalled);\n };\n const onTime = () => {\n if (video.currentTime > 0) done(false);\n };\n const timer = setTimeout(() => done(video.buffered.length > 0 && video.currentTime === 0), ms);\n video.addEventListener(\"timeupdate\", onTime);\n });\n}\n\n/**\n * Skip to the live edge when playback has fallen behind it. Exported for test;\n * a no-op when the gap is small, so it is safe on every `timeupdate`.\n */\nexport function chaseLiveEdge(video: HTMLVideoElement): void {\n const ranges = video.buffered;\n if (ranges.length === 0) return;\n const edge = ranges.end(ranges.length - 1);\n if (edge - video.currentTime <= MAX_DRIFT_S) return;\n video.currentTime = edge - EDGE_MARGIN_S;\n}\n\nexport class FlvViewTransport implements ViewTransport {\n readonly kind = \"flv_js\" as const;\n\n private player: FlvPlayer | null = null;\n private video: HTMLVideoElement | null = null;\n private endedCb: (() => void) | null = null;\n private bufferingCb: (() => void) | null = null;\n /** Aborts this attempt's element listeners; see start(). */\n private listeners: AbortController | null = null;\n\n constructor(\n private readonly signaling: SignalingClient,\n private readonly deliveryPath: string,\n ) {}\n\n onEnded(cb: () => void): void {\n this.endedCb = cb;\n }\n\n onBuffering(cb: () => void): void {\n this.bufferingCb = cb;\n }\n\n async start(_streamId: string, video: HTMLVideoElement): Promise<void> {\n this.video = video;\n const url = this.signaling.deliveryUrl(this.deliveryPath);\n\n // Bound to this attempt's lifetime. The player hands every candidate route\n // the SAME element, so listeners left behind by a route that failed keep\n // firing over the route that succeeded — and an orphaned chaseLiveEdge does\n // not just report, it SEEKS, yanking a healthy HLS playback around on behalf\n // of a dead FLV attempt. stop() aborts them.\n this.listeners = new AbortController();\n const { signal } = this.listeners;\n video.addEventListener(\"ended\", () => this.endedCb?.(), { signal });\n video.addEventListener(\"waiting\", () => this.bufferingCb?.(), { signal });\n video.addEventListener(\"timeupdate\", () => chaseLiveEdge(video), { signal });\n\n let mod: FlvModule;\n try {\n // Literal specifier on purpose: a bundler must be able to see it and inline\n // the library into the single-file drop-in build. A computed specifier\n // leaves a bare module request in the output, which no browser can resolve.\n // Types come from internal/flv.js.d.ts.\n mod = await import(\"flv.js\");\n } catch (cause) {\n throw mebiusError(\"CONNECTION_FAILED\", \"Balanced playback support failed to load.\", cause);\n }\n\n const flvjs = mod.default;\n if (!flvjs.isSupported()) {\n throw mebiusError(\"CONNECTION_FAILED\", \"Balanced playback is not supported in this browser.\");\n }\n\n this.attachPlayer(flvjs, video, url, true);\n // NOT awaited yet: video.play() only settles once playback actually begins,\n // so awaiting it here would hang on exactly the stall the retry below exists\n // to break. The outcome is collected after we know the stream moved.\n const firstPlay = playWithAutoplayFallback(video);\n firstPlay.catch(() => undefined); // the retry path is the error handler\n\n // Second attempt, without the audio track, when the first one never moves.\n //\n // An FLV header can claim audio the stream does not carry — a WebRTC\n // broadcast reaches the CDN as video-only (Opus cannot ride in FLV) and the\n // header still advertises audio. flv.js then holds playback forever waiting\n // for an audio init segment that never comes: metadata parses, the video\n // init segment lands, and currentTime stays at 0.\n //\n // Telling flv.js up front to ignore audio would silence every publisher that\n // DOES send AAC, so it cannot be the default — this only fires once, only\n // when the stream has demonstrably not started, and it is far cheaper than\n // the player's 8s route watchdog for a case that is otherwise unrecoverable.\n if (await stalledWithData(video, AUDIO_RETRY_MS)) {\n this.teardownPlayer();\n this.attachPlayer(flvjs, video, url, false);\n this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;\n return;\n }\n this.mutedByPolicy = (await firstPlay).mutedByPolicy;\n }\n\n private attachPlayer(\n flvjs: FlvModule[\"default\"],\n video: HTMLVideoElement,\n url: string,\n withAudio: boolean,\n ): void {\n const player = flvjs.createPlayer(\n { type: \"flv\", url, isLive: true, ...(withAudio ? {} : { hasAudio: false }) },\n LIVE_FLV_CONFIG,\n );\n this.player = player;\n player.on(flvjs.Events.ERROR ?? \"error\", () => this.bufferingCb?.());\n player.attachMediaElement(video);\n player.load();\n }\n\n private teardownPlayer(): void {\n if (!this.player) return;\n this.player.unload();\n this.player.detachMediaElement();\n this.player.destroy();\n this.player = null;\n }\n\n /** True when playback only started because the element had to be muted. */\n mutedByPolicy = false;\n\n async stop(): Promise<void> {\n this.listeners?.abort();\n this.listeners = null;\n this.teardownPlayer();\n if (this.video) {\n this.video.removeAttribute(\"src\");\n this.video.load();\n }\n this.video = null;\n }\n\n /** Decoded-frame count and timestamp of the previous getStats() call. */\n private lastFrames: { count: number; atMs: number } | null = null;\n\n /**\n * Real playback statistics for this route.\n *\n * Both numbers used to be hardcoded zeros, which is worse than reporting\n * nothing: the dashboard cannot tell a measured 0 kbps from an unmeasured\n * one, so every flv.js viewer in production showed a downlink of 0 and the\n * column read as a total outage. flv.js measures throughput itself\n * (`statisticsInfo.speed`, KB/s), and the element counts decoded frames, so\n * frame rate is the delta between two calls. Anything genuinely unavailable\n * is left undefined rather than zeroed.\n */\n async getStats(): Promise<PlaybackStats | null> {\n if (!this.video) return null;\n\n const speedKBs = this.player?.statisticsInfo?.speed;\n const bitrateKbps = typeof speedKBs === \"number\" ? Math.round(speedKBs * 8) : undefined;\n\n let framesPerSecond: number | undefined;\n const q = this.video.getVideoPlaybackQuality?.();\n const count = q?.totalVideoFrames;\n const atMs = Date.now();\n if (typeof count === \"number\") {\n const prev = this.lastFrames;\n if (prev && atMs > prev.atMs && count >= prev.count) {\n framesPerSecond = Math.round(((count - prev.count) * 1000) / (atMs - prev.atMs));\n }\n this.lastFrames = { count, atMs };\n }\n\n return { bitrateKbps, framesPerSecond, latencyMs: undefined };\n }\n}\n","/**\n * INTERNAL — transport interfaces + auto-selection.\n *\n * The public API never names a transport; it only asks for a playback *mode*.\n * This factory maps a mode to the right hidden delivery mechanism.\n */\nimport type { BroadcastStats, MebiusDelivery, PlaybackMode, PlaybackStats } from \"../types.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport { WhipPublishTransport } from \"./publish-transport.js\";\nimport { WhepViewTransport } from \"./ll-view-transport.js\";\nimport { HlsViewTransport } from \"./scale-view-transport.js\";\nimport { FlvViewTransport } from \"./balanced-view-transport.js\";\n\n/** Hidden transport that sends a captured stream to the gateway. */\nexport interface PublishTransport {\n start(streamId: string, stream: MediaStream): Promise<void>;\n stop(): Promise<void>;\n getStats(): Promise<BroadcastStats | null>;\n /** Swap the outgoing video track in place (e.g. on camera switch). */\n replaceVideoTrack(track: MediaStreamTrack | null): Promise<void>;\n}\n\n/**\n * Wire name of a playback transport, reported with telemetry.\n *\n * The dashboard has a \"player\" column and nothing ever filled it — the SDK sent\n * no such field, so the backend defaulted every viewer session to `flv_js`\n * whatever had actually played it. The route chosen is exactly the sort of\n * thing that explains a bad session, so it is worth one string per batch.\n */\nexport type ViewTransportKind = \"whep\" | \"flv_js\" | \"hls\";\n\n/** Hidden transport that renders a remote stream into a video element. */\nexport interface ViewTransport {\n /** Which mechanism this is, for telemetry. Never shown to the viewer. */\n readonly kind: ViewTransportKind;\n start(streamId: string, video: HTMLVideoElement): Promise<void>;\n stop(): Promise<void>;\n getStats(): Promise<PlaybackStats | null>;\n /** Fired by the transport when playback reaches its natural end. */\n onEnded(cb: () => void): void;\n /** Fired when the transport (re)enters a buffering state. */\n onBuffering(cb: () => void): void;\n}\n\nexport function createPublishTransport(signaling: SignalingClient): PublishTransport {\n return new WhipPublishTransport(signaling);\n}\n\n/**\n * Delivery kinds the gateway may offer. Neutral by design — the gateway names\n * an intent (\"fast\", \"wide\"), never a protocol, and this module is the only\n * place that decides which transport serves which intent. That is what lets the\n * gateway change protocol without an SDK release.\n */\nconst KIND_FAST = \"fast\";\nconst KIND_WIDE = \"wide\";\nconst KIND_LOCAL = \"local\";\n\n/** True when this browser can play a Media-Source-based stream (not iOS Safari). */\nfunction canPlayBuffered(): boolean {\n return typeof MediaSource !== \"undefined\";\n}\n\nfunction transportFor(\n kind: string,\n path: string,\n signaling: SignalingClient,\n): ViewTransport | null {\n if (kind === KIND_FAST) return canPlayBuffered() ? new FlvViewTransport(signaling, path) : null;\n if (kind === KIND_WIDE || kind === KIND_LOCAL) return new HlsViewTransport(signaling, path);\n // An unknown kind is a newer gateway talking to an older SDK. Skip it rather\n // than guess: the list is ordered, so the next entry is the intended fallback.\n return null;\n}\n\n/**\n * Ordered list of transports to try for a playback mode, most-preferred first.\n *\n * Returning a LIST rather than one transport is the whole point: a transport can\n * connect and still deliver no frames (a CDN edge with no ingest yet, MSE\n * failing mid-init, WebRTC reporting `connected` with zero frames). The player\n * walks this list on a watchdog, so a dead first choice degrades instead of\n * showing a black frame.\n *\n * `deliveries` comes from the gateway and is already in the gateway's preferred\n * order; ordering policy therefore lives server-side, not here.\n */\nexport function createViewCandidates(\n mode: PlaybackMode,\n signaling: SignalingClient,\n deliveries: readonly MebiusDelivery[] = [],\n): ViewTransport[] {\n const fromGateway = (kinds: readonly string[]): ViewTransport[] =>\n deliveries\n .filter((d) => kinds.includes(d.kind))\n .map((d) => transportFor(d.kind, d.path, signaling))\n .filter((t): t is ViewTransport => t !== null);\n\n // Every mode ends with the origin playlist, reachable with no delivery list at\n // all. Without this, a caller that never passes `deliveries` (every 0.x\n // integration today) would get an empty candidate list and fail to play.\n const originFallback = new HlsViewTransport(signaling);\n const allKinds = [KIND_FAST, KIND_WIDE, KIND_LOCAL];\n\n switch (mode) {\n case \"low-latency\":\n // The real-time pull is not in `deliveries` — it is signaled, not fetched.\n return [new WhepViewTransport(signaling), ...fromGateway(allKinds), originFallback];\n case \"balanced\":\n return [...fromGateway(allKinds), originFallback];\n case \"scale\":\n return [...fromGateway([KIND_WIDE, KIND_LOCAL]), originFallback];\n case \"auto\":\n // Take the gateway ordering verbatim: it knows which paths are actually\n // serving and what each one costs to serve.\n return [...fromGateway(allKinds), originFallback];\n }\n}\n\n/**\n * Single-transport selection, kept for callers written against 0.1/0.2.\n * @deprecated Use {@link createViewCandidates} — a single transport cannot fall\n * back, so a dead delivery becomes a black frame.\n */\nexport function createViewTransport(\n mode: PlaybackMode,\n signaling: SignalingClient,\n): ViewTransport {\n return createViewCandidates(mode, signaling)[0]!;\n}\n","/**\n * INTERNAL — quality-of-experience reporting.\n *\n * Mebius shows per-stream publish/playback quality (bitrate, fps, rtt, freezes)\n * in the integrator's dashboard, and derives viewer minutes from play-side\n * reports. Both come from here: nothing else in the product observes a viewer's\n * actual experience, because only the client can see it.\n *\n * The credential and the endpoint both come from the token your backend already\n * fetches — the SDK is never configured with them and never learns which tenant\n * it belongs to. The token is scoped by signed claims to one stream and one\n * project, so it can only ever write its own telemetry.\n */\n\n/**\n * SDK identifier reported with each batch.\n *\n * MUST match package.json. It had drifted (0.4.6 while the package shipped\n * 0.4.7), so the dashboard attributed every session to a version that was not\n * the one running — the first thing you check when a release regresses.\n */\nconst SDK_VERSION = \"web/0.4.8\";\n\n/** How often a batch is sent. Long enough to batch, short enough to survive a tab close. */\nconst FLUSH_INTERVAL_MS = 15_000;\n\n/**\n * Server cap on samples per request (beaconSchema: max 64). Flushing at this\n * point rather than growing without bound means a long broadcast on a throttled\n * network drops nothing to a 400.\n */\nconst MAX_BATCH = 64;\n\nexport interface QoeSample {\n /** Unix seconds. The server keys samples on this. */\n ts: number;\n bitrateKbps?: number;\n fps?: number;\n rttMs?: number;\n packetLossPct?: number;\n freezeMs?: number;\n firstFrameMs?: number;\n}\n\nexport interface TelemetryTarget {\n /** Absolute beacon URL, as returned with the token. */\n url: string;\n /** Beacon credential, as returned with the token. */\n token: string;\n}\n\n/** Best-effort environment description. Absent fields are simply not reported. */\nfunction describeDevice(): { os?: string; sdk: string } {\n const nav = typeof navigator === \"undefined\" ? undefined : navigator;\n // navigator.platform is deprecated but still the only universally available\n // hint; userAgentData exists on Chromium only. Neither is load-bearing — the\n // dashboard shows it as context, so a missing value costs nothing.\n const uaData = (nav as { userAgentData?: { platform?: string } } | undefined)?.userAgentData;\n return { os: uaData?.platform || nav?.platform || undefined, sdk: SDK_VERSION };\n}\n\nfunction describeNetwork(): { type?: string } | undefined {\n const conn = (\n typeof navigator === \"undefined\"\n ? undefined\n : (navigator as { connection?: { effectiveType?: string } }).connection\n );\n return conn?.effectiveType ? { type: conn.effectiveType } : undefined;\n}\n\n/**\n * Collects samples for one session and ships them in batches.\n *\n * Every failure path is silent by design: telemetry must never break playback or\n * a broadcast. A rejected batch is dropped rather than retried — the next batch\n * is 15s away and carries the same picture of stream health, so retrying would\n * only pile up requests against an endpoint that is already unhappy.\n */\nexport class QoeReporter {\n private readonly sessionId = randomId();\n private readonly buffer: QoeSample[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private unloadHandler: (() => void) | null = null;\n\n constructor(\n private readonly target: TelemetryTarget,\n private readonly role: \"pub\" | \"play\",\n private readonly streamId: string,\n private readonly userId?: string,\n /**\n * Which transport is playing. Reported so the dashboard's player column\n * reflects the route actually serving the viewer — the field was never\n * sent before, and the server filled the gap by assuming flv.js for\n * everyone.\n */\n private readonly playerKind?: string,\n ) {}\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => void this.flush(), FLUSH_INTERVAL_MS);\n // A viewer closing the tab is the normal end of a session, not an edge case:\n // without this the last interval of every session — and the watch time it\n // represents — is simply lost.\n if (typeof window !== \"undefined\") {\n this.unloadHandler = () => void this.flush(true);\n window.addEventListener(\"pagehide\", this.unloadHandler);\n }\n }\n\n add(sample: QoeSample): void {\n this.buffer.push(sample);\n if (this.buffer.length >= MAX_BATCH) void this.flush();\n }\n\n async stop(): Promise<void> {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n if (this.unloadHandler && typeof window !== \"undefined\") {\n window.removeEventListener(\"pagehide\", this.unloadHandler);\n }\n this.unloadHandler = null;\n await this.flush();\n }\n\n /** Send and clear the buffer. `beacon` uses sendBeacon, for page-unload flushes. */\n async flush(beacon = false): Promise<void> {\n if (!this.buffer.length) return;\n const samples = this.buffer.splice(0, MAX_BATCH);\n const body = JSON.stringify({\n sessionId: this.sessionId,\n streamId: this.streamId,\n role: this.role,\n userId: this.userId,\n playerKind: this.playerKind,\n samples,\n device: describeDevice(),\n network: describeNetwork(),\n });\n\n // sendBeacon cannot carry an Authorization header, so the credential travels\n // as a query parameter on the unload path only. Same token, same scope — the\n // server accepts either, and losing the final batch of every session was the\n // alternative.\n if (beacon && typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n const url = `${this.target.url}${this.target.url.includes(\"?\") ? \"&\" : \"?\"}token=${encodeURIComponent(this.target.token)}`;\n try {\n // text/plain, not application/json: application/json makes this a\n // non-simple request, so the browser must clear a CORS preflight first —\n // during unload, when it frequently never completes and the beacon is\n // dropped without a trace. text/plain is on the safelist and goes\n // straight out. The server reads the body as JSON either way; content\n // type is not what it parses on.\n navigator.sendBeacon(url, new Blob([body], { type: \"text/plain\" }));\n } catch {\n /* nothing to do at unload */\n }\n return;\n }\n\n try {\n await fetch(this.target.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${this.target.token}` },\n body,\n keepalive: true,\n });\n } catch {\n /* telemetry never breaks the stream */\n }\n }\n}\n\nfunction randomId(): string {\n const c = typeof crypto === \"undefined\" ? undefined : crypto;\n if (c?.randomUUID) return c.randomUUID();\n // Older Safari/WebView: a collision only merges two sessions' samples, so a\n // cheap fallback beats refusing to report at all.\n return `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n","import { mebiusError } from \"./errors.js\";\nimport { TypedEmitter, type BroadcasterEventMap } from \"./events.js\";\nimport { resolveVideoElement } from \"./internal/view-target.js\";\nimport type { SignalingClient } from \"./internal/signaling.js\";\nimport { createPublishTransport, type PublishTransport } from \"./internal/transport.js\";\nimport { QoeReporter, type TelemetryTarget } from \"./internal/telemetry.js\";\nimport type { BroadcasterOptions, MediaConstraint, ViewTarget } from \"./types.js\";\n\nconst STATS_INTERVAL_MS = 2000;\n\n/**\n * Publishes the local camera/microphone to a Mebius stream.\n *\n * Create one with {@link MebiusClient.createBroadcaster}, then\n * {@link MebiusBroadcaster.start | start} it with a stream id.\n */\nexport class MebiusBroadcaster extends TypedEmitter<BroadcasterEventMap> {\n private readonly transport: PublishTransport;\n private stream: MediaStream | null = null;\n private facingMode: \"user\" | \"environment\" = \"user\";\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n private started = false;\n private reporter: QoeReporter | null = null;\n\n /** @internal */\n constructor(\n signaling: SignalingClient,\n private readonly options: BroadcasterOptions,\n private readonly telemetry: TelemetryTarget | null = null,\n private readonly userId?: string,\n ) {\n super();\n this.transport = createPublishTransport(signaling);\n }\n\n /** Begin broadcasting under the given stream id. */\n async start(streamId: string): Promise<void> {\n if (this.started) return;\n this.stream = await this.capture();\n await this.transport.start(streamId, this.stream);\n this.started = true;\n // Report against the id the caller published under, so a sample can be traced\n // back to the stream row the dashboard shows.\n if (this.telemetry) {\n this.reporter = new QoeReporter(this.telemetry, \"pub\", streamId, this.userId);\n this.reporter.start();\n }\n this.startStats();\n this.emit(\"started\", { streamId });\n }\n\n /** Stop broadcasting and release the camera/microphone. */\n async stop(): Promise<void> {\n this.stopStats();\n // Flush before tearing down the transport: the last interval of a broadcast is\n // the one most likely to explain why it ended.\n await this.reporter?.stop();\n this.reporter = null;\n await this.transport.stop();\n this.stream?.getTracks().forEach((t) => t.stop());\n this.stream = null;\n this.started = false;\n this.emit(\"stopped\", undefined);\n }\n\n /** Flip between front and back camera (where available). */\n async switchCamera(): Promise<void> {\n if (!this.stream) return;\n this.facingMode = this.facingMode === \"user\" ? \"environment\" : \"user\";\n const next = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: this.facingMode },\n audio: false,\n });\n const newTrack = next.getVideoTracks()[0] ?? null;\n const oldTrack = this.stream.getVideoTracks()[0];\n if (oldTrack) {\n this.stream.removeTrack(oldTrack);\n oldTrack.stop();\n }\n if (newTrack) this.stream.addTrack(newTrack);\n await this.transport.replaceVideoTrack(newTrack);\n }\n\n /** Mute or unmute the outgoing microphone. */\n setMicEnabled(enabled: boolean): void {\n this.stream?.getAudioTracks().forEach((t) => (t.enabled = enabled));\n }\n\n /** Enable or disable the outgoing camera. */\n setCameraEnabled(enabled: boolean): void {\n this.stream?.getVideoTracks().forEach((t) => (t.enabled = enabled));\n }\n\n /**\n * Web convenience: render the local camera preview into a `<video>` element.\n * This is the web analog of the mobile preview view; it does not affect what\n * is broadcast.\n */\n attachPreview(target: ViewTarget): void {\n if (!this.stream) return;\n const video = resolveVideoElement(target);\n video.srcObject = this.stream;\n video.muted = true;\n void video.play().catch(() => undefined);\n }\n\n private async capture(): Promise<MediaStream> {\n const video = normalize(this.options.video, true);\n const audio = normalize(this.options.audio, true);\n try {\n return await navigator.mediaDevices.getUserMedia({ video, audio });\n } catch (cause) {\n throw mebiusError(\"PERMISSION_DENIED\", undefined, cause);\n }\n }\n\n private startStats(): void {\n this.statsTimer = setInterval(async () => {\n const stats = await this.transport.getStats();\n if (!stats) return;\n this.emit(\"stats\", stats);\n this.reporter?.add({\n ts: Math.floor(Date.now() / 1000),\n bitrateKbps: stats.bitrateKbps,\n fps: stats.framesPerSecond,\n rttMs: stats.rttMs,\n packetLossPct: stats.packetLossPct,\n });\n }, STATS_INTERVAL_MS);\n }\n\n private stopStats(): void {\n if (this.statsTimer) clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n}\n\nfunction normalize(c: MediaConstraint | undefined, fallback: boolean): boolean | MediaTrackConstraints {\n if (c === undefined) return fallback;\n return c;\n}\n","/**\n * INTERNAL — freeze-time accounting for playback telemetry.\n *\n * The dashboard's freeze ratio read 0.00% for every session ever recorded: the\n * player knew when playback stalled and when it resumed, and never subtracted\n * the two. A measured zero and an unmeasured zero look identical downstream, so\n * this is the difference between \"playback was flawless\" and \"nobody looked\".\n *\n * Split out of MebiusPlayer so the arithmetic — which is all edge cases — can\n * be tested without a DOM or a live stream.\n */\nexport class FreezeClock {\n /** When the current stall began, or null when playback is running. */\n private stalledSinceMs: number | null = null;\n /** Stall time that has ended but has not yet been shipped with a sample. */\n private pendingMs = 0;\n\n constructor(private readonly now: () => number = Date.now) {}\n\n /** True while a stall is in progress. */\n get stalled(): boolean {\n return this.stalledSinceMs !== null;\n }\n\n /**\n * Begin a stall. Re-entering while already stalled is ignored rather than\n * restarting the clock: flv.js fires `waiting` repeatedly through a single\n * long stall, and resetting the start on each would report a fraction of the\n * freeze that actually happened.\n */\n beginStall(): void {\n if (this.stalledSinceMs === null) this.stalledSinceMs = this.now();\n }\n\n /** End the current stall and bank its duration. No-op when not stalled. */\n endStall(): void {\n if (this.stalledSinceMs === null) return;\n this.pendingMs += Math.max(0, this.now() - this.stalledSinceMs);\n this.stalledSinceMs = null;\n }\n\n /**\n * Freeze milliseconds to report on this tick, resetting the counter.\n *\n * A stall still in progress is counted up to now and its clock restarted, so\n * a freeze longer than the sample interval is reported while it is happening\n * rather than landing whole in whichever sample eventually follows it. Every\n * millisecond is attributed exactly once — never dropped, never double-counted.\n */\n take(): number {\n if (this.stalledSinceMs !== null) {\n const now = this.now();\n this.pendingMs += Math.max(0, now - this.stalledSinceMs);\n this.stalledSinceMs = now;\n }\n const ms = this.pendingMs;\n this.pendingMs = 0;\n return ms;\n }\n\n /** Forget everything. Called when a session ends. */\n reset(): void {\n this.stalledSinceMs = null;\n this.pendingMs = 0;\n }\n}\n","import { TypedEmitter, type PlayerEventMap } from \"./events.js\";\nimport { mebiusError } from \"./errors.js\";\nimport { resolveVideoElement } from \"./internal/view-target.js\";\nimport type { SignalingClient } from \"./internal/signaling.js\";\nimport { createViewCandidates, type ViewTransport } from \"./internal/transport.js\";\nimport { QoeReporter, type TelemetryTarget } from \"./internal/telemetry.js\";\nimport { resetVideoElement } from \"./internal/autoplay.js\";\nimport { FreezeClock } from \"./internal/freeze-clock.js\";\nimport type { MebiusDelivery, PlayerOptions, ViewTarget } from \"./types.js\";\n\nconst STATS_INTERVAL_MS = 2000;\n\n/**\n * How long a route gets to produce its first frame before we move to the next.\n *\n * Not arbitrary: a route can report a healthy connection and still deliver\n * nothing — a CDN edge that has no ingest yet answers 200 with an empty stream,\n * and a real-time connection reports `connected` while zero frames arrive. The\n * only trustworthy signal is the picture actually advancing, so that is what is\n * measured. 8s is long enough to survive a slow first segment on mobile data and\n * short enough that a viewer has not yet left.\n */\nconst FIRST_FRAME_TIMEOUT_MS = 8000;\n\n/**\n * Which player currently drives a given element.\n *\n * A second player on the same element is an ordinary thing for an app to do —\n * a \"play\" button pressed twice, a component remounting — and it used to\n * orphan the first one. The new player resets the element, which detaches the\n * old MediaSource and removes its SourceBuffers, but the old player is still\n * running: its buffered-media library keeps polling buffers that no longer\n * belong to anything and floods the console with\n *\n * InvalidStateError: Failed to read the 'buffered' property from\n * 'SourceBuffer': This SourceBuffer has been removed from the parent media\n * source.\n *\n * The element can only have one owner, so taking ownership retires the\n * previous one. WeakMap because an element that goes out of scope must not be\n * kept alive by this bookkeeping.\n */\nconst ELEMENT_OWNER = new WeakMap<HTMLVideoElement, MebiusPlayer>();\n\n/**\n * Plays a Mebius stream into a `<video>` element.\n *\n * Create one with {@link MebiusClient.createPlayer}, optionally choosing a\n * playback {@link PlaybackMode | mode}; Mebius selects the delivery route, and\n * moves to the next one by itself if the current one stops producing frames.\n */\nexport class MebiusPlayer extends TypedEmitter<PlayerEventMap> {\n private readonly candidates: ViewTransport[];\n private transport: ViewTransport | null = null;\n private video: HTMLVideoElement | null = null;\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n private playing = false;\n private reporter: QoeReporter | null = null;\n /** True between a `buffering` event and the element actually resuming. */\n private stalled = false;\n /** Measures how long playback was actually frozen; see FreezeClock. */\n private readonly freeze = new FreezeClock();\n /** Cancels element listeners bound for the lifetime of one play(). */\n private elementListeners: AbortController | null = null;\n\n /** @internal */\n constructor(\n signaling: SignalingClient,\n options: PlayerOptions = {},\n deliveries: readonly MebiusDelivery[] = [],\n private readonly telemetry: TelemetryTarget | null = null,\n private readonly userId?: string,\n ) {\n super();\n this.candidates = createViewCandidates(options.mode ?? \"auto\", signaling, deliveries);\n }\n\n /** Start playing `streamId` into the given video element or selector. */\n async play(streamId: string, viewTarget: ViewTarget): Promise<void> {\n if (this.playing) return;\n const video = resolveVideoElement(viewTarget);\n // One element, one player. Whatever was driving it is finished, and stopping\n // it here is what keeps a second play() from leaving a live transport\n // attached to a MediaSource this one is about to replace.\n const previous = ELEMENT_OWNER.get(video);\n if (previous && previous !== this) await previous.stop();\n ELEMENT_OWNER.set(video, this);\n this.video = video;\n\n // `buffering` had no counterpart: an app that showed a spinner on it had\n // nothing to hide the spinner on, so a single mid-stream stall left the UI\n // reading \"buffering\" over perfectly smooth video for the rest of the\n // session. The element knows when it resumed; re-emitting `playing` there\n // gives every consumer the other half of the pair without inventing an\n // event they would have to know to handle.\n this.elementListeners = new AbortController();\n video.addEventListener(\n \"playing\",\n () => {\n if (!this.stalled || !this.playing) return;\n this.stalled = false;\n this.freeze.endStall();\n this.emit(\"playing\", { streamId });\n },\n { signal: this.elementListeners.signal },\n );\n\n // Measured across route attempts, not from the accepted route: a viewer who\n // waited through a dead edge waited, and reporting only the winning route's\n // time would hide exactly the delay worth knowing about.\n const startedAtMs = Date.now();\n let lastError: unknown = null;\n for (const candidate of this.candidates) {\n try {\n // Hand every route a clean element. Routes attach differently — a\n // MediaStream via srcObject, a playlist via src, MSE via attachMedia —\n // and while srcObject is set the element ignores src entirely. Without\n // this, one failed real-time attempt kept every later route black while\n // its own logs looked healthy.\n resetVideoElement(video);\n this.attach(candidate);\n await candidate.start(streamId, video);\n // start() resolving only means the route was opened, not that it is\n // delivering. Confirm with the picture itself before accepting it.\n if (await hasFirstFrame(video)) {\n this.transport = candidate;\n this.playing = true;\n if (this.telemetry) {\n this.reporter = new QoeReporter(\n this.telemetry,\n \"play\",\n streamId,\n this.userId,\n candidate.kind,\n );\n this.reporter.start();\n // One sample at join time carries the join delay. Viewer minutes are\n // derived from the span between a session's first and last sample, so\n // a viewer who leaves before the first stats tick still counts.\n this.reporter.add({ ts: Math.floor(Date.now() / 1000), firstFrameMs: Date.now() - startedAtMs });\n }\n this.startStats();\n this.emit(\"playing\", { streamId });\n return;\n }\n lastError = mebiusError(\"CONNECTION_FAILED\", \"A Mebius route delivered no video.\");\n } catch (cause) {\n lastError = cause;\n }\n // Tear the dead route down before opening the next one: leaving it attached\n // keeps a peer connection or a media source bound to the same element, and\n // the next route then renders into a element that is not free.\n await candidate.stop().catch(() => undefined);\n }\n\n // Release the element before giving up. Holding ownership of an element we\n // are not playing into would make the next player await a stop() on this\n // dead one, and would keep this player object alive through the map for as\n // long as the element exists.\n this.elementListeners?.abort();\n this.elementListeners = null;\n if (ELEMENT_OWNER.get(video) === this) ELEMENT_OWNER.delete(video);\n this.video = null;\n throw lastError ?? mebiusError(\"CONNECTION_FAILED\", \"No Mebius route could play this stream.\");\n }\n\n /** Stop playback and detach from the video element. */\n async stop(): Promise<void> {\n this.elementListeners?.abort();\n this.elementListeners = null;\n if (this.video && ELEMENT_OWNER.get(this.video) === this) {\n ELEMENT_OWNER.delete(this.video);\n }\n this.stalled = false;\n this.freeze.reset();\n this.stopStats();\n await this.reporter?.stop();\n this.reporter = null;\n await this.transport?.stop();\n this.transport = null;\n this.video = null;\n this.playing = false;\n }\n\n /**\n * Set output volume in the range 0..1.\n *\n * Any volume above zero also unmutes. Playback often starts muted — the\n * element may carry `muted` in the app's own markup, and the SDK itself mutes\n * and retries when the browser refuses to autoplay with sound — and\n * `video.volume` has no audible effect while `muted` is set. Setting volume\n * without clearing it meant an app whose only audio control was a slider\n * could never produce sound: the value moved, the stream stayed silent, and\n * nothing reported a problem.\n *\n * Volume 0 mutes rather than merely turning the level down, so a UI that\n * drags to zero also survives a later unmute at the element level.\n */\n setVolume(volume: number): void {\n const v = Math.min(1, Math.max(0, volume));\n if (!this.video) return;\n this.video.volume = v;\n this.video.muted = v === 0;\n }\n\n private attach(transport: ViewTransport): void {\n transport.onEnded(() => {\n // Only the route currently serving may end playback. A route we already\n // abandoned firing late must not close a stream that is playing fine.\n if (this.transport !== transport) return;\n this.playing = false;\n this.stopStats();\n // A stream ending is a session ending: flush now or the watch time since the\n // last batch is never counted.\n void this.reporter?.stop();\n this.reporter = null;\n this.emit(\"ended\", undefined);\n });\n transport.onBuffering(() => {\n if (this.transport !== transport) return;\n // Re-entering `buffering` while already stalled must not restart the\n // clock: flv.js fires `waiting` repeatedly through one long stall, and\n // resetting the start each time would report a fraction of the freeze.\n this.freeze.beginStall();\n this.stalled = true;\n this.emit(\"buffering\", undefined);\n });\n }\n\n private startStats(): void {\n this.statsTimer = setInterval(async () => {\n const stats = await this.transport?.getStats();\n // Freeze time is reported even when the transport has no stats to give:\n // a route too stalled to produce statistics is precisely the one whose\n // freezes matter most.\n const freezeMs = this.freeze.take();\n if (!stats) {\n if (freezeMs > 0) this.reporter?.add({ ts: Math.floor(Date.now() / 1000), freezeMs });\n return;\n }\n this.emit(\"stats\", stats);\n this.reporter?.add({\n ts: Math.floor(Date.now() / 1000),\n bitrateKbps: stats.bitrateKbps,\n fps: stats.framesPerSecond,\n freezeMs,\n });\n }, STATS_INTERVAL_MS);\n }\n\n private stopStats(): void {\n if (this.statsTimer) clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n}\n\n/**\n * Resolves true once the element is actually rendering, false on timeout.\n *\n * `timeupdate` is the signal rather than `readyState` because readiness only\n * says data arrived; a live stream that stalls right after its first buffer can\n * report ready forever without the picture moving.\n */\nfunction hasFirstFrame(video: HTMLVideoElement): Promise<boolean> {\n if (video.currentTime > 0 && !video.paused) return Promise.resolve(true);\n return new Promise((resolve) => {\n const done = (ok: boolean) => {\n clearTimeout(timer);\n video.removeEventListener(\"timeupdate\", onTime);\n resolve(ok);\n };\n const onTime = () => {\n if (video.currentTime > 0) done(true);\n };\n const timer = setTimeout(() => done(false), FIRST_FRAME_TIMEOUT_MS);\n video.addEventListener(\"timeupdate\", onTime);\n });\n}\n","/**\n * INTERNAL — gateway signaling client.\n *\n * This module is the ONE place that knows the wire protocols Mebius uses\n * behind the scenes (WHIP for publishing, WHEP for low-latency viewing, HLS\n * for scale viewing). None of these terms ever escape `internal/` — the public\n * API speaks only in Mebius vocabulary.\n *\n * The gateway HTTP contract (mebius-stream-engine public edge):\n * - Publish: POST {gateway}/whip/{streamId}?token=<jwt> (application/sdp)\n * - View low-latency: POST {gateway}/whep/{streamId}?token=<jwt> (application/sdp)\n * - View scale: GET {gateway}/live/{streamId}/index.m3u8?token=<jwt>\n * - Teardown: DELETE {resourceUrl}\n *\n * The engine validates the token from the `?token=` QUERY parameter (its\n * MediaMTX auth hook + HLS playback gate both read the query, not a header).\n * We still send `Authorization: Bearer <token>` for gateways that prefer it,\n * but the query token is what the engine actually enforces. HLS segment URLs\n * inside the playlist inherit `?token=` automatically (the engine rewrites the\n * m3u8), so no extra header is needed for segment fetches.\n */\nimport { mebiusError } from \"../errors.js\";\n\n/**\n * A media session direction. Deliberately neutral vocabulary (\"publish\" /\n * \"view\") so that if a type bundler ever inlines this into the public `.d.ts`\n * (e.g. via a private field reference), no wire-protocol term leaks to clients.\n * The concrete path segment is derived inside {@link SignalingClient} only.\n */\nexport type SessionKind = \"publish\" | \"view\";\n\nexport interface SessionResult {\n /** The remote session answer returned by the gateway. */\n answer: string;\n /** Resource URL to DELETE on teardown, if the gateway returned one. */\n resourceUrl: string | null;\n}\n\nexport class SignalingClient {\n constructor(\n private readonly gateway: string,\n private readonly token: string,\n ) {}\n\n private base(): string {\n return this.gateway.replace(/\\/+$/, \"\");\n }\n\n private headers(contentType?: string): HeadersInit {\n const h: Record<string, string> = { Authorization: `Bearer ${this.token}` };\n if (contentType) h[\"Content-Type\"] = contentType;\n return h;\n }\n\n /** Append the access token as a query param (the form the engine enforces). */\n private withToken(url: string): string {\n const sep = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${sep}token=${encodeURIComponent(this.token)}`;\n }\n\n // Build the playlist URL used by scale-mode playback (HLS path, hidden). The\n // engine serves the playlist under /live/{id}/index.m3u8 and requires the\n // token in the query; segment URIs in the playlist inherit it automatically.\n /**\n * Absolute, tokenized URL for a gateway-relative delivery path handed to us\n * by the gateway (`deliveries[].path`). The gateway decides which paths exist\n * and in what order; the SDK only resolves them against its own base and\n * attaches the access token. Anything that is not a plain gateway-relative\n * path is rejected rather than fetched: an absolute URL there would send the\n * token to a host we did not choose.\n */\n deliveryUrl(path: string): string {\n if (!path.startsWith(\"/\") || path.startsWith(\"//\") || path.includes(\"://\")) {\n throw mebiusError(\"CONNECTION_FAILED\", \"The gateway returned an unusable delivery path.\");\n }\n return this.withToken(`${this.base()}${path}`);\n }\n\n /** Playlist URL for scale-mode playback. */\n scalePlaylistUrl(streamId: string): string {\n return this.withToken(`${this.base()}/live/${encodeURIComponent(streamId)}/index.m3u8`);\n }\n\n // Maps a neutral session kind to the concrete signaling path segment. This\n // mapping (publish -> WHIP, view -> WHEP) lives ONLY in this method body, so\n // the protocol names never appear in any exported type signature.\n private pathFor(kind: SessionKind): string {\n return kind === \"publish\" ? \"whip\" : \"whep\";\n }\n\n // Performs the offer/answer exchange for a publish or a low-latency view\n // session. Protocol detail kept inside the method body so it never leaks into\n // the bundled public .d.ts.\n /**\n * Run the session offer/answer exchange. Throws a {@link MebiusError} with a\n * Mebius-flavored code on failure — never the raw protocol name.\n */\n async exchangeSession(\n kind: SessionKind,\n streamId: string,\n offer: string,\n ): Promise<SessionResult> {\n const url = this.withToken(`${this.base()}/${this.pathFor(kind)}/${encodeURIComponent(streamId)}`);\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: this.headers(\"application/sdp\"),\n body: offer,\n });\n } catch (cause) {\n throw mebiusError(\"CONNECTION_FAILED\", undefined, cause);\n }\n\n if (res.status === 401 || res.status === 403) {\n throw mebiusError(\"TOKEN_EXPIRED\");\n }\n if (res.status === 404) {\n throw mebiusError(\"STREAM_NOT_FOUND\");\n }\n if (!res.ok) {\n throw mebiusError(\"CONNECTION_FAILED\", `Mebius gateway returned ${res.status}.`);\n }\n\n const answer = await res.text();\n const location = res.headers.get(\"Location\");\n const resourceUrl = location ? new URL(location, url).toString() : null;\n return { answer, resourceUrl };\n }\n\n /** Tear down a previously-created session resource. Best-effort. */\n async deleteResource(resourceUrl: string | null): Promise<void> {\n if (!resourceUrl) return;\n try {\n await fetch(resourceUrl, { method: \"DELETE\", headers: this.headers() });\n } catch {\n // Teardown is best-effort; the gateway reaps idle sessions anyway.\n }\n }\n}\n","/**\n * INTERNAL — read (NOT verify) a Mebius token's expiry.\n *\n * The token is a short-lived JWT minted by the developer's backend. The client\n * never verifies it (only the gateway can) — it just peeks at `exp` so it can\n * proactively surface a TOKEN_EXPIRED error and let the app refresh.\n */\nexport interface TokenInfo {\n /** Expiry as a UNIX epoch in milliseconds, if present. */\n expiresAtMs: number | null;\n}\n\nfunction base64UrlDecode(input: string): string {\n const padded = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad = padded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n const b64 = padded + pad;\n if (typeof atob === \"function\") return atob(b64);\n // Node fallback (tests / SSR).\n return Buffer.from(b64, \"base64\").toString(\"binary\");\n}\n\nexport function readToken(token: string): TokenInfo {\n const parts = token.split(\".\");\n if (parts.length < 2) return { expiresAtMs: null };\n try {\n const payload = JSON.parse(base64UrlDecode(parts[1] ?? \"\")) as { exp?: number };\n return { expiresAtMs: typeof payload.exp === \"number\" ? payload.exp * 1000 : null };\n } catch {\n return { expiresAtMs: null };\n }\n}\n","import { MebiusBroadcaster } from \"./broadcaster.js\";\nimport { mebiusError } from \"./errors.js\";\nimport { TypedEmitter, type ClientEventMap } from \"./events.js\";\nimport { MebiusPlayer } from \"./player.js\";\nimport { SignalingClient } from \"./internal/signaling.js\";\nimport type { TelemetryTarget } from \"./internal/telemetry.js\";\nimport { readToken } from \"./internal/token.js\";\nimport type {\n BroadcasterOptions,\n MebiusDelivery,\n MebiusInitOptions,\n PlayerOptions,\n} from \"./types.js\";\n\n/**\n * A live connection to Mebius. Obtain one from {@link Mebius.connect}, then\n * create broadcasters and players from it.\n */\nexport class MebiusClient extends TypedEmitter<ClientEventMap> {\n private readonly signaling: SignalingClient;\n private expiryTimer: ReturnType<typeof setTimeout> | null = null;\n private connected = false;\n\n /** @internal */\n constructor(\n config: MebiusInitOptions,\n private readonly token: string,\n private readonly deliveries: readonly MebiusDelivery[] = [],\n private readonly telemetry: TelemetryTarget | null = null,\n private readonly userId?: string,\n ) {\n super();\n this.signaling = new SignalingClient(config.gateway, token);\n }\n\n /** @internal Called by {@link Mebius.connect}. */\n open(): void {\n const { expiresAtMs } = readToken(this.token);\n const now = Date.now();\n if (expiresAtMs !== null && expiresAtMs <= now) {\n // Surface asynchronously so listeners attached after connect() still fire.\n queueMicrotask(() => this.emit(\"error\", mebiusError(\"TOKEN_EXPIRED\")));\n return;\n }\n this.connected = true;\n if (expiresAtMs !== null) {\n this.expiryTimer = setTimeout(\n () => this.emit(\"error\", mebiusError(\"TOKEN_EXPIRED\")),\n Math.max(0, expiresAtMs - now),\n );\n }\n queueMicrotask(() => this.emit(\"connected\", undefined));\n }\n\n /** Create a broadcaster bound to this connection. */\n createBroadcaster(options: BroadcasterOptions = {}): MebiusBroadcaster {\n this.assertConnected();\n return new MebiusBroadcaster(this.signaling, options, this.telemetry, this.userId);\n }\n\n /** Create a player bound to this connection. */\n createPlayer(options: PlayerOptions = {}): MebiusPlayer {\n this.assertConnected();\n return new MebiusPlayer(this.signaling, options, this.deliveries, this.telemetry, this.userId);\n }\n\n /**\n * Create a monitor: a player tuned for watching a stream you are interacting\n * WITH rather than merely watching — the other side of a co-broadcast, where a\n * second or two of delay makes the interaction feel broken.\n *\n * It is a player with the delay budget spent differently, not a different API:\n * it starts on the real-time route and falls back on its own if that route\n * delivers no frames. Apps used to hand-roll this (open a real-time view, run a\n * timer, swap players when it stayed black); getting the fallback wrong showed a\n * black frame to a live audience, so it belongs here rather than in every app.\n */\n createMonitor(): MebiusPlayer {\n this.assertConnected();\n return new MebiusPlayer(this.signaling, { mode: \"low-latency\" }, this.deliveries, this.telemetry, this.userId);\n }\n\n /** Close the connection and release resources. */\n disconnect(reason?: string): void {\n if (this.expiryTimer) clearTimeout(this.expiryTimer);\n this.expiryTimer = null;\n this.connected = false;\n this.emit(\"disconnected\", { reason });\n this.removeAllListeners();\n }\n\n private assertConnected(): void {\n if (!this.connected) throw mebiusError(\"NOT_CONNECTED\");\n }\n}\n","import { MebiusClient } from \"./client.js\";\nimport { mebiusError } from \"./errors.js\";\nimport type { MebiusConnectOptions, MebiusInitOptions } from \"./types.js\";\n\nlet config: MebiusInitOptions | null = null;\n\n/**\n * Entry point to the Mebius Web SDK.\n *\n * ```ts\n * Mebius.init({ appId: \"app_123\", gateway: \"https://gateway.mebius.io\" });\n * const client = Mebius.connect({ token });\n * ```\n */\nexport const Mebius = {\n /** Configure the SDK once, before connecting. */\n init(options: MebiusInitOptions): void {\n if (!options.appId) throw mebiusError(\"UNKNOWN\", \"Mebius.init requires an appId.\");\n if (!options.gateway) throw mebiusError(\"UNKNOWN\", \"Mebius.init requires a gateway URL.\");\n config = { ...options };\n },\n\n /**\n * Connect using a short-lived token minted by your backend. Returns a\n * {@link MebiusClient}. Listen for `\"connected\"` / `\"error\"` on it.\n */\n connect(options: MebiusConnectOptions): MebiusClient {\n if (!config) {\n throw mebiusError(\"UNKNOWN\", \"Call Mebius.init() before Mebius.connect().\");\n }\n if (!options.token) throw mebiusError(\"UNKNOWN\", \"Mebius.connect requires a token.\");\n const telemetry =\n options.beaconToken && options.beaconUrl\n ? { token: options.beaconToken, url: options.beaconUrl }\n : null;\n const client = new MebiusClient(config, options.token, options.deliveries ?? [], telemetry, options.userId);\n client.open();\n return client;\n },\n\n /** @internal Reset configuration (used in tests). */\n _reset(): void {\n config = null;\n },\n};\n"],"mappings":"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","names":["STATS_INTERVAL_MS","config"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/events.ts","../src/internal/view-target.ts","../src/internal/webrtc-util.ts","../src/internal/publish-transport.ts","../src/internal/autoplay.ts","../src/internal/ll-view-transport.ts","../src/internal/scale-view-transport.ts","../src/internal/balanced-view-transport.ts","../src/internal/transport.ts","../src/internal/telemetry.ts","../src/broadcaster.ts","../src/internal/freeze-clock.ts","../src/player.ts","../src/internal/signaling.ts","../src/internal/token.ts","../src/client.ts","../src/mebius.ts"],"sourcesContent":["/**\n * @mebius-io/web — Mebius Client SDK for the web.\n *\n * Public surface only. Everything under `internal/` is private and is never\n * re-exported here.\n */\nexport { Mebius } from \"./mebius.js\";\nexport { MebiusClient } from \"./client.js\";\nexport { MebiusBroadcaster } from \"./broadcaster.js\";\nexport { MebiusPlayer } from \"./player.js\";\nexport { MebiusError, mebiusError } from \"./errors.js\";\n\nexport type {\n ClientEventMap,\n BroadcasterEventMap,\n PlayerEventMap,\n} from \"./events.js\";\n\nexport type {\n MebiusInitOptions,\n MebiusConnectOptions,\n MebiusDelivery,\n BroadcasterOptions,\n PlayerOptions,\n PlaybackMode,\n ViewTarget,\n MediaConstraint,\n BroadcastStats,\n PlaybackStats,\n MebiusErrorCode,\n} from \"./types.js\";\n","import type { MebiusErrorCode } from \"./types.js\";\n\n/**\n * The single error type the SDK surfaces. Always uses Mebius terminology —\n * never raw transport/protocol wording.\n *\n * Inspect {@link MebiusError.code} to decide how to recover (for example,\n * refresh the token on `\"TOKEN_EXPIRED\"`).\n */\nexport class MebiusError extends Error {\n readonly code: MebiusErrorCode;\n /** The underlying cause, if any. Useful for logging, opaque to the contract. */\n readonly cause?: unknown;\n\n constructor(code: MebiusErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = \"MebiusError\";\n this.code = code;\n this.cause = cause;\n Object.setPrototypeOf(this, MebiusError.prototype);\n }\n}\n\n/** Human-readable default messages, kept in Mebius terms. */\nconst DEFAULT_MESSAGES: Record<MebiusErrorCode, string> = {\n TOKEN_EXPIRED: \"Your Mebius token has expired. Mint a fresh token and reconnect.\",\n PERMISSION_DENIED: \"Camera/microphone permission was denied by the user or browser.\",\n CONNECTION_FAILED: \"Could not establish a connection to the Mebius gateway.\",\n NOT_CONNECTED: \"Not connected to Mebius. Call connect() before using this client.\",\n STREAM_NOT_FOUND: \"The requested stream could not be found on the Mebius gateway.\",\n UNKNOWN: \"An unexpected Mebius error occurred.\",\n};\n\n/** Create a {@link MebiusError}, falling back to a sensible default message. */\nexport function mebiusError(\n code: MebiusErrorCode,\n message?: string,\n cause?: unknown,\n): MebiusError {\n return new MebiusError(code, message ?? DEFAULT_MESSAGES[code], cause);\n}\n","import type { MebiusError } from \"./errors.js\";\nimport type { BroadcastStats, PlaybackStats } from \"./types.js\";\n\n// NOTE: these are `type` aliases (not interfaces) so they satisfy the\n// `Record<string, unknown>` constraint on TypedEmitter — TS only treats object\n// type aliases (not augmentable interfaces) as having an implicit index\n// signature.\n\n/** Event payloads emitted by {@link MebiusClient}. */\nexport type ClientEventMap = {\n connected: void;\n disconnected: { reason?: string };\n error: MebiusError;\n};\n\n/** Event payloads emitted by a broadcaster. */\nexport type BroadcasterEventMap = {\n started: { streamId: string };\n stopped: void;\n stats: BroadcastStats;\n};\n\n/** Event payloads emitted by a player. */\nexport type PlayerEventMap = {\n playing: { streamId: string };\n buffering: void;\n ended: void;\n stats: PlaybackStats;\n};\n\ntype Listener<T> = (payload: T) => void;\n\n/**\n * A tiny strongly-typed event emitter. `EventMap` maps each event name to its\n * payload type, so `on(\"started\", cb)` infers `cb`'s argument automatically.\n */\nexport class TypedEmitter<EventMap extends Record<string, unknown>> {\n private readonly listeners = new Map<keyof EventMap, Set<Listener<unknown>>>();\n\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof EventMap>(event: K, cb: Listener<EventMap[K]>): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(cb as Listener<unknown>);\n return () => this.off(event, cb);\n }\n\n /** Unsubscribe a previously-registered listener. */\n off<K extends keyof EventMap>(event: K, cb: Listener<EventMap[K]>): void {\n this.listeners.get(event)?.delete(cb as Listener<unknown>);\n }\n\n /** Emit an event to all listeners. Internal use. */\n protected emit<K extends keyof EventMap>(event: K, payload: EventMap[K]): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const cb of [...set]) (cb as Listener<EventMap[K]>)(payload);\n }\n\n /** Remove every listener. Internal use during teardown. */\n protected removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","/** INTERNAL — resolve a public {@link ViewTarget} to a real `<video>` element. */\nimport { mebiusError } from \"../errors.js\";\nimport type { ViewTarget } from \"../types.js\";\n\nexport function resolveVideoElement(target: ViewTarget): HTMLVideoElement {\n if (typeof target !== \"string\") {\n if (target instanceof HTMLVideoElement) return target;\n throw mebiusError(\"UNKNOWN\", \"View target must be a <video> element or a CSS selector.\");\n }\n const el = document.querySelector(target);\n if (!el) {\n throw mebiusError(\"UNKNOWN\", `No element matches the selector \"${target}\".`);\n }\n if (!(el instanceof HTMLVideoElement)) {\n throw mebiusError(\"UNKNOWN\", `Selector \"${target}\" did not resolve to a <video> element.`);\n }\n return el;\n}\n","/** INTERNAL — small WebRTC helpers shared by publish/view transports. */\n\n/**\n * Wait until ICE gathering completes (or a short timeout elapses) so the SDP\n * we send already contains candidates. Keeps the gateway exchange to a single\n * round-trip.\n */\nexport function waitForIceGathering(pc: RTCPeerConnection, timeoutMs = 2000): Promise<void> {\n if (pc.iceGatheringState === \"complete\") return Promise.resolve();\n return new Promise((resolve) => {\n const done = () => {\n pc.removeEventListener(\"icegatheringstatechange\", check);\n clearTimeout(timer);\n resolve();\n };\n const check = () => {\n if (pc.iceGatheringState === \"complete\") done();\n };\n const timer = setTimeout(done, timeoutMs);\n pc.addEventListener(\"icegatheringstatechange\", check);\n });\n}\n\n/** Default ICE configuration. The gateway may also relay; STUN aids direct paths. */\nexport const DEFAULT_RTC_CONFIG: RTCConfiguration = {\n iceServers: [{ urls: \"stun:stun.l.google.com:19302\" }],\n};\n","/**\n * INTERNAL — publish transport (WHIP over the gateway).\n *\n * Sends a locally-captured MediaStream to the Mebius gateway via a standard\n * WHIP offer/answer exchange. Hidden from the public API.\n */\nimport type { BroadcastStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { PublishTransport } from \"./transport.js\";\nimport { DEFAULT_RTC_CONFIG, waitForIceGathering } from \"./webrtc-util.js\";\n\n/**\n * Offer H264 ahead of everything else for the outgoing video track.\n *\n * Chrome negotiates VP8 by default, and VP8 is a dead end for every viewer who\n * is not on WebRTC: the server's HLS/FLV muxers cannot carry it, so they drop\n * the video track and publish an audio-only stream (\"skipping track (VP8)\").\n * The broadcast looks perfect to the publisher and has no picture for anyone\n * watching over HLS, FLV, or the CDN.\n *\n * H264 is what every one of those paths speaks, and negotiating it here means\n * the server never has to transcode — which would cost far more latency than\n * anything else in this SDK.\n *\n * Best-effort by design: `setCodecPreferences` is unavailable on older Safari,\n * and a browser without an H264 encoder has nothing to reorder. Both cases fall\n * through to the default negotiation rather than failing the broadcast.\n */\nfunction preferH264(pc: RTCPeerConnection): void {\n const caps = RTCRtpSender.getCapabilities?.(\"video\");\n if (!caps?.codecs) return;\n const h264 = caps.codecs.filter((c) => c.mimeType.toLowerCase() === \"video/h264\");\n if (h264.length === 0) return;\n const rest = caps.codecs.filter((c) => c.mimeType.toLowerCase() !== \"video/h264\");\n for (const tr of pc.getTransceivers()) {\n if (tr.sender.track?.kind !== \"video\") continue;\n try {\n tr.setCodecPreferences?.([...h264, ...rest]);\n } catch {\n // A browser that rejects the list negotiates its own way; still better\n // than no video for CDN viewers on the browsers that accept it.\n }\n }\n}\n\nexport class WhipPublishTransport implements PublishTransport {\n private pc: RTCPeerConnection | null = null;\n private resourceUrl: string | null = null;\n\n constructor(private readonly signaling: SignalingClient) {}\n\n async start(streamId: string, stream: MediaStream): Promise<void> {\n const pc = new RTCPeerConnection(DEFAULT_RTC_CONFIG);\n this.pc = pc;\n\n for (const track of stream.getTracks()) {\n pc.addTrack(track, stream);\n }\n preferH264(pc);\n\n const offer = await pc.createOffer();\n await pc.setLocalDescription(offer);\n await waitForIceGathering(pc);\n\n const localSdp = pc.localDescription?.sdp;\n if (!localSdp) throw mebiusError(\"CONNECTION_FAILED\", \"Failed to create a local session.\");\n\n const { answer, resourceUrl } = await this.signaling.exchangeSession(\n \"publish\",\n streamId,\n localSdp,\n );\n this.resourceUrl = resourceUrl;\n await pc.setRemoteDescription({ type: \"answer\", sdp: answer });\n }\n\n async replaceVideoTrack(track: MediaStreamTrack | null): Promise<void> {\n const sender = this.pc?.getSenders().find((s) => s.track?.kind === \"video\");\n if (sender) await sender.replaceTrack(track);\n }\n\n async stop(): Promise<void> {\n await this.signaling.deleteResource(this.resourceUrl);\n this.resourceUrl = null;\n this.pc?.getSenders().forEach((s) => s.track?.stop());\n this.pc?.close();\n this.pc = null;\n }\n\n /** Bytes sent and packet counters at the previous getStats() call. */\n private lastOutbound: { bytes: number; atMs: number } | null = null;\n\n /**\n * Live broadcast statistics.\n *\n * Two corrections over the obvious reading of RTCStats:\n *\n * `bitrateKbps` is the delta of `outbound-rtp.bytesSent`, not\n * `availableOutgoingBitrate`. The latter is the congestion controller's\n * ESTIMATE of headroom, so a broadcaster on a fast link reported several\n * megabits while actually sending a fraction of that — the dashboard's\n * \"bitrate adherence\" score was measuring the network, not the encoder.\n *\n * `packetLossPct` comes from the receiver's report (`remote-inbound-rtp`),\n * which is the only place that knows what did not arrive. It was never\n * reported at all, and publishQualityScore treats a missing value as zero\n * loss — so every publisher scored full marks on a fifth of the rubric no\n * matter how bad the uplink was.\n */\n async getStats(): Promise<BroadcastStats | null> {\n if (!this.pc) return null;\n const report = await this.pc.getStats();\n\n let framesPerSecond: number | undefined;\n let rttMs: number | undefined;\n let packetLossPct: number | undefined;\n let bytesSent: number | undefined;\n let packetsSent: number | undefined;\n let packetsLost: number | undefined;\n\n report.forEach((stat) => {\n if (stat.type === \"outbound-rtp\" && !stat.isRemote) {\n if (typeof stat.framesPerSecond === \"number\") framesPerSecond = stat.framesPerSecond;\n if (typeof stat.bytesSent === \"number\") bytesSent = (bytesSent ?? 0) + stat.bytesSent;\n if (typeof stat.packetsSent === \"number\") packetsSent = (packetsSent ?? 0) + stat.packetsSent;\n }\n if (stat.type === \"remote-inbound-rtp\") {\n if (typeof stat.packetsLost === \"number\") packetsLost = (packetsLost ?? 0) + stat.packetsLost;\n // The receiver's RTT is more accurate than the candidate pair's when\n // both are present, so it wins; the candidate pair fills in below.\n if (typeof stat.roundTripTime === \"number\") rttMs = Math.round(stat.roundTripTime * 1000);\n }\n if (stat.type === \"candidate-pair\" && stat.state === \"succeeded\") {\n if (rttMs == null && typeof stat.currentRoundTripTime === \"number\") {\n rttMs = Math.round(stat.currentRoundTripTime * 1000);\n }\n }\n });\n\n let bitrateKbps: number | undefined;\n const atMs = Date.now();\n if (bytesSent != null) {\n const prev = this.lastOutbound;\n if (prev && atMs > prev.atMs && bytesSent >= prev.bytes) {\n bitrateKbps = Math.round(((bytesSent - prev.bytes) * 8) / 1000 / ((atMs - prev.atMs) / 1000));\n }\n this.lastOutbound = { bytes: bytesSent, atMs };\n }\n\n if (packetsLost != null && packetsSent != null && packetsSent > 0) {\n packetLossPct = Math.max(0, Math.min(100, (packetsLost / packetsSent) * 100));\n }\n\n return { bitrateKbps, framesPerSecond, rttMs, packetLossPct };\n }\n}\n","/**\n * INTERNAL — getting a `<video>` element to actually render.\n *\n * Every transport used to call `video.play().catch(() => undefined)`. That hides\n * the single most common playback failure in a browser: Chrome and Safari refuse\n * to start playback WITH AUDIO unless the page has sticky user activation. The\n * promise rejects with NotAllowedError, the element stays on frame zero, and the\n * SDK reports nothing — so a stream that is serving perfectly looks dead, the\n * first-frame watchdog times out on every route in turn, and the whole thing\n * surfaces as \"no fallback, black video\" even though the fallback ran.\n *\n * A click is not enough on its own: activation is consumed and expires, and a\n * play() that happens after `await fetchToken()` and `await import(\"hls.js\")` is\n * often too late.\n *\n * Muted playback is always allowed. So: try with sound, and if the policy\n * refuses, mute and try again — a muted picture the viewer can unmute beats a\n * black rectangle. The caller learns which happened instead of nothing at all.\n */\n\nexport interface PlayOutcome {\n /** True when playback started only because we muted the element. */\n mutedByPolicy: boolean;\n}\n\n/** Is this the browser's autoplay policy talking, rather than a real failure? */\nfunction isAutoplayBlocked(e: unknown): boolean {\n // Chrome/Safari/Firefox all use NotAllowedError here. The name check is the\n // portable signal; message text is not.\n return typeof e === \"object\" && e !== null && (e as { name?: string }).name === \"NotAllowedError\";\n}\n\n/**\n * Start playback, retrying muted if the autoplay policy blocks sound.\n *\n * Rethrows anything that is NOT a policy refusal: an unsupported source or a\n * decode failure is exactly what the player's route-fallback exists for, and\n * swallowing it made a dead route indistinguishable from a slow one.\n */\nexport async function playWithAutoplayFallback(video: HTMLVideoElement): Promise<PlayOutcome> {\n // Set both here rather than relying on the app's markup: inline playback on\n // iOS is refused outright without playsInline, whatever the policy says.\n video.playsInline = true;\n try {\n await video.play();\n return { mutedByPolicy: false };\n } catch (e) {\n if (!isAutoplayBlocked(e) || video.muted) throw e;\n video.muted = true;\n await video.play();\n return { mutedByPolicy: true };\n }\n}\n\n/**\n * Return a video element to a clean state before a different transport attaches\n * to it.\n *\n * `srcObject` and `src` are not interchangeable slots: while srcObject is set,\n * the element IGNORES src entirely. So a real-time route that attached a\n * MediaStream and then failed would silently block the HLS route behind it from\n * ever showing a frame — the picture stays black while every log says the\n * playlist loaded fine.\n */\nexport function resetVideoElement(video: HTMLVideoElement): void {\n video.srcObject = null;\n video.removeAttribute(\"src\");\n video.load();\n}\n","/**\n * INTERNAL — low-latency view transport (WHEP over the gateway).\n *\n * Pulls a remote stream from the Mebius gateway via a standard WHEP exchange\n * and renders it into a video element. Hidden from the public API.\n */\nimport type { PlaybackStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { ViewTransport } from \"./transport.js\";\nimport { DEFAULT_RTC_CONFIG, waitForIceGathering } from \"./webrtc-util.js\";\nimport { playWithAutoplayFallback, resetVideoElement } from \"./autoplay.js\";\n\nexport class WhepViewTransport implements ViewTransport {\n readonly kind = \"whep\" as const;\n\n private pc: RTCPeerConnection | null = null;\n private resourceUrl: string | null = null;\n private endedCb: (() => void) | null = null;\n private bufferingCb: (() => void) | null = null;\n\n /** Element this route attached a MediaStream to, so stop() can release it. */\n private video: HTMLVideoElement | null = null;\n\n constructor(private readonly signaling: SignalingClient) {}\n\n onEnded(cb: () => void): void {\n this.endedCb = cb;\n }\n\n onBuffering(cb: () => void): void {\n this.bufferingCb = cb;\n }\n\n async start(streamId: string, video: HTMLVideoElement): Promise<void> {\n this.video = video;\n const pc = new RTCPeerConnection(DEFAULT_RTC_CONFIG);\n this.pc = pc;\n const remote = new MediaStream();\n\n pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n pc.addTransceiver(\"audio\", { direction: \"recvonly\" });\n\n pc.ontrack = (ev) => {\n remote.addTrack(ev.track);\n video.srcObject = remote;\n // Muted retry rather than giving up: \"left to the app\" meant a viewer saw a\n // black rectangle and the app was told nothing.\n void playWithAutoplayFallback(video)\n .then((o) => {\n this.mutedByPolicy = o.mutedByPolicy;\n })\n .catch(() => {\n /* a genuine failure here surfaces as no first frame, which the player's\n route watchdog already handles */\n });\n };\n pc.onconnectionstatechange = () => {\n if (pc.connectionState === \"disconnected\" || pc.connectionState === \"failed\") {\n this.bufferingCb?.();\n }\n if (pc.connectionState === \"closed\") this.endedCb?.();\n };\n\n const offer = await pc.createOffer();\n await pc.setLocalDescription(offer);\n await waitForIceGathering(pc);\n\n const localSdp = pc.localDescription?.sdp;\n if (!localSdp) throw mebiusError(\"CONNECTION_FAILED\", \"Failed to create a local session.\");\n\n const { answer, resourceUrl } = await this.signaling.exchangeSession(\n \"view\",\n streamId,\n localSdp,\n );\n this.resourceUrl = resourceUrl;\n await pc.setRemoteDescription({ type: \"answer\", sdp: answer });\n }\n\n async stop(): Promise<void> {\n await this.signaling.deleteResource(this.resourceUrl);\n this.resourceUrl = null;\n this.pc?.close();\n this.pc = null;\n // Release the element's MediaStream. While srcObject is set the element\n // ignores `src`, so leaving it behind stops the next route (HLS/FLV) from\n // ever rendering — a black picture with a perfectly healthy playlist.\n if (this.video) resetVideoElement(this.video);\n this.video = null;\n }\n\n /** True when playback only started because the element had to be muted. */\n mutedByPolicy = false;\n\n async getStats(): Promise<PlaybackStats | null> {\n if (!this.pc) return null;\n const report = await this.pc.getStats();\n let bitrateKbps = 0;\n let framesPerSecond = 0;\n let latencyMs: number | undefined;\n report.forEach((stat) => {\n if (stat.type === \"inbound-rtp\") {\n if (typeof stat.framesPerSecond === \"number\") framesPerSecond = stat.framesPerSecond;\n if (typeof stat.jitter === \"number\") latencyMs = Math.round(stat.jitter * 1000);\n }\n if (stat.type === \"candidate-pair\" && stat.state === \"succeeded\") {\n if (typeof stat.availableIncomingBitrate === \"number\") {\n bitrateKbps = Math.round(stat.availableIncomingBitrate / 1000);\n }\n }\n });\n return { bitrateKbps, framesPerSecond, latencyMs };\n }\n}\n","/**\n * INTERNAL — scale view transport (HLS via hls.js / native).\n *\n * For large-audience playback Mebius delivers an HLS playlist from the\n * gateway. hls.js is loaded lazily; Safari plays the playlist natively.\n * Hidden from the public API.\n */\nimport type { PlaybackStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { ViewTransport } from \"./transport.js\";\nimport { playWithAutoplayFallback } from \"./autoplay.js\";\n\n// Loaded on demand so it never weighs down low-latency-only apps.\ntype HlsModule = typeof import(\"hls.js\");\ntype HlsInstance = import(\"hls.js\").default;\n\n/**\n * Retry predicate for the playlist load. Exported for its test only.\n *\n * A 404 on a live playlist is usually a TIMING answer, not a permanent one: a CDN\n * edge only packages HLS once ingest has produced segments, so a viewer joining in\n * the first ~10s asks for a playlist that does not exist yet. hls.js never retries\n * a 4xx on its own (retryForHttpStatus excludes 400-499), so without this the\n * retry budget is dead config against the exact status the edge returns.\n *\n * 404 only — 401/403 means the play token is wrong, and retrying that is noise.\n */\nexport function retryWarmupNotFound(\n cfg: { maxNumRetry?: number } | null | undefined,\n retryCount: number,\n res: { code?: number } | undefined,\n retry: boolean,\n): boolean {\n return retry || (retryCount < (cfg?.maxNumRetry ?? 0) && res?.code === 404);\n}\n\nexport class HlsViewTransport implements ViewTransport {\n readonly kind = \"hls\" as const;\n\n private hls: HlsInstance | null = null;\n private video: HTMLVideoElement | null = null;\n private endedCb: (() => void) | null = null;\n private bufferingCb: (() => void) | null = null;\n /** Aborts this attempt's element listeners; see start(). */\n private listeners: AbortController | null = null;\n\n /**\n * deliveryPath, when given, is a gateway-relative path from the gateway's own\n * delivery list — that is how a CDN-backed playlist gets used instead of the\n * origin one. Without it this falls back to the origin playlist, which is\n * still correct, just served from our own bandwidth.\n */\n constructor(\n private readonly signaling: SignalingClient,\n private readonly deliveryPath?: string,\n ) {}\n\n onEnded(cb: () => void): void {\n this.endedCb = cb;\n }\n\n onBuffering(cb: () => void): void {\n this.bufferingCb = cb;\n }\n\n async start(streamId: string, video: HTMLVideoElement): Promise<void> {\n this.video = video;\n const url = this.deliveryPath\n ? this.signaling.deliveryUrl(this.deliveryPath)\n : this.signaling.scalePlaylistUrl(streamId);\n\n // Same reason as the FLV route: every candidate shares one element, so a\n // failed attempt's listeners must not outlive it.\n this.listeners = new AbortController();\n const { signal } = this.listeners;\n video.addEventListener(\"ended\", () => this.endedCb?.(), { signal });\n video.addEventListener(\"waiting\", () => this.bufferingCb?.(), { signal });\n\n // Media Source first, native second — NOT the other way round.\n //\n // The check used to be `if (video.canPlayType(\"application/vnd.apple.mpegurl\"))`,\n // and Chromium answers \"maybe\" to that. \"maybe\" is a legal answer meaning\n // \"I might, ask me again with codecs\", and Chromium says it while being\n // unable to play a playlist at all — so this route assigned video.src and\n // died with \"NotSupportedError: Failed to load because no supported source\n // was found\", on the browser most viewers use. Asking the library whether\n // it can drive this browser is a direct question with a direct answer;\n // canPlayType is neither.\n let Hls: HlsModule[\"default\"] | null = null;\n try {\n Hls = (await import(\"hls.js\")).default;\n } catch {\n // Bundling or network problem. Native is then the only chance, and Safari\n // is exactly the browser where it works.\n Hls = null;\n }\n\n if (!Hls?.isSupported()) {\n if (!video.canPlayType(\"application/vnd.apple.mpegurl\")) {\n throw mebiusError(\"CONNECTION_FAILED\", \"Scale playback is not supported in this browser.\");\n }\n video.src = url;\n this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;\n return;\n }\n\n // No forced lowLatencyMode: it is for LL-HLS playlists (EXT-X-PART), and\n // asserting it against an ordinary playlist makes hls.js wait for parts that\n // never arrive. hls.js turns it on by itself when the playlist advertises it.\n //\n // maxLiveSyncPlaybackRate lets hls.js catch up by playing fast when it has\n // drifted behind the live edge. Default is 1 — no catching up ever, so every\n // stall becomes permanent added latency for the rest of the session.\n //\n // 1.1, not 1.5. Catch-up is a pitch shift on the audio, and 1.5 is a 50%\n // one: viewers hear it as chipmunk speech, which is worse than the latency it\n // buys back. It also fights itself on an LL-HLS playlist, where the target is\n // PART-HOLD-BACK (0.5s here) — any network jitter reads as \"behind\", so the\n // player spends the session alternating between sprinting and starving.\n // 1.1 is inaudible and still recovers a 2s drift in ~20s.\n // Everything else is left at hls.js defaults on purpose: it already reads the\n // server's own HOLD-BACK / PART-HOLD-BACK target from the playlist, and a\n // number guessed here would only override a value the server measured.\n // Manifest retry, because a 404 on the playlist is usually a TIMING answer,\n // not a permanent one: a CDN edge only packages HLS once ingest has produced\n // segments, so a viewer who joins in the first ~10s of a stream asks for a\n // playlist that does not exist yet. hls.js defaults to 1 retry, which turns\n // that warm-up window into a hard candidate failure. 5 retries with backoff\n // capped at 2s covers ~10s of warm-up and costs nothing once the edge is hot.\n // See retryWarmupNotFound for why the predicate is mandatory, not decoration.\n const hls = new Hls({\n maxLiveSyncPlaybackRate: 1.1,\n manifestLoadPolicy: {\n default: {\n maxTimeToFirstByteMs: 10_000,\n maxLoadTimeMs: 20_000,\n timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 },\n errorRetry: {\n maxNumRetry: 5,\n retryDelayMs: 500,\n maxRetryDelayMs: 2_000,\n shouldRetry: (cfg, retryCount, _isTimeout, res, retry) =>\n retryWarmupNotFound(cfg, retryCount, res, retry),\n },\n },\n },\n });\n this.hls = hls;\n hls.on(Hls.Events.ERROR, (_evt, data) => {\n if (data.fatal) this.bufferingCb?.();\n });\n hls.loadSource(url);\n hls.attachMedia(video);\n this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;\n }\n\n /** True when playback only started because the element had to be muted. */\n mutedByPolicy = false;\n\n async stop(): Promise<void> {\n this.listeners?.abort();\n this.listeners = null;\n this.hls?.destroy();\n this.hls = null;\n if (this.video) {\n this.video.removeAttribute(\"src\");\n this.video.load();\n }\n this.video = null;\n }\n\n async getStats(): Promise<PlaybackStats | null> {\n if (!this.video) return null;\n const level = this.hls?.levels?.[this.hls.currentLevel];\n return {\n bitrateKbps: level ? Math.round(level.bitrate / 1000) : 0,\n framesPerSecond: 0,\n };\n }\n}\n","/**\n * INTERNAL — balanced view transport (HTTP-FLV via flv.js).\n *\n * For typical one-to-many web viewers Mebius pulls an HTTP-FLV stream and feeds\n * it through flv.js (Media Source Extensions). Latency is ~1-3s — lower than\n * HLS, higher than the WebRTC pull — and it scales over a CDN edge.\n *\n * History worth keeping: 0.2.0 deleted this file on the reading that the mode\n * was \"unserved\", because the gateway had no route for it. The route simply had\n * not been built yet — production web playback has always been HTTP-FLV. The\n * gateway now serves it, so this comes back, with one change: the URL is no\n * longer derived from a hardcoded path. It comes from the gateway's own\n * delivery list, so the gateway can move or re-point that path without an SDK\n * release.\n *\n * Browser-only: flv.js needs MSE, which iOS Safari lacks — createViewCandidates\n * is what keeps this off platforms that cannot play it. flv.js is a BUNDLED\n * dependency, never a peer dependency: an integrator must never have to type a\n * transport library's name to make Mebius work.\n *\n * Hidden from the public API; the public surface speaks only of the\n * `\"balanced\"` playback mode.\n */\nimport type { PlaybackStats } from \"../types.js\";\nimport { mebiusError } from \"../errors.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport type { ViewTransport } from \"./transport.js\";\nimport { playWithAutoplayFallback } from \"./autoplay.js\";\n\ntype FlvModule = typeof import(\"flv.js\");\ntype FlvPlayer = ReturnType<FlvModule[\"default\"][\"createPlayer\"]>;\n\n/**\n * flv.js tuned for live rather than for its VOD defaults.\n *\n * The defaults cost us both things this route exists to provide:\n * - `enableStashBuffer: true` with a 384KB initial stash holds bytes in the IO\n * layer before any of them reach MSE. At a webcam's ~400kbps that is ~8s of\n * nothing on screen — past the player's first-frame watchdog, so a perfectly\n * healthy stream got dropped as \"delivered no video\", and when it did start,\n * the stash was pure added delay.\n * - `lazyLoad: true` aborts the HTTP connection once 3 minutes are buffered.\n * For live that reconnect is a fresh 302 through the gateway and a fresh\n * stall for the viewer, buying nothing.\n * The cleanup pair keeps the SourceBuffer from growing without bound over a long\n * watch; `reuseRedirectedURL` keeps a reconnect on the signed CDN URL we were\n * already handed instead of re-running the gateway redirect.\n */\nconst LIVE_FLV_CONFIG = {\n enableStashBuffer: false,\n stashInitialSize: 128,\n lazyLoad: false,\n autoCleanupSourceBuffer: true,\n autoCleanupMaxBackwardDuration: 30,\n autoCleanupMinBackwardDuration: 10,\n reuseRedirectedURL: true,\n} as const;\n\n/**\n * Seconds behind the newest buffered byte before we skip forward.\n *\n * flv.js does not chase the live edge (that is mpegts.js). Without this, every\n * stall the network hands us is permanent latency: the player resumes where it\n * paused and stays that far behind for the rest of the session, so a viewer who\n * hit two stalls is minutes behind by the end. Bounded at 2s — below that the\n * skip is more visible than the delay it removes.\n */\nconst MAX_DRIFT_S = 2;\n/** Where to land when skipping: short of the edge, or we starve immediately. */\nconst EDGE_MARGIN_S = 0.4;\n\n/**\n * How long the first attempt gets to produce a frame before the audio-less\n * retry. Short on purpose: this path only exists for a stream that is already\n * broken, and every millisecond here is join delay for the viewer.\n */\nconst AUDIO_RETRY_MS = 2500;\n\n/**\n * Resolves true when media has landed but playback still cannot start.\n *\n * Both halves matter. `currentTime === 0` alone is also what a slow first\n * segment looks like, and treating that as the audio-lie case drops audio for\n * the rest of the session on nothing worse than a cold CDN edge — observed\n * once, as a stream that played perfectly and silently. Buffered data with a\n * clock that will not move is the actual signature: the demuxer fed the\n * SourceBuffer and the element is still waiting for a track that never arrives.\n */\nfunction stalledWithData(video: HTMLVideoElement, ms: number): Promise<boolean> {\n if (video.currentTime > 0) return Promise.resolve(false);\n return new Promise((resolve) => {\n const done = (stalled: boolean) => {\n clearTimeout(timer);\n video.removeEventListener(\"timeupdate\", onTime);\n resolve(stalled);\n };\n const onTime = () => {\n if (video.currentTime > 0) done(false);\n };\n const timer = setTimeout(() => done(video.buffered.length > 0 && video.currentTime === 0), ms);\n video.addEventListener(\"timeupdate\", onTime);\n });\n}\n\n/**\n * Skip to the live edge when playback has fallen behind it. Exported for test;\n * a no-op when the gap is small, so it is safe on every `timeupdate`.\n */\nexport function chaseLiveEdge(video: HTMLVideoElement): void {\n const ranges = video.buffered;\n if (ranges.length === 0) return;\n const edge = ranges.end(ranges.length - 1);\n if (edge - video.currentTime <= MAX_DRIFT_S) return;\n video.currentTime = edge - EDGE_MARGIN_S;\n}\n\nexport class FlvViewTransport implements ViewTransport {\n readonly kind = \"flv_js\" as const;\n\n private player: FlvPlayer | null = null;\n private video: HTMLVideoElement | null = null;\n private endedCb: (() => void) | null = null;\n private bufferingCb: (() => void) | null = null;\n /** Aborts this attempt's element listeners; see start(). */\n private listeners: AbortController | null = null;\n\n constructor(\n private readonly signaling: SignalingClient,\n private readonly deliveryPath: string,\n ) {}\n\n onEnded(cb: () => void): void {\n this.endedCb = cb;\n }\n\n onBuffering(cb: () => void): void {\n this.bufferingCb = cb;\n }\n\n async start(_streamId: string, video: HTMLVideoElement): Promise<void> {\n this.video = video;\n const url = this.signaling.deliveryUrl(this.deliveryPath);\n\n // Bound to this attempt's lifetime. The player hands every candidate route\n // the SAME element, so listeners left behind by a route that failed keep\n // firing over the route that succeeded — and an orphaned chaseLiveEdge does\n // not just report, it SEEKS, yanking a healthy HLS playback around on behalf\n // of a dead FLV attempt. stop() aborts them.\n this.listeners = new AbortController();\n const { signal } = this.listeners;\n video.addEventListener(\"ended\", () => this.endedCb?.(), { signal });\n video.addEventListener(\"waiting\", () => this.bufferingCb?.(), { signal });\n video.addEventListener(\"timeupdate\", () => chaseLiveEdge(video), { signal });\n\n let mod: FlvModule;\n try {\n // Literal specifier on purpose: a bundler must be able to see it and inline\n // the library into the single-file drop-in build. A computed specifier\n // leaves a bare module request in the output, which no browser can resolve.\n // Types come from internal/flv.js.d.ts.\n mod = await import(\"flv.js\");\n } catch (cause) {\n throw mebiusError(\"CONNECTION_FAILED\", \"Balanced playback support failed to load.\", cause);\n }\n\n const flvjs = mod.default;\n if (!flvjs.isSupported()) {\n throw mebiusError(\"CONNECTION_FAILED\", \"Balanced playback is not supported in this browser.\");\n }\n\n this.attachPlayer(flvjs, video, url, true);\n // NOT awaited yet: video.play() only settles once playback actually begins,\n // so awaiting it here would hang on exactly the stall the retry below exists\n // to break. The outcome is collected after we know the stream moved.\n const firstPlay = playWithAutoplayFallback(video);\n firstPlay.catch(() => undefined); // the retry path is the error handler\n\n // Second attempt, without the audio track, when the first one never moves.\n //\n // An FLV header can claim audio the stream does not carry — a WebRTC\n // broadcast reaches the CDN as video-only (Opus cannot ride in FLV) and the\n // header still advertises audio. flv.js then holds playback forever waiting\n // for an audio init segment that never comes: metadata parses, the video\n // init segment lands, and currentTime stays at 0.\n //\n // Telling flv.js up front to ignore audio would silence every publisher that\n // DOES send AAC, so it cannot be the default — this only fires once, only\n // when the stream has demonstrably not started, and it is far cheaper than\n // the player's 8s route watchdog for a case that is otherwise unrecoverable.\n if (await stalledWithData(video, AUDIO_RETRY_MS)) {\n this.teardownPlayer();\n this.attachPlayer(flvjs, video, url, false);\n this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;\n return;\n }\n this.mutedByPolicy = (await firstPlay).mutedByPolicy;\n }\n\n private attachPlayer(\n flvjs: FlvModule[\"default\"],\n video: HTMLVideoElement,\n url: string,\n withAudio: boolean,\n ): void {\n const player = flvjs.createPlayer(\n { type: \"flv\", url, isLive: true, ...(withAudio ? {} : { hasAudio: false }) },\n LIVE_FLV_CONFIG,\n );\n this.player = player;\n player.on(flvjs.Events.ERROR ?? \"error\", () => this.bufferingCb?.());\n player.attachMediaElement(video);\n player.load();\n }\n\n private teardownPlayer(): void {\n if (!this.player) return;\n this.player.unload();\n this.player.detachMediaElement();\n this.player.destroy();\n this.player = null;\n }\n\n /** True when playback only started because the element had to be muted. */\n mutedByPolicy = false;\n\n async stop(): Promise<void> {\n this.listeners?.abort();\n this.listeners = null;\n this.teardownPlayer();\n if (this.video) {\n this.video.removeAttribute(\"src\");\n this.video.load();\n }\n this.video = null;\n }\n\n /** Decoded-frame count and timestamp of the previous getStats() call. */\n private lastFrames: { count: number; atMs: number } | null = null;\n\n /**\n * Real playback statistics for this route.\n *\n * Both numbers used to be hardcoded zeros, which is worse than reporting\n * nothing: the dashboard cannot tell a measured 0 kbps from an unmeasured\n * one, so every flv.js viewer in production showed a downlink of 0 and the\n * column read as a total outage. flv.js measures throughput itself\n * (`statisticsInfo.speed`, KB/s), and the element counts decoded frames, so\n * frame rate is the delta between two calls. Anything genuinely unavailable\n * is left undefined rather than zeroed.\n */\n async getStats(): Promise<PlaybackStats | null> {\n if (!this.video) return null;\n\n const speedKBs = this.player?.statisticsInfo?.speed;\n const bitrateKbps = typeof speedKBs === \"number\" ? Math.round(speedKBs * 8) : undefined;\n\n let framesPerSecond: number | undefined;\n const q = this.video.getVideoPlaybackQuality?.();\n const count = q?.totalVideoFrames;\n const atMs = Date.now();\n if (typeof count === \"number\") {\n const prev = this.lastFrames;\n if (prev && atMs > prev.atMs && count >= prev.count) {\n framesPerSecond = Math.round(((count - prev.count) * 1000) / (atMs - prev.atMs));\n }\n this.lastFrames = { count, atMs };\n }\n\n return { bitrateKbps, framesPerSecond, latencyMs: undefined };\n }\n}\n","/**\n * INTERNAL — transport interfaces + auto-selection.\n *\n * The public API never names a transport; it only asks for a playback *mode*.\n * This factory maps a mode to the right hidden delivery mechanism.\n */\nimport type { BroadcastStats, MebiusDelivery, PlaybackMode, PlaybackStats } from \"../types.js\";\nimport type { SignalingClient } from \"./signaling.js\";\nimport { WhipPublishTransport } from \"./publish-transport.js\";\nimport { WhepViewTransport } from \"./ll-view-transport.js\";\nimport { HlsViewTransport } from \"./scale-view-transport.js\";\nimport { FlvViewTransport } from \"./balanced-view-transport.js\";\n\n/** Hidden transport that sends a captured stream to the gateway. */\nexport interface PublishTransport {\n start(streamId: string, stream: MediaStream): Promise<void>;\n stop(): Promise<void>;\n getStats(): Promise<BroadcastStats | null>;\n /** Swap the outgoing video track in place (e.g. on camera switch). */\n replaceVideoTrack(track: MediaStreamTrack | null): Promise<void>;\n}\n\n/**\n * Wire name of a playback transport, reported with telemetry.\n *\n * The dashboard has a \"player\" column and nothing ever filled it — the SDK sent\n * no such field, so the backend defaulted every viewer session to `flv_js`\n * whatever had actually played it. The route chosen is exactly the sort of\n * thing that explains a bad session, so it is worth one string per batch.\n */\nexport type ViewTransportKind = \"whep\" | \"flv_js\" | \"hls\";\n\n/** Hidden transport that renders a remote stream into a video element. */\nexport interface ViewTransport {\n /** Which mechanism this is, for telemetry. Never shown to the viewer. */\n readonly kind: ViewTransportKind;\n start(streamId: string, video: HTMLVideoElement): Promise<void>;\n stop(): Promise<void>;\n getStats(): Promise<PlaybackStats | null>;\n /** Fired by the transport when playback reaches its natural end. */\n onEnded(cb: () => void): void;\n /** Fired when the transport (re)enters a buffering state. */\n onBuffering(cb: () => void): void;\n}\n\nexport function createPublishTransport(signaling: SignalingClient): PublishTransport {\n return new WhipPublishTransport(signaling);\n}\n\n/**\n * Delivery kinds the gateway may offer. Neutral by design — the gateway names\n * an intent (\"fast\", \"wide\"), never a protocol, and this module is the only\n * place that decides which transport serves which intent. That is what lets the\n * gateway change protocol without an SDK release.\n */\nconst KIND_FAST = \"fast\";\nconst KIND_WIDE = \"wide\";\nconst KIND_LOCAL = \"local\";\n\n/** True when this browser can play a Media-Source-based stream (not iOS Safari). */\nfunction canPlayBuffered(): boolean {\n return typeof MediaSource !== \"undefined\";\n}\n\nfunction transportFor(\n kind: string,\n path: string,\n signaling: SignalingClient,\n): ViewTransport | null {\n if (kind === KIND_FAST) return canPlayBuffered() ? new FlvViewTransport(signaling, path) : null;\n if (kind === KIND_WIDE || kind === KIND_LOCAL) return new HlsViewTransport(signaling, path);\n // An unknown kind is a newer gateway talking to an older SDK. Skip it rather\n // than guess: the list is ordered, so the next entry is the intended fallback.\n return null;\n}\n\n/**\n * Ordered list of transports to try for a playback mode, most-preferred first.\n *\n * Returning a LIST rather than one transport is the whole point: a transport can\n * connect and still deliver no frames (a CDN edge with no ingest yet, MSE\n * failing mid-init, WebRTC reporting `connected` with zero frames). The player\n * walks this list on a watchdog, so a dead first choice degrades instead of\n * showing a black frame.\n *\n * `deliveries` comes from the gateway and is already in the gateway's preferred\n * order; ordering policy therefore lives server-side, not here.\n */\nexport function createViewCandidates(\n mode: PlaybackMode,\n signaling: SignalingClient,\n deliveries: readonly MebiusDelivery[] = [],\n): ViewTransport[] {\n const fromGateway = (kinds: readonly string[]): ViewTransport[] =>\n deliveries\n .filter((d) => kinds.includes(d.kind))\n .map((d) => transportFor(d.kind, d.path, signaling))\n .filter((t): t is ViewTransport => t !== null);\n\n // Every mode ends with the origin playlist, reachable with no delivery list at\n // all. Without this, a caller that never passes `deliveries` (every 0.x\n // integration today) would get an empty candidate list and fail to play.\n const originFallback = new HlsViewTransport(signaling);\n const allKinds = [KIND_FAST, KIND_WIDE, KIND_LOCAL];\n\n switch (mode) {\n case \"low-latency\":\n // The real-time pull is not in `deliveries` — it is signaled, not fetched.\n return [new WhepViewTransport(signaling), ...fromGateway(allKinds), originFallback];\n case \"balanced\":\n return [...fromGateway(allKinds), originFallback];\n case \"scale\":\n return [...fromGateway([KIND_WIDE, KIND_LOCAL]), originFallback];\n case \"auto\":\n // Take the gateway ordering verbatim: it knows which paths are actually\n // serving and what each one costs to serve.\n return [...fromGateway(allKinds), originFallback];\n }\n}\n\n/**\n * Single-transport selection, kept for callers written against 0.1/0.2.\n * @deprecated Use {@link createViewCandidates} — a single transport cannot fall\n * back, so a dead delivery becomes a black frame.\n */\nexport function createViewTransport(\n mode: PlaybackMode,\n signaling: SignalingClient,\n): ViewTransport {\n return createViewCandidates(mode, signaling)[0]!;\n}\n","/**\n * INTERNAL — quality-of-experience reporting.\n *\n * Mebius shows per-stream publish/playback quality (bitrate, fps, rtt, freezes)\n * in the integrator's dashboard, and derives viewer minutes from play-side\n * reports. Both come from here: nothing else in the product observes a viewer's\n * actual experience, because only the client can see it.\n *\n * The credential and the endpoint both come from the token your backend already\n * fetches — the SDK is never configured with them and never learns which tenant\n * it belongs to. The token is scoped by signed claims to one stream and one\n * project, so it can only ever write its own telemetry.\n */\n\n/**\n * SDK identifier reported with each batch.\n *\n * MUST match package.json. It had drifted (0.4.6 while the package shipped\n * 0.4.7), so the dashboard attributed every session to a version that was not\n * the one running — the first thing you check when a release regresses.\n */\nconst SDK_VERSION = \"web/0.4.8\";\n\n/** How often a batch is sent. Long enough to batch, short enough to survive a tab close. */\nconst FLUSH_INTERVAL_MS = 15_000;\n\n/**\n * Server cap on samples per request (beaconSchema: max 64). Flushing at this\n * point rather than growing without bound means a long broadcast on a throttled\n * network drops nothing to a 400.\n */\nconst MAX_BATCH = 64;\n\nexport interface QoeSample {\n /** Unix seconds. The server keys samples on this. */\n ts: number;\n bitrateKbps?: number;\n fps?: number;\n rttMs?: number;\n packetLossPct?: number;\n freezeMs?: number;\n firstFrameMs?: number;\n}\n\nexport interface TelemetryTarget {\n /** Absolute beacon URL, as returned with the token. */\n url: string;\n /** Beacon credential, as returned with the token. */\n token: string;\n}\n\n/** Best-effort environment description. Absent fields are simply not reported. */\nfunction describeDevice(): { os?: string; sdk: string } {\n const nav = typeof navigator === \"undefined\" ? undefined : navigator;\n // navigator.platform is deprecated but still the only universally available\n // hint; userAgentData exists on Chromium only. Neither is load-bearing — the\n // dashboard shows it as context, so a missing value costs nothing.\n const uaData = (nav as { userAgentData?: { platform?: string } } | undefined)?.userAgentData;\n return { os: uaData?.platform || nav?.platform || undefined, sdk: SDK_VERSION };\n}\n\nfunction describeNetwork(): { type?: string } | undefined {\n const conn = (\n typeof navigator === \"undefined\"\n ? undefined\n : (navigator as { connection?: { effectiveType?: string } }).connection\n );\n return conn?.effectiveType ? { type: conn.effectiveType } : undefined;\n}\n\n/**\n * Collects samples for one session and ships them in batches.\n *\n * Every failure path is silent by design: telemetry must never break playback or\n * a broadcast. A rejected batch is dropped rather than retried — the next batch\n * is 15s away and carries the same picture of stream health, so retrying would\n * only pile up requests against an endpoint that is already unhappy.\n */\nexport class QoeReporter {\n private readonly sessionId = randomId();\n private readonly buffer: QoeSample[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private unloadHandler: (() => void) | null = null;\n\n constructor(\n private readonly target: TelemetryTarget,\n private readonly role: \"pub\" | \"play\",\n private readonly streamId: string,\n private readonly userId?: string,\n /**\n * Which transport is playing. Reported so the dashboard's player column\n * reflects the route actually serving the viewer — the field was never\n * sent before, and the server filled the gap by assuming flv.js for\n * everyone.\n */\n private readonly playerKind?: string,\n ) {}\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => void this.flush(), FLUSH_INTERVAL_MS);\n // A viewer closing the tab is the normal end of a session, not an edge case:\n // without this the last interval of every session — and the watch time it\n // represents — is simply lost.\n if (typeof window !== \"undefined\") {\n this.unloadHandler = () => void this.flush(true);\n window.addEventListener(\"pagehide\", this.unloadHandler);\n }\n }\n\n add(sample: QoeSample): void {\n this.buffer.push(sample);\n if (this.buffer.length >= MAX_BATCH) void this.flush();\n }\n\n async stop(): Promise<void> {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n if (this.unloadHandler && typeof window !== \"undefined\") {\n window.removeEventListener(\"pagehide\", this.unloadHandler);\n }\n this.unloadHandler = null;\n await this.flush();\n }\n\n /** Send and clear the buffer. `beacon` uses sendBeacon, for page-unload flushes. */\n async flush(beacon = false): Promise<void> {\n if (!this.buffer.length) return;\n const samples = this.buffer.splice(0, MAX_BATCH);\n const body = JSON.stringify({\n sessionId: this.sessionId,\n streamId: this.streamId,\n role: this.role,\n userId: this.userId,\n playerKind: this.playerKind,\n samples,\n device: describeDevice(),\n network: describeNetwork(),\n });\n\n // sendBeacon cannot carry an Authorization header, so the credential travels\n // as a query parameter on the unload path only. Same token, same scope — the\n // server accepts either, and losing the final batch of every session was the\n // alternative.\n if (beacon && typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n const url = `${this.target.url}${this.target.url.includes(\"?\") ? \"&\" : \"?\"}token=${encodeURIComponent(this.target.token)}`;\n try {\n // text/plain, not application/json: application/json makes this a\n // non-simple request, so the browser must clear a CORS preflight first —\n // during unload, when it frequently never completes and the beacon is\n // dropped without a trace. text/plain is on the safelist and goes\n // straight out. The server reads the body as JSON either way; content\n // type is not what it parses on.\n navigator.sendBeacon(url, new Blob([body], { type: \"text/plain\" }));\n } catch {\n /* nothing to do at unload */\n }\n return;\n }\n\n try {\n await fetch(this.target.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${this.target.token}` },\n body,\n keepalive: true,\n });\n } catch {\n /* telemetry never breaks the stream */\n }\n }\n}\n\nfunction randomId(): string {\n const c = typeof crypto === \"undefined\" ? undefined : crypto;\n if (c?.randomUUID) return c.randomUUID();\n // Older Safari/WebView: a collision only merges two sessions' samples, so a\n // cheap fallback beats refusing to report at all.\n return `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n","import { mebiusError } from \"./errors.js\";\nimport { TypedEmitter, type BroadcasterEventMap } from \"./events.js\";\nimport { resolveVideoElement } from \"./internal/view-target.js\";\nimport type { SignalingClient } from \"./internal/signaling.js\";\nimport { createPublishTransport, type PublishTransport } from \"./internal/transport.js\";\nimport { QoeReporter, type TelemetryTarget } from \"./internal/telemetry.js\";\nimport type { BroadcasterOptions, MediaConstraint, ViewTarget } from \"./types.js\";\n\nconst STATS_INTERVAL_MS = 2000;\n\n/**\n * Publishes the local camera/microphone to a Mebius stream.\n *\n * Create one with {@link MebiusClient.createBroadcaster}, then\n * {@link MebiusBroadcaster.start | start} it with a stream id.\n */\nexport class MebiusBroadcaster extends TypedEmitter<BroadcasterEventMap> {\n private readonly transport: PublishTransport;\n private stream: MediaStream | null = null;\n private facingMode: \"user\" | \"environment\" = \"user\";\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n private started = false;\n private reporter: QoeReporter | null = null;\n\n /** @internal */\n constructor(\n signaling: SignalingClient,\n private readonly options: BroadcasterOptions,\n private readonly telemetry: TelemetryTarget | null = null,\n private readonly userId?: string,\n ) {\n super();\n this.transport = createPublishTransport(signaling);\n }\n\n /** Begin broadcasting under the given stream id. */\n async start(streamId: string): Promise<void> {\n if (this.started) return;\n this.stream = await this.capture();\n await this.transport.start(streamId, this.stream);\n this.started = true;\n // Report against the id the caller published under, so a sample can be traced\n // back to the stream row the dashboard shows.\n if (this.telemetry) {\n this.reporter = new QoeReporter(this.telemetry, \"pub\", streamId, this.userId);\n this.reporter.start();\n }\n this.startStats();\n this.emit(\"started\", { streamId });\n }\n\n /** Stop broadcasting and release the camera/microphone. */\n async stop(): Promise<void> {\n this.stopStats();\n // Flush before tearing down the transport: the last interval of a broadcast is\n // the one most likely to explain why it ended.\n await this.reporter?.stop();\n this.reporter = null;\n await this.transport.stop();\n this.stream?.getTracks().forEach((t) => t.stop());\n this.stream = null;\n this.started = false;\n this.emit(\"stopped\", undefined);\n }\n\n /** Flip between front and back camera (where available). */\n async switchCamera(): Promise<void> {\n if (!this.stream) return;\n this.facingMode = this.facingMode === \"user\" ? \"environment\" : \"user\";\n const next = await navigator.mediaDevices.getUserMedia({\n video: { facingMode: this.facingMode },\n audio: false,\n });\n const newTrack = next.getVideoTracks()[0] ?? null;\n const oldTrack = this.stream.getVideoTracks()[0];\n if (oldTrack) {\n this.stream.removeTrack(oldTrack);\n oldTrack.stop();\n }\n if (newTrack) this.stream.addTrack(newTrack);\n await this.transport.replaceVideoTrack(newTrack);\n }\n\n /** Mute or unmute the outgoing microphone. */\n setMicEnabled(enabled: boolean): void {\n this.stream?.getAudioTracks().forEach((t) => (t.enabled = enabled));\n }\n\n /** Enable or disable the outgoing camera. */\n setCameraEnabled(enabled: boolean): void {\n this.stream?.getVideoTracks().forEach((t) => (t.enabled = enabled));\n }\n\n /**\n * Web convenience: render the local camera preview into a `<video>` element.\n * This is the web analog of the mobile preview view; it does not affect what\n * is broadcast.\n */\n attachPreview(target: ViewTarget): void {\n if (!this.stream) return;\n const video = resolveVideoElement(target);\n video.srcObject = this.stream;\n video.muted = true;\n void video.play().catch(() => undefined);\n }\n\n private async capture(): Promise<MediaStream> {\n const video = normalize(this.options.video, true);\n const audio = normalize(this.options.audio, true);\n try {\n return await navigator.mediaDevices.getUserMedia({ video, audio });\n } catch (cause) {\n throw mebiusError(\"PERMISSION_DENIED\", undefined, cause);\n }\n }\n\n private startStats(): void {\n this.statsTimer = setInterval(async () => {\n const stats = await this.transport.getStats();\n if (!stats) return;\n this.emit(\"stats\", stats);\n this.reporter?.add({\n ts: Math.floor(Date.now() / 1000),\n bitrateKbps: stats.bitrateKbps,\n fps: stats.framesPerSecond,\n rttMs: stats.rttMs,\n packetLossPct: stats.packetLossPct,\n });\n }, STATS_INTERVAL_MS);\n }\n\n private stopStats(): void {\n if (this.statsTimer) clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n}\n\nfunction normalize(c: MediaConstraint | undefined, fallback: boolean): boolean | MediaTrackConstraints {\n if (c === undefined) return fallback;\n return c;\n}\n","/**\n * INTERNAL — freeze-time accounting for playback telemetry.\n *\n * The dashboard's freeze ratio read 0.00% for every session ever recorded: the\n * player knew when playback stalled and when it resumed, and never subtracted\n * the two. A measured zero and an unmeasured zero look identical downstream, so\n * this is the difference between \"playback was flawless\" and \"nobody looked\".\n *\n * Split out of MebiusPlayer so the arithmetic — which is all edge cases — can\n * be tested without a DOM or a live stream.\n */\nexport class FreezeClock {\n /** When the current stall began, or null when playback is running. */\n private stalledSinceMs: number | null = null;\n /** Stall time that has ended but has not yet been shipped with a sample. */\n private pendingMs = 0;\n\n constructor(private readonly now: () => number = Date.now) {}\n\n /** True while a stall is in progress. */\n get stalled(): boolean {\n return this.stalledSinceMs !== null;\n }\n\n /**\n * Begin a stall. Re-entering while already stalled is ignored rather than\n * restarting the clock: flv.js fires `waiting` repeatedly through a single\n * long stall, and resetting the start on each would report a fraction of the\n * freeze that actually happened.\n */\n beginStall(): void {\n if (this.stalledSinceMs === null) this.stalledSinceMs = this.now();\n }\n\n /** End the current stall and bank its duration. No-op when not stalled. */\n endStall(): void {\n if (this.stalledSinceMs === null) return;\n this.pendingMs += Math.max(0, this.now() - this.stalledSinceMs);\n this.stalledSinceMs = null;\n }\n\n /**\n * Freeze milliseconds to report on this tick, resetting the counter.\n *\n * A stall still in progress is counted up to now and its clock restarted, so\n * a freeze longer than the sample interval is reported while it is happening\n * rather than landing whole in whichever sample eventually follows it. Every\n * millisecond is attributed exactly once — never dropped, never double-counted.\n */\n take(): number {\n if (this.stalledSinceMs !== null) {\n const now = this.now();\n this.pendingMs += Math.max(0, now - this.stalledSinceMs);\n this.stalledSinceMs = now;\n }\n const ms = this.pendingMs;\n this.pendingMs = 0;\n return ms;\n }\n\n /** Forget everything. Called when a session ends. */\n reset(): void {\n this.stalledSinceMs = null;\n this.pendingMs = 0;\n }\n}\n","import { TypedEmitter, type PlayerEventMap } from \"./events.js\";\nimport { mebiusError } from \"./errors.js\";\nimport { resolveVideoElement } from \"./internal/view-target.js\";\nimport type { SignalingClient } from \"./internal/signaling.js\";\nimport { createViewCandidates, type ViewTransport } from \"./internal/transport.js\";\nimport { QoeReporter, type TelemetryTarget } from \"./internal/telemetry.js\";\nimport { resetVideoElement } from \"./internal/autoplay.js\";\nimport { FreezeClock } from \"./internal/freeze-clock.js\";\nimport type { MebiusDelivery, PlayerOptions, ViewTarget } from \"./types.js\";\n\nconst STATS_INTERVAL_MS = 2000;\n\n/**\n * How long a route gets to produce its first frame before we move to the next.\n *\n * Not arbitrary: a route can report a healthy connection and still deliver\n * nothing — a CDN edge that has no ingest yet answers 200 with an empty stream,\n * and a real-time connection reports `connected` while zero frames arrive. The\n * only trustworthy signal is the picture actually advancing, so that is what is\n * measured. 8s is long enough to survive a slow first segment on mobile data and\n * short enough that a viewer has not yet left.\n */\nconst FIRST_FRAME_TIMEOUT_MS = 8000;\n\n/**\n * Which player currently drives a given element.\n *\n * A second player on the same element is an ordinary thing for an app to do —\n * a \"play\" button pressed twice, a component remounting — and it used to\n * orphan the first one. The new player resets the element, which detaches the\n * old MediaSource and removes its SourceBuffers, but the old player is still\n * running: its buffered-media library keeps polling buffers that no longer\n * belong to anything and floods the console with\n *\n * InvalidStateError: Failed to read the 'buffered' property from\n * 'SourceBuffer': This SourceBuffer has been removed from the parent media\n * source.\n *\n * The element can only have one owner, so taking ownership retires the\n * previous one. WeakMap because an element that goes out of scope must not be\n * kept alive by this bookkeeping.\n */\nconst ELEMENT_OWNER = new WeakMap<HTMLVideoElement, MebiusPlayer>();\n\n/**\n * Plays a Mebius stream into a `<video>` element.\n *\n * Create one with {@link MebiusClient.createPlayer}, optionally choosing a\n * playback {@link PlaybackMode | mode}; Mebius selects the delivery route, and\n * moves to the next one by itself if the current one stops producing frames.\n */\nexport class MebiusPlayer extends TypedEmitter<PlayerEventMap> {\n private readonly candidates: ViewTransport[];\n private transport: ViewTransport | null = null;\n private video: HTMLVideoElement | null = null;\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n private playing = false;\n private reporter: QoeReporter | null = null;\n /** True between a `buffering` event and the element actually resuming. */\n private stalled = false;\n /** Measures how long playback was actually frozen; see FreezeClock. */\n private readonly freeze = new FreezeClock();\n /** Cancels element listeners bound for the lifetime of one play(). */\n private elementListeners: AbortController | null = null;\n\n /** @internal */\n constructor(\n signaling: SignalingClient,\n options: PlayerOptions = {},\n deliveries: readonly MebiusDelivery[] = [],\n private readonly telemetry: TelemetryTarget | null = null,\n private readonly userId?: string,\n ) {\n super();\n this.candidates = createViewCandidates(options.mode ?? \"auto\", signaling, deliveries);\n }\n\n /** Start playing `streamId` into the given video element or selector. */\n async play(streamId: string, viewTarget: ViewTarget): Promise<void> {\n if (this.playing) return;\n const video = resolveVideoElement(viewTarget);\n // One element, one player. Whatever was driving it is finished, and stopping\n // it here is what keeps a second play() from leaving a live transport\n // attached to a MediaSource this one is about to replace.\n const previous = ELEMENT_OWNER.get(video);\n if (previous && previous !== this) await previous.stop();\n ELEMENT_OWNER.set(video, this);\n this.video = video;\n\n // `buffering` had no counterpart: an app that showed a spinner on it had\n // nothing to hide the spinner on, so a single mid-stream stall left the UI\n // reading \"buffering\" over perfectly smooth video for the rest of the\n // session. The element knows when it resumed; re-emitting `playing` there\n // gives every consumer the other half of the pair without inventing an\n // event they would have to know to handle.\n this.elementListeners = new AbortController();\n video.addEventListener(\n \"playing\",\n () => {\n if (!this.stalled || !this.playing) return;\n this.stalled = false;\n this.freeze.endStall();\n this.emit(\"playing\", { streamId });\n },\n { signal: this.elementListeners.signal },\n );\n\n // Measured across route attempts, not from the accepted route: a viewer who\n // waited through a dead edge waited, and reporting only the winning route's\n // time would hide exactly the delay worth knowing about.\n const startedAtMs = Date.now();\n let lastError: unknown = null;\n for (const candidate of this.candidates) {\n try {\n // Hand every route a clean element. Routes attach differently — a\n // MediaStream via srcObject, a playlist via src, MSE via attachMedia —\n // and while srcObject is set the element ignores src entirely. Without\n // this, one failed real-time attempt kept every later route black while\n // its own logs looked healthy.\n resetVideoElement(video);\n this.attach(candidate);\n await candidate.start(streamId, video);\n // start() resolving only means the route was opened, not that it is\n // delivering. Confirm with the picture itself before accepting it.\n if (await hasFirstFrame(video)) {\n this.transport = candidate;\n this.playing = true;\n if (this.telemetry) {\n this.reporter = new QoeReporter(\n this.telemetry,\n \"play\",\n streamId,\n this.userId,\n candidate.kind,\n );\n this.reporter.start();\n // One sample at join time carries the join delay. Viewer minutes are\n // derived from the span between a session's first and last sample, so\n // a viewer who leaves before the first stats tick still counts.\n this.reporter.add({ ts: Math.floor(Date.now() / 1000), firstFrameMs: Date.now() - startedAtMs });\n }\n this.startStats();\n this.emit(\"playing\", { streamId });\n return;\n }\n lastError = mebiusError(\"CONNECTION_FAILED\", \"A Mebius route delivered no video.\");\n } catch (cause) {\n lastError = cause;\n }\n // Tear the dead route down before opening the next one: leaving it attached\n // keeps a peer connection or a media source bound to the same element, and\n // the next route then renders into a element that is not free.\n await candidate.stop().catch(() => undefined);\n }\n\n // Release the element before giving up. Holding ownership of an element we\n // are not playing into would make the next player await a stop() on this\n // dead one, and would keep this player object alive through the map for as\n // long as the element exists.\n this.elementListeners?.abort();\n this.elementListeners = null;\n if (ELEMENT_OWNER.get(video) === this) ELEMENT_OWNER.delete(video);\n this.video = null;\n throw lastError ?? mebiusError(\"CONNECTION_FAILED\", \"No Mebius route could play this stream.\");\n }\n\n /** Stop playback and detach from the video element. */\n async stop(): Promise<void> {\n this.elementListeners?.abort();\n this.elementListeners = null;\n if (this.video && ELEMENT_OWNER.get(this.video) === this) {\n ELEMENT_OWNER.delete(this.video);\n }\n this.stalled = false;\n this.freeze.reset();\n this.stopStats();\n await this.reporter?.stop();\n this.reporter = null;\n await this.transport?.stop();\n this.transport = null;\n this.video = null;\n this.playing = false;\n }\n\n /**\n * Set output volume in the range 0..1.\n *\n * Any volume above zero also unmutes. Playback often starts muted — the\n * element may carry `muted` in the app's own markup, and the SDK itself mutes\n * and retries when the browser refuses to autoplay with sound — and\n * `video.volume` has no audible effect while `muted` is set. Setting volume\n * without clearing it meant an app whose only audio control was a slider\n * could never produce sound: the value moved, the stream stayed silent, and\n * nothing reported a problem.\n *\n * Volume 0 mutes rather than merely turning the level down, so a UI that\n * drags to zero also survives a later unmute at the element level.\n */\n setVolume(volume: number): void {\n const v = Math.min(1, Math.max(0, volume));\n if (!this.video) return;\n this.video.volume = v;\n this.video.muted = v === 0;\n }\n\n private attach(transport: ViewTransport): void {\n transport.onEnded(() => {\n // Only the route currently serving may end playback. A route we already\n // abandoned firing late must not close a stream that is playing fine.\n if (this.transport !== transport) return;\n this.playing = false;\n this.stopStats();\n // A stream ending is a session ending: flush now or the watch time since the\n // last batch is never counted.\n void this.reporter?.stop();\n this.reporter = null;\n this.emit(\"ended\", undefined);\n });\n transport.onBuffering(() => {\n if (this.transport !== transport) return;\n // Re-entering `buffering` while already stalled must not restart the\n // clock: flv.js fires `waiting` repeatedly through one long stall, and\n // resetting the start each time would report a fraction of the freeze.\n this.freeze.beginStall();\n this.stalled = true;\n this.emit(\"buffering\", undefined);\n });\n }\n\n private startStats(): void {\n this.statsTimer = setInterval(async () => {\n const stats = await this.transport?.getStats();\n // Freeze time is reported even when the transport has no stats to give:\n // a route too stalled to produce statistics is precisely the one whose\n // freezes matter most.\n const freezeMs = this.freeze.take();\n if (!stats) {\n if (freezeMs > 0) this.reporter?.add({ ts: Math.floor(Date.now() / 1000), freezeMs });\n return;\n }\n this.emit(\"stats\", stats);\n this.reporter?.add({\n ts: Math.floor(Date.now() / 1000),\n bitrateKbps: stats.bitrateKbps,\n fps: stats.framesPerSecond,\n freezeMs,\n });\n }, STATS_INTERVAL_MS);\n }\n\n private stopStats(): void {\n if (this.statsTimer) clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n}\n\n/**\n * Resolves true once the element is actually rendering, false on timeout.\n *\n * `timeupdate` is the signal rather than `readyState` because readiness only\n * says data arrived; a live stream that stalls right after its first buffer can\n * report ready forever without the picture moving.\n */\nfunction hasFirstFrame(video: HTMLVideoElement): Promise<boolean> {\n if (video.currentTime > 0 && !video.paused) return Promise.resolve(true);\n return new Promise((resolve) => {\n const done = (ok: boolean) => {\n clearTimeout(timer);\n video.removeEventListener(\"timeupdate\", onTime);\n resolve(ok);\n };\n const onTime = () => {\n if (video.currentTime > 0) done(true);\n };\n const timer = setTimeout(() => done(false), FIRST_FRAME_TIMEOUT_MS);\n video.addEventListener(\"timeupdate\", onTime);\n });\n}\n","/**\n * INTERNAL — gateway signaling client.\n *\n * This module is the ONE place that knows the wire protocols Mebius uses\n * behind the scenes (WHIP for publishing, WHEP for low-latency viewing, HLS\n * for scale viewing). None of these terms ever escape `internal/` — the public\n * API speaks only in Mebius vocabulary.\n *\n * The gateway HTTP contract (mebius-stream-engine public edge):\n * - Publish: POST {gateway}/whip/{streamId}?token=<jwt> (application/sdp)\n * - View low-latency: POST {gateway}/whep/{streamId}?token=<jwt> (application/sdp)\n * - View scale: GET {gateway}/live/{streamId}/index.m3u8?token=<jwt>\n * - Teardown: DELETE {resourceUrl}\n *\n * The engine validates the token from the `?token=` QUERY parameter (its\n * MediaMTX auth hook + HLS playback gate both read the query, not a header).\n * We still send `Authorization: Bearer <token>` for gateways that prefer it,\n * but the query token is what the engine actually enforces. HLS segment URLs\n * inside the playlist inherit `?token=` automatically (the engine rewrites the\n * m3u8), so no extra header is needed for segment fetches.\n */\nimport { mebiusError } from \"../errors.js\";\n\n/**\n * A media session direction. Deliberately neutral vocabulary (\"publish\" /\n * \"view\") so that if a type bundler ever inlines this into the public `.d.ts`\n * (e.g. via a private field reference), no wire-protocol term leaks to clients.\n * The concrete path segment is derived inside {@link SignalingClient} only.\n */\nexport type SessionKind = \"publish\" | \"view\";\n\nexport interface SessionResult {\n /** The remote session answer returned by the gateway. */\n answer: string;\n /** Resource URL to DELETE on teardown, if the gateway returned one. */\n resourceUrl: string | null;\n}\n\nexport class SignalingClient {\n constructor(\n private readonly gateway: string,\n private readonly token: string,\n ) {}\n\n private base(): string {\n return this.gateway.replace(/\\/+$/, \"\");\n }\n\n private headers(contentType?: string): HeadersInit {\n const h: Record<string, string> = { Authorization: `Bearer ${this.token}` };\n if (contentType) h[\"Content-Type\"] = contentType;\n return h;\n }\n\n /** Append the access token as a query param (the form the engine enforces). */\n private withToken(url: string): string {\n const sep = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${sep}token=${encodeURIComponent(this.token)}`;\n }\n\n // Build the playlist URL used by scale-mode playback (HLS path, hidden). The\n // engine serves the playlist under /live/{id}/index.m3u8 and requires the\n // token in the query; segment URIs in the playlist inherit it automatically.\n /**\n * Absolute, tokenized URL for a gateway-relative delivery path handed to us\n * by the gateway (`deliveries[].path`). The gateway decides which paths exist\n * and in what order; the SDK only resolves them against its own base and\n * attaches the access token. Anything that is not a plain gateway-relative\n * path is rejected rather than fetched: an absolute URL there would send the\n * token to a host we did not choose.\n */\n deliveryUrl(path: string): string {\n if (!path.startsWith(\"/\") || path.startsWith(\"//\") || path.includes(\"://\")) {\n throw mebiusError(\"CONNECTION_FAILED\", \"The gateway returned an unusable delivery path.\");\n }\n return this.withToken(`${this.base()}${path}`);\n }\n\n /** Playlist URL for scale-mode playback. */\n scalePlaylistUrl(streamId: string): string {\n return this.withToken(`${this.base()}/live/${encodeURIComponent(streamId)}/index.m3u8`);\n }\n\n // Maps a neutral session kind to the concrete signaling path segment. This\n // mapping (publish -> WHIP, view -> WHEP) lives ONLY in this method body, so\n // the protocol names never appear in any exported type signature.\n private pathFor(kind: SessionKind): string {\n return kind === \"publish\" ? \"whip\" : \"whep\";\n }\n\n // Performs the offer/answer exchange for a publish or a low-latency view\n // session. Protocol detail kept inside the method body so it never leaks into\n // the bundled public .d.ts.\n /**\n * Run the session offer/answer exchange. Throws a {@link MebiusError} with a\n * Mebius-flavored code on failure — never the raw protocol name.\n */\n async exchangeSession(\n kind: SessionKind,\n streamId: string,\n offer: string,\n ): Promise<SessionResult> {\n const url = this.withToken(`${this.base()}/${this.pathFor(kind)}/${encodeURIComponent(streamId)}`);\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n headers: this.headers(\"application/sdp\"),\n body: offer,\n });\n } catch (cause) {\n throw mebiusError(\"CONNECTION_FAILED\", undefined, cause);\n }\n\n if (res.status === 401 || res.status === 403) {\n throw mebiusError(\"TOKEN_EXPIRED\");\n }\n if (res.status === 404) {\n throw mebiusError(\"STREAM_NOT_FOUND\");\n }\n if (!res.ok) {\n throw mebiusError(\"CONNECTION_FAILED\", `Mebius gateway returned ${res.status}.`);\n }\n\n const answer = await res.text();\n const location = res.headers.get(\"Location\");\n const resourceUrl = location ? new URL(location, url).toString() : null;\n return { answer, resourceUrl };\n }\n\n /** Tear down a previously-created session resource. Best-effort. */\n async deleteResource(resourceUrl: string | null): Promise<void> {\n if (!resourceUrl) return;\n try {\n await fetch(resourceUrl, { method: \"DELETE\", headers: this.headers() });\n } catch {\n // Teardown is best-effort; the gateway reaps idle sessions anyway.\n }\n }\n}\n","/**\n * INTERNAL — read (NOT verify) a Mebius token's expiry.\n *\n * The token is a short-lived JWT minted by the developer's backend. The client\n * never verifies it (only the gateway can) — it just peeks at `exp` so it can\n * proactively surface a TOKEN_EXPIRED error and let the app refresh.\n */\nexport interface TokenInfo {\n /** Expiry as a UNIX epoch in milliseconds, if present. */\n expiresAtMs: number | null;\n}\n\nfunction base64UrlDecode(input: string): string {\n const padded = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad = padded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n const b64 = padded + pad;\n if (typeof atob === \"function\") return atob(b64);\n // Node fallback (tests / SSR).\n return Buffer.from(b64, \"base64\").toString(\"binary\");\n}\n\nexport function readToken(token: string): TokenInfo {\n const parts = token.split(\".\");\n if (parts.length < 2) return { expiresAtMs: null };\n try {\n const payload = JSON.parse(base64UrlDecode(parts[1] ?? \"\")) as { exp?: number };\n return { expiresAtMs: typeof payload.exp === \"number\" ? payload.exp * 1000 : null };\n } catch {\n return { expiresAtMs: null };\n }\n}\n","import { MebiusBroadcaster } from \"./broadcaster.js\";\nimport { mebiusError } from \"./errors.js\";\nimport { TypedEmitter, type ClientEventMap } from \"./events.js\";\nimport { MebiusPlayer } from \"./player.js\";\nimport { SignalingClient } from \"./internal/signaling.js\";\nimport type { TelemetryTarget } from \"./internal/telemetry.js\";\nimport { readToken } from \"./internal/token.js\";\nimport type {\n BroadcasterOptions,\n MebiusDelivery,\n MebiusInitOptions,\n PlayerOptions,\n} from \"./types.js\";\n\n/**\n * A live connection to Mebius. Obtain one from {@link Mebius.connect}, then\n * create broadcasters and players from it.\n */\nexport class MebiusClient extends TypedEmitter<ClientEventMap> {\n private readonly signaling: SignalingClient;\n private expiryTimer: ReturnType<typeof setTimeout> | null = null;\n private connected = false;\n\n /** @internal */\n constructor(\n config: MebiusInitOptions,\n private readonly token: string,\n private readonly deliveries: readonly MebiusDelivery[] = [],\n private readonly telemetry: TelemetryTarget | null = null,\n private readonly userId?: string,\n ) {\n super();\n this.signaling = new SignalingClient(config.gateway, token);\n }\n\n /** @internal Called by {@link Mebius.connect}. */\n open(): void {\n const { expiresAtMs } = readToken(this.token);\n const now = Date.now();\n if (expiresAtMs !== null && expiresAtMs <= now) {\n // Surface asynchronously so listeners attached after connect() still fire.\n queueMicrotask(() => this.emit(\"error\", mebiusError(\"TOKEN_EXPIRED\")));\n return;\n }\n this.connected = true;\n if (expiresAtMs !== null) {\n this.expiryTimer = setTimeout(\n () => this.emit(\"error\", mebiusError(\"TOKEN_EXPIRED\")),\n Math.max(0, expiresAtMs - now),\n );\n }\n queueMicrotask(() => this.emit(\"connected\", undefined));\n }\n\n /** Create a broadcaster bound to this connection. */\n createBroadcaster(options: BroadcasterOptions = {}): MebiusBroadcaster {\n this.assertConnected();\n return new MebiusBroadcaster(this.signaling, options, this.telemetry, this.userId);\n }\n\n /** Create a player bound to this connection. */\n createPlayer(options: PlayerOptions = {}): MebiusPlayer {\n this.assertConnected();\n return new MebiusPlayer(this.signaling, options, this.deliveries, this.telemetry, this.userId);\n }\n\n /**\n * Create a monitor: a player tuned for watching a stream you are interacting\n * WITH rather than merely watching — the other side of a co-broadcast, where a\n * second or two of delay makes the interaction feel broken.\n *\n * It is a player with the delay budget spent differently, not a different API:\n * it starts on the real-time route and falls back on its own if that route\n * delivers no frames. Apps used to hand-roll this (open a real-time view, run a\n * timer, swap players when it stayed black); getting the fallback wrong showed a\n * black frame to a live audience, so it belongs here rather than in every app.\n */\n createMonitor(): MebiusPlayer {\n this.assertConnected();\n return new MebiusPlayer(this.signaling, { mode: \"low-latency\" }, this.deliveries, this.telemetry, this.userId);\n }\n\n /** Close the connection and release resources. */\n disconnect(reason?: string): void {\n if (this.expiryTimer) clearTimeout(this.expiryTimer);\n this.expiryTimer = null;\n this.connected = false;\n this.emit(\"disconnected\", { reason });\n this.removeAllListeners();\n }\n\n private assertConnected(): void {\n if (!this.connected) throw mebiusError(\"NOT_CONNECTED\");\n }\n}\n","import { MebiusClient } from \"./client.js\";\nimport { mebiusError } from \"./errors.js\";\nimport type { MebiusConnectOptions, MebiusInitOptions } from \"./types.js\";\n\nlet config: MebiusInitOptions | null = null;\n\n/**\n * Entry point to the Mebius Web SDK.\n *\n * ```ts\n * Mebius.init({ appId: \"app_123\", gateway: \"https://gateway.mebius.io\" });\n * const client = Mebius.connect({ token });\n * ```\n */\nexport const Mebius = {\n /** Configure the SDK once, before connecting. */\n init(options: MebiusInitOptions): void {\n if (!options.appId) throw mebiusError(\"UNKNOWN\", \"Mebius.init requires an appId.\");\n if (!options.gateway) throw mebiusError(\"UNKNOWN\", \"Mebius.init requires a gateway URL.\");\n config = { ...options };\n },\n\n /**\n * Connect using a short-lived token minted by your backend. Returns a\n * {@link MebiusClient}. Listen for `\"connected\"` / `\"error\"` on it.\n */\n connect(options: MebiusConnectOptions): MebiusClient {\n if (!config) {\n throw mebiusError(\"UNKNOWN\", \"Call Mebius.init() before Mebius.connect().\");\n }\n if (!options.token) throw mebiusError(\"UNKNOWN\", \"Mebius.connect requires a token.\");\n const telemetry =\n options.beaconToken && options.beaconUrl\n ? { token: options.beaconToken, url: options.beaconUrl }\n : null;\n const client = new MebiusClient(config, options.token, options.deliveries ?? [], telemetry, options.userId);\n client.open();\n return client;\n },\n\n /** @internal Reset configuration (used in tests). */\n _reset(): void {\n config = null;\n },\n};\n"],"mappings":"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","names":["STATS_INTERVAL_MS","config"]}
|