@stream-io/video-client 1.56.0 → 1.57.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 +10 -0
- package/dist/index.browser.es.js +39 -19
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +39 -19
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +39 -19
- package/dist/index.es.js.map +1 -1
- package/dist/src/helpers/MediaPlaybackWatchdog.d.ts +4 -0
- package/package.json +11 -11
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +1 -2
- package/src/devices/devicePersistence.ts +1 -2
- package/src/helpers/MediaPlaybackWatchdog.ts +35 -16
- package/src/helpers/__tests__/MediaPlaybackWatchdog.test.ts +137 -8
- package/src/helpers/__tests__/ViewportTracker.test.ts +1 -2
- package/src/reporting/ClientEventReporter.ts +2 -8
- package/src/rtc/types.ts +1 -2
- package/src/stats/rtc/types.ts +8 -2
- package/src/store/CallState.ts +1 -2
- package/src/types.ts +1 -4
|
@@ -18,12 +18,16 @@ export declare class MediaPlaybackWatchdog {
|
|
|
18
18
|
private tracer;
|
|
19
19
|
private controller;
|
|
20
20
|
private pendingTimer;
|
|
21
|
+
private settleTimer;
|
|
21
22
|
private attempt;
|
|
22
23
|
private disposed;
|
|
24
|
+
private stopped;
|
|
23
25
|
constructor(opts: MediaPlaybackWatchdogOptions);
|
|
24
26
|
private attach;
|
|
25
27
|
dispose: () => void;
|
|
28
|
+
private clearTimers;
|
|
26
29
|
private onPlaying;
|
|
30
|
+
private onSettled;
|
|
27
31
|
private onPauseOrSuspend;
|
|
28
32
|
private scheduleRecovery;
|
|
29
33
|
private computeSkipReason;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.57.0",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.es.js",
|
|
6
6
|
"browser": "dist/index.browser.es.js",
|
|
@@ -36,31 +36,31 @@
|
|
|
36
36
|
"@protobuf-ts/twirp-transport": "^2.11.1",
|
|
37
37
|
"@stream-io/logger": "^2.0.0",
|
|
38
38
|
"@stream-io/worker-timer": "^1.2.5",
|
|
39
|
-
"axios": "^1.
|
|
39
|
+
"axios": "^1.19.0",
|
|
40
40
|
"rxjs": "~7.8.2",
|
|
41
41
|
"sdp-transform": "^2.15.0",
|
|
42
42
|
"ua-parser-js": "^1.0.41",
|
|
43
43
|
"webrtc-adapter": "^8.2.4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@openapitools/openapi-generator-cli": "^2.
|
|
46
|
+
"@openapitools/openapi-generator-cli": "^2.40.1",
|
|
47
47
|
"@rollup/plugin-replace": "^6.0.3",
|
|
48
48
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
49
|
-
"@stream-io/audio-filters-web": "^0.10.
|
|
50
|
-
"@stream-io/node-sdk": "^0.7.
|
|
49
|
+
"@stream-io/audio-filters-web": "^0.10.4",
|
|
50
|
+
"@stream-io/node-sdk": "^0.7.63",
|
|
51
51
|
"@stream-io/typescript-config": "^0.0.0",
|
|
52
52
|
"@total-typescript/shoehorn": "^0.1.2",
|
|
53
|
-
"@types/node": "^25.9.
|
|
53
|
+
"@types/node": "^25.9.5",
|
|
54
54
|
"@types/sdp-transform": "^2.15.0",
|
|
55
55
|
"@types/ua-parser-js": "^0.7.39",
|
|
56
|
-
"@vitest/coverage-v8": "^4.1.
|
|
56
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
57
57
|
"dotenv": "^16.6.1",
|
|
58
|
-
"happy-dom": "^20.
|
|
59
|
-
"prettier": "^3.
|
|
58
|
+
"happy-dom": "^20.11.1",
|
|
59
|
+
"prettier": "^3.9.6",
|
|
60
60
|
"rimraf": "^6.1.3",
|
|
61
|
-
"rollup": "^4.
|
|
61
|
+
"rollup": "^4.62.4",
|
|
62
62
|
"typescript": "^6.0.3",
|
|
63
|
-
"vitest": "^4.1.
|
|
63
|
+
"vitest": "^4.1.10",
|
|
64
64
|
"vitest-mock-extended": "^4.0.0"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -16,8 +16,7 @@ import { settled, withoutConcurrency } from '../../helpers/concurrency';
|
|
|
16
16
|
import { ClientEventReporter } from '../../reporting';
|
|
17
17
|
|
|
18
18
|
let speechActivityCallback:
|
|
19
|
-
|
|
20
|
-
| null = null;
|
|
19
|
+
((state: { isSoundDetected: boolean }) => void) | null = null;
|
|
21
20
|
let unsubscribeMocks: ReturnType<typeof vi.fn>[] = [];
|
|
22
21
|
|
|
23
22
|
vi.mock('../../helpers/platforms.ts', () => {
|
|
@@ -24,8 +24,7 @@ export type LocalDevicePreference = {
|
|
|
24
24
|
|
|
25
25
|
export type LocalDevicePreferences = {
|
|
26
26
|
[type in DevicePreferenceKey]?:
|
|
27
|
-
| LocalDevicePreference
|
|
28
|
-
| LocalDevicePreference[];
|
|
27
|
+
LocalDevicePreference | LocalDevicePreference[];
|
|
29
28
|
};
|
|
30
29
|
|
|
31
30
|
export const defaultDeviceId = 'default';
|
|
@@ -23,8 +23,10 @@ export class MediaPlaybackWatchdog {
|
|
|
23
23
|
private tracer: Tracer;
|
|
24
24
|
private controller = new AbortController();
|
|
25
25
|
private pendingTimer: ReturnType<typeof setTimeout> | undefined;
|
|
26
|
+
private settleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
26
27
|
private attempt = 0;
|
|
27
28
|
private disposed = false;
|
|
29
|
+
private stopped = false;
|
|
28
30
|
|
|
29
31
|
constructor(opts: MediaPlaybackWatchdogOptions) {
|
|
30
32
|
this.element = opts.element;
|
|
@@ -46,33 +48,47 @@ export class MediaPlaybackWatchdog {
|
|
|
46
48
|
if (this.disposed) return;
|
|
47
49
|
this.disposed = true;
|
|
48
50
|
this.controller.abort();
|
|
51
|
+
this.clearTimers();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
private clearTimers = () => {
|
|
49
55
|
if (this.pendingTimer) clearTimeout(this.pendingTimer);
|
|
50
56
|
this.pendingTimer = undefined;
|
|
57
|
+
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
58
|
+
this.settleTimer = undefined;
|
|
51
59
|
};
|
|
52
60
|
|
|
53
61
|
private onPlaying = () => {
|
|
54
|
-
if (this.attempt > 0) {
|
|
55
|
-
this.tracer.trace('mediaPlayback.recover.success', {
|
|
56
|
-
kind: this.kind,
|
|
57
|
-
attempts: this.attempt,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
this.attempt = 0;
|
|
61
62
|
if (this.pendingTimer) clearTimeout(this.pendingTimer);
|
|
62
63
|
this.pendingTimer = undefined;
|
|
64
|
+
if (this.attempt === 0 || this.settleTimer) return;
|
|
65
|
+
this.settleTimer ??= setTimeout(this.onSettled, 1000);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
private onSettled = () => {
|
|
69
|
+
this.settleTimer = undefined;
|
|
70
|
+
if (this.element.paused) return;
|
|
71
|
+
this.tracer.trace('mediaPlayback.recover.success', {
|
|
72
|
+
kind: this.kind,
|
|
73
|
+
attempts: this.attempt,
|
|
74
|
+
});
|
|
75
|
+
this.attempt = 0;
|
|
76
|
+
this.stopped = false;
|
|
63
77
|
};
|
|
64
78
|
|
|
65
79
|
private onPauseOrSuspend = (event: Event) => {
|
|
66
|
-
if (this.disposed) return;
|
|
80
|
+
if (this.disposed || this.stopped) return;
|
|
67
81
|
this.tracer.trace('mediaPlayback.paused', {
|
|
68
82
|
kind: this.kind,
|
|
69
83
|
reason: event.type,
|
|
70
84
|
});
|
|
85
|
+
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
86
|
+
this.settleTimer = undefined;
|
|
71
87
|
this.scheduleRecovery();
|
|
72
88
|
};
|
|
73
89
|
|
|
74
90
|
private scheduleRecovery = () => {
|
|
75
|
-
if (this.disposed || this.pendingTimer) return;
|
|
91
|
+
if (this.disposed || this.stopped || this.pendingTimer) return;
|
|
76
92
|
const skipReason = this.computeSkipReason();
|
|
77
93
|
if (skipReason) {
|
|
78
94
|
this.tracer.trace('mediaPlayback.recover.skipped', {
|
|
@@ -81,6 +97,16 @@ export class MediaPlaybackWatchdog {
|
|
|
81
97
|
});
|
|
82
98
|
return;
|
|
83
99
|
}
|
|
100
|
+
|
|
101
|
+
if (this.attempt >= 10) {
|
|
102
|
+
this.stopped = true;
|
|
103
|
+
this.clearTimers();
|
|
104
|
+
this.tracer.trace('mediaPlayback.recover.giveUp', {
|
|
105
|
+
kind: this.kind,
|
|
106
|
+
attempts: this.attempt,
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
84
110
|
const delay = this.attempt === 0 ? 0 : retryInterval(this.attempt);
|
|
85
111
|
this.pendingTimer = setTimeout(this.attemptPlay, delay);
|
|
86
112
|
};
|
|
@@ -109,13 +135,6 @@ export class MediaPlaybackWatchdog {
|
|
|
109
135
|
} catch (err) {
|
|
110
136
|
if (this.disposed) return;
|
|
111
137
|
this.logger.warn(`Failed to recover ${this.kind} playback`, err);
|
|
112
|
-
if (this.attempt >= 10) {
|
|
113
|
-
this.tracer.trace('mediaPlayback.recover.giveUp', {
|
|
114
|
-
kind: this.kind,
|
|
115
|
-
attempts: this.attempt,
|
|
116
|
-
});
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
138
|
this.scheduleRecovery();
|
|
120
139
|
}
|
|
121
140
|
};
|
|
@@ -28,20 +28,33 @@ const createMediaElement = (
|
|
|
28
28
|
state: FakeMediaState = {},
|
|
29
29
|
) => {
|
|
30
30
|
const el = document.createElement(kind) as HTMLMediaElement;
|
|
31
|
-
Object.defineProperty(el, 'srcObject', {
|
|
31
|
+
Object.defineProperty(el, 'srcObject', {
|
|
32
|
+
value: 'srcObject' in state ? state.srcObject : new MediaStream(),
|
|
33
|
+
writable: true,
|
|
34
|
+
});
|
|
32
35
|
Object.defineProperty(el, 'paused', { writable: true, configurable: true });
|
|
33
36
|
Object.defineProperty(el, 'readyState', {
|
|
37
|
+
value: state.readyState ?? 4,
|
|
38
|
+
writable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(el, 'ended', {
|
|
42
|
+
value: state.ended ?? false,
|
|
34
43
|
writable: true,
|
|
35
44
|
configurable: true,
|
|
36
45
|
});
|
|
37
|
-
|
|
38
|
-
el.srcObject = 'srcObject' in state ? state.srcObject : new MediaStream();
|
|
39
|
-
el.paused = state.paused ?? true;
|
|
40
|
-
el.readyState = state.readyState ?? 4;
|
|
41
|
-
el.ended = state.ended ?? false;
|
|
46
|
+
setPaused(el, state.paused ?? true);
|
|
42
47
|
return el;
|
|
43
48
|
};
|
|
44
49
|
|
|
50
|
+
const setPaused = (el: HTMLMediaElement, paused: boolean) => {
|
|
51
|
+
Object.defineProperty(el, 'paused', {
|
|
52
|
+
value: paused,
|
|
53
|
+
writable: true,
|
|
54
|
+
configurable: true,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
45
58
|
type SetupOpts = {
|
|
46
59
|
kind?: 'audio' | 'video';
|
|
47
60
|
state?: FakeMediaState;
|
|
@@ -111,15 +124,131 @@ describe('MediaPlaybackWatchdog', () => {
|
|
|
111
124
|
await vi.advanceTimersByTimeAsync(6000);
|
|
112
125
|
expect(play).toHaveBeenCalledTimes(2);
|
|
113
126
|
|
|
114
|
-
// Simulate the element actually starting to play
|
|
127
|
+
// Simulate the element actually starting to play, and staying up long
|
|
128
|
+
// enough for the recovery to count as settled.
|
|
129
|
+
setPaused(el, false);
|
|
115
130
|
el.dispatchEvent(new Event('playing'));
|
|
131
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
116
132
|
|
|
117
|
-
// A subsequent pause should
|
|
133
|
+
// A subsequent pause should trigger a fresh, immediate recovery attempt.
|
|
134
|
+
setPaused(el, true);
|
|
118
135
|
el.dispatchEvent(new Event('pause'));
|
|
119
136
|
await vi.advanceTimersByTimeAsync(0);
|
|
120
137
|
expect(play).toHaveBeenCalledTimes(3);
|
|
121
138
|
});
|
|
122
139
|
|
|
140
|
+
it('backs off instead of retrying immediately when playback has not settled', async () => {
|
|
141
|
+
el.dispatchEvent(new Event('pause'));
|
|
142
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
143
|
+
expect(play).toHaveBeenCalledTimes(1);
|
|
144
|
+
|
|
145
|
+
// resumed, but re-paused before the settle delay elapsed
|
|
146
|
+
setPaused(el, false);
|
|
147
|
+
el.dispatchEvent(new Event('playing'));
|
|
148
|
+
expect(tracer.trace).not.toHaveBeenCalledWith(
|
|
149
|
+
'mediaPlayback.recover.success',
|
|
150
|
+
{
|
|
151
|
+
kind: 'audio',
|
|
152
|
+
attempts: 1,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
156
|
+
setPaused(el, true);
|
|
157
|
+
el.dispatchEvent(new Event('pause'));
|
|
158
|
+
|
|
159
|
+
// the next attempt is subject to backoff, not scheduled on the next tick
|
|
160
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
161
|
+
expect(play).toHaveBeenCalledTimes(1);
|
|
162
|
+
|
|
163
|
+
await vi.advanceTimersByTimeAsync(6000);
|
|
164
|
+
expect(play).toHaveBeenCalledTimes(2);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('stops fighting an element that re-pauses after every successful play', async () => {
|
|
168
|
+
setup({ kind: 'video' });
|
|
169
|
+
// Safari re-pauses the element right after play() resolves. This used to
|
|
170
|
+
// reset the attempt counter, so the loop spun with a zero delay forever.
|
|
171
|
+
play.mockImplementation(async () => {
|
|
172
|
+
setPaused(el, false);
|
|
173
|
+
el.dispatchEvent(new Event('playing'));
|
|
174
|
+
setPaused(el, true);
|
|
175
|
+
el.dispatchEvent(new Event('pause'));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
el.dispatchEvent(new Event('pause'));
|
|
179
|
+
for (let i = 0; i < 20; i++) {
|
|
180
|
+
await vi.advanceTimersByTimeAsync(6000);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
expect(play).toHaveBeenCalledTimes(10);
|
|
184
|
+
expect(tracer.trace).toHaveBeenCalledWith('mediaPlayback.recover.giveUp', {
|
|
185
|
+
kind: 'video',
|
|
186
|
+
attempts: 10,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// External playback can still emit `playing` after the watchdog gave up
|
|
190
|
+
// (for example from a user gesture or another playback helper). If it
|
|
191
|
+
// re-pauses before settling, it must not revive the watchdog or trace a
|
|
192
|
+
// false recovery success.
|
|
193
|
+
setPaused(el, false);
|
|
194
|
+
el.dispatchEvent(new Event('playing'));
|
|
195
|
+
setPaused(el, true);
|
|
196
|
+
el.dispatchEvent(new Event('pause'));
|
|
197
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
198
|
+
|
|
199
|
+
expect(tracer.trace).not.toHaveBeenCalledWith(
|
|
200
|
+
'mediaPlayback.recover.success',
|
|
201
|
+
{
|
|
202
|
+
kind: 'video',
|
|
203
|
+
attempts: 10,
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// and it stays stopped, without tracing the pause storm
|
|
208
|
+
const traceMock = tracer.trace as unknown as MockInstance;
|
|
209
|
+
const tracesBefore = traceMock.mock.calls.length;
|
|
210
|
+
for (let i = 0; i < 100; i++) {
|
|
211
|
+
el.dispatchEvent(new Event('pause'));
|
|
212
|
+
}
|
|
213
|
+
await vi.advanceTimersByTimeAsync(6000);
|
|
214
|
+
|
|
215
|
+
expect(play).toHaveBeenCalledTimes(10);
|
|
216
|
+
expect(traceMock.mock.calls.length).toBe(tracesBefore);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('revives once playback genuinely holds for the settle window', async () => {
|
|
220
|
+
setup({ kind: 'video' });
|
|
221
|
+
play.mockImplementation(async () => {
|
|
222
|
+
setPaused(el, false);
|
|
223
|
+
el.dispatchEvent(new Event('playing'));
|
|
224
|
+
setPaused(el, true);
|
|
225
|
+
el.dispatchEvent(new Event('pause'));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
el.dispatchEvent(new Event('pause'));
|
|
229
|
+
for (let i = 0; i < 20; i++) {
|
|
230
|
+
await vi.advanceTimersByTimeAsync(6000);
|
|
231
|
+
}
|
|
232
|
+
expect(play).toHaveBeenCalledTimes(10); // gave up
|
|
233
|
+
|
|
234
|
+
play.mockResolvedValue(undefined);
|
|
235
|
+
setPaused(el, false);
|
|
236
|
+
el.dispatchEvent(new Event('playing'));
|
|
237
|
+
await vi.advanceTimersByTimeAsync(1500);
|
|
238
|
+
|
|
239
|
+
expect(tracer.trace).toHaveBeenCalledWith('mediaPlayback.recover.success', {
|
|
240
|
+
kind: 'video',
|
|
241
|
+
attempts: 10,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// recovery is live again: the attempt counter was reset, so a fresh pause
|
|
245
|
+
// gets an immediate attempt rather than being ignored
|
|
246
|
+
setPaused(el, true);
|
|
247
|
+
el.dispatchEvent(new Event('pause'));
|
|
248
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
249
|
+
expect(play).toHaveBeenCalledTimes(11);
|
|
250
|
+
});
|
|
251
|
+
|
|
123
252
|
it('does not attempt recovery when srcObject is null', async () => {
|
|
124
253
|
setup({ state: { srcObject: null } });
|
|
125
254
|
|
|
@@ -38,8 +38,7 @@ describe('ViewportTracker', () => {
|
|
|
38
38
|
|
|
39
39
|
it('updates participant viewportVisibilityState as visibility changes', () => {
|
|
40
40
|
let visibilityHandler:
|
|
41
|
-
|
|
42
|
-
| undefined;
|
|
41
|
+
((entry: IntersectionObserverEntry) => void) | undefined;
|
|
43
42
|
vi.spyOn(viewportTracker, 'observe').mockImplementation((_el, handler) => {
|
|
44
43
|
visibilityHandler = handler;
|
|
45
44
|
return vi.fn();
|
|
@@ -33,18 +33,12 @@ export type ClientEventStage =
|
|
|
33
33
|
| 'FirstAudioFrame';
|
|
34
34
|
|
|
35
35
|
export type MediaPermissionState =
|
|
36
|
-
| '
|
|
37
|
-
| 'FAILED'
|
|
38
|
-
| 'GRANTED'
|
|
39
|
-
| 'NOT_INITIATED';
|
|
36
|
+
'INITIATED' | 'FAILED' | 'GRANTED' | 'NOT_INITIATED';
|
|
40
37
|
|
|
41
38
|
export type ReportedIceState = 'CONNECTED' | 'FAILED' | 'NOT_CONNECTED';
|
|
42
39
|
|
|
43
40
|
export type JoinReason =
|
|
44
|
-
|
|
45
|
-
| 'network-available'
|
|
46
|
-
| 'migration'
|
|
47
|
-
| 'full-rejoin';
|
|
41
|
+
'first-attempt' | 'network-available' | 'migration' | 'full-rejoin';
|
|
48
42
|
|
|
49
43
|
export type ClientEventStandardCode =
|
|
50
44
|
| 'CLIENT_ABORTED'
|
package/src/rtc/types.ts
CHANGED
|
@@ -39,8 +39,7 @@ export const ReconnectReason = {
|
|
|
39
39
|
} as const;
|
|
40
40
|
|
|
41
41
|
export type ReconnectReason =
|
|
42
|
-
|
|
43
|
-
| (string & {});
|
|
42
|
+
(typeof ReconnectReason)[keyof typeof ReconnectReason] | (string & {});
|
|
44
43
|
|
|
45
44
|
export type OnReconnectionNeeded = (
|
|
46
45
|
kind: WebsocketReconnectStrategy,
|
package/src/stats/rtc/types.ts
CHANGED
|
@@ -12,9 +12,15 @@ export type RTCStatsDataType =
|
|
|
12
12
|
| boolean
|
|
13
13
|
| RTCOfferOptions
|
|
14
14
|
| [string | RTCDataChannelInit | undefined] // createDataChannel
|
|
15
|
-
| (
|
|
15
|
+
| (
|
|
16
|
+
| RTCOfferOptions
|
|
17
|
+
| undefined // createOffer | createAnswer
|
|
18
|
+
)
|
|
16
19
|
| RTCSessionDescriptionInit
|
|
17
|
-
| (
|
|
20
|
+
| (
|
|
21
|
+
| RTCIceCandidateInit
|
|
22
|
+
| RTCIceCandidate // addIceCandidate
|
|
23
|
+
)
|
|
18
24
|
| object
|
|
19
25
|
| number
|
|
20
26
|
| null
|
package/src/store/CallState.ts
CHANGED
|
@@ -341,8 +341,7 @@ export class CallState {
|
|
|
341
341
|
|
|
342
342
|
private readonly eventHandlers: {
|
|
343
343
|
[EventType in VideoEvent['type']]:
|
|
344
|
-
|
|
345
|
-
| undefined;
|
|
344
|
+
((event: Extract<VideoEvent, { type: EventType }>) => void) | undefined;
|
|
346
345
|
};
|
|
347
346
|
|
|
348
347
|
/**
|
package/src/types.ts
CHANGED
|
@@ -143,10 +143,7 @@ export interface StreamVideoParticipant extends Participant {
|
|
|
143
143
|
export type VideoTrackType = 'videoTrack' | 'screenShareTrack';
|
|
144
144
|
export type AudioTrackType = 'audioTrack' | 'screenShareAudioTrack';
|
|
145
145
|
export type TrackMuteType =
|
|
146
|
-
| '
|
|
147
|
-
| 'video'
|
|
148
|
-
| 'screenshare'
|
|
149
|
-
| 'screenshare_audio';
|
|
146
|
+
'audio' | 'video' | 'screenshare' | 'screenshare_audio';
|
|
150
147
|
|
|
151
148
|
/**
|
|
152
149
|
* Represents a participant's pin state.
|