@pexip/media 19.1.0 → 20.0.1
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 +73 -0
- package/dist/audioMixingProcessor.d.ts +1 -1
- package/dist/audioMixingProcessor.js +5 -5
- package/dist/audioProcessor.d.ts +1 -1
- package/dist/audioProcessor.js +4 -2
- package/dist/baseLogger.d.ts +15 -6
- package/dist/baseLogger.js +13 -7
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/displayMedia.d.ts +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/logger.js +1 -4
- package/dist/media.d.ts +6 -2
- package/dist/media.js +92 -51
- package/dist/previewController.js +3 -4
- package/dist/signals.d.ts +4 -4
- package/dist/signals.js +8 -5
- package/dist/status.d.ts +2 -1
- package/dist/status.js +8 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/typeGuard.d.ts +2 -1
- package/dist/typeGuard.js +3 -0
- package/dist/types.d.ts +3 -3
- package/dist/userMedia.d.ts +8 -1
- package/dist/userMedia.js +357 -20
- package/dist/utils.d.ts +10 -8
- package/dist/utils.js +81 -37
- package/dist/videoProcessor.d.ts +5 -3
- package/dist/videoProcessor.js +53 -60
- package/package.json +22 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# @pexip/media
|
|
2
2
|
|
|
3
|
+
## 20.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 091652a: emit `onAudioMuteStateChanged` and `onVideoMuteStateChanged` each
|
|
8
|
+
time we call `syncMuteState`
|
|
9
|
+
- @pexip/media-control@20.0.1
|
|
10
|
+
- @pexip/media-processor@20.0.1
|
|
11
|
+
- @pexip/signal@16.8.0
|
|
12
|
+
- @pexip/utils@17.0.0
|
|
13
|
+
|
|
14
|
+
## 20.0.0
|
|
15
|
+
|
|
16
|
+
### Major Changes
|
|
17
|
+
|
|
18
|
+
- 2bfd9e1: Adds esm entry points.
|
|
19
|
+
- c975587: `dynamicProcessingDimensions` is a func.
|
|
20
|
+
- 7679b89: Removes `adjustResolution`.
|
|
21
|
+
|
|
22
|
+
### Minor Changes
|
|
23
|
+
|
|
24
|
+
- dd5a4eb: Adds optional `gpuAPI` getter, sets the transformer backend when
|
|
25
|
+
opened.
|
|
26
|
+
- 34bf3dd: Add isVideoStreamTrackProcessorAPIs
|
|
27
|
+
- 5855473: Updates the segmenter processing width and height based on the track
|
|
28
|
+
settings if `dynamicProcessingDimensions` is `true` in `videoProcessor`.
|
|
29
|
+
- 3555d9d: Adds `build:sourcemap` script.
|
|
30
|
+
- 7d94f5c: Take unauthorized device into account
|
|
31
|
+
- ed53b05: Upgrade Typescript
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- 4146978: Bump logger proxy set to info level
|
|
36
|
+
- 0beec97: Fix mergeStatus to take requesting single device into consideration
|
|
37
|
+
|
|
38
|
+
When changing 1 device should not affect another type of device
|
|
39
|
+
|
|
40
|
+
- 67a18d0: Use recommended linter rules
|
|
41
|
+
- e1882e7: Request media only when there is no prompt permission
|
|
42
|
+
- 3882f98: No unused imports
|
|
43
|
+
- 50d0af0: Assert track and input
|
|
44
|
+
- 5adb1aa: Update according Logger changes
|
|
45
|
+
- 79c52c9: No signals reuse for clone
|
|
46
|
+
- 43d0b9c: Always use the merged constraints
|
|
47
|
+
- 6470495: Fix applyFeatures
|
|
48
|
+
- 6d68da2: Stop processing non-live track
|
|
49
|
+
- 58ecca1: Fix mergeStatus fallback case
|
|
50
|
+
- e76a15e: Migrate to biome linter
|
|
51
|
+
- Updated dependencies [2bfd9e1]
|
|
52
|
+
- Updated dependencies [4fe77de]
|
|
53
|
+
- Updated dependencies [b0d8af9]
|
|
54
|
+
- Updated dependencies [2bbff64]
|
|
55
|
+
- Updated dependencies [4baa8a4]
|
|
56
|
+
- Updated dependencies [db3a370]
|
|
57
|
+
- Updated dependencies [4ea0492]
|
|
58
|
+
- Updated dependencies [3555d9d]
|
|
59
|
+
- Updated dependencies [d3909ed]
|
|
60
|
+
- Updated dependencies [0416d6c]
|
|
61
|
+
- Updated dependencies [1601985]
|
|
62
|
+
- Updated dependencies [7d94f5c]
|
|
63
|
+
- Updated dependencies [3cb5aee]
|
|
64
|
+
- Updated dependencies [fa06b8b]
|
|
65
|
+
- Updated dependencies [84956b8]
|
|
66
|
+
- Updated dependencies [ed53b05]
|
|
67
|
+
- Updated dependencies [5855473]
|
|
68
|
+
- Updated dependencies [941363c]
|
|
69
|
+
- Updated dependencies [d681fd3]
|
|
70
|
+
- Updated dependencies [e76a15e]
|
|
71
|
+
- @pexip/media-processor@20.0.0
|
|
72
|
+
- @pexip/media-control@20.0.0
|
|
73
|
+
- @pexip/utils@17.0.0
|
|
74
|
+
- @pexip/signal@16.8.0
|
|
75
|
+
|
|
3
76
|
## 19.1.0
|
|
4
77
|
|
|
5
78
|
### Minor Changes
|
|
@@ -10,7 +10,7 @@ interface AudioStreamProcessorProps {
|
|
|
10
10
|
declare const FEATURE_KEYS: ['mixWithAdditionalMedia'];
|
|
11
11
|
type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
|
|
12
12
|
type FeatureProps = Pick<AudioStreamProcessorProps, FeaturePropKeys>;
|
|
13
|
-
export declare const updateFeatureProps: (constraints: MediaDeviceRequest[
|
|
13
|
+
export declare const updateFeatureProps: (constraints: MediaDeviceRequest["audio"], props: FeatureProps) => FeatureProps;
|
|
14
14
|
/**
|
|
15
15
|
* Create a Audio Mixing Processor and will own the stream passed-in
|
|
16
16
|
*/
|
|
@@ -12,7 +12,8 @@ export const updateFeatureProps = (constraints, props) => {
|
|
|
12
12
|
const [feature] = extracted[key];
|
|
13
13
|
if (feature !== undefined && props[key] !== feature) {
|
|
14
14
|
props[key] = feature;
|
|
15
|
-
|
|
15
|
+
accm[key] = feature;
|
|
16
|
+
return accm;
|
|
16
17
|
}
|
|
17
18
|
return accm;
|
|
18
19
|
}, {});
|
|
@@ -155,11 +156,10 @@ export const createAudioMixingProcess = (getCurrentMedia, label = PROCESSOR_LABE
|
|
|
155
156
|
overrideMute: true,
|
|
156
157
|
mute: toMute => {
|
|
157
158
|
prevMediaTrack.mute(toMute);
|
|
158
|
-
mainSource.node?.mediaStream
|
|
159
|
-
|
|
160
|
-
.forEach(track => {
|
|
159
|
+
for (const track of mainSource.node?.mediaStream.getAudioTracks() ??
|
|
160
|
+
[]) {
|
|
161
161
|
track.enabled = !toMute;
|
|
162
|
-
}
|
|
162
|
+
}
|
|
163
163
|
},
|
|
164
164
|
get muted() {
|
|
165
165
|
return (
|
package/dist/audioProcessor.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ interface AudioStreamProcessorProps {
|
|
|
78
78
|
declare const FEATURE_KEYS: ['denoise', 'vad', 'asd', 'contentHint'];
|
|
79
79
|
type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
|
|
80
80
|
type FeatureProps = Pick<AudioStreamProcessorProps, FeaturePropKeys>;
|
|
81
|
-
export declare const updateFeatureProps: (constraints: MediaDeviceRequest[
|
|
81
|
+
export declare const updateFeatureProps: (constraints: MediaDeviceRequest["audio"], props: FeatureProps) => FeatureProps;
|
|
82
82
|
/**
|
|
83
83
|
* Create a Audio Stream Processor and will own the stream passed-in
|
|
84
84
|
*/
|
package/dist/audioProcessor.js
CHANGED
|
@@ -32,7 +32,8 @@ export const updateFeatureProps = (constraints, props) => {
|
|
|
32
32
|
const [[feature] = []] = extracted[key];
|
|
33
33
|
if (isAudioContentHint(feature) && props[key] !== feature) {
|
|
34
34
|
props[key] = feature;
|
|
35
|
-
|
|
35
|
+
accm[key] = feature;
|
|
36
|
+
return accm;
|
|
36
37
|
}
|
|
37
38
|
return accm;
|
|
38
39
|
}
|
|
@@ -42,7 +43,8 @@ export const updateFeatureProps = (constraints, props) => {
|
|
|
42
43
|
const [feature] = extracted[key];
|
|
43
44
|
if (feature !== undefined && props[key] !== feature) {
|
|
44
45
|
props[key] = feature;
|
|
45
|
-
|
|
46
|
+
accm[key] = feature;
|
|
47
|
+
return accm;
|
|
46
48
|
}
|
|
47
49
|
return accm;
|
|
48
50
|
}
|
package/dist/baseLogger.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare enum LogLevels {
|
|
|
9
9
|
warn = 40,
|
|
10
10
|
error = 50,
|
|
11
11
|
fatal = 60,
|
|
12
|
-
silent
|
|
12
|
+
silent = 9007199254740991
|
|
13
13
|
}
|
|
14
14
|
export type LogLevelsString = keyof typeof LogLevels;
|
|
15
15
|
export type LogMethods = {
|
|
@@ -21,14 +21,23 @@ export type LogMethods = {
|
|
|
21
21
|
*/
|
|
22
22
|
export interface Logger extends LogMethods {
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Add a pattern to the redactor and an optional mask for the redaction.
|
|
25
25
|
*
|
|
26
|
-
* @
|
|
27
|
-
* The redaction set is applied globally, and only applies to the log file, not console logs.
|
|
26
|
+
* @see {@link Redactor}
|
|
28
27
|
*
|
|
29
|
-
* @param value -
|
|
28
|
+
* @param value - The pattern to add.
|
|
29
|
+
* @param replaceTo - The mask to use for redaction.
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
addRedactPattern(value: string, redactTo?: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Redact the provided string by replacing all occurrences of the patterns
|
|
34
|
+
* and return a new string with redacted values.
|
|
35
|
+
*
|
|
36
|
+
* @see {@link Redactor}
|
|
37
|
+
*
|
|
38
|
+
* @param text - The string to redact.
|
|
39
|
+
*/
|
|
40
|
+
redact(text: string): string;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* Create a logger with console API, and map fatal to error, skipping trace
|
package/dist/baseLogger.js
CHANGED
|
@@ -6,8 +6,7 @@ export var LogLevels;
|
|
|
6
6
|
LogLevels[LogLevels["warn"] = 40] = "warn";
|
|
7
7
|
LogLevels[LogLevels["error"] = 50] = "error";
|
|
8
8
|
LogLevels[LogLevels["fatal"] = 60] = "fatal";
|
|
9
|
-
|
|
10
|
-
LogLevels[LogLevels["silent"] = Number.MAX_SAFE_INTEGER] = "silent";
|
|
9
|
+
LogLevels[LogLevels["silent"] = 9007199254740991] = "silent";
|
|
11
10
|
})(LogLevels || (LogLevels = {}));
|
|
12
11
|
/**
|
|
13
12
|
* Create a logger with console API, and map fatal to error, skipping trace
|
|
@@ -15,15 +14,22 @@ export var LogLevels;
|
|
|
15
14
|
*/
|
|
16
15
|
export function createConsoleLogger() {
|
|
17
16
|
return Object.freeze({
|
|
18
|
-
/* eslint-disable no-console -- set logger to console */
|
|
19
17
|
fatal: (meta, message) => console.error(message, meta),
|
|
20
18
|
error: (meta, message) => console.error(message, meta),
|
|
21
19
|
warn: (meta, message) => console.warn(message, meta),
|
|
22
20
|
info: (meta, message) => console.info(message, meta),
|
|
23
21
|
debug: (meta, message) => console.debug(message, meta),
|
|
24
|
-
trace() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
trace() {
|
|
23
|
+
// Noop
|
|
24
|
+
},
|
|
25
|
+
silent() {
|
|
26
|
+
// Noop
|
|
27
|
+
},
|
|
28
|
+
redact(text) {
|
|
29
|
+
return text;
|
|
30
|
+
},
|
|
31
|
+
addRedactPattern() {
|
|
32
|
+
// Noop
|
|
33
|
+
},
|
|
28
34
|
});
|
|
29
35
|
}
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
package/dist/displayMedia.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { DisplayMediaOptions } from '@pexip/media-control';
|
|
2
|
-
export declare const createGetDisplayMedia: (getDefaultConstraints: () => DisplayMediaOptions, getDisplayMedia?: MediaDevices[
|
|
2
|
+
export declare const createGetDisplayMedia: (getDefaultConstraints: () => DisplayMediaOptions, getDisplayMedia?: MediaDevices["getDisplayMedia"]) => (constraints?: DisplayMediaOptions) => Promise<MediaStream | undefined>;
|
|
3
3
|
export type GetDisplayMedia = ReturnType<typeof createGetDisplayMedia>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
export
|
|
2
|
-
export { createAudioStreamProcess } from './audioProcessor';
|
|
3
|
-
export { createVideoStreamProcess } from './videoProcessor';
|
|
4
|
-
export * from './types';
|
|
5
|
-
export { setLogger } from './logger';
|
|
1
|
+
export * from './displayMedia';
|
|
6
2
|
export * from './previewController';
|
|
7
|
-
export * from './status';
|
|
8
3
|
export * from './signals';
|
|
4
|
+
export * from './status';
|
|
5
|
+
export * from './typeGuard';
|
|
6
|
+
export * from './types';
|
|
9
7
|
export { applyContentHint } from './utils';
|
|
10
8
|
export { createAudioMixingProcess } from './audioMixingProcessor';
|
|
11
|
-
export
|
|
9
|
+
export { createAudioStreamProcess } from './audioProcessor';
|
|
10
|
+
export { createMedia } from './media';
|
|
11
|
+
export { createVideoStreamProcess } from './videoProcessor';
|
|
12
|
+
export { setLogger } from './logger';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
export
|
|
2
|
-
export { createAudioStreamProcess } from './audioProcessor';
|
|
3
|
-
export { createVideoStreamProcess } from './videoProcessor';
|
|
4
|
-
export * from './types';
|
|
5
|
-
export { setLogger } from './logger';
|
|
1
|
+
export * from './displayMedia';
|
|
6
2
|
export * from './previewController';
|
|
7
|
-
export * from './status';
|
|
8
3
|
export * from './signals';
|
|
4
|
+
export * from './status';
|
|
5
|
+
export * from './typeGuard';
|
|
6
|
+
export * from './types';
|
|
9
7
|
export { applyContentHint } from './utils';
|
|
10
8
|
export { createAudioMixingProcess } from './audioMixingProcessor';
|
|
11
|
-
export
|
|
9
|
+
export { createAudioStreamProcess } from './audioProcessor';
|
|
10
|
+
export { createMedia } from './media';
|
|
11
|
+
export { createVideoStreamProcess } from './videoProcessor';
|
|
12
|
+
export { setLogger } from './logger';
|
package/dist/logger.js
CHANGED
|
@@ -30,11 +30,8 @@ export const createLogProxyHandler = (logger, name, scope) => {
|
|
|
30
30
|
logger.debug({ scope, name, prop: p, value }, `called get ${name}[${String(p)}]`);
|
|
31
31
|
return value;
|
|
32
32
|
},
|
|
33
|
-
// eslint-disable-next-line max-params --- from lib.es2015 and log
|
|
34
33
|
set: (target, p, value, receiver) => {
|
|
35
|
-
logger.
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- it's a log call...
|
|
37
|
-
{ scope, name, prop: p, value }, `called set ${name}[${String(p)}]`);
|
|
34
|
+
logger.info({ scope, name, prop: p, value }, `called set ${name}[${String(p)}]`);
|
|
38
35
|
return Reflect.set(target, p, value, receiver);
|
|
39
36
|
},
|
|
40
37
|
};
|
package/dist/media.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IndexedDevices, MediaDeviceRequest, InputDevicePermission } from '@pexip/media-control';
|
|
1
|
+
import type { IndexedDevices, MediaDeviceRequest, InputDevicePermission, InputConstraintSet } from '@pexip/media-control';
|
|
2
2
|
import type { GetUserMediaProcess, Media, MediaController, MediaOptions, MediaSignals, MediaTrack } from './types';
|
|
3
3
|
export interface MediaUpdaterOptions {
|
|
4
4
|
getUserMedia: GetUserMediaProcess;
|
|
@@ -7,8 +7,12 @@ export interface MediaUpdaterOptions {
|
|
|
7
7
|
onMediaTracksChanged: (media: Media, tracks: MediaTrack[]) => void;
|
|
8
8
|
getInputDevicePermission?: () => Promise<InputDevicePermission>;
|
|
9
9
|
signals?: MediaSignals;
|
|
10
|
+
getDefaultConstraints?: () => {
|
|
11
|
+
audio?: InputConstraintSet | false;
|
|
12
|
+
video?: InputConstraintSet | false;
|
|
13
|
+
};
|
|
10
14
|
}
|
|
11
|
-
export declare const createMediaUpdater: ({ getUserMedia, getCurrentDevices, shouldDiscardMedia: shouldDisgardMedia, onMediaTracksChanged, getInputDevicePermission, signals, }: MediaUpdaterOptions) => (constraints: MediaDeviceRequest, currentMedia: Media | undefined) => Promise<void>;
|
|
15
|
+
export declare const createMediaUpdater: ({ getUserMedia, getCurrentDevices, shouldDiscardMedia: shouldDisgardMedia, onMediaTracksChanged, getInputDevicePermission, getDefaultConstraints, signals, }: MediaUpdaterOptions) => (constraints: MediaDeviceRequest, currentMedia: Media | undefined) => Promise<void>;
|
|
12
16
|
/**
|
|
13
17
|
* Create an object to interact with the media scream, which is usually used for
|
|
14
18
|
* our main stream.
|
package/dist/media.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createIndexedDevices, createStreamTrackEventSubscriptions, 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';
|
|
5
5
|
import { createModuleLogger, logger } from './logger';
|
|
6
6
|
import { createGetUserMediaProcess, requestUserMediaWithRetry, } from './userMedia';
|
|
7
|
-
import { AUDIO_SETTINGS_KEYS, MIXING_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, buildMedia, createMediaProcessor, diffSettings, getDevicesChanges, hasPtzFeature, mergeSettings, refineMediaConstraints, } from './utils';
|
|
8
|
-
import { getPermissionStatus,
|
|
7
|
+
import { AUDIO_SETTINGS_KEYS, MIXING_SETTINGS_KEYS, PAN_TILT_ZOOM_SETTINGS_KEYS, VIDEO_SETTINGS_KEYS, buildMedia, createMediaProcessor, diffSettings, getDevicesChanges, getSettingsFromKeys, hasPtzFeature, isApplyingRenderEffect, mergeSettings, refineMediaConstraints, } from './utils';
|
|
8
|
+
import { getPermissionStatus, isRequestPermissionNeeded } from './status';
|
|
9
9
|
import { isMedia } from './typeGuard';
|
|
10
10
|
import { updateFeatureProps as getVideoFeatures } from './videoProcessor';
|
|
11
11
|
import { updateFeatureProps as getAudioFeatures } from './audioProcessor';
|
|
12
12
|
import { updateFeatureProps as getMixingFeatures } from './audioMixingProcessor';
|
|
13
|
-
import { GET_USER_MEDIA_TIMEOUT_MS } from './constants';
|
|
14
|
-
export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDiscardMedia: shouldDisgardMedia, onMediaTracksChanged, getInputDevicePermission = getInputDevicePermissionState, signals, }) => {
|
|
13
|
+
import { GET_USER_MEDIA_TIMEOUT_MS, DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS, } from './constants';
|
|
14
|
+
export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDiscardMedia: shouldDisgardMedia, onMediaTracksChanged, getInputDevicePermission = getInputDevicePermissionState, getDefaultConstraints, signals, }) => {
|
|
15
15
|
return async (constraints, currentMedia) => {
|
|
16
16
|
const currentDevices = await getCurrentDevices();
|
|
17
17
|
const permission = await getInputDevicePermission();
|
|
@@ -41,14 +41,26 @@ export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDisc
|
|
|
41
41
|
const videoFeatures = getVideoFeatures(constraints.video, {});
|
|
42
42
|
const audioFeatures = getAudioFeatures(constraints.audio, {});
|
|
43
43
|
const mixingFeatures = getMixingFeatures(constraints.audio, {});
|
|
44
|
-
const hasRequestedResolution =
|
|
44
|
+
const hasRequestedResolution = isApplyingRenderEffect(videoFeatures.videoSegmentation ?? 'none') || // Skip when there is a render effect to modify the video
|
|
45
45
|
currentDevices.size('videoinput') === 0 || // Skip when no authorized video found
|
|
46
46
|
isRequestedResolution(constraints.video, currentMedia?.getVideoTracks().at(0)?.getSettings());
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
const defaultAudioSettings = getSettingsFromKeys(AUDIO_SETTINGS_KEYS, getDefaultConstraints?.().audio);
|
|
48
|
+
const audioFeaturesChanged = defaultAudioSettings === false
|
|
49
|
+
? undefined
|
|
50
|
+
: diffSettings(AUDIO_SETTINGS_KEYS)(prevAudioSettings ?? defaultAudioSettings, audioFeatures);
|
|
51
|
+
const defaultVideoSettings = getSettingsFromKeys(VIDEO_SETTINGS_KEYS, getDefaultConstraints?.().video);
|
|
52
|
+
const videoFeaturesChanged = defaultVideoSettings === false
|
|
53
|
+
? undefined
|
|
54
|
+
: diffSettings(VIDEO_SETTINGS_KEYS)(prevVideoSettings ?? defaultVideoSettings, videoFeatures);
|
|
55
|
+
const defaultMixingSettings = getSettingsFromKeys(MIXING_SETTINGS_KEYS, getDefaultConstraints?.().audio);
|
|
56
|
+
const mixingFeaturesChanged = defaultMixingSettings === false
|
|
57
|
+
? undefined
|
|
58
|
+
: diffSettings(MIXING_SETTINGS_KEYS)(prevAudioSettings ?? defaultMixingSettings, mixingFeatures);
|
|
59
|
+
const defaultPTZSettings = getSettingsFromKeys(PAN_TILT_ZOOM_SETTINGS_KEYS, getDefaultConstraints?.().video);
|
|
60
|
+
const ptzFeaturesChanges = defaultPTZSettings === false
|
|
61
|
+
? undefined
|
|
62
|
+
: hasPtzFeature() &&
|
|
63
|
+
diffSettings(PAN_TILT_ZOOM_SETTINGS_KEYS)(prevVideoSettings ?? defaultPTZSettings, videoFeatures);
|
|
52
64
|
const audioRequest = refineMediaConstraints({
|
|
53
65
|
kind: 'audioinput',
|
|
54
66
|
request: constraints.audio,
|
|
@@ -62,9 +74,9 @@ export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDisc
|
|
|
62
74
|
currentDevices,
|
|
63
75
|
permission: permission.video,
|
|
64
76
|
currentMediaTracks: currentMedia?.getVideoTracks() ?? [],
|
|
65
|
-
force: !hasRequestedResolution ||
|
|
77
|
+
force: !hasRequestedResolution || !isEmpty(ptzFeaturesChanges),
|
|
66
78
|
});
|
|
67
|
-
if (audioRequest
|
|
79
|
+
if (audioRequest || videoRequest) {
|
|
68
80
|
const media = await getUserMedia({
|
|
69
81
|
constraints: { audio: audioRequest, video: videoRequest },
|
|
70
82
|
originalConstraints: constraints,
|
|
@@ -80,6 +92,48 @@ export const createMediaUpdater = ({ getUserMedia, getCurrentDevices, shouldDisc
|
|
|
80
92
|
...(videoRequest ? media.getVideoTracks() : []),
|
|
81
93
|
]);
|
|
82
94
|
}
|
|
95
|
+
else if (currentMedia?.status) {
|
|
96
|
+
// No media request, update status only
|
|
97
|
+
const anyAudioTrack = currentMedia
|
|
98
|
+
.getAudioTracks()
|
|
99
|
+
.some(track => track.track?.readyState === 'live');
|
|
100
|
+
const anyVideoTrack = currentMedia
|
|
101
|
+
.getVideoTracks()
|
|
102
|
+
.some(track => track.track?.readyState === 'live');
|
|
103
|
+
if (anyAudioTrack) {
|
|
104
|
+
if (anyVideoTrack) {
|
|
105
|
+
currentMedia.status = UserMediaStatus.PermissionsGranted;
|
|
106
|
+
}
|
|
107
|
+
else if (currentDevices.size('videoinput') === 0) {
|
|
108
|
+
currentMedia.status =
|
|
109
|
+
UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
currentMedia.status =
|
|
113
|
+
UserMediaStatus.PermissionsOnlyAudioinput;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
if (anyVideoTrack) {
|
|
118
|
+
if (currentDevices.size('audioinput') === 0) {
|
|
119
|
+
currentMedia.status =
|
|
120
|
+
UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
currentMedia.status =
|
|
124
|
+
UserMediaStatus.PermissionsOnlyVideoinput;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (currentDevices.size('audioinput') === 0 &&
|
|
128
|
+
currentDevices.size('videoinput') === 0) {
|
|
129
|
+
currentMedia.status = UserMediaStatus.NoDevicesFound;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
currentMedia.status = UserMediaStatus.PermissionsRejected;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
logger.debug({ status: currentMedia.status }, 'Update existing media status');
|
|
136
|
+
}
|
|
83
137
|
const audioDiff = constraints.audio &&
|
|
84
138
|
audioRequest === undefined &&
|
|
85
139
|
currentMedia?.getAudioTracks().at(0)
|
|
@@ -206,29 +260,25 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
206
260
|
},
|
|
207
261
|
});
|
|
208
262
|
const getCurrentDevices = async () => {
|
|
209
|
-
if (props.devices.size()) {
|
|
210
|
-
return props.devices;
|
|
211
|
-
}
|
|
212
263
|
props.devices = createIndexedDevices(await getDevices());
|
|
213
264
|
return props.devices;
|
|
214
265
|
};
|
|
215
266
|
const syncMuteState = (tracks) => {
|
|
216
267
|
const inputMuted = getMuteState();
|
|
217
268
|
for (const track of tracks) {
|
|
218
|
-
const prevMuted = track.muted;
|
|
219
269
|
switch (track.kind) {
|
|
220
270
|
case 'audioinput': {
|
|
221
271
|
track.mute(inputMuted.audio);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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);
|
|
225
275
|
break;
|
|
226
276
|
}
|
|
227
277
|
case 'videoinput':
|
|
228
278
|
track.mute(inputMuted.video);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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);
|
|
232
282
|
break;
|
|
233
283
|
default:
|
|
234
284
|
break;
|
|
@@ -277,10 +327,11 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
277
327
|
getUserMedia: getUserMediaProcess,
|
|
278
328
|
getCurrentDevices,
|
|
279
329
|
shouldDiscardMedia: () => props.discardMedia,
|
|
330
|
+
getDefaultConstraints,
|
|
280
331
|
onMediaTracksChanged: (media, tracks) => {
|
|
281
332
|
// Sync mute state to input tracks
|
|
282
333
|
syncMuteState(tracks);
|
|
283
|
-
|
|
334
|
+
for (const track of tracks) {
|
|
284
335
|
if (track.track) {
|
|
285
336
|
const unsubscribe = createStreamTrackEventSubscriptions(track.track, {
|
|
286
337
|
ended: track => {
|
|
@@ -291,7 +342,8 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
291
342
|
unmute: signals.onStreamTrackUnmuted?.emit,
|
|
292
343
|
});
|
|
293
344
|
}
|
|
294
|
-
}
|
|
345
|
+
}
|
|
346
|
+
logger.debug({ media, tracks }, 'Update media tracks');
|
|
295
347
|
props.media = media;
|
|
296
348
|
processAndUpdateMedia(media, tracks).catch((error) => {
|
|
297
349
|
if (error instanceof Error) {
|
|
@@ -337,13 +389,14 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
337
389
|
queue.enqueue(async () => {
|
|
338
390
|
const permission = await getInputDevicePermissionState();
|
|
339
391
|
const status = getPermissionStatus(permission);
|
|
340
|
-
|
|
392
|
+
logger.info({ permission, status }, 'tryAndGetUserMedia');
|
|
393
|
+
if (!isRequestPermissionNeeded(status)) {
|
|
341
394
|
await updateMedia(mergeMediaConstraints(constraints));
|
|
342
395
|
}
|
|
343
396
|
else {
|
|
344
397
|
const currentDevices = await getCurrentDevices();
|
|
345
398
|
props.media = buildMedia({
|
|
346
|
-
constraints,
|
|
399
|
+
constraints: mergeMediaConstraints(constraints),
|
|
347
400
|
permission,
|
|
348
401
|
devices: currentDevices,
|
|
349
402
|
status,
|
|
@@ -354,34 +407,22 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
354
407
|
}
|
|
355
408
|
});
|
|
356
409
|
};
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
if (
|
|
360
|
-
|
|
410
|
+
let deviceChangeTimeoutId = 0;
|
|
411
|
+
const clearDeviceChangeTimeout = () => {
|
|
412
|
+
if (deviceChangeTimeoutId) {
|
|
413
|
+
clearTimeout(deviceChangeTimeoutId);
|
|
414
|
+
deviceChangeTimeoutId = 0;
|
|
361
415
|
}
|
|
362
416
|
};
|
|
363
|
-
const handleDeviceChange = (
|
|
364
|
-
|
|
417
|
+
const handleDeviceChange = () => {
|
|
418
|
+
logger.debug('devicechange event triggered');
|
|
419
|
+
clearDeviceChangeTimeout();
|
|
420
|
+
deviceChangeTimeoutId = setTimeout(getCurrentDevices, DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS);
|
|
365
421
|
};
|
|
366
422
|
// Subscribe Media Events for devices
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
logger.debug(event.detail, 'NoInputDevices emitted');
|
|
371
|
-
handleNoInputs(event.detail.devices);
|
|
372
|
-
break;
|
|
373
|
-
case MediaEventType.DevicesFound:
|
|
374
|
-
case MediaEventType.DevicesLost:
|
|
375
|
-
logger.debug(event.detail, 'DevicesFound/Lost emitted');
|
|
376
|
-
handleDeviceChange([
|
|
377
|
-
...event.detail.authorizedDevices,
|
|
378
|
-
...event.detail.unauthorizedDevices,
|
|
379
|
-
]);
|
|
380
|
-
break;
|
|
381
|
-
default:
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
});
|
|
423
|
+
if ('mediaDevices' in navigator) {
|
|
424
|
+
navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange);
|
|
425
|
+
}
|
|
385
426
|
return {
|
|
386
427
|
get updatingMedia() {
|
|
387
428
|
return props.updatingMedia;
|
|
@@ -21,7 +21,8 @@ const extractFeaturesToConstraints = (keysToLookFor, settings) => {
|
|
|
21
21
|
}
|
|
22
22
|
return keysToLookFor.reduce((set, key) => {
|
|
23
23
|
if (settings[key] !== undefined) {
|
|
24
|
-
|
|
24
|
+
// @ts-expect-error performance
|
|
25
|
+
set[key] = settings[key];
|
|
25
26
|
}
|
|
26
27
|
return set;
|
|
27
28
|
}, {});
|
|
@@ -289,11 +290,9 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
|
|
|
289
290
|
const { audio: previewAudio, video: previewVideo } = props.media?.getSettings() ?? { audio: {}, video: {} };
|
|
290
291
|
if (force || hasChanges()) {
|
|
291
292
|
const audioSettings = extractFeaturesToConstraints(AUDIO_SETTINGS_KEYS, previewAudio);
|
|
292
|
-
const videoSettings = extractFeaturesToConstraints(VIDEO_SETTINGS_KEYS, previewVideo);
|
|
293
293
|
// Omit the width & height settings from the preview so that the one
|
|
294
294
|
// from the main can be applied
|
|
295
|
-
|
|
296
|
-
delete videoSettings.height;
|
|
295
|
+
const { width, height, ...videoSettings } = extractFeaturesToConstraints(VIDEO_SETTINGS_KEYS, previewVideo);
|
|
297
296
|
logger.info({ audioInput, videoInput, audioSettings, videoSettings }, 'Apply changes to main');
|
|
298
297
|
try {
|
|
299
298
|
await replaceMainStream({
|
package/dist/signals.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Signal, SignalVariant } from '@pexip/signal';
|
|
2
2
|
import type { IndexedDevices } from '@pexip/media-control';
|
|
3
|
-
import type { MediaSignals } from './types';
|
|
3
|
+
import type { MediaSignals, MediaSignalsOptional } from './types';
|
|
4
4
|
/**
|
|
5
5
|
* Create a general signal with consistent scoped name
|
|
6
6
|
*
|
|
@@ -8,7 +8,7 @@ import type { MediaSignals } from './types';
|
|
|
8
8
|
* @param crucial - Signify if the signal is unmissable. @defaultValue true
|
|
9
9
|
* @param variant - The variant of the signal @see Signal @defaultValue 'generic'
|
|
10
10
|
*/
|
|
11
|
-
export declare const createMediaSignal: <T = undefined>(name: string, crucial?: boolean, variant?: SignalVariant) => Signal<T>;
|
|
11
|
+
export declare const createMediaSignal: <T = undefined>(name: string, crucial?: boolean, variant?: Exclude<SignalVariant, "batched">) => Signal<T, T>;
|
|
12
12
|
export declare const REQUIRED_SIGNAL_KEYS: readonly ["onMediaChanged", "onVAD", "onSilentDetected"];
|
|
13
13
|
/**
|
|
14
14
|
* Create and return all required and optional (if specified with `more`),
|
|
@@ -23,12 +23,12 @@ export declare const REQUIRED_SIGNAL_KEYS: readonly ["onMediaChanged", "onVAD",
|
|
|
23
23
|
*
|
|
24
24
|
* @see REQUIRED_SIGNAL_KEYS
|
|
25
25
|
*/
|
|
26
|
-
export declare const createMediaSignals: <K extends
|
|
26
|
+
export declare const createMediaSignals: <K extends keyof MediaSignalsOptional>(more: K[], scope?: string) => Pick<Required<MediaSignals>, "onMediaChanged" | "onVAD" | "onSilentDetected" | K>;
|
|
27
27
|
export type GeneratedMediaSignals<K extends keyof MediaSignals> = Pick<Required<MediaSignals>, K>;
|
|
28
28
|
/**
|
|
29
29
|
* Internal signals
|
|
30
30
|
* @internal
|
|
31
31
|
*/
|
|
32
32
|
export declare const internalSignals: {
|
|
33
|
-
onDevicesChanged: Signal<IndexedDevices>;
|
|
33
|
+
onDevicesChanged: Signal<IndexedDevices, IndexedDevices>;
|
|
34
34
|
};
|
package/dist/signals.js
CHANGED
|
@@ -6,7 +6,9 @@ import { createSignal } from '@pexip/signal';
|
|
|
6
6
|
* @param crucial - Signify if the signal is unmissable. @defaultValue true
|
|
7
7
|
* @param variant - The variant of the signal @see Signal @defaultValue 'generic'
|
|
8
8
|
*/
|
|
9
|
-
export const createMediaSignal = (name, crucial = true,
|
|
9
|
+
export const createMediaSignal = (name, crucial = true,
|
|
10
|
+
// FIXME: need `schedule` function to support `batched` variant
|
|
11
|
+
variant = 'generic') => createSignal({
|
|
10
12
|
name: `media/${name}`,
|
|
11
13
|
allowEmittingWithoutObserver: !crucial,
|
|
12
14
|
variant,
|
|
@@ -31,10 +33,11 @@ export const REQUIRED_SIGNAL_KEYS = [
|
|
|
31
33
|
*/
|
|
32
34
|
export const createMediaSignals = (more, scope = '') => {
|
|
33
35
|
const signalScope = scope && [scope, ':'].join('');
|
|
34
|
-
return [...REQUIRED_SIGNAL_KEYS, ...more].reduce((signals, key) =>
|
|
35
|
-
|
|
36
|
-
[key]
|
|
37
|
-
|
|
36
|
+
return [...REQUIRED_SIGNAL_KEYS, ...more].reduce((signals, key) => {
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
signals[key] = createMediaSignal(`${signalScope}${key}`);
|
|
39
|
+
return signals;
|
|
40
|
+
}, {});
|
|
38
41
|
};
|
|
39
42
|
/**
|
|
40
43
|
* Internal signals
|