@stream-io/video-client 1.6.2 → 1.6.4

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.
@@ -2,6 +2,7 @@ import { Observable } from 'rxjs';
2
2
  import { Call } from '../Call';
3
3
  import { CameraDirection, CameraManagerState } from './CameraManagerState';
4
4
  import { InputMediaDeviceManager } from './InputMediaDeviceManager';
5
+ import { PublishOptions } from '../types';
5
6
  type PreferredCodec = 'vp8' | 'h264' | string;
6
7
  export declare class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
7
8
  private targetResolution;
@@ -11,7 +12,19 @@ export declare class CameraManager extends InputMediaDeviceManager<CameraManager
11
12
  * @internal internal use only, not part of the public API.
12
13
  */
13
14
  preferredCodec: PreferredCodec | undefined;
15
+ /**
16
+ * The preferred bitrate for encoding the video.
17
+ *
18
+ * @internal internal use only, not part of the public API.
19
+ */
20
+ preferredBitrate: number | undefined;
14
21
  constructor(call: Call);
22
+ /**
23
+ * The publish options for the camera.
24
+ *
25
+ * @internal internal use only, not part of the public API.
26
+ */
27
+ get publishOptions(): PublishOptions;
15
28
  /**
16
29
  * Select the camera direction.
17
30
  *
@@ -39,6 +52,13 @@ export declare class CameraManager extends InputMediaDeviceManager<CameraManager
39
52
  * @param codec the codec to use for encoding the video.
40
53
  */
41
54
  setPreferredCodec(codec: 'vp8' | 'h264' | string | undefined): void;
55
+ /**
56
+ * Sets the preferred bitrate for encoding the video.
57
+ *
58
+ * @internal internal use only, not part of the public API.
59
+ * @param bitrate the bitrate to use for encoding the video.
60
+ */
61
+ setPreferredBitrate(bitrate: number | undefined): void;
42
62
  protected getDevices(): Observable<MediaDeviceInfo[]>;
43
63
  protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
44
64
  protected publishStream(stream: MediaStream): Promise<void>;
