@stream-io/video-client 0.3.15 → 0.3.17
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 +528 -466
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +529 -465
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +528 -466
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +9 -8
- package/dist/src/coordinator/connection/types.d.ts +0 -4
- package/dist/src/devices/InputMediaDeviceManager.d.ts +2 -2
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +2 -2
- package/dist/src/devices/MicrophoneManager.d.ts +3 -0
- package/dist/src/devices/MicrophoneManagerState.d.ts +18 -0
- package/dist/src/devices/SpeakerManager.d.ts +0 -1
- package/dist/src/devices/__tests__/mocks.d.ts +2 -4
- package/dist/src/devices/index.d.ts +2 -0
- package/dist/src/events/__tests__/mutes.test.d.ts +1 -0
- package/dist/src/events/index.d.ts +1 -0
- package/dist/src/events/mutes.d.ts +7 -0
- package/dist/src/rtc/Publisher.d.ts +2 -21
- package/dist/version.d.ts +1 -1
- package/package.json +5 -5
- package/src/Call.ts +17 -50
- package/src/coordinator/connection/types.ts +0 -4
- package/src/devices/CameraManager.ts +1 -1
- package/src/devices/InputMediaDeviceManager.ts +7 -5
- package/src/devices/InputMediaDeviceManagerState.ts +3 -3
- package/src/devices/MicrophoneManager.ts +56 -1
- package/src/devices/MicrophoneManagerState.ts +30 -0
- package/src/devices/SpeakerManager.ts +0 -2
- package/src/devices/__tests__/CameraManager.test.ts +3 -5
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +4 -7
- package/src/devices/__tests__/MicrophoneManager.test.ts +77 -6
- package/src/devices/__tests__/mocks.ts +14 -5
- package/src/devices/index.ts +2 -0
- package/src/events/__tests__/mutes.test.ts +133 -0
- package/src/events/callEventHandlers.ts +3 -0
- package/src/events/index.ts +1 -0
- package/src/events/mutes.ts +48 -0
- package/src/helpers/sound-detector.ts +7 -1
- package/src/rtc/Publisher.ts +2 -28
|
@@ -2,7 +2,7 @@ import { Call } from '../../Call';
|
|
|
2
2
|
import { StreamClient } from '../../coordinator/connection/client';
|
|
3
3
|
import { CallingState, StreamVideoWriteableStateStore } from '../../store';
|
|
4
4
|
|
|
5
|
-
import { afterEach, beforeEach, describe,
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
import { mockCall, mockVideoDevices, mockVideoStream } from './mocks';
|
|
7
7
|
import { InputMediaDeviceManager } from '../InputMediaDeviceManager';
|
|
8
8
|
import { InputMediaDeviceManagerState } from '../InputMediaDeviceManagerState';
|
|
@@ -67,8 +67,7 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
it('enable device - after joined to call', async () => {
|
|
70
|
-
|
|
71
|
-
manager['call'].state.callingState = CallingState.JOINED;
|
|
70
|
+
manager['call'].state.setCallingState(CallingState.JOINED);
|
|
72
71
|
|
|
73
72
|
await manager.enable();
|
|
74
73
|
|
|
@@ -93,8 +92,7 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
93
92
|
});
|
|
94
93
|
|
|
95
94
|
it('disable device - after joined to call', async () => {
|
|
96
|
-
|
|
97
|
-
manager['call'].state.callingState = CallingState.JOINED;
|
|
95
|
+
manager['call'].state.setCallingState(CallingState.JOINED);
|
|
98
96
|
await manager.enable();
|
|
99
97
|
|
|
100
98
|
await manager.disable();
|
|
@@ -136,8 +134,7 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
136
134
|
});
|
|
137
135
|
|
|
138
136
|
it('select device when status is enabled and in call', async () => {
|
|
139
|
-
|
|
140
|
-
manager['call'].state.callingState = CallingState.JOINED;
|
|
137
|
+
manager['call'].state.setCallingState(CallingState.JOINED);
|
|
141
138
|
await manager.enable();
|
|
142
139
|
|
|
143
140
|
const deviceId = mockVideoDevices[1].deviceId;
|
|
@@ -2,12 +2,17 @@ import { Call } from '../../Call';
|
|
|
2
2
|
import { StreamClient } from '../../coordinator/connection/client';
|
|
3
3
|
import { CallingState, StreamVideoWriteableStateStore } from '../../store';
|
|
4
4
|
|
|
5
|
-
import { afterEach, beforeEach, describe,
|
|
6
|
-
import {
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
|
|
6
|
+
import { mockAudioDevices, mockAudioStream, mockCall } from './mocks';
|
|
7
7
|
import { getAudioStream } from '../devices';
|
|
8
8
|
import { TrackType } from '../../gen/video/sfu/models/models';
|
|
9
9
|
import { MicrophoneManager } from '../MicrophoneManager';
|
|
10
10
|
import { of } from 'rxjs';
|
|
11
|
+
import {
|
|
12
|
+
createSoundDetector,
|
|
13
|
+
SoundStateChangeHandler,
|
|
14
|
+
} from '../../helpers/sound-detector';
|
|
15
|
+
import { OwnCapability } from '../../gen/coordinator';
|
|
11
16
|
|
|
12
17
|
vi.mock('../devices.ts', () => {
|
|
13
18
|
console.log('MOCKING devices API');
|
|
@@ -20,6 +25,13 @@ vi.mock('../devices.ts', () => {
|
|
|
20
25
|
};
|
|
21
26
|
});
|
|
22
27
|
|
|
28
|
+
vi.mock('../../helpers/sound-detector.ts', () => {
|
|
29
|
+
console.log('MOCKING sound detector');
|
|
30
|
+
return {
|
|
31
|
+
createSoundDetector: vi.fn(),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
23
35
|
vi.mock('../../Call.ts', () => {
|
|
24
36
|
console.log('MOCKING Call');
|
|
25
37
|
return {
|
|
@@ -64,8 +76,7 @@ describe('MicrophoneManager', () => {
|
|
|
64
76
|
});
|
|
65
77
|
|
|
66
78
|
it('publish stream', async () => {
|
|
67
|
-
|
|
68
|
-
manager['call'].state.callingState = CallingState.JOINED;
|
|
79
|
+
manager['call'].state.setCallingState(CallingState.JOINED);
|
|
69
80
|
|
|
70
81
|
await manager.enable();
|
|
71
82
|
|
|
@@ -75,8 +86,7 @@ describe('MicrophoneManager', () => {
|
|
|
75
86
|
});
|
|
76
87
|
|
|
77
88
|
it('stop publish stream', async () => {
|
|
78
|
-
|
|
79
|
-
manager['call'].state.callingState = CallingState.JOINED;
|
|
89
|
+
manager['call'].state.setCallingState(CallingState.JOINED);
|
|
80
90
|
await manager.enable();
|
|
81
91
|
|
|
82
92
|
await manager.disable();
|
|
@@ -97,6 +107,67 @@ describe('MicrophoneManager', () => {
|
|
|
97
107
|
expect(manager.state.mediaStream!.getAudioTracks()[0].enabled).toBe(false);
|
|
98
108
|
});
|
|
99
109
|
|
|
110
|
+
it(`should start sound detection if mic is disabled`, async () => {
|
|
111
|
+
await manager.enable();
|
|
112
|
+
// @ts-expect-error
|
|
113
|
+
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
114
|
+
await manager.disable();
|
|
115
|
+
|
|
116
|
+
expect(manager['startSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it(`should stop sound detection if mic is enabled`, async () => {
|
|
120
|
+
manager.state.setSpeakingWhileMuted(true);
|
|
121
|
+
manager['soundDetectorCleanup'] = () => {};
|
|
122
|
+
|
|
123
|
+
await manager.enable();
|
|
124
|
+
|
|
125
|
+
expect(manager.state.speakingWhileMuted).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should update speaking while muted state', async () => {
|
|
129
|
+
const mock = createSoundDetector as Mock;
|
|
130
|
+
let handler: SoundStateChangeHandler;
|
|
131
|
+
mock.mockImplementation((_: MediaStream, h: SoundStateChangeHandler) => {
|
|
132
|
+
handler = h;
|
|
133
|
+
});
|
|
134
|
+
await manager['startSpeakingWhileMutedDetection']();
|
|
135
|
+
|
|
136
|
+
expect(manager.state.speakingWhileMuted).toBe(false);
|
|
137
|
+
|
|
138
|
+
handler!({ isSoundDetected: true, audioLevel: 2 });
|
|
139
|
+
|
|
140
|
+
expect(manager.state.speakingWhileMuted).toBe(true);
|
|
141
|
+
|
|
142
|
+
handler!({ isSoundDetected: false, audioLevel: 0 });
|
|
143
|
+
|
|
144
|
+
expect(manager.state.speakingWhileMuted).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should stop speaking while muted notifications if user loses permission to send audio', async () => {
|
|
148
|
+
await manager.enable();
|
|
149
|
+
await manager.disable();
|
|
150
|
+
|
|
151
|
+
// @ts-expect-error
|
|
152
|
+
vi.spyOn(manager, 'stopSpeakingWhileMutedDetection');
|
|
153
|
+
manager['call'].state.setOwnCapabilities([]);
|
|
154
|
+
|
|
155
|
+
expect(manager['stopSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should start speaking while muted notifications if user gains permission to send audio', async () => {
|
|
159
|
+
await manager.enable();
|
|
160
|
+
await manager.disable();
|
|
161
|
+
|
|
162
|
+
manager['call'].state.setOwnCapabilities([]);
|
|
163
|
+
|
|
164
|
+
// @ts-expect-error
|
|
165
|
+
vi.spyOn(manager, 'stopSpeakingWhileMutedDetection');
|
|
166
|
+
manager['call'].state.setOwnCapabilities([OwnCapability.SEND_AUDIO]);
|
|
167
|
+
|
|
168
|
+
expect(manager['stopSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
169
|
+
});
|
|
170
|
+
|
|
100
171
|
afterEach(() => {
|
|
101
172
|
vi.clearAllMocks();
|
|
102
173
|
vi.resetModules();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { vi } from 'vitest';
|
|
2
|
-
import { CallingState } from '../../store';
|
|
2
|
+
import { CallingState, CallState } from '../../store';
|
|
3
|
+
import { OwnCapability } from '../../gen/coordinator';
|
|
3
4
|
|
|
4
5
|
export const mockVideoDevices = [
|
|
5
6
|
{
|
|
@@ -63,10 +64,14 @@ export const mockAudioDevices = [
|
|
|
63
64
|
] as MediaDeviceInfo[];
|
|
64
65
|
|
|
65
66
|
export const mockCall = () => {
|
|
67
|
+
const callState = new CallState();
|
|
68
|
+
callState.setCallingState(CallingState.JOINED);
|
|
69
|
+
callState.setOwnCapabilities([
|
|
70
|
+
OwnCapability.SEND_AUDIO,
|
|
71
|
+
OwnCapability.SEND_VIDEO,
|
|
72
|
+
]);
|
|
66
73
|
return {
|
|
67
|
-
state:
|
|
68
|
-
callingState: CallingState.IDLE,
|
|
69
|
-
},
|
|
74
|
+
state: callState,
|
|
70
75
|
publishVideoStream: vi.fn(),
|
|
71
76
|
publishAudioStream: vi.fn(),
|
|
72
77
|
stopPublish: vi.fn(),
|
|
@@ -79,6 +84,10 @@ export const mockAudioStream = () => {
|
|
|
79
84
|
deviceId: mockAudioDevices[0].deviceId,
|
|
80
85
|
}),
|
|
81
86
|
enabled: true,
|
|
87
|
+
readyState: 'live',
|
|
88
|
+
stop: () => {
|
|
89
|
+
track.readyState = 'ended';
|
|
90
|
+
},
|
|
82
91
|
};
|
|
83
92
|
return {
|
|
84
93
|
getAudioTracks: () => [track],
|
|
@@ -95,7 +104,7 @@ export const mockVideoStream = () => {
|
|
|
95
104
|
enabled: true,
|
|
96
105
|
readyState: 'live',
|
|
97
106
|
stop: () => {
|
|
98
|
-
track.readyState = '
|
|
107
|
+
track.readyState = 'ended';
|
|
99
108
|
},
|
|
100
109
|
};
|
|
101
110
|
return {
|
package/src/devices/index.ts
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Call } from '../../Call';
|
|
3
|
+
import {
|
|
4
|
+
TrackType,
|
|
5
|
+
TrackUnpublishReason,
|
|
6
|
+
} from '../../gen/video/sfu/models/models';
|
|
7
|
+
import { SfuEvent } from '../../gen/video/sfu/event/events';
|
|
8
|
+
import { StreamClient } from '../../coordinator/connection/client';
|
|
9
|
+
import { handleRemoteSoftMute } from '../mutes';
|
|
10
|
+
import { SfuEventListener } from '../../rtc';
|
|
11
|
+
|
|
12
|
+
describe('mutes', () => {
|
|
13
|
+
describe('soft mute', () => {
|
|
14
|
+
let handler: SfuEventListener;
|
|
15
|
+
let call: Call;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// @ts-expect-error
|
|
19
|
+
call = new Call({
|
|
20
|
+
type: 'test',
|
|
21
|
+
id: 'test',
|
|
22
|
+
streamClient: new StreamClient('api-key'),
|
|
23
|
+
});
|
|
24
|
+
// disable all event handlers
|
|
25
|
+
call['dispatcher'].offAll();
|
|
26
|
+
|
|
27
|
+
// @ts-expect-error partial data
|
|
28
|
+
call.publisher = vi.fn();
|
|
29
|
+
// @ts-expect-error partial data
|
|
30
|
+
call.publisher.isPublishing = vi.fn().mockReturnValue(true);
|
|
31
|
+
|
|
32
|
+
vi.spyOn(call, 'stopPublish').mockResolvedValue(undefined);
|
|
33
|
+
vi.spyOn(call.camera, 'disable').mockResolvedValue(undefined);
|
|
34
|
+
vi.spyOn(call.microphone, 'disable').mockResolvedValue(undefined);
|
|
35
|
+
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
call.on = (event: string, h) => {
|
|
38
|
+
if (event === 'trackUnpublished') {
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
handler = h;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
handleRemoteSoftMute(call);
|
|
45
|
+
|
|
46
|
+
// @ts-expect-error partial data
|
|
47
|
+
call.state.updateOrAddParticipant('session-id', {
|
|
48
|
+
userId: 'user-id',
|
|
49
|
+
sessionId: 'session-id',
|
|
50
|
+
isLocalParticipant: true,
|
|
51
|
+
publishedTracks: [
|
|
52
|
+
TrackType.VIDEO,
|
|
53
|
+
TrackType.AUDIO,
|
|
54
|
+
TrackType.SCREEN_SHARE,
|
|
55
|
+
TrackType.SCREEN_SHARE_AUDIO,
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should automatically mute only when cause is moderation', async () => {
|
|
61
|
+
const event: SfuEvent = {
|
|
62
|
+
eventPayload: {
|
|
63
|
+
oneofKind: 'trackUnpublished',
|
|
64
|
+
trackUnpublished: {
|
|
65
|
+
cause: TrackUnpublishReason.PERMISSION_REVOKED,
|
|
66
|
+
type: TrackType.VIDEO,
|
|
67
|
+
sessionId: 'session-id',
|
|
68
|
+
userId: 'user-id',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await handler!(event);
|
|
74
|
+
expect(call.camera.disable).not.toHaveBeenCalled();
|
|
75
|
+
expect(call.stopPublish).not.toHaveBeenCalledWith(TrackType.VIDEO);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle remote soft video mute', async () => {
|
|
79
|
+
const event: SfuEvent = {
|
|
80
|
+
eventPayload: {
|
|
81
|
+
oneofKind: 'trackUnpublished',
|
|
82
|
+
trackUnpublished: {
|
|
83
|
+
cause: TrackUnpublishReason.MODERATION,
|
|
84
|
+
type: TrackType.VIDEO,
|
|
85
|
+
sessionId: 'session-id',
|
|
86
|
+
userId: 'user-id',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
await handler!(event);
|
|
92
|
+
expect(call.camera.disable).toHaveBeenCalled();
|
|
93
|
+
expect(call.stopPublish).toHaveBeenCalledWith(TrackType.VIDEO);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should handle remote soft audio mute', async () => {
|
|
97
|
+
const event: SfuEvent = {
|
|
98
|
+
eventPayload: {
|
|
99
|
+
oneofKind: 'trackUnpublished',
|
|
100
|
+
trackUnpublished: {
|
|
101
|
+
cause: TrackUnpublishReason.MODERATION,
|
|
102
|
+
type: TrackType.AUDIO,
|
|
103
|
+
sessionId: 'session-id',
|
|
104
|
+
userId: 'user-id',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
await handler!(event);
|
|
110
|
+
expect(call.microphone.disable).toHaveBeenCalled();
|
|
111
|
+
expect(call.stopPublish).toHaveBeenCalledWith(TrackType.AUDIO);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle remote soft screenshare mute', async () => {
|
|
115
|
+
const event: SfuEvent = {
|
|
116
|
+
eventPayload: {
|
|
117
|
+
oneofKind: 'trackUnpublished',
|
|
118
|
+
trackUnpublished: {
|
|
119
|
+
cause: TrackUnpublishReason.MODERATION,
|
|
120
|
+
type: TrackType.SCREEN_SHARE,
|
|
121
|
+
sessionId: 'session-id',
|
|
122
|
+
userId: 'user-id',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
await handler!(event);
|
|
128
|
+
expect(call.camera.disable).not.toHaveBeenCalled();
|
|
129
|
+
expect(call.microphone.disable).not.toHaveBeenCalled();
|
|
130
|
+
expect(call.stopPublish).toHaveBeenCalledWith(TrackType.SCREEN_SHARE);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -2,6 +2,7 @@ import { Call } from '../Call';
|
|
|
2
2
|
import { Dispatcher } from '../rtc';
|
|
3
3
|
import { CallState } from '../store';
|
|
4
4
|
import {
|
|
5
|
+
handleRemoteSoftMute,
|
|
5
6
|
watchAudioLevelChanged,
|
|
6
7
|
watchCallAccepted,
|
|
7
8
|
watchCallEnded,
|
|
@@ -61,6 +62,8 @@ export const registerEventHandlers = (
|
|
|
61
62
|
|
|
62
63
|
call.on('callGrantsUpdated', watchCallGrantsUpdated(state)),
|
|
63
64
|
call.on('pinsUpdated', watchPinsUpdated(state)),
|
|
65
|
+
|
|
66
|
+
handleRemoteSoftMute(call),
|
|
64
67
|
];
|
|
65
68
|
|
|
66
69
|
if (call.ringing) {
|
package/src/events/index.ts
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Call } from '../Call';
|
|
2
|
+
import {
|
|
3
|
+
TrackType,
|
|
4
|
+
TrackUnpublishReason,
|
|
5
|
+
} from '../gen/video/sfu/models/models';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An event handler that handles soft mutes.
|
|
9
|
+
*
|
|
10
|
+
* @param call the call.
|
|
11
|
+
*/
|
|
12
|
+
export const handleRemoteSoftMute = (call: Call) => {
|
|
13
|
+
return call.on('trackUnpublished', async (event) => {
|
|
14
|
+
if (event.eventPayload.oneofKind !== 'trackUnpublished') return;
|
|
15
|
+
const {
|
|
16
|
+
trackUnpublished: { cause, type, sessionId },
|
|
17
|
+
} = event.eventPayload;
|
|
18
|
+
const { localParticipant } = call.state;
|
|
19
|
+
if (
|
|
20
|
+
cause === TrackUnpublishReason.MODERATION &&
|
|
21
|
+
sessionId === localParticipant?.sessionId
|
|
22
|
+
) {
|
|
23
|
+
const logger = call.logger;
|
|
24
|
+
logger(
|
|
25
|
+
'info',
|
|
26
|
+
`Local participant's ${TrackType[type]} track is muted remotely`,
|
|
27
|
+
);
|
|
28
|
+
try {
|
|
29
|
+
if (type === TrackType.VIDEO) {
|
|
30
|
+
await call.camera.disable();
|
|
31
|
+
} else if (type === TrackType.AUDIO) {
|
|
32
|
+
await call.microphone.disable();
|
|
33
|
+
} else {
|
|
34
|
+
logger(
|
|
35
|
+
'warn',
|
|
36
|
+
'Unsupported track type to soft mute',
|
|
37
|
+
TrackType[type],
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (call.publisher?.isPublishing(type)) {
|
|
41
|
+
await call.stopPublish(type);
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
logger('error', 'Failed to stop publishing', error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
@@ -82,7 +82,13 @@ export const createSoundDetector = (
|
|
|
82
82
|
? 100
|
|
83
83
|
: Math.round((averagedDataValue / audioLevelThreshold) * 100);
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
// When the track is disabled, it takes time for the buffer to empty
|
|
86
|
+
// This check will ensure that we don't send anything if the track is disabled
|
|
87
|
+
if (audioStream.getAudioTracks()[0]?.enabled) {
|
|
88
|
+
onSoundDetectedStateChanged({ isSoundDetected, audioLevel: percentage });
|
|
89
|
+
} else {
|
|
90
|
+
onSoundDetectedStateChanged({ isSoundDetected: false, audioLevel: 0 });
|
|
91
|
+
}
|
|
86
92
|
}, detectionFrequencyInMs);
|
|
87
93
|
|
|
88
94
|
return async function stop() {
|
package/src/rtc/Publisher.ts
CHANGED
|
@@ -29,14 +29,13 @@ import { getOSInfo } from '../client-details';
|
|
|
29
29
|
|
|
30
30
|
const logger: Logger = getLogger(['Publisher']);
|
|
31
31
|
|
|
32
|
-
export type
|
|
32
|
+
export type PublisherConstructorOpts = {
|
|
33
33
|
sfuClient: StreamSfuClient;
|
|
34
34
|
state: CallState;
|
|
35
35
|
dispatcher: Dispatcher;
|
|
36
36
|
connectionConfig?: RTCConfiguration;
|
|
37
37
|
isDtxEnabled: boolean;
|
|
38
38
|
isRedEnabled: boolean;
|
|
39
|
-
preferredVideoCodec?: string;
|
|
40
39
|
iceRestartDelay?: number;
|
|
41
40
|
};
|
|
42
41
|
|
|
@@ -86,18 +85,8 @@ export class Publisher {
|
|
|
86
85
|
[TrackType.UNSPECIFIED]: undefined,
|
|
87
86
|
};
|
|
88
87
|
|
|
89
|
-
/**
|
|
90
|
-
* A map keeping track of track types that were published to the SFU.
|
|
91
|
-
* This map shouldn't be cleared when unpublishing a track, as it is used
|
|
92
|
-
* to determine whether a track was published before.
|
|
93
|
-
*
|
|
94
|
-
* @private
|
|
95
|
-
*/
|
|
96
|
-
private readonly trackTypePublishHistory = new Map<TrackType, boolean>();
|
|
97
|
-
|
|
98
88
|
private readonly isDtxEnabled: boolean;
|
|
99
89
|
private readonly isRedEnabled: boolean;
|
|
100
|
-
private readonly preferredVideoCodec?: string;
|
|
101
90
|
|
|
102
91
|
private readonly unsubscribeOnIceRestart: () => void;
|
|
103
92
|
|
|
@@ -118,7 +107,6 @@ export class Publisher {
|
|
|
118
107
|
* @param dispatcher the dispatcher to use.
|
|
119
108
|
* @param isDtxEnabled whether DTX is enabled.
|
|
120
109
|
* @param isRedEnabled whether RED is enabled.
|
|
121
|
-
* @param preferredVideoCodec the preferred video codec.
|
|
122
110
|
* @param iceRestartDelay the delay in milliseconds to wait before restarting ICE once connection goes to `disconnected` state.
|
|
123
111
|
*/
|
|
124
112
|
constructor({
|
|
@@ -128,15 +116,13 @@ export class Publisher {
|
|
|
128
116
|
state,
|
|
129
117
|
isDtxEnabled,
|
|
130
118
|
isRedEnabled,
|
|
131
|
-
preferredVideoCodec,
|
|
132
119
|
iceRestartDelay = 2500,
|
|
133
|
-
}:
|
|
120
|
+
}: PublisherConstructorOpts) {
|
|
134
121
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
135
122
|
this.sfuClient = sfuClient;
|
|
136
123
|
this.state = state;
|
|
137
124
|
this.isDtxEnabled = isDtxEnabled;
|
|
138
125
|
this.isRedEnabled = isRedEnabled;
|
|
139
|
-
this.preferredVideoCodec = preferredVideoCodec;
|
|
140
126
|
this.iceRestartDelay = iceRestartDelay;
|
|
141
127
|
|
|
142
128
|
this.unsubscribeOnIceRestart = dispatcher.on(
|
|
@@ -278,7 +264,6 @@ export class Publisher {
|
|
|
278
264
|
logger('debug', `Added ${TrackType[trackType]} transceiver`);
|
|
279
265
|
this.transceiverInitOrder.push(trackType);
|
|
280
266
|
this.transceiverRegistry[trackType] = transceiver;
|
|
281
|
-
this.trackTypePublishHistory.set(trackType, true);
|
|
282
267
|
|
|
283
268
|
if ('setCodecPreferences' in transceiver && codecPreferences) {
|
|
284
269
|
logger(
|
|
@@ -362,17 +347,6 @@ export class Publisher {
|
|
|
362
347
|
return false;
|
|
363
348
|
};
|
|
364
349
|
|
|
365
|
-
/**
|
|
366
|
-
* Returns true if the given track type was ever published to the SFU.
|
|
367
|
-
* Contrary to `isPublishing`, this method returns true if a certain
|
|
368
|
-
* track type was published before, even if it is currently unpublished.
|
|
369
|
-
*
|
|
370
|
-
* @param trackType the track type to check.
|
|
371
|
-
*/
|
|
372
|
-
hasEverPublished = (trackType: TrackType): boolean => {
|
|
373
|
-
return this.trackTypePublishHistory.get(trackType) ?? false;
|
|
374
|
-
};
|
|
375
|
-
|
|
376
350
|
/**
|
|
377
351
|
* Returns true if the given track type is currently live
|
|
378
352
|
*
|