@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
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
|
+
|
|
46
|
+
## 20.2.0
|
|
47
|
+
|
|
48
|
+
### Minor Changes
|
|
49
|
+
|
|
50
|
+
- 574cf03: Adjusts blur passes based on processing height:
|
|
51
|
+
|
|
52
|
+
- `getBlurKernelSize` can now take `lowestProcessingHeight` so that the blur
|
|
53
|
+
passes are adjusted compared to the lowest processing height used by the
|
|
54
|
+
consumer. This fixes an issue with different blur amount being applied to
|
|
55
|
+
frames of different resolutions.
|
|
56
|
+
- background blur amount is updated every time processing dimensions are
|
|
57
|
+
updated.
|
|
58
|
+
|
|
59
|
+
- be12a15: Add `getDefaultConstraints` and rename `mediaSignals` to
|
|
60
|
+
`mainMediaSignal`
|
|
61
|
+
|
|
62
|
+
- Add `getDefaultConstraints` to be consistent to the main one
|
|
63
|
+
- Rename `mediaSignals` to `mainMediaSignal`
|
|
64
|
+
- Fix `Media['clone']` to avoid interfering the original pipeline
|
|
65
|
+
|
|
66
|
+
- b4c3bad: Stop camera as Mute
|
|
67
|
+
|
|
68
|
+
- Decouple updatingMedia to updatingAudio and updatingVideo
|
|
69
|
+
- Stop camera when mute, and re-request the same camera when unmute
|
|
70
|
+
|
|
71
|
+
- 752005c: Release the track when providing false constraints independently
|
|
72
|
+
|
|
73
|
+
### Patch Changes
|
|
74
|
+
|
|
75
|
+
- Updated dependencies [f5e5b51]
|
|
76
|
+
- Updated dependencies [6fddc11]
|
|
77
|
+
- @pexip/media-processor@20.2.0
|
|
78
|
+
- @pexip/signal@16.9.0
|
|
79
|
+
- @pexip/media-control@20.2.0
|
|
80
|
+
- @pexip/utils@17.0.0
|
|
81
|
+
|
|
3
82
|
## 20.1.0
|
|
4
83
|
|
|
5
84
|
### 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.d.ts
CHANGED
|
@@ -19,4 +19,4 @@ export declare const createMediaUpdater: ({ getUserMedia, getCurrentDevices, sho
|
|
|
19
19
|
*
|
|
20
20
|
* @param options - @see MediaOptions
|
|
21
21
|
*/
|
|
22
|
-
export declare const createMedia: ({ getMuteState, signals, audioProcessors, videoProcessors, getDefaultConstraints, }: MediaOptions) => MediaController;
|
|
22
|
+
export declare const createMedia: ({ getMuteState, signals, audioProcessors, videoProcessors, stopVideoTrackAsMute, getDefaultConstraints, }: MediaOptions) => MediaController;
|
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';
|
|
@@ -15,27 +15,35 @@ export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDisc
|
|
|
15
15
|
return async (constraints, currentMedia) => {
|
|
16
16
|
const currentDevices = await getCurrentDevices();
|
|
17
17
|
const permission = await getInputDevicePermission();
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
18
|
+
const releaseAudio = constraints.audio === false || permission.audio === 'denied';
|
|
19
|
+
const releaseVideo = constraints.video === false || permission.video === 'denied';
|
|
20
|
+
if (releaseAudio || releaseVideo) {
|
|
21
|
+
// Release related
|
|
21
22
|
for (const track of currentMedia?.getTracks() ?? []) {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
if ((track.kind === 'audioinput' && releaseAudio) ||
|
|
24
|
+
(track.kind === 'videoinput' && releaseVideo)) {
|
|
25
|
+
await track.release();
|
|
26
|
+
currentMedia?.removeTrack(track);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// When both are true, there is nothing to request
|
|
30
|
+
if (releaseAudio && releaseVideo) {
|
|
31
|
+
currentMedia?.setOriginalConstraints(constraints);
|
|
32
|
+
const media = currentMedia ??
|
|
33
|
+
buildMedia({
|
|
34
|
+
constraints,
|
|
35
|
+
permission,
|
|
36
|
+
devices: currentDevices,
|
|
37
|
+
status: permission.audio === 'denied' &&
|
|
38
|
+
permission.video === 'denied'
|
|
39
|
+
? UserMediaStatus.PermissionsRejected
|
|
40
|
+
: UserMediaStatus.PermissionsGranted,
|
|
41
|
+
stream: undefined,
|
|
42
|
+
signals,
|
|
43
|
+
tracks: [],
|
|
44
|
+
});
|
|
45
|
+
return onMediaTracksChanged(media, []);
|
|
24
46
|
}
|
|
25
|
-
currentMedia?.setOriginalConstraints(constraints);
|
|
26
|
-
const media = currentMedia ??
|
|
27
|
-
buildMedia({
|
|
28
|
-
constraints,
|
|
29
|
-
permission,
|
|
30
|
-
devices: currentDevices,
|
|
31
|
-
status: permissionRejected
|
|
32
|
-
? UserMediaStatus.PermissionsRejected
|
|
33
|
-
: UserMediaStatus.PermissionsGranted,
|
|
34
|
-
stream: undefined,
|
|
35
|
-
signals,
|
|
36
|
-
tracks: [],
|
|
37
|
-
});
|
|
38
|
-
return onMediaTracksChanged(media, []);
|
|
39
47
|
}
|
|
40
48
|
const { audio: prevAudioSettings, video: prevVideoSettings } = currentMedia?.getSettings() ?? {};
|
|
41
49
|
const videoFeatures = getVideoFeatures(constraints.video, {});
|
|
@@ -206,9 +214,20 @@ const createMediaPropsHandler = (signals) => ({
|
|
|
206
214
|
}
|
|
207
215
|
return true;
|
|
208
216
|
}
|
|
209
|
-
case '
|
|
217
|
+
case 'updatingAudio': {
|
|
218
|
+
if (target[p] === value) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
const result = Reflect.set(target, p, value);
|
|
222
|
+
signals?.onUpdatingAudio?.emit(value);
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
case 'updatingVideo': {
|
|
226
|
+
if (target[p] === value) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
210
229
|
const result = Reflect.set(target, p, value);
|
|
211
|
-
signals?.
|
|
230
|
+
signals?.onUpdatingVideo?.emit(value);
|
|
212
231
|
return result;
|
|
213
232
|
}
|
|
214
233
|
default: {
|
|
@@ -223,11 +242,12 @@ const createMediaPropsHandler = (signals) => ({
|
|
|
223
242
|
*
|
|
224
243
|
* @param options - @see MediaOptions
|
|
225
244
|
*/
|
|
226
|
-
export const createMedia = ({ getMuteState, signals, audioProcessors, videoProcessors, getDefaultConstraints = () => ({}), }) => {
|
|
245
|
+
export const createMedia = ({ getMuteState, signals, audioProcessors, videoProcessors, stopVideoTrackAsMute = () => true, getDefaultConstraints = () => ({}), }) => {
|
|
227
246
|
const _props = {
|
|
228
247
|
devices: createIndexedDevices([]),
|
|
229
248
|
discardMedia: false,
|
|
230
|
-
|
|
249
|
+
updatingAudio: false,
|
|
250
|
+
updatingVideo: false,
|
|
231
251
|
};
|
|
232
252
|
const props = new Proxy(_props, createMediaPropsHandler(signals));
|
|
233
253
|
const queue = createAsyncQueue({
|
|
@@ -263,28 +283,6 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
263
283
|
props.devices = createIndexedDevices(await getDevices());
|
|
264
284
|
return props.devices;
|
|
265
285
|
};
|
|
266
|
-
const syncMuteState = (tracks) => {
|
|
267
|
-
const inputMuted = getMuteState();
|
|
268
|
-
for (const track of tracks) {
|
|
269
|
-
switch (track.kind) {
|
|
270
|
-
case 'audioinput': {
|
|
271
|
-
track.mute(inputMuted.audio);
|
|
272
|
-
// FIXME: this can fire too often, but we want to make sure we signal initial state as well.
|
|
273
|
-
// We should only fire this when needed.
|
|
274
|
-
signals.onAudioMuteStateChanged?.emit(track.muted);
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
case 'videoinput':
|
|
278
|
-
track.mute(inputMuted.video);
|
|
279
|
-
// FIXME: this can fire too often, but we want to make sure we signal initial state as well.
|
|
280
|
-
// We should only fire this when needed.
|
|
281
|
-
signals.onVideoMuteStateChanged?.emit(track.muted);
|
|
282
|
-
break;
|
|
283
|
-
default:
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
286
|
const processMedia = createMediaProcessor({
|
|
289
287
|
audioProcessors,
|
|
290
288
|
videoProcessors,
|
|
@@ -304,19 +302,22 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
304
302
|
},
|
|
305
303
|
});
|
|
306
304
|
const getUserMediaProcess = createGetUserMediaProcess({
|
|
307
|
-
getUserMedia: requestUserMediaWithRetry(getCurrentDevices),
|
|
308
305
|
getCurrentDevices,
|
|
306
|
+
getUserMedia: requestUserMediaWithRetry(getCurrentDevices),
|
|
309
307
|
signals,
|
|
308
|
+
stopVideoTrackAsMute,
|
|
309
|
+
updateMedia: constraints => updateMedia(constraints),
|
|
310
310
|
});
|
|
311
311
|
const processAndUpdateMedia = async (media, tracks) => {
|
|
312
312
|
const processedTracks = await processMedia(tracks);
|
|
313
|
-
// Sync mute state to processed tracks
|
|
314
|
-
syncMuteState(processedTracks);
|
|
315
313
|
for (const [idx, track] of processedTracks.entries()) {
|
|
316
314
|
const originTrack = tracks.at(idx);
|
|
317
315
|
assert(originTrack, 'Processed track should always has the original track in the same order');
|
|
318
316
|
// Only replace track when they are not the same
|
|
319
317
|
if (originTrack.id !== track.id) {
|
|
318
|
+
if (originTrack.muted !== undefined) {
|
|
319
|
+
track.mute(originTrack.muted);
|
|
320
|
+
}
|
|
320
321
|
media.removeTrack(originTrack);
|
|
321
322
|
media.addTrack(track);
|
|
322
323
|
}
|
|
@@ -329,19 +330,12 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
329
330
|
shouldDiscardMedia: () => props.discardMedia,
|
|
330
331
|
getDefaultConstraints,
|
|
331
332
|
onMediaTracksChanged: (media, tracks) => {
|
|
332
|
-
|
|
333
|
-
syncMuteState(tracks);
|
|
333
|
+
const inputMuted = getMuteState();
|
|
334
334
|
for (const track of tracks) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
unsubscribe();
|
|
340
|
-
},
|
|
341
|
-
mute: signals.onStreamTrackMuted?.emit,
|
|
342
|
-
unmute: signals.onStreamTrackUnmuted?.emit,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
335
|
+
// Sync mute state to input tracks
|
|
336
|
+
track.mute(track.kind === 'audioinput'
|
|
337
|
+
? inputMuted.audio
|
|
338
|
+
: inputMuted.video);
|
|
345
339
|
}
|
|
346
340
|
logger.debug({ media, tracks }, 'Update media tracks');
|
|
347
341
|
props.media = media;
|
|
@@ -359,12 +353,24 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
359
353
|
* @param constraints - @see MediaDeviceRequest
|
|
360
354
|
*/
|
|
361
355
|
const updateMedia = async (constraints) => {
|
|
356
|
+
const updateAudio = constraints.audio !== undefined;
|
|
357
|
+
const updateVideo = constraints.video !== undefined;
|
|
362
358
|
try {
|
|
363
|
-
|
|
359
|
+
if (updateAudio) {
|
|
360
|
+
props.updatingAudio = true;
|
|
361
|
+
}
|
|
362
|
+
if (updateVideo) {
|
|
363
|
+
props.updatingVideo = true;
|
|
364
|
+
}
|
|
364
365
|
await updateMediaProcess(constraints, props.media);
|
|
365
366
|
}
|
|
366
367
|
finally {
|
|
367
|
-
|
|
368
|
+
if (updateAudio) {
|
|
369
|
+
props.updatingAudio = false;
|
|
370
|
+
}
|
|
371
|
+
if (updateVideo) {
|
|
372
|
+
props.updatingVideo = false;
|
|
373
|
+
}
|
|
368
374
|
}
|
|
369
375
|
};
|
|
370
376
|
const mergeMediaConstraints = (constraints) => {
|
|
@@ -424,8 +430,11 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
424
430
|
navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange);
|
|
425
431
|
}
|
|
426
432
|
return {
|
|
427
|
-
get
|
|
428
|
-
return props.
|
|
433
|
+
get updatingAudio() {
|
|
434
|
+
return props.updatingAudio;
|
|
435
|
+
},
|
|
436
|
+
get updatingVideo() {
|
|
437
|
+
return props.updatingVideo;
|
|
429
438
|
},
|
|
430
439
|
get media() {
|
|
431
440
|
return props.media;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IndexedDevices, MediaDeviceInfoLike, MediaDeviceRequest } from '@pexip/media-control';
|
|
1
|
+
import type { IndexedDevices, InputConstraintSet, MediaDeviceInfoLike, MediaDeviceRequest } from '@pexip/media-control';
|
|
2
2
|
import type { AsyncQueueOptions } from '@pexip/utils';
|
|
3
3
|
import type { Media, MediaSignals, TrackProcessor, Unsubscribe } from './types';
|
|
4
4
|
type EventCallback<T> = (event: T) => void;
|
|
@@ -12,30 +12,45 @@ export interface PreviewEventHandler {
|
|
|
12
12
|
audioInputError?: EventErrorCallback;
|
|
13
13
|
applyChangesError?: EventErrorCallback;
|
|
14
14
|
revertChangesError?: EventErrorCallback;
|
|
15
|
-
|
|
15
|
+
audioMuted?: EventCallback<boolean | undefined>;
|
|
16
|
+
audioSuspended?: EventCallback<boolean | undefined>;
|
|
16
17
|
updatingMain?: EventCallback<boolean>;
|
|
18
|
+
updatingPreviewAudio?: EventCallback<boolean>;
|
|
19
|
+
updatingPreviewVideo?: EventCallback<boolean>;
|
|
20
|
+
videoMuted?: EventCallback<boolean | undefined>;
|
|
21
|
+
videoSuspended?: EventCallback<boolean | undefined>;
|
|
17
22
|
unsubscribeMain?: Unsubscribe;
|
|
18
23
|
}
|
|
19
24
|
export interface PreviewStreamParams {
|
|
20
25
|
getCurrentDevices: () => IndexedDevices;
|
|
21
26
|
getCurrentMedia: () => Media | undefined;
|
|
22
27
|
updateMainStream: (request: MediaDeviceRequest) => Promise<void>;
|
|
23
|
-
|
|
28
|
+
mainMediaSignal: MediaSignals['onMediaChanged'];
|
|
29
|
+
signals: MediaSignals;
|
|
24
30
|
onEnded?: () => void;
|
|
25
31
|
fftSize?: number;
|
|
26
32
|
queueOptions?: Partial<AsyncQueueOptions>;
|
|
27
33
|
audioProcessors: TrackProcessor[];
|
|
28
34
|
videoProcessors: TrackProcessor[];
|
|
35
|
+
/**
|
|
36
|
+
* Pass default constraints to use with get media wrappers
|
|
37
|
+
*/
|
|
38
|
+
getDefaultConstraints?: () => {
|
|
39
|
+
audio?: InputConstraintSet | false;
|
|
40
|
+
video?: InputConstraintSet | false;
|
|
41
|
+
};
|
|
29
42
|
}
|
|
30
43
|
export interface PreviewControllerProps {
|
|
31
44
|
media: Media | undefined;
|
|
32
45
|
audioInput?: MediaDeviceInfoLike;
|
|
33
46
|
videoInput?: MediaDeviceInfoLike;
|
|
34
|
-
|
|
47
|
+
updatingPreviewAudio: boolean;
|
|
48
|
+
updatingPreviewVideo: boolean;
|
|
35
49
|
updatingMain: boolean;
|
|
36
50
|
originalMainAudioInput?: MediaDeviceInfoLike;
|
|
37
51
|
discardMedia: boolean;
|
|
38
52
|
initialized: boolean;
|
|
53
|
+
signals: MediaSignals;
|
|
39
54
|
}
|
|
40
55
|
export interface PreviewStreamController {
|
|
41
56
|
media: Media | undefined;
|
|
@@ -44,8 +59,10 @@ export interface PreviewStreamController {
|
|
|
44
59
|
inputChanged: boolean;
|
|
45
60
|
audioInput: PreviewInput;
|
|
46
61
|
videoInput: PreviewInput;
|
|
47
|
-
updatingPreview: boolean;
|
|
48
62
|
updatingMain: boolean;
|
|
63
|
+
updatingPreviewAudio: boolean;
|
|
64
|
+
updatingPreviewVideo: boolean;
|
|
65
|
+
updatePreviewInput(input: PreviewInput): void;
|
|
49
66
|
updateAudioInput(id: string): void;
|
|
50
67
|
updateVideoInput(id: string): void;
|
|
51
68
|
applyChanges(force?: boolean): Promise<void>;
|
|
@@ -54,13 +71,18 @@ export interface PreviewStreamController {
|
|
|
54
71
|
onMediaChanged(callback: EventCallback<Media>): Unsubscribe;
|
|
55
72
|
onAudioInputChanged(callback: EventCallback<PreviewInput>): Unsubscribe;
|
|
56
73
|
onVideoInputChanged(callback: EventCallback<PreviewInput>): Unsubscribe;
|
|
57
|
-
|
|
74
|
+
onUpdatingPreviewAudio(callback: EventCallback<boolean>): Unsubscribe;
|
|
75
|
+
onUpdatingPreviewVideo(callback: EventCallback<boolean>): Unsubscribe;
|
|
58
76
|
onUpdatingMain(callback: EventCallback<boolean>): Unsubscribe;
|
|
59
77
|
onAudioInputError(callback: EventErrorCallback): Unsubscribe;
|
|
60
78
|
onVideoInputError(callback: EventErrorCallback): Unsubscribe;
|
|
61
79
|
onApplyChangesError(callback: EventErrorCallback): Unsubscribe;
|
|
62
80
|
onRevertChangesError(callback: EventErrorCallback): Unsubscribe;
|
|
81
|
+
onAudioMuted(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
82
|
+
onAudioSuspended(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
83
|
+
onVideoMuted(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
84
|
+
onVideoSuspended(callback: EventCallback<boolean | undefined>): Unsubscribe;
|
|
63
85
|
}
|
|
64
|
-
export declare const createPreviewStreamController: ({ getCurrentDevices, getCurrentMedia, updateMainStream, onEnded,
|
|
86
|
+
export declare const createPreviewStreamController: ({ getCurrentDevices, getCurrentMedia, updateMainStream, onEnded, mainMediaSignal, queueOptions, audioProcessors, videoProcessors, getDefaultConstraints, signals, }: PreviewStreamParams) => PreviewStreamController;
|
|
65
87
|
export type CreatePreviewStreamController = typeof createPreviewStreamController;
|
|
66
88
|
export {};
|