@stream-io/video-client 1.9.2 → 1.10.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 +14 -0
- package/dist/index.browser.es.js +260 -26
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +260 -26
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +260 -26
- package/dist/index.es.js.map +1 -1
- package/dist/src/gen/video/sfu/models/models.d.ts +134 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +27 -1
- package/dist/src/stats/SfuStatsReporter.d.ts +13 -1
- package/dist/src/store/rxUtils.d.ts +2 -1
- package/package.json +1 -1
- package/src/Call.ts +3 -0
- package/src/coordinator/connection/client.ts +1 -1
- package/src/devices/InputMediaDeviceManager.ts +12 -4
- package/src/devices/__tests__/CameraManager.test.ts +2 -2
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +1 -1
- package/src/devices/devices.ts +32 -12
- package/src/gen/video/sfu/models/models.ts +192 -0
- package/src/gen/video/sfu/signal_rpc/signal.ts +48 -0
- package/src/stats/SfuStatsReporter.ts +83 -5
- package/src/store/rxUtils.ts +6 -5
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
import { combineLatest } from 'rxjs';
|
|
1
2
|
import { StreamSfuClient } from '../StreamSfuClient';
|
|
2
|
-
import { StatsOptions } from '../gen/coordinator';
|
|
3
|
+
import { OwnCapability, StatsOptions } from '../gen/coordinator';
|
|
3
4
|
import { getLogger } from '../logger';
|
|
4
5
|
import { Publisher, Subscriber } from '../rtc';
|
|
5
6
|
import { flatten, getSdkName, getSdkVersion } from './utils';
|
|
6
7
|
import { getWebRTCInfo, LocalClientDetailsType } from '../client-details';
|
|
8
|
+
import { InputDevices } from '../gen/video/sfu/models/models';
|
|
9
|
+
import { CameraManager, MicrophoneManager } from '../devices';
|
|
10
|
+
import { createSubscription } from '../store/rxUtils';
|
|
11
|
+
import { CallState } from '../store';
|
|
7
12
|
|
|
8
13
|
export type SfuStatsReporterOptions = {
|
|
9
14
|
options: StatsOptions;
|
|
10
15
|
clientDetails: LocalClientDetailsType;
|
|
11
16
|
subscriber: Subscriber;
|
|
12
17
|
publisher?: Publisher;
|
|
18
|
+
microphone: MicrophoneManager;
|
|
19
|
+
camera: CameraManager;
|
|
20
|
+
state: CallState;
|
|
13
21
|
};
|
|
14
22
|
|
|
15
23
|
export class SfuStatsReporter {
|
|
@@ -20,34 +28,91 @@ export class SfuStatsReporter {
|
|
|
20
28
|
private readonly sfuClient: StreamSfuClient;
|
|
21
29
|
private readonly subscriber: Subscriber;
|
|
22
30
|
private readonly publisher?: Publisher;
|
|
31
|
+
private readonly microphone: MicrophoneManager;
|
|
32
|
+
private readonly camera: CameraManager;
|
|
33
|
+
private readonly state: CallState;
|
|
23
34
|
|
|
24
35
|
private intervalId: NodeJS.Timeout | undefined;
|
|
36
|
+
private unsubscribeDevicePermissionsSubscription?: () => void;
|
|
37
|
+
private unsubscribeListDevicesSubscription?: () => void;
|
|
25
38
|
private readonly sdkName: string;
|
|
26
39
|
private readonly sdkVersion: string;
|
|
27
40
|
private readonly webRTCVersion: string;
|
|
41
|
+
private readonly inputDevices = new Map<'mic' | 'camera', InputDevices>();
|
|
28
42
|
|
|
29
43
|
constructor(
|
|
30
44
|
sfuClient: StreamSfuClient,
|
|
31
|
-
{
|
|
45
|
+
{
|
|
46
|
+
options,
|
|
47
|
+
clientDetails,
|
|
48
|
+
subscriber,
|
|
49
|
+
publisher,
|
|
50
|
+
microphone,
|
|
51
|
+
camera,
|
|
52
|
+
state,
|
|
53
|
+
}: SfuStatsReporterOptions,
|
|
32
54
|
) {
|
|
33
55
|
this.sfuClient = sfuClient;
|
|
34
56
|
this.options = options;
|
|
35
57
|
this.subscriber = subscriber;
|
|
36
58
|
this.publisher = publisher;
|
|
37
|
-
|
|
59
|
+
this.microphone = microphone;
|
|
60
|
+
this.camera = camera;
|
|
61
|
+
this.state = state;
|
|
38
62
|
|
|
39
63
|
const { sdk, browser } = clientDetails;
|
|
40
|
-
|
|
41
64
|
this.sdkName = getSdkName(sdk);
|
|
42
65
|
this.sdkVersion = getSdkVersion(sdk);
|
|
43
66
|
|
|
44
|
-
//
|
|
67
|
+
// use the WebRTC version if set by the SDK (React Native) otherwise,
|
|
68
|
+
// use the browser version as a fallback
|
|
69
|
+
const webRTCInfo = getWebRTCInfo();
|
|
45
70
|
this.webRTCVersion =
|
|
46
71
|
webRTCInfo?.version ||
|
|
47
72
|
`${browser?.name || ''}-${browser?.version || ''}` ||
|
|
48
73
|
'N/A';
|
|
49
74
|
}
|
|
50
75
|
|
|
76
|
+
private observeDevice = (
|
|
77
|
+
device: CameraManager | MicrophoneManager,
|
|
78
|
+
kind: 'mic' | 'camera',
|
|
79
|
+
) => {
|
|
80
|
+
const { hasBrowserPermission$ } = device.state;
|
|
81
|
+
this.unsubscribeDevicePermissionsSubscription?.();
|
|
82
|
+
this.unsubscribeDevicePermissionsSubscription = createSubscription(
|
|
83
|
+
combineLatest([hasBrowserPermission$, this.state.ownCapabilities$]),
|
|
84
|
+
([hasPermission, ownCapabilities]) => {
|
|
85
|
+
// cleanup the previous listDevices() subscription in case
|
|
86
|
+
// permissions or capabilities have changed.
|
|
87
|
+
// we will subscribe again if everything is in order.
|
|
88
|
+
this.unsubscribeListDevicesSubscription?.();
|
|
89
|
+
const hasCapability =
|
|
90
|
+
kind === 'mic'
|
|
91
|
+
? ownCapabilities.includes(OwnCapability.SEND_AUDIO)
|
|
92
|
+
: ownCapabilities.includes(OwnCapability.SEND_VIDEO);
|
|
93
|
+
if (!hasPermission || !hasCapability) {
|
|
94
|
+
this.inputDevices.set(kind, {
|
|
95
|
+
currentDevice: '',
|
|
96
|
+
availableDevices: [],
|
|
97
|
+
isPermitted: false,
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.unsubscribeListDevicesSubscription = createSubscription(
|
|
102
|
+
combineLatest([device.listDevices(), device.state.selectedDevice$]),
|
|
103
|
+
([devices, deviceId]) => {
|
|
104
|
+
const selected = devices.find((d) => d.deviceId === deviceId);
|
|
105
|
+
this.inputDevices.set(kind, {
|
|
106
|
+
currentDevice: selected?.label || deviceId || '',
|
|
107
|
+
availableDevices: devices.map((d) => d.label),
|
|
108
|
+
isPermitted: true,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
51
116
|
private run = async () => {
|
|
52
117
|
const [subscriberStats, publisherStats] = await Promise.all([
|
|
53
118
|
this.subscriber.getStats().then(flatten).then(JSON.stringify),
|
|
@@ -60,11 +125,18 @@ export class SfuStatsReporter {
|
|
|
60
125
|
webrtcVersion: this.webRTCVersion,
|
|
61
126
|
subscriberStats,
|
|
62
127
|
publisherStats,
|
|
128
|
+
audioDevices: this.inputDevices.get('mic'),
|
|
129
|
+
videoDevices: this.inputDevices.get('camera'),
|
|
130
|
+
deviceState: { oneofKind: undefined },
|
|
63
131
|
});
|
|
64
132
|
};
|
|
65
133
|
|
|
66
134
|
start = () => {
|
|
67
135
|
if (this.options.reporting_interval_ms <= 0) return;
|
|
136
|
+
|
|
137
|
+
this.observeDevice(this.microphone, 'mic');
|
|
138
|
+
this.observeDevice(this.camera, 'camera');
|
|
139
|
+
|
|
68
140
|
clearInterval(this.intervalId);
|
|
69
141
|
this.intervalId = setInterval(() => {
|
|
70
142
|
this.run().catch((err) => {
|
|
@@ -74,6 +146,12 @@ export class SfuStatsReporter {
|
|
|
74
146
|
};
|
|
75
147
|
|
|
76
148
|
stop = () => {
|
|
149
|
+
this.unsubscribeDevicePermissionsSubscription?.();
|
|
150
|
+
this.unsubscribeDevicePermissionsSubscription = undefined;
|
|
151
|
+
this.unsubscribeListDevicesSubscription?.();
|
|
152
|
+
this.unsubscribeListDevicesSubscription = undefined;
|
|
153
|
+
|
|
154
|
+
this.inputDevices.clear();
|
|
77
155
|
clearInterval(this.intervalId);
|
|
78
156
|
this.intervalId = undefined;
|
|
79
157
|
};
|
package/src/store/rxUtils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { combineLatest, Observable, Subject } from 'rxjs';
|
|
2
2
|
import { withoutConcurrency } from '../helpers/concurrency';
|
|
3
|
+
import { getLogger } from '../logger';
|
|
3
4
|
|
|
4
5
|
type FunctionPatch<T> = (currentValue: T) => T;
|
|
5
6
|
|
|
@@ -63,12 +64,15 @@ export const setCurrentValue = <T>(subject: Subject<T>, update: Patch<T>) => {
|
|
|
63
64
|
*
|
|
64
65
|
* @param observable the observable to subscribe to.
|
|
65
66
|
* @param handler the handler to call when the observable emits a value.
|
|
67
|
+
* @param onError an optional error handler.
|
|
66
68
|
*/
|
|
67
69
|
export const createSubscription = <T>(
|
|
68
70
|
observable: Observable<T>,
|
|
69
71
|
handler: (value: T) => void,
|
|
72
|
+
onError: (error: any) => void = (error) =>
|
|
73
|
+
getLogger(['RxUtils'])('warn', 'An observable emitted an error', error),
|
|
70
74
|
) => {
|
|
71
|
-
const subscription = observable.subscribe(handler);
|
|
75
|
+
const subscription = observable.subscribe({ next: handler, error: onError });
|
|
72
76
|
return () => {
|
|
73
77
|
subscription.unsubscribe();
|
|
74
78
|
};
|
|
@@ -87,10 +91,7 @@ export const createSafeAsyncSubscription = <T>(
|
|
|
87
91
|
handler: (value: T) => Promise<void>,
|
|
88
92
|
) => {
|
|
89
93
|
const tag = Symbol();
|
|
90
|
-
|
|
94
|
+
return createSubscription(observable, (value) => {
|
|
91
95
|
withoutConcurrency(tag, () => handler(value));
|
|
92
96
|
});
|
|
93
|
-
return () => {
|
|
94
|
-
subscription.unsubscribe();
|
|
95
|
-
};
|
|
96
97
|
};
|