@pexip/media 19.1.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 CHANGED
@@ -1,5 +1,67 @@
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
+
3
65
  ## 19.1.0
4
66
 
5
67
  ### 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['audio'], props: FeatureProps) => FeatureProps;
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
- return { ...accm, [key]: feature };
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
- .getAudioTracks()
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 (
@@ -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['audio'], props: FeatureProps) => FeatureProps;
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
  */
@@ -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
- return { ...accm, [key]: feature };
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
- return { ...accm, [key]: feature };
46
+ accm[key] = feature;
47
+ return accm;
46
48
  }
47
49
  return accm;
48
50
  }
@@ -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
- * Adds a value to the redaction set, which makes it replaced by [REDACTED] when logged to file.
24
+ * Add a pattern to the redactor and an optional mask for the redaction.
25
25
  *
26
- * @remarks
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 - the string to redact
28
+ * @param value - The pattern to add.
29
+ * @param replaceTo - The mask to use for redaction.
30
30
  */
31
- redact(value: string): void;
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
@@ -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
- // eslint-disable-next-line @typescript-eslint/prefer-literal-enum-member -- needs infinity
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() { }, // Noop
25
- silent() { }, // Noop
26
- redact() { }, // Noop
27
- /* eslint-enable no-console -- set logger to console */
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
  }
@@ -5,3 +5,4 @@ export declare const PROCESSOR_LABELS: Readonly<{
5
5
  AudioMixingProcessor: "AudioMixingProcessor";
6
6
  }>;
7
7
  export declare const GET_USER_MEDIA_TIMEOUT_MS = 10000;
8
+ export declare const DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS = 500;
package/dist/constants.js CHANGED
@@ -5,3 +5,4 @@ export const PROCESSOR_LABELS = Object.freeze({
5
5
  AudioMixingProcessor: 'AudioMixingProcessor',
6
6
  });
7
7
  export const GET_USER_MEDIA_TIMEOUT_MS = 10000;
8
+ export const DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS = 500;
@@ -1,3 +1,3 @@
1
1
  import type { DisplayMediaOptions } from '@pexip/media-control';
2
- export declare const createGetDisplayMedia: (getDefaultConstraints: () => DisplayMediaOptions, getDisplayMedia?: MediaDevices['getDisplayMedia']) => (constraints?: DisplayMediaOptions) => Promise<MediaStream | undefined>;
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 { createMedia } from './media';
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 * from './displayMedia';
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 { createMedia } from './media';
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 * from './displayMedia';
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.debug(
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 { MediaEventType, createIndexedDevices, createStreamTrackEventSubscriptions, getDevices, getInputDevicePermissionState, isIndexedDevices, isRequestedResolution, mergeConstraints, subscribe, } from '@pexip/media-control';
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, isInitialPermissionsGranted } from './status';
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 = ['blur', 'overlay'].includes(videoFeatures.videoSegmentation ?? '') || // Skip when there is a render effect to modify the video
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 audioFeaturesChanged = diffSettings(AUDIO_SETTINGS_KEYS)(prevAudioSettings, audioFeatures);
48
- const videoFeaturesChanged = diffSettings(VIDEO_SETTINGS_KEYS)(prevVideoSettings, videoFeatures);
49
- const mixingFeaturesChanged = diffSettings(MIXING_SETTINGS_KEYS)(prevAudioSettings, mixingFeatures);
50
- const ptzFeaturesChanges = hasPtzFeature() &&
51
- diffSettings(['pan', 'tilt', 'zoom'])(prevVideoSettings, videoFeatures);
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 || Boolean(ptzFeaturesChanges),
77
+ force: !hasRequestedResolution || !isEmpty(ptzFeaturesChanges),
66
78
  });
67
- if (audioRequest !== undefined || videoRequest !== undefined) {
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,9 +260,6 @@ 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
  };
@@ -277,10 +328,11 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
277
328
  getUserMedia: getUserMediaProcess,
278
329
  getCurrentDevices,
