@stream-io/video-client 0.3.24 → 0.3.26

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,54 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import * as TestData from './participant-data';
3
+ import { VisibilityState } from '../../types';
4
+ import { paginatedLayoutSortPreset } from '../presets';
5
+ import { TrackType } from '../../gen/video/sfu/models/models';
6
+
7
+ describe('presets', () => {
8
+ it('paginatedLayoutSortPreset', () => {
9
+ const ps = TestData.participants().map((p) => ({
10
+ ...p,
11
+ viewportVisibilityState: {
12
+ videoTrack: VisibilityState.UNKNOWN,
13
+ screenShareTrack: VisibilityState.UNKNOWN,
14
+ },
15
+ }));
16
+
17
+ expect(ps.sort(paginatedLayoutSortPreset).map((p) => p.name)).toEqual([
18
+ 'F',
19
+ 'B',
20
+ 'E',
21
+ 'D',
22
+ 'A',
23
+ 'C',
24
+ ]);
25
+
26
+ // server-pin C
27
+ ps.at(-1)!.pin = {
28
+ isLocalPin: false,
29
+ pinnedAt: Date.now(),
30
+ };
31
+
32
+ expect(ps.sort(paginatedLayoutSortPreset).map((p) => p.name)).toEqual([
33
+ 'C',
34
+ 'F',
35
+ 'B',
36
+ 'E',
37
+ 'D',
38
+ 'A',
39
+ ]);
40
+
41
+ ps.at(-3)!.publishedTracks = [TrackType.AUDIO]; // E
42
+ ps.at(-2)!.isDominantSpeaker = false; // D
43
+ ps.at(-1)!.isDominantSpeaker = true; // A
44
+
45
+ expect(ps.sort(paginatedLayoutSortPreset).map((p) => p.name)).toEqual([
46
+ 'C',
47
+ 'F',
48
+ 'B',
49
+ 'A',
50
+ 'E',
51
+ 'D',
52
+ ]);
53
+ });
54
+ });
@@ -20,6 +20,19 @@ const ifInvisibleBy = conditional(
20
20
  b.viewportVisibilityState?.videoTrack === VisibilityState.INVISIBLE,
21
21
  );
22
22
 
23
+ /**
24
+ * A comparator that applies the decorated comparator when a participant is
25
+ * either invisible or its visibility state isn't known.
26
+ * For visible participants, it ensures stable sorting.
27
+ */
28
+ const ifInvisibleOrUnknownBy = conditional(
29
+ (a: StreamVideoParticipant, b: StreamVideoParticipant) =>
30
+ a.viewportVisibilityState?.videoTrack === VisibilityState.INVISIBLE ||
31
+ a.viewportVisibilityState?.videoTrack === VisibilityState.UNKNOWN ||
32
+ b.viewportVisibilityState?.videoTrack === VisibilityState.INVISIBLE ||
33
+ b.viewportVisibilityState?.videoTrack === VisibilityState.UNKNOWN,
34
+ );
35
+
23
36
  /**
24
37
  * The default sorting preset.
25
38
  */
@@ -48,6 +61,21 @@ export const speakerLayoutSortPreset = combineComparators(
48
61
  // ifInvisibleBy(name),
49
62
  );
50
63
 
64
+ /**
65
+ * The sorting preset for layouts that don't render all participants but
66
+ * instead, render them in pages.
67
+ */
68
+ export const paginatedLayoutSortPreset = combineComparators(
69
+ pinned,
70
+ screenSharing,
71
+ dominantSpeaker,
72
+ ifInvisibleOrUnknownBy(speaking),
73
+ ifInvisibleOrUnknownBy(reactionType('raised-hand')),
74
+ ifInvisibleOrUnknownBy(publishingVideo),
75
+ ifInvisibleOrUnknownBy(publishingAudio),
76
+ // ifInvisibleOrUnknownBy(name),
77
+ );
78
+
51
79
  /**
52
80
  * The sorting preset for livestreams and audio rooms.
53
81
  */
