@stream-io/video-client 2.0.0-beta.0 → 2.0.0-beta.1
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 +18 -0
- package/dist/index.browser.es.js +1126 -791
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +1126 -791
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +1126 -791
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +37 -11
- package/dist/src/StreamVideoClient.d.ts +15 -21
- package/dist/src/coordinator/connection/types.d.ts +16 -0
- package/dist/src/events/call.d.ts +0 -11
- package/dist/src/gen/coordinator/index.d.ts +73 -0
- package/dist/src/reporting/ClientEventReporter.d.ts +11 -1
- package/dist/src/ringing/RingStatePoller.d.ts +46 -0
- package/dist/src/ringing/RingTimeout.d.ts +27 -0
- package/dist/src/ringing/index.d.ts +4 -0
- package/dist/src/ringing/reconcileRingState.d.ts +18 -0
- package/dist/src/ringing/resolveOwnRingOutcome.d.ts +32 -0
- package/dist/src/rtc/Publisher.d.ts +18 -0
- package/dist/src/rtc/TransceiverCache.d.ts +10 -0
- package/dist/src/rtc/e2ee/E2EEManager.d.ts +10 -1
- package/dist/src/stats/utils.d.ts +6 -0
- package/dist/src/store/CallState.d.ts +118 -267
- package/dist/src/store/ClientState.d.ts +62 -0
- package/dist/src/store/index.d.ts +1 -1
- package/dist/src/store/subjects.d.ts +15 -0
- package/dist/src/store/types.d.ts +22 -0
- package/dist/src/types.d.ts +33 -4
- package/index.ts +1 -0
- package/package.json +2 -2
- package/src/Call.ts +285 -125
- package/src/StreamSfuClient.ts +6 -2
- package/src/StreamVideoClient.ts +40 -47
- package/src/__tests__/Call.autodrop.test.ts +118 -66
- package/src/__tests__/Call.getRingState.test.ts +87 -0
- package/src/__tests__/Call.lifecycle.test.ts +499 -6
- package/src/__tests__/Call.publishing.test.ts +2 -2
- package/src/__tests__/Call.ringSettled.test.ts +71 -0
- package/src/__tests__/Call.ringingLifecycle.test.ts +242 -0
- package/src/__tests__/StreamSfuClient.test.ts +48 -0
- package/src/__tests__/StreamVideoClient.disconnect.test.ts +62 -0
- package/src/coordinator/connection/types.ts +19 -0
- package/src/devices/__tests__/CameraManager.test.ts +2 -2
- package/src/devices/__tests__/DeviceManager.test.ts +2 -2
- package/src/devices/__tests__/DeviceManagerFilters.test.ts +2 -2
- package/src/devices/__tests__/MicrophoneManager.test.ts +2 -2
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +2 -2
- package/src/devices/__tests__/ScreenShareManager.test.ts +3 -3
- package/src/devices/__tests__/SpeakerManager.test.ts +4 -4
- package/src/events/__tests__/call.test.ts +5 -220
- package/src/events/call.ts +1 -73
- package/src/events/callEventHandlers.ts +12 -23
- package/src/gen/coordinator/index.ts +67 -0
- package/src/helpers/__tests__/AudioBindingsWatchdog.test.ts +2 -2
- package/src/helpers/__tests__/DynascaleManager.test.ts +2 -2
- package/src/helpers/__tests__/ViewportTracker.test.ts +2 -2
- package/src/reporting/ClientEventReporter.ts +30 -4
- package/src/reporting/__tests__/ClientEventReporter.test.ts +184 -7
- package/src/ringing/RingStatePoller.ts +177 -0
- package/src/ringing/RingTimeout.ts +90 -0
- package/src/ringing/__tests__/RingStatePoller.test.ts +462 -0
- package/src/ringing/__tests__/RingTimeout.test.ts +199 -0
- package/src/ringing/__tests__/reconcileRingState.test.ts +330 -0
- package/src/ringing/__tests__/resolveOwnRingOutcome.test.ts +103 -0
- package/src/ringing/index.ts +4 -0
- package/src/ringing/reconcileRingState.ts +108 -0
- package/src/ringing/resolveOwnRingOutcome.ts +58 -0
- package/src/rtc/Publisher.ts +66 -12
- package/src/rtc/TransceiverCache.ts +17 -0
- package/src/rtc/__tests__/Call.reconnect.test.ts +5 -5
- package/src/rtc/__tests__/Publisher.test.ts +209 -0
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -0
- package/src/rtc/e2ee/E2EEManager.ts +10 -1
- package/src/rtc/e2ee/EncryptionManager.ts +1 -1
- package/src/rtc/e2ee/SPEC.md +1 -1
- package/src/stats/__tests__/utils.test.ts +33 -0
- package/src/stats/utils.ts +15 -0
- package/src/store/CallState.ts +305 -550
- package/src/store/ClientState.ts +101 -0
- package/src/store/__tests__/CallState.test.ts +60 -1
- package/src/store/__tests__/ClientState.test.ts +35 -0
- package/src/store/__tests__/rxUtils.test.ts +67 -1
- package/src/store/index.ts +1 -1
- package/src/store/rxUtils.ts +8 -4
- package/src/store/subjects.ts +30 -0
- package/src/store/types.ts +45 -0
- package/src/types.ts +40 -4
- package/dist/src/store/stateStore.d.ts +0 -84
- package/src/store/stateStore.ts +0 -156
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { getCurrentValue, type Patch, setCurrentValue } from './rxUtils';
|
|
2
|
+
import { subject } from './subjects';
|
|
3
|
+
import type { Call } from '../Call';
|
|
4
|
+
import type { OwnUserResponse } from '../gen/coordinator';
|
|
5
|
+
import { videoLoggerSystem } from '../logger';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Holds the state of the client, and the calls it created or tracks.
|
|
9
|
+
* You can subscribe to the exposed observables to get notified about changes.
|
|
10
|
+
*/
|
|
11
|
+
export class ClientState {
|
|
12
|
+
readonly logger = videoLoggerSystem.getLogger('ClientState');
|
|
13
|
+
|
|
14
|
+
private connectedUserSubject = subject<OwnUserResponse>();
|
|
15
|
+
private callsSubject = subject<Call[]>([]);
|
|
16
|
+
|
|
17
|
+
/** Data describing a user successfully connected over WS to the coordinator. */
|
|
18
|
+
connectedUser$ = this.connectedUserSubject.asObservable();
|
|
19
|
+
|
|
20
|
+
/** A list of {@link Call} objects created/tracked by this client. */
|
|
21
|
+
calls$ = this.callsSubject.asObservable();
|
|
22
|
+
|
|
23
|
+
/** The currently connected user. */
|
|
24
|
+
get connectedUser() {
|
|
25
|
+
return getCurrentValue(this.connectedUserSubject);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A list of {@link Call} objects created/tracked by this client. */
|
|
29
|
+
get calls() {
|
|
30
|
+
return getCurrentValue(this.callsSubject);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sets the currently connected user.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
* @param user the user to set as connected.
|
|
38
|
+
*/
|
|
39
|
+
setConnectedUser = (user: Patch<OwnUserResponse | undefined>) => {
|
|
40
|
+
return setCurrentValue(this.connectedUserSubject, user);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the list of {@link Call} objects created/tracked by this client.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
* @param calls the calls to set.
|
|
48
|
+
*/
|
|
49
|
+
setCalls = (calls: Patch<Call[]>) => {
|
|
50
|
+
return setCurrentValue(this.callsSubject, calls);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Adds a {@link Call} object to the list of calls tracked by this client.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
* @param call the call to add.
|
|
58
|
+
*/
|
|
59
|
+
registerCall = (call: Call) => {
|
|
60
|
+
if (!this.calls.find((c) => c.cid === call.cid)) {
|
|
61
|
+
this.setCalls((calls) => [...calls, call]);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Registers a {@link Call} object if it doesn't exist, otherwise updates it.
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
* @param call the call to register or update.
|
|
70
|
+
*/
|
|
71
|
+
registerOrUpdateCall = (call: Call) => {
|
|
72
|
+
if (this.calls.find((c) => c.cid === call.cid)) {
|
|
73
|
+
return this.setCalls((calls) =>
|
|
74
|
+
calls.map((c) => (c.cid === call.cid ? call : c)),
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
return this.registerCall(call);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Removes a {@link Call} object from the list of calls tracked by this client.
|
|
83
|
+
*
|
|
84
|
+
* @internal
|
|
85
|
+
* @param call the call to remove.
|
|
86
|
+
*/
|
|
87
|
+
unregisterCall = (call: Call) => {
|
|
88
|
+
this.logger.trace(`Unregistering call: ${call.cid}`);
|
|
89
|
+
return this.setCalls((calls) => calls.filter((c) => c !== call));
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Finds a {@link Call} object in the list of calls tracked by this client.
|
|
94
|
+
*
|
|
95
|
+
* @param type the type of call to find.
|
|
96
|
+
* @param id the id of the call to find.
|
|
97
|
+
*/
|
|
98
|
+
findCall = (type: string, id: string) => {
|
|
99
|
+
return this.calls.find((c) => c.type === type && c.id === id);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
CallAcceptedEvent,
|
|
25
25
|
CallEndedEvent,
|
|
26
26
|
CallUpdatedEvent,
|
|
27
|
+
type GetCallRingStateResponse,
|
|
27
28
|
MemberResponse,
|
|
28
29
|
OwnCapability,
|
|
29
30
|
} from '../../gen/coordinator';
|
|
@@ -47,7 +48,7 @@ describe('CallState', () => {
|
|
|
47
48
|
getter,
|
|
48
49
|
`A getter for ${observable} is missing. Please define it like this:
|
|
49
50
|
get ${key}() {
|
|
50
|
-
return
|
|
51
|
+
return getCurrentValue(this.${observable});
|
|
51
52
|
}
|
|
52
53
|
`,
|
|
53
54
|
).toBeDefined();
|
|
@@ -1474,4 +1475,62 @@ describe('CallState', () => {
|
|
|
1474
1475
|
expect(state['closedCaptionsTasks'].size).toBe(0);
|
|
1475
1476
|
});
|
|
1476
1477
|
});
|
|
1478
|
+
|
|
1479
|
+
describe('updateFromRingState', () => {
|
|
1480
|
+
const ringState = fromPartial<GetCallRingStateResponse>({
|
|
1481
|
+
session_id: 'session-1',
|
|
1482
|
+
accepted_by: { bob: '2026-08-24T10:00:04Z' },
|
|
1483
|
+
rejected_by: { carol: '2026-08-24T10:00:09Z' },
|
|
1484
|
+
missed_by: { dave: '2026-08-24T10:00:35Z' },
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
const withSession = (id: string) => {
|
|
1488
|
+
const state = new CallState();
|
|
1489
|
+
state['sessionSubject'].next(
|
|
1490
|
+
fromPartial({
|
|
1491
|
+
id,
|
|
1492
|
+
accepted_by: {},
|
|
1493
|
+
rejected_by: {},
|
|
1494
|
+
missed_by: {},
|
|
1495
|
+
participants: [fromPartial({ user_session_id: 'p1' })],
|
|
1496
|
+
}),
|
|
1497
|
+
);
|
|
1498
|
+
return state;
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
it('merges the ring maps into the current session', () => {
|
|
1502
|
+
const state = withSession('session-1');
|
|
1503
|
+
state.updateFromRingState(ringState);
|
|
1504
|
+
|
|
1505
|
+
expect(state.session?.accepted_by).toEqual(ringState.accepted_by);
|
|
1506
|
+
expect(state.session?.rejected_by).toEqual(ringState.rejected_by);
|
|
1507
|
+
expect(state.session?.missed_by).toEqual(ringState.missed_by);
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
it('leaves the session roster untouched', () => {
|
|
1511
|
+
const state = withSession('session-1');
|
|
1512
|
+
state.updateFromRingState(ringState);
|
|
1513
|
+
|
|
1514
|
+
expect(state.session?.participants).toHaveLength(1);
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
it('ignores a ring state that belongs to another session', () => {
|
|
1518
|
+
const state = withSession('session-2');
|
|
1519
|
+
state.updateFromRingState(ringState);
|
|
1520
|
+
|
|
1521
|
+
expect(state.session?.accepted_by).toEqual({});
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
it('applies the end timestamps', () => {
|
|
1525
|
+
const state = withSession('session-1');
|
|
1526
|
+
state.updateFromRingState({
|
|
1527
|
+
...ringState,
|
|
1528
|
+
session_ended_at: '2026-08-24T10:01:00Z',
|
|
1529
|
+
call_ended_at: '2026-08-24T10:01:00Z',
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
expect(state.session?.ended_at).toBe('2026-08-24T10:01:00Z');
|
|
1533
|
+
expect(state.endedAt).toEqual(new Date('2026-08-24T10:01:00Z'));
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1477
1536
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ClientState } from '../ClientState';
|
|
3
|
+
|
|
4
|
+
describe('ClientState', () => {
|
|
5
|
+
describe('API assertions', () => {
|
|
6
|
+
it('every exposed observable$ should have a getter', () => {
|
|
7
|
+
const state = new ClientState();
|
|
8
|
+
const observables = Object.keys(
|
|
9
|
+
Object.getOwnPropertyDescriptors(state),
|
|
10
|
+
).filter((key) => key.endsWith('$'));
|
|
11
|
+
|
|
12
|
+
// the observables must stay own enumerable properties: consumers walk
|
|
13
|
+
// `Object.entries(client.state)` to collect the current state.
|
|
14
|
+
expect(observables).toContain('connectedUser$');
|
|
15
|
+
expect(observables).toContain('calls$');
|
|
16
|
+
|
|
17
|
+
// @ts-expect-error - __proto__
|
|
18
|
+
const getters = Object.getOwnPropertyDescriptors(state.__proto__);
|
|
19
|
+
|
|
20
|
+
for (const observable of observables) {
|
|
21
|
+
const key = observable.slice(0, -1); // remove $
|
|
22
|
+
const getter = getters[key];
|
|
23
|
+
expect(
|
|
24
|
+
getter,
|
|
25
|
+
`A getter for ${observable} is missing. Please define it like this:
|
|
26
|
+
get ${key}() {
|
|
27
|
+
return getCurrentValue(this.${key}Subject);
|
|
28
|
+
}
|
|
29
|
+
`,
|
|
30
|
+
).toBeDefined();
|
|
31
|
+
expect(typeof getter.get).toEqual('function');
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AsyncSubject,
|
|
4
|
+
BehaviorSubject,
|
|
5
|
+
EMPTY,
|
|
6
|
+
of,
|
|
7
|
+
ReplaySubject,
|
|
8
|
+
Subject,
|
|
9
|
+
throwError,
|
|
10
|
+
} from 'rxjs';
|
|
3
11
|
import { promiseWithResolvers } from '../../helpers/promise';
|
|
4
12
|
import {
|
|
5
13
|
createSafeAsyncSubscription,
|
|
@@ -26,6 +34,64 @@ describe('getCurrentValue', () => {
|
|
|
26
34
|
const err = new Error('observable failed');
|
|
27
35
|
expect(() => getCurrentValue(throwError(() => err))).toThrow(err);
|
|
28
36
|
});
|
|
37
|
+
|
|
38
|
+
it('rethrows the error of an errored BehaviorSubject', () => {
|
|
39
|
+
const err = new Error('subject failed');
|
|
40
|
+
const subject = new BehaviorSubject(7);
|
|
41
|
+
subject.error(err);
|
|
42
|
+
expect(() => getCurrentValue(subject)).toThrow(err);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('throws when the BehaviorSubject has been unsubscribed', () => {
|
|
46
|
+
const subject = new BehaviorSubject(7);
|
|
47
|
+
subject.unsubscribe();
|
|
48
|
+
expect(() => getCurrentValue(subject)).toThrow(/unsubscribed/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('still reads the value of a completed BehaviorSubject', () => {
|
|
52
|
+
// A completed BehaviorSubject keeps holding its value, and we read that
|
|
53
|
+
// value directly. Subscribing instead would yield nothing, because RxJS
|
|
54
|
+
// doesn't replay to subscribers of a stopped subject.
|
|
55
|
+
const subject = new BehaviorSubject(7);
|
|
56
|
+
subject.complete();
|
|
57
|
+
expect(getCurrentValue(subject)).toBe(7);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns undefined for a Subject that has not emitted', () => {
|
|
61
|
+
expect(getCurrentValue(new Subject<number>())).toBeUndefined();
|
|
62
|
+
expect(getCurrentValue(EMPTY)).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns the last buffered value of a ReplaySubject', () => {
|
|
66
|
+
const subject = new ReplaySubject<string>(1);
|
|
67
|
+
subject.next('a');
|
|
68
|
+
subject.next('b');
|
|
69
|
+
expect(getCurrentValue(subject)).toBe('b');
|
|
70
|
+
|
|
71
|
+
const buffered = new ReplaySubject<string>(2);
|
|
72
|
+
buffered.next('a');
|
|
73
|
+
buffered.next('b');
|
|
74
|
+
expect(getCurrentValue(buffered)).toBe('b');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('still reads a completed ReplaySubject, which does replay', () => {
|
|
78
|
+
const subject = new ReplaySubject<string>(1);
|
|
79
|
+
subject.next('z');
|
|
80
|
+
subject.complete();
|
|
81
|
+
expect(getCurrentValue(subject)).toBe('z');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('reads the value an AsyncSubject emitted on completion', () => {
|
|
85
|
+
const pending = new AsyncSubject<string>();
|
|
86
|
+
pending.next('q');
|
|
87
|
+
expect(getCurrentValue(pending)).toBeUndefined();
|
|
88
|
+
pending.complete();
|
|
89
|
+
expect(getCurrentValue(pending)).toBe('q');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('returns the last synchronous emission, not the first', () => {
|
|
93
|
+
expect(getCurrentValue(of(1, 2, 3))).toBe(3);
|
|
94
|
+
});
|
|
29
95
|
});
|
|
30
96
|
|
|
31
97
|
describe('setCurrentValue', () => {
|
package/src/store/index.ts
CHANGED
package/src/store/rxUtils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorSubject,
|
|
1
|
+
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
|
2
2
|
import { withoutConcurrency } from '../helpers/concurrency';
|
|
3
3
|
import { videoLoggerSystem } from '../logger';
|
|
4
4
|
|
|
@@ -39,12 +39,16 @@ const isFunctionPatch = <T>(update: Patch<T>): update is FunctionPatch<T> =>
|
|
|
39
39
|
*
|
|
40
40
|
* @param observable$ the observable to get the value from.
|
|
41
41
|
*/
|
|
42
|
-
export const getCurrentValue = <T>(observable$: Observable<T>) => {
|
|
42
|
+
export const getCurrentValue = <T>(observable$: Observable<T>): T => {
|
|
43
|
+
if (observable$ instanceof BehaviorSubject) {
|
|
44
|
+
return (observable$ as BehaviorSubject<T>).getValue();
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
let value!: T;
|
|
44
48
|
let err: Error | undefined = undefined;
|
|
45
|
-
|
|
49
|
+
observable$
|
|
46
50
|
.subscribe({
|
|
47
|
-
next: (
|
|
51
|
+
next: (v) => {
|
|
48
52
|
value = v;
|
|
49
53
|
},
|
|
50
54
|
error: (e) => {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BehaviorSubject,
|
|
3
|
+
distinctUntilChanged,
|
|
4
|
+
Observable,
|
|
5
|
+
shareReplay,
|
|
6
|
+
} from 'rxjs';
|
|
7
|
+
|
|
8
|
+
/** Creates a BehaviorSubject seeded with the given value. */
|
|
9
|
+
export function subject<T>(initialValue: T): BehaviorSubject<T>;
|
|
10
|
+
/** Creates a BehaviorSubject which starts out holding `undefined`. */
|
|
11
|
+
export function subject<T>(): BehaviorSubject<T | undefined>;
|
|
12
|
+
export function subject<T>(initialValue?: T) {
|
|
13
|
+
return new BehaviorSubject(initialValue);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates an Observable from the given subject by piping it to the
|
|
18
|
+
* `distinctUntilChanged()` operator.
|
|
19
|
+
*/
|
|
20
|
+
export const duc = <T>(
|
|
21
|
+
source: BehaviorSubject<T>,
|
|
22
|
+
comparator?: (a: T, b: T) => boolean,
|
|
23
|
+
): Observable<T> => source.pipe(distinctUntilChanged(comparator));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Multicasts the given Observable, replaying the latest computed value to
|
|
27
|
+
* every new subscriber instead of re-running the pipeline for each of them.
|
|
28
|
+
*/
|
|
29
|
+
export const shared = <T>(source: Observable<T>): Observable<T> =>
|
|
30
|
+
source.pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { VideoEvent } from '../gen/coordinator';
|
|
2
|
+
|
|
3
|
+
/** Handles a single video event of the given type. */
|
|
4
|
+
export type EventHandler<T extends VideoEvent['type']> = (
|
|
5
|
+
event: Extract<VideoEvent, { type: T }>,
|
|
6
|
+
) => void;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Identity type constrained to real event names, so that a name which no
|
|
10
|
+
* longer exists in `VideoEvent` fails to compile instead of being ignored.
|
|
11
|
+
*/
|
|
12
|
+
type EventTypes<T extends VideoEvent['type']> = T;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Events that intentionally leave the call state untouched. Listing a name
|
|
16
|
+
* here excuses it from `CallStateEventHandlers`, which is what makes a newly
|
|
17
|
+
* introduced event fail to compile until it has been triaged.
|
|
18
|
+
*/
|
|
19
|
+
type UnhandledEventType = EventTypes<
|
|
20
|
+
| 'call.frame_recording_ready'
|
|
21
|
+
| 'call.kicked_user'
|
|
22
|
+
| 'call.moderation_blur'
|
|
23
|
+
| 'call.moderation_warning'
|
|
24
|
+
| 'call.permission_request'
|
|
25
|
+
| 'call.recording_ready'
|
|
26
|
+
| 'call.rtmp_broadcast_failed'
|
|
27
|
+
| 'call.rtmp_broadcast_started'
|
|
28
|
+
| 'call.rtmp_broadcast_stopped'
|
|
29
|
+
| 'call.stats_report_ready'
|
|
30
|
+
| 'call.transcription_ready'
|
|
31
|
+
| 'call.user_feedback_submitted'
|
|
32
|
+
| 'call.user_muted'
|
|
33
|
+
| 'connection.error'
|
|
34
|
+
| 'connection.ok'
|
|
35
|
+
| 'health.check'
|
|
36
|
+
| 'user.updated'
|
|
37
|
+
| 'custom'
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
export type AllEventHandlers = {
|
|
41
|
+
[T in VideoEvent['type']]: EventHandler<T>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** A handler for every event that does update the call state. */
|
|
45
|
+
export type CallStateEventHandlers = Omit<AllEventHandlers, UnhandledEventType>;
|
package/src/types.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
User,
|
|
23
23
|
} from './coordinator/connection/types';
|
|
24
24
|
import type { Comparator } from './sorting';
|
|
25
|
-
import type {
|
|
25
|
+
import type { ClientState } from './store';
|
|
26
26
|
import { AxiosError } from 'axios';
|
|
27
27
|
import type { Call } from './Call';
|
|
28
28
|
|
|
@@ -357,9 +357,9 @@ export type CallConstructor = {
|
|
|
357
357
|
sortParticipantsBy?: Comparator<StreamVideoParticipant>;
|
|
358
358
|
|
|
359
359
|
/**
|
|
360
|
-
* The state
|
|
360
|
+
* The state of the client.
|
|
361
361
|
*/
|
|
362
|
-
|
|
362
|
+
clientState: ClientState;
|
|
363
363
|
};
|
|
364
364
|
|
|
365
365
|
type StreamVideoClientBaseOptions = {
|
|
@@ -458,7 +458,17 @@ type StreamRNVideoSDKEndCallReason =
|
|
|
458
458
|
| 'unknown';
|
|
459
459
|
|
|
460
460
|
type StreamRNVideoSDKCallingX = {
|
|
461
|
-
|
|
461
|
+
/**
|
|
462
|
+
* @param isCancelled - polled around the bridge's waits: registration is
|
|
463
|
+
* skipped, or undone, when it returns true. Supplied by the join attempt
|
|
464
|
+
* because the call's own state cannot distinguish a fresh join starting
|
|
465
|
+
* from `LEFT` from an abandoned one that reached `LEFT` while waiting.
|
|
466
|
+
*/
|
|
467
|
+
joinCall: (
|
|
468
|
+
call: Call,
|
|
469
|
+
activeCalls: Call[],
|
|
470
|
+
isCancelled?: () => boolean,
|
|
471
|
+
) => Promise<void>;
|
|
462
472
|
endCall: (
|
|
463
473
|
call: Call,
|
|
464
474
|
reason?: StreamRNVideoSDKEndCallReason,
|
|
@@ -468,8 +478,34 @@ type StreamRNVideoSDKCallingX = {
|
|
|
468
478
|
unwireAudioEngineSubscription: () => void;
|
|
469
479
|
};
|
|
470
480
|
|
|
481
|
+
/**
|
|
482
|
+
* React Native's preparation and cleanup for a ringing call's join.
|
|
483
|
+
*
|
|
484
|
+
* A ringing call is joined by the SDK rather than by app code, so there is no
|
|
485
|
+
* point at which the app holds the call and can still set it up. These are that
|
|
486
|
+
* point, and the matching release.
|
|
487
|
+
*/
|
|
488
|
+
type StreamRNVideoSDKRingingCallLifecycle = {
|
|
489
|
+
/**
|
|
490
|
+
* Runs the app's pre-join setup and resolves once it is done, bounded by a
|
|
491
|
+
* deadline. Rejecting fails the join closed rather than joining with nothing
|
|
492
|
+
* installed.
|
|
493
|
+
*/
|
|
494
|
+
beforeJoin: (call: Call) => Promise<void>;
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* The join failed terminally. Ends the ringing flow and releases what the
|
|
498
|
+
* setup installed. Never rejects, so the original join error survives.
|
|
499
|
+
*/
|
|
500
|
+
onJoinFailed: (call: Call) => Promise<void>;
|
|
501
|
+
|
|
502
|
+
/** The call has ended; release whatever the pre-join hook installed. */
|
|
503
|
+
onLeave: (call: Call) => void;
|
|
504
|
+
};
|
|
505
|
+
|
|
471
506
|
export type StreamRNVideoSDKGlobals = {
|
|
472
507
|
callingX: StreamRNVideoSDKCallingX;
|
|
508
|
+
ringingCallLifecycle: StreamRNVideoSDKRingingCallLifecycle;
|
|
473
509
|
callManager: {
|
|
474
510
|
/**
|
|
475
511
|
* Sets up the in call manager.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { BehaviorSubject, Observable } from 'rxjs';
|
|
2
|
-
import type { Patch } from './rxUtils';
|
|
3
|
-
import { Call } from '../Call';
|
|
4
|
-
import type { OwnUserResponse } from '../gen/coordinator';
|
|
5
|
-
export declare class StreamVideoWriteableStateStore {
|
|
6
|
-
/**
|
|
7
|
-
* A store keeping data of a successfully connected user over WS to the coordinator server.
|
|
8
|
-
*/
|
|
9
|
-
connectedUserSubject: BehaviorSubject<OwnUserResponse | undefined>;
|
|
10
|
-
/**
|
|
11
|
-
* A list of {@link Call} objects created/tracked by this client.
|
|
12
|
-
*/
|
|
13
|
-
callsSubject: BehaviorSubject<Call[]>;
|
|
14
|
-
constructor();
|
|
15
|
-
/**
|
|
16
|
-
* The currently connected user.
|
|
17
|
-
*/
|
|
18
|
-
get connectedUser(): OwnUserResponse | undefined;
|
|
19
|
-
/**
|
|
20
|
-
* Sets the currently connected user.
|
|
21
|
-
*
|
|
22
|
-
* @internal
|
|
23
|
-
* @param user the user to set as connected.
|
|
24
|
-
*/
|
|
25
|
-
setConnectedUser: (user: Patch<OwnUserResponse | undefined>) => OwnUserResponse | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* A list of {@link Call} objects created/tracked by this client.
|
|
28
|
-
*/
|
|
29
|
-
get calls(): Call[];
|
|
30
|
-
/**
|
|
31
|
-
* Sets the list of {@link Call} objects created/tracked by this client.
|
|
32
|
-
* @param calls
|
|
33
|
-
*/
|
|
34
|
-
setCalls: (calls: Patch<Call[]>) => Call[];
|
|
35
|
-
/**
|
|
36
|
-
* Adds a {@link Call} object to the list of {@link Call} objects created/tracked by this client.
|
|
37
|
-
*
|
|
38
|
-
* @param call the call to add.
|
|
39
|
-
*/
|
|
40
|
-
registerCall: (call: Call) => void;
|
|
41
|
-
/**
|
|
42
|
-
* Registers a {@link Call} object if it doesn't exist, otherwise updates it.
|
|
43
|
-
*
|
|
44
|
-
* @param call the call to register or update.
|
|
45
|
-
*/
|
|
46
|
-
registerOrUpdateCall: (call: Call) => void | Call[];
|
|
47
|
-
/**
|
|
48
|
-
* Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
|
|
49
|
-
*
|
|
50
|
-
* @param call the call to remove
|
|
51
|
-
*/
|
|
52
|
-
unregisterCall: (call: Call) => Call[];
|
|
53
|
-
/**
|
|
54
|
-
* Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
|
|
55
|
-
*
|
|
56
|
-
* @param type the type of call to find.
|
|
57
|
-
* @param id the id of the call to find.
|
|
58
|
-
*/
|
|
59
|
-
findCall: (type: string, id: string) => Call | undefined;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* A reactive store that exposes state variables in a reactive manner.
|
|
63
|
-
* You can subscribe to changes of the different state variables.
|
|
64
|
-
* This central store contains all the state variables related to [`StreamVideoClient`](./StreamVideClient.md) and [`Call`](./Call.md).
|
|
65
|
-
*/
|
|
66
|
-
export declare class StreamVideoReadOnlyStateStore {
|
|
67
|
-
/**
|
|
68
|
-
* Data describing a user successfully connected over WS to coordinator server.
|
|
69
|
-
*/
|
|
70
|
-
connectedUser$: Observable<OwnUserResponse | undefined>;
|
|
71
|
-
/**
|
|
72
|
-
* A list of {@link Call} objects created/tracked by this client.
|
|
73
|
-
*/
|
|
74
|
-
calls$: Observable<Call[]>;
|
|
75
|
-
constructor(store: StreamVideoWriteableStateStore);
|
|
76
|
-
/**
|
|
77
|
-
* The current user connected over WS to the backend.
|
|
78
|
-
*/
|
|
79
|
-
get connectedUser(): OwnUserResponse | undefined;
|
|
80
|
-
/**
|
|
81
|
-
* A list of {@link Call} objects created/tracked by this client.
|
|
82
|
-
*/
|
|
83
|
-
get calls(): Call[];
|
|
84
|
-
}
|