@vindral/web-sdk 4.1.10 → 4.2.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/react.d.ts CHANGED
@@ -65,6 +65,10 @@ declare class Emitter<TEvents, TEmits = TEvents, ArgLessEvents extends VoidKeys<
65
65
  */
66
66
  once<T extends ArgLessEvents>(eventName: T, fn: () => void): void;
67
67
  once<T extends ArgEvents>(eventName: T, fn: (args: TEvents[T]) => void): void;
68
+ /**
69
+ * Check whether there are any listeners registered for the given event.
70
+ */
71
+ hasListeners<T extends keyof TEvents | keyof TEmits>(eventName: T): boolean;
68
72
  /**
69
73
  * Reset the event emitter
70
74
  */
@@ -137,72 +141,13 @@ interface ReconnectState {
137
141
  */
138
142
  reconnectRetries: number;
139
143
  }
140
- interface RenditionProps {
141
- id: number;
142
- /** */
143
- bitRate: number;
144
- /** */
145
- codecString?: string;
146
- /** */
147
- language?: string;
148
- /** */
149
- meta?: Record<string, string>;
150
- }
151
- interface VideoRenditionProps {
152
- /** */
153
- codec: VideoCodec;
154
- /** */
155
- frameRate: [
156
- number,
157
- number
158
- ];
159
- /** */
160
- width: number;
161
- /** */
162
- height: number;
163
- }
164
- interface AudioRenditionProps {
165
- /** */
166
- codec: AudioCodec;
167
- /** */
168
- channels: number;
169
- /** */
170
- sampleRate: number;
171
- }
172
- interface TextRenditionProps {
173
- codec: "webvtt";
174
- kind: "subtitles" | "captions";
175
- label?: string;
176
- }
177
- type VideoRendition = VideoRenditionProps & RenditionProps;
178
- type AudioRendition = AudioRenditionProps & RenditionProps;
179
- type TextRendition = TextRenditionProps & RenditionProps;
180
- type Rendition = VideoRendition | AudioRendition | TextRendition;
181
144
  interface Size {
182
145
  /** */
183
146
  width: number;
184
147
  /** */
185
148
  height: number;
186
149
  }
187
- interface VideoConstraint {
188
- /** */
189
- width: number;
190
- /** */
191
- height: number;
192
- /** */
193
- bitRate: number;
194
- /** */
195
- codec?: VideoCodec;
196
- /** */
197
- codecString?: string;
198
- }
199
150
  interface AdvancedOptions {
200
- /**
201
- * Constrains wasm decoding to this resolution.
202
- * By default it is set to 1280 in width and height.
203
- * This guarantees better performance on older devices and reduces battery drain in general.
204
- */
205
- wasmDecodingConstraint: Partial<VideoConstraint>;
206
151
  }
