@pexip/media 20.1.0 → 20.3.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 +79 -0
- package/dist/audioMixingProcessor.d.ts +3 -3
- package/dist/audioMixingProcessor.js +7 -6
- package/dist/audioProcessor.d.ts +5 -4
- package/dist/audioProcessor.js +13 -9
- package/dist/media.d.ts +1 -1
- package/dist/media.js +74 -65
- package/dist/previewController.d.ts +29 -7
- package/dist/previewController.js +118 -19
- package/dist/types.d.ts +51 -48
- package/dist/userMedia.d.ts +9 -4
- package/dist/userMedia.js +67 -21
- package/dist/utils.d.ts +3 -1
- package/dist/utils.js +177 -116
- package/dist/videoProcessor.d.ts +8 -5
- package/dist/videoProcessor.js +94 -53
- package/package.json +6 -6
- package/dist/tsconfig.build.tsbuildinfo +0 -1
package/dist/utils.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare const findExpectedInput: (devices: IndexedDevices, constraints: I
|
|
|
28
28
|
* there is no track to check
|
|
29
29
|
*/
|
|
30
30
|
export declare const isTrackMuted: (track: MediaStreamTrack | undefined) => boolean | undefined;
|
|
31
|
+
export declare const isTrackEnded: (track: MediaStreamTrack) => boolean;
|
|
31
32
|
export declare const buildMedia: (mediaInit: MediaInit, onDevicesChanged?: import("@pexip/signal").Signal<IndexedDevices, IndexedDevices>) => Media;
|
|
32
33
|
/**
|
|
33
34
|
* Shallow copy the provided object and override with provided overriding
|
|
@@ -58,11 +59,12 @@ export declare const mergeSettings: (settingsA: ExtendedMediaTrackSettings | und
|
|
|
58
59
|
* @param percentage - The percentage of image height to calculate the blur
|
|
59
60
|
* kernel size
|
|
60
61
|
* @param height - The image height
|
|
62
|
+
* @param lowestProcessingHeight - The lowest image height that is processed by the consumer, used to adjust the blur kernel size across different resolutions
|
|
61
63
|
* @param max - The upper bound
|
|
62
64
|
*
|
|
63
65
|
* @returns blur kernel size
|
|
64
66
|
*/
|
|
65
|
-
export declare const getBlurKernelSize: (percentage: number, height: number, max?: number) => number;
|
|
67
|
+
export declare const getBlurKernelSize: (percentage: number, height: number, lowestProcessingHeight?: number, max?: number) => number;
|
|
66
68
|
/**
|
|
67
69
|
* Apply the content hint to the track
|
|
68
70
|
*
|
package/dist/utils.js
CHANGED
|
@@ -19,6 +19,10 @@ export const createMediaTrack = (trackInit) => {
|
|
|
19
19
|
(trackInit.kind === trackInit.input?.kind &&
|
|
20
20
|
trackInit.kind === toMediaDeviceInputKind(trackInit.track));
|
|
21
21
|
assert(trackConsistency, `Inconsistent track kind: ${trackInit.input?.kind} ${trackInit.track && toMediaDeviceInputKind(trackInit.track)} vs ${trackInit.kind}`);
|
|
22
|
+
assert(!hasOwn(trackInit, 'mute') ||
|
|
23
|
+
(hasOwn(trackInit, 'mute') && hasOwn(trackInit, 'overrideMute')), 'Inconsistent overrideMute mute()');
|
|
24
|
+
assert(!hasOwn(trackInit, 'muted') ||
|
|
25
|
+
(hasOwn(trackInit, 'muted') && hasOwn(trackInit, 'overrideMute')), 'Inconsistent overrideMute muted');
|
|
22
26
|
const currentConstraints = typeof trackInit.constraints === 'boolean' ? {} : trackInit.constraints;
|
|
23
27
|
// Find the original source track through the linked list
|
|
24
28
|
let sourceMediaTrack = trackInit.previousMediaTrack;
|
|
@@ -41,6 +45,25 @@ export const createMediaTrack = (trackInit) => {
|
|
|
41
45
|
...(trackInit.getSettings?.() ?? {}),
|
|
42
46
|
};
|
|
43
47
|
};
|
|
48
|
+
// Suscribe track events
|
|
49
|
+
let trackUnsubscribe;
|
|
50
|
+
if (trackInit.track) {
|
|
51
|
+
trackUnsubscribe = createStreamTrackEventSubscriptions(trackInit.track, {
|
|
52
|
+
ended(track) {
|
|
53
|
+
assert(track.id === trackInit.track?.id, 'Same Track');
|
|
54
|
+
void mediaTrack.release();
|
|
55
|
+
trackInit.signals?.onMediaTrackStopped?.emit(mediaTrack);
|
|
56
|
+
},
|
|
57
|
+
mute(track) {
|
|
58
|
+
assert(track.id === trackInit.track?.id, 'Same Track');
|
|
59
|
+
trackInit.signals?.onMediaTrackSuspended?.emit(mediaTrack);
|
|
60
|
+
},
|
|
61
|
+
unmute(track) {
|
|
62
|
+
assert(track.id === trackInit.track?.id, 'Same Track');
|
|
63
|
+
trackInit.signals?.onMediaTrackResumed?.emit(mediaTrack);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
44
67
|
const mediaTrack = {
|
|
45
68
|
get kind() {
|
|
46
69
|
return trackInit.kind;
|
|
@@ -52,9 +75,10 @@ export const createMediaTrack = (trackInit) => {
|
|
|
52
75
|
return trackInit.track;
|
|
53
76
|
},
|
|
54
77
|
get input() {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
if (!trackInit.track) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return trackInit.input;
|
|
58
82
|
},
|
|
59
83
|
get expectedInput() {
|
|
60
84
|
return trackInit.expectedInput;
|
|
@@ -69,33 +93,53 @@ export const createMediaTrack = (trackInit) => {
|
|
|
69
93
|
if (trackInit.overrideMute) {
|
|
70
94
|
return trackInit.muted;
|
|
71
95
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
if (trackInit.track) {
|
|
97
|
+
return !trackInit.track.enabled;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
},
|
|
101
|
+
get stopped() {
|
|
102
|
+
return trackInit.track && isTrackEnded(trackInit.track);
|
|
103
|
+
},
|
|
104
|
+
get suspended() {
|
|
105
|
+
// Any track that is suspended in the chain will cause this track to be suspended
|
|
106
|
+
return (trackInit.previousMediaTrack?.suspended ||
|
|
107
|
+
trackInit.track?.muted);
|
|
77
108
|
},
|
|
78
109
|
get source() {
|
|
79
|
-
return sourceMediaTrack ??
|
|
110
|
+
return sourceMediaTrack ?? this;
|
|
80
111
|
},
|
|
81
|
-
mute(toMute) {
|
|
112
|
+
mute(toMute, soft) {
|
|
113
|
+
if (this.muted === toMute) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
82
116
|
if (trackInit.overrideMute) {
|
|
83
|
-
|
|
117
|
+
trackInit.mute?.(toMute, this, soft);
|
|
84
118
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
119
|
+
else {
|
|
120
|
+
if (trackInit.track) {
|
|
121
|
+
trackInit.track.enabled = !toMute;
|
|
122
|
+
}
|
|
123
|
+
trackInit.previousMediaTrack?.mute(toMute, soft);
|
|
88
124
|
}
|
|
125
|
+
trackInit.signals?.onMediaTrackMuted?.emit(this);
|
|
89
126
|
},
|
|
90
127
|
getSettings,
|
|
91
128
|
getConstraints() {
|
|
92
129
|
return getConstraints();
|
|
93
130
|
},
|
|
94
|
-
clone(signals) {
|
|
131
|
+
clone(signals, label) {
|
|
95
132
|
return createMediaTrack({
|
|
96
|
-
|
|
133
|
+
kind: this.kind,
|
|
134
|
+
label: [label, this.label]
|
|
135
|
+
.flatMap(a => (a ? [a] : []))
|
|
136
|
+
.join('|'),
|
|
137
|
+
input: this.input,
|
|
97
138
|
constraints: getConstraints(),
|
|
98
|
-
track:
|
|
139
|
+
track: this.track?.clone(),
|
|
140
|
+
// When Cloning a MediaTrack, overrideMute should be turned off since
|
|
141
|
+
// it might corrupt the logic of its original media pipeline
|
|
142
|
+
overrideMute: false,
|
|
99
143
|
signals,
|
|
100
144
|
});
|
|
101
145
|
},
|
|
@@ -135,23 +179,20 @@ export const createMediaTrack = (trackInit) => {
|
|
|
135
179
|
Object.assign(currentConstraints, resolvedConstraints);
|
|
136
180
|
},
|
|
137
181
|
async release() {
|
|
182
|
+
trackUnsubscribe?.();
|
|
138
183
|
trackInit.track?.stop();
|
|
139
|
-
if (trackInit.track) {
|
|
140
|
-
trackInit.signals?.onTrackReleased?.emit(trackInit.track);
|
|
141
|
-
trackInit.signals?.[trackInit.track.kind === 'audio'
|
|
142
|
-
? 'onAudioMuteStateChanged'
|
|
143
|
-
: 'onVideoMuteStateChanged']?.emit(undefined);
|
|
144
|
-
}
|
|
145
|
-
await trackInit.previousMediaTrack?.release();
|
|
146
184
|
await trackInit.release?.();
|
|
185
|
+
await trackInit.previousMediaTrack?.release();
|
|
186
|
+
trackInit.signals?.onMediaTrackStopped?.emit(this);
|
|
147
187
|
},
|
|
148
188
|
toJSON() {
|
|
149
189
|
return {
|
|
150
|
-
label:
|
|
151
|
-
track:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
190
|
+
label: this.label,
|
|
191
|
+
track: this.track,
|
|
192
|
+
input: this.input,
|
|
193
|
+
previousMediaTrack: this.previousMediaTrack,
|
|
194
|
+
constraints: this.getConstraints(),
|
|
195
|
+
settings: this.getSettings(),
|
|
155
196
|
};
|
|
156
197
|
},
|
|
157
198
|
};
|
|
@@ -161,7 +202,7 @@ export const createTrackProcessingPipeline = (processors) => async (track) => {
|
|
|
161
202
|
let lastTrack = track;
|
|
162
203
|
for (const process of processors) {
|
|
163
204
|
if (lastTrack.track?.readyState !== 'live') {
|
|
164
|
-
|
|
205
|
+
continue;
|
|
165
206
|
}
|
|
166
207
|
lastTrack = await process(lastTrack);
|
|
167
208
|
}
|
|
@@ -208,17 +249,22 @@ export const findExpectedInput = (devices, constraints, input, kind) => {
|
|
|
208
249
|
* there is no track to check
|
|
209
250
|
*/
|
|
210
251
|
export const isTrackMuted = (track) => {
|
|
211
|
-
if (!track
|
|
252
|
+
if (!track) {
|
|
212
253
|
return undefined;
|
|
213
254
|
}
|
|
214
255
|
return track.muted || !track.enabled;
|
|
215
256
|
};
|
|
257
|
+
export const isTrackEnded = (track) => track.readyState === 'ended';
|
|
216
258
|
export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevicesChanged) => {
|
|
217
259
|
const props = {
|
|
218
260
|
originalConstraints: mediaInit.originalConstraints ?? mediaInit.constraints,
|
|
219
261
|
status: mediaInit.status,
|
|
220
262
|
devices: mediaInit.devices,
|
|
221
263
|
stream: mediaInit.stream ?? new MediaStream(),
|
|
264
|
+
// `undefined` is used to avoid double state overriding,
|
|
265
|
+
// because 2 subsequent `susppend` events could invalidate the previous assignment
|
|
266
|
+
audioAlreadyMuted: undefined,
|
|
267
|
+
videoAlreadyMuted: undefined,
|
|
222
268
|
audioTrack: mediaInit.tracks
|
|
223
269
|
.flatMap(track => {
|
|
224
270
|
return track.kind === 'audioinput' ? [track] : [];
|
|
@@ -228,74 +274,77 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
228
274
|
.flatMap(track => (track.kind === 'videoinput' ? [track] : []))
|
|
229
275
|
.at(0),
|
|
230
276
|
};
|
|
231
|
-
const subscribeTrackEvents = (track) => createStreamTrackEventSubscriptions(track, {
|
|
232
|
-
ended: track => {
|
|
233
|
-
mediaInit.signals?.[track.kind === 'audio'
|
|
234
|
-
? 'onAudioMuteStateChanged'
|
|
235
|
-
: 'onVideoMuteStateChanged']?.emit(undefined);
|
|
236
|
-
mediaInit.signals?.onStreamTrackEndedFinal?.emit(track);
|
|
237
|
-
},
|
|
238
|
-
mute: track => {
|
|
239
|
-
mediaInit.signals?.[track.kind === 'audio'
|
|
240
|
-
? 'onAudioMuteStateChanged'
|
|
241
|
-
: 'onVideoMuteStateChanged']?.emit(true);
|
|
242
|
-
mediaInit.signals?.onStreamTrackMuted?.emit(track);
|
|
243
|
-
},
|
|
244
|
-
unmute: track => {
|
|
245
|
-
mediaInit.signals?.[track.kind === 'audio'
|
|
246
|
-
? 'onAudioMuteStateChanged'
|
|
247
|
-
: 'onVideoMuteStateChanged']?.emit(!track.enabled);
|
|
248
|
-
mediaInit.signals?.onStreamTrackUnmuted?.emit(track);
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
const subscribeTrackAndSourceTrackEvents = (track) => {
|
|
252
|
-
let trackUnsubscribe;
|
|
253
|
-
let sourceTrackUnsubscribe;
|
|
254
|
-
if (track.track) {
|
|
255
|
-
trackUnsubscribe = subscribeTrackEvents(track.track);
|
|
256
|
-
if (track.source.track && track.source.track !== track.track) {
|
|
257
|
-
sourceTrackUnsubscribe = subscribeTrackEvents(track.source.track);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return () => {
|
|
261
|
-
trackUnsubscribe?.();
|
|
262
|
-
sourceTrackUnsubscribe?.();
|
|
263
|
-
};
|
|
264
|
-
};
|
|
265
|
-
const trackSubscriptions = new Map(mediaInit.tracks.flatMap(track => {
|
|
266
|
-
if (track.track) {
|
|
267
|
-
return [
|
|
268
|
-
[track.track, subscribeTrackAndSourceTrackEvents(track)],
|
|
269
|
-
];
|
|
270
|
-
}
|
|
271
|
-
return [];
|
|
272
|
-
}));
|
|
273
277
|
const getConstraints = () => {
|
|
274
278
|
const audio = props.audioTrack?.getConstraints() ?? mediaInit.constraints.audio;
|
|
275
279
|
const video = props.videoTrack?.getConstraints() ?? mediaInit.constraints.video;
|
|
276
280
|
return { audio: audio, video: video };
|
|
277
281
|
};
|
|
278
|
-
|
|
282
|
+
const updateDevices = (devices) => {
|
|
279
283
|
props.devices = devices;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
284
|
+
};
|
|
285
|
+
// Two-hand control to avoid leaking of the media
|
|
286
|
+
// When the track is suspended by the system, we also mute it if it is not already muted.
|
|
287
|
+
// As it is possible that the track could still flow data even if it is system muted
|
|
288
|
+
const handleSuspended = (track) => {
|
|
289
|
+
switch (track.kind) {
|
|
290
|
+
case 'audioinput': {
|
|
291
|
+
if (props.audioAlreadyMuted === undefined) {
|
|
292
|
+
props.audioAlreadyMuted = props.audioTrack?.muted === true;
|
|
293
|
+
}
|
|
294
|
+
if (!props.audioAlreadyMuted) {
|
|
295
|
+
props.audioTrack?.mute(true, true);
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case 'videoinput': {
|
|
300
|
+
if (props.videoAlreadyMuted === undefined) {
|
|
301
|
+
props.videoAlreadyMuted = props.videoTrack?.muted === true;
|
|
302
|
+
}
|
|
303
|
+
if (!props.videoAlreadyMuted) {
|
|
304
|
+
props.videoTrack?.mute(true, true);
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
292
308
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
309
|
+
};
|
|
310
|
+
// When the track is resumed by the system, we only unmute the track
|
|
311
|
+
// if an only if it is muted by the suspended event
|
|
312
|
+
const handleResumed = (track) => {
|
|
313
|
+
switch (track.kind) {
|
|
314
|
+
case 'audioinput': {
|
|
315
|
+
assert(props.audioAlreadyMuted !== undefined, 'Audio Resume should not be triggerred before Suspended');
|
|
316
|
+
if (props.audioTrack?.suspended === false) {
|
|
317
|
+
if (!props.audioAlreadyMuted) {
|
|
318
|
+
props.audioTrack.mute(false);
|
|
319
|
+
}
|
|
320
|
+
props.audioAlreadyMuted = undefined;
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case 'videoinput': {
|
|
325
|
+
assert(props.videoAlreadyMuted !== undefined, 'Video Resume should not be triggerred before Suspended');
|
|
326
|
+
if (props.videoTrack?.suspended === false) {
|
|
327
|
+
if (!props.videoAlreadyMuted) {
|
|
328
|
+
props.videoTrack.mute(false);
|
|
329
|
+
}
|
|
330
|
+
props.videoAlreadyMuted = undefined;
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
297
334
|
}
|
|
298
335
|
};
|
|
336
|
+
const subscribe = () => [
|
|
337
|
+
onDevicesChanged.add(updateDevices),
|
|
338
|
+
mediaInit.signals?.onMediaTrackSuspended?.add(handleSuspended),
|
|
339
|
+
mediaInit.signals?.onMediaTrackResumed?.add(handleResumed),
|
|
340
|
+
];
|
|
341
|
+
let subscriptions = subscribe();
|
|
342
|
+
const unsubscribe = () => {
|
|
343
|
+
for (const unsubscribe of subscriptions) {
|
|
344
|
+
unsubscribe?.();
|
|
345
|
+
}
|
|
346
|
+
subscriptions.length = 0;
|
|
347
|
+
};
|
|
299
348
|
return {
|
|
300
349
|
get id() {
|
|
301
350
|
return props.stream.id;
|
|
@@ -360,16 +409,16 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
360
409
|
props.devices.size('videoinput') > 0));
|
|
361
410
|
},
|
|
362
411
|
muteAudio(mute) {
|
|
363
|
-
if (!props.audioTrack) {
|
|
412
|
+
if (!props.audioTrack || props.audioTrack.suspended) {
|
|
364
413
|
return;
|
|
365
414
|
}
|
|
366
|
-
|
|
415
|
+
props.audioTrack.mute(mute);
|
|
367
416
|
},
|
|
368
417
|
muteVideo(mute) {
|
|
369
|
-
if (!props.videoTrack) {
|
|
418
|
+
if (!props.videoTrack || props.videoTrack.suspended) {
|
|
370
419
|
return;
|
|
371
420
|
}
|
|
372
|
-
|
|
421
|
+
props.videoTrack?.mute(mute);
|
|
373
422
|
},
|
|
374
423
|
applyConstraints: async (constraints) => {
|
|
375
424
|
await Promise.all([
|
|
@@ -382,12 +431,7 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
382
431
|
]);
|
|
383
432
|
},
|
|
384
433
|
async release() {
|
|
385
|
-
unsubscribe
|
|
386
|
-
unsubscribe = undefined;
|
|
387
|
-
for (const unsubscribe of trackSubscriptions.values()) {
|
|
388
|
-
unsubscribe();
|
|
389
|
-
}
|
|
390
|
-
trackSubscriptions.clear();
|
|
434
|
+
unsubscribe();
|
|
391
435
|
await Promise.all([
|
|
392
436
|
props.audioTrack?.release(),
|
|
393
437
|
props.videoTrack?.release(),
|
|
@@ -397,26 +441,25 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
397
441
|
audio: props.audioTrack?.getSettings(),
|
|
398
442
|
video: props.videoTrack?.getSettings(),
|
|
399
443
|
}),
|
|
400
|
-
clone(signals) {
|
|
444
|
+
clone(signals, label) {
|
|
401
445
|
const tracks = [props.audioTrack, props.videoTrack].flatMap(track => {
|
|
402
446
|
if (!track?.source) {
|
|
403
447
|
return [];
|
|
404
448
|
}
|
|
405
|
-
const cloned = track.source.clone(signals);
|
|
406
|
-
|
|
407
|
-
cloned.mute(false);
|
|
408
|
-
return cloned;
|
|
449
|
+
const cloned = track.source.clone(signals, label);
|
|
450
|
+
return [cloned];
|
|
409
451
|
});
|
|
410
452
|
const stream = this.stream &&
|
|
411
453
|
new MediaStream(tracks.flatMap(track => (track.track ? [track.track] : [])));
|
|
412
454
|
const clonedMedia = buildMedia({
|
|
413
|
-
tracks,
|
|
414
|
-
devices: this.devices,
|
|
415
|
-
status: this.status,
|
|
416
|
-
stream,
|
|
417
455
|
constraints: this.getConstraints(),
|
|
456
|
+
devices: this.devices,
|
|
418
457
|
originalConstraints: this.getOriginalConstraints(),
|
|
419
458
|
permission: mediaInit.permission,
|
|
459
|
+
signals,
|
|
460
|
+
status: this.status,
|
|
461
|
+
stream,
|
|
462
|
+
tracks,
|
|
420
463
|
});
|
|
421
464
|
return clonedMedia;
|
|
422
465
|
},
|
|
@@ -429,6 +472,9 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
429
472
|
getVideoTracks() {
|
|
430
473
|
return props.videoTrack ? [props.videoTrack] : [];
|
|
431
474
|
},
|
|
475
|
+
isCurrentTrack(track) {
|
|
476
|
+
return track === props.audioTrack || track === props.videoTrack;
|
|
477
|
+
},
|
|
432
478
|
addTrack(track) {
|
|
433
479
|
switch (track.kind) {
|
|
434
480
|
case 'audioinput':
|
|
@@ -446,13 +492,22 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
446
492
|
default:
|
|
447
493
|
break;
|
|
448
494
|
}
|
|
495
|
+
if (subscriptions.length === 0) {
|
|
496
|
+
subscriptions = subscribe();
|
|
497
|
+
}
|
|
449
498
|
if (track.track) {
|
|
450
499
|
props.stream.addTrack(track.track);
|
|
451
|
-
if (!trackSubscriptions.has(track.track)) {
|
|
452
|
-
trackSubscriptions.set(track.track, subscribeTrackAndSourceTrackEvents(track));
|
|
453
|
-
}
|
|
454
500
|
mediaInit.signals?.onAddTrack?.emit(track.track);
|
|
455
501
|
props.stream.dispatchEvent(new MediaStreamTrackEvent('addtrack', { track: track.track }));
|
|
502
|
+
if (track.muted) {
|
|
503
|
+
mediaInit.signals?.onMediaTrackMuted?.emit(track);
|
|
504
|
+
}
|
|
505
|
+
if (track.suspended) {
|
|
506
|
+
mediaInit.signals?.onMediaTrackSuspended?.emit(track);
|
|
507
|
+
}
|
|
508
|
+
if (track.stopped) {
|
|
509
|
+
mediaInit.signals?.onMediaTrackStopped?.emit(track);
|
|
510
|
+
}
|
|
456
511
|
}
|
|
457
512
|
},
|
|
458
513
|
removeTrack(track) {
|
|
@@ -467,13 +522,14 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
467
522
|
}
|
|
468
523
|
if (removed && track.track) {
|
|
469
524
|
props.stream.removeTrack(track.track);
|
|
470
|
-
trackSubscriptions.get(track.track)?.();
|
|
471
|
-
trackSubscriptions.delete(track.track);
|
|
472
525
|
mediaInit.signals?.onRemoveTrack?.emit(track.track);
|
|
473
526
|
props.stream.dispatchEvent(new MediaStreamTrackEvent('removetrack', {
|
|
474
527
|
track: track.track,
|
|
475
528
|
}));
|
|
476
529
|
}
|
|
530
|
+
if (subscriptions.length) {
|
|
531
|
+
unsubscribe();
|
|
532
|
+
}
|
|
477
533
|
},
|
|
478
534
|
toJSON() {
|
|
479
535
|
return {
|
|
@@ -634,15 +690,20 @@ export const mergeSettings = (settingsA, settingsB) => {
|
|
|
634
690
|
* @param percentage - The percentage of image height to calculate the blur
|
|
635
691
|
* kernel size
|
|
636
692
|
* @param height - The image height
|
|
693
|
+
* @param lowestProcessingHeight - The lowest image height that is processed by the consumer, used to adjust the blur kernel size across different resolutions
|
|
637
694
|
* @param max - The upper bound
|
|
638
695
|
*
|
|
639
696
|
* @returns blur kernel size
|
|
640
697
|
*/
|
|
641
|
-
export const getBlurKernelSize = (percentage, height, max = calculateMaxBlurPass(height)) => {
|
|
698
|
+
export const getBlurKernelSize = (percentage, height, lowestProcessingHeight, max = calculateMaxBlurPass(height)) => {
|
|
642
699
|
if (height <= 0 || percentage <= 0 || max <= 0) {
|
|
643
700
|
return 0;
|
|
644
701
|
}
|
|
645
|
-
|
|
702
|
+
const minHeightMaxBlurPass = lowestProcessingHeight
|
|
703
|
+
? calculateMaxBlurPass(lowestProcessingHeight)
|
|
704
|
+
: max;
|
|
705
|
+
return Math.min(Math.ceil(percentage * 0.01 * max) +
|
|
706
|
+
Math.max(0, max - minHeightMaxBlurPass), max);
|
|
646
707
|
};
|
|
647
708
|
/**
|
|
648
709
|
* Apply the content hint to the track
|
package/dist/videoProcessor.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RenderBackend, SegmentationModel, SegmentationTransform, VideoProcessor } from '@pexip/media-processor';
|
|
2
2
|
import type { MediaDeviceRequest } from '@pexip/media-control';
|
|
3
|
-
import type { TrackProcessor,
|
|
3
|
+
import type { MediaSignals, Segmenters, TrackProcessor, VideoContentHint, VideoRenderParams, VideoStreamTrackProcessorAPIs } from './types';
|
|
4
4
|
interface ProcessorDeps {
|
|
5
|
-
videoProcessor?: () => VideoProcessor;
|
|
6
|
-
transformer?: SegmentationTransform;
|
|
7
5
|
segmenters: Partial<Segmenters>;
|
|
6
|
+
transformer?: SegmentationTransform;
|
|
7
|
+
videoProcessor?: () => VideoProcessor;
|
|
8
8
|
videoSegmentationModel?: SegmentationModel;
|
|
9
9
|
}
|
|
10
10
|
interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<ProcessorDeps, 'videoProcessor'> {
|
|
@@ -18,14 +18,17 @@ interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<Pro
|
|
|
18
18
|
* Whether or to enable this processor
|
|
19
19
|
*/
|
|
20
20
|
shouldEnable: () => boolean;
|
|
21
|
+
lowestProcessingHeight?: number;
|
|
21
22
|
processingWidth: number;
|
|
22
23
|
processingHeight: number;
|
|
23
24
|
hasInitializedDeps?: boolean;
|
|
24
25
|
width?: number;
|
|
25
26
|
height?: number;
|
|
26
27
|
label?: string;
|
|
28
|
+
stopAsMute?: () => boolean;
|
|
27
29
|
dynamicProcessingDimensions?: () => boolean;
|
|
28
30
|
gpuAPI?: () => RenderBackend;
|
|
31
|
+
signals?: MediaSignals;
|
|
29
32
|
}
|
|
30
33
|
interface VideoStreamProcessProps extends Partial<VideoRenderParams>, Required<ProcessorDeps> {
|
|
31
34
|
hasInitialized: boolean;
|
|
@@ -35,5 +38,5 @@ declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUr
|
|
|
35
38
|
type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
|
|
36
39
|
type FeatureProps = Pick<Partial<VideoStreamProcessProps>, FeaturePropKeys>;
|
|
37
40
|
export declare const updateFeatureProps: (constraints: MediaDeviceRequest["video"], props: FeatureProps) => FeatureProps;
|
|
38
|
-
export declare const createVideoStreamProcess: ({
|
|
41
|
+
export declare const createVideoStreamProcess: ({ backgroundImageUrl, dynamicProcessingDimensions, edgeBlurAmount, foregroundThreshold, frameRate, gpuAPI, label, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute, trackProcessorAPI, videoSegmentation, signals, ...options }: VideoStreamProcessOptions) => TrackProcessor;
|
|
39
42
|
export {};
|