@stream-io/video-client 0.3.20 → 0.3.22
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 +69 -49
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +69 -49
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +69 -49
- package/dist/index.es.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -3
- package/src/__tests__/server-side/call.test.ts +1 -1
- package/src/helpers/DynascaleManager.ts +37 -15
- package/src/helpers/__tests__/DynascaleManager.test.ts +51 -11
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.3.
|
|
1
|
+
export declare const version = "0.3.22";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22",
|
|
4
4
|
"packageManager": "yarn@3.2.4",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.es.js",
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"@rollup/plugin-replace": "^5.0.2",
|
|
49
49
|
"@rollup/plugin-typescript": "^11.1.2",
|
|
50
50
|
"@types/jsonwebtoken": "^9.0.1",
|
|
51
|
-
"@types/rimraf": "^3.0.2",
|
|
52
51
|
"@types/sdp-transform": "^2.4.6",
|
|
53
52
|
"@types/ua-parser-js": "^0.7.36",
|
|
54
53
|
"@types/ws": "^8.5.4",
|
|
@@ -56,7 +55,7 @@
|
|
|
56
55
|
"dotenv": "^16.3.1",
|
|
57
56
|
"happy-dom": "^11.0.2",
|
|
58
57
|
"prettier": "^2.8.4",
|
|
59
|
-
"rimraf": "^
|
|
58
|
+
"rimraf": "^5.0.1",
|
|
60
59
|
"rollup": "^3.28.1",
|
|
61
60
|
"typescript": "^4.9.5",
|
|
62
61
|
"vite": "^4.4.9",
|
|
@@ -120,9 +120,9 @@ describe('call API', () => {
|
|
|
120
120
|
response = await call.update({
|
|
121
121
|
settings_override: {
|
|
122
122
|
recording: {
|
|
123
|
-
mode: RecordSettingsRequestModeEnum.AVAILABLE,
|
|
124
123
|
audio_only: false,
|
|
125
124
|
quality: RecordSettingsRequestQualityEnum._1080P,
|
|
125
|
+
mode: RecordSettingsRequestModeEnum.AUTO_ON,
|
|
126
126
|
},
|
|
127
127
|
},
|
|
128
128
|
});
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { ViewportTracker } from './ViewportTracker';
|
|
23
23
|
import { getLogger } from '../logger';
|
|
24
24
|
import { getSdkInfo } from '../client-details';
|
|
25
|
+
import { isFirefox, isSafari } from './browsers';
|
|
25
26
|
|
|
26
27
|
const DEFAULT_VIEWPORT_VISIBILITY_STATE: Record<
|
|
27
28
|
VideoTrackType,
|
|
@@ -152,6 +153,14 @@ export class DynascaleManager {
|
|
|
152
153
|
debounceType: DebounceType,
|
|
153
154
|
dimension: VideoDimension | undefined,
|
|
154
155
|
) => {
|
|
156
|
+
if (dimension && (dimension.width === 0 || dimension.height === 0)) {
|
|
157
|
+
// ignore 0x0 dimensions. this can happen when the video element
|
|
158
|
+
// is not visible (e.g., has display: none).
|
|
159
|
+
// we treat this as "unsubscription" as we don't want to keep
|
|
160
|
+
// consuming bandwidth for a video that is not visible on the screen.
|
|
161
|
+
this.logger('debug', `Ignoring 0x0 dimension`, boundParticipant);
|
|
162
|
+
dimension = undefined;
|
|
163
|
+
}
|
|
155
164
|
this.call.updateSubscriptionsPartial(
|
|
156
165
|
trackType,
|
|
157
166
|
{ [sessionId]: { dimension } },
|
|
@@ -171,6 +180,12 @@ export class DynascaleManager {
|
|
|
171
180
|
shareReplay(1),
|
|
172
181
|
);
|
|
173
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Since the video elements are now being removed from the DOM (React SDK) upon
|
|
185
|
+
* visibility change, this subscription is not in use an stays here only for the
|
|
186
|
+
* plain JS integrations where integrators might choose not to remove the video
|
|
187
|
+
* elements from the DOM.
|
|
188
|
+
*/
|
|
174
189
|
// keep copy for resize observer handler
|
|
175
190
|
let viewportVisibilityState: VisibilityState | undefined;
|
|
176
191
|
const viewportVisibilityStateSubscription =
|
|
@@ -231,6 +246,8 @@ export class DynascaleManager {
|
|
|
231
246
|
});
|
|
232
247
|
resizeObserver?.observe(videoElement);
|
|
233
248
|
|
|
249
|
+
// element renders and gets bound - track subscription gets
|
|
250
|
+
// triggered first other ones get skipped on initial subscriptions
|
|
234
251
|
const publishedTracksSubscription = boundParticipant.isLocalParticipant
|
|
235
252
|
? null
|
|
236
253
|
: participant$
|
|
@@ -248,16 +265,24 @@ export class DynascaleManager {
|
|
|
248
265
|
.subscribe((isPublishing) => {
|
|
249
266
|
if (isPublishing) {
|
|
250
267
|
// the participant just started to publish a track
|
|
251
|
-
requestTrackWithDimensions(DebounceType.
|
|
268
|
+
requestTrackWithDimensions(DebounceType.FAST, {
|
|
252
269
|
width: videoElement.clientWidth,
|
|
253
270
|
height: videoElement.clientHeight,
|
|
254
271
|
});
|
|
255
272
|
} else {
|
|
256
273
|
// the participant just stopped publishing a track
|
|
257
|
-
requestTrackWithDimensions(DebounceType.
|
|
274
|
+
requestTrackWithDimensions(DebounceType.FAST, undefined);
|
|
258
275
|
}
|
|
259
276
|
});
|
|
260
277
|
|
|
278
|
+
videoElement.autoplay = true;
|
|
279
|
+
videoElement.playsInline = true;
|
|
280
|
+
|
|
281
|
+
// explicitly marking the element as muted will allow autoplay to work
|
|
282
|
+
// without prior user interaction:
|
|
283
|
+
// https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
|
|
284
|
+
videoElement.muted = true;
|
|
285
|
+
|
|
261
286
|
const streamSubscription = participant$
|
|
262
287
|
.pipe(
|
|
263
288
|
distinctUntilKeyChanged(
|
|
@@ -268,25 +293,22 @@ export class DynascaleManager {
|
|
|
268
293
|
const source =
|
|
269
294
|
trackType === 'videoTrack' ? p.videoStream : p.screenShareStream;
|
|
270
295
|
if (videoElement.srcObject === source) return;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
296
|
+
videoElement.srcObject = source ?? null;
|
|
297
|
+
if (isSafari() || isFirefox()) {
|
|
298
|
+
setTimeout(() => {
|
|
299
|
+
videoElement.srcObject = source ?? null;
|
|
274
300
|
videoElement.play().catch((e) => {
|
|
275
301
|
this.logger('warn', `Failed to play stream`, e);
|
|
276
302
|
});
|
|
277
|
-
|
|
278
|
-
|
|
303
|
+
// we add extra delay until we attempt to force-play
|
|
304
|
+
// the participant's media stream in Firefox and Safari,
|
|
305
|
+
// as they seem to have some timing issues
|
|
306
|
+
}, 25);
|
|
307
|
+
}
|
|
279
308
|
});
|
|
280
|
-
videoElement.playsInline = true;
|
|
281
|
-
videoElement.autoplay = true;
|
|
282
|
-
|
|
283
|
-
// explicitly marking the element as muted will allow autoplay to work
|
|
284
|
-
// without prior user interaction:
|
|
285
|
-
// https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
|
|
286
|
-
videoElement.muted = true;
|
|
287
309
|
|
|
288
310
|
return () => {
|
|
289
|
-
requestTrackWithDimensions(DebounceType.
|
|
311
|
+
requestTrackWithDimensions(DebounceType.FAST, undefined);
|
|
290
312
|
viewportVisibilityStateSubscription?.unsubscribe();
|
|
291
313
|
publishedTracksSubscription?.unsubscribe();
|
|
292
314
|
streamSubscription.unsubscribe();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import '../../rtc/__tests__/mocks/webrtc.mocks';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
|
|
8
8
|
import { DynascaleManager } from '../DynascaleManager';
|
|
9
9
|
import { Call } from '../../Call';
|
|
10
10
|
import { StreamClient } from '../../coordinator/connection/client';
|
|
@@ -195,7 +195,7 @@ describe('DynascaleManager', () => {
|
|
|
195
195
|
expect(updateSubscription).toHaveBeenCalledWith(
|
|
196
196
|
'videoTrack',
|
|
197
197
|
{ 'session-id': { dimension: undefined } },
|
|
198
|
-
DebounceType.
|
|
198
|
+
DebounceType.FAST,
|
|
199
199
|
);
|
|
200
200
|
|
|
201
201
|
call.state.updateParticipant('session-id', {
|
|
@@ -212,7 +212,7 @@ describe('DynascaleManager', () => {
|
|
|
212
212
|
},
|
|
213
213
|
},
|
|
214
214
|
},
|
|
215
|
-
DebounceType.
|
|
215
|
+
DebounceType.FAST,
|
|
216
216
|
);
|
|
217
217
|
|
|
218
218
|
call.state.updateParticipant('session-id', {
|
|
@@ -222,7 +222,7 @@ describe('DynascaleManager', () => {
|
|
|
222
222
|
expect(updateSubscription).toHaveBeenCalledWith(
|
|
223
223
|
'videoTrack',
|
|
224
224
|
{ 'session-id': { dimension: undefined } },
|
|
225
|
-
DebounceType.
|
|
225
|
+
DebounceType.FAST,
|
|
226
226
|
);
|
|
227
227
|
|
|
228
228
|
cleanup?.();
|
|
@@ -230,7 +230,7 @@ describe('DynascaleManager', () => {
|
|
|
230
230
|
expect(updateSubscription).toHaveBeenCalledWith(
|
|
231
231
|
'videoTrack',
|
|
232
232
|
{ 'session-id': { dimension: undefined } },
|
|
233
|
-
DebounceType.
|
|
233
|
+
DebounceType.FAST,
|
|
234
234
|
);
|
|
235
235
|
});
|
|
236
236
|
|
|
@@ -271,7 +271,7 @@ describe('DynascaleManager', () => {
|
|
|
271
271
|
},
|
|
272
272
|
},
|
|
273
273
|
},
|
|
274
|
-
DebounceType.
|
|
274
|
+
DebounceType.FAST,
|
|
275
275
|
);
|
|
276
276
|
expect(play).toHaveBeenCalled();
|
|
277
277
|
expect(videoElement.srcObject).toBe(mediaStream);
|
|
@@ -281,7 +281,7 @@ describe('DynascaleManager', () => {
|
|
|
281
281
|
expect(updateSubscription).toHaveBeenCalledWith(
|
|
282
282
|
'videoTrack',
|
|
283
283
|
{ 'session-id': { dimension: undefined } },
|
|
284
|
-
DebounceType.
|
|
284
|
+
DebounceType.FAST,
|
|
285
285
|
);
|
|
286
286
|
});
|
|
287
287
|
|
|
@@ -308,7 +308,7 @@ describe('DynascaleManager', () => {
|
|
|
308
308
|
expect(updateSubscription).toHaveBeenCalledWith(
|
|
309
309
|
'videoTrack',
|
|
310
310
|
{ 'session-id': { dimension: undefined } },
|
|
311
|
-
DebounceType.
|
|
311
|
+
DebounceType.FAST,
|
|
312
312
|
);
|
|
313
313
|
|
|
314
314
|
call.state.updateParticipant('session-id', {
|
|
@@ -369,7 +369,7 @@ describe('DynascaleManager', () => {
|
|
|
369
369
|
expect(updateSubscription).toHaveBeenCalledWith(
|
|
370
370
|
'videoTrack',
|
|
371
371
|
{ 'session-id': { dimension: undefined } },
|
|
372
|
-
DebounceType.
|
|
372
|
+
DebounceType.FAST,
|
|
373
373
|
);
|
|
374
374
|
});
|
|
375
375
|
|
|
@@ -417,7 +417,7 @@ describe('DynascaleManager', () => {
|
|
|
417
417
|
},
|
|
418
418
|
},
|
|
419
419
|
},
|
|
420
|
-
DebounceType.
|
|
420
|
+
DebounceType.FAST,
|
|
421
421
|
);
|
|
422
422
|
|
|
423
423
|
// @ts-ignore simulate resize
|
|
@@ -439,7 +439,47 @@ describe('DynascaleManager', () => {
|
|
|
439
439
|
expect(updateSubscription).toHaveBeenCalledWith(
|
|
440
440
|
'videoTrack',
|
|
441
441
|
{ 'session-id': { dimension: undefined } },
|
|
442
|
-
DebounceType.
|
|
442
|
+
DebounceType.FAST,
|
|
443
|
+
);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('video: should unsubscribe when element dimensions are zero', () => {
|
|
447
|
+
// @ts-ignore
|
|
448
|
+
call.state.updateOrAddParticipant('session-id', {
|
|
449
|
+
userId: 'user-id',
|
|
450
|
+
sessionId: 'session-id',
|
|
451
|
+
publishedTracks: [TrackType.VIDEO],
|
|
452
|
+
viewportVisibilityState: {
|
|
453
|
+
videoTrack: VisibilityState.VISIBLE,
|
|
454
|
+
screenShareTrack: VisibilityState.UNKNOWN,
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
let updateSubscription = vi.spyOn(call, 'updateSubscriptionsPartial');
|
|
459
|
+
|
|
460
|
+
// @ts-ignore simulate resize
|
|
461
|
+
videoElement.clientHeight = 0;
|
|
462
|
+
// @ts-ignore simulate resize
|
|
463
|
+
videoElement.clientWidth = 0;
|
|
464
|
+
|
|
465
|
+
const cleanup = dynascaleManager.bindVideoElement(
|
|
466
|
+
videoElement,
|
|
467
|
+
'session-id',
|
|
468
|
+
'videoTrack',
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
expect(updateSubscription).toHaveBeenCalledWith(
|
|
472
|
+
'videoTrack',
|
|
473
|
+
{ 'session-id': { dimension: undefined } },
|
|
474
|
+
DebounceType.FAST,
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
cleanup?.();
|
|
478
|
+
|
|
479
|
+
expect(updateSubscription).toHaveBeenLastCalledWith(
|
|
480
|
+
'videoTrack',
|
|
481
|
+
{ 'session-id': { dimension: undefined } },
|
|
482
|
+
DebounceType.FAST,
|
|
443
483
|
);
|
|
444
484
|
});
|
|
445
485
|
});
|