207
152
  interface DrmOptions {
208
153
  /**
@@ -230,6 +175,7 @@ interface DrmOptions {
230
175
  }
231
176
  type Media = "audio" | "video" | "audio+video";
232
177
  type WebCodecsHardwareAccelerationPreference = "no-preference" | "prefer-hardware" | "prefer-software";
178
+ type DecoderType = "mse" | "webcodecs" | "wasm";
233
179
  interface Options {
234
180
  /**
235
181
  * URL to use when connecting to the stream
@@ -296,11 +242,9 @@ interface Options {
296
242
  */
297
243
  burstEnabled?: boolean;
298
244
  /**
299
- * Enable usage of the MediaSource API on supported browsers.
300
- *
301
- * Is enabled by default.
302
- *
303
- * Note: We recommend to keep this at the default value unless you have very specific needs.
245
+ * @deprecated Use `decoders` instead.
246
+ * Setting `mseEnabled: false` is equivalent to `decoders: ["wasm"]`.
247
+ * When both `mseEnabled` and `decoders` are provided, `decoders` takes precedence.
304
248
  */
305
249
  mseEnabled?: boolean;
306
250
  /**
@@ -417,7 +361,7 @@ interface Options {
417
361
  * The wake lock requires that the audio has been activated at least once for the instance, othwerwise it will not work.
418
362
  * Other devices already provide wake lock by default.
419
363
  *
420
- * This option is redundant and has no effect if iosMediaElementEnabled is enabled since that automatically enables wake lock.
364
+ * This option is redundant and has no effect if streamToMediaElementEnabled is enabled since that automatically enables wake lock.
421
365
  *
422
366
  * Disabled by default.
423
367
  */
@@ -427,26 +371,20 @@ interface Options {
427
371
  */
428
372
  pauseSupportEnabled?: boolean;
429
373
  /**
430
- * Enables iOS devices to use a media element for playback. This enables fullscreen and picture in picture support on iOS.
431
- */
432
- iosMediaElementEnabled?: boolean;
433
- /**
434
- * Enable WebCodecs API for hardware-accelerated decoding.
374
+ * Stream canvas-rendered video/audio to a media element via captureStream.
435
375
  *
436
- * When enabled, the browser's native VideoDecoder and AudioDecoder APIs will be used instead
437
- * of WASM decoding on supported browsers (Chrome 94+, Edge 94+, Safari 16.4+). This provides:
438
- * - Hardware-accelerated decoding (uses GPU for video)
439
- * - Better performance at higher resolutions
440
- * - Lower CPU usage and battery consumption
376
+ * This enables platform media features such as fullscreen and picture-in-picture
377
+ * for non-MSE playback by routing the canvas output through a `<video>` element.
441
378
  *
442
- * Disabled by default to allow gradual rollout and testing.
379
+ * Disabled by default.
380
+ */
381
+ streamToMediaElementEnabled?: boolean;
382
+ /**
383
+ * @deprecated Use `streamToMediaElementEnabled` instead.
443
384
  *
444
- * Note: Automatically falls back to WASM decoding if:
445
- * - The browser doesn't support WebCodecs
446
- * - WebCodecs initialization fails
447
- * - This option is set to false or undefined
385
+ * Legacy alias kept for backwards compatibility.
448
386
  */
449
- webcodecsEnabled?: boolean;
387
+ iosMediaElementEnabled?: boolean;
450
388
  /**
451
389
  * Hardware acceleration preference for WebCodecs video decoding.
452
390
  *
@@ -454,14 +392,29 @@ interface Options {
454
392
  */
455
393
  webcodecsHardwareAcceleration?: WebCodecsHardwareAccelerationPreference;
456
394
  /**
457
- * Enable OffscreenCanvas rendering in worker thread (requires webcodecsEnabled: true).
395
+ * Enable OffscreenCanvas rendering in worker thread.
458
396
  *
459
397
  * When enabled, video rendering happens in the worker thread using OffscreenCanvas.
460
- * Disabled by default to allow gradual rollout and testing.
398
+ * Automatically enabled when `decoders` resolves to WebCodecs. Set to `false`
399
+ * to explicitly disable even when WebCodecs is active.
461
400
  *
462
- * Note: Requires browser support for OffscreenCanvas and webcodecsEnabled must be true, and iosMediaElementEnabled must be false.
401
+ * Requires browser support for OffscreenCanvas and WebCodecs.
402
+ * Works with `streamToMediaElementEnabled` for fullscreen/PiP support.
463
403
  */
464
404
  offscreenCanvasEnabled?: boolean;
405
+ /**
406
+ * Ordered list of decoders to try.
407
+ *
408
+ * The runtime walks the list and uses the first decoder that is available on the
409
+ * current platform. When DRM is active, MSE is forced regardless of order.
410
+ *
411
+ * When omitted, the platform default order is used (`["mse", "wasm"]`).
412
+ *
413
+ * @example
414
+ * // Prefer WebCodecs over MSE
415
+ * decoders: ["webcodecs", "mse", "wasm"]
416
+ */
417
+ decoders?: DecoderType[];
465
418
  /**
466
419
  * Advanced options to override default behaviour.
467
420
  */
@@ -480,7 +433,6 @@ interface ClientOverrides {
480
433
  burstEnabled?: boolean;
481
434
  sizeBasedResolutionCapEnabled?: boolean;
482
435
  separateVideoSocketEnabled?: boolean;
483
- webcodecsEnabled?: boolean;
484
436
  webcodecsHardwareAcceleration?: WebCodecsHardwareAccelerationPreference;
485
437
  offscreenCanvasEnabled?: boolean;
486
438
  videoCodecs?: string[];
@@ -524,6 +476,47 @@ interface TracksCatalog extends CatalogRoot {
524
476
  namespace: Namespace;
525
477
  tracks: Array<TrackObject>;
526
478
  }
479
+ interface RenditionProps {
480
+ id: number;
481
+ /** */
482
+ bitRate: number;
483
+ /** */
484
+ codecString?: string;
485
+ /** */
486
+ language?: string;
487
+ /** */
488
+ meta?: Record<string, string>;
489
+ }
490
+ interface VideoRenditionProps {
491
+ /** */
492
+ codec: VideoCodec;
493
+ /** */
494
+ frameRate: [
495
+ number,
496
+ number
497
+ ];
498
+ /** */
499
+ width: number;
500
+ /** */
501
+ height: number;
502
+ }
503
+ interface AudioRenditionProps {
504
+ /** */
505
+ codec: AudioCodec;
506
+ /** */
507
+ channels: number;
508
+ /** */
509
+ sampleRate: number;
510
+ }
511
+ interface TextRenditionProps {
512
+ codec: "webvtt";
513
+ kind: "subtitles" | "captions";
514
+ label?: string;
515
+ }
516
+ type VideoRendition = VideoRenditionProps & RenditionProps;
517
+ type AudioRendition = AudioRenditionProps & RenditionProps;
518
+ type TextRendition = TextRenditionProps & RenditionProps;
519
+ type Rendition = VideoRendition | AudioRendition | TextRendition;
527
520
  interface Telemetry {
528
521
  url: string;
529
522
  probability?: number;
@@ -787,6 +780,13 @@ interface DecoderStatistics {
787
780
  videoDecodeTime: MinMaxAverage;
788
781
  audioDecodeTime: MinMaxAverage;
789
782
  videoTransportTime: MinMaxAverage;
783
+ videoKeyframeIntervalMs?: number;
784
+ renderedFrameCount?: number;
785
+ rendererDroppedFrameCount?: number;
786
+ videoRenderer?: "OffscreenCanvas" | "Canvas" | "MSE";
787
+ videoDecoder?: "WebCodecs" | "WASM" | "MSE";
788
+ audioRenderer?: "AudioContext" | "MSE";
789
+ audioDecoder?: "WebCodecs" | "WASM" | "MSE";
790
790
  }
791
791
  interface DocumentStateModulesStatistics {
792
792
  isVisible: boolean;
@@ -836,6 +836,11 @@ interface MseModuleStatistics {
836
836
  droppedVideoFrames?: number;
837
837
  successfulVideoAppendCalls?: number;
838
838
  successfulAudioAppendsCalls?: number;
839
+ videoKeyframeIntervalMs?: number;
840
+ videoRenderer?: "OffscreenCanvas" | "Canvas" | "MSE";
841
+ videoDecoder?: "WebCodecs" | "WASM" | "MSE";
842
+ audioRenderer?: "AudioContext" | "MSE";
843
+ audioDecoder?: "WebCodecs" | "WASM" | "MSE";
839
844
  }
840
845
  interface QualityOfServiceModuleStatistics {
841
846
  /**
@@ -916,8 +921,6 @@ interface SyncModuleStatistics {
916
921
  interface VideoPlayerStatistics {
917
922
  renderedFrameCount: number;
918
923
  rendererDroppedFrameCount: number;
919
- contextLostCount: number;
920
- contextRestoredCount: number;
921
924
  }
922
925
  type ModuleStatistics = AdaptivityStatistics & BufferTimeStatistics & ConnectionStatistics & ConstraintCapStatistics & DecoderStatistics & DocumentStateModulesStatistics & IncomingDataModuleStatistics & JitterModuleStatistics & MseModuleStatistics & PlaybackModuleStatistics & QualityOfServiceModuleStatistics & RenditionsModuleStatistics & SyncModuleStatistics & TelemetryModuleStatistics & VideoPlayerStatistics;
923
926
  type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAgentInformation"]> & {
@@ -960,6 +963,8 @@ type Statistics = ModuleStatistics & ReturnType<UserAgentInformation["getUserAge
960
963
  * Note that an actual frame render often happens much quicker, but that is not counted as TTFF.
961
964
  */
962
965
  timeToFirstFrame?: number;
966
+ streamToMediaElementEnabled?: boolean;
967
+ /** @deprecated */
963
968
  iosMediaElementEnabled?: boolean;
964
969
  /**
965
970
  * Average bitrate for the entire session in bits/second.
@@ -1026,6 +1031,8 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1026
1031
  private clientId;
1027
1032
  private _channels;
1028
1033
  private createdAt;
1034
+ private offscreenCanvasElement?;
1035
+ private webCodecsRenditionSupport;
1029
1036
  private hasCalledConnect;
1030
1037
  private latestEmittedLanguages;
1031
1038
  private wakeLock;
@@ -1036,6 +1043,7 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1036
1043
  private sizes;
1037
1044
  private isSuspended;
1038
1045
  private disconnectTimeout;
1046
+ private offscreenSubtitleInterval?;
1039
1047
  constructor(options: Options);
1040
1048
  /**
1041
1049
  * Attaches the video view to a DOM element. The Vindral video view will be sized to fill this element while
@@ -1044,6 +1052,7 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1044
1052
  * @returns
1045
1053
  */
1046
1054
  attach: (container: HTMLElement) => void;
1055
+ private setElement;
1047
1056
  /**
1048
1057
  * Set the current volume.
1049
1058
  * Setting this to 0 is not equivalent to muting the audio.
@@ -1264,6 +1273,10 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1264
1273
  * The ranges are specified in milliseconds.
1265
1274
  */
1266
1275
  get audioBufferedRanges(): ReadonlyArray<TimeRange>;
1276
+ /**
1277
+ * The timestamps (in milliseconds) of video keyframes currently in the buffer.
1278
+ */
1279
+ get videoKeyframeTimestamps(): ReadonlyArray<number>;
1267
1280
  /**
1268
1281
  * The API client for calls to the public available endpoints of the Vindral Live CDN.
1269
1282
  */
@@ -1323,9 +1336,16 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1323
1336
  private isSupportedVideoCodecProfile;
1324
1337
  private supportedAudioCodecs;
1325
1338
  private initializeDecodingModule;
1339
+ /**
1340
+ * Set up a polling interval that forwards subtitle cues to the decoder worker
1341
+ * when OffscreenCanvas rendering is active. This is needed because the canvas modules'
1342
+ * own subtitle intervals either get killed (LegacyCanvasModule) or don't have the
1343
+ * OffscreenCanvas reference at construction time (ModernCanvasModule).
1344
+ */
1345
+ private setupOffscreenSubtitleRouting;
1326
1346
  /**
1327
1347
  * Fully unloads the instance. This disconnects the clients and stops any background tasks.
1328
- * This client instance can not be used after this has been called.
1348
+ * This client instance cannot be used after this has been called.
1329
1349
  */
1330
1350
  unload: () => Promise<void>;
1331
1351
  /**
@@ -1389,7 +1409,17 @@ declare class Vindral extends Emitter<PublicVindralEvents> {
1389
1409
  private get currentSubscription();
1390
1410
  private get targetSubscription();
1391
1411
  private timeToFirstFrame;
1412
+ private resolvedDecoder;
1392
1413
  private willUseMediaSource;
1414
+ private shouldUseWebCodecs;
1415
+ private isWebCodecsVideoAvailable;
1416
+ private isWebCodecsAudioAvailable;
1417
+ /**
1418
+ * Returns true if OffscreenCanvas should be used.
1419
+ * Implicitly enabled when the resolved decoder is WebCodecs, since OffscreenCanvas
1420
+ * is the optimal rendering path for WebCodecs (worker renders VideoFrames directly).
1421
+ */
1422
+ private shouldUseOffscreenCanvas;
1393
1423
  }
1394
1424
  interface TelemetryModuleStatistics {
1395
1425
  /**
@@ -1453,6 +1483,12 @@ interface TimeShiftInfo {
1453
1483
  urls: string[];
1454
1484
  duration: string;
1455
1485
  }
1486
+ interface CmafFragmentEvent {
1487
+ mediaType: "video" | "audio";
1488
+ initSegment: Readonly<Uint8Array<ArrayBuffer>>;
1489
+ fragment: Readonly<Uint8Array<ArrayBuffer>>;
1490
+ isKeyframe: boolean;
1491
+ }
1456
1492
  interface PublicVindralEvents {
1457
1493
  /**
1458
1494
  * When an error that requires action has occured
@@ -1553,6 +1589,13 @@ interface PublicVindralEvents {
1553
1589
  * Emitted when the timeshift URLs are updated.
1554
1590
  */
1555
1591
  ["timeshift info"]: Readonly<TimeShiftInfo>;
1592
+ /**
1593
+ * Emitted for each CMAF fragment received via the MoQ/VoQ path.
1594
+ * Contains raw fMP4 bytes suitable for recording.
1595
+ *
1596
+ * @internal Not part of the public API — may change without notice.
1597
+ */
1598
+ ["cmaf fragment"]: Readonly<CmafFragmentEvent>;
1556
1599
  ["buffer state event"]: Readonly<BufferStateEvent>;
1557
1600
  ["initialized media"]: void;
1558
1601
  }
@@ -1815,6 +1858,7 @@ declare class Controller extends HTMLElement {
1815
1858
  "mse-opus-enabled",
1816
1859
  "ios-background-play-enabled",
1817
1860
  "ios-wake-lock-enabled",
1861
+ "stream-to-media-element-enabled",
1818
1862
  "ios-media-element-enabled",
1819
1863
  "abr-enabled",
1820
1864
  "size-based-resolution-cap-enabled",
@@ -1829,8 +1873,8 @@ declare class Controller extends HTMLElement {
1829
1873
  "drm-playready-video-robustness",
1830
1874
  "drm-playready-audio-robustness",
1831
1875
  "webtransport-enabled",
1832
- "webcodecs-enabled",
1833
1876
  "webcodecs-hardware-acceleration",
1877
+ "decoders",
1834
1878
  "offscreen-canvas-enabled",
1835
1879
  "reconnect-retries",
1836
1880
  "auto-instance-enabled",
@@ -1920,7 +1964,7 @@ declare class PlayButton extends VindralButton {
1920
1964
  type PlayerAttributes = (typeof Player.observedAttributes)[number];
1921
1965
  declare class Player extends HTMLElement {
1922
1966
  #private;
1923
- 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" | "vu-meter" | "timeshift" | "timeshift-position" | "poster-src" | "url" | "edge-url" | "target-buffer-time" | "cast-receiver-id" | "cast-background" | "log-level" | "max-size" | "min-buffer-time" | "max-buffer-time" | "max-audio-bit-rate" | "burst-enabled" | "mse-enabled" | "mse-opus-enabled" | "ios-background-play-enabled" | "ios-wake-lock-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-enabled" | "webcodecs-hardware-acceleration" | "offscreen-canvas-enabled" | "reconnect-retries" | "auto-instance-enabled" | "advanced-rendition-menu-enabled" | "refresh-poster-enabled" | "stream-poll-enabled")[];
1967
+ 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" | "vu-meter" | "timeshift" | "timeshift-position" | "poster-src" | "url" | "decoders" | "edge-url" | "target-buffer-time" | "cast-receiver-id" | "cast-background" | "log-level" | "max-size" | "min-buffer-time" | "max-buffer-time" | "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")[];
1924
1968
  constructor();
1925
1969
  connectedCallback(): void;
1926
1970
  disconnectedCallback(): void;