@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.
- package/CHANGELOG.md +19 -0
- package/dist/index.browser.es.js +863 -455
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +867 -453
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +863 -455
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +18 -1
- package/dist/src/devices/CameraManager.d.ts +31 -0
- package/dist/src/devices/CameraManagerState.d.ts +28 -0
- package/dist/src/devices/InputMediaDeviceManager.d.ts +47 -0
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +69 -0
- package/dist/src/devices/MicrophoneManager.d.ts +19 -0
- package/dist/src/devices/MicrophoneManagerState.d.ts +4 -0
- package/dist/src/devices/__tests__/CameraManager.test.d.ts +1 -0
- package/dist/src/devices/__tests__/InputMediaDeviceManager.test.d.ts +1 -0
- package/dist/src/devices/__tests__/MicrophoneManager.test.d.ts +1 -0
- package/dist/src/devices/__tests__/mocks.d.ts +13 -0
- package/dist/src/devices/index.d.ts +6 -0
- package/dist/src/types.d.ts +4 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Call.ts +100 -3
- package/src/__tests__/StreamVideoClient.test.ts +3 -0
- package/src/devices/CameraManager.ts +73 -0
- package/src/devices/CameraManagerState.ts +61 -0
- package/src/devices/InputMediaDeviceManager.ts +121 -0
- package/src/devices/InputMediaDeviceManagerState.ts +111 -0
- package/src/devices/MicrophoneManager.ts +45 -0
- package/src/devices/MicrophoneManagerState.ts +9 -0
- package/src/devices/__tests__/CameraManager.test.ts +150 -0
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +159 -0
- package/src/devices/__tests__/MicrophoneManager.test.ts +103 -0
- package/src/devices/__tests__/mocks.ts +98 -0
- package/src/devices/index.ts +6 -0
- package/src/rtc/Publisher.ts +11 -19
- package/src/rtc/videoLayers.ts +7 -2
- package/src/types.ts +4 -0
package/dist/src/Call.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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';
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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.
|
|
1
|
+
export declare const version = "0.3.2";
|
package/package.json
CHANGED
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 {
|
|
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
|
-
|
|
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
|
+
}
|