@stream-io/video-client 0.3.28 → 0.3.30
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/README.md +6 -4
- package/dist/index.browser.es.js +382 -118
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +382 -116
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +382 -118
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +14 -10
- package/dist/src/devices/CameraManager.d.ts +0 -1
- package/dist/src/devices/InputMediaDeviceManager.d.ts +18 -15
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +22 -6
- package/dist/src/devices/MicrophoneManager.d.ts +0 -1
- package/dist/src/devices/ScreenShareManager.d.ts +39 -0
- package/dist/src/devices/ScreenShareState.d.ts +36 -0
- package/dist/src/devices/__tests__/ScreenShareManager.test.d.ts +1 -0
- package/dist/src/devices/__tests__/mocks.d.ts +3 -7
- package/dist/src/devices/index.d.ts +2 -0
- package/dist/src/helpers/DynascaleManager.d.ts +3 -2
- package/dist/src/helpers/__tests__/hq-audio-sdp.d.ts +1 -0
- package/dist/src/helpers/sdp-munging.d.ts +8 -0
- package/dist/src/rtc/Publisher.d.ts +7 -4
- package/dist/src/rtc/helpers/tracks.d.ts +2 -1
- package/dist/src/rtc/videoLayers.d.ts +2 -1
- package/dist/src/types.d.ts +20 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Call.ts +56 -12
- package/src/devices/CameraManager.ts +3 -4
- package/src/devices/InputMediaDeviceManager.ts +60 -45
- package/src/devices/InputMediaDeviceManagerState.ts +34 -14
- package/src/devices/MicrophoneManager.ts +3 -4
- package/src/devices/ScreenShareManager.ts +85 -0
- package/src/devices/ScreenShareState.ts +63 -0
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +16 -1
- package/src/devices/__tests__/ScreenShareManager.test.ts +119 -0
- package/src/devices/__tests__/mocks.ts +38 -1
- package/src/devices/devices.ts +10 -1
- package/src/devices/index.ts +2 -0
- package/src/helpers/DynascaleManager.ts +18 -3
- package/src/helpers/__tests__/DynascaleManager.test.ts +36 -1
- package/src/helpers/__tests__/hq-audio-sdp.ts +332 -0
- package/src/helpers/__tests__/sdp-munging.test.ts +13 -1
- package/src/helpers/sdp-munging.ts +49 -0
- package/src/rtc/Publisher.ts +87 -48
- package/src/rtc/Subscriber.ts +4 -1
- package/src/rtc/helpers/tracks.ts +16 -6
- package/src/rtc/videoLayers.ts +4 -2
- package/src/store/CallState.ts +3 -2
- package/src/store/__tests__/CallState.test.ts +1 -1
- package/src/types.ts +27 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { vi } from 'vitest';
|
|
2
2
|
import { CallingState, CallState } from '../../store';
|
|
3
3
|
import { OwnCapability } from '../../gen/coordinator';
|
|
4
|
+
import { Call } from '../../Call';
|
|
4
5
|
|
|
5
6
|
export const mockVideoDevices = [
|
|
6
7
|
{
|
|
@@ -63,7 +64,7 @@ export const mockAudioDevices = [
|
|
|
63
64
|
},
|
|
64
65
|
] as MediaDeviceInfo[];
|
|
65
66
|
|
|
66
|
-
export const mockCall = () => {
|
|
67
|
+
export const mockCall = (): Partial<Call> => {
|
|
67
68
|
const callState = new CallState();
|
|
68
69
|
callState.setCallingState(CallingState.JOINED);
|
|
69
70
|
callState.setOwnCapabilities([
|
|
@@ -74,6 +75,7 @@ export const mockCall = () => {
|
|
|
74
75
|
state: callState,
|
|
75
76
|
publishVideoStream: vi.fn(),
|
|
76
77
|
publishAudioStream: vi.fn(),
|
|
78
|
+
publishScreenShareStream: vi.fn(),
|
|
77
79
|
stopPublish: vi.fn(),
|
|
78
80
|
};
|
|
79
81
|
};
|
|
@@ -90,6 +92,7 @@ export const mockAudioStream = () => {
|
|
|
90
92
|
},
|
|
91
93
|
};
|
|
92
94
|
return {
|
|
95
|
+
getTracks: () => [track],
|
|
93
96
|
getAudioTracks: () => [track],
|
|
94
97
|
} as MediaStream;
|
|
95
98
|
};
|
|
@@ -108,6 +111,40 @@ export const mockVideoStream = () => {
|
|
|
108
111
|
},
|
|
109
112
|
};
|
|
110
113
|
return {
|
|
114
|
+
getTracks: () => [track],
|
|
111
115
|
getVideoTracks: () => [track],
|
|
112
116
|
} as MediaStream;
|
|
113
117
|
};
|
|
118
|
+
|
|
119
|
+
export const mockScreenShareStream = (includeAudio: boolean = true) => {
|
|
120
|
+
const track = {
|
|
121
|
+
getSettings: () => ({
|
|
122
|
+
deviceId: 'screen',
|
|
123
|
+
}),
|
|
124
|
+
enabled: true,
|
|
125
|
+
readyState: 'live',
|
|
126
|
+
stop: () => {
|
|
127
|
+
track.readyState = 'ended';
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const tracks = [track];
|
|
132
|
+
if (includeAudio) {
|
|
133
|
+
tracks.push({
|
|
134
|
+
getSettings: () => ({
|
|
135
|
+
deviceId: 'screen-audio',
|
|
136
|
+
}),
|
|
137
|
+
enabled: true,
|
|
138
|
+
readyState: 'live',
|
|
139
|
+
stop: () => {
|
|
140
|
+
track.readyState = 'ended';
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
getTracks: () => tracks,
|
|
147
|
+
getVideoTracks: () => tracks,
|
|
148
|
+
getAudioTracks: () => tracks,
|
|
149
|
+
} as MediaStream;
|
|
150
|
+
};
|
package/src/devices/devices.ts
CHANGED
|
@@ -199,7 +199,16 @@ export const getScreenShareStream = async (
|
|
|
199
199
|
try {
|
|
200
200
|
return await navigator.mediaDevices.getDisplayMedia({
|
|
201
201
|
video: true,
|
|
202
|
-
audio:
|
|
202
|
+
audio: {
|
|
203
|
+
channelCount: {
|
|
204
|
+
ideal: 2,
|
|
205
|
+
},
|
|
206
|
+
echoCancellation: false,
|
|
207
|
+
autoGainControl: false,
|
|
208
|
+
noiseSuppression: false,
|
|
209
|
+
},
|
|
210
|
+
// @ts-expect-error - not present in types yet
|
|
211
|
+
systemAudio: 'include',
|
|
203
212
|
...options,
|
|
204
213
|
});
|
|
205
214
|
} catch (e) {
|
package/src/devices/index.ts
CHANGED
|
@@ -5,5 +5,7 @@ export * from './CameraManager';
|
|
|
5
5
|
export * from './CameraManagerState';
|
|
6
6
|
export * from './MicrophoneManager';
|
|
7
7
|
export * from './MicrophoneManagerState';
|
|
8
|
+
export * from './ScreenShareManager';
|
|
9
|
+
export * from './ScreenShareState';
|
|
8
10
|
export * from './SpeakerManager';
|
|
9
11
|
export * from './SpeakerState';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Call } from '../Call';
|
|
2
2
|
import {
|
|
3
|
+
AudioTrackType,
|
|
3
4
|
DebounceType,
|
|
4
5
|
StreamVideoLocalParticipant,
|
|
5
6
|
StreamVideoParticipant,
|
|
@@ -324,9 +325,14 @@ export class DynascaleManager {
|
|
|
324
325
|
*
|
|
325
326
|
* @param audioElement the audio element to bind to.
|
|
326
327
|
* @param sessionId the session id.
|
|
328
|
+
* @param trackType the kind of audio.
|
|
327
329
|
* @returns a cleanup function that will unbind the audio element.
|
|
328
330
|
*/
|
|
329
|
-
bindAudioElement = (
|
|
331
|
+
bindAudioElement = (
|
|
332
|
+
audioElement: HTMLAudioElement,
|
|
333
|
+
sessionId: string,
|
|
334
|
+
trackType: AudioTrackType,
|
|
335
|
+
) => {
|
|
330
336
|
const participant = this.call.state.findParticipantBySessionId(sessionId);
|
|
331
337
|
if (!participant || participant.isLocalParticipant) return;
|
|
332
338
|
|
|
@@ -343,9 +349,18 @@ export class DynascaleManager {
|
|
|
343
349
|
);
|
|
344
350
|
|
|
345
351
|
const updateMediaStreamSubscription = participant$
|
|
346
|
-
.pipe(
|
|
352
|
+
.pipe(
|
|
353
|
+
distinctUntilKeyChanged(
|
|
354
|
+
trackType === 'screenShareAudioTrack'
|
|
355
|
+
? 'screenShareAudioStream'
|
|
356
|
+
: 'audioStream',
|
|
357
|
+
),
|
|
358
|
+
)
|
|
347
359
|
.subscribe((p) => {
|
|
348
|
-
const source =
|
|
360
|
+
const source =
|
|
361
|
+
trackType === 'screenShareAudioTrack'
|
|
362
|
+
? p.screenShareAudioStream
|
|
363
|
+
: p.audioStream;
|
|
349
364
|
if (audioElement.srcObject === source) return;
|
|
350
365
|
|
|
351
366
|
setTimeout(() => {
|
|
@@ -103,7 +103,7 @@ describe('DynascaleManager', () => {
|
|
|
103
103
|
videoElement.clientHeight = 100;
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
it('should bind audio element', () => {
|
|
106
|
+
it('audio: should bind audio element', () => {
|
|
107
107
|
vi.useFakeTimers();
|
|
108
108
|
const audioElement = document.createElement('audio');
|
|
109
109
|
const play = vi.spyOn(audioElement, 'play').mockResolvedValue();
|
|
@@ -128,6 +128,7 @@ describe('DynascaleManager', () => {
|
|
|
128
128
|
const cleanup = dynascaleManager.bindAudioElement(
|
|
129
129
|
audioElement,
|
|
130
130
|
'session-id',
|
|
131
|
+
'audioTrack',
|
|
131
132
|
);
|
|
132
133
|
expect(audioElement.autoplay).toBe(true);
|
|
133
134
|
|
|
@@ -172,6 +173,40 @@ describe('DynascaleManager', () => {
|
|
|
172
173
|
cleanup?.();
|
|
173
174
|
});
|
|
174
175
|
|
|
176
|
+
it('audio: should bind screenShare audio element', () => {
|
|
177
|
+
vi.useFakeTimers();
|
|
178
|
+
const audioElement = document.createElement('audio');
|
|
179
|
+
const play = vi.spyOn(audioElement, 'play').mockResolvedValue();
|
|
180
|
+
|
|
181
|
+
// @ts-ignore
|
|
182
|
+
call.state.updateOrAddParticipant('session-id', {
|
|
183
|
+
userId: 'user-id',
|
|
184
|
+
sessionId: 'session-id',
|
|
185
|
+
publishedTracks: [TrackType.SCREEN_SHARE_AUDIO],
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const cleanup = dynascaleManager.bindAudioElement(
|
|
189
|
+
audioElement,
|
|
190
|
+
'session-id',
|
|
191
|
+
'screenShareAudioTrack',
|
|
192
|
+
);
|
|
193
|
+
expect(audioElement.autoplay).toBe(true);
|
|
194
|
+
|
|
195
|
+
const audioMediaStream = new MediaStream();
|
|
196
|
+
const screenShareAudioMediaStream = new MediaStream();
|
|
197
|
+
call.state.updateParticipant('session-id', {
|
|
198
|
+
audioStream: audioMediaStream,
|
|
199
|
+
screenShareAudioStream: screenShareAudioMediaStream,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
vi.runAllTimers();
|
|
203
|
+
|
|
204
|
+
expect(play).toHaveBeenCalled();
|
|
205
|
+
expect(audioElement.srcObject).toBe(screenShareAudioMediaStream);
|
|
206
|
+
|
|
207
|
+
cleanup?.();
|
|
208
|
+
});
|
|
209
|
+
|
|
175
210
|
it('video: should update subscription when track becomes available', () => {
|
|
176
211
|
const updateSubscription = vi.spyOn(call, 'updateSubscriptionsPartial');
|
|
177
212
|
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
export const initialSdp = `
|
|
2
|
+
v=0
|
|
3
|
+
o=- 898697271686242868 5 IN IP4 127.0.0.1
|
|
4
|
+
s=-
|
|
5
|
+
t=0 0
|
|
6
|
+
a=group:BUNDLE 0 1 2 3
|
|
7
|
+
a=extmap-allow-mixed
|
|
8
|
+
a=msid-semantic: WMS e893e3ad-d9e8-4b56-998f-0d89213dd857
|
|
9
|
+
m=video 60017 UDP/TLS/RTP/SAVPF 96 97 102 103 104 105 106 107 108 109 127 125 39 40 45 46 98 99 100 101 112 113 116 117 118
|
|
10
|
+
c=IN IP4 79.125.240.146
|
|
11
|
+
a=rtcp:9 IN IP4 0.0.0.0
|
|
12
|
+
a=candidate:2824354870 1 udp 2122260223 192.168.1.102 60017 typ host generation 0 network-id 2
|
|
13
|
+
a=candidate:427386432 1 udp 2122194687 192.168.1.244 63221 typ host generation 0 network-id 1 network-cost 10
|
|
14
|
+
a=candidate:2841136656 1 udp 1686052607 79.125.240.146 60017 typ srflx raddr 192.168.1.102 rport 60017 generation 0 network-id 2
|
|
15
|
+
a=candidate:410588262 1 udp 1685987071 79.125.240.146 63221 typ srflx raddr 192.168.1.244 rport 63221 generation 0 network-id 1 network-cost 10
|
|
16
|
+
a=candidate:3600277166 1 tcp 1518280447 192.168.1.102 9 typ host tcptype active generation 0 network-id 2
|
|
17
|
+
a=candidate:1740014808 1 tcp 1518214911 192.168.1.244 9 typ host tcptype active generation 0 network-id 1 network-cost 10
|
|
18
|
+
a=ice-ufrag:GM64
|
|
19
|
+
a=ice-pwd:ANZFilRRlZJ3bg9AD40eRu7n
|
|
20
|
+
a=ice-options:trickle
|
|
21
|
+
a=fingerprint:sha-256 38:1F:02:E5:2A:49:9A:2A:D9:8E:B9:9B:4C:40:21:B7:F1:C4:27:8E:B5:68:D6:E0:91:08:D9:CB:2B:AC:B3:87
|
|
22
|
+
a=setup:actpass
|
|
23
|
+
a=mid:0
|
|
24
|
+
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
|
|
25
|
+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
|
26
|
+
a=extmap:3 urn:3gpp:video-orientation
|
|
27
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
|
28
|
+
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
|
29
|
+
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
|
|
30
|
+
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
|
|
31
|
+
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
|
|
32
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
|
|
33
|
+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
|
34
|
+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
|
35
|
+
a=sendonly
|
|
36
|
+
a=msid:e893e3ad-d9e8-4b56-998f-0d89213dd857 44e5d53f-ce6a-4bf4-824a-335113d86e6e
|
|
37
|
+
a=rtcp-mux
|
|
38
|
+
a=rtcp-rsize
|
|
39
|
+
a=rtpmap:96 VP8/90000
|
|
40
|
+
a=rtcp-fb:96 goog-remb
|
|
41
|
+
a=rtcp-fb:96 transport-cc
|
|
42
|
+
a=rtcp-fb:96 ccm fir
|
|
43
|
+
a=rtcp-fb:96 nack
|
|
44
|
+
a=rtcp-fb:96 nack pli
|
|
45
|
+
a=rtpmap:97 rtx/90000
|
|
46
|
+
a=fmtp:97 apt=96
|
|
47
|
+
a=rtpmap:102 H264/90000
|
|
48
|
+
a=rtcp-fb:102 goog-remb
|
|
49
|
+
a=rtcp-fb:102 transport-cc
|
|
50
|
+
a=rtcp-fb:102 ccm fir
|
|
51
|
+
a=rtcp-fb:102 nack
|
|
52
|
+
a=rtcp-fb:102 nack pli
|
|
53
|
+
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
|
|
54
|
+
a=rtpmap:103 rtx/90000
|
|
55
|
+
a=fmtp:103 apt=102
|
|
56
|
+
a=rtpmap:104 H264/90000
|
|
57
|
+
a=rtcp-fb:104 goog-remb
|
|
58
|
+
a=rtcp-fb:104 transport-cc
|
|
59
|
+
a=rtcp-fb:104 ccm fir
|
|
60
|
+
a=rtcp-fb:104 nack
|
|
61
|
+
a=rtcp-fb:104 nack pli
|
|
62
|
+
a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
|
|
63
|
+
a=rtpmap:105 rtx/90000
|
|
64
|
+
a=fmtp:105 apt=104
|
|
65
|
+
a=rtpmap:106 H264/90000
|
|
66
|
+
a=rtcp-fb:106 goog-remb
|
|
67
|
+
a=rtcp-fb:106 transport-cc
|
|
68
|
+
a=rtcp-fb:106 ccm fir
|
|
69
|
+
a=rtcp-fb:106 nack
|
|
70
|
+
a=rtcp-fb:106 nack pli
|
|
71
|
+
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
|
|
72
|
+
a=rtpmap:107 rtx/90000
|
|
73
|
+
a=fmtp:107 apt=106
|
|
74
|
+
a=rtpmap:108 H264/90000
|
|
75
|
+
a=rtcp-fb:108 goog-remb
|
|
76
|
+
a=rtcp-fb:108 transport-cc
|
|
77
|
+
a=rtcp-fb:108 ccm fir
|
|
78
|
+
a=rtcp-fb:108 nack
|
|
79
|
+
a=rtcp-fb:108 nack pli
|
|
80
|
+
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
|
|
81
|
+
a=rtpmap:109 rtx/90000
|
|
82
|
+
a=fmtp:109 apt=108
|
|
83
|
+
a=rtpmap:127 H264/90000
|
|
84
|
+
a=rtcp-fb:127 goog-remb
|
|
85
|
+
a=rtcp-fb:127 transport-cc
|
|
86
|
+
a=rtcp-fb:127 ccm fir
|
|
87
|
+
a=rtcp-fb:127 nack
|
|
88
|
+
a=rtcp-fb:127 nack pli
|
|
89
|
+
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
|
|
90
|
+
a=rtpmap:125 rtx/90000
|
|
91
|
+
a=fmtp:125 apt=127
|
|
92
|
+
a=rtpmap:39 H264/90000
|
|
93
|
+
a=rtcp-fb:39 goog-remb
|
|
94
|
+
a=rtcp-fb:39 transport-cc
|
|
95
|
+
a=rtcp-fb:39 ccm fir
|
|
96
|
+
a=rtcp-fb:39 nack
|
|
97
|
+
a=rtcp-fb:39 nack pli
|
|
98
|
+
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
|
|
99
|
+
a=rtpmap:40 rtx/90000
|
|
100
|
+
a=fmtp:40 apt=39
|
|
101
|
+
a=rtpmap:45 AV1/90000
|
|
102
|
+
a=rtcp-fb:45 goog-remb
|
|
103
|
+
a=rtcp-fb:45 transport-cc
|
|
104
|
+
a=rtcp-fb:45 ccm fir
|
|
105
|
+
a=rtcp-fb:45 nack
|
|
106
|
+
a=rtcp-fb:45 nack pli
|
|
107
|
+
a=rtpmap:46 rtx/90000
|
|
108
|
+
a=fmtp:46 apt=45
|
|
109
|
+
a=rtpmap:98 VP9/90000
|
|
110
|
+
a=rtcp-fb:98 goog-remb
|
|
111
|
+
a=rtcp-fb:98 transport-cc
|
|
112
|
+
a=rtcp-fb:98 ccm fir
|
|
113
|
+
a=rtcp-fb:98 nack
|
|
114
|
+
a=rtcp-fb:98 nack pli
|
|
115
|
+
a=fmtp:98 profile-id=0
|
|
116
|
+
a=rtpmap:99 rtx/90000
|
|
117
|
+
a=fmtp:99 apt=98
|
|
118
|
+
a=rtpmap:100 VP9/90000
|
|
119
|
+
a=rtcp-fb:100 goog-remb
|
|
120
|
+
a=rtcp-fb:100 transport-cc
|
|
121
|
+
a=rtcp-fb:100 ccm fir
|
|
122
|
+
a=rtcp-fb:100 nack
|
|
123
|
+
a=rtcp-fb:100 nack pli
|
|
124
|
+
a=fmtp:100 profile-id=2
|
|
125
|
+
a=rtpmap:101 rtx/90000
|
|
126
|
+
a=fmtp:101 apt=100
|
|
127
|
+
a=rtpmap:112 H264/90000
|
|
128
|
+
a=rtcp-fb:112 goog-remb
|
|
129
|
+
a=rtcp-fb:112 transport-cc
|
|
130
|
+
a=rtcp-fb:112 ccm fir
|
|
131
|
+
a=rtcp-fb:112 nack
|
|
132
|
+
a=rtcp-fb:112 nack pli
|
|
133
|
+
a=fmtp:112 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
|
|
134
|
+
a=rtpmap:113 rtx/90000
|
|
135
|
+
a=fmtp:113 apt=112
|
|
136
|
+
a=rtpmap:116 red/90000
|
|
137
|
+
a=rtpmap:117 rtx/90000
|
|
138
|
+
a=fmtp:117 apt=116
|
|
139
|
+
a=rtpmap:118 ulpfec/90000
|
|
140
|
+
a=rid:q send
|
|
141
|
+
a=rid:h send
|
|
142
|
+
a=rid:f send
|
|
143
|
+
a=simulcast:send q;h;f
|
|
144
|
+
m=audio 9 UDP/TLS/RTP/SAVPF 63 111 9 0 8 13 110 126
|
|
145
|
+
c=IN IP4 0.0.0.0
|
|
146
|
+
a=rtcp:9 IN IP4 0.0.0.0
|
|
147
|
+
a=ice-ufrag:GM64
|
|
148
|
+
a=ice-pwd:ANZFilRRlZJ3bg9AD40eRu7n
|
|
149
|
+
a=ice-options:trickle
|
|
150
|
+
a=fingerprint:sha-256 38:1F:02:E5:2A:49:9A:2A:D9:8E:B9:9B:4C:40:21:B7:F1:C4:27:8E:B5:68:D6:E0:91:08:D9:CB:2B:AC:B3:87
|
|
151
|
+
a=setup:actpass
|
|
152
|
+
a=mid:1
|
|
153
|
+
a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
|
154
|
+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
|
155
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
|
156
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
|
|
157
|
+
a=sendonly
|
|
158
|
+
a=msid:- cb02416c-8035-4e80-8b7e-becd6b7f600f
|
|
159
|
+
a=rtcp-mux
|
|
160
|
+
a=rtpmap:63 red/48000/2
|
|
161
|
+
a=fmtp:63 111/111
|
|
162
|
+
a=rtpmap:111 opus/48000/2
|
|
163
|
+
a=rtcp-fb:111 transport-cc
|
|
164
|
+
a=fmtp:111 minptime=10;usedtx=1;useinbandfec=1
|
|
165
|
+
a=rtpmap:9 G722/8000
|
|
166
|
+
a=rtpmap:0 PCMU/8000
|
|
167
|
+
a=rtpmap:8 PCMA/8000
|
|
168
|
+
a=rtpmap:13 CN/8000
|
|
169
|
+
a=rtpmap:110 telephone-event/48000
|
|
170
|
+
a=rtpmap:126 telephone-event/8000
|
|
171
|
+
a=ssrc:743121750 cname:mbGW+aeWMLFhPbBC
|
|
172
|
+
a=ssrc:743121750 msid:- cb02416c-8035-4e80-8b7e-becd6b7f600f
|
|
173
|
+
m=video 9 UDP/TLS/RTP/SAVPF 96 97 102 103 104 105 106 107 108 109 127 125 39 40 45 46 98 99 100 101 112 113 116 117 118
|
|
174
|
+
c=IN IP4 0.0.0.0
|
|
175
|
+
a=rtcp:9 IN IP4 0.0.0.0
|
|
176
|
+
a=ice-ufrag:GM64
|
|
177
|
+
a=ice-pwd:ANZFilRRlZJ3bg9AD40eRu7n
|
|
178
|
+
a=ice-options:trickle
|
|
179
|
+
a=fingerprint:sha-256 38:1F:02:E5:2A:49:9A:2A:D9:8E:B9:9B:4C:40:21:B7:F1:C4:27:8E:B5:68:D6:E0:91:08:D9:CB:2B:AC:B3:87
|
|
180
|
+
a=setup:actpass
|
|
181
|
+
a=mid:2
|
|
182
|
+
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
|
|
183
|
+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
|
184
|
+
a=extmap:3 urn:3gpp:video-orientation
|
|
185
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
|
186
|
+
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
|
187
|
+
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
|
|
188
|
+
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
|
|
189
|
+
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
|
|
190
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
|
|
191
|
+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
|
192
|
+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
|
193
|
+
a=sendonly
|
|
194
|
+
a=msid:04f96a2a-d107-4a12-b545-cfe1dc2c4d37 4a1ae8a7-6c89-44d3-a9a1-02abce0012c7
|
|
195
|
+
a=rtcp-mux
|
|
196
|
+
a=rtcp-rsize
|
|
197
|
+
a=rtpmap:96 VP8/90000
|
|
198
|
+
a=rtcp-fb:96 goog-remb
|
|
199
|
+
a=rtcp-fb:96 transport-cc
|
|
200
|
+
a=rtcp-fb:96 ccm fir
|
|
201
|
+
a=rtcp-fb:96 nack
|
|
202
|
+
a=rtcp-fb:96 nack pli
|
|
203
|
+
a=rtpmap:97 rtx/90000
|
|
204
|
+
a=fmtp:97 apt=96
|
|
205
|
+
a=rtpmap:102 H264/90000
|
|
206
|
+
a=rtcp-fb:102 goog-remb
|
|
207
|
+
a=rtcp-fb:102 transport-cc
|
|
208
|
+
a=rtcp-fb:102 ccm fir
|
|
209
|
+
a=rtcp-fb:102 nack
|
|
210
|
+
a=rtcp-fb:102 nack pli
|
|
211
|
+
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
|
|
212
|
+
a=rtpmap:103 rtx/90000
|
|
213
|
+
a=fmtp:103 apt=102
|
|
214
|
+
a=rtpmap:104 H264/90000
|
|
215
|
+
a=rtcp-fb:104 goog-remb
|
|
216
|
+
a=rtcp-fb:104 transport-cc
|
|
217
|
+
a=rtcp-fb:104 ccm fir
|
|
218
|
+
a=rtcp-fb:104 nack
|
|
219
|
+
a=rtcp-fb:104 nack pli
|
|
220
|
+
a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
|
|
221
|
+
a=rtpmap:105 rtx/90000
|
|
222
|
+
a=fmtp:105 apt=104
|
|
223
|
+
a=rtpmap:106 H264/90000
|
|
224
|
+
a=rtcp-fb:106 goog-remb
|
|
225
|
+
a=rtcp-fb:106 transport-cc
|
|
226
|
+
a=rtcp-fb:106 ccm fir
|
|
227
|
+
a=rtcp-fb:106 nack
|
|
228
|
+
a=rtcp-fb:106 nack pli
|
|
229
|
+
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
|
|
230
|
+
a=rtpmap:107 rtx/90000
|
|
231
|
+
a=fmtp:107 apt=106
|
|
232
|
+
a=rtpmap:108 H264/90000
|
|
233
|
+
a=rtcp-fb:108 goog-remb
|
|
234
|
+
a=rtcp-fb:108 transport-cc
|
|
235
|
+
a=rtcp-fb:108 ccm fir
|
|
236
|
+
a=rtcp-fb:108 nack
|
|
237
|
+
a=rtcp-fb:108 nack pli
|
|
238
|
+
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
|
|
239
|
+
a=rtpmap:109 rtx/90000
|
|
240
|
+
a=fmtp:109 apt=108
|
|
241
|
+
a=rtpmap:127 H264/90000
|
|
242
|
+
a=rtcp-fb:127 goog-remb
|
|
243
|
+
a=rtcp-fb:127 transport-cc
|
|
244
|
+
a=rtcp-fb:127 ccm fir
|
|
245
|
+
a=rtcp-fb:127 nack
|
|
246
|
+
a=rtcp-fb:127 nack pli
|
|
247
|
+
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
|
|
248
|
+
a=rtpmap:125 rtx/90000
|
|
249
|
+
a=fmtp:125 apt=127
|
|
250
|
+
a=rtpmap:39 H264/90000
|
|
251
|
+
a=rtcp-fb:39 goog-remb
|
|
252
|
+
a=rtcp-fb:39 transport-cc
|
|
253
|
+
a=rtcp-fb:39 ccm fir
|
|
254
|
+
a=rtcp-fb:39 nack
|
|
255
|
+
a=rtcp-fb:39 nack pli
|
|
256
|
+
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
|
|
257
|
+
a=rtpmap:40 rtx/90000
|
|
258
|
+
a=fmtp:40 apt=39
|
|
259
|
+
a=rtpmap:45 AV1/90000
|
|
260
|
+
a=rtcp-fb:45 goog-remb
|
|
261
|
+
a=rtcp-fb:45 transport-cc
|
|
262
|
+
a=rtcp-fb:45 ccm fir
|
|
263
|
+
a=rtcp-fb:45 nack
|
|
264
|
+
a=rtcp-fb:45 nack pli
|
|
265
|
+
a=rtpmap:46 rtx/90000
|
|
266
|
+
a=fmtp:46 apt=45
|
|
267
|
+
a=rtpmap:98 VP9/90000
|
|
268
|
+
a=rtcp-fb:98 goog-remb
|
|
269
|
+
a=rtcp-fb:98 transport-cc
|
|
270
|
+
a=rtcp-fb:98 ccm fir
|
|
271
|
+
a=rtcp-fb:98 nack
|
|
272
|
+
a=rtcp-fb:98 nack pli
|
|
273
|
+
a=fmtp:98 profile-id=0
|
|
274
|
+
a=rtpmap:99 rtx/90000
|
|
275
|
+
a=fmtp:99 apt=98
|
|
276
|
+
a=rtpmap:100 VP9/90000
|
|
277
|
+
a=rtcp-fb:100 goog-remb
|
|
278
|
+
a=rtcp-fb:100 transport-cc
|
|
279
|
+
a=rtcp-fb:100 ccm fir
|
|
280
|
+
a=rtcp-fb:100 nack
|
|
281
|
+
a=rtcp-fb:100 nack pli
|
|
282
|
+
a=fmtp:100 profile-id=2
|
|
283
|
+
a=rtpmap:101 rtx/90000
|
|
284
|
+
a=fmtp:101 apt=100
|
|
285
|
+
a=rtpmap:112 H264/90000
|
|
286
|
+
a=rtcp-fb:112 goog-remb
|
|
287
|
+
a=rtcp-fb:112 transport-cc
|
|
288
|
+
a=rtcp-fb:112 ccm fir
|
|
289
|
+
a=rtcp-fb:112 nack
|
|
290
|
+
a=rtcp-fb:112 nack pli
|
|
291
|
+
a=fmtp:112 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
|
|
292
|
+
a=rtpmap:113 rtx/90000
|
|
293
|
+
a=fmtp:113 apt=112
|
|
294
|
+
a=rtpmap:116 red/90000
|
|
295
|
+
a=rtpmap:117 rtx/90000
|
|
296
|
+
a=fmtp:117 apt=116
|
|
297
|
+
a=rtpmap:118 ulpfec/90000
|
|
298
|
+
a=ssrc-group:FID 4072017687 3466187747
|
|
299
|
+
a=ssrc:4072017687 cname:mbGW+aeWMLFhPbBC
|
|
300
|
+
a=ssrc:4072017687 msid:04f96a2a-d107-4a12-b545-cfe1dc2c4d37 4a1ae8a7-6c89-44d3-a9a1-02abce0012c7
|
|
301
|
+
a=ssrc:3466187747 cname:mbGW+aeWMLFhPbBC
|
|
302
|
+
a=ssrc:3466187747 msid:04f96a2a-d107-4a12-b545-cfe1dc2c4d37 4a1ae8a7-6c89-44d3-a9a1-02abce0012c7
|
|
303
|
+
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126
|
|
304
|
+
c=IN IP4 0.0.0.0
|
|
305
|
+
a=rtcp:9 IN IP4 0.0.0.0
|
|
306
|
+
a=ice-ufrag:GM64
|
|
307
|
+
a=ice-pwd:ANZFilRRlZJ3bg9AD40eRu7n
|
|
308
|
+
a=ice-options:trickle
|
|
309
|
+
a=fingerprint:sha-256 38:1F:02:E5:2A:49:9A:2A:D9:8E:B9:9B:4C:40:21:B7:F1:C4:27:8E:B5:68:D6:E0:91:08:D9:CB:2B:AC:B3:87
|
|
310
|
+
a=setup:actpass
|
|
311
|
+
a=mid:3
|
|
312
|
+
a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
|
313
|
+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
|
314
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
|
315
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
|
|
316
|
+
a=sendonly
|
|
317
|
+
a=msid:- 75b8c290-1f46-464c-8dc5-a4a3bced313a
|
|
318
|
+
a=rtcp-mux
|
|
319
|
+
a=rtpmap:111 opus/48000/2
|
|
320
|
+
a=rtcp-fb:111 transport-cc
|
|
321
|
+
a=fmtp:111 minptime=10;usedtx=1;useinbandfec=1
|
|
322
|
+
a=rtpmap:63 red/48000/2
|
|
323
|
+
a=fmtp:63 111/111
|
|
324
|
+
a=rtpmap:9 G722/8000
|
|
325
|
+
a=rtpmap:0 PCMU/8000
|
|
326
|
+
a=rtpmap:8 PCMA/8000
|
|
327
|
+
a=rtpmap:13 CN/8000
|
|
328
|
+
a=rtpmap:110 telephone-event/48000
|
|
329
|
+
a=rtpmap:126 telephone-event/8000
|
|
330
|
+
a=ssrc:1281279951 cname:mbGW+aeWMLFhPbBC
|
|
331
|
+
a=ssrc:1281279951 msid:- 75b8c290-1f46-464c-8dc5-a4a3bced313a
|
|
332
|
+
`.trim();
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
enableHighQualityAudio,
|
|
4
|
+
removeCodec,
|
|
5
|
+
setPreferredCodec,
|
|
6
|
+
toggleDtx,
|
|
7
|
+
} from '../sdp-munging';
|
|
8
|
+
import { initialSdp as HQAudioSDP } from './hq-audio-sdp';
|
|
3
9
|
|
|
4
10
|
const sdpWithRed = `v=0
|
|
5
11
|
o=- 3265541491372987511 2 IN IP4 127.0.0.1
|
|
@@ -275,4 +281,10 @@ a=maxptime:40`;
|
|
|
275
281
|
const dtxDisabledSdp = toggleDtx(dtxEnabledSdp, false);
|
|
276
282
|
expect(dtxDisabledSdp.search('usedtx=0') !== -1).toBeTruthy();
|
|
277
283
|
});
|
|
284
|
+
|
|
285
|
+
it('enables HighQuality audio for Opus', () => {
|
|
286
|
+
const sdpWithHighQualityAudio = enableHighQualityAudio(HQAudioSDP, '3');
|
|
287
|
+
expect(sdpWithHighQualityAudio).toContain('maxaveragebitrate=510000');
|
|
288
|
+
expect(sdpWithHighQualityAudio).toContain('stereo=1');
|
|
289
|
+
});
|
|
278
290
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as SDP from 'sdp-transform';
|
|
2
|
+
|
|
1
3
|
type Media = {
|
|
2
4
|
original: string;
|
|
3
5
|
mediaWithPorts: string;
|
|
@@ -227,3 +229,50 @@ export const toggleDtx = (sdp: string, enable: boolean): string => {
|
|
|
227
229
|
}
|
|
228
230
|
return sdp;
|
|
229
231
|
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Enables high-quality audio through SDP munging for the given trackMid.
|
|
235
|
+
*
|
|
236
|
+
* @param sdp the SDP to munge.
|
|
237
|
+
* @param trackMid the trackMid.
|
|
238
|
+
* @param maxBitrate the max bitrate to set.
|
|
239
|
+
*/
|
|
240
|
+
export const enableHighQualityAudio = (
|
|
241
|
+
sdp: string,
|
|
242
|
+
trackMid: string,
|
|
243
|
+
maxBitrate: number = 510000,
|
|
244
|
+
): string => {
|
|
245
|
+
maxBitrate = Math.max(Math.min(maxBitrate, 510000), 96000);
|
|
246
|
+
|
|
247
|
+
const parsedSdp = SDP.parse(sdp);
|
|
248
|
+
const audioMedia = parsedSdp.media.find(
|
|
249
|
+
(m) => m.type === 'audio' && String(m.mid) === trackMid,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (!audioMedia) return sdp;
|
|
253
|
+
|
|
254
|
+
const opusRtp = audioMedia.rtp.find((r) => r.codec === 'opus');
|
|
255
|
+
if (!opusRtp) return sdp;
|
|
256
|
+
|
|
257
|
+
const opusFmtp = audioMedia.fmtp.find((f) => f.payload === opusRtp.payload);
|
|
258
|
+
if (!opusFmtp) return sdp;
|
|
259
|
+
|
|
260
|
+
// enable stereo, if not already enabled
|
|
261
|
+
if (opusFmtp.config.match(/stereo=(\d)/)) {
|
|
262
|
+
opusFmtp.config = opusFmtp.config.replace(/stereo=(\d)/, 'stereo=1');
|
|
263
|
+
} else {
|
|
264
|
+
opusFmtp.config = `${opusFmtp.config};stereo=1`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// set maxaveragebitrate, to the given value
|
|
268
|
+
if (opusFmtp.config.match(/maxaveragebitrate=(\d*)/)) {
|
|
269
|
+
opusFmtp.config = opusFmtp.config.replace(
|
|
270
|
+
/maxaveragebitrate=(\d*)/,
|
|
271
|
+
`maxaveragebitrate=${maxBitrate}`,
|
|
272
|
+
);
|
|
273
|
+
} else {
|
|
274
|
+
opusFmtp.config = `${opusFmtp.config};maxaveragebitrate=${maxBitrate}`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return SDP.write(parsedSdp);
|
|
278
|
+
};
|