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