@pexip/media 17.3.0 → 18.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.
@@ -0,0 +1,108 @@
1
+ import type { MediaDeviceRequest, MediaDeviceInfoLike } from '@pexip/media-control';
2
+ import { UserMediaStatus } from './types';
3
+ import type { Media, MediaAttributes, Pipeline, Process, ProcessMedia, ExtendedMediaTrackSettingsKey, ExtendedMediaTrackSettings } from './types';
4
+ export declare const makeDeriveDeviceStatus: (constraints: MediaDeviceRequest) => (audio: UserMediaStatus, video: UserMediaStatus, both: UserMediaStatus) => UserMediaStatus;
5
+ export declare const createMediaProcess: (process: ProcessMedia) => Process<Promise<Media>>;
6
+ export declare const createMediaPipeline: <T = MediaDeviceRequest>(init: Pipeline<T> | (() => Pipeline<T>)) => {
7
+ pipe: (process: Process<Promise<Media>>) => void;
8
+ execute: (m: T) => Promise<Media>;
9
+ };
10
+ /**
11
+ * Interpret provided input to resolve to a MediaDeviceInfoLike when possible
12
+ * otherwise `undefined`
13
+ */
14
+ export declare const interpretInput: (input: boolean | MediaDeviceInfoLike | undefined, getCurrentInput: () => MediaDeviceInfoLike | undefined) => MediaDeviceInfoLike | undefined;
15
+ type InputConstraints = MediaDeviceRequest['audio'];
16
+ interface MediaInputInfo {
17
+ devices: MediaDeviceInfoLike[];
18
+ input: MediaDeviceInfoLike | undefined;
19
+ }
20
+ /**
21
+ * Memorized Expected Input
22
+ */
23
+ export declare const createMemorizedGetExpectedInput: () => (constraints: InputConstraints, getInfo: () => MediaInputInfo) => ExpectedInput;
24
+ /**
25
+ * A utility function to check if the provided track is muted. There are
26
+ * 2 factors to be considered: `MediaStreamTrack['muted']` and `MediaStreamTrack['enabled']`.
27
+ *
28
+ * ```
29
+ * | muted \ enabled | true | false |
30
+ * |-----------------| ----- | ----- |
31
+ * | true | true | true |
32
+ * | false | false | true |
33
+ * ```
34
+ *
35
+ * @param tracks - The tracks can be got from `MediaStream['getAudioStats']` or
36
+ * `MediaStream['getVideoTracks']`
37
+ *
38
+ * @returns `true` means muted, `false` means not muted and `undefined` means
39
+ * there is no track to check
40
+ */
41
+ export declare const isMuted: (tracks: MediaStreamTrack[] | undefined) => boolean | undefined;
42
+ type ExpectedInput = MediaDeviceInfoLike | undefined;
43
+ export declare const buildMedia: (getMedia: () => Partial<Media>, onSetStatus?: ((status: UserMediaStatus) => void) | undefined) => Media;
44
+ /**
45
+ * Clone the media from the rawStream (if any), otherwise, stream
46
+ */
47
+ export declare const cloneMedia: (media: Media) => Promise<Media>;
48
+ /**
49
+ * Shallow copy the provided object and override with provided overriding
50
+ *
51
+ * @param original - Original object
52
+ * @param overriding - Object of the same type to override the original
53
+ *
54
+ * @returns a shallow copied object
55
+ */
56
+ export declare const shallowCopy: <T>(original: T, overriding: Partial<T>) => T;
57
+ export declare const getDevicesChanges: (prev: MediaDeviceInfoLike[], next: MediaDeviceInfoLike[]) => {
58
+ unauthorized: MediaDeviceInfoLike[];
59
+ authorized: MediaDeviceInfoLike[];
60
+ found: MediaDeviceInfoLike[];
61
+ lost: MediaDeviceInfoLike[];
62
+ devices: MediaDeviceInfoLike[];
63
+ };
64
+ /**
65
+ * Apply Extended constraints on top of the original
66
+ *
67
+ * @param media - The media from the media pipeline
68
+ * @param applyExtended - The function to be called when the previous
69
+ * `applyConstraints` is done
70
+ */
71
+ export declare const applyExtendedConstraints: (media: Media, applyExtended: (constraints: MediaDeviceRequest) => Promise<void>) => (constraints: MediaDeviceRequest) => Promise<void>;
72
+ export declare const AUDIO_SETTINGS_KEYS: ExtendedMediaTrackSettingsKey[];
73
+ export declare const VIDEO_SETTINGS_KEYS: ExtendedMediaTrackSettingsKey[];
74
+ export declare const MIXING_SETTINGS_KEYS: ExtendedMediaTrackSettingsKey[];
75
+ export declare const hasSettingsChanged: (keysToLookFor: ExtendedMediaTrackSettingsKey[]) => (settingsA: ExtendedMediaTrackSettings | undefined, settingsB: ExtendedMediaTrackSettings | undefined) => boolean;
76
+ export declare const toJSON: (media: Partial<MediaAttributes>) => {
77
+ constraints: MediaDeviceRequest | undefined;
78
+ devices: MediaDeviceInfoLike[] | undefined;
79
+ stream: MediaStream | undefined;
80
+ rawStream: MediaStream | undefined;
81
+ audioInput: MediaDeviceInfoLike | undefined;
82
+ videoInput: MediaDeviceInfoLike | undefined;
83
+ expectedAudioInput: MediaDeviceInfoLike | undefined;
84
+ expectedVideoInput: MediaDeviceInfoLike | undefined;
85
+ status: UserMediaStatus | undefined;
86
+ audioMuted: boolean | undefined;
87
+ videoMuted: boolean | undefined;
88
+ };
89
+ export declare const wrapToJSON: (media: Media) => Media;
90
+ /**
91
+ * A function to get the blur kernel size of image height
92
+ *
93
+ * @param percentage - The percentage of image height to calculate the blur
94
+ * kernel size
95
+ * @param height - The image height
96
+ * @param max - The upper bound
97
+ *
98
+ * @returns blur kernel size
99
+ */
100
+ export declare const getBlurKernelSize: (percentage: number, height: number, max?: number) => number;
101
+ /**
102
+ * Apply the content hint to the track
103
+ *
104
+ * @param hint - Content hint
105
+ * @param track - The track to be applied
106
+ */
107
+ export declare const applyContentHint: <T extends "" | "speech" | "speech-recognition" | "music" | "motion" | "detail" | "text">(hint?: T | undefined) => (track: MediaStreamTrack) => void;
108
+ export {};
package/dist/utils.js ADDED
@@ -0,0 +1,361 @@
1
+ import { applyConstraints, createTrackDevicesChanges, extractConstraintsWithKeys, findDeviceFromConstraints, muteStreamTrack, relaxInputConstraint, stopMediaStream, isAudioInput, isVideoInput, } from '@pexip/media-control';
2
+ import { calculateMaxBlurPass } from '@pexip/media-processor';
3
+ import { UserMediaStatus } from './types';
4
+ import { isOverConstrained } from './status';
5
+ import { isMedia } from './typeGuard';
6
+ export const makeDeriveDeviceStatus = (constraints) => (audio, video, both) => {
7
+ if (!constraints.audio && constraints.video) {
8
+ return video;
9
+ }
10
+ if (!constraints.video && constraints.audio) {
11
+ return audio;
12
+ }
13
+ return both;
14
+ };
15
+ export const createMediaProcess = (process) => async (mediaP) => {
16
+ const media = await mediaP;
17
+ return process(media) ?? media;
18
+ };
19
+ export const createMediaPipeline = (init) => {
20
+ const getPipeline = () => (typeof init === 'function' ? init() : init);
21
+ return {
22
+ pipe: (process) => {
23
+ getPipeline().push(process);
24
+ },
25
+ execute: async (m) => {
26
+ const [first, ...processes] = getPipeline();
27
+ if (first) {
28
+ const piped = processes.reduce((prev, next) => next(prev), first(m));
29
+ return piped;
30
+ }
31
+ const media = m instanceof Promise ? (await m) : m;
32
+ if (isMedia(media)) {
33
+ return Promise.resolve(media);
34
+ }
35
+ throw new Error('Expect a media input or a processor');
36
+ },
37
+ };
38
+ };
39
+ /**
40
+ * Interpret provided input to resolve to a MediaDeviceInfoLike when possible
41
+ * otherwise `undefined`
42
+ */
43
+ export const interpretInput = (input, getCurrentInput) => {
44
+ if (input === true || input === undefined) {
45
+ return getCurrentInput();
46
+ }
47
+ if (input === false) {
48
+ return undefined;
49
+ }
50
+ return input;
51
+ };
52
+ /**
53
+ * Memorized Expected Input
54
+ */
55
+ export const createMemorizedGetExpectedInput = () => {
56
+ const props = {
57
+ cachedExpectedInputs: new Map(),
58
+ };
59
+ return (constraints, getInfo) => {
60
+ if (props.cachedExpectedInputs.has(constraints)) {
61
+ return props.cachedExpectedInputs.get(constraints);
62
+ }
63
+ const { devices, input } = getInfo();
64
+ // Update cache
65
+ const relaxedConstraints = relaxInputConstraint(constraints, devices);
66
+ const { device: [[device] = []], } = extractConstraintsWithKeys(['device'])(relaxedConstraints);
67
+ // The result from `findDeviceFromConstraints` has more restrictive
68
+ // result since it also consider if the device can be found from the
69
+ // device list
70
+ const found = device ?? findDeviceFromConstraints(constraints, devices);
71
+ const expectedInput = interpretInput(found, () => input);
72
+ props.cachedExpectedInputs.clear();
73
+ props.cachedExpectedInputs.set(constraints, expectedInput);
74
+ return expectedInput;
75
+ };
76
+ };
77
+ /**
78
+ * A utility function to check if the provided track is muted. There are
79
+ * 2 factors to be considered: `MediaStreamTrack['muted']` and `MediaStreamTrack['enabled']`.
80
+ *
81
+ * ```
82
+ * | muted \ enabled | true | false |
83
+ * |-----------------| ----- | ----- |
84
+ * | true | true | true |
85
+ * | false | false | true |
86
+ * ```
87
+ *
88
+ * @param tracks - The tracks can be got from `MediaStream['getAudioStats']` or
89
+ * `MediaStream['getVideoTracks']`
90
+ *
91
+ * @returns `true` means muted, `false` means not muted and `undefined` means
92
+ * there is no track to check
93
+ */
94
+ export const isMuted = (tracks) => {
95
+ if (!tracks?.length) {
96
+ return undefined;
97
+ }
98
+ return !tracks.some(track => !track.muted && track.enabled);
99
+ };
100
+ export const buildMedia = (getMedia, onSetStatus) => {
101
+ const props = {
102
+ status: getMedia().status ?? UserMediaStatus.Initial,
103
+ devices: getMedia().devices ?? [],
104
+ constraints: getMedia().constraints,
105
+ };
106
+ const getExpectedAudioInput = createMemorizedGetExpectedInput();
107
+ const getExpectedVideoInput = createMemorizedGetExpectedInput();
108
+ const muteTrack = (kind) => (muted) => {
109
+ const { muteAudio, muteVideo, stream } = getMedia();
110
+ const mute = kind === 'audio' ? muteAudio : muteVideo;
111
+ if (mute) {
112
+ return mute(muted);
113
+ }
114
+ return muteStreamTrack(stream)(muted, kind);
115
+ };
116
+ const release = () => {
117
+ const { release, stream } = getMedia();
118
+ if (release) {
119
+ return release();
120
+ }
121
+ return new Promise(resolve => {
122
+ stopMediaStream(stream);
123
+ resolve();
124
+ });
125
+ };
126
+ return {
127
+ get constraints() {
128
+ return props.constraints;
129
+ },
130
+ get devices() {
131
+ return props.devices;
132
+ },
133
+ set devices(newDevices) {
134
+ props.devices = newDevices;
135
+ },
136
+ get stream() {
137
+ return getMedia().stream;
138
+ },
139
+ get expectedAudioInput() {
140
+ const media = getMedia();
141
+ return getExpectedAudioInput(props.constraints?.audio, () => ({
142
+ devices: media.devices?.filter(isAudioInput) ?? [],
143
+ input: media.audioInput,
144
+ }));
145
+ },
146
+ get expectedVideoInput() {
147
+ const media = getMedia();
148
+ return getExpectedVideoInput(props.constraints?.video, () => ({
149
+ devices: media.devices?.filter(isVideoInput) ?? [],
150
+ input: media.videoInput,
151
+ }));
152
+ },
153
+ get rawStream() {
154
+ const { rawStream, stream } = getMedia();
155
+ return rawStream ?? stream;
156
+ },
157
+ get audioInput() {
158
+ return getMedia().audioInput;
159
+ },
160
+ get videoInput() {
161
+ return getMedia().videoInput;
162
+ },
163
+ get status() {
164
+ return props.status;
165
+ },
166
+ set status(status) {
167
+ props.status = status;
168
+ onSetStatus?.(status);
169
+ },
170
+ set constraints(value) {
171
+ props.constraints = value;
172
+ },
173
+ get audioMuted() {
174
+ return isMuted(getMedia().stream?.getAudioTracks());
175
+ },
176
+ get videoMuted() {
177
+ return isMuted(getMedia().stream?.getVideoTracks());
178
+ },
179
+ muteAudio: muteTrack('audio'),
180
+ muteVideo: muteTrack('video'),
181
+ applyConstraints: async (constraints) => {
182
+ const { stream, applyConstraints: prevApplyConstraints } = getMedia();
183
+ if (prevApplyConstraints) {
184
+ return await prevApplyConstraints(constraints);
185
+ }
186
+ return await applyConstraints(stream?.getTracks(), constraints);
187
+ },
188
+ release,
189
+ getSettings: () => {
190
+ const { getSettings, stream } = getMedia();
191
+ if (!stream) {
192
+ return {
193
+ audio: [],
194
+ video: [],
195
+ };
196
+ }
197
+ if (getSettings) {
198
+ return getSettings();
199
+ }
200
+ return {
201
+ audio: stream
202
+ .getAudioTracks()
203
+ .map(track => track.getSettings()),
204
+ video: stream
205
+ .getVideoTracks()
206
+ .map(track => track.getSettings()),
207
+ };
208
+ },
209
+ toJSON: () => toJSON(getMedia()),
210
+ };
211
+ };
212
+ /**
213
+ * Clone the media from the rawStream (if any), otherwise, stream
214
+ */
215
+ export const cloneMedia = async (media) => {
216
+ const stream = (media.rawStream ?? media.stream)?.clone();
217
+ // Restore the enabled state for all cloned track
218
+ stream?.getTracks().forEach(track => (track.enabled = true));
219
+ const { audio, video } = media.getSettings();
220
+ const clonedMedia = buildMedia(() => ({
221
+ stream,
222
+ constraints: media?.constraints,
223
+ devices: media?.devices,
224
+ status: media?.status,
225
+ rawStream: stream,
226
+ audioInput: media?.audioInput,
227
+ videoInput: media?.videoInput,
228
+ getSettings: () => ({ audio, video }),
229
+ }));
230
+ return Promise.resolve(clonedMedia);
231
+ };
232
+ /**
233
+ * Shallow copy the provided object and override with provided overriding
234
+ *
235
+ * @param original - Original object
236
+ * @param overriding - Object of the same type to override the original
237
+ *
238
+ * @returns a shallow copied object
239
+ */
240
+ export const shallowCopy = (original, overriding) => {
241
+ const copy = Object.create(Object.getPrototypeOf(original), Object.getOwnPropertyDescriptors(original));
242
+ return Object.defineProperties(copy, Object.getOwnPropertyDescriptors(overriding));
243
+ };
244
+ export const getDevicesChanges = (prev, next) => {
245
+ const trackChanges = createTrackDevicesChanges(prev);
246
+ return trackChanges(next);
247
+ };
248
+ /**
249
+ * Apply Extended constraints on top of the original
250
+ *
251
+ * @param media - The media from the media pipeline
252
+ * @param applyExtended - The function to be called when the previous
253
+ * `applyConstraints` is done
254
+ */
255
+ export const applyExtendedConstraints = (media, applyExtended) =>
256
+ /**
257
+ * Apply constraints
258
+ * @param constraints - The constraints to be applied to the media
259
+ */
260
+ async (constraints) => {
261
+ await media.applyConstraints(constraints);
262
+ if (!isOverConstrained(media.status)) {
263
+ await applyExtended(constraints);
264
+ }
265
+ };
266
+ export const AUDIO_SETTINGS_KEYS = [
267
+ 'denoise',
268
+ 'vad',
269
+ 'asd',
270
+ 'contentHint',
271
+ ];
272
+ export const VIDEO_SETTINGS_KEYS = [
273
+ 'frameRate',
274
+ 'videoSegmentation',
275
+ 'videoSegmentationModel',
276
+ 'foregroundThreshold',
277
+ 'backgroundBlurAmount',
278
+ 'edgeBlurAmount',
279
+ 'maskCombineRatio',
280
+ 'backgroundImageUrl',
281
+ 'width',
282
+ 'height',
283
+ 'contentHint',
284
+ ];
285
+ export const MIXING_SETTINGS_KEYS = [
286
+ 'mixWithAdditionalMedia',
287
+ ];
288
+ export const hasSettingsChanged = (keysToLookFor) => {
289
+ const cache = {};
290
+ return (settingsA, settingsB) => {
291
+ if (cache.result !== undefined &&
292
+ cache.settingsA === settingsA &&
293
+ cache.settingsB === settingsB) {
294
+ return cache.result;
295
+ }
296
+ cache.settingsA = settingsA;
297
+ cache.settingsB = settingsB;
298
+ for (const key of keysToLookFor) {
299
+ if (settingsA === settingsB) {
300
+ cache.result = false;
301
+ return cache.result;
302
+ }
303
+ if (settingsA === undefined || settingsB === undefined) {
304
+ cache.result = true;
305
+ return cache.result;
306
+ }
307
+ if (settingsA[key] !== settingsB[key]) {
308
+ cache.result = true;
309
+ return cache.result;
310
+ }
311
+ }
312
+ cache.result = false;
313
+ return cache.result;
314
+ };
315
+ };
316
+ export const toJSON = (media) => {
317
+ return {
318
+ constraints: media.constraints,
319
+ devices: media.devices,
320
+ stream: media.stream,
321
+ rawStream: media.rawStream,
322
+ audioInput: media.audioInput,
323
+ videoInput: media.videoInput,
324
+ expectedAudioInput: media.expectedAudioInput,
325
+ expectedVideoInput: media.expectedVideoInput,
326
+ status: media.status,
327
+ audioMuted: media.audioMuted,
328
+ videoMuted: media.videoMuted,
329
+ };
330
+ };
331
+ export const wrapToJSON = (media) => {
332
+ media.toJSON = () => toJSON(media);
333
+ return media;
334
+ };
335
+ /**
336
+ * A function to get the blur kernel size of image height
337
+ *
338
+ * @param percentage - The percentage of image height to calculate the blur
339
+ * kernel size
340
+ * @param height - The image height
341
+ * @param max - The upper bound
342
+ *
343
+ * @returns blur kernel size
344
+ */
345
+ export const getBlurKernelSize = (percentage, height, max = calculateMaxBlurPass(height)) => {
346
+ if (height <= 0 || percentage <= 0 || max <= 0) {
347
+ return 0;
348
+ }
349
+ return Math.min(Math.ceil(percentage * 0.01 * max), max);
350
+ };
351
+ /**
352
+ * Apply the content hint to the track
353
+ *
354
+ * @param hint - Content hint
355
+ * @param track - The track to be applied
356
+ */
357
+ export const applyContentHint = (hint) => (track) => {
358
+ if (hint !== undefined && hint !== track.contentHint) {
359
+ track.contentHint = hint;
360
+ }
361
+ };
@@ -0,0 +1,41 @@
1
+ import type { VideoProcessor, SegmentationTransform, SegmentationModel } from '@pexip/media-processor';
2
+ import type { MediaDeviceRequest } from '@pexip/media-control';
3
+ import type { Process, Media, VideoRenderParams, Segmenters, VideoStreamTrackProcessorAPIs, VideoContentHint } from './types';
4
+ interface ProcessorDeps {
5
+ videoProcessor?: () => VideoProcessor;
6
+ transformer?: SegmentationTransform;
7
+ segmenters: Partial<Segmenters>;
8
+ videoSegmentationModel?: SegmentationModel;
9
+ }
10
+ interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<ProcessorDeps, 'videoProcessor'> {
11
+ /**
12
+ * What API to use for processing the MediaStreamTrack
13
+ * `stream` - Use MediaStreamTrackProcessor, when available
14
+ * `canvas` - Use Canvas
15
+ */
16
+ trackProcessorAPI?: () => VideoStreamTrackProcessorAPIs;
17
+ /**
18
+ * Whether or to enable this processor
19
+ */
20
+ shouldEnable: () => boolean;
21
+ /**
22
+ * Callback when error occurs
23
+ */
24
+ onError?: (error: Error) => void;
25
+ processingWidth: number;
26
+ processingHeight: number;
27
+ hasInitializedDeps?: boolean;
28
+ width?: number;
29
+ height?: number;
30
+ scope?: string;
31
+ }
32
+ interface VideoStreamProcessProps extends Partial<VideoRenderParams>, Required<ProcessorDeps> {
33
+ hasInitialized: boolean;
34
+ contentHint?: VideoContentHint;
35
+ }
36
+ declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUrl", "maskCombineRatio", "edgeBlurAmount", "foregroundThreshold", "frameRate", "videoSegmentation", "videoSegmentationModel", "width", "height", "pan", "tilt", "zoom", "contentHint"];
37
+ type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
38
+ type FeatureProps = Pick<Partial<VideoStreamProcessProps>, FeaturePropKeys>;
39
+ export declare const updateFeatureProps: (constraints: MediaDeviceRequest['video'], props: FeatureProps) => FeatureProps;
40
+ export declare const createVideoStreamProcess: ({ trackProcessorAPI, processingWidth, processingHeight, shouldEnable, frameRate, videoSegmentation, foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, scope, ...options }: VideoStreamProcessOptions) => Process<Promise<Media>>;
41
+ export {};