@@ -14,3 +14,7 @@ export declare const getPreferredCodecs: (kind: "audio" | "video", preferredCode
14
14
  * @param direction the direction of the transceiver.
15
15
  */
16
16
  export declare const getGenericSdp: (direction: RTCRtpTransceiverDirection) => Promise<string>;
17
+ /**
18
+ * Returns the optimal codec for RN.
19
+ */
20
+ export declare const getRNOptimalCodec: () => "h264" | "vp8" | undefined;
@@ -10,8 +10,9 @@ export type OptimalVideoLayer = RTCRtpEncodingParameters & {
10
10
  *
11
11
  * @param videoTrack the video track to find optimal layers for.
12
12
  * @param targetResolution the expected target resolution.
13
+ * @param preferredBitrate the preferred bitrate for the video track.
13
14
  */
14
- export declare const findOptimalVideoLayers: (videoTrack: MediaStreamTrack, targetResolution?: TargetResolutionResponse) => OptimalVideoLayer[];
15
+ export declare const findOptimalVideoLayers: (videoTrack: MediaStreamTrack, targetResolution: TargetResolutionResponse | undefined, preferredBitrate: number | undefined) => OptimalVideoLayer[];
15
16
  /**
16
17
  * Computes the maximum bitrate for a given resolution.
17
18
  * If the current resolution is lower than the target resolution,
@@ -22,6 +23,7 @@ export declare const findOptimalVideoLayers: (videoTrack: MediaStreamTrack, targ
22
23
  * @param targetResolution the target resolution.
23
24
  * @param currentWidth the current width of the track.
24
25
  * @param currentHeight the current height of the track.
26
+ * @param preferredBitrate the preferred bitrate for the track.
25
27
  */
26
- export declare const getComputedMaxBitrate: (targetResolution: TargetResolutionResponse, currentWidth: number, currentHeight: number) => number;
28
+ export declare const getComputedMaxBitrate: (targetResolution: TargetResolutionResponse, currentWidth: number, currentHeight: number, preferredBitrate: number | undefined) => number;
27
29
  export declare const findOptimalScreenSharingLayers: (videoTrack: MediaStreamTrack, preferences?: ScreenShareSettings, defaultMaxBitrate?: number) => OptimalVideoLayer[];
@@ -111,6 +111,7 @@ export type SubscriptionChanges = {
111
111
  };
112
112
  export type PublishOptions = {
113
113
  preferredCodec?: string | null;
114
+ preferredBitrate?: number;
114
115
  screenShareSettings?: ScreenShareSettings;
115
116
  };
116
117
  export type ScreenShareSettings = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
package/src/Call.ts CHANGED
@@ -1324,9 +1324,10 @@ export class Call {
1324
1324
  case TrackType.VIDEO:
1325
1325
  const videoStream = this.camera.state.mediaStream;
1326
1326
  if (videoStream) {
1327
- await this.publishVideoStream(videoStream, {
1328
- preferredCodec: this.camera.preferredCodec,
1329
- });
1327
+ await this.publishVideoStream(
1328
+ videoStream,
1329
+ this.camera.publishOptions,
1330
+ );
1330
1331
  }
1331
1332
  break;
1332
1333
  case TrackType.SCREEN_SHARE:
@@ -2209,9 +2210,10 @@ export class Call {
2209
2210
  this.camera.state.mediaStream &&
2210
2211
  !this.publisher?.isPublishing(TrackType.VIDEO)
2211
2212
  ) {
2212
- await this.publishVideoStream(this.camera.state.mediaStream, {
2213
- preferredCodec: this.camera.preferredCodec,
2214
- });
2213
+ await this.publishVideoStream(
2214
+ this.camera.state.mediaStream,
2215
+ this.camera.publishOptions,
2216
+ );
2215
2217
  }
2216
2218
 
2217
2219
  // Start camera if backend config specifies, and there is no local setting
@@ -320,6 +320,7 @@ export class StreamVideoClient {
320
320
  return;
321
321
  }
322
322
  const userId = this.streamClient.user?.id;
323
+ const apiKey = this.streamClient.key;
323
324
  const disconnectUser = () => this.streamClient.disconnectUser(timeout);
324
325
  this.disconnectionPromise = this.connectionPromise
325
326
  ? this.connectionPromise.then(() => disconnectUser())
@@ -329,7 +330,7 @@ export class StreamVideoClient {
329
330
  );
330
331
  await this.disconnectionPromise;
331
332
  if (userId) {
332
- StreamVideoClient._instanceMap.delete(userId);
333
+ StreamVideoClient._instanceMap.delete(apiKey + userId);
333
334
  }
334
335
  this.eventHandlersToUnregister.forEach((unregister) => unregister());
335
336
  this.eventHandlersToUnregister = [];
@@ -4,6 +4,7 @@ import { CameraDirection, CameraManagerState } from './CameraManagerState';
4
4
  import { InputMediaDeviceManager } from './InputMediaDeviceManager';
5
5
  import { getVideoDevices, getVideoStream } from './devices';
6
6
  import { TrackType } from '../gen/video/sfu/models/models';
7
+ import { PublishOptions } from '../types';
7
8
 
8
9
  type PreferredCodec = 'vp8' | 'h264' | string;
9
10
 
@@ -20,10 +21,29 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
20
21
  */
21
22
  preferredCodec: PreferredCodec | undefined;
22
23
 
24
+ /**
25
+ * The preferred bitrate for encoding the video.
26
+ *
27
+ * @internal internal use only, not part of the public API.
28
+ */
29
+ preferredBitrate: number | undefined;
30
+
23
31
  constructor(call: Call) {
24
32
  super(call, new CameraManagerState(), TrackType.VIDEO);
25
33
  }
26
34
 
35
+ /**
36
+ * The publish options for the camera.
37
+ *
38
+ * @internal internal use only, not part of the public API.
39
+ */
40
+ get publishOptions(): PublishOptions {
41
+ return {
42
+ preferredCodec: this.preferredCodec,
43
+ preferredBitrate: this.preferredBitrate,
44
+ };
45
+ }
46
+
27
47
  /**
28
48
  * Select the camera direction.
29
49
  *
@@ -88,6 +108,16 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
88
108
  this.preferredCodec = codec;
89
109
  }
90
110
 
111
+ /**
112
+ * Sets the preferred bitrate for encoding the video.
113
+ *
114
+ * @internal internal use only, not part of the public API.
115
+ * @param bitrate the bitrate to use for encoding the video.
116
+ */
117
+ setPreferredBitrate(bitrate: number | undefined) {
118
+ this.preferredBitrate = bitrate;
119
+ }
120
+
91
121
  protected getDevices(): Observable<MediaDeviceInfo[]> {
92
122
  return getVideoDevices();
93
123
  }
@@ -107,9 +137,7 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
107
137
  }
108
138
 
109
139
  protected publishStream(stream: MediaStream): Promise<void> {
110
- return this.call.publishVideoStream(stream, {
111
- preferredCodec: this.preferredCodec,
112
- });
140
+ return this.call.publishVideoStream(stream, this.publishOptions);
113
141
  }
114
142
 
115
143
  protected stopPublishStream(stopTracks: boolean): Promise<void> {
@@ -13,7 +13,7 @@ import {
13
13
  findOptimalVideoLayers,
14
14
  OptimalVideoLayer,
15
15
  } from './videoLayers';
16
- import { getPreferredCodecs } from './codecs';
16
+ import { getPreferredCodecs, getRNOptimalCodec } from './codecs';
17
17
  import { trackTypeToParticipantStreamKey } from './helpers/tracks';
18
18
  import { CallingState, CallState } from '../store';
19
19
  import { PublishOptions } from '../types';
@@ -22,7 +22,6 @@ import { enableHighQualityAudio, toggleDtx } from '../helpers/sdp-munging';
22
22
  import { Logger } from '../coordinator/connection/types';
23
23
  import { getLogger } from '../logger';
24
24
  import { Dispatcher } from './Dispatcher';
25
- import { getOSInfo } from '../client-details';
26
25
  import { VideoLayerSetting } from '../gen/video/sfu/event/events';
27
26
  import { TargetResolutionResponse } from '../gen/shims';
28
27
 
@@ -260,30 +259,18 @@ export class Publisher {
260
259
  const screenShareBitrate =
261
260
  settings?.screensharing.target_resolution?.bitrate;
262
261
 
262
+ const { preferredBitrate, preferredCodec, screenShareSettings } = opts;
263
263
  const videoEncodings =
264
264
  trackType === TrackType.VIDEO
265
- ? findOptimalVideoLayers(track, targetResolution)
265
+ ? findOptimalVideoLayers(track, targetResolution, preferredBitrate)
266
266
  : trackType === TrackType.SCREEN_SHARE
267
267
  ? findOptimalScreenSharingLayers(
268
268
  track,
269
- opts.screenShareSettings,
269
+ screenShareSettings,
270
270
  screenShareBitrate,
271
271
  )
272
272
  : undefined;
273
273
 
274
- let preferredCodec = opts.preferredCodec;
275
- if (!preferredCodec && trackType === TrackType.VIDEO && isReactNative()) {
276
- const osName = getOSInfo()?.name.toLowerCase();
277
- if (osName === 'ipados') {
278
- // in ipads it was noticed that if vp8 codec is used
279
- // then the bytes sent is 0 in the outbound-rtp
280
- // so we are forcing h264 codec for ipads
281
- preferredCodec = 'H264';
282
- } else if (osName === 'android') {
283
- preferredCodec = 'VP8';
284
- }
285
- }
286
-
287
274
  // listen for 'ended' event on the track as it might be ended abruptly
288
275
  // by an external factor as permission revokes, device disconnected, etc.
289
276
  // keep in mind that `track.stop()` doesn't trigger this event.
@@ -306,9 +293,14 @@ export class Publisher {
306
293
  this.transceiverRegistry[trackType] = transceiver;
307
294
  this.publishOptionsPerTrackType.set(trackType, opts);
308
295
 
296
+ const codec =
297
+ isReactNative() && trackType === TrackType.VIDEO && !preferredCodec
298
+ ? getRNOptimalCodec()
299
+ : preferredCodec;
300
+
309
301
  const codecPreferences =
310
302
  'setCodecPreferences' in transceiver
311
- ? this.getCodecPreferences(trackType, preferredCodec)
303
+ ? this.getCodecPreferences(trackType, codec)
312
304
  : undefined;
313
305
  if (codecPreferences) {
314
306
  this.logger(
@@ -721,7 +713,11 @@ export class Publisher {
721
713
  const publishOpts = this.publishOptionsPerTrackType.get(trackType);
722
714
  optimalLayers =
723
715
  trackType === TrackType.VIDEO
724
- ? findOptimalVideoLayers(track, targetResolution)
716
+ ? findOptimalVideoLayers(
717
+ track,
718
+ targetResolution,
719
+ publishOpts?.preferredBitrate,
720
+ )
725
721
  : trackType === TrackType.SCREEN_SHARE
726
722
  ? findOptimalScreenSharingLayers(
727
723
  track,
@@ -138,7 +138,12 @@ describe('videoLayers', () => {
138
138
  describe('getComputedMaxBitrate', () => {
139
139
  it('should scale target bitrate down if resolution is smaller than target resolution', () => {
140
140
  const targetResolution = { width: 1920, height: 1080, bitrate: 3000000 };
141
- const scaledBitrate = getComputedMaxBitrate(targetResolution, 1280, 720);
141
+ const scaledBitrate = getComputedMaxBitrate(
142
+ targetResolution,
143
+ 1280,
144
+ 720,
145
+ undefined,
146
+ );
142
147
  expect(scaledBitrate).toBe(1333333);
143
148
  });
144
149
 
@@ -148,7 +153,12 @@ describe('videoLayers', () => {
148
153
  const targetBitrates = ['f', 'h', 'q'].map((rid) => {
149
154
  const width = targetResolution.width / downscaleFactor;
150
155
  const height = targetResolution.height / downscaleFactor;
151
- const bitrate = getComputedMaxBitrate(targetResolution, width, height);
156
+ const bitrate = getComputedMaxBitrate(
157
+ targetResolution,
158
+ width,
159
+ height,
160
+ undefined,
161
+ );
152
162
  downscaleFactor *= 2;
153
163
  return {
154
164
  rid,
@@ -166,25 +176,45 @@ describe('videoLayers', () => {
166
176
 
167
177
  it('should not scale target bitrate if resolution is larger than target resolution', () => {
168
178
  const targetResolution = { width: 1280, height: 720, bitrate: 1000000 };
169
- const scaledBitrate = getComputedMaxBitrate(targetResolution, 2560, 1440);
179
+ const scaledBitrate = getComputedMaxBitrate(
180
+ targetResolution,
181
+ 2560,
182
+ 1440,
183
+ undefined,
184
+ );
170
185
  expect(scaledBitrate).toBe(1000000);
171
186
  });
172
187
 
173
188
  it('should not scale target bitrate if resolution is equal to target resolution', () => {
174
189
  const targetResolution = { width: 1280, height: 720, bitrate: 1000000 };
175
- const scaledBitrate = getComputedMaxBitrate(targetResolution, 1280, 720);
190
+ const scaledBitrate = getComputedMaxBitrate(
191
+ targetResolution,
192
+ 1280,
193
+ 720,
194
+ undefined,
195
+ );
176
196
  expect(scaledBitrate).toBe(1000000);
177
197
  });
178
198
 
179
199
  it('should handle 0 width and height', () => {
180
200
  const targetResolution = { width: 1280, height: 720, bitrate: 1000000 };
181
- const scaledBitrate = getComputedMaxBitrate(targetResolution, 0, 0);
201
+ const scaledBitrate = getComputedMaxBitrate(
202
+ targetResolution,
203
+ 0,
204
+ 0,
205
+ undefined,
206
+ );
182
207
  expect(scaledBitrate).toBe(0);
183
208
  });
184
209
 
185
210
  it('should handle 4k target resolution', () => {
186
211
  const targetResolution = { width: 3840, height: 2160, bitrate: 15000000 };
187
- const scaledBitrate = getComputedMaxBitrate(targetResolution, 1280, 720);
212
+ const scaledBitrate = getComputedMaxBitrate(
213
+ targetResolution,
214
+ 1280,
215
+ 720,
216
+ undefined,
217
+ );
188
218
  expect(scaledBitrate).toBe(1666667);
189
219
  });
190
220
  });
package/src/rtc/codecs.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { getOSInfo } from '../client-details';
2
+
1
3
  /**
2
4
  * Returns back a list of sorted codecs, with the preferred codec first.
3
5
  *
@@ -88,3 +90,16 @@ export const getGenericSdp = async (direction: RTCRtpTransceiverDirection) => {
88
90
  tempPc.close();
89
91
  return sdp;
90
92
  };
93
+
94
+ /**
95
+ * Returns the optimal codec for RN.
96
+ */
97
+ export const getRNOptimalCodec = () => {
98
+ const osName = getOSInfo()?.name.toLowerCase();
99
+ // in ipads it was noticed that if vp8 codec is used
100
+ // then the bytes sent is 0 in the outbound-rtp
101
+ // so we are forcing h264 codec for ipads
102
+ if (osName === 'ipados') return 'h264';
103
+ if (osName === 'android') return 'vp8';
104
+ return undefined;
105
+ };
@@ -25,16 +25,23 @@ const defaultBitratePerRid: Record<string, number> = {
25
25
  *
26
26
  * @param videoTrack the video track to find optimal layers for.
27
27
  * @param targetResolution the expected target resolution.
28
+ * @param preferredBitrate the preferred bitrate for the video track.
28
29
  */
29
30
  export const findOptimalVideoLayers = (
30
31
  videoTrack: MediaStreamTrack,
31
32
  targetResolution: TargetResolutionResponse = defaultTargetResolution,
33
+ preferredBitrate: number | undefined,
32
34
  ) => {
33
35
  const optimalVideoLayers: OptimalVideoLayer[] = [];
34
36
  const settings = videoTrack.getSettings();
35
37
  const { width: w = 0, height: h = 0 } = settings;
36
38
 
37
- const maxBitrate = getComputedMaxBitrate(targetResolution, w, h);
39
+ const maxBitrate = getComputedMaxBitrate(
40
+ targetResolution,
41
+ w,
42
+ h,
43
+ preferredBitrate,
44
+ );
38
45
  let downscaleFactor = 1;
39
46
  ['f', 'h', 'q'].forEach((rid) => {
40
47
  // Reversing the order [f, h, q] to [q, h, f] as Chrome uses encoding index
@@ -68,22 +75,29 @@ export const findOptimalVideoLayers = (
68
75
  * @param targetResolution the target resolution.
69
76
  * @param currentWidth the current width of the track.
70
77
  * @param currentHeight the current height of the track.
78
+ * @param preferredBitrate the preferred bitrate for the track.
71
79
  */
72
80
  export const getComputedMaxBitrate = (
73
81
  targetResolution: TargetResolutionResponse,
74
82
  currentWidth: number,
75
83
  currentHeight: number,
84
+ preferredBitrate: number | undefined,
76
85
  ): number => {
77
86
  // if the current resolution is lower than the target resolution,
78
87
  // we want to proportionally reduce the target bitrate
79
- const { width: targetWidth, height: targetHeight } = targetResolution;
88
+ const {
89
+ width: targetWidth,
90
+ height: targetHeight,
91
+ bitrate: targetBitrate,
92
+ } = targetResolution;
93
+ const bitrate = preferredBitrate || targetBitrate;
80
94
  if (currentWidth < targetWidth || currentHeight < targetHeight) {
81
95
  const currentPixels = currentWidth * currentHeight;
82
96
  const targetPixels = targetWidth * targetHeight;
83
97
  const reductionFactor = currentPixels / targetPixels;
84
- return Math.round(targetResolution.bitrate * reductionFactor);
98
+ return Math.round(bitrate * reductionFactor);
85
99
  }
86
- return targetResolution.bitrate;
100
+ return bitrate;
87
101
  };
88
102
 
89
103
  /**
package/src/types.ts CHANGED
@@ -148,6 +148,7 @@ export type SubscriptionChanges = {
148
148
 
149
149
  export type PublishOptions = {
150
150
  preferredCodec?: string | null;
151
+ preferredBitrate?: number;
151
152
  screenShareSettings?: ScreenShareSettings;
152
153
  };
153
154