@stream-io/video-client 1.49.0 → 1.51.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.browser.es.js +1404 -682
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +1404 -682
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +1404 -682
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +43 -3
  9. package/dist/src/coordinator/connection/client.d.ts +1 -1
  10. package/dist/src/coordinator/connection/connection.d.ts +31 -25
  11. package/dist/src/coordinator/connection/types.d.ts +14 -0
  12. package/dist/src/coordinator/connection/utils.d.ts +1 -0
  13. package/dist/src/devices/CameraManager.d.ts +1 -0
  14. package/dist/src/devices/DeviceManager.d.ts +23 -0
  15. package/dist/src/devices/DeviceManagerState.d.ts +0 -1
  16. package/dist/src/devices/VirtualDevice.d.ts +59 -0
  17. package/dist/src/devices/devicePersistence.d.ts +1 -1
  18. package/dist/src/devices/index.d.ts +1 -0
  19. package/dist/src/gen/video/sfu/event/events.d.ts +5 -1
  20. package/dist/src/gen/video/sfu/models/models.d.ts +34 -0
  21. package/dist/src/helpers/AudioBindingsWatchdog.d.ts +3 -3
  22. package/dist/src/helpers/BlockedAudioTracker.d.ts +30 -0
  23. package/dist/src/helpers/DynascaleManager.d.ts +8 -86
  24. package/dist/src/helpers/MediaPlaybackWatchdog.d.ts +32 -0
  25. package/dist/src/helpers/TrackSubscriptionManager.d.ts +114 -0
  26. package/dist/src/helpers/ViewportTracker.d.ts +11 -17
  27. package/dist/src/helpers/browsers.d.ts +13 -0
  28. package/dist/src/helpers/concurrency.d.ts +6 -4
  29. package/dist/src/rtc/BasePeerConnection.d.ts +7 -2
  30. package/dist/src/rtc/Publisher.d.ts +38 -3
  31. package/dist/src/rtc/Subscriber.d.ts +1 -0
  32. package/dist/src/rtc/TransceiverCache.d.ts +5 -1
  33. package/dist/src/rtc/helpers/degradationPreference.d.ts +3 -0
  34. package/dist/src/rtc/types.d.ts +2 -0
  35. package/dist/src/stats/rtc/types.d.ts +1 -1
  36. package/dist/src/store/rxUtils.d.ts +9 -0
  37. package/dist/src/types.d.ts +18 -0
  38. package/package.json +2 -2
  39. package/src/Call.ts +111 -33
  40. package/src/__tests__/Call.lifecycle.test.ts +67 -0
  41. package/src/coordinator/connection/__tests__/connection.test.ts +482 -0
  42. package/src/coordinator/connection/client.ts +1 -1
  43. package/src/coordinator/connection/connection.ts +149 -96
  44. package/src/coordinator/connection/types.ts +15 -0
  45. package/src/coordinator/connection/utils.ts +15 -0
  46. package/src/devices/CameraManager.ts +9 -2
  47. package/src/devices/DeviceManager.ts +239 -39
  48. package/src/devices/DeviceManagerState.ts +4 -2
  49. package/src/devices/VirtualDevice.ts +69 -0
  50. package/src/devices/__tests__/CameraManager.test.ts +19 -0
  51. package/src/devices/__tests__/DeviceManager.test.ts +404 -1
  52. package/src/devices/__tests__/mocks.ts +2 -0
  53. package/src/devices/devicePersistence.ts +2 -1
  54. package/src/devices/index.ts +1 -0
  55. package/src/gen/video/sfu/event/events.ts +15 -0
  56. package/src/gen/video/sfu/models/models.ts +44 -0
  57. package/src/helpers/AudioBindingsWatchdog.ts +10 -7
  58. package/src/helpers/BlockedAudioTracker.ts +74 -0
  59. package/src/helpers/DynascaleManager.ts +46 -337
  60. package/src/helpers/MediaPlaybackWatchdog.ts +121 -0
  61. package/src/helpers/TrackSubscriptionManager.ts +243 -0
  62. package/src/helpers/ViewportTracker.ts +74 -19
  63. package/src/helpers/__tests__/BlockedAudioTracker.test.ts +114 -0
  64. package/src/helpers/__tests__/DynascaleManager.test.ts +175 -122
  65. package/src/helpers/__tests__/MediaPlaybackWatchdog.test.ts +180 -0
  66. package/src/helpers/__tests__/TrackSubscriptionManager.test.ts +310 -0
  67. package/src/helpers/__tests__/ViewportTracker.test.ts +83 -0
  68. package/src/helpers/__tests__/browsers.test.ts +85 -1
  69. package/src/helpers/browsers.ts +24 -0
  70. package/src/helpers/concurrency.ts +9 -10
  71. package/src/rtc/BasePeerConnection.ts +15 -3
  72. package/src/rtc/Publisher.ts +185 -40
  73. package/src/rtc/Subscriber.ts +42 -14
  74. package/src/rtc/TransceiverCache.ts +10 -3
  75. package/src/rtc/__tests__/Call.reconnect.test.ts +121 -2
  76. package/src/rtc/__tests__/Publisher.test.ts +747 -88
  77. package/src/rtc/__tests__/Subscriber.test.ts +148 -3
  78. package/src/rtc/__tests__/mocks/webrtc.mocks.ts +13 -2
  79. package/src/rtc/helpers/__tests__/degradationPreference.test.ts +55 -0
  80. package/src/rtc/helpers/degradationPreference.ts +40 -0
  81. package/src/rtc/types.ts +2 -0
  82. package/src/stats/rtc/types.ts +1 -0
  83. package/src/store/__tests__/rxUtils.test.ts +276 -0
  84. package/src/store/rxUtils.ts +19 -0
  85. package/src/types.ts +19 -0
