@pexip/media 20.2.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 +43 -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.js +9 -37
- package/dist/previewController.d.ts +8 -3
- package/dist/previewController.js +27 -16
- package/dist/types.d.ts +43 -46
- package/dist/userMedia.js +24 -9
- package/dist/utils.js +152 -107
- package/dist/videoProcessor.d.ts +6 -5
- package/dist/videoProcessor.js +56 -31
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @pexip/media
|
|
2
2
|
|
|
3
|
+
## 20.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 69d6be4: Refactor `MediaTrack` to decouple system/hardware mute from mute
|
|
8
|
+
|
|
9
|
+
- Add `stopped` attributes to `MediaTrack` to indicate the track `live` or
|
|
10
|
+
`ended`
|
|
11
|
+
- Add `suspended` attributes to
|
|
12
|
+
`MediaTrack to indicate the track is being system/hardware muted`
|
|
13
|
+
- Add signal supports to `MediaTrack` to be able to subscribe track related
|
|
14
|
+
signals
|
|
15
|
+
- Implement Two-hand control to secure mute consistency
|
|
16
|
+
|
|
17
|
+
When there is `muted` event from `MediaStreamTrack`, the track will follow
|
|
18
|
+
the event to set the `enabled` attribute to `false` to avoid any
|
|
19
|
+
possibilities of leaking the local media to remote parties. When the
|
|
20
|
+
`unmuted` event is triggered, it depends on whether the `enabled` attribute
|
|
21
|
+
is set before the `muted` event or not to unmute the track.
|
|
22
|
+
|
|
23
|
+
Breaking changes:
|
|
24
|
+
|
|
25
|
+
- Replace `MediaStreamTrack` related signal with the new `MediaTrack` signals
|
|
26
|
+
|
|
27
|
+
- `onStreamTrackEnded` -> `onMediaTrackStopped`
|
|
28
|
+
- `onStreamTrackMuted` -> `onMediaTrackSuspended`
|
|
29
|
+
- `onStreamTrackUnmuted` -> `onMediaTrackResumed`
|
|
30
|
+
- `onStreamTrackEnabled` -> `onMediaTrackMuted`
|
|
31
|
+
- `onAudioMuteStateChanged` -> `onMediaTrackMuted`
|
|
32
|
+
- `onVideoMuteStateChanged` -> `onMediaTrackMuted`
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- f8a8159: Fix Subsequant mute/unmute events handling
|
|
37
|
+
- e347770: Handle suspended state fro preview controller
|
|
38
|
+
- 0a375c7: Delay stopping video track to ensure last frame being black
|
|
39
|
+
- Updated dependencies [69d6be4]
|
|
40
|
+
- Updated dependencies [adc1a50]
|
|
41
|
+
- @pexip/media-control@20.3.0
|
|
42
|
+
- @pexip/utils@17.0.1
|
|
43
|
+
- @pexip/media-processor@20.3.0
|
|
44
|
+
- @pexip/signal@16.9.0
|
|
45
|
+
|
|
3
46
|
## 20.2.0
|
|
4
47
|
|
|
5
48
|
### Minor Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MediaDeviceRequest } from '@pexip/media-control';
|
|
2
2
|
import type { AudioGraph, AudioNodeInit } from '@pexip/media-processor';
|
|
3
|
-
import type { TrackProcessor } from './types';
|
|
3
|
+
import type { MediaSignals, TrackProcessor } from './types';
|
|
4
4
|
interface AudioStreamProcessorProps {
|
|
5
5
|
mixWithAdditionalMedia?: boolean;
|
|
6
6
|
merger?: AudioNodeInit<ChannelMergerNode, ChannelMergerNode>;
|
|
@@ -12,7 +12,7 @@ type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
|
|
|
12
12
|
type FeatureProps = Pick<AudioStreamProcessorProps, FeaturePropKeys>;
|
|
13
13
|
export declare const updateFeatureProps: (constraints: MediaDeviceRequest["audio"], props: FeatureProps) => FeatureProps;
|
|
14
14
|
/**
|
|
15
|
-
* Create
|
|
15
|
+
* Create an Audio Mixing Processor and will own the stream passed-in
|
|
16
16
|
*/
|
|
17
|
-
export declare const createAudioMixingProcess: (getCurrentMedia: () => MediaStream | undefined, label?: "AudioMixingProcessor") => TrackProcessor;
|
|
17
|
+
export declare const createAudioMixingProcess: (getCurrentMedia: () => MediaStream | undefined, signals?: MediaSignals, label?: "AudioMixingProcessor") => TrackProcessor;
|
|
18
18
|
export {};
|
|
@@ -2,7 +2,7 @@ import { extractConstraintsWithKeys } from '@pexip/media-control';
|
|
|
2
2
|
import { isEmpty, assert } from '@pexip/utils';
|
|
3
3
|
import { createAudioGraph, createAudioGraphProxy, createChannelMergerGraphNode, createStreamDestinationGraphNode, createStreamSourceGraphNode, resumeAudioOnUnmute, } from '@pexip/media-processor';
|
|
4
4
|
import { logger } from './logger';
|
|
5
|
-
import { createMediaTrack
|
|
5
|
+
import { createMediaTrack } from './utils';
|
|
6
6
|
import { PROCESSOR_LABELS } from './constants';
|
|
7
7
|
const FEATURE_KEYS = ['mixWithAdditionalMedia'];
|
|
8
8
|
const getAudioConstraints = extractConstraintsWithKeys(FEATURE_KEYS);
|
|
@@ -19,9 +19,9 @@ export const updateFeatureProps = (constraints, props) => {
|
|
|
19
19
|
}, {});
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
22
|
-
* Create
|
|
22
|
+
* Create an Audio Mixing Processor and will own the stream passed-in
|
|
23
23
|
*/
|
|
24
|
-
export const createAudioMixingProcess = (getCurrentMedia, label = PROCESSOR_LABELS.AudioMixingProcessor) => {
|
|
24
|
+
export const createAudioMixingProcess = (getCurrentMedia, signals, label = PROCESSOR_LABELS.AudioMixingProcessor) => {
|
|
25
25
|
const props = {};
|
|
26
26
|
return async (prevMediaTrack) => {
|
|
27
27
|
updateFeatureProps(prevMediaTrack.getConstraints(), props);
|
|
@@ -154,12 +154,12 @@ export const createAudioMixingProcess = (getCurrentMedia, label = PROCESSOR_LABE
|
|
|
154
154
|
expectedInput: prevMediaTrack.expectedInput,
|
|
155
155
|
track,
|
|
156
156
|
overrideMute: true,
|
|
157
|
-
mute: toMute => {
|
|
158
|
-
prevMediaTrack.mute(toMute);
|
|
157
|
+
mute: (toMute, _self, soft) => {
|
|
159
158
|
for (const track of mainSource.node?.mediaStream.getAudioTracks() ??
|
|
160
159
|
[]) {
|
|
161
160
|
track.enabled = !toMute;
|
|
162
161
|
}
|
|
162
|
+
prevMediaTrack.mute(toMute, soft);
|
|
163
163
|
},
|
|
164
164
|
get muted() {
|
|
165
165
|
return (
|
|
@@ -168,11 +168,12 @@ export const createAudioMixingProcess = (getCurrentMedia, label = PROCESSOR_LABE
|
|
|
168
168
|
!!prevMediaTrack.muted ||
|
|
169
169
|
mainSource.node?.mediaStream
|
|
170
170
|
.getAudioTracks()
|
|
171
|
-
.some(
|
|
171
|
+
.some(track => track.enabled === false));
|
|
172
172
|
},
|
|
173
173
|
constraints: prevMediaTrack.getConstraints(),
|
|
174
174
|
applyConstraints,
|
|
175
175
|
release,
|
|
176
|
+
signals,
|
|
176
177
|
getSettings: () => {
|
|
177
178
|
const mixWithAdditionalMedia = !!props.mixWithAdditionalMedia;
|
|
178
179
|
const contentHint = track.contentHint ?? '';
|
package/dist/audioProcessor.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnalyzerNodeInit, AudioGraph, AudioGraphOptions, AudioNodeInit, DenoiseWorkletNodeInit, ThrottleOptions } from '@pexip/media-processor';
|
|
2
2
|
import type { MediaDeviceRequest } from '@pexip/media-control';
|
|
3
|
-
import type {
|
|
3
|
+
import type { AudioContentHint, DenoiseParams, MediaSignals, TrackProcessor } from './types';
|
|
4
4
|
type AudioNodeInits = AudioNodeInit[];
|
|
5
5
|
/**
|
|
6
6
|
* A function to be called to create the AudioNodes needed for the graph
|
|
@@ -60,6 +60,7 @@ interface AudioProcessOptions {
|
|
|
60
60
|
*/
|
|
61
61
|
silentThreshold?: number;
|
|
62
62
|
label?: string;
|
|
63
|
+
signals?: MediaSignals;
|
|
63
64
|
}
|
|
64
65
|
interface AudioStreamProcessorProps {
|
|
65
66
|
audioGraphOptions?: AudioGraphOptions;
|
|
@@ -80,7 +81,7 @@ type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
|
|
|
80
81
|
type FeatureProps = Pick<AudioStreamProcessorProps, FeaturePropKeys>;
|
|
81
82
|
export declare const updateFeatureProps: (constraints: MediaDeviceRequest["audio"], props: FeatureProps) => FeatureProps;
|
|
82
83
|
/**
|
|
83
|
-
* Create
|
|
84
|
+
* Create an Audio Stream Processor and will own the stream passed-in
|
|
84
85
|
*/
|
|
85
|
-
export declare const createAudioStreamProcess: ({ analyzerUpdateFrequency, audioGraphOptions, audioSignalDetectionDuration, clock, createNodes, denoiseParams, fftSize, onAudioSignalDetected, onVoiceActivityDetected, shouldEnable, silentThreshold, throttleMs, label, }: AudioProcessOptions & ThrottleOptions) => TrackProcessor;
|
|
86
|
+
export declare const createAudioStreamProcess: ({ analyzerUpdateFrequency, audioGraphOptions, audioSignalDetectionDuration, clock, createNodes, denoiseParams, fftSize, onAudioSignalDetected, onVoiceActivityDetected, shouldEnable, signals, silentThreshold, throttleMs, label, }: AudioProcessOptions & ThrottleOptions) => TrackProcessor;
|
|
86
87
|
export {};
|
package/dist/audioProcessor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extractConstraintsWithKeys } from '@pexip/media-control';
|
|
2
2
|
import { createQueue, isEmpty, assert } from '@pexip/utils';
|
|
3
|
-
import {
|
|
3
|
+
import { avg, createAnalyzerSubscribableGraphNode, createAudioGraph, createAudioGraphProxy, createAudioSignalDetector, createDenoiseWorkletGraphNode, createStreamDestinationGraphNode, createStreamSourceGraphNode, createVADetector, createVoiceDetectorFromProbability, createVoiceDetectorFromTimeData, } from '@pexip/media-processor';
|
|
4
4
|
import { PROCESSOR_LABELS } from './constants';
|
|
5
5
|
import { logger } from './logger';
|
|
6
6
|
import { isAudioContentHint } from './typeGuard';
|
|
@@ -54,12 +54,12 @@ export const updateFeatureProps = (constraints, props) => {
|
|
|
54
54
|
}, {});
|
|
55
55
|
};
|
|
56
56
|
/**
|
|
57
|
-
* Create
|
|
57
|
+
* Create an Audio Stream Processor and will own the stream passed-in
|
|
58
58
|
*/
|
|
59
59
|
export const createAudioStreamProcess = ({ analyzerUpdateFrequency = 0.5, // 0.5 Hz
|
|
60
60
|
audioGraphOptions, audioSignalDetectionDuration = 4.0, // 4 seconds
|
|
61
61
|
clock, createNodes, denoiseParams, fftSize = 2048, // FFT size
|
|
62
|
-
onAudioSignalDetected, onVoiceActivityDetected, shouldEnable, silentThreshold = 10.0 / 32767, // At least one LSB 16-bit data (compare is on absolute value).
|
|
62
|
+
onAudioSignalDetected, onVoiceActivityDetected, shouldEnable, signals, silentThreshold = 10.0 / 32767, // At least one LSB 16-bit data (compare is on absolute value).
|
|
63
63
|
throttleMs = 3000, // 3 seconds
|
|
64
64
|
label = PROCESSOR_LABELS.AudioProcessor, }) => {
|
|
65
65
|
const props = {
|
|
@@ -198,11 +198,6 @@ label = PROCESSOR_LABELS.AudioProcessor, }) => {
|
|
|
198
198
|
props.analyzer = undefined;
|
|
199
199
|
props.audioGraph = undefined;
|
|
200
200
|
};
|
|
201
|
-
const mute = (mute) => {
|
|
202
|
-
if (track) {
|
|
203
|
-
track.enabled = !mute;
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
201
|
return createMediaTrack({
|
|
207
202
|
label,
|
|
208
203
|
kind: 'audioinput',
|
|
@@ -210,9 +205,18 @@ label = PROCESSOR_LABELS.AudioProcessor, }) => {
|
|
|
210
205
|
previousMediaTrack: prevMediaTrack,
|
|
211
206
|
input: prevMediaTrack.input,
|
|
212
207
|
expectedInput: prevMediaTrack.expectedInput,
|
|
213
|
-
|
|
208
|
+
overrideMute: true,
|
|
209
|
+
mute(toMute) {
|
|
210
|
+
if (track) {
|
|
211
|
+
track.enabled = !toMute;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
get muted() {
|
|
215
|
+
return !track.enabled;
|
|
216
|
+
},
|
|
214
217
|
track,
|
|
215
218
|
release,
|
|
219
|
+
signals,
|
|
216
220
|
applyConstraints: async (constraints) => {
|
|
217
221
|
if (isEmpty(constraints)) {
|
|
218
222
|
return;
|
package/dist/media.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createIndexedDevices,
|
|
1
|
+
import { createIndexedDevices, getDevices, getInputDevicePermissionState, isIndexedDevices, isRequestedResolution, mergeConstraints, } from '@pexip/media-control';
|
|
2
2
|
import { createAsyncQueue, isEmpty, assert } from '@pexip/utils';
|
|
3
3
|
import { internalSignals } from './signals';
|
|
4
4
|
import { UserMediaStatus } from './types';
|
|
@@ -283,28 +283,6 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
283
283
|
props.devices = createIndexedDevices(await getDevices());
|
|
284
284
|
return props.devices;
|
|
285
285
|
};
|
|
286
|
-
const syncMuteState = (tracks) => {
|
|
287
|
-
const inputMuted = getMuteState();
|
|
288
|
-
for (const track of tracks) {
|
|
289
|
-
switch (track.kind) {
|
|
290
|
-
case 'audioinput': {
|
|
291
|
-
track.mute(inputMuted.audio);
|
|
292
|
-
// FIXME: this can fire too often, but we want to make sure we signal initial state as well.
|
|
293
|
-
// We should only fire this when needed.
|
|
294
|
-
signals.onAudioMuteStateChanged?.emit(track.muted);
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
case 'videoinput':
|
|
298
|
-
track.mute(inputMuted.video);
|
|
299
|
-
// FIXME: this can fire too often, but we want to make sure we signal initial state as well.
|
|
300
|
-
// We should only fire this when needed.
|
|
301
|
-
signals.onVideoMuteStateChanged?.emit(track.muted);
|
|
302
|
-
break;
|
|
303
|
-
default:
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
286
|
const processMedia = createMediaProcessor({
|
|
309
287
|
audioProcessors,
|
|
310
288
|
videoProcessors,
|
|
@@ -332,13 +310,14 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
332
310
|
});
|
|
333
311
|
const processAndUpdateMedia = async (media, tracks) => {
|
|
334
312
|
const processedTracks = await processMedia(tracks);
|
|
335
|
-
// Sync mute state to processed tracks
|
|
336
|
-
syncMuteState(processedTracks);
|
|
337
313
|
for (const [idx, track] of processedTracks.entries()) {
|
|
338
314
|
const originTrack = tracks.at(idx);
|
|
339
315
|
assert(originTrack, 'Processed track should always has the original track in the same order');
|
|
340
316
|
// Only replace track when they are not the same
|
|
341
317
|
if (originTrack.id !== track.id) {
|
|
318
|
+
if (originTrack.muted !== undefined) {
|
|
319
|
+
track.mute(originTrack.muted);
|
|
320
|
+
}
|
|
342
321
|
media.removeTrack(originTrack);
|
|
343
322
|
media.addTrack(track);
|
|
344
323
|
}
|
|
@@ -351,19 +330,12 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
351
330
|
shouldDiscardMedia: () => props.discardMedia,
|
|
352
331
|
getDefaultConstraints,
|
|
353
332
|
onMediaTracksChanged: (media, tracks) => {
|
|
354
|
-
|
|
355
|
-
syncMuteState(tracks);
|
|
333
|
+
const inputMuted = getMuteState();
|
|
356
334
|
for (const track of tracks) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
unsubscribe();
|
|
362
|
-
},
|
|
363
|
-
mute: signals.onStreamTrackMuted?.emit,
|
|
364
|
-
unmute: signals.onStreamTrackUnmuted?.emit,
|
|
365
|
-
});
|
|
366
|
-
}
|
|
335
|
+
// Sync mute state to input tracks
|
|
336
|
+
track.mute(track.kind === 'audioinput'
|
|
337
|
+
? inputMuted.audio
|
|
338
|
+
: inputMuted.video);
|
|
367
339
|
}
|
|
368
340
|
logger.debug({ media, tracks }, 'Update media tracks');
|
|
369
341
|
props.media = media;
|
|
@@ -12,11 +12,13 @@ export interface PreviewEventHandler {
|
|
|
12
12
|
audioInputError?: EventErrorCallback;
|
|
13
13
|
applyChangesError?: EventErrorCallback;
|
|
14
14
|
revertChangesError?: EventErrorCallback;
|
|
15
|
+
audioMuted?: EventCallback<boolean | undefined>;
|
|
16
|
+
audioSuspended?: EventCallback<boolean | undefined>;
|
|
17
|
+
updatingMain?: EventCallback<boolean>;
|
|
15
18
|
updatingPreviewAudio?: EventCallback<boolean>;
|
|
16
19
|
updatingPreviewVideo?: EventCallback<boolean>;
|
|
17
|
-
audioMuted?: EventCallback<boolean | undefined>;
|
|
18
20
|
videoMuted?: EventCallback<boolean | undefined>;
|
|
19
|
-
|
|
21
|
+
videoSuspended?: EventCallback<boolean | undefined>;
|
|
20
22
|
unsubscribeMain?: Unsubscribe;
|
|
21
23
|
}
|
|
22
24
|
export interface PreviewStreamParams {
|
|
@@ -24,6 +26,7 @@ export interface PreviewStreamParams {
|
|
|
24
26
|
getCurrentMedia: () => Media | undefined;
|
|
25
27
|
updateMainStream: (request: MediaDeviceRequest) => Promise<void>;
|
|
26
28
|
mainMediaSignal: MediaSignals['onMediaChanged'];
|
|
29
|
+
signals: MediaSignals;
|
|
27
30
|
onEnded?: () => void;
|
|
28
31
|
fftSize?: number;
|
|
29
32
|
queueOptions?: Partial<AsyncQueueOptions>;
|
|
@@ -76,8 +79,10 @@ export interface PreviewStreamController {
|
|
|
76
79
|
onApplyChangesError(callback: EventErrorCallback): Unsubscribe;
|
|
77
80
|
onRevertChangesError(callback: EventErrorCallback): Unsubscribe;
|
|
78
81
|
onAudioMuted(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
82
|
+
onAudioSuspended(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
79
83
|
onVideoMuted(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
84
|
+
onVideoSuspended(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
80
85
|
}
|
|
81
|
-
export declare const createPreviewStreamController: ({ getCurrentDevices, getCurrentMedia, updateMainStream, onEnded, mainMediaSignal, queueOptions, audioProcessors, videoProcessors, getDefaultConstraints, }: PreviewStreamParams) => PreviewStreamController;
|
|
86
|
+
export declare const createPreviewStreamController: ({ getCurrentDevices, getCurrentMedia, updateMainStream, onEnded, mainMediaSignal, queueOptions, audioProcessors, videoProcessors, getDefaultConstraints, signals, }: PreviewStreamParams) => PreviewStreamController;
|
|
82
87
|
export type CreatePreviewStreamController = typeof createPreviewStreamController;
|
|
83
88
|
export {};
|
|
@@ -4,7 +4,6 @@ import { AUDIO_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, createMediaProcessor, hasSett
|
|
|
4
4
|
import { isMedia } from './typeGuard';
|
|
5
5
|
import { createModuleLogger } from './logger';
|
|
6
6
|
import { createGetUserMediaProcess, requestUserMediaWithRetry, } from './userMedia';
|
|
7
|
-
import { createMediaSignals } from './signals';
|
|
8
7
|
import { createMediaUpdater } from './media';
|
|
9
8
|
const DEFAULT_QUEUE_DELAY_MS = 100;
|
|
10
9
|
const DEFAULT_QUEUE_DROP_LAST = false;
|
|
@@ -52,7 +51,7 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
|
|
|
52
51
|
throttleInMS: DEFAULT_QUEUE_THROTTLE_MS,
|
|
53
52
|
delayInMS: DEFAULT_QUEUE_DELAY_MS,
|
|
54
53
|
dropLast: DEFAULT_QUEUE_DROP_LAST,
|
|
55
|
-
}, audioProcessors, videoProcessors, getDefaultConstraints = () => ({}), }) => {
|
|
54
|
+
}, audioProcessors, videoProcessors, getDefaultConstraints = () => ({}), signals, }) => {
|
|
56
55
|
const queue = createAsyncQueue(queueOptions);
|
|
57
56
|
const eventHandlers = {};
|
|
58
57
|
const internalProps = {
|
|
@@ -62,15 +61,7 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
|
|
|
62
61
|
updatingMain: false,
|
|
63
62
|
discardMedia: false,
|
|
64
63
|
initialized: false,
|
|
65
|
-
signals
|
|
66
|
-
'onAddTrack',
|
|
67
|
-
'onAudioMuteStateChanged',
|
|
68
|
-
'onRemoveTrack',
|
|
69
|
-
'onStatusChanged',
|
|
70
|
-
'onUpdatingAudio',
|
|
71
|
-
'onUpdatingVideo',
|
|
72
|
-
'onVideoMuteStateChanged',
|
|
73
|
-
], 'PreviewStreamController'),
|
|
64
|
+
signals,
|
|
74
65
|
};
|
|
75
66
|
const logger = createModuleLogger({
|
|
76
67
|
module: 'PreviewStreamController',
|
|
@@ -129,11 +120,29 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
|
|
|
129
120
|
},
|
|
130
121
|
});
|
|
131
122
|
const subscriptions = [
|
|
132
|
-
props.signals.
|
|
133
|
-
|
|
123
|
+
props.signals.onMediaTrackMuted?.add(track => {
|
|
124
|
+
if (track.kind === 'audioinput') {
|
|
125
|
+
eventHandlers.audioMuted?.(track.muted);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
eventHandlers.videoMuted?.(track.muted);
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
props.signals.onMediaTrackSuspended?.add(track => {
|
|
132
|
+
if (track.kind === 'audioinput') {
|
|
133
|
+
eventHandlers.audioSuspended?.(track.suspended);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
eventHandlers.videoSuspended?.(track.suspended);
|
|
137
|
+
}
|
|
134
138
|
}),
|
|
135
|
-
props.signals.
|
|
136
|
-
|
|
139
|
+
props.signals.onMediaTrackResumed?.add(track => {
|
|
140
|
+
if (track.kind === 'audioinput') {
|
|
141
|
+
eventHandlers.audioSuspended?.(track.suspended);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
eventHandlers.videoSuspended?.(track.suspended);
|
|
145
|
+
}
|
|
137
146
|
}),
|
|
138
147
|
props.signals.onAddTrack?.add(track => {
|
|
139
148
|
switch (track.kind) {
|
|
@@ -228,7 +237,7 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
|
|
|
228
237
|
return;
|
|
229
238
|
}
|
|
230
239
|
try {
|
|
231
|
-
const clonedMedia = mainMedia.clone(props.signals);
|
|
240
|
+
const clonedMedia = mainMedia.clone(props.signals, 'Preview');
|
|
232
241
|
props.media = clonedMedia;
|
|
233
242
|
queue.enqueue(async () => {
|
|
234
243
|
await processAndUpdateMedia(clonedMedia, clonedMedia.getTracks(), true);
|
|
@@ -484,6 +493,8 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
|
|
|
484
493
|
onUpdatingMain: toEvenHandler('updatingMain'),
|
|
485
494
|
onAudioMuted: toEvenHandler('audioMuted'),
|
|
486
495
|
onVideoMuted: toEvenHandler('videoMuted'),
|
|
496
|
+
onAudioSuspended: toEvenHandler('audioSuspended'),
|
|
497
|
+
onVideoSuspended: toEvenHandler('videoSuspended'),
|
|
487
498
|
applyChanges,
|
|
488
499
|
revertChanges,
|
|
489
500
|
cleanup,
|
package/dist/types.d.ts
CHANGED
|
@@ -127,8 +127,9 @@ export interface Media extends MediaAttributes {
|
|
|
127
127
|
getAudioTracks(): MediaTrack[];
|
|
128
128
|
getVideoTracks(): MediaTrack[];
|
|
129
129
|
addTrack(track: MediaTrack): void;
|
|
130
|
+
isCurrentTrack(track: MediaTrack): boolean;
|
|
130
131
|
removeTrack(track: MediaTrack): void;
|
|
131
|
-
clone(signals?: MediaSignals): Media;
|
|
132
|
+
clone(signals?: MediaSignals, label?: string): Media;
|
|
132
133
|
}
|
|
133
134
|
export interface MediaInit {
|
|
134
135
|
constraints: MediaDeviceRequest;
|
|
@@ -149,26 +150,35 @@ export interface MediaTrack {
|
|
|
149
150
|
* `track` is available otherwise it uses the `id` from the construction or the
|
|
150
151
|
* `kind`. @see MediaTrackInit
|
|
151
152
|
*/
|
|
152
|
-
id: string;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
readonly id: string;
|
|
154
|
+
/**
|
|
155
|
+
* It maps to MediaStreamTrack['readyState']
|
|
156
|
+
* When `true` the track's `readyState` is `ended`, `false` means `live` otherwise the track is not available
|
|
157
|
+
*/
|
|
158
|
+
readonly stopped?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Indicate that the media has been suspended by the User Agent, and it could happen at any time.
|
|
161
|
+
* `undefined` means the track is not available
|
|
162
|
+
*/
|
|
163
|
+
readonly suspended?: boolean;
|
|
164
|
+
readonly kind: 'audioinput' | 'videoinput';
|
|
165
|
+
readonly label?: string;
|
|
166
|
+
readonly previousMediaTrack?: MediaTrack;
|
|
157
167
|
/**
|
|
158
168
|
* The MediaStreamTrack object representing the track
|
|
159
169
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack
|
|
160
170
|
*/
|
|
161
|
-
track?: MediaStreamTrack;
|
|
171
|
+
readonly track?: MediaStreamTrack;
|
|
162
172
|
/**
|
|
163
173
|
* The `MediaDeviceInfo` object representing the device that the track is
|
|
164
174
|
* connected to. @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo
|
|
165
175
|
*/
|
|
166
|
-
input?: MediaDeviceInfoLike;
|
|
176
|
+
readonly input?: MediaDeviceInfoLike;
|
|
167
177
|
/**
|
|
168
178
|
* The `MediaDeviceInfo` object representing the device that the track is
|
|
169
179
|
* expected to be connected to. @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo
|
|
170
180
|
*/
|
|
171
|
-
expectedInput?: MediaDeviceInfoLike;
|
|
181
|
+
readonly expectedInput?: MediaDeviceInfoLike;
|
|
172
182
|
/**
|
|
173
183
|
* The current mute state of the track. `undefined` means the track is not available
|
|
174
184
|
*/
|
|
@@ -176,13 +186,20 @@ export interface MediaTrack {
|
|
|
176
186
|
/**
|
|
177
187
|
* The source track that it is originated from
|
|
178
188
|
*/
|
|
179
|
-
source: MediaTrack;
|
|
180
|
-
|
|
189
|
+
readonly source: MediaTrack;
|
|
190
|
+
/**
|
|
191
|
+
* Mute the track
|
|
192
|
+
*
|
|
193
|
+
* @param toMute - `true` to mute the track, `false` to unmute
|
|
194
|
+
* @param soft - `true` to indicate a soft mute i.e. setting the track to NOT `enabled`
|
|
195
|
+
* `false` or `undefined` for default mute
|
|
196
|
+
*/
|
|
197
|
+
mute(toMute: boolean, soft?: boolean): void;
|
|
181
198
|
getSettings(): ExtendedMediaTrackSettings;
|
|
182
199
|
getConstraints(): InputDeviceConstraint;
|
|
183
200
|
applyConstraints(constraints: InputDeviceConstraint): Promise<void>;
|
|
184
201
|
release(): Promise<void>;
|
|
185
|
-
clone(signals?: MediaSignals): MediaTrack;
|
|
202
|
+
clone(signals?: MediaSignals, label?: string): MediaTrack;
|
|
186
203
|
toJSON(): unknown;
|
|
187
204
|
}
|
|
188
205
|
export interface MediaTrackInit extends Partial<Omit<MediaTrack, 'mute'>> {
|
|
@@ -192,7 +209,15 @@ export interface MediaTrackInit extends Partial<Omit<MediaTrack, 'mute'>> {
|
|
|
192
209
|
constraints: InputDeviceConstraint;
|
|
193
210
|
overrideMute?: boolean;
|
|
194
211
|
signals?: MediaSignals;
|
|
195
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Overriding Mute the track
|
|
214
|
+
*
|
|
215
|
+
* @param toMute - `true` to mute the track, `false` to unmute
|
|
216
|
+
* @param self - The media track itself
|
|
217
|
+
* @param soft - `true` to indicate a soft mute i.e. setting the track to NOT `enabled`
|
|
218
|
+
* `false` or `undefined` for default mute
|
|
219
|
+
*/
|
|
220
|
+
mute?: (toMute: boolean, self: MediaTrack, soft?: boolean) => void;
|
|
196
221
|
}
|
|
197
222
|
export type Process<T> = (a: T) => Promise<MediaTrack>;
|
|
198
223
|
export type TrackProcessor = Process<MediaTrack>;
|
|
@@ -521,44 +546,16 @@ export type VideoRenderParams = Omit<RendererOptions, 'effects'> & {
|
|
|
521
546
|
tilt?: boolean;
|
|
522
547
|
zoom?: boolean;
|
|
523
548
|
};
|
|
524
|
-
export interface StreamTrackSignals {
|
|
525
|
-
/**
|
|
526
|
-
* MediaStreamTrack events: mute
|
|
527
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack#events
|
|
528
|
-
*/
|
|
529
|
-
onStreamTrackMuted: Signal<MediaStreamTrack>;
|
|
530
|
-
/**
|
|
531
|
-
* MediaStreamTrack events: unmute
|
|
532
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack#events
|
|
533
|
-
*/
|
|
534
|
-
onStreamTrackUnmuted: Signal<MediaStreamTrack>;
|
|
535
|
-
/**
|
|
536
|
-
* MediaStreamTrack events: ended
|
|
537
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack#events
|
|
538
|
-
*/
|
|
539
|
-
onStreamTrackEnded: Signal<MediaStreamTrack>;
|
|
540
|
-
/**
|
|
541
|
-
* Emit `MediaStreamTrack` whenever a call to `Media['muteAudio']` or
|
|
542
|
-
* `Media['muteVideo']`
|
|
543
|
-
*/
|
|
544
|
-
onStreamTrackEnabled: Signal<MediaStreamTrack>;
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Track state signals that used for the final track that the media pipeline
|
|
548
|
-
* produced
|
|
549
|
-
*/
|
|
550
|
-
export type StreamTrackSignalFinals = {
|
|
551
|
-
[key in keyof StreamTrackSignals as `${key}Final`]: Signal<MediaStreamTrack>;
|
|
552
|
-
};
|
|
553
549
|
export interface MediaChangesSignals {
|
|
554
550
|
onAddTrack: Signal<MediaStreamTrack>;
|
|
555
|
-
onAudioMuteStateChanged: Signal<boolean | undefined>;
|
|
556
|
-
onTrackReleased: Signal<MediaStreamTrack>;
|
|
557
551
|
onDevicesChanged: Signal<IndexedDevices>;
|
|
558
552
|
onMediaChanged: Signal<Media | undefined>;
|
|
553
|
+
onMediaTrackMuted: Signal<MediaTrack>;
|
|
554
|
+
onMediaTrackResumed: Signal<MediaTrack>;
|
|
555
|
+
onMediaTrackStopped: Signal<MediaTrack>;
|
|
556
|
+
onMediaTrackSuspended: Signal<MediaTrack>;
|
|
559
557
|
onRemoveTrack: Signal<MediaStreamTrack>;
|
|
560
558
|
onStatusChanged: Signal<UserMediaStatus>;
|
|
561
|
-
onVideoMuteStateChanged: Signal<boolean | undefined>;
|
|
562
559
|
onUpdatingAudio: Signal<boolean>;
|
|
563
560
|
onUpdatingVideo: Signal<boolean>;
|
|
564
561
|
}
|
|
@@ -566,7 +563,7 @@ export interface AudioDetectionSignals {
|
|
|
566
563
|
onVAD: Signal<undefined>;
|
|
567
564
|
onSilentDetected: Signal<boolean>;
|
|
568
565
|
}
|
|
569
|
-
export type MediaSignalsOptional = Omit<Partial<MediaChangesSignals>, 'onMediaChanged'
|
|
566
|
+
export type MediaSignalsOptional = Omit<Partial<MediaChangesSignals>, 'onMediaChanged'>;
|
|
570
567
|
export type MediaSignalsRequired = Pick<MediaChangesSignals, 'onMediaChanged'> & AudioDetectionSignals;
|
|
571
568
|
export type MediaSignals = MediaSignalsRequired & MediaSignalsOptional;
|
|
572
569
|
export declare enum DeniedDevices {
|
package/dist/userMedia.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MediaDeviceFailure, extractConstraintsWithKeys, findMediaInputFromMediaStreamTrack, getUserMedia, isExactDeviceConstraint, isStreamingRequestedDevices, relaxInputConstraint, } from '@pexip/media-control';
|
|
2
2
|
import { assert } from '@pexip/utils';
|
|
3
3
|
import { UserMediaStatus } from './types';
|
|
4
|
-
import { buildMedia, createMediaTrack, findExpectedInput,
|
|
4
|
+
import { buildMedia, createMediaTrack, findExpectedInput, makeDeriveDeviceStatus, } from './utils';
|
|
5
5
|
import { logger } from './logger';
|
|
6
6
|
import { isUnknownError } from './status';
|
|
7
7
|
import { PROCESSOR_LABELS } from './constants';
|
|
@@ -556,20 +556,33 @@ export const createGetUserMediaProcess = ({ getCurrentDevices, getUserMedia, sco
|
|
|
556
556
|
let audioMediaTrack;
|
|
557
557
|
let videoMediaTrack;
|
|
558
558
|
const extractContentHint = extractConstraintsWithKeys(['contentHint']);
|
|
559
|
+
let trackReleaseTimeoutID = 0;
|
|
559
560
|
/**
|
|
560
561
|
* Release the track as mute
|
|
561
562
|
*/
|
|
562
|
-
const releaseTrackAsMute = async (track, toMute) => {
|
|
563
|
+
const releaseTrackAsMute = async (track, toMute, soft) => {
|
|
563
564
|
if (!track?.track || track.muted === toMute) {
|
|
564
565
|
return;
|
|
565
566
|
}
|
|
566
567
|
track.track.enabled = !toMute;
|
|
568
|
+
if (soft) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
567
571
|
if (toMute) {
|
|
568
572
|
if (!track.stopped) {
|
|
569
|
-
|
|
573
|
+
// Track cannot be stopped immediately as we need to ensure
|
|
574
|
+
// the last frame is black to avoid frozen frame in a gateway call
|
|
575
|
+
trackReleaseTimeoutID = window.setTimeout(() => {
|
|
576
|
+
track.release();
|
|
577
|
+
trackReleaseTimeoutID = 0;
|
|
578
|
+
}, 500);
|
|
570
579
|
}
|
|
571
580
|
}
|
|
572
581
|
else {
|
|
582
|
+
if (trackReleaseTimeoutID) {
|
|
583
|
+
window.clearTimeout(trackReleaseTimeoutID);
|
|
584
|
+
trackReleaseTimeoutID = 0;
|
|
585
|
+
}
|
|
573
586
|
if (track.stopped) {
|
|
574
587
|
await updateMedia({
|
|
575
588
|
[track.kind === 'audioinput' ? 'audio' : 'video']: track.getConstraints(),
|
|
@@ -586,7 +599,7 @@ export const createGetUserMediaProcess = ({ getCurrentDevices, getUserMedia, sco
|
|
|
586
599
|
audioTrack.contentHint = contentHint ?? '';
|
|
587
600
|
}
|
|
588
601
|
audioMediaTrack = createMediaTrack({
|
|
589
|
-
label: PROCESSOR_LABELS.GetUserMedia,
|
|
602
|
+
label: [scope, PROCESSOR_LABELS.GetUserMedia].join('|'),
|
|
590
603
|
kind: 'audioinput',
|
|
591
604
|
track: audioTrack,
|
|
592
605
|
input: audioInput,
|
|
@@ -605,17 +618,19 @@ export const createGetUserMediaProcess = ({ getCurrentDevices, getUserMedia, sco
|
|
|
605
618
|
videoTrack.contentHint = contentHint ?? '';
|
|
606
619
|
}
|
|
607
620
|
videoMediaTrack = createMediaTrack({
|
|
608
|
-
label: PROCESSOR_LABELS.GetUserMedia,
|
|
621
|
+
label: [scope, PROCESSOR_LABELS.GetUserMedia].join('|'),
|
|
609
622
|
kind: 'videoinput',
|
|
610
623
|
track: videoTrack,
|
|
611
624
|
input: videoInput,
|
|
612
625
|
expectedInput: findExpectedInput(devices, constraints.video, videoInput, 'videoinput'),
|
|
613
|
-
overrideMute
|
|
626
|
+
get overrideMute() {
|
|
627
|
+
return stopVideoTrackAsMute();
|
|
628
|
+
},
|
|
614
629
|
get muted() {
|
|
615
|
-
return
|
|
630
|
+
return !videoTrack?.enabled;
|
|
616
631
|
},
|
|
617
|
-
mute(toMute, self) {
|
|
618
|
-
releaseTrackAsMute(self, toMute);
|
|
632
|
+
mute(toMute, self, soft) {
|
|
633
|
+
releaseTrackAsMute(self, toMute, soft);
|
|
619
634
|
},
|
|
620
635
|
constraints: constraints.video,
|
|
621
636
|
signals,
|
package/dist/utils.js
CHANGED
|
@@ -19,10 +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(!trackInit
|
|
23
|
-
(trackInit
|
|
24
|
-
|
|
25
|
-
|
|
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');
|
|
26
26
|
const currentConstraints = typeof trackInit.constraints === 'boolean' ? {} : trackInit.constraints;
|
|
27
27
|
// Find the original source track through the linked list
|
|
28
28
|
let sourceMediaTrack = trackInit.previousMediaTrack;
|
|
@@ -45,6 +45,25 @@ export const createMediaTrack = (trackInit) => {
|
|
|
45
45
|
...(trackInit.getSettings?.() ?? {}),
|
|
46
46
|
};
|
|
47
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
|
+
}
|
|
48
67
|
const mediaTrack = {
|
|
49
68
|
get kind() {
|
|
50
69
|
return trackInit.kind;
|
|
@@ -74,36 +93,50 @@ export const createMediaTrack = (trackInit) => {
|
|
|
74
93
|
if (trackInit.overrideMute) {
|
|
75
94
|
return trackInit.muted;
|
|
76
95
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return isTrackMuted(trackInit.track);
|
|
96
|
+
if (trackInit.track) {
|
|
97
|
+
return !trackInit.track.enabled;
|
|
80
98
|
}
|
|
81
|
-
return
|
|
99
|
+
return undefined;
|
|
82
100
|
},
|
|
83
101
|
get stopped() {
|
|
84
|
-
return trackInit.track
|
|
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);
|
|
85
108
|
},
|
|
86
109
|
get source() {
|
|
87
|
-
return sourceMediaTrack ??
|
|
110
|
+
return sourceMediaTrack ?? this;
|
|
88
111
|
},
|
|
89
|
-
mute(toMute) {
|
|
112
|
+
mute(toMute, soft) {
|
|
113
|
+
if (this.muted === toMute) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
90
116
|
if (trackInit.overrideMute) {
|
|
91
|
-
|
|
117
|
+
trackInit.mute?.(toMute, this, soft);
|
|
92
118
|
}
|
|
93
|
-
|
|
94
|
-
trackInit.track
|
|
119
|
+
else {
|
|
120
|
+
if (trackInit.track) {
|
|
121
|
+
trackInit.track.enabled = !toMute;
|
|
122
|
+
}
|
|
123
|
+
trackInit.previousMediaTrack?.mute(toMute, soft);
|
|
95
124
|
}
|
|
96
|
-
trackInit.
|
|
125
|
+
trackInit.signals?.onMediaTrackMuted?.emit(this);
|
|
97
126
|
},
|
|
98
127
|
getSettings,
|
|
99
128
|
getConstraints() {
|
|
100
129
|
return getConstraints();
|
|
101
130
|
},
|
|
102
|
-
clone(signals) {
|
|
131
|
+
clone(signals, label) {
|
|
103
132
|
return createMediaTrack({
|
|
104
|
-
|
|
133
|
+
kind: this.kind,
|
|
134
|
+
label: [label, this.label]
|
|
135
|
+
.flatMap(a => (a ? [a] : []))
|
|
136
|
+
.join('|'),
|
|
137
|
+
input: this.input,
|
|
105
138
|
constraints: getConstraints(),
|
|
106
|
-
track:
|
|
139
|
+
track: this.track?.clone(),
|
|
107
140
|
// When Cloning a MediaTrack, overrideMute should be turned off since
|
|
108
141
|
// it might corrupt the logic of its original media pipeline
|
|
109
142
|
overrideMute: false,
|
|
@@ -146,23 +179,20 @@ export const createMediaTrack = (trackInit) => {
|
|
|
146
179
|
Object.assign(currentConstraints, resolvedConstraints);
|
|
147
180
|
},
|
|
148
181
|
async release() {
|
|
182
|
+
trackUnsubscribe?.();
|
|
149
183
|
trackInit.track?.stop();
|
|
150
|
-
if (trackInit.track) {
|
|
151
|
-
trackInit.signals?.onTrackReleased?.emit(trackInit.track);
|
|
152
|
-
trackInit.signals?.[trackInit.track.kind === 'audio'
|
|
153
|
-
? 'onAudioMuteStateChanged'
|
|
154
|
-
: 'onVideoMuteStateChanged']?.emit(undefined);
|
|
155
|
-
}
|
|
156
|
-
await trackInit.previousMediaTrack?.release();
|
|
157
184
|
await trackInit.release?.();
|
|
185
|
+
await trackInit.previousMediaTrack?.release();
|
|
186
|
+
trackInit.signals?.onMediaTrackStopped?.emit(this);
|
|
158
187
|
},
|
|
159
188
|
toJSON() {
|
|
160
189
|
return {
|
|
161
|
-
label:
|
|
162
|
-
track:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
190
|
+
label: this.label,
|
|
191
|
+
track: this.track,
|
|
192
|
+
input: this.input,
|
|
193
|
+
previousMediaTrack: this.previousMediaTrack,
|
|
194
|
+
constraints: this.getConstraints(),
|
|
195
|
+
settings: this.getSettings(),
|
|
166
196
|
};
|
|
167
197
|
},
|
|
168
198
|
};
|
|
@@ -231,6 +261,10 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
231
261
|
status: mediaInit.status,
|
|
232
262
|
devices: mediaInit.devices,
|
|
233
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,
|
|
234
268
|
audioTrack: mediaInit.tracks
|
|
235
269
|
.flatMap(track => {
|
|
236
270
|
return track.kind === 'audioinput' ? [track] : [];
|
|
@@ -240,73 +274,76 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
240
274
|
.flatMap(track => (track.kind === 'videoinput' ? [track] : []))
|
|
241
275
|
.at(0),
|
|
242
276
|
};
|
|
243
|
-
const subscribeTrackEvents = (track) => createStreamTrackEventSubscriptions(track, {
|
|
244
|
-
ended: track => {
|
|
245
|
-
mediaInit.signals?.[track.kind === 'audio'
|
|
246
|
-
? 'onAudioMuteStateChanged'
|
|
247
|
-
: 'onVideoMuteStateChanged']?.emit(undefined);
|
|
248
|
-
mediaInit.signals?.onStreamTrackEndedFinal?.emit(track);
|
|
249
|
-
},
|
|
250
|
-
mute: track => {
|
|
251
|
-
mediaInit.signals?.[track.kind === 'audio'
|
|
252
|
-
? 'onAudioMuteStateChanged'
|
|
253
|
-
: 'onVideoMuteStateChanged']?.emit(true);
|
|
254
|
-
mediaInit.signals?.onStreamTrackMuted?.emit(track);
|
|
255
|
-
},
|
|
256
|
-
unmute: track => {
|
|
257
|
-
mediaInit.signals?.[track.kind === 'audio'
|
|
258
|
-
? 'onAudioMuteStateChanged'
|
|
259
|
-
: 'onVideoMuteStateChanged']?.emit(!track.enabled);
|
|
260
|
-
mediaInit.signals?.onStreamTrackUnmuted?.emit(track);
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
const subscribeTrackAndSourceTrackEvents = (track) => {
|
|
264
|
-
let trackUnsubscribe;
|
|
265
|
-
let sourceTrackUnsubscribe;
|
|
266
|
-
if (track.track) {
|
|
267
|
-
trackUnsubscribe = subscribeTrackEvents(track.track);
|
|
268
|
-
if (track.source.track && track.source.track !== track.track) {
|
|
269
|
-
sourceTrackUnsubscribe = subscribeTrackEvents(track.source.track);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return () => {
|
|
273
|
-
trackUnsubscribe?.();
|
|
274
|
-
sourceTrackUnsubscribe?.();
|
|
275
|
-
};
|
|
276
|
-
};
|
|
277
|
-
const trackSubscriptions = new Map(mediaInit.tracks.flatMap(track => {
|
|
278
|
-
if (track.track) {
|
|
279
|
-
return [
|
|
280
|
-
[track.track, subscribeTrackAndSourceTrackEvents(track)],
|
|
281
|
-
];
|
|
282
|
-
}
|
|
283
|
-
return [];
|
|
284
|
-
}));
|
|
285
277
|
const getConstraints = () => {
|
|
286
278
|
const audio = props.audioTrack?.getConstraints() ?? mediaInit.constraints.audio;
|
|
287
279
|
const video = props.videoTrack?.getConstraints() ?? mediaInit.constraints.video;
|
|
288
280
|
return { audio: audio, video: video };
|
|
289
281
|
};
|
|
290
|
-
|
|
282
|
+
const updateDevices = (devices) => {
|
|
291
283
|
props.devices = devices;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
}
|
|
308
|
+
}
|
|
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
|
+
}
|
|
304
334
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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?.();
|
|
309
345
|
}
|
|
346
|
+
subscriptions.length = 0;
|
|
310
347
|
};
|
|
311
348
|
return {
|
|
312
349
|
get id() {
|
|
@@ -372,16 +409,16 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
372
409
|
props.devices.size('videoinput') > 0));
|
|
373
410
|
},
|
|
374
411
|
muteAudio(mute) {
|
|
375
|
-
if (!props.audioTrack || props.audioTrack.
|
|
412
|
+
if (!props.audioTrack || props.audioTrack.suspended) {
|
|
376
413
|
return;
|
|
377
414
|
}
|
|
378
|
-
|
|
415
|
+
props.audioTrack.mute(mute);
|
|
379
416
|
},
|
|
380
417
|
muteVideo(mute) {
|
|
381
|
-
if (!props.videoTrack || props.videoTrack.
|
|
418
|
+
if (!props.videoTrack || props.videoTrack.suspended) {
|
|
382
419
|
return;
|
|
383
420
|
}
|
|
384
|
-
|
|
421
|
+
props.videoTrack?.mute(mute);
|
|
385
422
|
},
|
|
386
423
|
applyConstraints: async (constraints) => {
|
|
387
424
|
await Promise.all([
|
|
@@ -394,12 +431,7 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
394
431
|
]);
|
|
395
432
|
},
|
|
396
433
|
async release() {
|
|
397
|
-
unsubscribe
|
|
398
|
-
unsubscribe = undefined;
|
|
399
|
-
for (const unsubscribe of trackSubscriptions.values()) {
|
|
400
|
-
unsubscribe();
|
|
401
|
-
}
|
|
402
|
-
trackSubscriptions.clear();
|
|
434
|
+
unsubscribe();
|
|
403
435
|
await Promise.all([
|
|
404
436
|
props.audioTrack?.release(),
|
|
405
437
|
props.videoTrack?.release(),
|
|
@@ -409,13 +441,13 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
409
441
|
audio: props.audioTrack?.getSettings(),
|
|
410
442
|
video: props.videoTrack?.getSettings(),
|
|
411
443
|
}),
|
|
412
|
-
clone(signals) {
|
|
444
|
+
clone(signals, label) {
|
|
413
445
|
const tracks = [props.audioTrack, props.videoTrack].flatMap(track => {
|
|
414
446
|
if (!track?.source) {
|
|
415
447
|
return [];
|
|
416
448
|
}
|
|
417
|
-
const cloned = track.source.clone(signals);
|
|
418
|
-
return cloned;
|
|
449
|
+
const cloned = track.source.clone(signals, label);
|
|
450
|
+
return [cloned];
|
|
419
451
|
});
|
|
420
452
|
const stream = this.stream &&
|
|
421
453
|
new MediaStream(tracks.flatMap(track => (track.track ? [track.track] : [])));
|
|
@@ -440,6 +472,9 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
440
472
|
getVideoTracks() {
|
|
441
473
|
return props.videoTrack ? [props.videoTrack] : [];
|
|
442
474
|
},
|
|
475
|
+
isCurrentTrack(track) {
|
|
476
|
+
return track === props.audioTrack || track === props.videoTrack;
|
|
477
|
+
},
|
|
443
478
|
addTrack(track) {
|
|
444
479
|
switch (track.kind) {
|
|
445
480
|
case 'audioinput':
|
|
@@ -457,13 +492,22 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
457
492
|
default:
|
|
458
493
|
break;
|
|
459
494
|
}
|
|
495
|
+
if (subscriptions.length === 0) {
|
|
496
|
+
subscriptions = subscribe();
|
|
497
|
+
}
|
|
460
498
|
if (track.track) {
|
|
461
499
|
props.stream.addTrack(track.track);
|
|
462
|
-
if (!trackSubscriptions.has(track.track)) {
|
|
463
|
-
trackSubscriptions.set(track.track, subscribeTrackAndSourceTrackEvents(track));
|
|
464
|
-
}
|
|
465
500
|
mediaInit.signals?.onAddTrack?.emit(track.track);
|
|
466
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
|
+
}
|
|
467
511
|
}
|
|
468
512
|
},
|
|
469
513
|
removeTrack(track) {
|
|
@@ -478,13 +522,14 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
|
|
|
478
522
|
}
|
|
479
523
|
if (removed && track.track) {
|
|
480
524
|
props.stream.removeTrack(track.track);
|
|
481
|
-
trackSubscriptions.get(track.track)?.();
|
|
482
|
-
trackSubscriptions.delete(track.track);
|
|
483
525
|
mediaInit.signals?.onRemoveTrack?.emit(track.track);
|
|
484
526
|
props.stream.dispatchEvent(new MediaStreamTrackEvent('removetrack', {
|
|
485
527
|
track: track.track,
|
|
486
528
|
}));
|
|
487
529
|
}
|
|
530
|
+
if (subscriptions.length) {
|
|
531
|
+
unsubscribe();
|
|
532
|
+
}
|
|
488
533
|
},
|
|
489
534
|
toJSON() {
|
|
490
535
|
return {
|
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'> {
|
|
@@ -28,6 +28,7 @@ interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<Pro
|
|
|
28
28
|
stopAsMute?: () => boolean;
|
|
29
29
|
dynamicProcessingDimensions?: () => boolean;
|
|
30
30
|
gpuAPI?: () => RenderBackend;
|
|
31
|
+
signals?: MediaSignals;
|
|
31
32
|
}
|
|
32
33
|
interface VideoStreamProcessProps extends Partial<VideoRenderParams>, Required<ProcessorDeps> {
|
|
33
34
|
hasInitialized: boolean;
|
|
@@ -37,5 +38,5 @@ declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUr
|
|
|
37
38
|
type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
|
|
38
39
|
type FeatureProps = Pick<Partial<VideoStreamProcessProps>, FeaturePropKeys>;
|
|
39
40
|
export declare const updateFeatureProps: (constraints: MediaDeviceRequest["video"], props: FeatureProps) => FeatureProps;
|
|
40
|
-
export declare const createVideoStreamProcess: ({ backgroundImageUrl, dynamicProcessingDimensions, edgeBlurAmount, foregroundThreshold, frameRate, gpuAPI, label, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute, trackProcessorAPI, videoSegmentation, ...options }: VideoStreamProcessOptions) => TrackProcessor;
|
|
41
|
+
export declare const createVideoStreamProcess: ({ backgroundImageUrl, dynamicProcessingDimensions, edgeBlurAmount, foregroundThreshold, frameRate, gpuAPI, label, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute, trackProcessorAPI, videoSegmentation, signals, ...options }: VideoStreamProcessOptions) => TrackProcessor;
|
|
41
42
|
export {};
|
package/dist/videoProcessor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createCanvasTransform, createVideoProcessor, createVideoTrackProcessor, createVideoTrackProcessorWithFallback, isRenderEffects, isSegmentationModel, } from '@pexip/media-processor';
|
|
2
|
+
import { extractConstraintsWithKeys, getValueFromConstrainNumber, muteStreamTrack, } from '@pexip/media-control';
|
|
3
3
|
import { isEmpty, assert } from '@pexip/utils';
|
|
4
4
|
import { createMediaTrack, getBlurKernelSize } from './utils';
|
|
5
5
|
import { logger, proxyWithLog } from './logger';
|
|
@@ -142,7 +142,7 @@ const getTrackProcessor = (shouldUseStreamTrackProcessor, ...params) => {
|
|
|
142
142
|
return createVideoTrackProcessorWithFallback(...params);
|
|
143
143
|
};
|
|
144
144
|
export const createVideoStreamProcess = ({ backgroundImageUrl, dynamicProcessingDimensions = () => false, edgeBlurAmount, foregroundThreshold, frameRate, gpuAPI = () => 'webgl', // dynamically changing the config will not be picked up, not a priority for now
|
|
145
|
-
label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute = () => false, trackProcessorAPI = () => 'stream', videoSegmentation, ...options }) => {
|
|
145
|
+
label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute = () => false, trackProcessorAPI = () => 'stream', videoSegmentation, signals, ...options }) => {
|
|
146
146
|
const videoSegmentationModel = options.videoSegmentationModel ?? 'selfie';
|
|
147
147
|
const segmenter = options.segmenters[videoSegmentationModel];
|
|
148
148
|
if (!segmenter) {
|
|
@@ -253,36 +253,61 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
|
|
|
253
253
|
assert(track, 'Video track should be there');
|
|
254
254
|
// Inherit contentHint from previous track
|
|
255
255
|
track.contentHint = prevMediaTrack.track.contentHint;
|
|
256
|
-
|
|
257
|
-
props.videoProcessor().close();
|
|
258
|
-
await prevMediaTrack.release();
|
|
259
|
-
_videoProcessor = undefined;
|
|
260
|
-
props.hasInitialized = false;
|
|
261
|
-
};
|
|
262
|
-
const mute = (mute) => {
|
|
263
|
-
props.transformer.effects = mute
|
|
264
|
-
? 'none'
|
|
265
|
-
: props.videoSegmentation ?? 'none';
|
|
266
|
-
muteStreamTrack(stream)(mute, 'video');
|
|
267
|
-
if (mute && stopAsMute()) {
|
|
268
|
-
for (const track of stream.getVideoTracks()) {
|
|
269
|
-
track.stop();
|
|
270
|
-
}
|
|
271
|
-
release();
|
|
272
|
-
}
|
|
273
|
-
prevMediaTrack.mute(mute);
|
|
274
|
-
};
|
|
256
|
+
let trackReleaseTimeoutID = 0;
|
|
275
257
|
return createMediaTrack({
|
|
276
258
|
label,
|
|
277
259
|
kind: 'videoinput',
|
|
278
|
-
constraints
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
260
|
+
get constraints() {
|
|
261
|
+
return this.previousMediaTrack?.getConstraints();
|
|
262
|
+
},
|
|
263
|
+
get previousMediaTrack() {
|
|
264
|
+
return prevMediaTrack;
|
|
265
|
+
},
|
|
266
|
+
get input() {
|
|
267
|
+
return this.previousMediaTrack?.input;
|
|
268
|
+
},
|
|
269
|
+
get expectedInput() {
|
|
270
|
+
return this.previousMediaTrack?.expectedInput;
|
|
271
|
+
},
|
|
282
272
|
track,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
273
|
+
overrideMute: true,
|
|
274
|
+
get muted() {
|
|
275
|
+
return this.previousMediaTrack?.muted;
|
|
276
|
+
},
|
|
277
|
+
mute(mute, _self, soft) {
|
|
278
|
+
props.transformer.effects = mute
|
|
279
|
+
? 'none'
|
|
280
|
+
: props.videoSegmentation ?? 'none';
|
|
281
|
+
muteStreamTrack(stream)(mute, 'video');
|
|
282
|
+
if (stopAsMute() && !soft) {
|
|
283
|
+
if (mute) {
|
|
284
|
+
// Track cannot be stopped immediately as we need to ensure
|
|
285
|
+
// the last frame is black to avoid frozen frame in a gateway call
|
|
286
|
+
trackReleaseTimeoutID = window.setTimeout(() => {
|
|
287
|
+
for (const track of stream.getVideoTracks()) {
|
|
288
|
+
track.stop();
|
|
289
|
+
}
|
|
290
|
+
void this?.release?.();
|
|
291
|
+
trackReleaseTimeoutID = 0;
|
|
292
|
+
}, 500);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
if (trackReleaseTimeoutID) {
|
|
296
|
+
window.clearTimeout(trackReleaseTimeoutID);
|
|
297
|
+
trackReleaseTimeoutID = 0;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
this.previousMediaTrack?.mute(mute, soft);
|
|
302
|
+
},
|
|
303
|
+
signals,
|
|
304
|
+
async release() {
|
|
305
|
+
props.videoProcessor().close();
|
|
306
|
+
await this.previousMediaTrack?.release();
|
|
307
|
+
_videoProcessor = undefined;
|
|
308
|
+
props.hasInitialized = false;
|
|
309
|
+
},
|
|
310
|
+
async applyConstraints(constraints) {
|
|
286
311
|
if (isEmpty(constraints)) {
|
|
287
312
|
return;
|
|
288
313
|
}
|
|
@@ -293,8 +318,8 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
|
|
|
293
318
|
}
|
|
294
319
|
applyFeatures(props.transformer, features, lowestProcessingHeight);
|
|
295
320
|
if (dynamicProcessingDimensions()) {
|
|
296
|
-
const acceptedSettings =
|
|
297
|
-
if (acceptedSettings
|
|
321
|
+
const acceptedSettings = this.previousMediaTrack?.source.getSettings();
|
|
322
|
+
if (acceptedSettings?.width && acceptedSettings?.height) {
|
|
298
323
|
props.transformer.update({
|
|
299
324
|
processingHeight: acceptedSettings.height,
|
|
300
325
|
processingWidth: acceptedSettings.width,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pexip/media",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.3.0",
|
|
4
4
|
"description": "Home for media related stuff",
|
|
5
5
|
"homepage": "https://gitlab.com/pexip/zoo",
|
|
6
6
|
"bugs": "https://gitlab.com/pexip/zoo/issues",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"typecheck": "yarn tsc --noEmit -p ."
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@pexip/media-control": "20.
|
|
48
|
-
"@pexip/media-processor": "20.
|
|
47
|
+
"@pexip/media-control": "20.3.0",
|
|
48
|
+
"@pexip/media-processor": "20.3.0",
|
|
49
49
|
"@pexip/signal": "16.9.0",
|
|
50
|
-
"@pexip/utils": "17.0.
|
|
50
|
+
"@pexip/utils": "17.0.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@jest/globals": "^29.7.0",
|
|
54
|
-
"@pexip/bundler": "18.1.
|
|
54
|
+
"@pexip/bundler": "18.1.2",
|
|
55
55
|
"@swc/core": "^1.10.12",
|
|
56
56
|
"@swc/jest": "^0.2.37",
|
|
57
57
|
"jest": "^29.5.12",
|