@@ -12,7 +12,7 @@ import {
12
12
  import { CallStatsReport } from '../stats/types';
13
13
  import {
14
14
  BlockedUserEvent,
15
- CallBroadcastingStartedEvent,
15
+ CallHLSBroadcastingStartedEvent,
16
16
  CallIngressResponse,
17
17
  CallMemberAddedEvent,
18
18
  CallMemberRemovedEvent,
@@ -27,6 +27,7 @@ import {
27
27
  EgressResponse,
28
28
  MemberResponse,
29
29
  OwnCapability,
30
+ ThumbnailResponse,
30
31
  UnblockedUserEvent,
31
32
  UpdatedCallPermissionsEvent,
32
33
  UserResponse,
@@ -125,6 +126,9 @@ export class CallState {
125
126
  private endedBySubject = new BehaviorSubject<UserResponse | undefined>(
126
127
  undefined,
127
128
  );
129
+ private thumbnailsSubject = new BehaviorSubject<
130
+ ThumbnailResponse | undefined
131
+ >(undefined);
128
132
  private membersSubject = new BehaviorSubject<MemberResponse[]>([]);
129
133
  private ownCapabilitiesSubject = new BehaviorSubject<OwnCapability[]>([]);
130
134
  private callingStateSubject = new BehaviorSubject<CallingState>(
@@ -298,6 +302,11 @@ export class CallState {
298
302
  */
299
303
  endedBy$: Observable<UserResponse | undefined>;
300
304
 
305
+ /**
306
+ * Will provide the thumbnails of this call.
307
+ */
308
+ thumbnails$: Observable<ThumbnailResponse | undefined>;
309
+
301
310
  readonly logger: Logger;
302
311
 
303
312
  /**
@@ -381,6 +390,7 @@ export class CallState {
381
390
  this.settings$ = this.settingsSubject.asObservable();
382
391
  this.transcribing$ = this.transcribingSubject.asObservable();
383
392
  this.endedBy$ = this.endedBySubject.asObservable();
393
+ this.thumbnails$ = this.thumbnailsSubject.asObservable();
384
394
 
385
395
  this.eventHandlers = {
386
396
  // these events are not updating the call state:
@@ -396,7 +406,10 @@ export class CallState {
396
406
  // events that update call state:
397
407
  'call.accepted': (e) => this.updateFromCallResponse(e.call),
398
408
  'call.created': (e) => this.updateFromCallResponse(e.call),
399
- 'call.notification': (e) => this.updateFromCallResponse(e.call),
409
+ 'call.notification': (e) => {
410
+ this.updateFromCallResponse(e.call);
411
+ this.setMembers(e.members);
412
+ },
400
413
  'call.rejected': (e) => this.updateFromCallResponse(e.call),
401
414
  'call.ring': (e) => this.updateFromCallResponse(e.call),
402
415
  'call.live_started': (e) => this.updateFromCallResponse(e.call),
@@ -411,8 +424,8 @@ export class CallState {
411
424
  this.setCurrentValue(this.recordingSubject, true),
412
425
  'call.recording_stopped': () =>
413
426
  this.setCurrentValue(this.recordingSubject, false),
414
- 'call.broadcasting_started': this.updateFromBroadcastStarted,
415
- 'call.broadcasting_stopped': this.updateFromBroadcastStopped,
427
+ 'call.hls_broadcasting_started': this.updateFromHLSBroadcastStarted,
428
+ 'call.hls_broadcasting_stopped': this.updateFromHLSBroadcastStopped,
416
429
  'call.session_participant_joined':
417
430
  this.updateFromSessionParticipantJoined,
418
431
  'call.session_participant_left': this.updateFromSessionParticipantLeft,
@@ -740,6 +753,13 @@ export class CallState {
740
753
  return this.getCurrentValue(this.endedBy$);
741
754
  }
742
755
 
756
+ /**
757
+ * Will provide the thumbnails of this call, if enabled in the call settings.
758
+ */
759
+ get thumbnails() {
760
+ return this.getCurrentValue(this.thumbnails$);
761
+ }
762
+
743
763
  /**
744
764
  * Will try to find the participant with the given sessionId in the current call.
745
765
  *
@@ -943,6 +963,7 @@ export class CallState {
943
963
  this.setCurrentValue(this.sessionSubject, call.session);
944
964
  this.setCurrentValue(this.settingsSubject, call.settings);
945
965
  this.setCurrentValue(this.transcribingSubject, call.transcribing);
966
+ this.setCurrentValue(this.thumbnailsSubject, call.thumbnails);
946
967
  };
947
968
 
948
969
  private updateFromMemberRemoved = (event: CallMemberRemovedEvent) => {
@@ -958,15 +979,15 @@ export class CallState {
958
979
  ]);
959
980
  };
960
981
 
961
- private updateFromBroadcastStopped = () => {
982
+ private updateFromHLSBroadcastStopped = () => {
962
983
  this.setCurrentValue(this.egressSubject, (egress) => ({
963
984
  ...egress!,
964
985
  broadcasting: false,
965
986
  }));
966
987
  };
967
988
 
968
- private updateFromBroadcastStarted = (
969
- event: CallBroadcastingStartedEvent,
989
+ private updateFromHLSBroadcastStarted = (
990
+ event: CallHLSBroadcastingStartedEvent,
970
991
  ) => {
971
992
  this.setCurrentValue(this.egressSubject, (egress) => ({
972
993
  ...egress!,
@@ -24,6 +24,8 @@ import {
24
24
  import * as TestData from '../../sorting/__tests__/participant-data';
25
25
 
26
26
  describe('CallState', () => {
27
+ // TODO OL add API verification test -> observable$ should have a getter!
28
+
27
29
  describe('sorting', () => {
28
30
  it('should emit sorted participants', () => {
29
31
  const state = new CallState();
@@ -471,7 +473,7 @@ describe('CallState', () => {
471
473
  expect(state.recording).toBe(false);
472
474
  });
473
475
 
474
- it('handles call.broadcasting_started events', () => {
476
+ it('handles call.hls_broadcasting_started events', () => {
475
477
  const state = new CallState();
476
478
  state.updateFromCallResponse({
477
479
  // @ts-ignore
@@ -484,7 +486,7 @@ describe('CallState', () => {
484
486
  });
485
487
  // @ts-ignore
486
488
  state.updateFromEvent({
487
- type: 'call.broadcasting_started',
489
+ type: 'call.hls_broadcasting_started',
488
490
  hls_playlist_url: 'https://example.com/playlist.m3u8',
489
491
  });
490
492
  expect(state.egress?.broadcasting).toBe(true);
@@ -493,13 +495,13 @@ describe('CallState', () => {
493
495
  );
494
496
  });
495
497
 
496
- it('handles call.broadcasting_stopped events', () => {
498
+ it('handles call.hls_broadcasting_stopped events', () => {
497
499
  const state = new CallState();
498
500
  // @ts-ignore
499
501
  state.updateFromCallResponse({});
500
502
  // @ts-ignore
501
503
  state.updateFromEvent({
502
- type: 'call.broadcasting_stopped',
504
+ type: 'call.hls_broadcasting_stopped',
503
505
  });
504
506
  expect(state.egress?.broadcasting).toBe(false);
505
507
  });