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