@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
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { getUserMedia, hasChangedInput, isMediaDeviceInfo, } from '@pexip/media-control';
|
|
2
|
+
import { createAsyncQueue } from '@pexip/utils';
|
|
3
|
+
import { UserMediaStatus } from './types';
|
|
4
|
+
import { buildMedia, cloneMedia, createMediaPipeline, hasSettingsChanged, AUDIO_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, } from './utils';
|
|
5
|
+
import { isMedia } from './typeGuard';
|
|
6
|
+
import { createModuleLogger } from './logger';
|
|
7
|
+
import { isInitial } from './status';
|
|
8
|
+
import { createGetUserMediaProcess } from './userMedia';
|
|
9
|
+
const DEFAULT_QUEUE_SIZE = 1;
|
|
10
|
+
const DEFAULT_QUEUE_THROTTLE_MS = 500;
|
|
11
|
+
const DEFAULT_QUEUE_DELAY_MS = 100;
|
|
12
|
+
const DEFAULT_QUEUE_DROP_LAST = false;
|
|
13
|
+
const isPreviewInput = (value) => {
|
|
14
|
+
if (isMediaDeviceInfo(value) || value === undefined) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
const extractFeaturesToConstraints = (keysToLookFor, settings) => {
|
|
20
|
+
if (!settings) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
return keysToLookFor.reduce((set, key) => {
|
|
24
|
+
if (settings[key] !== undefined) {
|
|
25
|
+
return { ...set, [key]: settings[key] };
|
|
26
|
+
}
|
|
27
|
+
return set;
|
|
28
|
+
}, {});
|
|
29
|
+
};
|
|
30
|
+
const createEventHandler = (eventHandlers) => (key) => (callback) => {
|
|
31
|
+
eventHandlers[key] = callback;
|
|
32
|
+
return () => {
|
|
33
|
+
eventHandlers[key] = undefined;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const getUM = async (constraints) => [
|
|
37
|
+
await getUserMedia(constraints),
|
|
38
|
+
UserMediaStatus.PermissionsGranted,
|
|
39
|
+
];
|
|
40
|
+
const createAudioVideoProcessingSettingsChangeDetector = (getCurrentMedia, getPreviewMedia) => {
|
|
41
|
+
const hasAudioSettingsChanged = hasSettingsChanged(AUDIO_SETTINGS_KEYS);
|
|
42
|
+
const hasVideoSettingsChanged = hasSettingsChanged(VIDEO_SETTINGS_KEYS);
|
|
43
|
+
return () => {
|
|
44
|
+
const currentMedia = getCurrentMedia?.();
|
|
45
|
+
const previewMedia = getPreviewMedia();
|
|
46
|
+
const { audio: [mainAudio], video: [mainVideo], } = currentMedia?.getSettings() ?? { audio: [], video: [] };
|
|
47
|
+
const { audio: [previewAudio], video: [previewVideo], } = previewMedia.getSettings();
|
|
48
|
+
const changed = hasAudioSettingsChanged(mainAudio, previewAudio) ||
|
|
49
|
+
hasVideoSettingsChanged(mainVideo, previewVideo);
|
|
50
|
+
return changed;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMedia, updateMainStream, onEnded, mediaSignal, queueOptions = {
|
|
54
|
+
size: DEFAULT_QUEUE_SIZE,
|
|
55
|
+
throttleInMS: DEFAULT_QUEUE_THROTTLE_MS,
|
|
56
|
+
delayInMS: DEFAULT_QUEUE_DELAY_MS,
|
|
57
|
+
dropLast: DEFAULT_QUEUE_DROP_LAST,
|
|
58
|
+
}, processors, }) => {
|
|
59
|
+
const queue = createAsyncQueue(queueOptions);
|
|
60
|
+
const eventHandlers = {};
|
|
61
|
+
const release = () => {
|
|
62
|
+
if (isInitial(props.media.status)) {
|
|
63
|
+
props.discardMedia = true;
|
|
64
|
+
}
|
|
65
|
+
props.media = buildMedia(() => ({ release }));
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
};
|
|
68
|
+
const internalProps = {
|
|
69
|
+
media: buildMedia(() => ({ release })),
|
|
70
|
+
updatingPreview: false,
|
|
71
|
+
updatingMain: false,
|
|
72
|
+
discardMedia: false,
|
|
73
|
+
initialized: false,
|
|
74
|
+
};
|
|
75
|
+
const logger = createModuleLogger({
|
|
76
|
+
module: 'PreviewStreamController',
|
|
77
|
+
eventHandlers,
|
|
78
|
+
props: internalProps,
|
|
79
|
+
});
|
|
80
|
+
const cleanUpMainSubscription = () => {
|
|
81
|
+
if (eventHandlers.unsubscribeMain) {
|
|
82
|
+
eventHandlers.unsubscribeMain();
|
|
83
|
+
eventHandlers.unsubscribeMain = undefined;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const props = new Proxy(internalProps, {
|
|
87
|
+
get: (target, p) => {
|
|
88
|
+
return target[p];
|
|
89
|
+
},
|
|
90
|
+
set: (target, p, value) => {
|
|
91
|
+
if (target[p] === value) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
logger.debug({ oldValue: target[p], newValue: value }, `Update Props[${p}]`);
|
|
95
|
+
switch (p) {
|
|
96
|
+
case 'media': {
|
|
97
|
+
if (!isMedia(value)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
target[p] = value;
|
|
101
|
+
eventHandlers[p]?.(value);
|
|
102
|
+
// Unsubscribe main stream signal on change of inputs
|
|
103
|
+
cleanUpMainSubscription();
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
case 'audioInput':
|
|
107
|
+
case 'videoInput': {
|
|
108
|
+
if (!isPreviewInput(value)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
target[p] = value;
|
|
112
|
+
eventHandlers[p]?.(value);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
case 'updatingMain':
|
|
116
|
+
case 'updatingPreview': {
|
|
117
|
+
if (typeof value !== 'boolean') {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
target[p] = value;
|
|
121
|
+
eventHandlers[p]?.(value);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
Reflect.set(target, p, value);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const postMediaPipeline = createMediaPipeline(
|
|
131
|
+
// Ignore the type conversion between tuple and array
|
|
132
|
+
processors);
|
|
133
|
+
const initFromMain = (mainMedia) => {
|
|
134
|
+
if (!mainMedia.stream) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
postMediaPipeline
|
|
138
|
+
.execute(cloneMedia(mainMedia))
|
|
139
|
+
.then(media => {
|
|
140
|
+
props.media = media;
|
|
141
|
+
})
|
|
142
|
+
.catch((error) => {
|
|
143
|
+
logger.error({ error }, 'Failed to post process media');
|
|
144
|
+
})
|
|
145
|
+
.finally(() => {
|
|
146
|
+
props.initialized = true;
|
|
147
|
+
if (props.discardMedia) {
|
|
148
|
+
void props.media.release();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
props.audioInput = mainMedia.audioInput;
|
|
152
|
+
props.videoInput = mainMedia.videoInput;
|
|
153
|
+
props.originalMainAudioInput = mainMedia.audioInput;
|
|
154
|
+
};
|
|
155
|
+
const mainMedia = getCurrentMedia();
|
|
156
|
+
if (mainMedia?.stream) {
|
|
157
|
+
initFromMain(mainMedia);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
eventHandlers.unsubscribeMain = mediaSignal.add(initFromMain);
|
|
161
|
+
}
|
|
162
|
+
const replaceMainStream = async (constraints) => {
|
|
163
|
+
logger.debug({ constraints }, 'Replacing main stream');
|
|
164
|
+
props.updatingMain = true;
|
|
165
|
+
await updateMainStream(constraints);
|
|
166
|
+
props.updatingMain = false;
|
|
167
|
+
};
|
|
168
|
+
const mediaPipeline = createMediaPipeline(() => [
|
|
169
|
+
createGetUserMediaProcess(getUM, getCurrentDevices, {
|
|
170
|
+
initialMedia: props.media,
|
|
171
|
+
scope: 'PreviewStreamController',
|
|
172
|
+
}),
|
|
173
|
+
...processors,
|
|
174
|
+
]);
|
|
175
|
+
const updatePreviewMedia = async (constraints) => {
|
|
176
|
+
logger.debug({ constraints }, 'Requesting a new preview stream');
|
|
177
|
+
await props.media?.release();
|
|
178
|
+
const media = await mediaPipeline.execute(constraints);
|
|
179
|
+
if (props.discardMedia) {
|
|
180
|
+
logger.debug('Discard media');
|
|
181
|
+
return await media.release();
|
|
182
|
+
}
|
|
183
|
+
props.media = media;
|
|
184
|
+
};
|
|
185
|
+
const updateAudioInput = async (input) => {
|
|
186
|
+
props.audioInput = input;
|
|
187
|
+
const request = {
|
|
188
|
+
audio: { device: { exact: input } },
|
|
189
|
+
video: props.videoInput,
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
props.updatingPreview = true;
|
|
193
|
+
await updatePreviewMedia(request);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (error instanceof Error) {
|
|
197
|
+
const errorName = error.name === 'Error' ? error.message : error.name;
|
|
198
|
+
if (errorName === 'NotReadableError') {
|
|
199
|
+
try {
|
|
200
|
+
logger.debug({ input }, 'NotReadableError, trying to sync main stream AudioInput');
|
|
201
|
+
await replaceMainStream({
|
|
202
|
+
audio: input,
|
|
203
|
+
video: getCurrentMedia()?.videoInput,
|
|
204
|
+
});
|
|
205
|
+
await updatePreviewMedia(request);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
logger.warn({ error: err, input }, 'Unable to recover NotReadableError AudioInput for preview');
|
|
209
|
+
if (err instanceof Error) {
|
|
210
|
+
return eventHandlers.audioInputError?.(err);
|
|
211
|
+
}
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
logger.warn({ error, input }, 'Unable to update AudioInput for preview');
|
|
217
|
+
eventHandlers.audioInputError?.(error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
logger.error({ error, input }, 'Unable to update AudioInput for preview');
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
props.updatingPreview = false;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const updateVideoInput = async (input) => {
|
|
230
|
+
props.videoInput = input;
|
|
231
|
+
try {
|
|
232
|
+
props.updatingPreview = true;
|
|
233
|
+
await updatePreviewMedia({
|
|
234
|
+
audio: props.audioInput,
|
|
235
|
+
video: { device: { exact: input } },
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (error instanceof Error) {
|
|
240
|
+
logger.warn({ error, input }, 'Unable to update VideoInput for preview');
|
|
241
|
+
return eventHandlers.videoInputError?.(error);
|
|
242
|
+
}
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
props.updatingPreview = false;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const cleanup = () => {
|
|
250
|
+
logger.debug('Cleanup preview controller');
|
|
251
|
+
props.audioInput = undefined;
|
|
252
|
+
props.videoInput = undefined;
|
|
253
|
+
onEnded?.();
|
|
254
|
+
};
|
|
255
|
+
const hasChangedAudioInput = () => hasChangedInput(props.originalMainAudioInput, props.audioInput);
|
|
256
|
+
const hasChangedVideoInput = () => hasChangedInput(getCurrentMedia()?.videoInput, props.videoInput);
|
|
257
|
+
const hasAudioVideoProcessingSettingsChanged = createAudioVideoProcessingSettingsChangeDetector(getCurrentMedia, () => props.media);
|
|
258
|
+
const hasChanges = () => hasChangedAudioInput() ||
|
|
259
|
+
hasChangedVideoInput() ||
|
|
260
|
+
hasAudioVideoProcessingSettingsChanged();
|
|
261
|
+
const applyChanges = async (force = false) => {
|
|
262
|
+
const { audioInput, videoInput } = props;
|
|
263
|
+
const { audio: [previewAudio], video: [previewVideo], } = props.media.getSettings();
|
|
264
|
+
await props.media.release();
|
|
265
|
+
cleanUpMainSubscription();
|
|
266
|
+
if (force || hasChanges()) {
|
|
267
|
+
const audioSettings = extractFeaturesToConstraints(AUDIO_SETTINGS_KEYS, previewAudio);
|
|
268
|
+
const videoSettings = extractFeaturesToConstraints(VIDEO_SETTINGS_KEYS, previewVideo);
|
|
269
|
+
// Omit the width & height settings from the preview so that the one
|
|
270
|
+
// from the main can be applied
|
|
271
|
+
delete videoSettings.width;
|
|
272
|
+
delete videoSettings.height;
|
|
273
|
+
logger.info({ audioInput, videoInput, audioSettings, videoSettings }, 'Apply changes to main');
|
|
274
|
+
try {
|
|
275
|
+
await replaceMainStream({
|
|
276
|
+
audio: { device: audioInput, ...audioSettings },
|
|
277
|
+
video: { device: videoInput, ...videoSettings },
|
|
278
|
+
});
|
|
279
|
+
props.originalMainAudioInput = props.audioInput;
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (error instanceof Error) {
|
|
283
|
+
logger.warn({ error }, 'Unable to apply changes to main');
|
|
284
|
+
return eventHandlers.applyChangesError?.(error);
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
cleanup();
|
|
290
|
+
};
|
|
291
|
+
const revertChanges = async () => {
|
|
292
|
+
await props.media.release();
|
|
293
|
+
cleanUpMainSubscription();
|
|
294
|
+
const mainMedia = getCurrentMedia();
|
|
295
|
+
const mainStream = mainMedia?.stream;
|
|
296
|
+
if (mainMedia && mainStream) {
|
|
297
|
+
// Workaround for iOS Safari bug
|
|
298
|
+
// https://bugs.webkit.org/show_bug.cgi?id=179363
|
|
299
|
+
const mainMuted = !!mainStream.getTracks().some(t => t.muted);
|
|
300
|
+
const current = mainMedia.audioInput;
|
|
301
|
+
const prev = props.originalMainAudioInput;
|
|
302
|
+
const audioChanged = hasChangedInput(prev, current);
|
|
303
|
+
if (mainMuted || audioChanged) {
|
|
304
|
+
logger.info({
|
|
305
|
+
mainMuted,
|
|
306
|
+
audioChanged,
|
|
307
|
+
currentAudioInput: current,
|
|
308
|
+
prevAudioInput: prev,
|
|
309
|
+
mainMedia,
|
|
310
|
+
}, 'Reverting Changes to main');
|
|
311
|
+
try {
|
|
312
|
+
await replaceMainStream({
|
|
313
|
+
audio: prev,
|
|
314
|
+
video: getCurrentMedia()?.videoInput,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
if (error instanceof Error) {
|
|
319
|
+
logger.warn({ error }, 'Unable to revert changes to main');
|
|
320
|
+
return eventHandlers.revertChangesError?.(error);
|
|
321
|
+
}
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
cleanup();
|
|
327
|
+
};
|
|
328
|
+
const updateInput = (hasChanged, updater) => (input) => {
|
|
329
|
+
if (hasChanged(input)) {
|
|
330
|
+
queue.enqueue(async () => {
|
|
331
|
+
await updater(input);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const toEvenHandler = createEventHandler(eventHandlers);
|
|
336
|
+
return {
|
|
337
|
+
get media() {
|
|
338
|
+
return props.media;
|
|
339
|
+
},
|
|
340
|
+
get audioInputChanged() {
|
|
341
|
+
return hasChangedAudioInput();
|
|
342
|
+
},
|
|
343
|
+
get videoInputChanged() {
|
|
344
|
+
return hasChangedVideoInput();
|
|
345
|
+
},
|
|
346
|
+
get inputChanged() {
|
|
347
|
+
return hasChanges();
|
|
348
|
+
},
|
|
349
|
+
get audioInput() {
|
|
350
|
+
return props.audioInput;
|
|
351
|
+
},
|
|
352
|
+
get videoInput() {
|
|
353
|
+
return props.videoInput;
|
|
354
|
+
},
|
|
355
|
+
get updatingPreview() {
|
|
356
|
+
return props.updatingPreview;
|
|
357
|
+
},
|
|
358
|
+
get updatingMain() {
|
|
359
|
+
return props.updatingMain;
|
|
360
|
+
},
|
|
361
|
+
updateAudioInput: updateInput(input => props.initialized && hasChangedInput(props.audioInput, input), updateAudioInput),
|
|
362
|
+
updateVideoInput: updateInput(input => props.initialized && hasChangedInput(props.videoInput, input), updateVideoInput),
|
|
363
|
+
onMediaChanged: toEvenHandler('media'),
|
|
364
|
+
onAudioInputChanged: toEvenHandler('audioInput'),
|
|
365
|
+
onVideoInputChanged: toEvenHandler('videoInput'),
|
|
366
|
+
onAudioInputError: toEvenHandler('audioInputError'),
|
|
367
|
+
onVideoInputError: toEvenHandler('videoInputError'),
|
|
368
|
+
onApplyChangesError: toEvenHandler('applyChangesError'),
|
|
369
|
+
onRevertChangesError: toEvenHandler('revertChangesError'),
|
|
370
|
+
onUpdatingPreview: toEvenHandler('updatingPreview'),
|
|
371
|
+
onUpdatingMain: toEvenHandler('updatingMain'),
|
|
372
|
+
applyChanges,
|
|
373
|
+
revertChanges,
|
|
374
|
+
};
|
|
375
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Signal, SignalVariant } from '@pexip/signal';
|
|
2
|
+
import type { MediaSignals } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Create a general signal with consistent scoped name
|
|
5
|
+
*
|
|
6
|
+
* @param name - Signal name
|
|
7
|
+
* @param crucial - Signify if the signal is unmissable. @defaultValue true
|
|
8
|
+
* @param variant - The variant of the signal @see Signal @defaultValue 'generic'
|
|
9
|
+
*/
|
|
10
|
+
export declare const createMediaSignal: <T = undefined>(name: string, crucial?: boolean, variant?: SignalVariant) => Signal<T>;
|
|
11
|
+
export declare const REQUIRED_SIGNAL_KEYS: readonly ["onMediaChanged", "onVAD", "onSilentDetected"];
|
|
12
|
+
/**
|
|
13
|
+
* Create and return all required and optional (if specified with `more`),
|
|
14
|
+
* signals for media to work
|
|
15
|
+
*
|
|
16
|
+
* @param more - Keys from `MediaSignalsOptional`, @see MediaSignalsOptional
|
|
17
|
+
* @param scope - any scope prefix for the generated signal name, @see Signal
|
|
18
|
+
*
|
|
19
|
+
* The following signals created by default
|
|
20
|
+
* - 'onMediaChanged',
|
|
21
|
+
* - 'onVAD',
|
|
22
|
+
*
|
|
23
|
+
* @see REQUIRED_SIGNAL_KEYS
|
|
24
|
+
*/
|
|
25
|
+
export declare const createMediaSignals: <K extends "onDevicesChanged" | "onStatusChanged" | keyof import("./types").StreamTrackSignals>(more: K[], scope?: string) => Pick<Required<MediaSignals>, "onMediaChanged" | "onVAD" | "onSilentDetected" | K>;
|
package/dist/signals.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createSignal } from '@pexip/signal';
|
|
2
|
+
/**
|
|
3
|
+
* Create a general signal with consistent scoped name
|
|
4
|
+
*
|
|
5
|
+
* @param name - Signal name
|
|
6
|
+
* @param crucial - Signify if the signal is unmissable. @defaultValue true
|
|
7
|
+
* @param variant - The variant of the signal @see Signal @defaultValue 'generic'
|
|
8
|
+
*/
|
|
9
|
+
export const createMediaSignal = (name, crucial = true, variant = 'generic') => createSignal({
|
|
10
|
+
name: `media/${name}`,
|
|
11
|
+
allowEmittingWithoutObserver: !crucial,
|
|
12
|
+
variant,
|
|
13
|
+
});
|
|
14
|
+
export const REQUIRED_SIGNAL_KEYS = [
|
|
15
|
+
'onMediaChanged',
|
|
16
|
+
'onVAD',
|
|
17
|
+
'onSilentDetected',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Create and return all required and optional (if specified with `more`),
|
|
21
|
+
* signals for media to work
|
|
22
|
+
*
|
|
23
|
+
* @param more - Keys from `MediaSignalsOptional`, @see MediaSignalsOptional
|
|
24
|
+
* @param scope - any scope prefix for the generated signal name, @see Signal
|
|
25
|
+
*
|
|
26
|
+
* The following signals created by default
|
|
27
|
+
* - 'onMediaChanged',
|
|
28
|
+
* - 'onVAD',
|
|
29
|
+
*
|
|
30
|
+
* @see REQUIRED_SIGNAL_KEYS
|
|
31
|
+
*/
|
|
32
|
+
export const createMediaSignals = (more, scope = '') => {
|
|
33
|
+
const signalScope = scope && [scope, ':'].join('');
|
|
34
|
+
return [...REQUIRED_SIGNAL_KEYS, ...more].reduce((signals, key) => ({
|
|
35
|
+
...signals,
|
|
36
|
+
[key]: createMediaSignal(`${signalScope}${key}`),
|
|
37
|
+
}), {});
|
|
38
|
+
};
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { UserMediaStatus, DeniedDevices } from './types';
|
|
2
|
+
export type UserMediaValidator = (status: UserMediaStatus) => boolean;
|
|
3
|
+
export declare const isFallbackVideo: UserMediaValidator;
|
|
4
|
+
export declare const isFallbackAudio: UserMediaValidator;
|
|
5
|
+
export declare const isFallback: UserMediaValidator;
|
|
6
|
+
export declare const hasNoDevice: UserMediaValidator;
|
|
7
|
+
export declare const hasNoAudioDevices: UserMediaValidator;
|
|
8
|
+
export declare const hasNoVideoDevices: UserMediaValidator;
|
|
9
|
+
export declare const isGrantedOnlyVideoNoAudioDevices: UserMediaValidator;
|
|
10
|
+
export declare const isPromptAudio: UserMediaValidator;
|
|
11
|
+
export declare const isGrantedOnlyVideo: UserMediaValidator;
|
|
12
|
+
export declare const isGrantedOnlyAudioNoVideoDevices: UserMediaValidator;
|
|
13
|
+
export declare const isPromptVideo: UserMediaValidator;
|
|
14
|
+
export declare const isGrantedOnlyAudio: UserMediaValidator;
|
|
15
|
+
export declare const areBothGranted: UserMediaValidator;
|
|
16
|
+
export declare const isGrantedVideo: UserMediaValidator;
|
|
17
|
+
export declare const isGrantedAudio: UserMediaValidator;
|
|
18
|
+
export declare const isGranted: UserMediaValidator;
|
|
19
|
+
export declare const isOnlyAudioError: UserMediaValidator;
|
|
20
|
+
export declare const isOnlyVideoError: UserMediaValidator;
|
|
21
|
+
export declare const isRejected: UserMediaValidator;
|
|
22
|
+
export declare const isRejectedOnlyAudio: UserMediaValidator;
|
|
23
|
+
export declare const isRejectedOnlyVideo: UserMediaValidator;
|
|
24
|
+
export declare const isOverConstrained: UserMediaValidator;
|
|
25
|
+
export declare const isUnknownError: UserMediaValidator;
|
|
26
|
+
export declare const isInitial: UserMediaValidator;
|
|
27
|
+
export declare const isInitialPermissions: UserMediaValidator;
|
|
28
|
+
export declare const isInitialPermissionsNotGranted: UserMediaValidator;
|
|
29
|
+
export declare const isInitialPermissionsGranted: UserMediaValidator;
|
|
30
|
+
export declare const isAudioDeviceInUse: UserMediaValidator;
|
|
31
|
+
export declare const isDeviceInUse: UserMediaValidator;
|
|
32
|
+
export declare const isVideoDeviceInUse: UserMediaValidator;
|
|
33
|
+
export declare const toDeniedDevices: (status?: UserMediaStatus) => DeniedDevices | undefined;
|
|
34
|
+
export declare const getPermissionStatus: (getPermissionState?: (anyActiveStream?: boolean | undefined) => Promise<import("@pexip/media-control").InputDevicePermission>) => Promise<UserMediaStatus.Initial | UserMediaStatus.InitialPermissionsGranted | UserMediaStatus.InitialPermissionsNotGranted | UserMediaStatus.InitialPermissionsVideoInputDenied | UserMediaStatus.InitialPermissionsAudioInputDenied | UserMediaStatus.InitialPermissionsVideoInputGranted | UserMediaStatus.InitialPermissionsAudioInputGranted | UserMediaStatus.InitialPermissionsGrantedVideoInputDenied | UserMediaStatus.InitialPermissionsGrantedAudioInputDenied | UserMediaStatus.PermissionsRejected>;
|
|
35
|
+
export declare const deriveInitialPermissionStatus: (prevStatus: UserMediaStatus, getPermissionState?: (anyActiveStream?: boolean | undefined) => Promise<import("@pexip/media-control").InputDevicePermission>) => Promise<UserMediaStatus>;
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { getInputDevicePermissionState } from '@pexip/media-control';
|
|
2
|
+
import { UserMediaStatus, DeniedDevices } from './types';
|
|
3
|
+
export const isFallbackVideo = status => [
|
|
4
|
+
UserMediaStatus.PermissionsOnlyVideoinputFallback,
|
|
5
|
+
UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices,
|
|
6
|
+
UserMediaStatus.PermissionsGrantedFallback,
|
|
7
|
+
UserMediaStatus.PermissionsGrantedFallbackVideoinput,
|
|
8
|
+
].includes(status);
|
|
9
|
+
export const isFallbackAudio = status => [
|
|
10
|
+
UserMediaStatus.PermissionsOnlyAudioinputFallback,
|
|
11
|
+
UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices,
|
|
12
|
+
UserMediaStatus.PermissionsGrantedFallback,
|
|
13
|
+
UserMediaStatus.PermissionsGrantedFallbackAudioinput,
|
|
14
|
+
].includes(status);
|
|
15
|
+
export const isFallback = status => status === UserMediaStatus.PermissionsGrantedFallback ||
|
|
16
|
+
isFallbackAudio(status) ||
|
|
17
|
+
isFallbackVideo(status);
|
|
18
|
+
export const hasNoDevice = status => UserMediaStatus.NoDevicesFound === status;
|
|
19
|
+
export const hasNoAudioDevices = status => hasNoDevice(status) ||
|
|
20
|
+
status === UserMediaStatus.NoAudioDevicesFound ||
|
|
21
|
+
isGrantedOnlyVideoNoAudioDevices(status);
|
|
22
|
+
export const hasNoVideoDevices = status => hasNoDevice(status) ||
|
|
23
|
+
status === UserMediaStatus.NoVideoDevicesFound ||
|
|
24
|
+
isGrantedOnlyAudioNoVideoDevices(status);
|
|
25
|
+
export const isGrantedOnlyVideoNoAudioDevices = status => [
|
|
26
|
+
UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices,
|
|
27
|
+
UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices,
|
|
28
|
+
].includes(status);
|
|
29
|
+
export const isPromptAudio = status => [
|
|
30
|
+
UserMediaStatus.PermissionsOnlyVideoinput,
|
|
31
|
+
UserMediaStatus.PermissionsOnlyVideoinputFallback,
|
|
32
|
+
].includes(status);
|
|
33
|
+
export const isGrantedOnlyVideo = status => isGrantedOnlyVideoNoAudioDevices(status) ||
|
|
34
|
+
isPromptAudio(status) ||
|
|
35
|
+
isRejectedOnlyAudio(status) ||
|
|
36
|
+
isOnlyAudioError(status);
|
|
37
|
+
export const isGrantedOnlyAudioNoVideoDevices = status => [
|
|
38
|
+
UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices,
|
|
39
|
+
UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices,
|
|
40
|
+
].includes(status);
|
|
41
|
+
export const isPromptVideo = status => [
|
|
42
|
+
UserMediaStatus.PermissionsOnlyAudioinput,
|
|
43
|
+
UserMediaStatus.PermissionsOnlyAudioinputFallback,
|
|
44
|
+
].includes(status);
|
|
45
|
+
export const isGrantedOnlyAudio = status => isGrantedOnlyAudioNoVideoDevices(status) ||
|
|
46
|
+
isPromptVideo(status) ||
|
|
47
|
+
isRejectedOnlyVideo(status) ||
|
|
48
|
+
isOnlyVideoError(status);
|
|
49
|
+
export const areBothGranted = status => [
|
|
50
|
+
UserMediaStatus.PermissionsGranted,
|
|
51
|
+
UserMediaStatus.PermissionsGrantedFallback,
|
|
52
|
+
UserMediaStatus.PermissionsGrantedFallbackAudioinput,
|
|
53
|
+
UserMediaStatus.PermissionsGrantedFallbackVideoinput,
|
|
54
|
+
].includes(status);
|
|
55
|
+
export const isGrantedVideo = status => areBothGranted(status) ||
|
|
56
|
+
isGrantedOnlyVideoNoAudioDevices(status) ||
|
|
57
|
+
isGrantedOnlyVideo(status);
|
|
58
|
+
export const isGrantedAudio = status => areBothGranted(status) ||
|
|
59
|
+
isGrantedOnlyAudio(status) ||
|
|
60
|
+
isGrantedOnlyAudioNoVideoDevices(status);
|
|
61
|
+
export const isGranted = status => isGrantedVideo(status) || isGrantedAudio(status);
|
|
62
|
+
export const isOnlyAudioError = status => [
|
|
63
|
+
UserMediaStatus.PermissionsRejectedAudioInput,
|
|
64
|
+
UserMediaStatus.AudioDeviceInUse,
|
|
65
|
+
UserMediaStatus.AudioOverconstrained,
|
|
66
|
+
UserMediaStatus.InvalidAudioConstraints,
|
|
67
|
+
UserMediaStatus.NotSupportedErrorOnlyAudioInput,
|
|
68
|
+
].includes(status);
|
|
69
|
+
export const isOnlyVideoError = status => [
|
|
70
|
+
UserMediaStatus.PermissionsRejectedVideoInput,
|
|
71
|
+
UserMediaStatus.VideoDeviceInUse,
|
|
72
|
+
UserMediaStatus.VideoOverconstrained,
|
|
73
|
+
UserMediaStatus.InvalidVideoConstraints,
|
|
74
|
+
UserMediaStatus.NotSupportedErrorOnlyVideoInput,
|
|
75
|
+
].includes(status);
|
|
76
|
+
export const isRejected = status => UserMediaStatus.PermissionsRejected === status;
|
|
77
|
+
export const isRejectedOnlyAudio = status => [
|
|
78
|
+
UserMediaStatus.PermissionsRejectedAudioInput,
|
|
79
|
+
UserMediaStatus.InitialPermissionsGrantedAudioInputDenied,
|
|
80
|
+
UserMediaStatus.InitialPermissionsAudioInputDenied,
|
|
81
|
+
].includes(status);
|
|
82
|
+
export const isRejectedOnlyVideo = status => [
|
|
83
|
+
UserMediaStatus.PermissionsRejectedVideoInput,
|
|
84
|
+
UserMediaStatus.InitialPermissionsGrantedVideoInputDenied,
|
|
85
|
+
UserMediaStatus.InitialPermissionsVideoInputDenied,
|
|
86
|
+
].includes(status);
|
|
87
|
+
export const isOverConstrained = status => [
|
|
88
|
+
UserMediaStatus.Overconstrained,
|
|
89
|
+
UserMediaStatus.VideoOverconstrained,
|
|
90
|
+
UserMediaStatus.AudioOverconstrained,
|
|
91
|
+
].includes(status);
|
|
92
|
+
export const isUnknownError = status => [
|
|
93
|
+
UserMediaStatus.UnknownError,
|
|
94
|
+
UserMediaStatus.UnknownErrorOnlyVideoinput,
|
|
95
|
+
UserMediaStatus.UnknownErrorOnlyAudioinput,
|
|
96
|
+
].includes(status);
|
|
97
|
+
export const isInitial = status => status === UserMediaStatus.Initial;
|
|
98
|
+
export const isInitialPermissions = status => [
|
|
99
|
+
UserMediaStatus.InitialPermissionsAudioInputDenied,
|
|
100
|
+
UserMediaStatus.InitialPermissionsAudioInputGranted,
|
|
101
|
+
UserMediaStatus.InitialPermissionsGranted,
|
|
102
|
+
UserMediaStatus.InitialPermissionsGrantedAudioInputDenied,
|
|
103
|
+
UserMediaStatus.InitialPermissionsGrantedVideoInputDenied,
|
|
104
|
+
UserMediaStatus.InitialPermissionsNotGranted,
|
|
105
|
+
UserMediaStatus.InitialPermissionsVideoInputDenied,
|
|
106
|
+
UserMediaStatus.InitialPermissionsVideoInputGranted,
|
|
107
|
+
].includes(status);
|
|
108
|
+
export const isInitialPermissionsNotGranted = status => [
|
|
109
|
+
UserMediaStatus.InitialPermissionsAudioInputDenied,
|
|
110
|
+
UserMediaStatus.InitialPermissionsNotGranted,
|
|
111
|
+
UserMediaStatus.InitialPermissionsVideoInputDenied,
|
|
112
|
+
].includes(status);
|
|
113
|
+
export const isInitialPermissionsGranted = status => [
|
|
114
|
+
UserMediaStatus.InitialPermissionsGranted,
|
|
115
|
+
UserMediaStatus.InitialPermissionsGrantedAudioInputDenied,
|
|
116
|
+
UserMediaStatus.InitialPermissionsGrantedVideoInputDenied,
|
|
117
|
+
].includes(status);
|
|
118
|
+
export const isAudioDeviceInUse = status => status === UserMediaStatus.AudioDeviceInUse;
|
|
119
|
+
export const isDeviceInUse = status => [
|
|
120
|
+
UserMediaStatus.DevicesInUse,
|
|
121
|
+
UserMediaStatus.AudioDeviceInUse,
|
|
122
|
+
UserMediaStatus.VideoDeviceInUse,
|
|
123
|
+
].includes(status);
|
|
124
|
+
export const isVideoDeviceInUse = status => status === UserMediaStatus.VideoDeviceInUse;
|
|
125
|
+
export const toDeniedDevices = (status) => {
|
|
126
|
+
if (!status) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
if (isRejectedOnlyVideo(status)) {
|
|
130
|
+
return DeniedDevices.Camera;
|
|
131
|
+
}
|
|
132
|
+
if (isRejectedOnlyAudio(status)) {
|
|
133
|
+
return DeniedDevices.Microphone;
|
|
134
|
+
}
|
|
135
|
+
if (isRejected(status)) {
|
|
136
|
+
return DeniedDevices.Both;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
export const getPermissionStatus = async (getPermissionState = getInputDevicePermissionState) => {
|
|
140
|
+
const { audio, video } = await getPermissionState();
|
|
141
|
+
if (audio === 'denied') {
|
|
142
|
+
if (video === 'denied') {
|
|
143
|
+
return UserMediaStatus.PermissionsRejected;
|
|
144
|
+
}
|
|
145
|
+
if (video === 'granted') {
|
|
146
|
+
return UserMediaStatus.InitialPermissionsGrantedAudioInputDenied;
|
|
147
|
+
}
|
|
148
|
+
if (video === 'prompt') {
|
|
149
|
+
return UserMediaStatus.InitialPermissionsAudioInputDenied;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (audio === 'granted') {
|
|
153
|
+
if (video === 'denied') {
|
|
154
|
+
return UserMediaStatus.InitialPermissionsGrantedVideoInputDenied;
|
|
155
|
+
}
|
|
156
|
+
if (video === 'granted') {
|
|
157
|
+
return UserMediaStatus.InitialPermissionsGranted;
|
|
158
|
+
}
|
|
159
|
+
if (video === 'prompt') {
|
|
160
|
+
return UserMediaStatus.InitialPermissionsAudioInputGranted;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (audio === 'prompt') {
|
|
164
|
+
if (video === 'denied') {
|
|
165
|
+
return UserMediaStatus.InitialPermissionsVideoInputDenied;
|
|
166
|
+
}
|
|
167
|
+
if (video === 'granted') {
|
|
168
|
+
return UserMediaStatus.InitialPermissionsVideoInputGranted;
|
|
169
|
+
}
|
|
170
|
+
if (video === 'prompt') {
|
|
171
|
+
return UserMediaStatus.InitialPermissionsNotGranted;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return UserMediaStatus.Initial;
|
|
175
|
+
};
|
|
176
|
+
export const deriveInitialPermissionStatus = async (prevStatus, getPermissionState = getInputDevicePermissionState) => {
|
|
177
|
+
if (isInitialPermissions(prevStatus) || isRejected(prevStatus)) {
|
|
178
|
+
return prevStatus;
|
|
179
|
+
}
|
|
180
|
+
const { audio, video } = await getPermissionState();
|
|
181
|
+
if (areBothGranted(prevStatus) ||
|
|
182
|
+
(audio === 'granted' && video === 'granted')) {
|
|
183
|
+
return UserMediaStatus.InitialPermissionsGranted;
|
|
184
|
+
}
|
|
185
|
+
if (isGrantedAudio(prevStatus)) {
|
|
186
|
+
return isRejectedOnlyVideo(prevStatus)
|
|
187
|
+
? UserMediaStatus.InitialPermissionsGrantedVideoInputDenied
|
|
188
|
+
: UserMediaStatus.InitialPermissionsAudioInputGranted;
|
|
189
|
+
}
|
|
190
|
+
if (isGrantedVideo(prevStatus)) {
|
|
191
|
+
return isRejectedOnlyAudio(prevStatus)
|
|
192
|
+
? UserMediaStatus.InitialPermissionsGrantedAudioInputDenied
|
|
193
|
+
: UserMediaStatus.InitialPermissionsVideoInputGranted;
|
|
194
|
+
}
|
|
195
|
+
if (prevStatus === UserMediaStatus.DevicesInUse) {
|
|
196
|
+
return UserMediaStatus.InitialPermissionsGranted;
|
|
197
|
+
}
|
|
198
|
+
return await getPermissionStatus(getPermissionState);
|
|
199
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnalyzerNodeInit } from '@pexip/media-processor';
|
|
2
|
+
import type { Media, AudioContentHint, VideoContentHint } from './types';
|
|
3
|
+
import { UserMediaStatus } from './types';
|
|
4
|
+
export declare const isNonNullObject: (value: unknown) => value is Record<string, unknown>;
|
|
5
|
+
export declare const isUserMediaStatus: (value: unknown) => value is UserMediaStatus;
|
|
6
|
+
export declare const isMedia: (value: unknown) => value is Media;
|
|
7
|
+
export declare const isAnalyzerNodeInitProp: (value: unknown) => value is AnalyzerNodeInit | undefined;
|
|
8
|
+
export declare const isAudioContentHint: (value: unknown) => value is AudioContentHint;
|
|
9
|
+
export declare const isVideoContentHint: (value: unknown) => value is VideoContentHint;
|