@pexip/media 17.2.0 → 17.4.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/dist/types.js ADDED
@@ -0,0 +1,278 @@
1
+ export var UserMediaStatus;
2
+ (function (UserMediaStatus) {
3
+ /**
4
+ * The initial status
5
+ */
6
+ UserMediaStatus["Initial"] = "initial";
7
+ /**
8
+ * When we know the permissions were already granted from permission API
9
+ */
10
+ UserMediaStatus["InitialPermissionsGranted"] = "initial-permissions-granted";
11
+ /**
12
+ * When we do not know the permissions from permission API
13
+ */
14
+ UserMediaStatus["InitialPermissionsNotGranted"] = "initial-permissions-not-granted";
15
+ /**
16
+ * When video input permissions are initially denied in the browser and audio input permissions are unknown
17
+ */
18
+ UserMediaStatus["InitialPermissionsVideoInputDenied"] = "initial-permissions-videoinput-denied";
19
+ /**
20
+ * When audio input permissions are initially denied in the browser and video input permissions are unknown
21
+ */
22
+ UserMediaStatus["InitialPermissionsAudioInputDenied"] = "initial-permissions-audioinput-denied";
23
+ /**
24
+ * When video input permissions are granted in the browser and audio input permissions are unknown
25
+ */
26
+ UserMediaStatus["InitialPermissionsVideoInputGranted"] = "initial-permissions-videoinput-granted";
27
+ /**
28
+ * When audio input permissions are granted in the browser and video input permissions are unknown
29
+ */
30
+ UserMediaStatus["InitialPermissionsAudioInputGranted"] = "initial-permissions-audioinput-granted";
31
+ /**
32
+ * When audio input permissions are granted but video input permissions are initially denied
33
+ */
34
+ UserMediaStatus["InitialPermissionsGrantedVideoInputDenied"] = "initial-permissions-audioinput-granted-videoinput-denied";
35
+ /**
36
+ * When video input permissions are granted but audio input permissions are initially denied
37
+ */
38
+ UserMediaStatus["InitialPermissionsGrantedAudioInputDenied"] = "initial-permissions-videoinput-granted-audioinput-denied";
39
+ /**
40
+ * When there is no any kind of input devices
41
+ */
42
+ UserMediaStatus["NoDevicesFound"] = "no-devices-found";
43
+ /**
44
+ * When there is no any video input devices
45
+ */
46
+ UserMediaStatus["NoVideoDevicesFound"] = "no-video-devices-found";
47
+ /**
48
+ * When there is no any audio input devices
49
+ */
50
+ UserMediaStatus["NoAudioDevicesFound"] = "no-audio-devices-found";
51
+ /**
52
+ * Derived from `MediaDeviceFailure.AudioInputDeviceNotFoundError`
53
+ */
54
+ UserMediaStatus["AudioDeviceNotFound"] = "audio-device-not-found";
55
+ /**
56
+ * Derived from `MediaDeviceFailure.VideoInputDeviceNotFoundError`
57
+ */
58
+ UserMediaStatus["VideoDeviceNotFound"] = "video-device-not-found";
59
+ /**
60
+ * Derived from `MediaDeviceFailure.AudioAndVideoDeviceNotFoundError`
61
+ */
62
+ UserMediaStatus["AudioVideoDevicesNotFound"] = "audio-video-devices-not-found";
63
+ /**
64
+ * When Permission is granted by user for both video and audio, and both
65
+ * devices are exactly matched with the request constraints
66
+ */
67
+ UserMediaStatus["PermissionsGranted"] = "permissions-granted";
68
+ /**
69
+ * When Permission is granted by user for both video and audio, and both
70
+ * devices are NOT exactly matched with the request constraints
71
+ */
72
+ UserMediaStatus["PermissionsGrantedFallback"] = "permissions-granted-fallback-devices";
73
+ /**
74
+ * When Permission is granted by user for both video and audio, and audio
75
+ * input is NOT exactly matched with the request constraints
76
+ */
77
+ UserMediaStatus["PermissionsGrantedFallbackAudioinput"] = "permissions-granted-fallback-audioinput";
78
+ /**
79
+ * When Permission is granted by user for both video and audio, and video
80
+ * input is NOT exactly matched with the request constraints
81
+ */
82
+ UserMediaStatus["PermissionsGrantedFallbackVideoinput"] = "permissions-granted-fallback-videoinput";
83
+ /**
84
+ * When Permission for using both audio and video devices are rejected by the user
85
+ * from `PermissionDeniedError`
86
+ */
87
+ UserMediaStatus["PermissionsRejected"] = "permissions-rejected";
88
+ /**
89
+ * When Permission for using the audio device is rejected by the user
90
+ * from `PermissionDeniedError`
91
+ */
92
+ UserMediaStatus["PermissionsRejectedAudioInput"] = "permissions-rejected-audioinput";
93
+ /**
94
+ * When Permission for using the video device is rejected by the user
95
+ * from `PermissionDeniedError`
96
+ */
97
+ UserMediaStatus["PermissionsRejectedVideoInput"] = "permissions-rejected-videoinput";
98
+ /**
99
+ * When only request and return exact audio input device
100
+ */
101
+ UserMediaStatus["PermissionsOnlyAudioinput"] = "permissions-only-audioinput";
102
+ /**
103
+ * When only request audio input device because of no video devices
104
+ * available and returned exact audio input device
105
+ */
106
+ UserMediaStatus["PermissionsOnlyAudioinputNoVideoDevices"] = "permissions-only-audioinput-no-video-devices";
107
+ /**
108
+ * When only request and returned NOT exact audio input device
109
+ */
110
+ UserMediaStatus["PermissionsOnlyAudioinputFallback"] = "permissions-only-fallback-audioinput";
111
+ /**
112
+ * When only request audio input device because of no video devices
113
+ * available and returned NOT exact audio input device
114
+ */
115
+ UserMediaStatus["PermissionsOnlyAudioinputFallbackNoVideoDevices"] = "permissions-only-fallback-audioinput-no-video-devices";
116
+ /**
117
+ * When only request and return exact video input device
118
+ */
119
+ UserMediaStatus["PermissionsOnlyVideoinput"] = "permissions-only-videoinput";
120
+ /**
121
+ * When only request video input device because of no audio devices
122
+ * available and returned exact video input device
123
+ */
124
+ UserMediaStatus["PermissionsOnlyVideoinputNoAudioDevices"] = "permissions-only-videoinput-no-audio-devices";
125
+ /**
126
+ * When only request and returned NOT exact video input device
127
+ */
128
+ UserMediaStatus["PermissionsOnlyVideoinputFallback"] = "permissions-only-fallback-videoinput";
129
+ /**
130
+ * When only request video input device because of no video devices
131
+ * available and returned NOT exact video input device
132
+ */
133
+ UserMediaStatus["PermissionsOnlyVideoinputFallbackNoAudioDevices"] = "permissions-only-fallback-videoinput-no-audio-devices";
134
+ /**
135
+ * When requesting the audio device is used by other application
136
+ */
137
+ UserMediaStatus["AudioDeviceInUse"] = "audio-device-in-use";
138
+ /**
139
+ * When requesting the video device is used by other application
140
+ */
141
+ UserMediaStatus["VideoDeviceInUse"] = "video-device-in-use";
142
+ /**
143
+ * When requesting both audio and video devices are used by other application
144
+ */
145
+ UserMediaStatus["DevicesInUse"] = "devices-in-use";
146
+ /**
147
+ * When requesting both audio and video with over-constrained
148
+ */
149
+ UserMediaStatus["Overconstrained"] = "overconstrained";
150
+ /**
151
+ * When requesting video with over-constrained
152
+ */
153
+ UserMediaStatus["VideoOverconstrained"] = "video-overconstrained";
154
+ /**
155
+ * When requesting audio with over-constrained
156
+ */
157
+ UserMediaStatus["AudioOverconstrained"] = "audio-overconstrained";
158
+ /**
159
+ * When requesting both audio and video with invalid constraints
160
+ */
161
+ UserMediaStatus["InvalidConstraints"] = "invalid-constraints";
162
+ /**
163
+ * When requesting video with invalid constraints
164
+ */
165
+ UserMediaStatus["InvalidVideoConstraints"] = "invalid-video-constraints";
166
+ /**
167
+ * When requesting audio with invalid constraints
168
+ */
169
+ UserMediaStatus["InvalidAudioConstraints"] = "invalid-audio-constraints";
170
+ /**
171
+ * When requesting both audio and video with NotSupportedError
172
+ */
173
+ UserMediaStatus["NotSupportedError"] = "not-supported-error";
174
+ /**
175
+ * When requesting video with NotSupportedError
176
+ */
177
+ UserMediaStatus["NotSupportedErrorOnlyVideoInput"] = "not-supported-error-only-video";
178
+ /**
179
+ * When requesting audio with NotSupportedError
180
+ */
181
+ UserMediaStatus["NotSupportedErrorOnlyAudioInput"] = "not-supported-error-only-audio";
182
+ /**
183
+ * Unknown error from both video and audio
184
+ */
185
+ UserMediaStatus["UnknownError"] = "unknown-error";
186
+ /**
187
+ * Unknown error from audio device
188
+ */
189
+ UserMediaStatus["UnknownErrorOnlyAudioinput"] = "unknown-error-only-audioinput";
190
+ /**
191
+ * Unknown error from video device
192
+ */
193
+ UserMediaStatus["UnknownErrorOnlyVideoinput"] = "unknown-error-only-videoinput";
194
+ })(UserMediaStatus || (UserMediaStatus = {}));
195
+ export var DeniedDevices;
196
+ (function (DeniedDevices) {
197
+ DeniedDevices["Microphone"] = "microphone";
198
+ DeniedDevices["Camera"] = "camera";
199
+ DeniedDevices["Both"] = "microphone-and-camera";
200
+ })(DeniedDevices || (DeniedDevices = {}));
201
+ /**
202
+ * Audio content hints are only applicable when the MediaStreamTrack contains an
203
+ * audio track
204
+ *
205
+ * {@link https://www.w3.org/TR/mst-content-hint/#audio-content-hints}
206
+ */
207
+ export const AUDIO_CONTENT_HINTS = {
208
+ /**
209
+ * No hint has been provided, the implementation should make its
210
+ * best-informed guess on how to handle contained audio data. This may be
211
+ * inferred from how the track was opened or by doing content analysis
212
+ */
213
+ NoHint: '',
214
+ /**
215
+ * The track should be treated as if it contains speech data. Consuming this
216
+ * signal it may be appropriate to apply noise suppression or boost
217
+ * intelligibility of the incoming signal.
218
+ */
219
+ Speech: 'speech',
220
+ /**
221
+ * The track should be treated as if it contains data for the purpose of
222
+ * speech recognition by a machine. Consuming this signal it may be
223
+ * appropriate to boost intelligibility of the incoming signal for
224
+ * transcription and turn off audio-processing components that are used for
225
+ * human consumption.
226
+ */
227
+ SpeechRecognition: 'speech-recognition',
228
+ /**
229
+ * The track should be treated as if it contains music data. Generally this
230
+ * might imply tuning or turning off audio-processing components that are
231
+ * used to process speech data to prevent the audio from being distorted.
232
+ */
233
+ Music: 'music',
234
+ };
235
+ /**
236
+ * Video content hints are only applicable when the MediaStreamTrack contains a
237
+ * video track.
238
+ *
239
+ * {@link https://www.w3.org/TR/mst-content-hint/#video-content-hints}
240
+ */
241
+ export const VIDEO_CONTENT_HINTS = {
242
+ /**
243
+ * No hint has been provided, the implementation should make its
244
+ * best-informed guess on how contained video content should be treated.
245
+ * This can for example be inferred from how the track was opened or by
246
+ * doing content analysis.
247
+ */
248
+ NoHint: '',
249
+ /**
250
+ * The track should be treated as if it contains video where motion is
251
+ * important. This is normally webcam video, movies or video games.
252
+ * Quantization artefacts and downscaling are acceptable in order to
253
+ * preserve motion as well as possible while still retaining target
254
+ * bitrates. During low bitrates when compromises have to be made, more
255
+ * effort is spent on preserving frame rate than edge quality and details.
256
+ */
257
+ Motion: 'motion',
258
+ /**
259
+ * The track should be treated as if video details are extra important.
260
+ * This is generally applicable to presentations or web pages with text
261
+ * content, painting or line art. This setting would normally optimize for
262
+ * detail in the resulting individual frames rather than smooth playback.
263
+ * Artefacts from quantization or downscaling that make small text or line
264
+ * art unintelligible should be avoided.
265
+ */
266
+ Detail: 'detail',
267
+ /**
268
+ * The track should be treated as if video details are extra important, and
269
+ * that significant sharp edges and areas of consistent color can occur
270
+ * frequently. This is generally applicable to presentations or web pages
271
+ * with text content. This setting would normally optimize for detail in the
272
+ * resulting individual frames rather than smooth playback, and may take
273
+ * advantage of encoder tools that optimize for text rendering. Artefacts
274
+ * from quantization or downscaling that make small text or line art
275
+ * unintelligible should be avoided.
276
+ */
277
+ Text: 'text',
278
+ };
@@ -0,0 +1,38 @@
1
+ import type { MediaDeviceRequest, MediaDeviceInfoLike } from '@pexip/media-control';
2
+ import type { Process, Media } from './types';
3
+ import { UserMediaStatus } from './types';
4
+ import { makeDeriveDeviceStatus } from './utils';
5
+ export type UserMedia = [MediaStream | undefined, UserMediaStatus];
6
+ export type GetUserMedia<T extends Promise<Iterable<unknown>> = Promise<UserMedia>> = (constraints: MediaDeviceRequest) => T;
7
+ export declare const toErrorDeviceStatus: (error: Error, deriveDeviceStatus: ReturnType<typeof makeDeriveDeviceStatus>) => UserMediaStatus;
8
+ export declare const mapErrorStatus: (status: UserMediaStatus) => (constraints: MediaDeviceRequest) => UserMediaStatus;
9
+ export declare const toSameDeviceStatus: ({ audio, video, }: {
10
+ audio: boolean;
11
+ video: boolean;
12
+ }) => UserMediaStatus.PermissionsGranted | UserMediaStatus.PermissionsGrantedFallback | UserMediaStatus.PermissionsGrantedFallbackAudioinput | UserMediaStatus.PermissionsGrantedFallbackVideoinput;
13
+ export declare const toOnlyDeviceStatus: (kind: Extract<MediaDeviceKind, 'audioinput' | 'videoinput'>, matched: boolean, devices: MediaDeviceInfoLike[]) => UserMediaStatus.PermissionsOnlyAudioinput | UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices | UserMediaStatus.PermissionsOnlyAudioinputFallback | UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices | UserMediaStatus.PermissionsOnlyVideoinput | UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices | UserMediaStatus.PermissionsOnlyVideoinputFallback | UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices;
14
+ /**
15
+ * Try to come up with an error level according to provided UserMediaStatus and
16
+ * MediaDeviceRequest
17
+ *
18
+ * @param error - The error thrown from media request
19
+ * @param status - The status in result
20
+ * @param constraints - The constraints used for the request
21
+ */
22
+ export declare const deriveErrorLevel: (error: Error, status: UserMediaStatus, constraints: MediaDeviceRequest) => "error" | "warn";
23
+ export declare const requestUserMedia: (currentDevices?: MediaDeviceInfoLike[], getMedia?: ({ audio, video, }: MediaDeviceRequest) => Promise<MediaStream>) => GetUserMedia;
24
+ export declare const mergeNoDeviceStatus: (constraints: MediaDeviceRequest, anyDevices: {
25
+ audio: boolean;
26
+ video: boolean;
27
+ }, status: UserMediaStatus) => UserMediaStatus;
28
+ export declare const requestUserMediaWithRetry: (currentDevices?: MediaDeviceInfoLike[], createRequestUserMedia?: (currentDevices?: MediaDeviceInfoLike[], getMedia?: ({ audio, video, }: MediaDeviceRequest) => Promise<MediaStream>) => GetUserMedia) => GetUserMedia;
29
+ interface Options {
30
+ initialMedia: Media;
31
+ scope?: string;
32
+ }
33
+ type CreateGetUserMediaProcess = (getUserMedia: GetUserMedia, getCurrentDevices: () => MediaDeviceInfoLike[], options: Options) => Process<MediaDeviceRequest>;
34
+ /**
35
+ * A process to get user media
36
+ */
37
+ export declare const createGetUserMediaProcess: CreateGetUserMediaProcess;
38
+ export {};
@@ -0,0 +1,333 @@
1
+ import { MediaDeviceFailure, applyConstraints, findMediaInputFromMediaStreamTrack, getDevices, getUserMedia, isExactDeviceConstraint, isRequestedResolution, isStreamingRequestedDevices, muteStreamTrack, stopMediaStream, isAudioInput, isVideoInput, hasAnyInputs, hasAnyGrantedInput, } from '@pexip/media-control';
2
+ import { isEmpty } from '@pexip/utils';
3
+ import { UserMediaStatus } from './types';
4
+ import { shallowCopy, makeDeriveDeviceStatus, createMemorizedGetExpectedInput, isMuted, wrapToJSON, } from './utils';
5
+ import { logger } from './logger';
6
+ import { isUnknownError } from './status';
7
+ export const toErrorDeviceStatus = (error, deriveDeviceStatus) => {
8
+ switch (error.message) {
9
+ case MediaDeviceFailure.AudioInputDeviceNotFoundError:
10
+ return UserMediaStatus.AudioDeviceNotFound;
11
+ case MediaDeviceFailure.VideoInputDeviceNotFoundError:
12
+ return UserMediaStatus.VideoDeviceNotFound;
13
+ case MediaDeviceFailure.AudioAndVideoDeviceNotFoundError:
14
+ return UserMediaStatus.AudioVideoDevicesNotFound;
15
+ case MediaDeviceFailure.NotAllowedError:
16
+ case MediaDeviceFailure.NotFoundError:
17
+ case MediaDeviceFailure.SecurityError:
18
+ case MediaDeviceFailure.PermissionDeniedError:
19
+ return deriveDeviceStatus(UserMediaStatus.PermissionsRejectedAudioInput, UserMediaStatus.PermissionsRejectedVideoInput, UserMediaStatus.PermissionsRejected);
20
+ case MediaDeviceFailure.NotReadableError:
21
+ case MediaDeviceFailure.TrackStartError:
22
+ case MediaDeviceFailure.AbortError:
23
+ return deriveDeviceStatus(UserMediaStatus.AudioDeviceInUse, UserMediaStatus.VideoDeviceInUse, UserMediaStatus.DevicesInUse);
24
+ case MediaDeviceFailure.MissingConstraintsError:
25
+ return deriveDeviceStatus(UserMediaStatus.NoAudioDevicesFound, UserMediaStatus.NoVideoDevicesFound, UserMediaStatus.NoDevicesFound);
26
+ case MediaDeviceFailure.OverconstrainedError:
27
+ return deriveDeviceStatus(UserMediaStatus.AudioOverconstrained, UserMediaStatus.VideoOverconstrained, UserMediaStatus.Overconstrained);
28
+ case MediaDeviceFailure.TypeError:
29
+ return deriveDeviceStatus(UserMediaStatus.InvalidAudioConstraints, UserMediaStatus.InvalidVideoConstraints, UserMediaStatus.InvalidConstraints);
30
+ case MediaDeviceFailure.NotSupportedError:
31
+ return deriveDeviceStatus(UserMediaStatus.NotSupportedErrorOnlyAudioInput, UserMediaStatus.NotSupportedErrorOnlyVideoInput, UserMediaStatus.NotSupportedError);
32
+ default:
33
+ return deriveDeviceStatus(UserMediaStatus.UnknownErrorOnlyAudioinput, UserMediaStatus.UnknownErrorOnlyVideoinput, UserMediaStatus.UnknownError);
34
+ }
35
+ };
36
+ export const mapErrorStatus = (status) => (constraints) => {
37
+ const toStatus = makeDeriveDeviceStatus(constraints);
38
+ switch (status) {
39
+ case UserMediaStatus.AudioVideoDevicesNotFound:
40
+ return toStatus(UserMediaStatus.AudioDeviceNotFound, UserMediaStatus.VideoDeviceNotFound, status);
41
+ case UserMediaStatus.DevicesInUse:
42
+ return toStatus(UserMediaStatus.AudioDeviceInUse, UserMediaStatus.VideoDeviceInUse, status);
43
+ case UserMediaStatus.InvalidConstraints:
44
+ return toStatus(UserMediaStatus.InvalidAudioConstraints, UserMediaStatus.InvalidVideoConstraints, status);
45
+ case UserMediaStatus.NoDevicesFound:
46
+ return toStatus(UserMediaStatus.NoAudioDevicesFound, UserMediaStatus.NoVideoDevicesFound, status);
47
+ case UserMediaStatus.NotSupportedError:
48
+ return toStatus(UserMediaStatus.NotSupportedErrorOnlyAudioInput, UserMediaStatus.NotSupportedErrorOnlyVideoInput, status);
49
+ case UserMediaStatus.Overconstrained:
50
+ return toStatus(UserMediaStatus.AudioOverconstrained, UserMediaStatus.VideoOverconstrained, status);
51
+ case UserMediaStatus.PermissionsRejected:
52
+ return toStatus(UserMediaStatus.PermissionsRejectedAudioInput, UserMediaStatus.PermissionsRejectedVideoInput, status);
53
+ case UserMediaStatus.UnknownError:
54
+ return toStatus(UserMediaStatus.UnknownErrorOnlyAudioinput, UserMediaStatus.UnknownErrorOnlyVideoinput, status);
55
+ default:
56
+ return status;
57
+ }
58
+ };
59
+ export const toSameDeviceStatus = ({ audio, video, }) => {
60
+ if (audio) {
61
+ if (video) {
62
+ return UserMediaStatus.PermissionsGranted;
63
+ }
64
+ return UserMediaStatus.PermissionsGrantedFallbackVideoinput;
65
+ }
66
+ if (video) {
67
+ return UserMediaStatus.PermissionsGrantedFallbackAudioinput;
68
+ }
69
+ return UserMediaStatus.PermissionsGrantedFallback;
70
+ };
71
+ export const toOnlyDeviceStatus = (kind, matched, devices) => {
72
+ const hasRelatedDevices = devices.some(d => {
73
+ if (kind === 'audioinput') {
74
+ return d.kind === 'videoinput';
75
+ }
76
+ return d.kind === 'audioinput';
77
+ });
78
+ if (matched) {
79
+ if (hasRelatedDevices) {
80
+ return kind === 'audioinput'
81
+ ? UserMediaStatus.PermissionsOnlyAudioinput
82
+ : UserMediaStatus.PermissionsOnlyVideoinput;
83
+ }
84
+ return kind === 'audioinput'
85
+ ? UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices
86
+ : UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices;
87
+ }
88
+ if (hasRelatedDevices) {
89
+ return kind === 'audioinput'
90
+ ? UserMediaStatus.PermissionsOnlyAudioinputFallback
91
+ : UserMediaStatus.PermissionsOnlyVideoinputFallback;
92
+ }
93
+ return kind === 'audioinput'
94
+ ? UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices
95
+ : UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices;
96
+ };
97
+ /**
98
+ * Try to come up with an error level according to provided UserMediaStatus and
99
+ * MediaDeviceRequest
100
+ *
101
+ * @param error - The error thrown from media request
102
+ * @param status - The status in result
103
+ * @param constraints - The constraints used for the request
104
+ */
105
+ export const deriveErrorLevel = (error, status, constraints) => {
106
+ // Only log to error level when we should pay attention, e.g.
107
+ // UnknownError
108
+ const errorMsg = error.message;
109
+ return [
110
+ (errorMessage) => MediaDeviceFailure.TypeError === errorMessage,
111
+ (errorMessage) => MediaDeviceFailure.NotSupportedError === errorMessage,
112
+ (errorMessage) => MediaDeviceFailure.OverconstrainedError ===
113
+ errorMessage &&
114
+ ![constraints.audio, constraints.video].some(constraint => isExactDeviceConstraint(constraint)),
115
+ () => isUnknownError(status),
116
+ ].some(shouldUseError => shouldUseError(errorMsg))
117
+ ? 'error'
118
+ : 'warn';
119
+ };
120
+ export const requestUserMedia = (currentDevices, getMedia = getUserMedia) => async (constraints) => {
121
+ const deriveDeviceStatus = makeDeriveDeviceStatus(constraints);
122
+ const devices = currentDevices ?? (await getDevices());
123
+ try {
124
+ const stream = await getMedia(constraints);
125
+ const grantedDevices = hasAnyGrantedInput(devices)
126
+ ? devices
127
+ : await getDevices();
128
+ const { audio, video } = isStreamingRequestedDevices(constraints, stream, grantedDevices);
129
+ const onlyAudioStatus = toOnlyDeviceStatus('audioinput', audio, grantedDevices);
130
+ const onlyVideoStatus = toOnlyDeviceStatus('videoinput', video, grantedDevices);
131
+ const status = deriveDeviceStatus(onlyAudioStatus, onlyVideoStatus, toSameDeviceStatus({ audio, video }));
132
+ return [stream, status];
133
+ }
134
+ catch (error) {
135
+ if (error instanceof Error) {
136
+ const status = toErrorDeviceStatus(error, deriveDeviceStatus);
137
+ return [undefined, status];
138
+ }
139
+ throw error;
140
+ }
141
+ };
142
+ export const mergeNoDeviceStatus = (constraints, anyDevices, status) => {
143
+ if (status !== UserMediaStatus.NoDevicesFound ||
144
+ (!anyDevices.audio &&
145
+ !anyDevices.video &&
146
+ constraints.audio &&
147
+ constraints.video)) {
148
+ return status;
149
+ }
150
+ if (!anyDevices.audio && constraints.audio) {
151
+ return UserMediaStatus.NoAudioDevicesFound;
152
+ }
153
+ if (!anyDevices.video && constraints.video) {
154
+ return UserMediaStatus.NoVideoDevicesFound;
155
+ }
156
+ return status;
157
+ };
158
+ export const requestUserMediaWithRetry = (currentDevices, createRequestUserMedia = requestUserMedia) => async (constraints) => {
159
+ const devices = isEmpty(currentDevices)
160
+ ? await getDevices()
161
+ : currentDevices;
162
+ const [anyAudioDevices, anyVideoDevices] = hasAnyInputs(devices);
163
+ const request = (constraints, devices) => createRequestUserMedia(devices)(constraints);
164
+ if (anyAudioDevices && anyVideoDevices) {
165
+ const [stream, status] = await request(constraints, devices);
166
+ if (stream) {
167
+ return [stream, status];
168
+ }
169
+ const mapStatus = mapErrorStatus(status);
170
+ if (constraints.video) {
171
+ const [audioStream] = await request({
172
+ ...constraints,
173
+ video: false,
174
+ }, devices);
175
+ if (audioStream) {
176
+ // When success, we know it is video related error
177
+ const videoStatus = mapStatus({ video: true });
178
+ return [audioStream, videoStatus];
179
+ }
180
+ }
181
+ if (constraints.audio) {
182
+ const [videoStream] = await request({
183
+ ...constraints,
184
+ audio: false,
185
+ }, devices);
186
+ if (videoStream) {
187
+ // When success, we know it is audio related error
188
+ return [videoStream, mapStatus({ audio: true })];
189
+ }
190
+ }
191
+ return [stream, status];
192
+ }
193
+ else {
194
+ const [stream, status] = await request({
195
+ audio: anyAudioDevices && constraints.audio,
196
+ video: anyVideoDevices && constraints.video,
197
+ }, devices);
198
+ return [
199
+ stream,
200
+ mergeNoDeviceStatus(constraints, { audio: anyAudioDevices, video: anyVideoDevices }, status),
201
+ ];
202
+ }
203
+ };
204
+ /**
205
+ * A process to get user media
206
+ */
207
+ export const createGetUserMediaProcess = (getUserMedia, getCurrentDevices, { initialMedia, scope = 'media' }) => {
208
+ const props = {
209
+ unsubscribe: undefined,
210
+ currentDevices: getCurrentDevices(),
211
+ cachedAudioTrackId: '',
212
+ cachedVideoTrackId: '',
213
+ cachedAudioInput: undefined,
214
+ cachedVideoInput: undefined,
215
+ };
216
+ const getExpectedAudioInput = createMemorizedGetExpectedInput();
217
+ const getExpectedVideoInput = createMemorizedGetExpectedInput();
218
+ /**
219
+ * Memorized get media input
220
+ */
221
+ const getInput = ([track] = []) => {
222
+ if (!track) {
223
+ return undefined;
224
+ }
225
+ const isAudio = track.kind === 'audio';
226
+ const cacheKey = isAudio ? 'cachedAudioInput' : 'cachedVideoInput';
227
+ const cachedTrackId = isAudio
228
+ ? 'cachedAudioTrackId'
229
+ : 'cachedVideoTrackId';
230
+ const trackId = track.label;
231
+ const { width, height } = track.getSettings();
232
+ if (trackId === props[cachedTrackId] &&
233
+ track.readyState === 'live' &&
234
+ (isAudio ||
235
+ (!isAudio &&
236
+ isRequestedResolution({ width, height }, props[cacheKey])))) {
237
+ return props[cacheKey];
238
+ }
239
+ const devices = getCurrentDevices();
240
+ const findTrack = findMediaInputFromMediaStreamTrack(devices);
241
+ const input = findTrack(track);
242
+ // Update cache
243
+ props[cacheKey] = input;
244
+ props[cachedTrackId] = trackId;
245
+ return input;
246
+ };
247
+ return async (constraints) => {
248
+ const [stream, status] = await getUserMedia(constraints);
249
+ logger.debug({ scope, stream, status }, 'getUserMedia');
250
+ props.currentDevices = await getDevices();
251
+ const deriveDeviceStatus = makeDeriveDeviceStatus(constraints);
252
+ const muteTrack = muteStreamTrack(stream);
253
+ const muteAudio = (mute) => muteTrack(mute, 'audio');
254
+ const muteVideo = (mute) => muteTrack(mute, 'video');
255
+ return wrapToJSON(shallowCopy(initialMedia ?? {}, {
256
+ constraints,
257
+ rawStream: stream,
258
+ stream,
259
+ status,
260
+ get devices() {
261
+ return props.currentDevices;
262
+ },
263
+ set devices(newDevices) {
264
+ props.currentDevices = newDevices;
265
+ },
266
+ get audioInput() {
267
+ return getInput(stream?.getAudioTracks());
268
+ },
269
+ get videoInput() {
270
+ return getInput(stream?.getVideoTracks());
271
+ },
272
+ get expectedAudioInput() {
273
+ return getExpectedAudioInput(constraints.audio, () => ({
274
+ devices: getCurrentDevices().filter(isAudioInput),
275
+ input: getInput(stream?.getAudioTracks()),
276
+ }));
277
+ },
278
+ get expectedVideoInput() {
279
+ return getExpectedVideoInput(constraints.video, () => ({
280
+ devices: getCurrentDevices().filter(isVideoInput),
281
+ input: getInput(stream?.getVideoTracks()),
282
+ }));
283
+ },
284
+ muteAudio,
285
+ muteVideo,
286
+ get audioMuted() {
287
+ return isMuted(stream?.getAudioTracks());
288
+ },
289
+ get videoMuted() {
290
+ return isMuted(stream?.getVideoTracks());
291
+ },
292
+ applyConstraints: async (constraints) => {
293
+ try {
294
+ logger.debug({ scope, constraints: constraints }, 'apply native constraints');
295
+ return await applyConstraints(stream?.getTracks(), constraints);
296
+ }
297
+ catch (error) {
298
+ if (error instanceof Error) {
299
+ const status = toErrorDeviceStatus(error, deriveDeviceStatus);
300
+ logger.error({
301
+ scope,
302
+ status,
303
+ error,
304
+ constraints: constraints,
305
+ }, 'fail to apply native constraints');
306
+ initialMedia.status = status;
307
+ }
308
+ throw error;
309
+ }
310
+ },
311
+ getSettings: () => {
312
+ const settings = {
313
+ audio: stream
314
+ ?.getAudioTracks()
315
+ .map(track => track.getSettings()) ?? [],
316
+ video: stream
317
+ ?.getVideoTracks()
318
+ .map(track => track.getSettings()) ?? [],
319
+ };
320
+ logger.debug({ settings, scope }, 'get native settings');
321
+ return settings;
322
+ },
323
+ release: () => {
324
+ stopMediaStream(stream);
325
+ if (props.unsubscribe) {
326
+ props.unsubscribe();
327
+ props.unsubscribe = undefined;
328
+ }
329
+ return Promise.resolve();
330
+ },
331
+ }));
332
+ };
333
+ };