@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/CHANGELOG.md +39 -0
- package/dist/audioMixingProcessor.d.ts +18 -0
- package/dist/audioMixingProcessor.js +173 -0
- package/dist/audioProcessor.d.ts +86 -0
- package/dist/audioProcessor.js +310 -0
- package/dist/baseLogger.d.ts +37 -0
- package/dist/baseLogger.js +29 -0
- package/dist/displayMedia.d.ts +3 -0
- package/dist/displayMedia.js +31 -0
- package/dist/index.d.ts +11 -837
- package/dist/index.js +11 -0
- package/dist/logger.d.ts +11 -0
- package/dist/logger.js +44 -0
- package/dist/media.d.ts +8 -0
- package/dist/media.js +296 -0
- package/dist/previewController.d.ts +64 -0
- package/dist/previewController.js +375 -0
- package/dist/signals.d.ts +25 -0
- package/dist/signals.js +38 -0
- package/dist/status.d.ts +35 -0
- package/dist/status.js +199 -0
- package/dist/typeGuard.d.ts +9 -0
- package/dist/typeGuard.js +40 -0
- package/dist/types.d.ts +559 -0
- package/dist/types.js +278 -0
- package/dist/userMedia.d.ts +38 -0
- package/dist/userMedia.js +333 -0
- package/dist/utils.d.ts +108 -0
- package/dist/utils.js +360 -0
- package/dist/videoProcessor.d.ts +56 -0
- package/dist/videoProcessor.js +349 -0
- package/package.json +10 -10
- package/dist/index.mjs +0 -2900
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
|
+
};
|