@pexip/media 18.4.0 → 19.0.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 +130 -0
- package/README.md +9 -8
- package/dist/audioMixingProcessor.d.ts +2 -2
- package/dist/audioMixingProcessor.js +113 -100
- package/dist/audioProcessor.d.ts +4 -4
- package/dist/audioProcessor.js +117 -152
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/media.d.ts +12 -2
- package/dist/media.js +242 -170
- package/dist/previewController.d.ts +10 -9
- package/dist/previewController.js +99 -68
- package/dist/signals.d.ts +10 -1
- package/dist/signals.js +9 -0
- package/dist/status.d.ts +5 -2
- package/dist/status.js +3 -3
- package/dist/types.d.ts +135 -40
- package/dist/userMedia.d.ts +10 -8
- package/dist/userMedia.js +122 -178
- package/dist/utils.d.ts +25 -47
- package/dist/utils.js +487 -186
- package/dist/videoProcessor.d.ts +3 -7
- package/dist/videoProcessor.js +103 -139
- package/package.json +12 -7
package/dist/utils.js
CHANGED
|
@@ -1,42 +1,165 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createStreamTrackEventSubscriptions, createTrackDevicesChanges, extractConstraintsWithKeys, findDeviceFromConstraints, relaxInputConstraint, resolveMediaDeviceConstraints, shouldRequestDevice, toMediaDeviceInputKind, } from '@pexip/media-control';
|
|
2
2
|
import { calculateMaxBlurPass } from '@pexip/media-processor';
|
|
3
|
-
import { hasOwn } from '@pexip/utils';
|
|
4
|
-
import {
|
|
5
|
-
import { isOverConstrained } from './status';
|
|
6
|
-
import { isMedia } from './typeGuard';
|
|
3
|
+
import { hasOwn, assert, isEmpty } from '@pexip/utils';
|
|
4
|
+
import { internalSignals } from './signals';
|
|
7
5
|
export const makeDeriveDeviceStatus = (constraints) => (audio, video, both) => {
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
if (constraints.audio === undefined || constraints.audio) {
|
|
7
|
+
if (constraints.video === undefined || constraints.video) {
|
|
8
|
+
return both;
|
|
9
|
+
}
|
|
12
10
|
return audio;
|
|
13
11
|
}
|
|
12
|
+
if (constraints.video === undefined || constraints.video) {
|
|
13
|
+
return video;
|
|
14
|
+
}
|
|
14
15
|
return both;
|
|
15
16
|
};
|
|
16
|
-
export const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
17
|
+
export const createMediaTrack = (trackInit) => {
|
|
18
|
+
assert(trackInit.track === undefined ||
|
|
19
|
+
(trackInit.kind === trackInit.input?.kind &&
|
|
20
|
+
trackInit.kind === toMediaDeviceInputKind(trackInit.track),
|
|
21
|
+
'Inconsistent track kind'));
|
|
22
|
+
const currentConstraints = typeof trackInit.constraints === 'boolean' ? {} : trackInit.constraints;
|
|
23
|
+
// Find the original source track through the linked list
|
|
24
|
+
let sourceMediaTrack = trackInit.previousMediaTrack;
|
|
25
|
+
while (sourceMediaTrack?.previousMediaTrack) {
|
|
26
|
+
sourceMediaTrack = sourceMediaTrack.previousMediaTrack;
|
|
27
|
+
}
|
|
28
|
+
const getConstraints = () => {
|
|
29
|
+
if (trackInit.constraints === false) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...currentConstraints,
|
|
34
|
+
...trackInit.track?.getConstraints(),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const getSettings = () => {
|
|
38
|
+
return {
|
|
39
|
+
...trackInit.previousMediaTrack?.getSettings(),
|
|
40
|
+
...trackInit.track?.getSettings(),
|
|
41
|
+
...(trackInit.getSettings?.() ?? {}),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
22
44
|
return {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
get kind() {
|
|
46
|
+
return trackInit.kind;
|
|
47
|
+
},
|
|
48
|
+
get id() {
|
|
49
|
+
return trackInit.id ?? trackInit.track?.id ?? trackInit.kind;
|
|
50
|
+
},
|
|
51
|
+
get track() {
|
|
52
|
+
return trackInit.track;
|
|
53
|
+
},
|
|
54
|
+
get input() {
|
|
55
|
+
return trackInit.input;
|
|
56
|
+
},
|
|
57
|
+
get expectedInput() {
|
|
58
|
+
return trackInit.expectedInput;
|
|
59
|
+
},
|
|
60
|
+
get label() {
|
|
61
|
+
return trackInit.label;
|
|
62
|
+
},
|
|
63
|
+
get previousMediaTrack() {
|
|
64
|
+
return trackInit.previousMediaTrack;
|
|
65
|
+
},
|
|
66
|
+
get muted() {
|
|
67
|
+
if (trackInit.overrideMute) {
|
|
68
|
+
return trackInit.muted;
|
|
69
|
+
}
|
|
70
|
+
return (
|
|
71
|
+
// If the source track muted, the following processed track will
|
|
72
|
+
// be muted as well since there is no data to process
|
|
73
|
+
!!isTrackMuted(this.source.track) ||
|
|
74
|
+
isTrackMuted(trackInit.track));
|
|
75
|
+
},
|
|
76
|
+
get source() {
|
|
77
|
+
return sourceMediaTrack ?? this;
|
|
78
|
+
},
|
|
79
|
+
mute(toMute) {
|
|
80
|
+
if (trackInit.overrideMute) {
|
|
81
|
+
return trackInit.mute?.(toMute);
|
|
82
|
+
}
|
|
83
|
+
trackInit.previousMediaTrack?.mute(toMute);
|
|
84
|
+
if (trackInit.track) {
|
|
85
|
+
trackInit.track.enabled = !toMute;
|
|
31
86
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
87
|
+
},
|
|
88
|
+
getSettings,
|
|
89
|
+
getConstraints() {
|
|
90
|
+
return getConstraints();
|
|
91
|
+
},
|
|
92
|
+
clone() {
|
|
93
|
+
return createMediaTrack({
|
|
94
|
+
...trackInit,
|
|
95
|
+
constraints: getConstraints(),
|
|
96
|
+
track: trackInit.track?.clone(),
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
async applyConstraints(constraints) {
|
|
100
|
+
const resolvedConstraints = resolveMediaDeviceConstraints(constraints);
|
|
101
|
+
if (typeof resolvedConstraints === 'boolean' ||
|
|
102
|
+
resolvedConstraints === undefined) {
|
|
103
|
+
return Promise.resolve();
|
|
104
|
+
}
|
|
105
|
+
await trackInit.previousMediaTrack?.applyConstraints(resolvedConstraints);
|
|
106
|
+
await trackInit.applyConstraints?.(constraints);
|
|
107
|
+
if (trackInit.track) {
|
|
108
|
+
if ('contentHint' in resolvedConstraints &&
|
|
109
|
+
typeof resolvedConstraints.contentHint === 'string' &&
|
|
110
|
+
trackInit.track.contentHint !==
|
|
111
|
+
resolvedConstraints.contentHint) {
|
|
112
|
+
trackInit.track.contentHint =
|
|
113
|
+
resolvedConstraints.contentHint;
|
|
114
|
+
}
|
|
115
|
+
const settings = trackInit.track.getSettings();
|
|
116
|
+
const reducedConstraints = Object.keys(resolvedConstraints).reduce((accm, key) => {
|
|
117
|
+
const constraintKey = key;
|
|
118
|
+
if (constraintKey in settings) {
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment --- Type issue and need to be fixed when we have time
|
|
120
|
+
// @ts-expect-error
|
|
121
|
+
accm[constraintKey] =
|
|
122
|
+
resolvedConstraints[constraintKey];
|
|
123
|
+
}
|
|
124
|
+
return accm;
|
|
125
|
+
}, {});
|
|
126
|
+
// Firefox will throw an error if passing an empty constraint object
|
|
127
|
+
if (!isEmpty(reducedConstraints)) {
|
|
128
|
+
await trackInit.track.applyConstraints(reducedConstraints);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Update the current constraints
|
|
132
|
+
Object.assign(currentConstraints, resolvedConstraints);
|
|
133
|
+
},
|
|
134
|
+
async release() {
|
|
135
|
+
trackInit.track?.stop();
|
|
136
|
+
if (trackInit.track) {
|
|
137
|
+
trackInit.signals?.onTrackReleased?.emit(trackInit.track);
|
|
138
|
+
trackInit.signals?.[trackInit.track.kind === 'audio'
|
|
139
|
+
? 'onAudioMuteStateChanged'
|
|
140
|
+
: 'onVideoMuteStateChanged']?.emit(undefined);
|
|
35
141
|
}
|
|
36
|
-
|
|
142
|
+
await trackInit.previousMediaTrack?.release();
|
|
143
|
+
await trackInit.release?.();
|
|
144
|
+
},
|
|
145
|
+
toJSON() {
|
|
146
|
+
return {
|
|
147
|
+
label: trackInit.label,
|
|
148
|
+
track: trackInit.track,
|
|
149
|
+
previousMediaTrack: trackInit.previousMediaTrack,
|
|
150
|
+
constraints: getConstraints(),
|
|
151
|
+
settings: getSettings(),
|
|
152
|
+
};
|
|
37
153
|
},
|
|
38
154
|
};
|
|
39
155
|
};
|
|
156
|
+
export const createTrackProcessingPipeline = (processors) => async (track) => {
|
|
157
|
+
let lastTrack = track;
|
|
158
|
+
for (const process of processors) {
|
|
159
|
+
lastTrack = await process(lastTrack);
|
|
160
|
+
}
|
|
161
|
+
return lastTrack;
|
|
162
|
+
};
|
|
40
163
|
/**
|
|
41
164
|
* Interpret provided input to resolve to a MediaDeviceInfoLike when possible
|
|
42
165
|
* otherwise `undefined`
|
|
@@ -50,30 +173,15 @@ export const interpretInput = (input, getCurrentInput) => {
|
|
|
50
173
|
}
|
|
51
174
|
return input;
|
|
52
175
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return props.cachedExpectedInputs.get(constraints);
|
|
63
|
-
}
|
|
64
|
-
const { devices, input } = getInfo();
|
|
65
|
-
// Update cache
|
|
66
|
-
const relaxedConstraints = relaxInputConstraint(constraints, devices);
|
|
67
|
-
const { device: [[device] = []], } = extractConstraintsWithKeys(['device'])(relaxedConstraints);
|
|
68
|
-
// The result from `findDeviceFromConstraints` has more restrictive
|
|
69
|
-
// result since it also consider if the device can be found from the
|
|
70
|
-
// device list
|
|
71
|
-
const found = device ?? findDeviceFromConstraints(constraints, devices);
|
|
72
|
-
const expectedInput = interpretInput(found, () => input);
|
|
73
|
-
props.cachedExpectedInputs.clear();
|
|
74
|
-
props.cachedExpectedInputs.set(constraints, expectedInput);
|
|
75
|
-
return expectedInput;
|
|
76
|
-
};
|
|
176
|
+
export const findExpectedInput = (devices, constraints, input, kind) => {
|
|
177
|
+
const relaxedConstraints = relaxInputConstraint(kind, constraints, devices);
|
|
178
|
+
const { device: [[device] = []], } = extractConstraintsWithKeys(['device'])(relaxedConstraints);
|
|
179
|
+
// The result from `findDeviceFromConstraints` has more restrictive
|
|
180
|
+
// result since it also consider if the device can be found from the
|
|
181
|
+
// device list
|
|
182
|
+
const found = device ?? findDeviceFromConstraints(constraints, devices);
|
|
183
|
+
const expectedInput = interpretInput(found, () => input);
|
|
184
|
+
return expectedInput;
|
|
77
185
|
};
|
|
78
186
|
/**
|
|
79
187
|
* A utility function to check if the provided track is muted. There are
|
|
@@ -86,150 +194,294 @@ export const createMemorizedGetExpectedInput = () => {
|
|
|
86
194
|
* | false | false | true |
|
|
87
195
|
* ```
|
|
88
196
|
*
|
|
89
|
-
* @param
|
|
197
|
+
* @param track - The tracks can be got from `MediaStream['getAudioStats']` or
|
|
90
198
|
* `MediaStream['getVideoTracks']`
|
|
91
199
|
*
|
|
92
200
|
* @returns `true` means muted, `false` means not muted and `undefined` means
|
|
93
201
|
* there is no track to check
|
|
94
202
|
*/
|
|
95
|
-
export const
|
|
96
|
-
if (!
|
|
203
|
+
export const isTrackMuted = (track) => {
|
|
204
|
+
if (!track || track.readyState === 'ended') {
|
|
97
205
|
return undefined;
|
|
98
206
|
}
|
|
99
|
-
return
|
|
207
|
+
return track.muted || !track.enabled;
|
|
100
208
|
};
|
|
101
|
-
export const buildMedia = (
|
|
209
|
+
export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevicesChanged) => {
|
|
102
210
|
const props = {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
211
|
+
originalConstraints: mediaInit.originalConstraints ?? mediaInit.constraints,
|
|
212
|
+
status: mediaInit.status,
|
|
213
|
+
devices: mediaInit.devices,
|
|
214
|
+
stream: mediaInit.stream ?? new MediaStream(),
|
|
215
|
+
audioTrack: mediaInit.tracks
|
|
216
|
+
.flatMap(track => {
|
|
217
|
+
return track.kind === 'audioinput' ? [track] : [];
|
|
218
|
+
})
|
|
219
|
+
.at(0),
|
|
220
|
+
videoTrack: mediaInit.tracks
|
|
221
|
+
.flatMap(track => (track.kind === 'videoinput' ? [track] : []))
|
|
222
|
+
.at(0),
|
|
223
|
+
};
|
|
224
|
+
const subscribeTrackEvents = (track) => createStreamTrackEventSubscriptions(track, {
|
|
225
|
+
ended: track => {
|
|
226
|
+
mediaInit.signals?.[track.kind === 'audio'
|
|
227
|
+
? 'onAudioMuteStateChanged'
|
|
228
|
+
: 'onVideoMuteStateChanged']?.emit(undefined);
|
|
229
|
+
mediaInit.signals?.onStreamTrackEndedFinal?.emit(track);
|
|
230
|
+
},
|
|
231
|
+
mute: track => {
|
|
232
|
+
mediaInit.signals?.[track.kind === 'audio'
|
|
233
|
+
? 'onAudioMuteStateChanged'
|
|
234
|
+
: 'onVideoMuteStateChanged']?.emit(true);
|
|
235
|
+
mediaInit.signals?.onStreamTrackMuted?.emit(track);
|
|
236
|
+
},
|
|
237
|
+
unmute: track => {
|
|
238
|
+
mediaInit.signals?.[track.kind === 'audio'
|
|
239
|
+
? 'onAudioMuteStateChanged'
|
|
240
|
+
: 'onVideoMuteStateChanged']?.emit(!track.enabled);
|
|
241
|
+
mediaInit.signals?.onStreamTrackUnmuted?.emit(track);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
const subscribeTrackAndSourceTrackEvents = (track) => {
|
|
245
|
+
let trackUnsubscribe;
|
|
246
|
+
let sourceTrackUnsubscribe;
|
|
247
|
+
if (track.track) {
|
|
248
|
+
trackUnsubscribe = subscribeTrackEvents(track.track);
|
|
249
|
+
if (track.source.track && track.source.track !== track.track) {
|
|
250
|
+
sourceTrackUnsubscribe = subscribeTrackEvents(track.source.track);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return () => {
|
|
254
|
+
trackUnsubscribe?.();
|
|
255
|
+
sourceTrackUnsubscribe?.();
|
|
256
|
+
};
|
|
106
257
|
};
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (mute) {
|
|
113
|
-
return mute(muted);
|
|
258
|
+
const trackSubscriptions = new Map(mediaInit.tracks.flatMap(track => {
|
|
259
|
+
if (track.track) {
|
|
260
|
+
return [
|
|
261
|
+
[track.track, subscribeTrackAndSourceTrackEvents(track)],
|
|
262
|
+
];
|
|
114
263
|
}
|
|
115
|
-
return
|
|
264
|
+
return [];
|
|
265
|
+
}));
|
|
266
|
+
const getConstraints = () => {
|
|
267
|
+
const audio = props.audioTrack?.getConstraints() ?? mediaInit.constraints.audio;
|
|
268
|
+
const video = props.videoTrack?.getConstraints() ?? mediaInit.constraints.video;
|
|
269
|
+
return { audio: audio, video: video };
|
|
116
270
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
271
|
+
let unsubscribe = onDevicesChanged.add(devices => {
|
|
272
|
+
props.devices = devices;
|
|
273
|
+
});
|
|
274
|
+
const muteTrack = (track, mute) => {
|
|
275
|
+
const previousMuteState = track.muted;
|
|
276
|
+
const previousEnabledState = track.track?.enabled;
|
|
277
|
+
track.mute(mute);
|
|
278
|
+
const currentMuteState = track.muted;
|
|
279
|
+
const currentEnabledState = track.track?.enabled;
|
|
280
|
+
if (track.track &&
|
|
281
|
+
previousEnabledState !== undefined &&
|
|
282
|
+
currentEnabledState !== undefined &&
|
|
283
|
+
previousEnabledState !== currentEnabledState) {
|
|
284
|
+
mediaInit.signals?.onStreamTrackEnabled?.emit(track.track);
|
|
285
|
+
}
|
|
286
|
+
if (previousMuteState !== currentMuteState) {
|
|
287
|
+
mediaInit.signals?.[track.kind === 'audioinput'
|
|
288
|
+
? 'onAudioMuteStateChanged'
|
|
289
|
+
: 'onVideoMuteStateChanged']?.emit(currentMuteState);
|
|
121
290
|
}
|
|
122
|
-
return new Promise(resolve => {
|
|
123
|
-
stopMediaStream(stream);
|
|
124
|
-
resolve();
|
|
125
|
-
});
|
|
126
291
|
};
|
|
127
292
|
return {
|
|
128
|
-
get
|
|
129
|
-
return props.
|
|
293
|
+
get id() {
|
|
294
|
+
return props.stream.id;
|
|
295
|
+
},
|
|
296
|
+
get active() {
|
|
297
|
+
return !!props.stream.active;
|
|
130
298
|
},
|
|
131
299
|
get devices() {
|
|
132
300
|
return props.devices;
|
|
133
301
|
},
|
|
134
|
-
set devices(newDevices) {
|
|
135
|
-
props.devices = newDevices;
|
|
136
|
-
},
|
|
137
302
|
get stream() {
|
|
138
|
-
return
|
|
303
|
+
return props.stream;
|
|
139
304
|
},
|
|
140
305
|
get expectedAudioInput() {
|
|
141
|
-
|
|
142
|
-
return getExpectedAudioInput(props.constraints?.audio, () => ({
|
|
143
|
-
devices: media.devices?.filter(isAudioInput) ?? [],
|
|
144
|
-
input: media.audioInput,
|
|
145
|
-
}));
|
|
306
|
+
return props.audioTrack?.expectedInput;
|
|
146
307
|
},
|
|
147
308
|
get expectedVideoInput() {
|
|
148
|
-
|
|
149
|
-
return getExpectedVideoInput(props.constraints?.video, () => ({
|
|
150
|
-
devices: media.devices?.filter(isVideoInput) ?? [],
|
|
151
|
-
input: media.videoInput,
|
|
152
|
-
}));
|
|
153
|
-
},
|
|
154
|
-
get rawStream() {
|
|
155
|
-
const { rawStream, stream } = getMedia();
|
|
156
|
-
return rawStream ?? stream;
|
|
309
|
+
return props.videoTrack?.expectedInput;
|
|
157
310
|
},
|
|
158
311
|
get audioInput() {
|
|
159
|
-
return
|
|
312
|
+
return props.audioTrack?.input;
|
|
160
313
|
},
|
|
161
314
|
get videoInput() {
|
|
162
|
-
return
|
|
315
|
+
return props.videoTrack?.input;
|
|
163
316
|
},
|
|
164
317
|
get status() {
|
|
165
318
|
return props.status;
|
|
166
319
|
},
|
|
167
320
|
set status(status) {
|
|
168
|
-
props.status
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
props.constraints = value;
|
|
321
|
+
if (props.status !== status) {
|
|
322
|
+
props.status = status;
|
|
323
|
+
mediaInit.signals?.onStatusChanged?.emit(status);
|
|
324
|
+
}
|
|
173
325
|
},
|
|
174
326
|
get audioMuted() {
|
|
175
|
-
return
|
|
327
|
+
return props.audioTrack?.muted;
|
|
176
328
|
},
|
|
177
329
|
get videoMuted() {
|
|
178
|
-
return
|
|
330
|
+
return props.videoTrack?.muted;
|
|
331
|
+
},
|
|
332
|
+
getConstraints,
|
|
333
|
+
getOriginalConstraints() {
|
|
334
|
+
return props.originalConstraints;
|
|
335
|
+
},
|
|
336
|
+
isAudioInputUnavailable() {
|
|
337
|
+
return (mediaInit.permission.audio === 'granted' &&
|
|
338
|
+
props.devices.size('audioinput') === 0);
|
|
339
|
+
},
|
|
340
|
+
isVideoInputUnavailable() {
|
|
341
|
+
return (mediaInit.permission.video === 'granted' &&
|
|
342
|
+
props.devices.size('videoinput') === 0);
|
|
343
|
+
},
|
|
344
|
+
setOriginalConstraints(constraints) {
|
|
345
|
+
props.originalConstraints = constraints;
|
|
346
|
+
},
|
|
347
|
+
requestedAudio() {
|
|
348
|
+
return (Boolean(props.audioTrack?.track) ||
|
|
349
|
+
Boolean(props.originalConstraints.audio));
|
|
350
|
+
},
|
|
351
|
+
requestedVideo() {
|
|
352
|
+
return (Boolean(props.videoTrack?.track) ||
|
|
353
|
+
Boolean(props.originalConstraints.video));
|
|
354
|
+
},
|
|
355
|
+
muteAudio(mute) {
|
|
356
|
+
if (!props.audioTrack) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
muteTrack(props.audioTrack, mute);
|
|
360
|
+
},
|
|
361
|
+
muteVideo(mute) {
|
|
362
|
+
if (!props.videoTrack) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
muteTrack(props.videoTrack, mute);
|
|
179
366
|
},
|
|
180
|
-
muteAudio: muteTrack('audio'),
|
|
181
|
-
muteVideo: muteTrack('video'),
|
|
182
367
|
applyConstraints: async (constraints) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
368
|
+
await Promise.all([
|
|
369
|
+
typeof constraints.audio !== 'object'
|
|
370
|
+
? Promise.resolve()
|
|
371
|
+
: props.audioTrack?.applyConstraints(constraints.audio),
|
|
372
|
+
typeof constraints.video !== 'object'
|
|
373
|
+
? Promise.resolve()
|
|
374
|
+
: props.videoTrack?.applyConstraints(constraints.video),
|
|
375
|
+
]);
|
|
376
|
+
},
|
|
377
|
+
async release() {
|
|
378
|
+
unsubscribe?.();
|
|
379
|
+
unsubscribe = undefined;
|
|
380
|
+
trackSubscriptions.forEach(unsubscribe => unsubscribe());
|
|
381
|
+
trackSubscriptions.clear();
|
|
382
|
+
await Promise.all([
|
|
383
|
+
props.audioTrack?.release(),
|
|
384
|
+
props.videoTrack?.release(),
|
|
385
|
+
]);
|
|
386
|
+
},
|
|
387
|
+
getSettings: () => ({
|
|
388
|
+
audio: props.audioTrack?.getSettings(),
|
|
389
|
+
video: props.videoTrack?.getSettings(),
|
|
390
|
+
}),
|
|
391
|
+
clone() {
|
|
392
|
+
const tracks = [props.audioTrack, props.videoTrack].flatMap(track => {
|
|
393
|
+
if (!track?.source) {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
const cloned = track.source.clone();
|
|
397
|
+
// Restore the enabled state for all cloned track
|
|
398
|
+
cloned.mute(false);
|
|
399
|
+
return cloned;
|
|
400
|
+
});
|
|
401
|
+
const stream = this.stream &&
|
|
402
|
+
new MediaStream(tracks.flatMap(track => (track.track ? [track.track] : [])));
|
|
403
|
+
const clonedMedia = buildMedia({
|
|
404
|
+
tracks,
|
|
405
|
+
devices: this.devices,
|
|
406
|
+
status: this.status,
|
|
407
|
+
stream,
|
|
408
|
+
constraints: this.getConstraints(),
|
|
409
|
+
originalConstraints: this.getOriginalConstraints(),
|
|
410
|
+
permission: mediaInit.permission,
|
|
411
|
+
});
|
|
412
|
+
return clonedMedia;
|
|
413
|
+
},
|
|
414
|
+
getTracks() {
|
|
415
|
+
return [props.audioTrack, props.videoTrack].flatMap(track => track ? [track] : []);
|
|
416
|
+
},
|
|
417
|
+
getAudioTracks() {
|
|
418
|
+
return props.audioTrack ? [props.audioTrack] : [];
|
|
419
|
+
},
|
|
420
|
+
getVideoTracks() {
|
|
421
|
+
return props.videoTrack ? [props.videoTrack] : [];
|
|
422
|
+
},
|
|
423
|
+
addTrack(track) {
|
|
424
|
+
switch (track.kind) {
|
|
425
|
+
case 'audioinput':
|
|
426
|
+
if (track.id === props.audioTrack?.id) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
props.audioTrack = track;
|
|
430
|
+
break;
|
|
431
|
+
case 'videoinput':
|
|
432
|
+
if (track.id === props.videoTrack?.id) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
props.videoTrack = track;
|
|
436
|
+
break;
|
|
437
|
+
default:
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
if (track.track) {
|
|
441
|
+
props.stream.addTrack(track.track);
|
|
442
|
+
if (!trackSubscriptions.has(track.track)) {
|
|
443
|
+
trackSubscriptions.set(track.track, subscribeTrackAndSourceTrackEvents(track));
|
|
444
|
+
}
|
|
445
|
+
mediaInit.signals?.onAddTrack?.emit(track.track);
|
|
446
|
+
props.stream.dispatchEvent(new MediaStreamTrackEvent('addtrack', { track: track.track }));
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
removeTrack(track) {
|
|
450
|
+
let removed = false;
|
|
451
|
+
if (track.id === props.audioTrack?.id) {
|
|
452
|
+
props.audioTrack = undefined;
|
|
453
|
+
removed = true;
|
|
186
454
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
getSettings: () => {
|
|
191
|
-
const { getSettings, stream } = getMedia();
|
|
192
|
-
if (!stream) {
|
|
193
|
-
return {
|
|
194
|
-
audio: [],
|
|
195
|
-
video: [],
|
|
196
|
-
};
|
|
455
|
+
if (track.id === props.videoTrack?.id) {
|
|
456
|
+
props.videoTrack = undefined;
|
|
457
|
+
removed = true;
|
|
197
458
|
}
|
|
198
|
-
if (
|
|
199
|
-
|
|
459
|
+
if (removed && track.track) {
|
|
460
|
+
props.stream.removeTrack(track.track);
|
|
461
|
+
trackSubscriptions.get(track.track)?.();
|
|
462
|
+
trackSubscriptions.delete(track.track);
|
|
463
|
+
mediaInit.signals?.onRemoveTrack?.emit(track.track);
|
|
464
|
+
props.stream.dispatchEvent(new MediaStreamTrackEvent('removetrack', {
|
|
465
|
+
track: track.track,
|
|
466
|
+
}));
|
|
200
467
|
}
|
|
468
|
+
},
|
|
469
|
+
toJSON() {
|
|
201
470
|
return {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
471
|
+
constraints: this.getConstraints(),
|
|
472
|
+
devices: this.devices,
|
|
473
|
+
stream: this.stream,
|
|
474
|
+
audioInput: this.audioInput,
|
|
475
|
+
videoInput: this.videoInput,
|
|
476
|
+
expectedAudioInput: this.expectedAudioInput,
|
|
477
|
+
expectedVideoInput: this.expectedVideoInput,
|
|
478
|
+
status: this.status,
|
|
479
|
+
audioMuted: this.audioMuted,
|
|
480
|
+
videoMuted: this.videoMuted,
|
|
208
481
|
};
|
|
209
482
|
},
|
|
210
|
-
toJSON: () => toJSON(getMedia()),
|
|
211
483
|
};
|
|
212
484
|
};
|
|
213
|
-
/**
|
|
214
|
-
* Clone the media from the rawStream (if any), otherwise, stream
|
|
215
|
-
*/
|
|
216
|
-
export const cloneMedia = async (media) => {
|
|
217
|
-
const stream = (media.rawStream ?? media.stream)?.clone();
|
|
218
|
-
// Restore the enabled state for all cloned track
|
|
219
|
-
stream?.getTracks().forEach(track => (track.enabled = true));
|
|
220
|
-
const { audio, video } = media.getSettings();
|
|
221
|
-
const clonedMedia = buildMedia(() => ({
|
|
222
|
-
stream,
|
|
223
|
-
constraints: media?.constraints,
|
|
224
|
-
devices: media?.devices,
|
|
225
|
-
status: media?.status,
|
|
226
|
-
rawStream: stream,
|
|
227
|
-
audioInput: media?.audioInput,
|
|
228
|
-
videoInput: media?.videoInput,
|
|
229
|
-
getSettings: () => ({ audio, video }),
|
|
230
|
-
}));
|
|
231
|
-
return Promise.resolve(clonedMedia);
|
|
232
|
-
};
|
|
233
485
|
/**
|
|
234
486
|
* Shallow copy the provided object and override with provided overriding
|
|
235
487
|
*
|
|
@@ -246,28 +498,8 @@ export const getDevicesChanges = (prev, next) => {
|
|
|
246
498
|
const trackChanges = createTrackDevicesChanges(prev);
|
|
247
499
|
return trackChanges(next);
|
|
248
500
|
};
|
|
249
|
-
/**
|
|
250
|
-
* Apply Extended constraints on top of the original
|
|
251
|
-
*
|
|
252
|
-
* @param media - The media from the media pipeline
|
|
253
|
-
* @param applyExtended - The function to be called when the previous
|
|
254
|
-
* `applyConstraints` is done
|
|
255
|
-
*/
|
|
256
|
-
export const applyExtendedConstraints = (media, applyExtended) =>
|
|
257
|
-
/**
|
|
258
|
-
* Apply constraints
|
|
259
|
-
* @param constraints - The constraints to be applied to the media
|
|
260
|
-
*/
|
|
261
|
-
async (constraints) => {
|
|
262
|
-
await media.applyConstraints(constraints);
|
|
263
|
-
if (!isOverConstrained(media.status)) {
|
|
264
|
-
await applyExtended(constraints);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
501
|
export const AUDIO_SETTINGS_KEYS = [
|
|
268
502
|
'denoise',
|
|
269
|
-
'vad',
|
|
270
|
-
'asd',
|
|
271
503
|
'contentHint',
|
|
272
504
|
];
|
|
273
505
|
export const VIDEO_SETTINGS_KEYS = [
|
|
@@ -279,8 +511,6 @@ export const VIDEO_SETTINGS_KEYS = [
|
|
|
279
511
|
'edgeBlurAmount',
|
|
280
512
|
'maskCombineRatio',
|
|
281
513
|
'backgroundImageUrl',
|
|
282
|
-
'width',
|
|
283
|
-
'height',
|
|
284
514
|
'contentHint',
|
|
285
515
|
];
|
|
286
516
|
export const MIXING_SETTINGS_KEYS = [
|
|
@@ -314,24 +544,44 @@ export const hasSettingsChanged = (keysToLookFor) => {
|
|
|
314
544
|
return cache.result;
|
|
315
545
|
};
|
|
316
546
|
};
|
|
317
|
-
export const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
547
|
+
export const diffSettings = (keysToLookFor) => {
|
|
548
|
+
const cache = {};
|
|
549
|
+
return (settingsA, settingsB) => {
|
|
550
|
+
// Same as before
|
|
551
|
+
if (cache.settingsA === settingsA && cache.settingsB === settingsB) {
|
|
552
|
+
return cache.diff;
|
|
553
|
+
}
|
|
554
|
+
cache.settingsA = settingsA;
|
|
555
|
+
cache.settingsB = settingsB;
|
|
556
|
+
// Nothing changed
|
|
557
|
+
if (settingsA === settingsB) {
|
|
558
|
+
cache.diff = undefined;
|
|
559
|
+
return cache.diff;
|
|
560
|
+
}
|
|
561
|
+
if (settingsA === undefined || settingsB === undefined) {
|
|
562
|
+
cache.diff = settingsB;
|
|
563
|
+
return cache.diff;
|
|
564
|
+
}
|
|
565
|
+
for (const key of keysToLookFor) {
|
|
566
|
+
if (settingsA[key] !== settingsB[key]) {
|
|
567
|
+
if (cache.diff === undefined) {
|
|
568
|
+
cache.diff = {};
|
|
569
|
+
}
|
|
570
|
+
// @ts-expect-error --- Type issue and need to be fixed when we have time
|
|
571
|
+
cache.diff[key] = settingsB[key];
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return cache.diff;
|
|
330
575
|
};
|
|
331
576
|
};
|
|
332
|
-
export const
|
|
333
|
-
|
|
334
|
-
|
|
577
|
+
export const mergeSettings = (settingsA, settingsB) => {
|
|
578
|
+
if (settingsA === undefined) {
|
|
579
|
+
return settingsB;
|
|
580
|
+
}
|
|
581
|
+
if (settingsB === undefined) {
|
|
582
|
+
return settingsA;
|
|
583
|
+
}
|
|
584
|
+
return { ...settingsA, ...settingsB };
|
|
335
585
|
};
|
|
336
586
|
/**
|
|
337
587
|
* A function to get the blur kernel size of image height
|
|
@@ -370,3 +620,54 @@ export const hasPtzFeature = () => {
|
|
|
370
620
|
hasOwn(supports, 'tilt') &&
|
|
371
621
|
hasOwn(supports, 'zoom'));
|
|
372
622
|
};
|
|
623
|
+
export const refineMediaConstraints = (state) => {
|
|
624
|
+
switch (state.permission) {
|
|
625
|
+
case 'denied':
|
|
626
|
+
return false;
|
|
627
|
+
case 'granted': {
|
|
628
|
+
const requestNeeded = shouldRequestDevice({
|
|
629
|
+
kind: state.kind,
|
|
630
|
+
permission: state.permission,
|
|
631
|
+
currentDevices: state.currentDevices,
|
|
632
|
+
request: state.request,
|
|
633
|
+
tracks: state.currentMediaTracks.flatMap(track => track.source?.track ? [track.source.track] : []),
|
|
634
|
+
});
|
|
635
|
+
if (requestNeeded || state.force) {
|
|
636
|
+
return state.request;
|
|
637
|
+
}
|
|
638
|
+
if (state.request) {
|
|
639
|
+
// We already have all we need, skip the request
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
return state.request;
|
|
643
|
+
}
|
|
644
|
+
case 'prompt':
|
|
645
|
+
default:
|
|
646
|
+
return state.request;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
export const createMediaProcessor = ({ audioProcessors, videoProcessors, onProcessingError, }) => {
|
|
650
|
+
const processAudioTrack = createTrackProcessingPipeline(audioProcessors);
|
|
651
|
+
const processVideoTrack = createTrackProcessingPipeline(videoProcessors);
|
|
652
|
+
return async (newTracks) => {
|
|
653
|
+
const processTrack = async (track) => {
|
|
654
|
+
try {
|
|
655
|
+
switch (track.kind) {
|
|
656
|
+
case 'audioinput':
|
|
657
|
+
return await processAudioTrack(track);
|
|
658
|
+
case 'videoinput':
|
|
659
|
+
return await processVideoTrack(track);
|
|
660
|
+
default:
|
|
661
|
+
assert(false, `Unexpected track kind: ${track.kind}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
if (error instanceof Error) {
|
|
666
|
+
onProcessingError?.(error, track);
|
|
667
|
+
}
|
|
668
|
+
return track;
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
return await Promise.all(newTracks.map(track => processTrack(track)));
|
|
672
|
+
};
|
|
673
|
+
};
|