@stream-io/video-client 1.4.2 → 1.4.3
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 +7 -0
- package/dist/index.browser.es.js +214 -144
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +214 -142
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +214 -144
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +7 -7
- package/dist/src/StreamSfuClient.d.ts +7 -7
- package/dist/src/StreamVideoClient.d.ts +5 -5
- package/dist/src/coordinator/connection/client.d.ts +13 -14
- package/dist/src/coordinator/connection/connection.d.ts +3 -5
- package/dist/src/coordinator/connection/insights.d.ts +0 -1
- package/dist/src/devices/BrowserPermission.d.ts +24 -0
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +3 -3
- package/dist/src/devices/devices.d.ts +30 -11
- package/dist/src/helpers/ViewportTracker.d.ts +1 -1
- package/dist/src/helpers/lazy.d.ts +4 -0
- package/dist/src/helpers/sdp-munging.d.ts +2 -2
- package/dist/src/rtc/Dispatcher.d.ts +2 -2
- package/dist/src/rtc/codecs.d.ts +1 -1
- package/dist/src/rtc/signal.d.ts +0 -1
- package/dist/src/stats/utils.d.ts +4 -4
- package/dist/src/store/CallState.d.ts +1 -1
- package/package.json +4 -4
- package/src/devices/BrowserPermission.ts +152 -0
- package/src/devices/CameraManagerState.ts +2 -6
- package/src/devices/InputMediaDeviceManagerState.ts +10 -44
- package/src/devices/MicrophoneManagerState.ts +2 -6
- package/src/devices/__tests__/CameraManager.test.ts +3 -0
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +5 -3
- package/src/devices/__tests__/InputMediaDeviceManagerFilters.test.ts +6 -2
- package/src/devices/__tests__/InputMediaDeviceManagerState.test.ts +41 -51
- package/src/devices/__tests__/MicrophoneManager.test.ts +4 -1
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +8 -1
- package/src/devices/__tests__/SpeakerManager.test.ts +8 -1
- package/src/devices/__tests__/mocks.ts +6 -1
- package/src/devices/devices.ts +113 -112
- package/src/helpers/RNSpeechDetector.ts +1 -1
- package/src/helpers/lazy.ts +15 -0
package/src/devices/devices.ts
CHANGED
|
@@ -2,12 +2,15 @@ import {
|
|
|
2
2
|
concatMap,
|
|
3
3
|
debounceTime,
|
|
4
4
|
from,
|
|
5
|
+
fromEvent,
|
|
5
6
|
map,
|
|
6
7
|
merge,
|
|
7
|
-
Observable,
|
|
8
8
|
shareReplay,
|
|
9
|
+
startWith,
|
|
9
10
|
} from 'rxjs';
|
|
10
11
|
import { getLogger } from '../logger';
|
|
12
|
+
import { BrowserPermission } from './BrowserPermission';
|
|
13
|
+
import { lazy } from '../helpers/lazy';
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Returns an Observable that emits the list of available devices
|
|
@@ -16,49 +19,28 @@ import { getLogger } from '../logger';
|
|
|
16
19
|
* @param constraints the constraints to use when requesting the devices.
|
|
17
20
|
* @param kind the kind of devices to enumerate.
|
|
18
21
|
*/
|
|
19
|
-
const getDevices = (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
) => {
|
|
23
|
-
return new Observable<MediaDeviceInfo[]>((subscriber) => {
|
|
24
|
-
const enumerate = async () => {
|
|
22
|
+
const getDevices = (permission: BrowserPermission, kind: MediaDeviceKind) => {
|
|
23
|
+
return from(
|
|
24
|
+
(async () => {
|
|
25
25
|
let devices = await navigator.mediaDevices.enumerateDevices();
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
const needsGetUserMedia = devices.some(
|
|
26
|
+
// for privacy reasons, most browsers don't give you device labels
|
|
27
|
+
// unless you have a corresponding camera or microphone permission
|
|
28
|
+
const shouldPromptForBrowserPermission = devices.some(
|
|
30
29
|
(device) => device.kind === kind && device.label === '',
|
|
31
30
|
);
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
36
|
-
devices = await navigator.mediaDevices.enumerateDevices();
|
|
37
|
-
} finally {
|
|
38
|
-
if (mediaStream) disposeOfMediaStream(mediaStream);
|
|
39
|
-
}
|
|
31
|
+
if (shouldPromptForBrowserPermission) {
|
|
32
|
+
await permission.prompt({ throwOnNotAllowed: true });
|
|
33
|
+
devices = await navigator.mediaDevices.enumerateDevices();
|
|
40
34
|
}
|
|
41
|
-
return devices;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
enumerate()
|
|
45
|
-
.then((devices) => {
|
|
46
|
-
// notify subscribers and complete
|
|
47
|
-
subscriber.next(devices);
|
|
48
|
-
subscriber.complete();
|
|
49
|
-
})
|
|
50
|
-
.catch((error) => {
|
|
51
|
-
const logger = getLogger(['devices']);
|
|
52
|
-
logger('error', 'Failed to enumerate devices', error);
|
|
53
|
-
subscriber.error(error);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
35
|
+
return devices.filter((d) => d.kind === kind);
|
|
36
|
+
})(),
|
|
37
|
+
);
|
|
56
38
|
};
|
|
57
39
|
|
|
58
40
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
41
|
+
* Tells if the browser supports audio output change on 'audio' elements,
|
|
42
|
+
* see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId.
|
|
43
|
+
*/
|
|
62
44
|
export const checkIfAudioOutputChangeSupported = () => {
|
|
63
45
|
if (typeof document === 'undefined') return false;
|
|
64
46
|
const element = document.createElement('audio');
|
|
@@ -87,97 +69,92 @@ const videoDeviceConstraints = {
|
|
|
87
69
|
} satisfies MediaStreamConstraints;
|
|
88
70
|
|
|
89
71
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* @param create a function that creates an Observable.
|
|
72
|
+
* Keeps track of the browser permission to use microphone. This permission also
|
|
73
|
+
* affects an ability to enumerate audio devices.
|
|
94
74
|
*/
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
75
|
+
export const getAudioBrowserPermission = lazy(
|
|
76
|
+
() =>
|
|
77
|
+
new BrowserPermission({
|
|
78
|
+
constraints: audioDeviceConstraints,
|
|
79
|
+
queryName: 'microphone' as PermissionName,
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
102
82
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Keeps track of the browser permission to use camera. This permission also
|
|
85
|
+
* affects an ability to enumerate video devices.
|
|
86
|
+
*/
|
|
87
|
+
export const getVideoBrowserPermission = lazy(
|
|
88
|
+
() =>
|
|
89
|
+
new BrowserPermission({
|
|
90
|
+
constraints: videoDeviceConstraints,
|
|
91
|
+
queryName: 'camera' as PermissionName,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
95
|
+
const getDeviceChangeObserver = lazy(() => {
|
|
96
|
+
// 'addEventListener' is not available in React Native, returning
|
|
97
|
+
// an observable that will never fire
|
|
98
|
+
if (!navigator.mediaDevices.addEventListener) return from([]);
|
|
99
|
+
return fromEvent(navigator.mediaDevices, 'devicechange').pipe(
|
|
100
|
+
map(() => undefined),
|
|
117
101
|
debounceTime(500),
|
|
118
|
-
concatMap(() => from(navigator.mediaDevices.enumerateDevices())),
|
|
119
|
-
shareReplay(1),
|
|
120
102
|
);
|
|
121
103
|
});
|
|
122
104
|
|
|
123
|
-
const getAudioDevicesObserver = memoizedObservable(() => {
|
|
124
|
-
return merge(
|
|
125
|
-
getDevices(audioDeviceConstraints, 'audioinput'),
|
|
126
|
-
getDeviceChangeObserver(),
|
|
127
|
-
).pipe(shareReplay(1));
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const getAudioOutputDevicesObserver = memoizedObservable(() => {
|
|
131
|
-
return merge(
|
|
132
|
-
getDevices(audioDeviceConstraints, 'audiooutput'),
|
|
133
|
-
getDeviceChangeObserver(),
|
|
134
|
-
).pipe(shareReplay(1));
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const getVideoDevicesObserver = memoizedObservable(() => {
|
|
138
|
-
return merge(
|
|
139
|
-
getDevices(videoDeviceConstraints, 'videoinput'),
|
|
140
|
-
getDeviceChangeObserver(),
|
|
141
|
-
).pipe(shareReplay(1));
|
|
142
|
-
});
|
|
143
|
-
|
|
144
105
|
/**
|
|
145
|
-
* Prompts the user for a permission to use audio devices (if not already granted
|
|
106
|
+
* Prompts the user for a permission to use audio devices (if not already granted
|
|
107
|
+
* and was not prompted before) and lists the available 'audioinput' devices,
|
|
108
|
+
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
109
|
+
* the observable errors.
|
|
146
110
|
*/
|
|
147
|
-
export const getAudioDevices = () => {
|
|
148
|
-
return
|
|
149
|
-
|
|
111
|
+
export const getAudioDevices = lazy(() => {
|
|
112
|
+
return merge(
|
|
113
|
+
getDeviceChangeObserver(),
|
|
114
|
+
getAudioBrowserPermission().asObservable(),
|
|
115
|
+
).pipe(
|
|
116
|
+
startWith(undefined),
|
|
117
|
+
concatMap(() => getDevices(getAudioBrowserPermission(), 'audioinput')),
|
|
118
|
+
shareReplay(1),
|
|
150
119
|
);
|
|
151
|
-
};
|
|
120
|
+
});
|
|
152
121
|
|
|
153
122
|
/**
|
|
154
|
-
* Prompts the user for a permission to use video devices (if not already granted
|
|
123
|
+
* Prompts the user for a permission to use video devices (if not already granted
|
|
124
|
+
* and was not prompted before) and lists the available 'videoinput' devices,
|
|
125
|
+
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
126
|
+
* the observable errors.
|
|
155
127
|
*/
|
|
156
128
|
export const getVideoDevices = () => {
|
|
157
|
-
return
|
|
158
|
-
|
|
129
|
+
return merge(
|
|
130
|
+
getDeviceChangeObserver(),
|
|
131
|
+
getVideoBrowserPermission().asObservable(),
|
|
132
|
+
).pipe(
|
|
133
|
+
startWith(undefined),
|
|
134
|
+
concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput')),
|
|
135
|
+
shareReplay(1),
|
|
159
136
|
);
|
|
160
137
|
};
|
|
161
138
|
|
|
162
139
|
/**
|
|
163
|
-
* Prompts the user for a permission to use
|
|
140
|
+
* Prompts the user for a permission to use video devices (if not already granted
|
|
141
|
+
* and was not prompted before) and lists the available 'audiooutput' devices,
|
|
142
|
+
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
143
|
+
* the observable errors.
|
|
164
144
|
*/
|
|
165
145
|
export const getAudioOutputDevices = () => {
|
|
166
|
-
return
|
|
167
|
-
|
|
146
|
+
return merge(
|
|
147
|
+
getDeviceChangeObserver(),
|
|
148
|
+
getAudioBrowserPermission().asObservable(),
|
|
149
|
+
).pipe(
|
|
150
|
+
startWith(undefined),
|
|
151
|
+
concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput')),
|
|
152
|
+
shareReplay(1),
|
|
168
153
|
);
|
|
169
154
|
};
|
|
170
155
|
|
|
171
156
|
const getStream = async (constraints: MediaStreamConstraints) => {
|
|
172
|
-
|
|
173
|
-
return await navigator.mediaDevices.getUserMedia(constraints);
|
|
174
|
-
} catch (e) {
|
|
175
|
-
getLogger(['devices'])('error', `Failed to getUserMedia`, {
|
|
176
|
-
error: e,
|
|
177
|
-
constraints: constraints,
|
|
178
|
-
});
|
|
179
|
-
throw e;
|
|
180
|
-
}
|
|
157
|
+
return await navigator.mediaDevices.getUserMedia(constraints);
|
|
181
158
|
};
|
|
182
159
|
|
|
183
160
|
/**
|
|
@@ -197,7 +174,20 @@ export const getAudioStream = async (
|
|
|
197
174
|
...trackConstraints,
|
|
198
175
|
},
|
|
199
176
|
};
|
|
200
|
-
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
await getAudioBrowserPermission().prompt({
|
|
180
|
+
throwOnNotAllowed: true,
|
|
181
|
+
forcePrompt: true,
|
|
182
|
+
});
|
|
183
|
+
return getStream(constraints);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
getLogger(['devices'])('error', 'Failed to get audio stream', {
|
|
186
|
+
error: e,
|
|
187
|
+
constraints: constraints,
|
|
188
|
+
});
|
|
189
|
+
throw e;
|
|
190
|
+
}
|
|
201
191
|
};
|
|
202
192
|
|
|
203
193
|
/**
|
|
@@ -217,7 +207,19 @@ export const getVideoStream = async (
|
|
|
217
207
|
...trackConstraints,
|
|
218
208
|
},
|
|
219
209
|
};
|
|
220
|
-
|
|
210
|
+
try {
|
|
211
|
+
await getVideoBrowserPermission().prompt({
|
|
212
|
+
throwOnNotAllowed: true,
|
|
213
|
+
forcePrompt: true,
|
|
214
|
+
});
|
|
215
|
+
return getStream(constraints);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
getLogger(['devices'])('error', 'Failed to get video stream', {
|
|
218
|
+
error: e,
|
|
219
|
+
constraints: constraints,
|
|
220
|
+
});
|
|
221
|
+
throw e;
|
|
222
|
+
}
|
|
221
223
|
};
|
|
222
224
|
|
|
223
225
|
/**
|
|
@@ -257,12 +259,11 @@ export const getScreenShareStream = async (
|
|
|
257
259
|
export const deviceIds$ =
|
|
258
260
|
typeof navigator !== 'undefined' &&
|
|
259
261
|
typeof navigator.mediaDevices !== 'undefined'
|
|
260
|
-
?
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
)()
|
|
262
|
+
? getDeviceChangeObserver().pipe(
|
|
263
|
+
startWith(undefined),
|
|
264
|
+
concatMap(() => navigator.mediaDevices.enumerateDevices()),
|
|
265
|
+
shareReplay(1),
|
|
266
|
+
)
|
|
266
267
|
: undefined;
|
|
267
268
|
|
|
268
269
|
/**
|
|
@@ -7,7 +7,7 @@ const AUDIO_LEVEL_THRESHOLD = 0.2;
|
|
|
7
7
|
export class RNSpeechDetector {
|
|
8
8
|
private pc1 = new RTCPeerConnection({});
|
|
9
9
|
private pc2 = new RTCPeerConnection({});
|
|
10
|
-
private intervalId: NodeJS.
|
|
10
|
+
private intervalId: NodeJS.Timeout | undefined;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Starts the speech detection.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const uninitialized = Symbol('uninitialized');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lazily creates a value using a provided factory
|
|
5
|
+
*/
|
|
6
|
+
export function lazy<T>(factory: () => T): () => T {
|
|
7
|
+
let value: T | typeof uninitialized = uninitialized;
|
|
8
|
+
return () => {
|
|
9
|
+
if (value === uninitialized) {
|
|
10
|
+
value = factory();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return value;
|
|
14
|
+
};
|
|
15
|
+
}
|