@vindral/web-sdk 4.5.1 → 4.6.0-2-g8124b200

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.5.1",
2
+ "version": "4.6.0-2-g8124b200",
3
3
  "name": "@vindral/web-sdk",
4
4
  "main": "./legacy.umd.js",
5
5
  "module": "./legacy.es.js",
package/player.d.ts CHANGED
@@ -174,6 +174,17 @@ interface DrmOptions {
174
174
  type Media = "audio" | "video" | "audio+video";
175
175
  type WebCodecsHardwareAccelerationPreference = "no-preference" | "prefer-hardware" | "prefer-software";
176
176
  type DecoderType = "mse" | "webcodecs" | "wasm";
177
+ interface MediaSessionMetadata {
178
+ title?: string;
179
+ artist?: string;
180
+ album?: string;
181
+ /** Fetched by the operating system, so the URLs should not carry credentials. */
182
+ artwork?: Array<{
183
+ src: string;
184
+ sizes?: string;
185
+ type?: string;
186
+ }>;
187
+ }
177
188
  interface Options {
178
189
  /**
179
190
  * URL to use when connecting to the stream
@@ -253,13 +264,36 @@ interface Options {
253
264
  */
254
265
  mseOpusEnabled?: boolean;
255
266
  /**
256
- * Enable or disable support for playing audio in the background for iOS devices.
267
+ * Keep playback running while the page is in the background. Enabled by default.
268
+ * When disabled, the session disconnects while hidden and reconnects when visible.
257
269
  *
258
- * Is false (disabled) by default.
270
+ * Browser media policies still apply; iOS Safari pauses hidden video regardless.
271
+ * Call `play()` again from a user gesture or media-control action to resume.
259
272
  *
260
- * Note: This may be enabled by default in a future (major) release
273
+ * When enabled, hidden sessions that are muted outside Picture-in-Picture or browser-paused
274
+ * are disconnected after the inactivity timeout; an explicit `pause()` disconnects at once.
275
+ */
276
+ backgroundPlayEnabled?: boolean;
277
+ /**
278
+ * @deprecated Use `backgroundPlayEnabled` instead.
279
+ *
280
+ * Legacy alias kept for backwards compatibility.
261
281
  */
262
282
  iosBackgroundPlayEnabled?: boolean;
283
+ /**
284
+ * Show the stream in the browser and OS media controls (notification, lock screen, toolbar) and
285
+ * let play and pause from there reach the SDK. Enabled by default.
286
+ *
287
+ * The SDK takes over `navigator.mediaSession` for the document, so a page with its own Media
288
+ * Session integration should disable this. On canvas playback paths a silent `<audio>` element is
289
+ * added to `document.body` for the controls to attach to.
290
+ */
291
+ mediaSessionEnabled?: boolean;
292
+ /**
293
+ * Title, artist and artwork for the media controls. Without it the browser derives them from the
294
+ * page. Read once at construction.
295
+ */
296
+ mediaSessionMetadata?: MediaSessionMetadata;
263
297
  /**
264
298
  * Enable or disable Adaptive Bit Rate. This allows for automatically adapting the incoming bit rate based on
265
299
  * the viewers bandwidth and thus avoiding buffering events. This also disables the
@@ -374,6 +408,9 @@ interface Options {
374
408
  * This enables platform media features such as fullscreen and picture-in-picture
375
409
  * for non-MSE playback by routing the canvas output through a `<video>` element.
376
410
  *
411
+ * On iOS the audio then plays from its own element, so it continues in the background and under
412
+ * lock. After an extended pause from the lock screen, reopening Safari may be required to resume.
413
+ *
377
414
  * Disabled by default.
378
415
  */
379
416
  streamToMediaElementEnabled?: boolean;
@@ -827,6 +864,13 @@ interface JitterModuleStatistics {
827
864
  audioJitter: JitterStatistics | undefined;
828
865
  videoJitter: JitterStatistics | undefined;
829
866
  }
867
+ type DecoyState = "detached" | "attached" | "playing" | "blocked";
868
+ interface MediaSessionModuleStatistics {
869
+ mediaSessionSupported: boolean;
870
+ mediaSessionOwner: boolean;
871
+ mediaSessionPlaybackState: MediaSessionPlaybackState;
872
+ decoyState?: DecoyState;
873
+ }
830
874
  interface MseModuleStatistics {
831
875
  quotaErrorCount: number;
832
876
  mediaSourceOpenTime: number;
@@ -938,8 +982,10 @@ interface SyncModuleStatistics {
938
982
  interface VideoPlayerStatistics {
939
983
  renderedFrameCount: number;
940
984
  rendererDroppedFrameCount: number;
985
+ /** The platform's own view of the audio context, which a mute reading cannot tell apart. */
986
+ audioContextState?: AudioContextState | "none";
941
987
  }
942
- type ModuleStatistics = AdaptivityStatistics & BufferTimeStatistics & ConnectionStatistics & ConstraintCapStatistics & DecoderStatistics & DocumentStateModulesStatistics & IncomingDataModuleStatistics & JitterModuleStatistics & MseModuleStatistics & PlaybackModuleStatistics & QualityOfServiceModuleStatistics & RenditionsModuleStatistics & SyncModuleStatistics & TelemetryModuleStatistics & VideoPlayerStatistics;
988
+ type ModuleStatistics = AdaptivityStatistics & BufferTimeStatistics & ConnectionStatistics & ConstraintCapStatistics & DecoderStatistics & DocumentStateModulesStatistics & IncomingDataModuleStatistics & JitterModuleStatistics & Partial<MediaSessionModuleStatistics> & MseModuleStatistics & PlaybackModuleStatistics & QualityOfServiceModuleStatistics & RenditionsModuleStatistics & SyncModuleStatistics & TelemetryModuleStatistics & VideoPlayerStatistics;
943
989
  type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAgentInformation"]> & {
944
990
  /**
945
991
  * Version of the @vindral/web-sdk being used.
@@ -991,6 +1037,8 @@ type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAge
991
1037
  streamToMediaElementEnabled?: boolean;
992
1038
  /** @deprecated */
993
1039
  iosMediaElementEnabled?: boolean;
1040
+ /** Whether the element carrying the audio is paused right now, on the sinks that have one. */
1041
+ mediaElementPaused?: boolean;
994
1042
  /**
995
1043
  * Average bitrate for the entire session in bits/second.
996
1044
  */
@@ -1015,8 +1063,10 @@ type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAge
1015
1063
  declare class Vindral extends Emitter<PublicVindralEvents> {
1016
1064
  #private;
1017
1065
  private static MAX_POOL_SIZE;
1018
- private static DISCONNECT_TIMEOUT;
1019
1066
  private static REMOVE_CUE_THRESHOLD;
1067
+ private static BACKGROUND_INACTIVITY_DISCONNECT_TIMEOUT;
1068
+ /** How long a pause on a visible page may still turn out to be the platform backgrounding us. */
1069
+ private static VISIBLE_PAUSE_DISCONNECT_DELAY;
1020
1070
  /**
1021
1071
  * Picture in picture
1022
1072
  */
@@ -1059,6 +1109,10 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1059
1109
  private offscreenCanvasElement?;
1060
1110
  private webCodecsRenditionSupport;
1061
1111
  private hasCalledConnect;
1112
+ private hasRequestedAudioSession;
1113
+ private isUserPaused;
1114
+ /** Only the sinks that carry the audio report this, so a paused element here means silence. */
1115
+ private isMediaElementPaused;
1062
1116
  private latestEmittedLanguages;
1063
1117
  private wakeLock;
1064
1118
  private pool;
@@ -1073,7 +1127,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1073
1127
  private readonly unextractableSamplesWarned;
1074
1128
  private sizes;
1075
1129
  private isSuspended;
1076
- private disconnectTimeout;
1130
+ private inactivityDisconnectInterval;
1131
+ private visiblePauseTimeout;
1077
1132
  private offscreenSubtitleInterval?;
1078
1133
  constructor(options: Options);
1079
1134
  /**
@@ -1375,6 +1430,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1375
1430
  private isSupportedVideoCodecProfile;
1376
1431
  private supportedAudioCodecs;
1377
1432
  private initializeDecodingModule;
1433
+ private capturesCanvasOnWebKit;
1434
+ private connectWebKitVideoOutput;
1378
1435
  /**
1379
1436
  * Set up a polling interval that forwards subtitle cues to the decoder worker
1380
1437
  * when OffscreenCanvas rendering is active. This is needed because the canvas modules'
@@ -1406,6 +1463,12 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1406
1463
  * Pauses the stream. Call .play() to resume playback again.
1407
1464
  */
1408
1465
  pause: () => void;
1466
+ /**
1467
+ * A browser-initiated decoy pause means an interruption (another app took the focus, headphones
1468
+ * unplugged), so give the audio up rather than play over it. WebKit holds the decoy on purpose.
1469
+ */
1470
+ private yieldToInterruption;
1471
+ private fromNativeControls;
1409
1472
  private registerDebugInstance;
1410
1473
  /**
1411
1474
  *
@@ -1436,8 +1499,24 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1436
1499
  */
1437
1500
  getStatistics: () => Statistics;
1438
1501
  private resetModules;
1502
+ private disconnectForInactivePage;
1503
+ private scheduleInactivityDisconnect;
1504
+ /**
1505
+ * iOS pauses the element a moment before it hides the page, and that pause reaches us while the
1506
+ * page still reports visible. Acting on it at once tears the session down, so every app switch
1507
+ * comes back as a full reconnect. Wait for the hide that follows a platform pause instead.
1508
+ * Nowhere else pauses before hiding, so there a visible pause is the viewer's at once.
1509
+ */
1510
+ private onVisiblePause;
1511
+ private disconnectForVisiblePause;
1512
+ private clearVisiblePauseDisconnect;
1513
+ private clearInactivityDisconnect;
1514
+ private isHiddenWithBackgroundPlayback;
1515
+ private isVideoOnScreen;
1516
+ private isHiddenWithBackgroundPlayDisabled;
1439
1517
  private suspend;
1440
1518
  private unsuspend;
1519
+ private resumeModules;
1441
1520
  private getRuntimeInfo;
1442
1521
  private getPlaybackBufferState;
1443
1522
  private onMediaElementBufferState;
@@ -1451,6 +1530,20 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1451
1530
  private get targetSubscription();
1452
1531
  private timeToFirstFrame;
1453
1532
  private resolvedDecoder;
1533
+ /**
1534
+ * On mobile Chromium with WebCodecs audio the play action arrives but no sound comes back until
1535
+ * the page is visible, so a hidden pause is a stop there. Reads the decoder in use.
1536
+ */
1537
+ private hiddenPauseIsStop;
1538
+ private usesSplitCanvasAudio;
1539
+ private isWebKitCanvasPath;
1540
+ private holdsDecoyWhilePaused;
1541
+ private streamsToMediaElement;
1542
+ /**
1543
+ * Safari 16.4+: a playback audio session keeps Web Audio alive in the background. Document-wide
1544
+ * and not restorable, so it waits for playback to be wanted rather than for construction.
1545
+ */
1546
+ private requestAudioSessionOnce;
1454
1547
  private willUseMediaSource;
1455
1548
  private shouldUseWebCodecs;
1456
1549
  private isWebCodecsVideoAvailable;
@@ -1461,6 +1554,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1461
1554
  * is the optimal rendering path for WebCodecs (worker renders VideoFrames directly).
1462
1555
  */
1463
1556
  private shouldUseOffscreenCanvas;
1557
+ private isBackgroundPlayEnabled;
1558
+ private shouldAllowBackgroundPlayback;
1464
1559
  }
1465
1560
  interface TelemetryModuleStatistics {
1466
1561
  /**
@@ -1613,7 +1708,8 @@ interface PublicVindralEvents {
1613
1708
  * Is emitted during connection whether the channel is live or not.
1614
1709
  *
1615
1710
  * If the channel is not live, the Vindral instance will try to reconnect until the `reconnectHandler`
1616
- * determines that no more retries should be made.
1711
+ * determines that no more retries should be made. Each attempt also emits a non-fatal `error` with
1712
+ * the code `channel_not_live`.
1617
1713
  *
1618
1714
  * Note: If the web-sdk is instantiated at the same time as you are starting the stream it is possible
1619
1715
  * that this emits false until the started state has propagated through the system.
@@ -1916,7 +2012,9 @@ export declare class Controller extends HTMLElement {
1916
2012
  "burst-enabled",
1917
2013
  "mse-enabled",
1918
2014
  "mse-opus-enabled",
2015
+ "background-play-enabled",
1919
2016
  "ios-background-play-enabled",
2017
+ "media-session-enabled",
1920
2018
  "ios-wake-lock-enabled",
1921
2019
  "stream-to-media-element-enabled",
1922
2020
  "ios-media-element-enabled",
@@ -2033,7 +2131,7 @@ export declare class PlayButton extends VindralButton {
2033
2131
  */
2034
2132
  export declare class Player extends HTMLElement {
2035
2133
  #private;
2036
- static observedAttributes: readonly ("title" | "offline" | "advanced" | "poster" | "language" | "channels" | "buffering" | "paused" | "volume" | "muted" | "duration" | "user-interacting" | "is-casting" | "cast-available" | "cast-receiver-name" | "ui-locked" | "hide-ui-on-pause" | "is-fullscreen" | "is-fullscreen-fallback" | "rendition-levels" | "rendition-level" | "max-video-bit-rate" | "max-initial-bit-rate" | "abr-enabled" | "size-based-resolution-cap-enabled" | "channel-id" | "channel-group-id" | "pip-available" | "is-pip" | "airplay-available" | "is-airplaying" | "media" | "languages" | "text-tracks" | "text-track" | "needs-user-input-video" | "needs-user-input-audio" | "authentication-token" | "volume-level" | "cast" | "airplay" | "pip" | "fullscreen" | "double-click-fullscreen" | "vu-meter" | "timeshift" | "timeshift-position" | "timeline-markers" | "poster-src" | "debug-panel-open" | "target-buffer-time" | "drift" | "playback-latency" | "connection-state" | "url" | "decoders" | "min-buffer-time" | "max-buffer-time" | "edge-url" | "cast-receiver-id" | "cast-background" | "log-level" | "max-size" | "max-audio-bit-rate" | "burst-enabled" | "mse-enabled" | "mse-opus-enabled" | "ios-background-play-enabled" | "ios-wake-lock-enabled" | "stream-to-media-element-enabled" | "ios-media-element-enabled" | "telemetry-enabled" | "video-codecs" | "drm-headers" | "drm-queryparams" | "drm-widevine-video-robustness" | "drm-widevine-audio-robustness" | "drm-playready-video-robustness" | "drm-playready-audio-robustness" | "webtransport-enabled" | "webcodecs-hardware-acceleration" | "offscreen-canvas-enabled" | "reconnect-retries" | "auto-instance-enabled" | "advanced-rendition-menu-enabled" | "refresh-poster-enabled" | "stream-poll-enabled")[];
2134
+ static observedAttributes: readonly ("title" | "offline" | "advanced" | "poster" | "language" | "channels" | "buffering" | "paused" | "volume" | "muted" | "duration" | "user-interacting" | "is-casting" | "cast-available" | "cast-receiver-name" | "ui-locked" | "hide-ui-on-pause" | "is-fullscreen" | "is-fullscreen-fallback" | "rendition-levels" | "rendition-level" | "max-video-bit-rate" | "max-initial-bit-rate" | "abr-enabled" | "size-based-resolution-cap-enabled" | "channel-id" | "channel-group-id" | "pip-available" | "is-pip" | "airplay-available" | "is-airplaying" | "media" | "languages" | "text-tracks" | "text-track" | "needs-user-input-video" | "needs-user-input-audio" | "authentication-token" | "volume-level" | "cast" | "airplay" | "pip" | "fullscreen" | "double-click-fullscreen" | "vu-meter" | "timeshift" | "timeshift-position" | "timeline-markers" | "poster-src" | "debug-panel-open" | "target-buffer-time" | "drift" | "playback-latency" | "connection-state" | "url" | "decoders" | "min-buffer-time" | "max-buffer-time" | "edge-url" | "cast-receiver-id" | "cast-background" | "log-level" | "max-size" | "max-audio-bit-rate" | "burst-enabled" | "mse-enabled" | "mse-opus-enabled" | "background-play-enabled" | "ios-background-play-enabled" | "media-session-enabled" | "ios-wake-lock-enabled" | "stream-to-media-element-enabled" | "ios-media-element-enabled" | "telemetry-enabled" | "video-codecs" | "drm-headers" | "drm-queryparams" | "drm-widevine-video-robustness" | "drm-widevine-audio-robustness" | "drm-playready-video-robustness" | "drm-playready-audio-robustness" | "webtransport-enabled" | "webcodecs-hardware-acceleration" | "offscreen-canvas-enabled" | "reconnect-retries" | "auto-instance-enabled" | "advanced-rendition-menu-enabled" | "refresh-poster-enabled" | "stream-poll-enabled")[];
2037
2135
  constructor();
2038
2136
  connectedCallback(): void;
2039
2137
  disconnectedCallback(): void;
package/player.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./C2-H3s2r.js";
2
- import "./C3YYzwmQ.js";
3
- import { t } from "./ci2nH0xq.js";
1
+ import { t as e } from "./CcCOP7K4.js";
2
+ import "./tY-Iy2bb.js";
3
+ import { t } from "./CQewxOcB.js";
4
4
  export { t as registerComponents, e as registerDebugComponents };
package/react.d.ts CHANGED
@@ -176,6 +176,17 @@ interface DrmOptions {
176
176
  type Media = "audio" | "video" | "audio+video";
177
177
  type WebCodecsHardwareAccelerationPreference = "no-preference" | "prefer-hardware" | "prefer-software";
178
178
  type DecoderType = "mse" | "webcodecs" | "wasm";
179
+ interface MediaSessionMetadata {
180
+ title?: string;
181
+ artist?: string;
182
+ album?: string;
183
+ /** Fetched by the operating system, so the URLs should not carry credentials. */
184
+ artwork?: Array<{
185
+ src: string;
186
+ sizes?: string;
187
+ type?: string;
188
+ }>;
189
+ }
179
190
  interface Options {
180
191
  /**
181
192
  * URL to use when connecting to the stream
@@ -255,13 +266,36 @@ interface Options {
255
266
  */
256
267
  mseOpusEnabled?: boolean;
257
268
  /**
258
- * Enable or disable support for playing audio in the background for iOS devices.
269
+ * Keep playback running while the page is in the background. Enabled by default.
270
+ * When disabled, the session disconnects while hidden and reconnects when visible.
259
271
  *
260
- * Is false (disabled) by default.
272
+ * Browser media policies still apply; iOS Safari pauses hidden video regardless.
273
+ * Call `play()` again from a user gesture or media-control action to resume.
261
274
  *
262
- * Note: This may be enabled by default in a future (major) release
275
+ * When enabled, hidden sessions that are muted outside Picture-in-Picture or browser-paused
276
+ * are disconnected after the inactivity timeout; an explicit `pause()` disconnects at once.
277
+ */
278
+ backgroundPlayEnabled?: boolean;
279
+ /**
280
+ * @deprecated Use `backgroundPlayEnabled` instead.
281
+ *
282
+ * Legacy alias kept for backwards compatibility.
263
283
  */
264
284
  iosBackgroundPlayEnabled?: boolean;
285
+ /**
286
+ * Show the stream in the browser and OS media controls (notification, lock screen, toolbar) and
287
+ * let play and pause from there reach the SDK. Enabled by default.
288
+ *
289
+ * The SDK takes over `navigator.mediaSession` for the document, so a page with its own Media
290
+ * Session integration should disable this. On canvas playback paths a silent `<audio>` element is
291
+ * added to `document.body` for the controls to attach to.
292
+ */
293
+ mediaSessionEnabled?: boolean;
294
+ /**
295
+ * Title, artist and artwork for the media controls. Without it the browser derives them from the
296
+ * page. Read once at construction.
297
+ */
298
+ mediaSessionMetadata?: MediaSessionMetadata;
265
299
  /**
266
300
  * Enable or disable Adaptive Bit Rate. This allows for automatically adapting the incoming bit rate based on
267
301
  * the viewers bandwidth and thus avoiding buffering events. This also disables the
@@ -376,6 +410,9 @@ interface Options {
376
410
  * This enables platform media features such as fullscreen and picture-in-picture
377
411
  * for non-MSE playback by routing the canvas output through a `<video>` element.
378
412
  *
413
+ * On iOS the audio then plays from its own element, so it continues in the background and under
414
+ * lock. After an extended pause from the lock screen, reopening Safari may be required to resume.
415
+ *
379
416
  * Disabled by default.
380
417
  */
381
418
  streamToMediaElementEnabled?: boolean;
@@ -829,6 +866,13 @@ interface JitterModuleStatistics {
829
866
  audioJitter: JitterStatistics | undefined;
830
867
  videoJitter: JitterStatistics | undefined;
831
868
  }
869
+ type DecoyState = "detached" | "attached" | "playing" | "blocked";
870
+ interface MediaSessionModuleStatistics {
871
+ mediaSessionSupported: boolean;
872
+ mediaSessionOwner: boolean;
873
+ mediaSessionPlaybackState: MediaSessionPlaybackState;
874
+ decoyState?: DecoyState;
875
+ }
832
876
  interface MseModuleStatistics {
833
877
  quotaErrorCount: number;
834
878
  mediaSourceOpenTime: number;
@@ -940,8 +984,10 @@ interface SyncModuleStatistics {
940
984
  interface VideoPlayerStatistics {
941
985
  renderedFrameCount: number;
942
986
  rendererDroppedFrameCount: number;
987
+ /** The platform's own view of the audio context, which a mute reading cannot tell apart. */
988
+ audioContextState?: AudioContextState | "none";
943
989
  }
944
- type ModuleStatistics = AdaptivityStatistics & BufferTimeStatistics & ConnectionStatistics & ConstraintCapStatistics & DecoderStatistics & DocumentStateModulesStatistics & IncomingDataModuleStatistics & JitterModuleStatistics & MseModuleStatistics & PlaybackModuleStatistics & QualityOfServiceModuleStatistics & RenditionsModuleStatistics & SyncModuleStatistics & TelemetryModuleStatistics & VideoPlayerStatistics;
990
+ type ModuleStatistics = AdaptivityStatistics & BufferTimeStatistics & ConnectionStatistics & ConstraintCapStatistics & DecoderStatistics & DocumentStateModulesStatistics & IncomingDataModuleStatistics & JitterModuleStatistics & Partial<MediaSessionModuleStatistics> & MseModuleStatistics & PlaybackModuleStatistics & QualityOfServiceModuleStatistics & RenditionsModuleStatistics & SyncModuleStatistics & TelemetryModuleStatistics & VideoPlayerStatistics;
945
991
  type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAgentInformation"]> & {
946
992
  /**
947
993
  * Version of the @vindral/web-sdk being used.
@@ -993,6 +1039,8 @@ type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAge
993
1039
  streamToMediaElementEnabled?: boolean;
994
1040
  /** @deprecated */
995
1041
  iosMediaElementEnabled?: boolean;
1042
+ /** Whether the element carrying the audio is paused right now, on the sinks that have one. */
1043
+ mediaElementPaused?: boolean;
996
1044
  /**
997
1045
  * Average bitrate for the entire session in bits/second.
998
1046
  */
@@ -1017,8 +1065,10 @@ type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAge
1017
1065
  declare class Vindral extends Emitter<PublicVindralEvents> {
1018
1066
  #private;
1019
1067
  private static MAX_POOL_SIZE;
1020
- private static DISCONNECT_TIMEOUT;
1021
1068
  private static REMOVE_CUE_THRESHOLD;
1069
+ private static BACKGROUND_INACTIVITY_DISCONNECT_TIMEOUT;
1070
+ /** How long a pause on a visible page may still turn out to be the platform backgrounding us. */
1071
+ private static VISIBLE_PAUSE_DISCONNECT_DELAY;
1022
1072
  /**
1023
1073
  * Picture in picture
1024
1074
  */
@@ -1061,6 +1111,10 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1061
1111
  private offscreenCanvasElement?;
1062
1112
  private webCodecsRenditionSupport;
1063
1113
  private hasCalledConnect;
1114
+ private hasRequestedAudioSession;
1115
+ private isUserPaused;
1116
+ /** Only the sinks that carry the audio report this, so a paused element here means silence. */
1117
+ private isMediaElementPaused;
1064
1118
  private latestEmittedLanguages;
1065
1119
  private wakeLock;
1066
1120
  private pool;
@@ -1075,7 +1129,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1075
1129
  private readonly unextractableSamplesWarned;
1076
1130
  private sizes;
1077
1131
  private isSuspended;
1078
- private disconnectTimeout;
1132
+ private inactivityDisconnectInterval;
1133
+ private visiblePauseTimeout;
1079
1134
  private offscreenSubtitleInterval?;
1080
1135
  constructor(options: Options);
1081
1136
  /**
@@ -1377,6 +1432,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1377
1432
  private isSupportedVideoCodecProfile;
1378
1433
  private supportedAudioCodecs;
1379
1434
  private initializeDecodingModule;
1435
+ private capturesCanvasOnWebKit;
1436
+ private connectWebKitVideoOutput;
1380
1437
  /**
1381
1438
  * Set up a polling interval that forwards subtitle cues to the decoder worker
1382
1439
  * when OffscreenCanvas rendering is active. This is needed because the canvas modules'
@@ -1408,6 +1465,12 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1408
1465
  * Pauses the stream. Call .play() to resume playback again.
1409
1466
  */
1410
1467
  pause: () => void;
1468
+ /**
1469
+ * A browser-initiated decoy pause means an interruption (another app took the focus, headphones
1470
+ * unplugged), so give the audio up rather than play over it. WebKit holds the decoy on purpose.
1471
+ */
1472
+ private yieldToInterruption;
1473
+ private fromNativeControls;
1411
1474
  private registerDebugInstance;
1412
1475
  /**
1413
1476
  *
@@ -1438,8 +1501,24 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1438
1501
  */
1439
1502
  getStatistics: () => Statistics;
1440
1503
  private resetModules;
1504
+ private disconnectForInactivePage;
1505
+ private scheduleInactivityDisconnect;
1506
+ /**
1507
+ * iOS pauses the element a moment before it hides the page, and that pause reaches us while the
1508
+ * page still reports visible. Acting on it at once tears the session down, so every app switch
1509
+ * comes back as a full reconnect. Wait for the hide that follows a platform pause instead.
1510
+ * Nowhere else pauses before hiding, so there a visible pause is the viewer's at once.
1511
+ */
1512
+ private onVisiblePause;
1513
+ private disconnectForVisiblePause;
1514
+ private clearVisiblePauseDisconnect;
1515
+ private clearInactivityDisconnect;
1516
+ private isHiddenWithBackgroundPlayback;
1517
+ private isVideoOnScreen;
1518
+ private isHiddenWithBackgroundPlayDisabled;
1441
1519
  private suspend;
1442
1520
  private unsuspend;
1521
+ private resumeModules;
1443
1522
  private getRuntimeInfo;
1444
1523
  private getPlaybackBufferState;
1445
1524
  private onMediaElementBufferState;
@@ -1453,6 +1532,20 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1453
1532
  private get targetSubscription();
1454
1533
  private timeToFirstFrame;
1455
1534
  private resolvedDecoder;
1535
+ /**
1536
+ * On mobile Chromium with WebCodecs audio the play action arrives but no sound comes back until
1537
+ * the page is visible, so a hidden pause is a stop there. Reads the decoder in use.
1538
+ */
1539
+ private hiddenPauseIsStop;
1540
+ private usesSplitCanvasAudio;
1541
+ private isWebKitCanvasPath;
1542
+ private holdsDecoyWhilePaused;
1543
+ private streamsToMediaElement;
1544
+ /**
1545
+ * Safari 16.4+: a playback audio session keeps Web Audio alive in the background. Document-wide
1546
+ * and not restorable, so it waits for playback to be wanted rather than for construction.
1547
+ */
1548
+ private requestAudioSessionOnce;
1456
1549
  private willUseMediaSource;
1457
1550
  private shouldUseWebCodecs;
1458
1551
  private isWebCodecsVideoAvailable;
@@ -1463,6 +1556,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1463
1556
  * is the optimal rendering path for WebCodecs (worker renders VideoFrames directly).
1464
1557
  */
1465
1558
  private shouldUseOffscreenCanvas;
1559
+ private isBackgroundPlayEnabled;
1560
+ private shouldAllowBackgroundPlayback;
1466
1561
  }
1467
1562
  interface TelemetryModuleStatistics {
1468
1563
  /**
@@ -1615,7 +1710,8 @@ interface PublicVindralEvents {
1615
1710
  * Is emitted during connection whether the channel is live or not.
1616
1711
  *
1617
1712
  * If the channel is not live, the Vindral instance will try to reconnect until the `reconnectHandler`
1618
- * determines that no more retries should be made.
1713
+ * determines that no more retries should be made. Each attempt also emits a non-fatal `error` with
1714
+ * the code `channel_not_live`.
1619
1715
  *
1620
1716
  * Note: If the web-sdk is instantiated at the same time as you are starting the stream it is possible
1621
1717
  * that this emits false until the started state has propagated through the system.
@@ -1925,7 +2021,9 @@ declare class Controller extends HTMLElement {
1925
2021
  "burst-enabled",
1926
2022
  "mse-enabled",
1927
2023
  "mse-opus-enabled",
2024
+ "background-play-enabled",
1928
2025
  "ios-background-play-enabled",
2026
+ "media-session-enabled",
1929
2027
  "ios-wake-lock-enabled",
1930
2028
  "stream-to-media-element-enabled",
1931
2029
  "ios-media-element-enabled",
@@ -2043,7 +2141,7 @@ declare class PlayButton extends VindralButton {
2043
2141
  type PlayerAttributes = (typeof Player.observedAttributes)[number];
2044
2142
  declare class Player extends HTMLElement {
2045
2143
  #private;
2046
- static observedAttributes: readonly ("title" | "offline" | "advanced" | "poster" | "language" | "channels" | "buffering" | "paused" | "volume" | "muted" | "duration" | "user-interacting" | "is-casting" | "cast-available" | "cast-receiver-name" | "ui-locked" | "hide-ui-on-pause" | "is-fullscreen" | "is-fullscreen-fallback" | "rendition-levels" | "rendition-level" | "max-video-bit-rate" | "max-initial-bit-rate" | "abr-enabled" | "size-based-resolution-cap-enabled" | "channel-id" | "channel-group-id" | "pip-available" | "is-pip" | "airplay-available" | "is-airplaying" | "media" | "languages" | "text-tracks" | "text-track" | "needs-user-input-video" | "needs-user-input-audio" | "authentication-token" | "volume-level" | "cast" | "airplay" | "pip" | "fullscreen" | "double-click-fullscreen" | "vu-meter" | "timeshift" | "timeshift-position" | "timeline-markers" | "poster-src" | "debug-panel-open" | "target-buffer-time" | "drift" | "playback-latency" | "connection-state" | "url" | "decoders" | "min-buffer-time" | "max-buffer-time" | "edge-url" | "cast-receiver-id" | "cast-background" | "log-level" | "max-size" | "max-audio-bit-rate" | "burst-enabled" | "mse-enabled" | "mse-opus-enabled" | "ios-background-play-enabled" | "ios-wake-lock-enabled" | "stream-to-media-element-enabled" | "ios-media-element-enabled" | "telemetry-enabled" | "video-codecs" | "drm-headers" | "drm-queryparams" | "drm-widevine-video-robustness" | "drm-widevine-audio-robustness" | "drm-playready-video-robustness" | "drm-playready-audio-robustness" | "webtransport-enabled" | "webcodecs-hardware-acceleration" | "offscreen-canvas-enabled" | "reconnect-retries" | "auto-instance-enabled" | "advanced-rendition-menu-enabled" | "refresh-poster-enabled" | "stream-poll-enabled")[];
2144
+ static observedAttributes: readonly ("title" | "offline" | "advanced" | "poster" | "language" | "channels" | "buffering" | "paused" | "volume" | "muted" | "duration" | "user-interacting" | "is-casting" | "cast-available" | "cast-receiver-name" | "ui-locked" | "hide-ui-on-pause" | "is-fullscreen" | "is-fullscreen-fallback" | "rendition-levels" | "rendition-level" | "max-video-bit-rate" | "max-initial-bit-rate" | "abr-enabled" | "size-based-resolution-cap-enabled" | "channel-id" | "channel-group-id" | "pip-available" | "is-pip" | "airplay-available" | "is-airplaying" | "media" | "languages" | "text-tracks" | "text-track" | "needs-user-input-video" | "needs-user-input-audio" | "authentication-token" | "volume-level" | "cast" | "airplay" | "pip" | "fullscreen" | "double-click-fullscreen" | "vu-meter" | "timeshift" | "timeshift-position" | "timeline-markers" | "poster-src" | "debug-panel-open" | "target-buffer-time" | "drift" | "playback-latency" | "connection-state" | "url" | "decoders" | "min-buffer-time" | "max-buffer-time" | "edge-url" | "cast-receiver-id" | "cast-background" | "log-level" | "max-size" | "max-audio-bit-rate" | "burst-enabled" | "mse-enabled" | "mse-opus-enabled" | "background-play-enabled" | "ios-background-play-enabled" | "media-session-enabled" | "ios-wake-lock-enabled" | "stream-to-media-element-enabled" | "ios-media-element-enabled" | "telemetry-enabled" | "video-codecs" | "drm-headers" | "drm-queryparams" | "drm-widevine-video-robustness" | "drm-widevine-audio-robustness" | "drm-playready-video-robustness" | "drm-playready-audio-robustness" | "webtransport-enabled" | "webcodecs-hardware-acceleration" | "offscreen-canvas-enabled" | "reconnect-retries" | "auto-instance-enabled" | "advanced-rendition-menu-enabled" | "refresh-poster-enabled" | "stream-poll-enabled")[];
2047
2145
  constructor();
2048
2146
  connectedCallback(): void;
2049
2147
  disconnectedCallback(): void;