@stream-io/video-client 0.3.2 → 0.3.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.
- package/CHANGELOG.md +14 -0
- package/dist/index.browser.es.js +77 -56
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +77 -56
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +77 -56
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +2 -1
- package/dist/src/coordinator/connection/client.d.ts +9 -4
- package/dist/src/devices/CameraManager.d.ts +3 -9
- package/dist/src/devices/InputMediaDeviceManager.d.ts +11 -5
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +6 -1
- package/dist/src/devices/MicrophoneManager.d.ts +3 -9
- package/dist/src/devices/MicrophoneManagerState.d.ts +1 -0
- package/dist/src/rtc/Publisher.d.ts +2 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Call.ts +3 -2
- package/src/StreamVideoClient.ts +1 -10
- package/src/__tests__/StreamVideoClient.test.ts +13 -0
- package/src/coordinator/connection/client.ts +28 -0
- package/src/devices/CameraManager.ts +8 -17
- package/src/devices/CameraManagerState.ts +1 -1
- package/src/devices/InputMediaDeviceManager.ts +39 -17
- package/src/devices/InputMediaDeviceManagerState.ts +12 -2
- package/src/devices/MicrophoneManager.ts +8 -17
- package/src/devices/MicrophoneManagerState.ts +4 -0
- package/src/devices/__tests__/CameraManager.test.ts +4 -14
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +48 -5
- package/src/devices/__tests__/MicrophoneManager.test.ts +8 -7
- package/src/rtc/Publisher.ts +8 -2
- package/src/rtc/__tests__/Publisher.test.ts +67 -1
|
@@ -7,6 +7,14 @@ import { mockCall, mockVideoDevices, mockVideoStream } from './mocks';
|
|
|
7
7
|
import { InputMediaDeviceManager } from '../InputMediaDeviceManager';
|
|
8
8
|
import { InputMediaDeviceManagerState } from '../InputMediaDeviceManagerState';
|
|
9
9
|
import { of } from 'rxjs';
|
|
10
|
+
import { disposeOfMediaStream } from '../devices';
|
|
11
|
+
|
|
12
|
+
vi.mock('../devices.ts', () => {
|
|
13
|
+
console.log('MOCKING devices');
|
|
14
|
+
return {
|
|
15
|
+
disposeOfMediaStream: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
10
18
|
|
|
11
19
|
vi.mock('../../Call.ts', () => {
|
|
12
20
|
console.log('MOCKING Call');
|
|
@@ -24,8 +32,8 @@ class TestInputMediaDeviceManager extends InputMediaDeviceManager<TestInputMedia
|
|
|
24
32
|
public getStream = vi.fn(() => Promise.resolve(mockVideoStream()));
|
|
25
33
|
public publishStream = vi.fn();
|
|
26
34
|
public stopPublishStream = vi.fn();
|
|
27
|
-
public
|
|
28
|
-
public
|
|
35
|
+
public muteTracks = vi.fn();
|
|
36
|
+
public unmuteTracks = vi.fn();
|
|
29
37
|
|
|
30
38
|
constructor(call: Call) {
|
|
31
39
|
super(call, new TestInputMediaDeviceManagerState());
|
|
@@ -92,14 +100,14 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
92
100
|
expect(manager.state.status).toBe('disabled');
|
|
93
101
|
});
|
|
94
102
|
|
|
95
|
-
it('disable
|
|
103
|
+
it('disable device - after joined to call', async () => {
|
|
96
104
|
// @ts-expect-error
|
|
97
105
|
manager['call'].state.callingState = CallingState.JOINED;
|
|
98
106
|
await manager.enable();
|
|
99
107
|
|
|
100
108
|
await manager.disable();
|
|
101
109
|
|
|
102
|
-
expect(manager.stopPublishStream).toHaveBeenCalledWith();
|
|
110
|
+
expect(manager.stopPublishStream).toHaveBeenCalledWith(true);
|
|
103
111
|
});
|
|
104
112
|
|
|
105
113
|
it('toggle device', async () => {
|
|
@@ -124,6 +132,16 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
124
132
|
expect(manager.publishStream).not.toHaveBeenCalled();
|
|
125
133
|
});
|
|
126
134
|
|
|
135
|
+
it('select device when status is enabled', async () => {
|
|
136
|
+
await manager.enable();
|
|
137
|
+
const prevStream = manager.state.mediaStream;
|
|
138
|
+
|
|
139
|
+
const deviceId = mockVideoDevices[1].deviceId;
|
|
140
|
+
await manager.select(deviceId);
|
|
141
|
+
|
|
142
|
+
expect(disposeOfMediaStream).toHaveBeenCalledWith(prevStream);
|
|
143
|
+
});
|
|
144
|
+
|
|
127
145
|
it('select device when status is enabled and in call', async () => {
|
|
128
146
|
// @ts-expect-error
|
|
129
147
|
manager['call'].state.callingState = CallingState.JOINED;
|
|
@@ -132,7 +150,7 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
132
150
|
const deviceId = mockVideoDevices[1].deviceId;
|
|
133
151
|
await manager.select(deviceId);
|
|
134
152
|
|
|
135
|
-
expect(manager.stopPublishStream).toHaveBeenCalledWith();
|
|
153
|
+
expect(manager.stopPublishStream).toHaveBeenCalledWith(true);
|
|
136
154
|
expect(manager.getStream).toHaveBeenCalledWith({
|
|
137
155
|
deviceId,
|
|
138
156
|
});
|
|
@@ -152,6 +170,31 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
152
170
|
expect(spy.mock.calls.length).toBe(1);
|
|
153
171
|
});
|
|
154
172
|
|
|
173
|
+
it('should resume previously enabled state', async () => {
|
|
174
|
+
vi.spyOn(manager, 'enable');
|
|
175
|
+
|
|
176
|
+
await manager.enable();
|
|
177
|
+
|
|
178
|
+
expect(manager.enable).toHaveBeenCalledTimes(1);
|
|
179
|
+
|
|
180
|
+
await manager.disable();
|
|
181
|
+
await manager.resume();
|
|
182
|
+
|
|
183
|
+
expect(manager.enable).toHaveBeenCalledTimes(2);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it(`shouldn't resume if previous state is disabled`, async () => {
|
|
187
|
+
vi.spyOn(manager, 'enable');
|
|
188
|
+
|
|
189
|
+
await manager.disable();
|
|
190
|
+
|
|
191
|
+
expect(manager.enable).not.toHaveBeenCalled();
|
|
192
|
+
|
|
193
|
+
await manager.resume();
|
|
194
|
+
|
|
195
|
+
expect(manager.enable).not.toHaveBeenCalled();
|
|
196
|
+
});
|
|
197
|
+
|
|
155
198
|
afterEach(() => {
|
|
156
199
|
vi.clearAllMocks();
|
|
157
200
|
vi.resetModules();
|
|
@@ -81,19 +81,20 @@ describe('MicrophoneManager', () => {
|
|
|
81
81
|
|
|
82
82
|
await manager.disable();
|
|
83
83
|
|
|
84
|
-
expect(manager['call'].stopPublish).toHaveBeenCalledWith(
|
|
84
|
+
expect(manager['call'].stopPublish).toHaveBeenCalledWith(
|
|
85
|
+
TrackType.AUDIO,
|
|
86
|
+
false,
|
|
87
|
+
);
|
|
85
88
|
});
|
|
86
89
|
|
|
87
|
-
it('
|
|
90
|
+
it('disable-enable mic should set track.enabled', async () => {
|
|
88
91
|
await manager.enable();
|
|
89
92
|
|
|
90
|
-
manager.
|
|
91
|
-
|
|
92
|
-
expect(manager.state.mediaStream?.getAudioTracks()[0].enabled).toBe(false);
|
|
93
|
+
expect(manager.state.mediaStream!.getAudioTracks()[0].enabled).toBe(true);
|
|
93
94
|
|
|
94
|
-
manager.
|
|
95
|
+
await manager.disable();
|
|
95
96
|
|
|
96
|
-
expect(manager.state.mediaStream
|
|
97
|
+
expect(manager.state.mediaStream!.getAudioTracks()[0].enabled).toBe(false);
|
|
97
98
|
});
|
|
98
99
|
|
|
99
100
|
afterEach(() => {
|
package/src/rtc/Publisher.ts
CHANGED
|
@@ -283,6 +283,9 @@ export class Publisher {
|
|
|
283
283
|
previousTrack.removeEventListener('ended', handleTrackEnded);
|
|
284
284
|
track.addEventListener('ended', handleTrackEnded);
|
|
285
285
|
}
|
|
286
|
+
if (!track.enabled) {
|
|
287
|
+
track.enabled = true;
|
|
288
|
+
}
|
|
286
289
|
await transceiver.sender.replaceTrack(track);
|
|
287
290
|
}
|
|
288
291
|
|
|
@@ -298,8 +301,9 @@ export class Publisher {
|
|
|
298
301
|
* Stops publishing the given track type to the SFU, if it is currently being published.
|
|
299
302
|
* Underlying track will be stopped and removed from the publisher.
|
|
300
303
|
* @param trackType the track type to unpublish.
|
|
304
|
+
* @param stopTrack specifies whether track should be stopped or just disabled
|
|
301
305
|
*/
|
|
302
|
-
unpublishStream = async (trackType: TrackType) => {
|
|
306
|
+
unpublishStream = async (trackType: TrackType, stopTrack: boolean) => {
|
|
303
307
|
const transceiver = this.pc
|
|
304
308
|
.getTransceivers()
|
|
305
309
|
.find((t) => t === this.transceiverRegistry[trackType] && t.sender.track);
|
|
@@ -308,7 +312,9 @@ export class Publisher {
|
|
|
308
312
|
transceiver.sender.track &&
|
|
309
313
|
transceiver.sender.track.readyState === 'live'
|
|
310
314
|
) {
|
|
311
|
-
|
|
315
|
+
stopTrack
|
|
316
|
+
? transceiver.sender.track.stop()
|
|
317
|
+
: (transceiver.sender.track.enabled = false);
|
|
312
318
|
return this.notifyTrackMuteStateChanged(
|
|
313
319
|
undefined,
|
|
314
320
|
transceiver.sender.track,
|
|
@@ -139,7 +139,7 @@ describe('Publisher', () => {
|
|
|
139
139
|
expect(state.localParticipant?.videoDeviceId).toEqual('test-device-id-2');
|
|
140
140
|
|
|
141
141
|
// stop publishing
|
|
142
|
-
await publisher.unpublishStream(TrackType.VIDEO);
|
|
142
|
+
await publisher.unpublishStream(TrackType.VIDEO, true);
|
|
143
143
|
expect(newTrack.stop).toHaveBeenCalled();
|
|
144
144
|
expect(state.localParticipant?.publishedTracks).not.toContain(
|
|
145
145
|
TrackType.VIDEO,
|
|
@@ -147,6 +147,72 @@ describe('Publisher', () => {
|
|
|
147
147
|
expect(state.localParticipant?.videoDeviceId).toEqual('test-device-id-2');
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
it('can publish and un-pubish with just enabling and disabling tracks', async () => {
|
|
151
|
+
const mediaStream = new MediaStream();
|
|
152
|
+
const track = new MediaStreamTrack();
|
|
153
|
+
mediaStream.addTrack(track);
|
|
154
|
+
|
|
155
|
+
state.setParticipants([
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
{
|
|
158
|
+
isLocalParticipant: true,
|
|
159
|
+
userId: 'test-user-id',
|
|
160
|
+
sessionId: sessionId,
|
|
161
|
+
publishedTracks: [],
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
vi.spyOn(track, 'getSettings').mockReturnValue({
|
|
166
|
+
width: 640,
|
|
167
|
+
height: 480,
|
|
168
|
+
deviceId: 'test-device-id',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const transceiver = new RTCRtpTransceiver();
|
|
172
|
+
vi.spyOn(transceiver.sender, 'track', 'get').mockReturnValue(track);
|
|
173
|
+
vi.spyOn(publisher['pc'], 'addTransceiver').mockReturnValue(transceiver);
|
|
174
|
+
vi.spyOn(publisher['pc'], 'getTransceivers').mockReturnValue([transceiver]);
|
|
175
|
+
|
|
176
|
+
sfuClient.updateMuteState = vi.fn();
|
|
177
|
+
|
|
178
|
+
// initial publish
|
|
179
|
+
await publisher.publishStream(mediaStream, track, TrackType.VIDEO);
|
|
180
|
+
|
|
181
|
+
expect(state.localParticipant?.videoDeviceId).toEqual('test-device-id');
|
|
182
|
+
expect(state.localParticipant?.publishedTracks).toContain(TrackType.VIDEO);
|
|
183
|
+
expect(state.localParticipant?.videoStream).toEqual(mediaStream);
|
|
184
|
+
expect(transceiver.setCodecPreferences).toHaveBeenCalled();
|
|
185
|
+
expect(sfuClient.updateMuteState).toHaveBeenCalledWith(
|
|
186
|
+
TrackType.VIDEO,
|
|
187
|
+
false,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(track.addEventListener).toHaveBeenCalledWith(
|
|
191
|
+
'ended',
|
|
192
|
+
expect.any(Function),
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// stop publishing
|
|
196
|
+
await publisher.unpublishStream(TrackType.VIDEO, false);
|
|
197
|
+
expect(track.stop).not.toHaveBeenCalled();
|
|
198
|
+
expect(track.enabled).toBe(false);
|
|
199
|
+
expect(state.localParticipant?.publishedTracks).not.toContain(
|
|
200
|
+
TrackType.VIDEO,
|
|
201
|
+
);
|
|
202
|
+
expect(state.localParticipant?.videoStream).toBeUndefined();
|
|
203
|
+
|
|
204
|
+
const addEventListenerSpy = vi.spyOn(track, 'addEventListener');
|
|
205
|
+
const removeEventListenerSpy = vi.spyOn(track, 'removeEventListener');
|
|
206
|
+
|
|
207
|
+
// start publish again
|
|
208
|
+
await publisher.publishStream(mediaStream, track, TrackType.VIDEO);
|
|
209
|
+
|
|
210
|
+
expect(track.enabled).toBe(true);
|
|
211
|
+
// republishing the same stream should use the previously registered event handlers
|
|
212
|
+
expect(removeEventListenerSpy).not.toHaveBeenCalled();
|
|
213
|
+
expect(addEventListenerSpy).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
|
|
150
216
|
describe('Publisher migration', () => {
|
|
151
217
|
it('should update the sfuClient and peer connection configuration', async () => {
|
|
152
218
|
const newSfuClient = new StreamSfuClient({
|