@pexip/media 19.0.0 → 20.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -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 +96 -46
- package/dist/previewController.js +4 -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 +84 -39
- 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,80 @@
|
|
|
1
1
|
# @pexip/media
|
|
2
2
|
|
|
3
|
+
## 20.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 2bfd9e1: Adds esm entry points.
|
|
8
|
+
- c975587: `dynamicProcessingDimensions` is a func.
|
|
9
|
+
- 7679b89: Removes `adjustResolution`.
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- dd5a4eb: Adds optional `gpuAPI` getter, sets the transformer backend when
|
|
14
|
+
opened.
|
|
15
|
+
- 34bf3dd: Add isVideoStreamTrackProcessorAPIs
|
|
16
|
+
- 5855473: Updates the segmenter processing width and height based on the track
|
|
17
|
+
settings if `dynamicProcessingDimensions` is `true` in `videoProcessor`.
|
|
18
|
+
- 3555d9d: Adds `build:sourcemap` script.
|
|
19
|
+
- 7d94f5c: Take unauthorized device into account
|
|
20
|
+
- ed53b05: Upgrade Typescript
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- 4146978: Bump logger proxy set to info level
|
|
25
|
+
- 0beec97: Fix mergeStatus to take requesting single device into consideration
|
|
26
|
+
|
|
27
|
+
When changing 1 device should not affect another type of device
|
|
28
|
+
|
|
29
|
+
- 67a18d0: Use recommended linter rules
|
|
30
|
+
- e1882e7: Request media only when there is no prompt permission
|
|
31
|
+
- 3882f98: No unused imports
|
|
32
|
+
- 50d0af0: Assert track and input
|
|
33
|
+
- 5adb1aa: Update according Logger changes
|
|
34
|
+
- 79c52c9: No signals reuse for clone
|
|
35
|
+
- 43d0b9c: Always use the merged constraints
|
|
36
|
+
- 6470495: Fix applyFeatures
|
|
37
|
+
- 6d68da2: Stop processing non-live track
|
|
38
|
+
- 58ecca1: Fix mergeStatus fallback case
|
|
39
|
+
- e76a15e: Migrate to biome linter
|
|
40
|
+
- Updated dependencies [2bfd9e1]
|
|
41
|
+
- Updated dependencies [4fe77de]
|
|
42
|
+
- Updated dependencies [b0d8af9]
|
|
43
|
+
- Updated dependencies [2bbff64]
|
|
44
|
+
- Updated dependencies [4baa8a4]
|
|
45
|
+
- Updated dependencies [db3a370]
|
|
46
|
+
- Updated dependencies [4ea0492]
|
|
47
|
+
- Updated dependencies [3555d9d]
|
|
48
|
+
- Updated dependencies [d3909ed]
|
|
49
|
+
- Updated dependencies [0416d6c]
|
|
50
|
+
- Updated dependencies [1601985]
|
|
51
|
+
- Updated dependencies [7d94f5c]
|
|
52
|
+
- Updated dependencies [3cb5aee]
|
|
53
|
+
- Updated dependencies [fa06b8b]
|
|
54
|
+
- Updated dependencies [84956b8]
|
|
55
|
+
- Updated dependencies [ed53b05]
|
|
56
|
+
- Updated dependencies [5855473]
|
|
57
|
+
- Updated dependencies [941363c]
|
|
58
|
+
- Updated dependencies [d681fd3]
|
|
59
|
+
- Updated dependencies [e76a15e]
|
|
60
|
+
- @pexip/media-processor@20.0.0
|
|
61
|
+
- @pexip/media-control@20.0.0
|
|
62
|
+
- @pexip/utils@17.0.0
|
|
63
|
+
- @pexip/signal@16.8.0
|
|
64
|
+
|
|
65
|
+
## 19.1.0
|
|
66
|
+
|
|
67
|
+
### Minor Changes
|
|
68
|
+
|
|
69
|
+
- 7271998: Fix `syncMuteState` and
|
|
70
|
+
`onAudioMuteStateChanged`/`onVideoMuteStateChanged`
|
|
71
|
+
|
|
72
|
+
### Patch Changes
|
|
73
|
+
|
|
74
|
+
- Updated dependencies [e3a9d84]
|
|
75
|
+
- @pexip/media-processor@19.1.0
|
|
76
|
+
- @pexip/media-control@19.1.0
|
|
77
|
+
|
|
3
78
|
## 19.0.0
|
|
4
79
|
|
|
5
80
|
### Major 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,21 +260,26 @@ 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) {
|
|
269
|
+
const prevMuted = track.muted;
|
|
218
270
|
switch (track.kind) {
|
|
219
|
-
case 'audioinput':
|
|
271
|
+
case 'audioinput': {
|
|
220
272
|
track.mute(inputMuted.audio);
|
|
273
|
+
if (prevMuted !== track.muted) {
|
|
274
|
+
signals.onAudioMuteStateChanged?.emit(track.muted);
|
|
275
|
+
}
|
|
221
276
|
break;
|
|
277
|
+
}
|
|
222
278
|
case 'videoinput':
|
|
223
279
|
track.mute(inputMuted.video);
|
|
280
|
+
if (prevMuted !== track.muted) {
|
|
281
|
+
signals.onVideoMuteStateChanged?.emit(track.muted);
|
|
282
|
+
}
|
|
224
283
|
break;
|
|
225
284
|
default:
|
|
226
285
|
break;
|
|
@@ -269,10 +328,11 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
269
328
|
getUserMedia: getUserMediaProcess,
|
|
270
329
|
getCurrentDevices,
|
|
271
330
|
shouldDiscardMedia: () => props.discardMedia,
|
|
331
|
+
getDefaultConstraints,
|
|
272
332
|
onMediaTracksChanged: (media, tracks) => {
|
|
273
333
|
// Sync mute state to input tracks
|
|
274
|
-
syncMuteState(
|
|
275
|
-
|
|
334
|
+
syncMuteState(tracks);
|
|
335
|
+
for (const track of tracks) {
|
|
276
336
|
if (track.track) {
|
|
277
337
|
const unsubscribe = createStreamTrackEventSubscriptions(track.track, {
|
|
278
338
|
ended: track => {
|
|
@@ -283,7 +343,8 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
283
343
|
unmute: signals.onStreamTrackUnmuted?.emit,
|
|
284
344
|
});
|
|
285
345
|
}
|
|
286
|
-
}
|
|
346
|
+
}
|
|
347
|
+
logger.debug({ media, tracks }, 'Update media tracks');
|
|
287
348
|
props.media = media;
|
|
288
349
|
processAndUpdateMedia(media, tracks).catch((error) => {
|
|
289
350
|
if (error instanceof Error) {
|
|
@@ -329,13 +390,14 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
329
390
|
queue.enqueue(async () => {
|
|
330
391
|
const permission = await getInputDevicePermissionState();
|
|
331
392
|
const status = getPermissionStatus(permission);
|
|
332
|
-
|
|
393
|
+
logger.info({ permission, status }, 'tryAndGetUserMedia');
|
|
394
|
+
if (!isRequestPermissionNeeded(status)) {
|
|
333
395
|
await updateMedia(mergeMediaConstraints(constraints));
|
|
334
396
|
}
|
|
335
397
|
else {
|
|
336
398
|
const currentDevices = await getCurrentDevices();
|
|
337
399
|
props.media = buildMedia({
|
|
338
|
-
constraints,
|
|
400
|
+
constraints: mergeMediaConstraints(constraints),
|
|
339
401
|
permission,
|
|
340
402
|
devices: currentDevices,
|
|
341
403
|
status,
|
|
@@ -346,34 +408,22 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
|
|
|
346
408
|
}
|
|
347
409
|
});
|
|
348
410
|
};
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
if (
|
|
352
|
-
|
|
411
|
+
let deviceChangeTimeoutId = 0;
|
|
412
|
+
const clearDeviceChangeTimeout = () => {
|
|
413
|
+
if (deviceChangeTimeoutId) {
|
|
414
|
+
clearTimeout(deviceChangeTimeoutId);
|
|
415
|
+
deviceChangeTimeoutId = 0;
|
|
353
416
|
}
|
|
354
417
|
};
|
|
355
|
-
const handleDeviceChange = (
|
|
356
|
-
|
|
418
|
+
const handleDeviceChange = () => {
|
|
419
|
+
logger.debug('devicechange event triggered');
|
|
420
|
+
clearDeviceChangeTimeout();
|
|
421
|
+
deviceChangeTimeoutId = setTimeout(getCurrentDevices, DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS);
|
|
357
422
|
};
|
|
358
423
|
// Subscribe Media Events for devices
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
logger.debug(event.detail, 'NoInputDevices emitted');
|
|
363
|
-
handleNoInputs(event.detail.devices);
|
|
364
|
-
break;
|
|
365
|
-
case MediaEventType.DevicesFound:
|
|
366
|
-
case MediaEventType.DevicesLost:
|
|
367
|
-
logger.debug(event.detail, 'DevicesFound/Lost emitted');
|
|
368
|
-
handleDeviceChange([
|
|
369
|
-
...event.detail.authorizedDevices,
|
|
370
|
-
...event.detail.unauthorizedDevices,
|
|
371
|
-
]);
|
|
372
|
-
break;
|
|
373
|
-
default:
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
});
|
|
424
|
+
if ('mediaDevices' in navigator) {
|
|
425
|
+
navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange);
|
|
426
|
+
}
|
|
377
427
|
return {
|
|
378
428
|
get updatingMedia() {
|
|
379
429
|
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({
|
|
@@ -321,6 +320,7 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
|
|
|
321
320
|
if (mainMedia && mainStream) {
|
|
322
321
|
// Workaround for iOS Safari bug
|
|
323
322
|
// https://bugs.webkit.org/show_bug.cgi?id=179363
|
|
323
|
+
// https://github.com/pexip/mcu/issues/43138
|
|
324
324
|
const mainMuted = !!mainStream.getTracks().some(t => t.muted);
|
|
325
325
|
const current = mainMedia.audioInput;
|
|
326
326
|
const prev = props.originalMainAudioInput;
|
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
|