@@ -5,7 +5,11 @@ import type { AcceptCallResponse, BlockUserResponse, CallRingEvent, CallSettings
5
5
  import { AudioTrackType, CallConstructor, CallLeaveOptions, CallRecordingType, ClientPublishOptions, ClosedCaptionsSettings, JoinCallData, StartCallRecordingFnType, TrackMuteType, VideoTrackType } from './types';
6
6
  import { ClientCapability, TrackType, VideoDimension } from './gen/video/sfu/models/models';
7
7
  import { Tracer } from './stats';
8
+ import { AudioBindingsWatchdog } from './helpers/AudioBindingsWatchdog';
9
+ import { BlockedAudioTracker } from './helpers/BlockedAudioTracker';
10
+ import { TrackSubscriptionManager } from './helpers/TrackSubscriptionManager';
8
11
  import { DynascaleManager } from './helpers/DynascaleManager';
12
+ import { ViewportTracker } from './helpers/ViewportTracker';
9
13
  import { PermissionsContext } from './permissions';
10
14
  import { StreamClient } from './coordinator/connection/client';
11
15
  import { AllCallEvents, CallEventListener, RejectReason } from './coordinator/connection/types';
@@ -55,7 +59,28 @@ export declare class Call {
55
59
  /**
56
60
  * The DynascaleManager instance.
57
61
  */
58
- readonly dynascaleManager: DynascaleManager;
62
+ readonly dynascaleManager: DynascaleManager | undefined;
63
+ /**
64
+ * Tracks viewport visibility for participant video elements.
65
+ * Available only in DOM environments.
66
+ */
67
+ readonly viewportTracker: ViewportTracker | undefined;
68
+ /**
69
+ * Owns the SFU-side video-subscription state (per-session and global overrides).
70
+ */
71
+ readonly trackSubscriptionManager: TrackSubscriptionManager;
72
+ /**
73
+ * Warns periodically when a remote participant is publishing audio, but no
74
+ * `<audio>` element has been bound for them.
75
+ */
76
+ readonly audioBindingsWatchdog: AudioBindingsWatchdog | undefined;
77
+ /**
78
+ * Tracks audio elements blocked by the browser's autoplay policy.
79
+ * Subscribe to `blockedAudioTracker.autoplayBlocked$` to react to the
80
+ * blocked state, and call {@link Call.resumeAudio} inside a user gesture
81
+ * to retry playback.
82
+ */
83
+ readonly blockedAudioTracker: BlockedAudioTracker;
59
84
  subscriber?: Subscriber;
60
85
  publisher?: Publisher;
61
86
  /**
@@ -78,6 +103,7 @@ export declare class Call {
78
103
  private statsReportingIntervalInMs;
79
104
  private statsReporter?;
80
105
  private sfuStatsReporter?;
106
+ private lastStatsOptions?;
81
107
  private dropTimeout;
82
108
  private readonly clientStore;
83
109
  readonly streamClient: StreamClient;
@@ -376,6 +402,16 @@ export declare class Call {
376
402
  * @param trackTypes the track types to update the call state with.
377
403
  */
378
404
  private updateLocalStreamState;
405
+ /**
406
+ * Re-arms the encoder for a currently published track type. Useful for
407
+ * working around WebKit's stalled sender bug after an iOS audio session
408
+ * interruption (Siri, PSTN call).
409
+ *
410
+ * @internal
411
+ *
412
+ * @param trackType the track type to refresh.
413
+ */
414
+ refreshPublishedTrack: (trackType: TrackType) => Promise<void>;
379
415
  /**
380
416
  * Updates the preferred publishing options
381
417
  *
@@ -777,13 +813,13 @@ export declare class Call {
777
813
  * @param sessionId the session id.
778
814
  * @param trackType the video mode.
779
815
  */
780
- trackElementVisibility: <T extends HTMLElement>(element: T, sessionId: string, trackType: VideoTrackType) => () => void;
816
+ trackElementVisibility: <T extends HTMLElement>(element: T, sessionId: string, trackType: VideoTrackType) => (() => void) | undefined;
781
817
  /**
782
818
  * Sets the viewport element to track bound video elements for visibility.
783
819
  *
784
820
  * @param element the viewport element.
785
821
  */
786
- setViewport: <T extends HTMLElement>(element: T) => () => void;
822
+ setViewport: <T extends HTMLElement>(element: T) => (() => void) | undefined;
787
823
  /**
788
824
  * Binds a DOM <video> element to the given session id.
789
825
  * This method will make sure that the video element will play
@@ -813,6 +849,10 @@ export declare class Call {
813
849
  bindAudioElement: (audioElement: HTMLAudioElement, sessionId: string, trackType?: AudioTrackType) => (() => void) | undefined;
814
850
  /**
815
851
  * Plays all audio elements blocked by the browser's autoplay policy.
852
+ * Must be called from within a user gesture (e.g., click handler).
853
+ *
854
+ * Subscribe to `call.blockedAudioTracker.autoplayBlocked$` to know when a
855
+ * gesture is required.
816
856
  */
817
857
  resumeAudio: () => Promise<void>;
818
858
  /**
@@ -113,7 +113,7 @@ export declare class StreamClient {
113
113
  */
114
114
  _setupConnectionIdPromise: () => void;
115
115
  get connectionIdPromise(): Promise<string | undefined> | undefined;
116
- get isConnectionIsPromisePending(): boolean;
116
+ get isConnectionIdPromisePending(): boolean;
117
117
  get wsPromise(): Promise<ConnectedEvent | undefined> | undefined;
118
118
  _logApiRequest: (type: string, url: string, data: unknown, config: AxiosRequestConfig & {
119
119
  config?: AxiosRequestConfig & {
@@ -1,5 +1,5 @@
1
1
  import { StreamClient } from './client';
2
- import type { UR } from './types';
2
+ import type { UR, WSConnectionError } from './types';
3
3
  import type { LogLevel } from '@stream-io/logger';
4
4
  import type { ConnectedEvent } from '../../gen/coordinator';
5
5
  /**
@@ -20,43 +20,48 @@ import type { ConnectedEvent } from '../../gen/coordinator';
20
20
  * - if the servers fails to publish a message to the client, the WS connection is destroyed
21
21
  */
22
22
  export declare class StableWSConnection {
23
+ client: StreamClient;
24
+ ws?: WebSocket;
25
+ /** Incremented when a new WS connection is made */
26
+ wsID: number;
27
+ /** We only make 1 attempt to reconnect at the same time.. */
28
+ isConnecting: boolean;
29
+ /** To avoid reconnect if client is disconnected */
30
+ isDisconnected: boolean;
31
+ /** Boolean that indicates if we have a working connection to the server */
32
+ isHealthy: boolean;
23
33
  connectionID?: string;
24
34
  private connectionOpenSafe?;
35
+ resolveConnectionOpen?: (value: ConnectedEvent) => void;
36
+ rejectConnectionOpen?: (reason?: WSConnectionError) => void;
37
+ /** Boolean that indicates if the connection promise is resolved */
38
+ isConnectionOpenResolved?: boolean;
39
+ /** consecutive failures influence the duration of the timeout */
25
40
  consecutiveFailures: number;
41
+ /** keep track of the total number of failures */
42
+ totalFailures: number;
43
+ /** Send a health check message every 25 seconds */
26
44
  pingInterval: number;
27
45
  healthCheckTimeoutRef?: number;
28
- isConnecting: boolean;
29
- isDisconnected: boolean;
30
- isHealthy: boolean;
31
- isConnectionOpenResolved?: boolean;
32
- lastEvent: Date | null;
33
46
  connectionCheckTimeout: number;
34
47
  connectionCheckTimeoutRef?: NodeJS.Timeout;
35
- rejectConnectionOpen?: (reason?: Error & {
36
- code?: string | number;
37
- isWSFailure?: boolean;
38
- StatusCode?: string | number;
39
- }) => void;
40
- resolveConnectionOpen?: (value: ConnectedEvent) => void;
41
- totalFailures: number;
42
- ws?: WebSocket;
43
- wsID: number;
44
- client: StreamClient;
48
+ /** Store the last event time for health checks */
49
+ lastEvent: Date | null;
45
50
  constructor(client: StreamClient);
46
- _log: (msg: string, extra?: UR, level?: LogLevel) => void;
51
+ _log: (msg: string, extra?: UR | Error, level?: LogLevel) => void;
47
52
  setClient: (client: StreamClient) => void;
48
53
  /**
49
54
  * connect - Connect to the WS URL
50
55
  * the default 15s timeout allows between 2~3 tries
51
- * @return {ConnectAPIResponse<ConnectedEvent>} Promise that completes once the first health check message is received
56
+ * @return Promise that completes once the first health check message is received
52
57
  */
53
- connect(timeout?: number): Promise<ConnectedEvent | undefined>;
58
+ connect: (timeout?: number) => Promise<ConnectedEvent | undefined>;
54
59
  /**
55
60
  * _waitForHealthy polls the promise connection to see if its resolved until it times out
56
61
  * the default 15s timeout allows between 2~3 tries
57
62
  * @param timeout duration(ms)
58
63
  */
59
- _waitForHealthy(timeout?: number): Promise<ConnectedEvent | undefined>;
64
+ _waitForHealthy: (timeout?: number) => Promise<ConnectedEvent | undefined>;
60
65
  /**
61
66
  * Builds and returns the url for websocket.
62
67
  * @private
@@ -65,15 +70,17 @@ export declare class StableWSConnection {
65
70
  _buildUrl: () => string;
66
71
  /**
67
72
  * disconnect - Disconnect the connection and doesn't recover...
68
- *
69
73
  */
70
- disconnect(timeout?: number): Promise<void>;
74
+ disconnect: (timeout?: number) => Promise<void>;
71
75
  /**
72
76
  * _connect - Connect to the WS endpoint
73
77
  *
74
- * @return {ConnectAPIResponse<ConnectedEvent>} Promise that completes once the first health check message is received
78
+ * @param timeoutMs handshake watchdog deadline in ms. Defaults to
79
+ * `client.defaultWSTimeout` when not provided. Top-level `connect(timeout)`
80
+ * passes its own timeout through so caller-supplied deadlines are honored.
81
+ * @return Promise that completes once the first health check message is received
75
82
  */
76
- _connect(): Promise<ConnectedEvent | undefined>;
83
+ _connect: (timeoutMs?: number) => Promise<ConnectedEvent | undefined>;
77
84
  /**
78
85
  * _reconnect - Retry the connection to WS endpoint
79
86
  *
@@ -90,7 +97,6 @@ export declare class StableWSConnection {
90
97
  * onlineStatusChanged - this function is called when the browser connects or disconnects from the internet.
91
98
  *
92
99
  * @param {Event} event Event with type online or offline
93
- *
94
100
  */
95
101
  onlineStatusChanged: (event: Event) => void;
96
102
  onopen: (wsID: number) => void;
@@ -36,6 +36,20 @@ export type APIErrorResponse = {
36
36
  details?: ErrorResponseDetails;
37
37
  unrecoverable?: boolean;
38
38
  };
39
+ /**
40
+ * A standard `Error` augmented with the metadata that the coordinator
41
+ * WebSocket connection layer attaches to rejection causes.
42
+ * `isWSFailure: true` marks transient/retriable failures; absent or `false`
43
+ * indicates a permanent failure that should not be retried.
44
+ */
45
+ export type WSConnectionError = Error & {
46
+ code?: string | number;
47
+ isWSFailure?: boolean;
48
+ StatusCode?: string | number;
49
+ reason?: string;
50
+ wasClean?: boolean;
51
+ target?: EventTarget | null;
52
+ };
39
53
  export declare class ErrorFromResponse<T> extends Error {
40
54
  code: number | null;
41
55
  status: number;
@@ -2,6 +2,7 @@ import type { AxiosResponse } from 'axios';
2
2
  import type { APIErrorResponse } from './types';
3
3
  import type { ConnectionErrorEvent } from '../../gen/coordinator';
4
4
  export declare const sleep: (m: number) => Promise<unknown>;
5
+ export declare const timeboxed: <T extends Promise<unknown>[]>(promises: [...T], ms: number) => Promise<{ [K in keyof T]: Awaited<T[K]>; }>;
5
6
  export declare function isFunction<T>(value: Function | T): value is Function;
6
7
  /**
7
8
  * A map of known error codes.
@@ -45,5 +45,6 @@ export declare class CameraManager extends DeviceManager<CameraManagerState> {
45
45
  */
46
46
  apply(settings: VideoSettingsResponse, publish: boolean): Promise<void>;
47
47
  protected getDevices(): Observable<MediaDeviceInfo[]>;
48
+ protected getResolvedConstraints(constraints: MediaTrackConstraints): MediaTrackConstraints;
48
49
  protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
49
50
  }
@@ -6,6 +6,7 @@ import { ScopedLogger } from '../logger';
6
6
  import { TrackType } from '../gen/video/sfu/models/models';
7
7
  import { MediaStreamFilter, MediaStreamFilterRegistrationResult } from './filters';
8
8
  import { DevicePersistenceOptions } from './devicePersistence';
9
+ import { VirtualDevice, VirtualDeviceEntry, VirtualDeviceHandle } from './VirtualDevice';
9
10
  export declare abstract class DeviceManager<S extends DeviceManagerState<C>, C = MediaTrackConstraints> {
10
11
  /**
11
12
  * if true, stops the media stream when call is left
@@ -16,10 +17,14 @@ export declare abstract class DeviceManager<S extends DeviceManagerState<C>, C =
16
17
  protected readonly call: Call;
17
18
  protected readonly trackType: TrackType;
18
19
  protected subscriptions: (() => void)[];
20
+ protected currentStreamCleanups: (() => void)[];
19
21
  protected devicePersistence: Required<DevicePersistenceOptions>;
20
22
  protected areSubscriptionsSetUp: boolean;
21
23
  private isTrackStoppedDueToTrackEnd;
22
24
  private filters;
25
+ private virtualDevicesSubject;
26
+ private activeVirtualSession;
27
+ private virtualDeviceConcurrencyTag;
23
28
  private statusChangeConcurrencyTag;
24
29
  private filterRegistrationConcurrencyTag;
25
30
  protected constructor(call: Call, state: S, trackType: TrackType, devicePersistence: Required<DevicePersistenceOptions>);
@@ -32,6 +37,21 @@ export declare abstract class DeviceManager<S extends DeviceManagerState<C>, C =
32
37
  * @returns an Observable that will be updated if a device is connected or disconnected
33
38
  */
34
39
  listDevices(): Observable<MediaDeviceInfo[]>;
40
+ /**
41
+ * Registers a virtual camera or microphone backed by a caller-supplied
42
+ * stream factory. The device appears in `listDevices()` and can be selected
43
+ * via `select()` like any real device.
44
+ *
45
+ * Web only. React Native is not supported.
46
+ *
47
+ * Only supported for camera and microphone managers; calling on any other
48
+ * manager throws.
49
+ */
50
+ registerVirtualDevice(virtualDevice: VirtualDevice<C>): VirtualDeviceHandle;
51
+ protected sanitizeVirtualStream(stream: MediaStream): MediaStream;
52
+ protected findVirtualDevice(deviceId: string | undefined): VirtualDeviceEntry<C> | undefined;
53
+ private stopActiveVirtualSession;
54
+ protected getSelectedStream(constraints: C): Promise<MediaStream>;
35
55
  /**
36
56
  * Returns `true` when this device is in enabled state.
37
57
  */
@@ -89,8 +109,10 @@ export declare abstract class DeviceManager<S extends DeviceManagerState<C>, C =
89
109
  * @internal
90
110
  */
91
111
  dispose: () => void;
112
+ private runCurrentStreamCleanups;
92
113
  protected applySettingsToStream(): Promise<void>;
93
114
  protected abstract getDevices(): Observable<MediaDeviceInfo[]>;
115
+ protected getResolvedConstraints(constraints: C): C;
94
116
  protected abstract getStream(constraints: C): Promise<MediaStream>;
95
117
  protected publishStream(stream: MediaStream, options?: TrackPublishOptions): Promise<void>;
96
118
  protected stopPublishStream(): Promise<void>;
@@ -101,6 +123,7 @@ export declare abstract class DeviceManager<S extends DeviceManagerState<C>, C =
101
123
  private stopTracks;
102
124
  private muteLocalStream;
103
125
  protected unmuteStream(): Promise<void>;
126
+ private setLocalInterrupted;
104
127
  private get mediaDeviceKind();
105
128
  private handleDisconnectedOrReplacedDevices;
106
129
  protected findDevice(devices: MediaDeviceInfo[], deviceId: string): MediaDeviceInfo | undefined;
@@ -15,7 +15,6 @@ export declare abstract class DeviceManagerState<C = MediaTrackConstraints> {
15
15
  prevStatus: InputDeviceStatus;
16
16
  /**
17
17
  * An Observable that emits the current media stream, or `undefined` if the device is currently disabled.
18
- *
19
18
  */
20
19
  mediaStream$: Observable<MediaStream | undefined>;
21
20
  /**
@@ -0,0 +1,59 @@
1
+ /**
2
+ * A MediaStream produced for a virtual device session, along with an optional
3
+ * cleanup callback. Returned by {@link VirtualDevice.getUserMedia}.
4
+ */
5
+ export interface VirtualDeviceSession {
6
+ readonly stream: MediaStream;
7
+ readonly stop?: () => void | Promise<void>;
8
+ }
9
+ /**
10
+ * A virtual camera or microphone definition supplied by the integrator.
11
+ *
12
+ * Pass this to `camera.registerVirtualDevice()` /
13
+ * `microphone.registerVirtualDevice()` to make it appear in the device list
14
+ * and become selectable.
15
+ */
16
+ export interface VirtualDevice<C = MediaTrackConstraints> {
17
+ /**
18
+ * Human-readable label shown in device dropdowns.
19
+ */
20
+ label: string;
21
+ /**
22
+ * Called when the virtual device is selected and the SDK needs media.
23
+ * Returns the MediaStream to publish along with an optional `stop`
24
+ * callback that runs when the session is replaced, the tracks end, or
25
+ * the device is unregistered.
26
+ *
27
+ * `constraints` is the resolved set the SDK would otherwise pass to
28
+ * `getUserMedia` for a real device.
29
+ */
30
+ getUserMedia: (constraints: C) => VirtualDeviceSession | Promise<VirtualDeviceSession>;
31
+ }
32
+ /**
33
+ * @internal Internal entry stored in the device manager's registry.
34
+ */
35
+ export interface VirtualDeviceEntry<C = MediaTrackConstraints> extends VirtualDevice<C> {
36
+ readonly deviceId: string;
37
+ readonly kind: 'audioinput' | 'videoinput';
38
+ }
39
+ /**
40
+ * @internal Tracks the currently active virtual device session inside the
41
+ * device manager so its `stop` callback can be invoked when the session is
42
+ * replaced or torn down.
43
+ */
44
+ export interface ActiveVirtualSession {
45
+ deviceId: string;
46
+ stop?: () => void | Promise<void>;
47
+ }
48
+ export interface VirtualDeviceHandle {
49
+ /**
50
+ * The device id under which the virtual device was registered. Pass this
51
+ * to `camera.select()` / `microphone.select()` to switch to it.
52
+ */
53
+ readonly deviceId: string;
54
+ /**
55
+ * Removes the virtual device from the manager. If it is currently selected,
56
+ * the selection is reset so the SDK falls back to the default device.
57
+ */
58
+ unregister: () => Promise<void>;
59
+ }
@@ -21,7 +21,7 @@ export type LocalDevicePreferences = {
21
21
  };
22
22
  export declare const defaultDeviceId = "default";
23
23
  export declare const normalize: (options: DevicePersistenceOptions | undefined) => Required<DevicePersistenceOptions>;
24
- export declare const createSyntheticDevice: (deviceId: string, kind: MediaDeviceKind) => MediaDeviceInfo;
24
+ export declare const createSyntheticDevice: (deviceId: string, kind: MediaDeviceKind, label?: string) => MediaDeviceInfo;
25
25
  export declare const readPreferences: (storageKey: string) => LocalDevicePreferences;
26
26
  export declare const writePreferences: (currentDevice: MediaDeviceInfo | undefined, deviceKey: DevicePreferenceKey, muted: boolean | undefined, storageKey: string) => void;
27
27
  export declare const toPreferenceList: (preference?: LocalDevicePreference | LocalDevicePreference[]) => LocalDevicePreference[];
@@ -10,3 +10,4 @@ export * from './ScreenShareManager';
10
10
  export * from './ScreenShareState';
11
11
  export * from './SpeakerManager';
12
12
  export * from './SpeakerState';
13
+ export * from './VirtualDevice';
@@ -1,5 +1,5 @@
1
1
  import { MessageType } from '@protobuf-ts/runtime';
2
- import { CallEndedReason, CallGrants, CallState, ClientCapability, ClientDetails, Codec, ConnectionQuality, Error as Error$, GoAwayReason, ICETrickle as ICETrickle$, Participant, ParticipantCount, ParticipantSource, PeerType, Pin, PublishOption, SubscribeOption, TrackInfo, TrackType, TrackUnpublishReason, WebsocketReconnectStrategy } from '../models/models';
2
+ import { CallEndedReason, CallGrants, CallState, ClientCapability, ClientDetails, Codec, ConnectionQuality, DegradationPreference, Error as Error$, GoAwayReason, ICETrickle as ICETrickle$, Participant, ParticipantCount, ParticipantSource, PeerType, Pin, PublishOption, SubscribeOption, TrackInfo, TrackType, TrackUnpublishReason, WebsocketReconnectStrategy } from '../models/models';
3
3
  import { TrackSubscriptionDetails } from '../signal_rpc/signal';
4
4
  /**
5
5
  * SFUEvent is a message that is sent from the SFU to the client.
@@ -785,6 +785,10 @@ export interface VideoSender {
785
785
  * @generated from protobuf field: int32 publish_option_id = 5;
786
786
  */
787
787
  publishOptionId: number;
788
+ /**
789
+ * @generated from protobuf field: stream.video.sfu.models.DegradationPreference degradation_preference = 6;
790
+ */
791
+ degradationPreference: DegradationPreference;
788
792
  }
789
793
  /**
790
794
  * sent to users when they need to change the quality of their video
@@ -303,6 +303,12 @@ export interface PublishOption {
303
303
  * @generated from protobuf field: repeated stream.video.sfu.models.AudioBitrate audio_bitrate_profiles = 10;
304
304
  */
305
305
  audioBitrateProfiles: AudioBitrate[];
306
+ /**
307
+ * The degradation preference for video encoding.
308
+ *
309
+ * @generated from protobuf field: stream.video.sfu.models.DegradationPreference degradation_preference = 11;
310
+ */
311
+ degradationPreference: DegradationPreference;
306
312
  }
307
313
  /**
308
314
  * @generated from protobuf message stream.video.sfu.models.Codec
@@ -1168,6 +1174,34 @@ export declare enum ClientCapability {
1168
1174
  */
1169
1175
  SUBSCRIBER_VIDEO_PAUSE = 1
1170
1176
  }
1177
+ /**
1178
+ * DegradationPreference represents the RTCDegradationPreference from WebRTC.
1179
+ * See https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/setParameters#degradationpreference
1180
+ *
1181
+ * @generated from protobuf enum stream.video.sfu.models.DegradationPreference
1182
+ */
1183
+ export declare enum DegradationPreference {
1184
+ /**
1185
+ * @generated from protobuf enum value: DEGRADATION_PREFERENCE_UNSPECIFIED = 0;
1186
+ */
1187
+ UNSPECIFIED = 0,
1188
+ /**
1189
+ * @generated from protobuf enum value: DEGRADATION_PREFERENCE_BALANCED = 1;
1190
+ */
1191
+ BALANCED = 1,
1192
+ /**
1193
+ * @generated from protobuf enum value: DEGRADATION_PREFERENCE_MAINTAIN_FRAMERATE = 2;
1194
+ */
1195
+ MAINTAIN_FRAMERATE = 2,
1196
+ /**
1197
+ * @generated from protobuf enum value: DEGRADATION_PREFERENCE_MAINTAIN_RESOLUTION = 3;
1198
+ */
1199
+ MAINTAIN_RESOLUTION = 3,
1200
+ /**
1201
+ * @generated from protobuf enum value: DEGRADATION_PREFERENCE_MAINTAIN_FRAMERATE_AND_RESOLUTION = 4;
1202
+ */
1203
+ MAINTAIN_FRAMERATE_AND_RESOLUTION = 4
1204
+ }
1171
1205
  declare class CallState$Type extends MessageType<CallState> {
1172
1206
  constructor();
1173
1207
  }
@@ -6,19 +6,19 @@ import { Tracer } from '../stats';
6
6
  * remote participants whose audio streams have no bound element.
7
7
  */
8
8
  export declare class AudioBindingsWatchdog {
9
- private state;
10
- private tracer;
11
9
  private bindings;
12
10
  private enabled;
13
11
  private watchdogInterval?;
14
12
  private readonly unsubscribeCallingState;
15
13
  private logger;
14
+ private readonly state;
15
+ private readonly tracer;
16
16
  constructor(state: CallState, tracer: Tracer);
17
17
  /**
18
18
  * Registers an audio element binding for the given session and track type.
19
19
  * Warns if a different element is already bound to the same key.
20
20
  */
21
- register: (audioElement: HTMLAudioElement, sessionId: string, trackType: AudioTrackType) => void;
21
+ register: (element: HTMLAudioElement, sessionId: string, trackType: AudioTrackType) => void;
22
22
  /**
23
23
  * Removes the audio element binding for the given session and track type.
24
24
  */
@@ -0,0 +1,30 @@
1
+ import { Tracer } from '../stats';
2
+ /**
3
+ * Tracks audio elements that the browser's autoplay policy has blocked.
4
+ */
5
+ export declare class BlockedAudioTracker {
6
+ private logger;
7
+ private tracer;
8
+ private blockedElementsSubject;
9
+ /**
10
+ * Whether the browser's autoplay policy is blocking audio playback.
11
+ * Will be `true` when at least one audio element is currently blocked.
12
+ * Use {@link resumeAudio} within a user gesture to unblock.
13
+ */
14
+ autoplayBlocked$: import("rxjs").Observable<boolean>;
15
+ constructor(tracer: Tracer);
16
+ /**
17
+ * Registers an audio element as blocked by the browser's autoplay policy.
18
+ */
19
+ markBlocked: (audioElement: HTMLAudioElement, blocked: boolean) => void;
20
+ /**
21
+ * Returns whether the given audio element is currently flagged as blocked
22
+ * by the browser's autoplay policy.
23
+ */
24
+ isBlocked: (audioElement: HTMLAudioElement) => boolean;
25
+ /**
26
+ * Plays all audio elements blocked by the browser's autoplay policy.
27
+ * Must be called from within a user gesture (e.g., click handler).
28
+ */
29
+ resumeAudio: () => Promise<void>;
30
+ }
@@ -1,102 +1,32 @@
1
- import { AudioTrackType, DebounceType, VideoTrackType } from '../types';
2
- import { VideoDimension } from '../gen/video/sfu/models/models';
3
- import { ViewportTracker } from './ViewportTracker';
4
- import { AudioBindingsWatchdog } from './AudioBindingsWatchdog';
5
- import type { TrackSubscriptionDetails } from '../gen/video/sfu/signal_rpc/signal';
1
+ import { AudioTrackType, VideoTrackType } from '../types';
2
+ import type { BlockedAudioTracker } from './BlockedAudioTracker';
3
+ import type { TrackSubscriptionManager } from './TrackSubscriptionManager';
6
4
  import { CallState } from '../store';
7
- import type { StreamSfuClient } from '../StreamSfuClient';
8
5
  import { SpeakerManager } from '../devices';
9
6
  import { Tracer } from '../stats';
10
- type VideoTrackSubscriptionOverride = {
11
- enabled: true;
12
- dimension: VideoDimension;
13
- } | {
14
- enabled: false;
15
- };
16
- declare const globalOverrideKey: unique symbol;
17
- interface VideoTrackSubscriptionOverrides {
18
- [sessionId: string]: VideoTrackSubscriptionOverride | undefined;
19
- [globalOverrideKey]?: VideoTrackSubscriptionOverride;
20
- }
21
7
  /**
22
8
  * A manager class that handles dynascale related tasks like:
23
9
  *
24
10
  * - binding video elements to session ids
25
11
  * - binding audio elements to session ids
26
- * - tracking element visibility
27
- * - updating subscriptions based on viewport visibility
28
- * - updating subscriptions based on video element dimensions
29
- * - updating subscriptions based on published tracks
30
12
  */
31
13
  export declare class DynascaleManager {
32
- /**
33
- * The viewport tracker instance.
34
- */
35
- readonly viewportTracker: ViewportTracker;
36
14
  private logger;
37
15
  private callState;
38
16
  private speaker;
39
- private tracer;
17
+ private readonly tracer;
40
18
  private useWebAudio;
41
19
  private audioContext;
42
- private sfuClient;
43
- private pendingSubscriptionsUpdate;
44
- readonly audioBindingsWatchdog: AudioBindingsWatchdog | undefined;
45
- /**
46
- * Audio elements that were blocked by the browser's autoplay policy.
47
- * These can be retried by calling `resumeAudio()` from a user gesture.
48
- */
49
- private blockedAudioElementsSubject;
50
- /**
51
- * Whether the browser's autoplay policy is blocking audio playback.
52
- * Will be `true` when the browser blocks autoplay (e.g., no prior user interaction).
53
- * Use `resumeAudio()` within a user gesture to unblock.
54
- */
55
- autoplayBlocked$: import("rxjs").Observable<boolean>;
56
- private addBlockedAudioElement;
57
- private removeBlockedAudioElement;
58
- private videoTrackSubscriptionOverridesSubject;
59
- videoTrackSubscriptionOverrides$: import("rxjs").Observable<VideoTrackSubscriptionOverrides>;
60
- incomingVideoSettings$: import("rxjs").Observable<{
61
- enabled: boolean;
62
- preferredResolution: VideoDimension | undefined;
63
- participants: {
64
- [k: string]: {
65
- enabled: boolean;
66
- preferredResolution: VideoDimension | undefined;
67
- };
68
- };
69
- isParticipantVideoEnabled: (sessionId: string) => boolean;
70
- }>;
20
+ private trackSubscriptionManager;
21
+ private blockedAudioTracker;
71
22
  /**
72
23
  * Creates a new DynascaleManager instance.
73
24
  */
74
- constructor(callState: CallState, speaker: SpeakerManager, tracer: Tracer);
25
+ constructor(callState: CallState, speaker: SpeakerManager, tracer: Tracer, trackSubscriptionManager: TrackSubscriptionManager, blockedAudioTracker: BlockedAudioTracker);
75
26
  /**
76
- * Disposes the allocated resources and closes the audio context if it was created.
27
+ * Closes the audio context if it was created.
77
28
  */
78
29
  dispose: () => Promise<void>;
79
- setSfuClient(sfuClient: StreamSfuClient | undefined): void;
80
- get trackSubscriptions(): TrackSubscriptionDetails[];
81
- get videoTrackSubscriptionOverrides(): VideoTrackSubscriptionOverrides;
82
- setVideoTrackSubscriptionOverrides: (override: VideoTrackSubscriptionOverride | undefined, sessionIds?: string[]) => VideoTrackSubscriptionOverrides;
83
- applyTrackSubscriptions: (debounceType?: DebounceType) => void;
84
- /**
85
- * Will begin tracking the given element for visibility changes within the
86
- * configured viewport element (`call.setViewport`).
87
- *
88
- * @param element the element to track.
89
- * @param sessionId the session id.
90
- * @param trackType the kind of video.
91
- * @returns Untrack.
92
- */
93
- trackElementVisibility: <T extends HTMLElement>(element: T, sessionId: string, trackType: VideoTrackType) => () => void;
94
- /**
95
- * Sets the viewport element to track bound video elements for visibility.
96
- *
97
- * @param element the viewport element.
98
- */
99
- setViewport: <T extends HTMLElement>(element: T) => () => void;
100
30
  /**
101
31
  * Sets whether to use WebAudio API for audio playback.
102
32
  * Must be set before joining the call.
@@ -134,14 +64,6 @@ export declare class DynascaleManager {
134
64
  * @returns a cleanup function that will unbind the audio element.
135
65
  */
136
66
  bindAudioElement: (audioElement: HTMLAudioElement, sessionId: string, trackType: AudioTrackType) => (() => void) | undefined;
137
- /**
138
- * Plays all audio elements blocked by the browser's autoplay policy.
139
- * Must be called from within a user gesture (e.g., click handler).
140
- *
141
- * @returns a promise that resolves when all blocked elements have been retried.
142
- */
143
- resumeAudio: () => Promise<void>;
144
67
  private getOrCreateAudioContext;
145
68
  private resumeAudioContext;
146
69
  }
147
- export {};