@stream-io/video-client 0.3.0 → 0.3.2

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 (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.browser.es.js +863 -455
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +867 -453
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +863 -455
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +18 -1
  9. package/dist/src/devices/CameraManager.d.ts +31 -0
  10. package/dist/src/devices/CameraManagerState.d.ts +28 -0
  11. package/dist/src/devices/InputMediaDeviceManager.d.ts +47 -0
  12. package/dist/src/devices/InputMediaDeviceManagerState.d.ts +69 -0
  13. package/dist/src/devices/MicrophoneManager.d.ts +19 -0
  14. package/dist/src/devices/MicrophoneManagerState.d.ts +4 -0
  15. package/dist/src/devices/__tests__/CameraManager.test.d.ts +1 -0
  16. package/dist/src/devices/__tests__/InputMediaDeviceManager.test.d.ts +1 -0
  17. package/dist/src/devices/__tests__/MicrophoneManager.test.d.ts +1 -0
  18. package/dist/src/devices/__tests__/mocks.d.ts +13 -0
  19. package/dist/src/devices/index.d.ts +6 -0
  20. package/dist/src/types.d.ts +4 -0
  21. package/dist/version.d.ts +1 -1
  22. package/package.json +1 -1
  23. package/src/Call.ts +100 -3
  24. package/src/__tests__/StreamVideoClient.test.ts +3 -0
  25. package/src/devices/CameraManager.ts +73 -0
  26. package/src/devices/CameraManagerState.ts +61 -0
  27. package/src/devices/InputMediaDeviceManager.ts +121 -0
  28. package/src/devices/InputMediaDeviceManagerState.ts +111 -0
  29. package/src/devices/MicrophoneManager.ts +45 -0
  30. package/src/devices/MicrophoneManagerState.ts +9 -0
  31. package/src/devices/__tests__/CameraManager.test.ts +150 -0
  32. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +159 -0
  33. package/src/devices/__tests__/MicrophoneManager.test.ts +103 -0
  34. package/src/devices/__tests__/mocks.ts +98 -0
  35. package/src/devices/index.ts +6 -0
  36. package/src/rtc/Publisher.ts +11 -19
  37. package/src/rtc/videoLayers.ts +7 -2
  38. package/src/types.ts +4 -0
@@ -5,7 +5,10 @@ import { AcceptCallResponse, BlockUserResponse, EndCallResponse, GetCallResponse
5
5
  import { CallConstructor, CallLeaveOptions, DebounceType, JoinCallData, PublishOptions, SubscriptionChanges } from './types';
6
6
  import { ViewportTracker } from './helpers/ViewportTracker';
7
7
  import { PermissionsContext } from './permissions';
8
+ import { StreamClient } from './coordinator/connection/client';
8
9
  import { CallEventHandler, CallEventTypes, EventTypes, Logger } from './coordinator/connection/types';
10
+ import { CameraManager } from './devices/CameraManager';
11
+ import { MicrophoneManager } from './devices/MicrophoneManager';
9
12
  /**
10
13
  * An object representation of a `Call`.
11
14
  */
@@ -35,6 +38,14 @@ export declare class Call {
35
38
  * updates from the backend.
36
39
  */
37
40
  watching: boolean;
41
+ /**
42
+ * Device manager for the camera
43
+ */
44
+ readonly camera: CameraManager;
45
+ /**
46
+ * Device manager for the microhpone
47
+ */
48
+ readonly microphone: MicrophoneManager;
38
49
  /**
39
50
  * Flag telling whether this call is a "ringing" call.
40
51
  */
@@ -55,7 +66,7 @@ export declare class Call {
55
66
  private statsReporter?;
56
67
  private dropTimeout;
57
68
  private readonly clientStore;
58
- private readonly streamClient;
69
+ readonly streamClient: StreamClient;
59
70
  private sfuClient?;
60
71
  private reconnectAttempts;
61
72
  private maxReconnectAttempts;
@@ -249,6 +260,8 @@ export declare class Call {
249
260
  *
250
261
  *
251
262
  * @param deviceId the selected device, pass `undefined` to clear the device selection
263
+ *
264
+ * @deprecated use call.microphone.select
252
265
  */
253
266
  setAudioDevice: (deviceId?: string) => void;
254
267
  /**
@@ -257,6 +270,8 @@ export declare class Call {
257
270
  * This method only stores the selection, if you want to start publishing a media stream call the [`publishVideoStream` method](#publishvideostream) that will set `videoDeviceId` as well.
258
271
  *
259
272
  * @param deviceId the selected device, pass `undefined` to clear the device selection
273
+ *
274
+ * @deprecated use call.camera.select
260
275
  */
261
276
  setVideoDevice: (deviceId?: string) => void;
262
277
  /**
@@ -459,4 +474,6 @@ export declare class Call {
459
474
  sendCustomEvent: (payload: {
460
475
  [key: string]: any;
461
476
  }) => Promise<SendEventResponse>;
477
+ private initCamera;
478
+ private initMic;
462
479
  }
@@ -0,0 +1,31 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Call } from '../Call';
3
+ import { CameraDirection, CameraManagerState } from './CameraManagerState';
4
+ import { InputMediaDeviceManager } from './InputMediaDeviceManager';
5
+ export declare class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
6
+ constructor(call: Call);
7
+ /**
8
+ * Select the camera direaction
9
+ * @param direction
10
+ */
11
+ selectDirection(direction: Exclude<CameraDirection, undefined>): Promise<void>;
12
+ /**
13
+ * Flips the camera direction: if it's front it will change to back, if it's back, it will change to front.
14
+ *
15
+ * Note: if there is no available camera with the desired direction, this method will do nothing.
16
+ * @returns
17
+ */
18
+ flip(): Promise<void>;
19
+ protected getDevices(): Observable<MediaDeviceInfo[]>;
20
+ protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
21
+ protected publishStream(stream: MediaStream): Promise<void>;
22
+ protected stopPublishStream(): Promise<void>;
23
+ /**
24
+ * Disables the video tracks of the camera
25
+ */
26
+ pause(): void;
27
+ /**
28
+ * (Re)enables the video tracks of the camera
29
+ */
30
+ resume(): void;
31
+ }
@@ -0,0 +1,28 @@
1
+ import { Observable } from 'rxjs';
2
+ import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
3
+ export type CameraDirection = 'front' | 'back' | undefined;
4
+ export declare class CameraManagerState extends InputMediaDeviceManagerState {
5
+ private directionSubject;
6
+ /**
7
+ * Observable that emits the preferred camera direction
8
+ * front - means the camera facing the user
9
+ * back - means the camera facing the environment
10
+ */
11
+ direction$: Observable<CameraDirection>;
12
+ constructor();
13
+ /**
14
+ * The preferred camera direction
15
+ * front - means the camera facing the user
16
+ * back - means the camera facing the environment
17
+ */
18
+ get direction(): CameraDirection;
19
+ /**
20
+ * @internal
21
+ */
22
+ setDirection(direction: CameraDirection): void;
23
+ /**
24
+ * @internal
25
+ */
26
+ setMediaStream(stream: MediaStream | undefined): void;
27
+ protected getDeviceIdFromStream(stream: MediaStream): string | undefined;
28
+ }
@@ -0,0 +1,47 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Call } from '../Call';
3
+ import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
4
+ export declare abstract class InputMediaDeviceManager<T extends InputMediaDeviceManagerState> {
5
+ protected readonly call: Call;
6
+ readonly state: T;
7
+ constructor(call: Call, state: T);
8
+ /**
9
+ * Lists the available audio/video devices
10
+ *
11
+ * Note: It prompts the user for a permission to use devices (if not already granted)
12
+ *
13
+ * @returns an Observable that will be updated if a device is connected or disconnected
14
+ */
15
+ listDevices(): Observable<MediaDeviceInfo[]>;
16
+ /**
17
+ * Starts camera/microphone
18
+ */
19
+ enable(): Promise<void>;
20
+ /**
21
+ * Stops camera/microphone
22
+ * @returns
23
+ */
24
+ disable(): Promise<void>;
25
+ /**
26
+ * If current device statis is disabled, it will enable the device, else it will disable it.
27
+ * @returns
28
+ */
29
+ toggle(): Promise<void>;
30
+ /**
31
+ * Select device
32
+ *
33
+ * Note: this method is not supported in React Native
34
+ *
35
+ * @param deviceId
36
+ */
37
+ select(deviceId: string | undefined): Promise<void>;
38
+ protected applySettingsToStream(): Promise<void>;
39
+ abstract pause(): void;
40
+ abstract resume(): void;
41
+ protected abstract getDevices(): Observable<MediaDeviceInfo[]>;
42
+ protected abstract getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
43
+ protected abstract publishStream(stream: MediaStream): Promise<void>;
44
+ protected abstract stopPublishStream(): Promise<void>;
45
+ private stopStream;
46
+ private startStream;
47
+ }
@@ -0,0 +1,69 @@
1
+ import { BehaviorSubject, Observable } from 'rxjs';
2
+ import { RxUtils } from '../store';
3
+ export type InputDeviceStatus = 'enabled' | 'disabled' | undefined;
4
+ export declare abstract class InputMediaDeviceManagerState {
5
+ protected statusSubject: BehaviorSubject<InputDeviceStatus>;
6
+ protected mediaStreamSubject: BehaviorSubject<MediaStream | undefined>;
7
+ protected selectedDeviceSubject: BehaviorSubject<string | undefined>;
8
+ /**
9
+ * An Observable that emits the current media stream, or `undefined` if the device is currently disabled.
10
+ *
11
+ */
12
+ mediaStream$: Observable<MediaStream | undefined>;
13
+ /**
14
+ * An Observable that emits the currently selected device
15
+ */
16
+ selectedDevice$: Observable<string | undefined>;
17
+ /**
18
+ * An Observable that emits the device status
19
+ */
20
+ status$: Observable<InputDeviceStatus>;
21
+ constructor();
22
+ /**
23
+ * The device status
24
+ */
25
+ get status(): InputDeviceStatus;
26
+ /**
27
+ * The currently selected device
28
+ */
29
+ get selectedDevice(): string | undefined;
30
+ /**
31
+ * The current media stream, or `undefined` if the device is currently disabled.
32
+ */
33
+ get mediaStream(): MediaStream | undefined;
34
+ /**
35
+ * Gets the current value of an observable, or undefined if the observable has
36
+ * not emitted a value yet.
37
+ *
38
+ * @param observable$ the observable to get the value from.
39
+ */
40
+ getCurrentValue: <T>(observable$: Observable<T>) => T;
41
+ /**
42
+ * @internal
43
+ * @param status
44
+ */
45
+ setStatus(status: InputDeviceStatus): void;
46
+ /**
47
+ * @internal
48
+ * @param stream
49
+ */
50
+ setMediaStream(stream: MediaStream | undefined): void;
51
+ /**
52
+ * @internal
53
+ * @param stream
54
+ */
55
+ setDevice(deviceId: string | undefined): void;
56
+ /**
57
+ * Updates the value of the provided Subject.
58
+ * An `update` can either be a new value or a function which takes
59
+ * the current value and returns a new value.
60
+ *
61
+ * @internal
62
+ *
63
+ * @param subject the subject to update.
64
+ * @param update the update to apply to the subject.
65
+ * @return the updated value.
66
+ */
67
+ protected setCurrentValue: <T>(subject: import("rxjs").Subject<T>, update: RxUtils.Patch<T>) => T;
68
+ protected abstract getDeviceIdFromStream(stream: MediaStream): string | undefined;
69
+ }
@@ -0,0 +1,19 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Call } from '../Call';
3
+ import { InputMediaDeviceManager } from './InputMediaDeviceManager';
4
+ import { MicrophoneManagerState } from './MicrophoneManagerState';
5
+ export declare class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManagerState> {
6
+ constructor(call: Call);
7
+ protected getDevices(): Observable<MediaDeviceInfo[]>;
8
+ protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
9
+ protected publishStream(stream: MediaStream): Promise<void>;
10
+ protected stopPublishStream(): Promise<void>;
11
+ /**
12
+ * Disables the audio tracks of the microphone
13
+ */
14
+ pause(): void;
15
+ /**
16
+ * (Re)enables the audio tracks of the microphone
17
+ */
18
+ resume(): void;
19
+ }
@@ -0,0 +1,4 @@
1
+ import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
2
+ export declare class MicrophoneManagerState extends InputMediaDeviceManagerState {
3
+ protected getDeviceIdFromStream(stream: MediaStream): string | undefined;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { CallingState } from '../../store';
2
+ export declare const mockVideoDevices: MediaDeviceInfo[];
3
+ export declare const mockAudioDevices: MediaDeviceInfo[];
4
+ export declare const mockCall: () => {
5
+ state: {
6
+ callingState: CallingState;
7
+ };
8
+ publishVideoStream: import("@vitest/spy").Mock<any, any>;
9
+ publishAudioStream: import("@vitest/spy").Mock<any, any>;
10
+ stopPublish: import("@vitest/spy").Mock<any, any>;
11
+ };
12
+ export declare const mockAudioStream: () => MediaStream;
13
+ export declare const mockVideoStream: () => MediaStream;
@@ -1 +1,7 @@
1
1
  export * from './devices';
2
+ export * from './InputMediaDeviceManager';
3
+ export * from './InputMediaDeviceManagerState';
4
+ export * from './CameraManager';
5
+ export * from './CameraManagerState';
6
+ export * from './MicrophoneManager';
7
+ export * from './MicrophoneManagerState';
@@ -65,10 +65,14 @@ export interface StreamVideoParticipant extends Participant {
65
65
  export interface StreamVideoLocalParticipant extends StreamVideoParticipant {
66
66
  /**
67
67
  * The device ID of the currently selected audio input device of the local participant (returned by the [MediaDevices API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia))
68
+ *
69
+ * @deprecated use call.microphone.state.selectedDevice
68
70
  */
69
71
  audioDeviceId?: string;
70
72
  /**
71
73
  * The device ID of the currently selected video input device of the local participant (returned by the [MediaDevices API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia))
74
+ *
75
+ * @deprecated use call.camera.state.selectedDevice
72
76
  */
73
77
  videoDeviceId?: string;
74
78
  /**
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "0.3.0";
1
+ export declare const version = "0.3.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
package/src/Call.ts CHANGED
@@ -9,7 +9,11 @@ import {
9
9
  Subscriber,
10
10
  } from './rtc';
11
11
  import { muteTypeToTrackType } from './rtc/helpers/tracks';
12
- import { GoAwayReason, TrackType } from './gen/video/sfu/models/models';
12
+ import {
13
+ GoAwayReason,
14
+ SdkType,
15
+ TrackType,
16
+ } from './gen/video/sfu/models/models';
13
17
  import {
14
18
  registerEventHandlers,
15
19
  registerRingingCallEventHandlers,
@@ -109,8 +113,11 @@ import {
109
113
  Logger,
110
114
  StreamCallEvent,
111
115
  } from './coordinator/connection/types';
112
- import { getClientDetails } from './client-details';
116
+ import { getClientDetails, getSdkInfo } from './client-details';
113
117
  import { getLogger } from './logger';
118
+ import { CameraManager } from './devices/CameraManager';
119
+ import { MicrophoneManager } from './devices/MicrophoneManager';
120
+ import { CameraDirection } from './devices/CameraManagerState';
114
121
 
115
122
  /**
116
123
  * An object representation of a `Call`.
@@ -147,6 +154,16 @@ export class Call {
147
154
  */
148
155
  watching: boolean;
149
156
 
157
+ /**
158
+ * Device manager for the camera
159
+ */
160
+ readonly camera: CameraManager;
161
+
162
+ /**
163
+ * Device manager for the microhpone
164
+ */
165
+ readonly microphone: MicrophoneManager;
166
+
150
167
  /**
151
168
  * Flag telling whether this call is a "ringing" call.
152
169
  */
@@ -175,7 +192,7 @@ export class Call {
175
192
  private dropTimeout: ReturnType<typeof setTimeout> | undefined;
176
193
 
177
194
  private readonly clientStore: StreamVideoWriteableStateStore;
178
- private readonly streamClient: StreamClient;
195
+ public readonly streamClient: StreamClient;
179
196
  private sfuClient?: StreamSfuClient;
180
197
  private reconnectAttempts = 0;
181
198
  private maxReconnectAttempts = 10;
@@ -250,6 +267,9 @@ export class Call {
250
267
  (subscriptions) => this.sfuClient?.updateSubscriptions(subscriptions),
251
268
  ),
252
269
  );
270
+
271
+ this.camera = new CameraManager(this);
272
+ this.microphone = new MicrophoneManager(this);
253
273
  }
254
274
 
255
275
  private registerEffects() {
@@ -904,6 +924,12 @@ export class Call {
904
924
  this.reconnectAttempts = 0; // reset the reconnect attempts counter
905
925
  this.state.setCallingState(CallingState.JOINED);
906
926
 
927
+ // React uses a different device management for now
928
+ if (getSdkInfo()?.type !== SdkType.REACT) {
929
+ this.initCamera();
930
+ this.initMic();
931
+ }
932
+
907
933
  // 3. once we have the "joinResponse", and possibly reconciled the local state
908
934
  // we schedule a fast subscription update for all remote participants
909
935
  // that were visible before we reconnected or migrated to a new SFU.
@@ -1190,6 +1216,8 @@ export class Call {
1190
1216
  *
1191
1217
  *
1192
1218
  * @param deviceId the selected device, pass `undefined` to clear the device selection
1219
+ *
1220
+ * @deprecated use call.microphone.select
1193
1221
  */
1194
1222
  setAudioDevice = (deviceId?: string) => {
1195
1223
  if (!this.sfuClient) return;
@@ -1204,6 +1232,8 @@ export class Call {
1204
1232
  * This method only stores the selection, if you want to start publishing a media stream call the [`publishVideoStream` method](#publishvideostream) that will set `videoDeviceId` as well.
1205
1233
  *
1206
1234
  * @param deviceId the selected device, pass `undefined` to clear the device selection
1235
+ *
1236
+ * @deprecated use call.camera.select
1207
1237
  */
1208
1238
  setVideoDevice = (deviceId?: string) => {
1209
1239
  if (!this.sfuClient) return;
@@ -1681,4 +1711,71 @@ export class Call {
1681
1711
  { custom: payload },
1682
1712
  );
1683
1713
  };
1714
+
1715
+ private initCamera() {
1716
+ if (
1717
+ this.state.localParticipant?.videoStream ||
1718
+ !this.permissionsContext.hasPermission('send-video')
1719
+ ) {
1720
+ return;
1721
+ }
1722
+
1723
+ // Set camera direction if it's not yet set
1724
+ // This will also start publishing if camera is enabled
1725
+ if (!this.camera.state.direction && !this.camera.state.selectedDevice) {
1726
+ let defaultDirection: CameraDirection = 'front';
1727
+ const backendSetting = this.state.settings?.video.camera_facing;
1728
+ if (backendSetting) {
1729
+ defaultDirection = backendSetting === 'front' ? 'front' : 'back';
1730
+ }
1731
+ this.camera.selectDirection(defaultDirection);
1732
+ } else if (this.camera.state.status === 'enabled') {
1733
+ // Publish already started media streams (this is the case if there is a lobby screen before join)
1734
+ // Wait for media stream
1735
+ this.camera.state.mediaStream$
1736
+ .pipe(takeWhile((s) => s === undefined, true))
1737
+ .subscribe((stream) => {
1738
+ if (!this.state.localParticipant?.videoStream) {
1739
+ this.publishVideoStream(stream!);
1740
+ }
1741
+ });
1742
+ }
1743
+
1744
+ // Apply backend config (this is the case if there is no lobby screen before join)
1745
+ if (
1746
+ this.camera.state.status === undefined &&
1747
+ this.state.settings?.video.camera_default_on
1748
+ ) {
1749
+ void this.camera.enable();
1750
+ }
1751
+ }
1752
+
1753
+ private initMic() {
1754
+ if (
1755
+ this.state.localParticipant?.audioStream ||
1756
+ !this.permissionsContext.hasPermission('send-audio')
1757
+ ) {
1758
+ return;
1759
+ }
1760
+
1761
+ // Publish already started media streams (this is the case if there is a lobby screen before join)
1762
+ if (this.microphone.state.status === 'enabled') {
1763
+ // Wait for media stream
1764
+ this.microphone.state.mediaStream$
1765
+ .pipe(takeWhile((s) => s === undefined, true))
1766
+ .subscribe((stream) => {
1767
+ if (!this.state.localParticipant?.audioStream) {
1768
+ this.publishAudioStream(stream!);
1769
+ }
1770
+ });
1771
+ }
1772
+
1773
+ // Apply backend config (this is the case if there is no lobby screen before join)
1774
+ if (
1775
+ this.microphone.state.status === undefined &&
1776
+ this.state.settings?.audio.mic_default_on
1777
+ ) {
1778
+ void this.microphone.enable();
1779
+ }
1780
+ }
1684
1781
  }
@@ -7,6 +7,9 @@ import { StreamVideoServerClient } from '../StreamVideoServerClient';
7
7
  const apiKey = process.env.STREAM_API_KEY!;
8
8
  const secret = process.env.STREAM_SECRET!;
9
9
 
10
+ vi.mock('../devices/CameraManager.ts');
11
+ vi.mock('../devices/MicrophoneManager.ts');
12
+
10
13
  const tokenProvider = (userId: string) => {
11
14
  const serverClient = new StreamVideoServerClient(apiKey, { secret });
12
15
  return async () => {
@@ -0,0 +1,73 @@
1
+ import { Observable } from 'rxjs';
2
+ import { Call } from '../Call';
3
+ import { CameraDirection, CameraManagerState } from './CameraManagerState';
4
+ import { InputMediaDeviceManager } from './InputMediaDeviceManager';
5
+ import { getVideoDevices, getVideoStream } from './devices';
6
+ import { TrackType } from '../gen/video/sfu/models/models';
7
+
8
+ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
9
+ constructor(call: Call) {
10
+ super(call, new CameraManagerState());
11
+ }
12
+
13
+ /**
14
+ * Select the camera direaction
15
+ * @param direction
16
+ */
17
+ async selectDirection(direction: Exclude<CameraDirection, undefined>) {
18
+ this.state.setDirection(direction);
19
+ // Providing both device id and direction doesn't work, so we deselect the device
20
+ this.state.setDevice(undefined);
21
+ await this.applySettingsToStream();
22
+ }
23
+
24
+ /**
25
+ * Flips the camera direction: if it's front it will change to back, if it's back, it will change to front.
26
+ *
27
+ * Note: if there is no available camera with the desired direction, this method will do nothing.
28
+ * @returns
29
+ */
30
+ async flip() {
31
+ const newDirection = this.state.direction === 'front' ? 'back' : 'front';
32
+ this.selectDirection(newDirection);
33
+ }
34
+
35
+ protected getDevices(): Observable<MediaDeviceInfo[]> {
36
+ return getVideoDevices();
37
+ }
38
+ protected getStream(
39
+ constraints: MediaTrackConstraints,
40
+ ): Promise<MediaStream> {
41
+ // We can't set both device id and facing mode
42
+ // Device id has higher priority
43
+ if (!constraints.deviceId && this.state.direction) {
44
+ constraints.facingMode =
45
+ this.state.direction === 'front' ? 'user' : 'environment';
46
+ }
47
+ return getVideoStream(constraints);
48
+ }
49
+ protected publishStream(stream: MediaStream): Promise<void> {
50
+ return this.call.publishVideoStream(stream);
51
+ }
52
+ protected stopPublishStream(): Promise<void> {
53
+ return this.call.stopPublish(TrackType.VIDEO);
54
+ }
55
+
56
+ /**
57
+ * Disables the video tracks of the camera
58
+ */
59
+ pause() {
60
+ this.state.mediaStream?.getVideoTracks().forEach((track) => {
61
+ track.enabled = false;
62
+ });
63
+ }
64
+
65
+ /**
66
+ * (Re)enables the video tracks of the camera
67
+ */
68
+ resume() {
69
+ this.state.mediaStream?.getVideoTracks().forEach((track) => {
70
+ track.enabled = true;
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,61 @@
1
+ import { BehaviorSubject, Observable, distinctUntilChanged } from 'rxjs';
2
+ import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
3
+ import { isReactNative } from '../helpers/platforms';
4
+
5
+ export type CameraDirection = 'front' | 'back' | undefined;
6
+
7
+ export class CameraManagerState extends InputMediaDeviceManagerState {
8
+ private directionSubject = new BehaviorSubject<CameraDirection>(undefined);
9
+
10
+ /**
11
+ * Observable that emits the preferred camera direction
12
+ * front - means the camera facing the user
13
+ * back - means the camera facing the environment
14
+ */
15
+ direction$: Observable<CameraDirection>;
16
+
17
+ constructor() {
18
+ super();
19
+ this.direction$ = this.directionSubject
20
+ .asObservable()
21
+ .pipe(distinctUntilChanged());
22
+ }
23
+
24
+ /**
25
+ * The preferred camera direction
26
+ * front - means the camera facing the user
27
+ * back - means the camera facing the environment
28
+ */
29
+ get direction() {
30
+ return this.getCurrentValue(this.direction$);
31
+ }
32
+
33
+ /**
34
+ * @internal
35
+ */
36
+ setDirection(direction: CameraDirection) {
37
+ this.setCurrentValue(this.directionSubject, direction);
38
+ }
39
+
40
+ /**
41
+ * @internal
42
+ */
43
+ setMediaStream(stream: MediaStream | undefined): void {
44
+ super.setMediaStream(stream);
45
+ if (stream) {
46
+ // RN getSettings() doesn't return facingMode, so we don't verify camera direction
47
+ const direction = isReactNative()
48
+ ? this.direction
49
+ : stream.getVideoTracks()[0]?.getSettings().facingMode === 'environment'
50
+ ? 'back'
51
+ : 'front';
52
+ this.setDirection(direction);
53
+ }
54
+ }
55
+
56
+ protected getDeviceIdFromStream(stream: MediaStream): string | undefined {
57
+ return stream.getVideoTracks()[0]?.getSettings().deviceId as
58
+ | string
59
+ | undefined;
60
+ }
61
+ }