@vindral/web-sdk 4.5.1-9-gb7e40eb2c → 4.6.0

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-9-gb7e40eb2c",
2
+ "version": "4.6.0",
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
@@ -269,6 +280,20 @@ interface Options {
269
280
  * Legacy alias kept for backwards compatibility.
270
281
  */
271
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;
272
297
  /**
273
298
  * Enable or disable Adaptive Bit Rate. This allows for automatically adapting the incoming bit rate based on
274
299
  * the viewers bandwidth and thus avoiding buffering events. This also disables the
@@ -383,6 +408,9 @@ interface Options {
383
408
  * This enables platform media features such as fullscreen and picture-in-picture
384
409
  * for non-MSE playback by routing the canvas output through a `<video>` element.
385
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
+ *
386
414
  * Disabled by default.
387
415
  */
388
416
  streamToMediaElementEnabled?: boolean;
@@ -836,6 +864,13 @@ interface JitterModuleStatistics {
836
864
  audioJitter: JitterStatistics | undefined;
837
865
  videoJitter: JitterStatistics | undefined;
838
866
  }
867
+ type DecoyState = "detached" | "attached" | "playing" | "blocked";
868
+ interface MediaSessionModuleStatistics {
869
+ mediaSessionSupported: boolean;
870
+ mediaSessionOwner: boolean;
871
+ mediaSessionPlaybackState: MediaSessionPlaybackState;
872
+ decoyState?: DecoyState;
873
+ }
839
874
  interface MseModuleStatistics {
840
875
  quotaErrorCount: number;
841
876
  mediaSourceOpenTime: number;
@@ -947,8 +982,10 @@ interface SyncModuleStatistics {
947
982
  interface VideoPlayerStatistics {
948
983
  renderedFrameCount: number;
949
984
  rendererDroppedFrameCount: number;
985
+ /** The platform's own view of the audio context, which a mute reading cannot tell apart. */
986
+ audioContextState?: AudioContextState | "none";
950
987
  }
951
- 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;
952
989
  type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAgentInformation"]> & {
953
990
  /**
954
991
  * Version of the @vindral/web-sdk being used.
@@ -1000,6 +1037,8 @@ type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAge
1000
1037
  streamToMediaElementEnabled?: boolean;
1001
1038
  /** @deprecated */
1002
1039
  iosMediaElementEnabled?: boolean;
1040
+ /** Whether the element carrying the audio is paused right now, on the sinks that have one. */
1041
+ mediaElementPaused?: boolean;
1003
1042
  /**
1004
1043
  * Average bitrate for the entire session in bits/second.
1005
1044
  */
@@ -1026,6 +1065,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1026
1065
  private static MAX_POOL_SIZE;
1027
1066
  private static REMOVE_CUE_THRESHOLD;
1028
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;
1029
1070
  /**
1030
1071
  * Picture in picture
1031
1072
  */
@@ -1068,7 +1109,10 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1068
1109
  private offscreenCanvasElement?;
1069
1110
  private webCodecsRenditionSupport;
1070
1111
  private hasCalledConnect;
1112
+ private hasRequestedAudioSession;
1071
1113
  private isUserPaused;
1114
+ /** Only the sinks that carry the audio report this, so a paused element here means silence. */
1115
+ private isMediaElementPaused;
1072
1116
  private latestEmittedLanguages;
1073
1117
  private wakeLock;
1074
1118
  private pool;
@@ -1084,6 +1128,7 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1084
1128
  private sizes;
1085
1129
  private isSuspended;
1086
1130
  private inactivityDisconnectInterval;
1131
+ private visiblePauseTimeout;
1087
1132
  private offscreenSubtitleInterval?;
1088
1133
  constructor(options: Options);
1089
1134
  /**
@@ -1385,6 +1430,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1385
1430
  private isSupportedVideoCodecProfile;
1386
1431
  private supportedAudioCodecs;
1387
1432
  private initializeDecodingModule;
1433
+ private capturesCanvasOnWebKit;
1434
+ private connectWebKitVideoOutput;
1388
1435
  /**
1389
1436
  * Set up a polling interval that forwards subtitle cues to the decoder worker
1390
1437
  * when OffscreenCanvas rendering is active. This is needed because the canvas modules'
@@ -1416,6 +1463,12 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1416
1463
  * Pauses the stream. Call .play() to resume playback again.
1417
1464
  */
1418
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;
1419
1472
  private registerDebugInstance;
1420
1473
  /**
1421
1474
  *
@@ -1448,8 +1501,18 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1448
1501
  private resetModules;
1449
1502
  private disconnectForInactivePage;
1450
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;
1451
1513
  private clearInactivityDisconnect;
1452
1514
  private isHiddenWithBackgroundPlayback;
1515
+ private isVideoOnScreen;
1453
1516
  private isHiddenWithBackgroundPlayDisabled;
1454
1517
  private suspend;
1455
1518
  private unsuspend;
@@ -1467,6 +1530,20 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1467
1530
  private get targetSubscription();
1468
1531
  private timeToFirstFrame;
1469
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;
1470
1547
  private willUseMediaSource;
1471
1548
  private shouldUseWebCodecs;
1472
1549
  private isWebCodecsVideoAvailable;
@@ -1937,6 +2014,7 @@ export declare class Controller extends HTMLElement {
1937
2014
  "mse-opus-enabled",
1938
2015
  "background-play-enabled",
1939
2016
  "ios-background-play-enabled",
2017
+ "media-session-enabled",
1940
2018
  "ios-wake-lock-enabled",
1941
2019
  "stream-to-media-element-enabled",
1942
2020
  "ios-media-element-enabled",
@@ -2053,7 +2131,7 @@ export declare class PlayButton extends VindralButton {
2053
2131
  */
2054
2132
  export declare class Player extends HTMLElement {
2055
2133
  #private;
2056
- 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" | "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")[];
2057
2135
  constructor();
2058
2136
  connectedCallback(): void;
2059
2137
  disconnectedCallback(): void;
package/player.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { t as e } from "./DbdwBdhd.js";
2
- import "./Cj61-Cjf.js";
3
- import { t } from "./DZ1kJVLj.js";
2
+ import "./CN8w7_en.js";
3
+ import { t } from "./BV8nX-0b.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
@@ -271,6 +282,20 @@ interface Options {
271
282
  * Legacy alias kept for backwards compatibility.
272
283
  */
273
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;
274
299
  /**
275
300
  * Enable or disable Adaptive Bit Rate. This allows for automatically adapting the incoming bit rate based on
276
301
  * the viewers bandwidth and thus avoiding buffering events. This also disables the
@@ -385,6 +410,9 @@ interface Options {
385
410
  * This enables platform media features such as fullscreen and picture-in-picture
386
411
  * for non-MSE playback by routing the canvas output through a `<video>` element.
387
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
+ *
388
416
  * Disabled by default.
389
417
  */
390
418
  streamToMediaElementEnabled?: boolean;
@@ -838,6 +866,13 @@ interface JitterModuleStatistics {
838
866
  audioJitter: JitterStatistics | undefined;
839
867
  videoJitter: JitterStatistics | undefined;
840
868
  }
869
+ type DecoyState = "detached" | "attached" | "playing" | "blocked";
870
+ interface MediaSessionModuleStatistics {
871
+ mediaSessionSupported: boolean;
872
+ mediaSessionOwner: boolean;
873
+ mediaSessionPlaybackState: MediaSessionPlaybackState;
874
+ decoyState?: DecoyState;
875
+ }
841
876
  interface MseModuleStatistics {
842
877
  quotaErrorCount: number;
843
878
  mediaSourceOpenTime: number;
@@ -949,8 +984,10 @@ interface SyncModuleStatistics {
949
984
  interface VideoPlayerStatistics {
950
985
  renderedFrameCount: number;
951
986
  rendererDroppedFrameCount: number;
987
+ /** The platform's own view of the audio context, which a mute reading cannot tell apart. */
988
+ audioContextState?: AudioContextState | "none";
952
989
  }
953
- 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;
954
991
  type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAgentInformation"]> & {
955
992
  /**
956
993
  * Version of the @vindral/web-sdk being used.
@@ -1002,6 +1039,8 @@ type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAge
1002
1039
  streamToMediaElementEnabled?: boolean;
1003
1040
  /** @deprecated */
1004
1041
  iosMediaElementEnabled?: boolean;
1042
+ /** Whether the element carrying the audio is paused right now, on the sinks that have one. */
1043
+ mediaElementPaused?: boolean;
1005
1044
  /**
1006
1045
  * Average bitrate for the entire session in bits/second.
1007
1046
  */
@@ -1028,6 +1067,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1028
1067
  private static MAX_POOL_SIZE;
1029
1068
  private static REMOVE_CUE_THRESHOLD;
1030
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;
1031
1072
  /**
1032
1073
  * Picture in picture
1033
1074
  */
@@ -1070,7 +1111,10 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1070
1111
  private offscreenCanvasElement?;
1071
1112
  private webCodecsRenditionSupport;
1072
1113
  private hasCalledConnect;
1114
+ private hasRequestedAudioSession;
1073
1115
  private isUserPaused;
1116
+ /** Only the sinks that carry the audio report this, so a paused element here means silence. */
1117
+ private isMediaElementPaused;
1074
1118
  private latestEmittedLanguages;
1075
1119
  private wakeLock;
1076
1120
  private pool;
@@ -1086,6 +1130,7 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1086
1130
  private sizes;
1087
1131
  private isSuspended;
1088
1132
  private inactivityDisconnectInterval;
1133
+ private visiblePauseTimeout;
1089
1134
  private offscreenSubtitleInterval?;
1090
1135
  constructor(options: Options);
1091
1136
  /**
@@ -1387,6 +1432,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1387
1432
  private isSupportedVideoCodecProfile;
1388
1433
  private supportedAudioCodecs;
1389
1434
  private initializeDecodingModule;
1435
+ private capturesCanvasOnWebKit;
1436
+ private connectWebKitVideoOutput;
1390
1437
  /**
1391
1438
  * Set up a polling interval that forwards subtitle cues to the decoder worker
1392
1439
  * when OffscreenCanvas rendering is active. This is needed because the canvas modules'
@@ -1418,6 +1465,12 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1418
1465
  * Pauses the stream. Call .play() to resume playback again.
1419
1466
  */
1420
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;
1421
1474
  private registerDebugInstance;
1422
1475
  /**
1423
1476
  *
@@ -1450,8 +1503,18 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1450
1503
  private resetModules;
1451
1504
  private disconnectForInactivePage;
1452
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;
1453
1515
  private clearInactivityDisconnect;
1454
1516
  private isHiddenWithBackgroundPlayback;
1517
+ private isVideoOnScreen;
1455
1518
  private isHiddenWithBackgroundPlayDisabled;
1456
1519
  private suspend;
1457
1520
  private unsuspend;
@@ -1469,6 +1532,20 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1469
1532
  private get targetSubscription();
1470
1533
  private timeToFirstFrame;
1471
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;
1472
1549
  private willUseMediaSource;
1473
1550
  private shouldUseWebCodecs;
1474
1551
  private isWebCodecsVideoAvailable;
@@ -1946,6 +2023,7 @@ declare class Controller extends HTMLElement {
1946
2023
  "mse-opus-enabled",
1947
2024
  "background-play-enabled",
1948
2025
  "ios-background-play-enabled",
2026
+ "media-session-enabled",
1949
2027
  "ios-wake-lock-enabled",
1950
2028
  "stream-to-media-element-enabled",
1951
2029
  "ios-media-element-enabled",
@@ -2063,7 +2141,7 @@ declare class PlayButton extends VindralButton {
2063
2141
  type PlayerAttributes = (typeof Player.observedAttributes)[number];
2064
2142
  declare class Player extends HTMLElement {
2065
2143
  #private;
2066
- 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" | "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")[];
2067
2145
  constructor();
2068
2146
  connectedCallback(): void;
2069
2147
  disconnectedCallback(): void;
@@ -1,5 +1,5 @@
1
- import "./Cj61-Cjf.js";
2
- import { t as e } from "./DZ1kJVLj.js";
1
+ import "./CN8w7_en.js";
2
+ import { t as e } from "./BV8nX-0b.js";
3
3
  //#region src/vindral-player-component.ts
4
4
  e();
5
5
  //#endregion
package/CN4egPyY.js DELETED
@@ -1,149 +0,0 @@
1
- import { n as e, t } from "./BsfwXDui.js";
2
- import { t as n } from "./DrWNJwZx.js";
3
- import { t as r } from "./BKeRxR4O.js";
4
- import { i, n as a, t as o } from "./CbyVl6wP.js";
5
- //#region ../../libs/web-sdk/src/decoders/PendingSampleMap.ts
6
- var s = class {
7
- constructor() {
8
- e(this, "map", /* @__PURE__ */ new Map());
9
- }
10
- enqueue(e, t) {
11
- let r = this.map.get(e);
12
- if (r) r.push(t);
13
- else {
14
- let r = new n(Infinity);
15
- r.push(t), this.map.set(e, r);
16
- }
17
- }
18
- dequeue(e) {
19
- let t = this.map.get(e);
20
- if (!t) return;
21
- let n = t.pop();
22
- return t.isEmpty() && this.map.delete(e), n;
23
- }
24
- dequeueWithFallback(e) {
25
- let t = this.dequeue(e);
26
- if (t !== void 0) return t;
27
- for (let [e, t] of this.map) {
28
- let n = t.pop();
29
- if (t.isEmpty() && this.map.delete(e), n !== void 0) return n;
30
- }
31
- }
32
- removeLast(e) {
33
- let t = this.map.get(e);
34
- t && (t.popLast(), t.isEmpty() && this.map.delete(e));
35
- }
36
- clear() {
37
- this.map.clear();
38
- }
39
- }, c, l = globalThis, u = class {
40
- constructor(c) {
41
- var l = this;
42
- e(this, "decodedSamples", []), e(this, "decoder", null), e(this, "logger", void 0), e(this, "initSegments", /* @__PURE__ */ new Map()), e(this, "previousSample", void 0), e(this, "previousCodec", void 0), e(this, "pendingSamples", new s()), e(this, "errors", new n(10)), e(this, "resetDecoderState", () => {
43
- this.pendingSamples.clear(), this.previousSample = void 0, this.previousCodec = void 0;
44
- }), e(this, "ensureDecoder", () => ((!this.decoder || this.decoder.state === "closed") && (this.decoder = this.createDecoder()), this.decoder)), e(this, "createDecoder", () => new AudioDecoder({
45
- output: (e) => {
46
- let t = this.pendingSamples.dequeueWithFallback(e.timestamp);
47
- if (!t) {
48
- e.close();
49
- return;
50
- }
51
- let { sample: n, decodeStartTime: i } = t;
52
- try {
53
- let t = e.numberOfFrames, a = e.numberOfChannels, o = e.sampleRate, s = e.timestamp, c = e.duration, l = new Float32Array(t * a);
54
- e.copyTo(l, {
55
- planeIndex: 0,
56
- format: "f32"
57
- });
58
- let u = Math.round(s * n.timescale / 1e6), d = typeof c == "number" && Number.isFinite(c) ? Math.max(0, Math.round(c * n.timescale / 1e6)) : n.duration, f = r(r({}, n), {}, {
59
- timestamp: u,
60
- duration: d,
61
- type: "audio",
62
- format: "f32",
63
- data: l,
64
- sampleRate: o,
65
- channels: a,
66
- statistics: {
67
- decodeTime: Math.max(performance.now() - i, 0),
68
- transportTimeFromWorker: 0,
69
- transportTimeToWorker: 0,
70
- samplesInBatch: 1
71
- }
72
- });
73
- this.decodedSamples.push(f);
74
- } finally {
75
- e.close();
76
- }
77
- },
78
- error: (e) => {
79
- this.logger.error("Decoder error:", e.name, e.message), this.resetDecoderState(), this.errors.push(e);
80
- }
81
- })), e(this, "buildCodecString", o), e(this, "getDescription", (e, t) => {
82
- let n = this.initSegments.get(t.renditionId);
83
- if (n && n.data.byteLength > 0 && n.codec === e) return n.data;
84
- if (e === "opus") return a(t.channels, t.sampleRate);
85
- if (e === "aac") return i(t.channels, t.sampleRate);
86
- }), e(this, "unload", () => {
87
- this.decodedSamples = [], this.resetDecoderState(), this.decoder && this.decoder.state !== "closed" && this.decoder.close(), this.decoder = null;
88
- }), e(this, "initSegment", (e) => {
89
- this.initSegments.set(e.renditionId, e);
90
- }), e(this, "enqueue", (e) => {
91
- var t, n;
92
- let r = this.ensureDecoder(), i = e.codec, a = ((t = this.previousSample) == null ? void 0 : t.channelId) !== e.channelId, o = ((n = this.previousSample) == null ? void 0 : n.renditionId) !== e.renditionId, s = this.previousCodec !== i;
93
- if (!this.previousSample || a || o || s) {
94
- this.previousSample && r.state === "configured" && r.flush().catch((e) => {
95
- this.logger.warn("Failed to flush before reconfiguration:", e);
96
- });
97
- let t = this.buildCodecString(i), n = this.getDescription(i, e), a = {
98
- codec: t,
99
- sampleRate: e.sampleRate,
100
- numberOfChannels: e.channels,
101
- description: n
102
- };
103
- try {
104
- r.configure(a), this.previousCodec = i;
105
- } catch (t) {
106
- this.logger.error(`Failed to configure decoder for ${i} (rendition ${e.renditionId}), dropping sample`, t), this.errors.push(t instanceof Error ? t : /* @__PURE__ */ Error(`Failed to configure decoder for ${i}`)), this.resetDecoderState(), this.ensureDecoder();
107
- return;
108
- }
109
- }
110
- if (e.timescale <= 0) {
111
- this.logger.error(`Invalid timescale ${e.timescale} in sample, dropping sample`);
112
- return;
113
- }
114
- this.previousSample = e;
115
- let c = Math.floor(1e6 * e.timestamp / e.timescale), l = Math.floor(1e6 * e.duration / e.timescale);
116
- try {
117
- let t = new EncodedAudioChunk({
118
- type: "key",
119
- timestamp: c,
120
- duration: l > 0 ? l : void 0,
121
- data: e.data
122
- });
123
- this.pendingSamples.enqueue(c, {
124
- sample: e,
125
- decodeStartTime: performance.now()
126
- }), r.decode(t);
127
- } catch (e) {
128
- this.logger.error("Failed to decode chunk:", e), this.pendingSamples.removeLast(c), r.state === "closed" && (this.resetDecoderState(), this.ensureDecoder());
129
- }
130
- }), e(this, "flush", t(function* () {
131
- if (l.decoder && l.decoder.state === "configured") try {
132
- yield l.decoder.flush();
133
- } catch (e) {
134
- l.logger.error("Failed to flush decoder:", e), l.resetDecoderState();
135
- }
136
- })), e(this, "error", () => this.errors.pop()), e(this, "take", () => this.decodedSamples.shift()), this.logger = c, this.decoder = this.createDecoder();
137
- }
138
- };
139
- c = u, e(u, "isSupported", () => l.AudioDecoder !== void 0), e(u, "create", function() {
140
- var e = t(function* (e) {
141
- if (!c.isSupported()) throw Error("WebCodecs AudioDecoder is not supported in this environment");
142
- return new c(e);
143
- });
144
- return function(t) {
145
- return e.apply(this, arguments);
146
- };
147
- }());
148
- //#endregion
149
- export { u as WebCodecsAudioDecoderContext };