@stream-io/video-client 0.3.2 → 0.3.3
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 +7 -0
- package/dist/index.browser.es.js +67 -52
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +67 -52
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +67 -52
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +2 -1
- 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/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
package/dist/src/Call.d.ts
CHANGED
|
@@ -217,8 +217,9 @@ export declare class Call {
|
|
|
217
217
|
*
|
|
218
218
|
*
|
|
219
219
|
* @param trackType the track type to stop publishing.
|
|
220
|
+
* @param stopTrack if `true` the track will be stopped, else it will be just disabled
|
|
220
221
|
*/
|
|
221
|
-
stopPublish: (trackType: TrackType) => Promise<void>;
|
|
222
|
+
stopPublish: (trackType: TrackType, stopTrack?: boolean) => Promise<void>;
|
|
222
223
|
/**
|
|
223
224
|
* Update track subscription configuration for one or more participants.
|
|
224
225
|
* You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
|
|
@@ -19,13 +19,7 @@ export declare class CameraManager extends InputMediaDeviceManager<CameraManager
|
|
|
19
19
|
protected getDevices(): Observable<MediaDeviceInfo[]>;
|
|
20
20
|
protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
|
|
21
21
|
protected publishStream(stream: MediaStream): Promise<void>;
|
|
22
|
-
protected stopPublishStream(): Promise<void>;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*/
|
|
26
|
-
pause(): void;
|
|
27
|
-
/**
|
|
28
|
-
* (Re)enables the video tracks of the camera
|
|
29
|
-
*/
|
|
30
|
-
resume(): void;
|
|
22
|
+
protected stopPublishStream(stopTracks: boolean): Promise<void>;
|
|
23
|
+
protected muteTracks(): void;
|
|
24
|
+
protected unmuteTracks(): void;
|
|
31
25
|
}
|
|
@@ -19,11 +19,17 @@ export declare abstract class InputMediaDeviceManager<T extends InputMediaDevice
|
|
|
19
19
|
enable(): Promise<void>;
|
|
20
20
|
/**
|
|
21
21
|
* Stops camera/microphone
|
|
22
|
+
*
|
|
22
23
|
* @returns
|
|
23
24
|
*/
|
|
24
25
|
disable(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* If status was previously enabled, it will reenable the device.
|
|
28
|
+
*/
|
|
29
|
+
resume(): Promise<void>;
|
|
25
30
|
/**
|
|
26
31
|
* If current device statis is disabled, it will enable the device, else it will disable it.
|
|
32
|
+
*
|
|
27
33
|
* @returns
|
|
28
34
|
*/
|
|
29
35
|
toggle(): Promise<void>;
|
|
@@ -36,12 +42,12 @@ export declare abstract class InputMediaDeviceManager<T extends InputMediaDevice
|
|
|
36
42
|
*/
|
|
37
43
|
select(deviceId: string | undefined): Promise<void>;
|
|
38
44
|
protected applySettingsToStream(): Promise<void>;
|
|
39
|
-
abstract pause(): void;
|
|
40
|
-
abstract resume(): void;
|
|
41
45
|
protected abstract getDevices(): Observable<MediaDeviceInfo[]>;
|
|
42
46
|
protected abstract getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
|
|
43
47
|
protected abstract publishStream(stream: MediaStream): Promise<void>;
|
|
44
|
-
protected abstract stopPublishStream(): Promise<void>;
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
protected abstract stopPublishStream(stopTracks: boolean): Promise<void>;
|
|
49
|
+
protected abstract muteTracks(): void;
|
|
50
|
+
protected abstract unmuteTracks(): void;
|
|
51
|
+
private muteStream;
|
|
52
|
+
private unmuteStream;
|
|
47
53
|
}
|
|
@@ -2,9 +2,14 @@ import { BehaviorSubject, Observable } from 'rxjs';
|
|
|
2
2
|
import { RxUtils } from '../store';
|
|
3
3
|
export type InputDeviceStatus = 'enabled' | 'disabled' | undefined;
|
|
4
4
|
export declare abstract class InputMediaDeviceManagerState {
|
|
5
|
+
readonly disableMode: 'stop-tracks' | 'disable-tracks';
|
|
5
6
|
protected statusSubject: BehaviorSubject<InputDeviceStatus>;
|
|
6
7
|
protected mediaStreamSubject: BehaviorSubject<MediaStream | undefined>;
|
|
7
8
|
protected selectedDeviceSubject: BehaviorSubject<string | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
prevStatus: InputDeviceStatus;
|
|
8
13
|
/**
|
|
9
14
|
* An Observable that emits the current media stream, or `undefined` if the device is currently disabled.
|
|
10
15
|
*
|
|
@@ -18,7 +23,7 @@ export declare abstract class InputMediaDeviceManagerState {
|
|
|
18
23
|
* An Observable that emits the device status
|
|
19
24
|
*/
|
|
20
25
|
status$: Observable<InputDeviceStatus>;
|
|
21
|
-
constructor();
|
|
26
|
+
constructor(disableMode?: 'stop-tracks' | 'disable-tracks');
|
|
22
27
|
/**
|
|
23
28
|
* The device status
|
|
24
29
|
*/
|
|
@@ -7,13 +7,7 @@ export declare class MicrophoneManager extends InputMediaDeviceManager<Microphon
|
|
|
7
7
|
protected getDevices(): Observable<MediaDeviceInfo[]>;
|
|
8
8
|
protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
|
|
9
9
|
protected publishStream(stream: MediaStream): Promise<void>;
|
|
10
|
-
protected stopPublishStream(): Promise<void>;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
pause(): void;
|
|
15
|
-
/**
|
|
16
|
-
* (Re)enables the audio tracks of the microphone
|
|
17
|
-
*/
|
|
18
|
-
resume(): void;
|
|
10
|
+
protected stopPublishStream(stopTracks: boolean): Promise<void>;
|
|
11
|
+
protected muteTracks(): void;
|
|
12
|
+
protected unmuteTracks(): void;
|
|
19
13
|
}
|
|
@@ -76,8 +76,9 @@ export declare class Publisher {
|
|
|
76
76
|
* Stops publishing the given track type to the SFU, if it is currently being published.
|
|
77
77
|
* Underlying track will be stopped and removed from the publisher.
|
|
78
78
|
* @param trackType the track type to unpublish.
|
|
79
|
+
* @param stopTrack specifies whether track should be stopped or just disabled
|
|
79
80
|
*/
|
|
80
|
-
unpublishStream: (trackType: TrackType) => Promise<void>;
|
|
81
|
+
unpublishStream: (trackType: TrackType, stopTrack: boolean) => Promise<void>;
|
|
81
82
|
/**
|
|
82
83
|
* Returns true if the given track type is currently being published to the SFU.
|
|
83
84
|
*
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.3.
|
|
1
|
+
export declare const version = "0.3.3";
|
package/package.json
CHANGED
package/src/Call.ts
CHANGED
|
@@ -1092,10 +1092,11 @@ export class Call {
|
|
|
1092
1092
|
*
|
|
1093
1093
|
*
|
|
1094
1094
|
* @param trackType the track type to stop publishing.
|
|
1095
|
+
* @param stopTrack if `true` the track will be stopped, else it will be just disabled
|
|
1095
1096
|
*/
|
|
1096
|
-
stopPublish = async (trackType: TrackType) => {
|
|
1097
|
+
stopPublish = async (trackType: TrackType, stopTrack: boolean = true) => {
|
|
1097
1098
|
this.logger('info', `stopPublish ${TrackType[trackType]}`);
|
|
1098
|
-
await this.publisher?.unpublishStream(trackType);
|
|
1099
|
+
await this.publisher?.unpublishStream(trackType, stopTrack);
|
|
1099
1100
|
};
|
|
1100
1101
|
|
|
1101
1102
|
/**
|
|
@@ -49,25 +49,16 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
|
|
|
49
49
|
protected publishStream(stream: MediaStream): Promise<void> {
|
|
50
50
|
return this.call.publishVideoStream(stream);
|
|
51
51
|
}
|
|
52
|
-
protected stopPublishStream(): Promise<void> {
|
|
53
|
-
return this.call.stopPublish(TrackType.VIDEO);
|
|
52
|
+
protected stopPublishStream(stopTracks: boolean): Promise<void> {
|
|
53
|
+
return this.call.stopPublish(TrackType.VIDEO, stopTracks);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.state.mediaStream?.getVideoTracks().forEach((track) => {
|
|
61
|
-
track.enabled = false;
|
|
62
|
-
});
|
|
56
|
+
protected muteTracks(): void {
|
|
57
|
+
this.state.mediaStream
|
|
58
|
+
?.getVideoTracks()
|
|
59
|
+
.forEach((t) => (t.enabled = false));
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* (Re)enables the video tracks of the camera
|
|
67
|
-
*/
|
|
68
|
-
resume() {
|
|
69
|
-
this.state.mediaStream?.getVideoTracks().forEach((track) => {
|
|
70
|
-
track.enabled = true;
|
|
71
|
-
});
|
|
61
|
+
protected unmuteTracks(): void {
|
|
62
|
+
this.state.mediaStream?.getVideoTracks().forEach((t) => (t.enabled = true));
|
|
72
63
|
}
|
|
73
64
|
}
|
|
@@ -15,7 +15,7 @@ export class CameraManagerState extends InputMediaDeviceManagerState {
|
|
|
15
15
|
direction$: Observable<CameraDirection>;
|
|
16
16
|
|
|
17
17
|
constructor() {
|
|
18
|
-
super();
|
|
18
|
+
super('stop-tracks');
|
|
19
19
|
this.direction$ = this.directionSubject
|
|
20
20
|
.asObservable()
|
|
21
21
|
.pipe(distinctUntilChanged());
|
|
@@ -28,24 +28,39 @@ export abstract class InputMediaDeviceManager<
|
|
|
28
28
|
if (this.state.status === 'enabled') {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
await this.
|
|
31
|
+
await this.unmuteStream();
|
|
32
32
|
this.state.setStatus('enabled');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Stops camera/microphone
|
|
37
|
+
*
|
|
37
38
|
* @returns
|
|
38
39
|
*/
|
|
39
40
|
async disable() {
|
|
40
41
|
if (this.state.status === 'disabled') {
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
this.state.prevStatus = this.state.status;
|
|
45
|
+
await this.muteStream(this.state.disableMode === 'stop-tracks');
|
|
44
46
|
this.state.setStatus('disabled');
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
/**
|
|
50
|
+
* If status was previously enabled, it will reenable the device.
|
|
51
|
+
*/
|
|
52
|
+
async resume() {
|
|
53
|
+
if (
|
|
54
|
+
this.state.prevStatus === 'enabled' &&
|
|
55
|
+
this.state.status === 'disabled'
|
|
56
|
+
) {
|
|
57
|
+
this.enable();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
/**
|
|
48
62
|
* If current device statis is disabled, it will enable the device, else it will disable it.
|
|
63
|
+
*
|
|
49
64
|
* @returns
|
|
50
65
|
*/
|
|
51
66
|
async toggle() {
|
|
@@ -76,15 +91,11 @@ export abstract class InputMediaDeviceManager<
|
|
|
76
91
|
|
|
77
92
|
protected async applySettingsToStream() {
|
|
78
93
|
if (this.state.status === 'enabled') {
|
|
79
|
-
await this.
|
|
80
|
-
await this.
|
|
94
|
+
await this.muteStream();
|
|
95
|
+
await this.unmuteStream();
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
abstract pause(): void;
|
|
85
|
-
|
|
86
|
-
abstract resume(): void;
|
|
87
|
-
|
|
88
99
|
protected abstract getDevices(): Observable<MediaDeviceInfo[]>;
|
|
89
100
|
|
|
90
101
|
protected abstract getStream(
|
|
@@ -93,26 +104,37 @@ export abstract class InputMediaDeviceManager<
|
|
|
93
104
|
|
|
94
105
|
protected abstract publishStream(stream: MediaStream): Promise<void>;
|
|
95
106
|
|
|
96
|
-
protected abstract stopPublishStream(): Promise<void>;
|
|
107
|
+
protected abstract stopPublishStream(stopTracks: boolean): Promise<void>;
|
|
108
|
+
|
|
109
|
+
protected abstract muteTracks(): void;
|
|
110
|
+
|
|
111
|
+
protected abstract unmuteTracks(): void;
|
|
97
112
|
|
|
98
|
-
private async
|
|
113
|
+
private async muteStream(stopTracks: boolean = true) {
|
|
99
114
|
if (!this.state.mediaStream) {
|
|
100
115
|
return;
|
|
101
116
|
}
|
|
102
117
|
if (this.call.state.callingState === CallingState.JOINED) {
|
|
103
|
-
await this.stopPublishStream();
|
|
118
|
+
await this.stopPublishStream(stopTracks);
|
|
104
119
|
} else if (this.state.mediaStream) {
|
|
105
|
-
|
|
120
|
+
stopTracks
|
|
121
|
+
? disposeOfMediaStream(this.state.mediaStream)
|
|
122
|
+
: this.muteTracks();
|
|
123
|
+
}
|
|
124
|
+
if (stopTracks) {
|
|
125
|
+
this.state.setMediaStream(undefined);
|
|
106
126
|
}
|
|
107
|
-
this.state.setMediaStream(undefined);
|
|
108
127
|
}
|
|
109
128
|
|
|
110
|
-
private async
|
|
129
|
+
private async unmuteStream() {
|
|
130
|
+
let stream: MediaStream;
|
|
111
131
|
if (this.state.mediaStream) {
|
|
112
|
-
|
|
132
|
+
stream = this.state.mediaStream;
|
|
133
|
+
this.unmuteTracks();
|
|
134
|
+
} else {
|
|
135
|
+
const constraints = { deviceId: this.state.selectedDevice };
|
|
136
|
+
stream = await this.getStream(constraints);
|
|
113
137
|
}
|
|
114
|
-
const constraints = { deviceId: this.state.selectedDevice };
|
|
115
|
-
const stream = await this.getStream(constraints);
|
|
116
138
|
if (this.call.state.callingState === CallingState.JOINED) {
|
|
117
139
|
await this.publishStream(stream);
|
|
118
140
|
}
|
|
@@ -11,6 +11,10 @@ export abstract class InputMediaDeviceManagerState {
|
|
|
11
11
|
protected selectedDeviceSubject = new BehaviorSubject<string | undefined>(
|
|
12
12
|
undefined,
|
|
13
13
|
);
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
prevStatus: InputDeviceStatus;
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* An Observable that emits the current media stream, or `undefined` if the device is currently disabled.
|
|
@@ -28,12 +32,18 @@ export abstract class InputMediaDeviceManagerState {
|
|
|
28
32
|
*/
|
|
29
33
|
status$: Observable<InputDeviceStatus>;
|
|
30
34
|
|
|
31
|
-
constructor(
|
|
35
|
+
constructor(
|
|
36
|
+
public readonly disableMode:
|
|
37
|
+
| 'stop-tracks'
|
|
38
|
+
| 'disable-tracks' = 'stop-tracks',
|
|
39
|
+
) {
|
|
32
40
|
this.mediaStream$ = this.mediaStreamSubject.asObservable();
|
|
33
41
|
this.selectedDevice$ = this.selectedDeviceSubject
|
|
34
42
|
.asObservable()
|
|
35
43
|
.pipe(distinctUntilChanged());
|
|
36
|
-
this.status$ = this.statusSubject
|
|
44
|
+
this.status$ = this.statusSubject
|
|
45
|
+
.asObservable()
|
|
46
|
+
.pipe(distinctUntilChanged());
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
@@ -21,25 +21,16 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
21
21
|
protected publishStream(stream: MediaStream): Promise<void> {
|
|
22
22
|
return this.call.publishAudioStream(stream);
|
|
23
23
|
}
|
|
24
|
-
protected stopPublishStream(): Promise<void> {
|
|
25
|
-
return this.call.stopPublish(TrackType.AUDIO);
|
|
24
|
+
protected stopPublishStream(stopTracks: boolean): Promise<void> {
|
|
25
|
+
return this.call.stopPublish(TrackType.AUDIO, stopTracks);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.state.mediaStream?.getAudioTracks().forEach((track) => {
|
|
33
|
-
track.enabled = false;
|
|
34
|
-
});
|
|
28
|
+
protected muteTracks(): void {
|
|
29
|
+
this.state.mediaStream
|
|
30
|
+
?.getAudioTracks()
|
|
31
|
+
.forEach((t) => (t.enabled = false));
|
|
35
32
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* (Re)enables the audio tracks of the microphone
|
|
39
|
-
*/
|
|
40
|
-
resume() {
|
|
41
|
-
this.state.mediaStream?.getAudioTracks().forEach((track) => {
|
|
42
|
-
track.enabled = true;
|
|
43
|
-
});
|
|
33
|
+
protected unmuteTracks(): void {
|
|
34
|
+
this.state.mediaStream?.getAudioTracks().forEach((t) => (t.enabled = true));
|
|
44
35
|
}
|
|
45
36
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
|
|
2
2
|
|
|
3
3
|
export class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
4
|
+
constructor() {
|
|
5
|
+
super('disable-tracks');
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
protected getDeviceIdFromStream(stream: MediaStream): string | undefined {
|
|
5
9
|
return stream.getAudioTracks()[0]?.getSettings().deviceId as
|
|
6
10
|
| string
|
|
@@ -8,7 +8,6 @@ import { getVideoStream } from '../devices';
|
|
|
8
8
|
import { TrackType } from '../../gen/video/sfu/models/models';
|
|
9
9
|
import { CameraManager } from '../CameraManager';
|
|
10
10
|
import { of } from 'rxjs';
|
|
11
|
-
import { CallSettingsResponse } from '../../gen/coordinator';
|
|
12
11
|
|
|
13
12
|
vi.mock('../devices.ts', () => {
|
|
14
13
|
console.log('MOCKING devices API');
|
|
@@ -83,7 +82,10 @@ describe('CameraManager', () => {
|
|
|
83
82
|
|
|
84
83
|
await manager.disable();
|
|
85
84
|
|
|
86
|
-
expect(manager['call'].stopPublish).toHaveBeenCalledWith(
|
|
85
|
+
expect(manager['call'].stopPublish).toHaveBeenCalledWith(
|
|
86
|
+
TrackType.VIDEO,
|
|
87
|
+
true,
|
|
88
|
+
);
|
|
87
89
|
});
|
|
88
90
|
|
|
89
91
|
it('flip', async () => {
|
|
@@ -131,18 +133,6 @@ describe('CameraManager', () => {
|
|
|
131
133
|
});
|
|
132
134
|
});
|
|
133
135
|
|
|
134
|
-
it('should pause and resume tracks', async () => {
|
|
135
|
-
await manager.enable();
|
|
136
|
-
|
|
137
|
-
manager.pause();
|
|
138
|
-
|
|
139
|
-
expect(manager.state.mediaStream?.getVideoTracks()[0].enabled).toBe(false);
|
|
140
|
-
|
|
141
|
-
manager.resume();
|
|
142
|
-
|
|
143
|
-
expect(manager.state.mediaStream?.getVideoTracks()[0].enabled).toBe(true);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
136
|
afterEach(() => {
|
|
147
137
|
vi.clearAllMocks();
|
|
148
138
|
vi.resetModules();
|
|
@@ -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({
|