279
330
  shouldDiscardMedia: () => props.discardMedia,
331
+ getDefaultConstraints,
280
332
  onMediaTracksChanged: (media, tracks) => {
281
333
  // Sync mute state to input tracks
282
334
  syncMuteState(tracks);
283
- tracks.forEach(track => {
335
+ for (const track of tracks) {
284
336
  if (track.track) {
285
337
  const unsubscribe = createStreamTrackEventSubscriptions(track.track, {
286
338
  ended: track => {
@@ -291,7 +343,8 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
291
343
  unmute: signals.onStreamTrackUnmuted?.emit,
292
344
  });
293
345
  }
294
- });
346
+ }
347
+ logger.debug({ media, tracks }, 'Update media tracks');
295
348
  props.media = media;
296
349
  processAndUpdateMedia(media, tracks).catch((error) => {
297
350
  if (error instanceof Error) {
@@ -337,13 +390,14 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
337
390
  queue.enqueue(async () => {
338
391
  const permission = await getInputDevicePermissionState();
339
392
  const status = getPermissionStatus(permission);
340
- if (isInitialPermissionsGranted(status)) {
393
+ logger.info({ permission, status }, 'tryAndGetUserMedia');
394
+ if (!isRequestPermissionNeeded(status)) {
341
395
  await updateMedia(mergeMediaConstraints(constraints));
342
396
  }
343
397
  else {
344
398
  const currentDevices = await getCurrentDevices();
345
399
  props.media = buildMedia({
346
- constraints,
400
+ constraints: mergeMediaConstraints(constraints),
347
401
  permission,
348
402
  devices: currentDevices,
349
403
  status,
@@ -354,34 +408,22 @@ export const createMedia = ({ getMuteState, signals, audioProcessors, videoProce
354
408
  }
355
409
  });
356
410
  };
357
- // Media Control Event handlers
358
- const handleNoInputs = (_availableDevices) => {
359
- if (props.media) {
360
- props.media.status = UserMediaStatus.NoDevicesFound;
411
+ let deviceChangeTimeoutId = 0;
412
+ const clearDeviceChangeTimeout = () => {
413
+ if (deviceChangeTimeoutId) {
414
+ clearTimeout(deviceChangeTimeoutId);
415
+ deviceChangeTimeoutId = 0;
361
416
  }
362
417
  };
363
- const handleDeviceChange = (devices) => {
364
- props.devices = createIndexedDevices(devices);
418
+ const handleDeviceChange = () => {
419
+ logger.debug('devicechange event triggered');
420
+ clearDeviceChangeTimeout();
421
+ deviceChangeTimeoutId = setTimeout(getCurrentDevices, DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS);
365
422
  };
366
423
  // Subscribe Media Events for devices
367
- subscribe(event => {
368
- switch (event.detail.type) {
369
- case MediaEventType.NoInputDevices:
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
- });
424
+ if ('mediaDevices' in navigator) {
425
+ navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange);
426
+ }
385
427
  return {
386
428
  get updatingMedia() {
387
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
- return { ...set, [key]: settings[key] };
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
- delete videoSettings.width;
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 "onAddTrack" | "onAudioMuteStateChanged" | "onTrackReleased" | "onDevicesChanged" | "onRemoveTrack" | "onStatusChanged" | "onVideoMuteStateChanged" | "onUpdatingMedia" | keyof import("./types").StreamTrackSignals | "onStreamTrackMutedFinal" | "onStreamTrackUnmutedFinal" | "onStreamTrackEndedFinal" | "onStreamTrackEnabledFinal">(more: K[], scope?: string) => Pick<Required<MediaSignals>, "onMediaChanged" | "onVAD" | "onSilentDetected" | K>;
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, variant = 'generic') => createSignal({
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
- ...signals,
36
- [key]: createMediaSignal(`${signalScope}${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
package/dist/status.d.ts CHANGED
@@ -26,6 +26,7 @@ export declare const isUnknownError: UserMediaValidator;
26
26
  export declare const isInitial: UserMediaValidator;
27
27
  export declare const isInitialPermissions: UserMediaValidator;
28
28
  export declare const isInitialPermissionsNotGranted: UserMediaValidator;
29
+ export declare const isRequestPermissionNeeded: UserMediaValidator;
29
30
  export declare const isInitialPermissionsGranted: UserMediaValidator;
30
31
  export declare const isAudioDeviceInUse: UserMediaValidator;
31
32
  export declare const isDeviceInUse: UserMediaValidator;
@@ -36,4 +37,4 @@ export declare const getPermissionStatus: ({ audio, video, }: {
36
37
  audio: PermissionState;
37
38
  video: PermissionState;
38
39
  }) => UserMediaStatus.Initial | UserMediaStatus.InitialPermissionsGranted | UserMediaStatus.InitialPermissionsNotGranted | UserMediaStatus.InitialPermissionsVideoInputDenied | UserMediaStatus.InitialPermissionsAudioInputDenied | UserMediaStatus.InitialPermissionsVideoInputGranted | UserMediaStatus.InitialPermissionsAudioInputGranted | UserMediaStatus.InitialPermissionsGrantedVideoInputDenied | UserMediaStatus.InitialPermissionsGrantedAudioInputDenied | UserMediaStatus.PermissionsRejected;
39
- export declare const deriveInitialPermissionStatus: (prevStatus: UserMediaStatus, getPermissionState?: (anyActiveAudioTrack?: boolean | undefined, anyActiveVideoTrack?: boolean | undefined, getDevices?: (() => Promise<MediaDeviceInfo[]>) | undefined) => Promise<import("@pexip/media-control").InputDevicePermission>) => Promise<UserMediaStatus>;
40
+ export declare const deriveInitialPermissionStatus: (prevStatus: UserMediaStatus, getPermissionState?: (anyActiveAudioTrack?: boolean, anyActiveVideoTrack?: boolean, getDevices?: () => Promise<MediaDeviceInfo[]>) => Promise<import("@pexip/media-control").InputDevicePermission>) => Promise<UserMediaStatus>;
package/dist/status.js CHANGED
@@ -110,10 +110,18 @@ export const isInitialPermissionsNotGranted = status => [
110
110
  UserMediaStatus.InitialPermissionsNotGranted,
111
111
  UserMediaStatus.InitialPermissionsVideoInputDenied,
112
112
  ].includes(status);
113
+ const PermissionPromptStatusSet = new Set([
114
+ UserMediaStatus.InitialPermissionsNotGranted,
115
+ UserMediaStatus.InitialPermissionsAudioInputGranted,
116
+ UserMediaStatus.InitialPermissionsVideoInputGranted,
117
+ ]);
118
+ export const isRequestPermissionNeeded = status => PermissionPromptStatusSet.has(status);
113
119
  export const isInitialPermissionsGranted = status => [
114
120
  UserMediaStatus.InitialPermissionsGranted,
115
121
  UserMediaStatus.InitialPermissionsGrantedAudioInputDenied,
122
+ UserMediaStatus.InitialPermissionsAudioInputGranted,
116
123
  UserMediaStatus.InitialPermissionsGrantedVideoInputDenied,
124
+ UserMediaStatus.InitialPermissionsVideoInputGranted,
117
125
  ].includes(status);
118
126
  export const isAudioDeviceInUse = status => status === UserMediaStatus.AudioDeviceInUse;
119
127
  export const isDeviceInUse = status => [
@@ -0,0 +1 @@
1
+ {"root":["../src/audioMixingProcessor.ts","../src/audioProcessor.ts","../src/baseLogger.ts","../src/constants.ts","../src/displayMedia.ts","../src/index.ts","../src/logger.ts","../src/media.ts","../src/previewController.ts","../src/signals.ts","../src/status.ts","../src/typeGuard.ts","../src/types.ts","../src/userMedia.ts","../src/utils.ts","../src/videoProcessor.ts","../../../@types/globals.d.ts"],"version":"5.7.3"}