Skip to content

Commit 426bcc7

Browse files
committed
Merge branch 'rn-76' into test-design-with-rn-76
2 parents 5fc43aa + 4a689d6 commit 426bcc7

File tree

38 files changed

+5370
-1478
lines changed

38 files changed

+5370
-1478
lines changed

packages/client/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
44

5+
## [1.11.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.3...@stream-io/video-client-1.11.4) (2024-11-21)
6+
7+
8+
### Bug Fixes
9+
10+
* experimental option to force single codec preference in the SDP ([#1581](https://github.com/GetStream/stream-video-js/issues/1581)) ([894a86e](https://github.com/GetStream/stream-video-js/commit/894a86e407dc0dd36b7463bb964c86da0c3055d1))
11+
512
## [1.11.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.2...@stream-io/video-client-1.11.3) (2024-11-20)
613

714

packages/client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stream-io/video-client",
3-
"version": "1.11.3",
3+
"version": "1.11.4",
44
"packageManager": "[email protected]",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.es.js",

packages/client/src/helpers/__tests__/sdp-munging.test.ts

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { describe, expect, it } from 'vitest';
2-
import { enableHighQualityAudio, toggleDtx } from '../sdp-munging';
2+
import {
3+
enableHighQualityAudio,
4+
preserveCodec,
5+
toggleDtx,
6+
} from '../sdp-munging';
37
import { initialSdp as HQAudioSDP } from './hq-audio-sdp';
48

59
describe('sdp-munging', () => {
@@ -21,4 +25,167 @@ a=maxptime:40`;
2125
expect(sdpWithHighQualityAudio).toContain('maxaveragebitrate=510000');
2226
expect(sdpWithHighQualityAudio).toContain('stereo=1');
2327
});
28+
29+
it('preserves the preferred codec', () => {
30+
const sdp = `v=0
31+
o=- 8608371809202407637 2 IN IP4 127.0.0.1
32+
s=-
33+
t=0 0
34+
a=extmap-allow-mixed
35+
a=msid-semantic: WMS 52fafc21-b8bb-4f4f-8072-86a29cb6590e
36+
a=group:BUNDLE 0
37+
m=video 9 UDP/TLS/RTP/SAVPF 98 100 99 101
38+
c=IN IP4 0.0.0.0
39+
a=rtpmap:98 VP9/90000
40+
a=rtpmap:99 rtx/90000
41+
a=rtpmap:100 VP9/90000
42+
a=rtpmap:101 rtx/90000
43+
a=fmtp:98 profile-id=0
44+
a=fmtp:99 apt=98
45+
a=fmtp:100 profile-id=2
46+
a=fmtp:101 apt=100
47+
a=rtcp:9 IN IP4 0.0.0.0
48+
a=rtcp-fb:98 goog-remb
49+
a=rtcp-fb:98 transport-cc
50+
a=rtcp-fb:98 ccm fir
51+
a=rtcp-fb:98 nack
52+
a=rtcp-fb:98 nack pli
53+
a=rtcp-fb:100 goog-remb
54+
a=rtcp-fb:100 transport-cc
55+
a=rtcp-fb:100 ccm fir
56+
a=rtcp-fb:100 nack
57+
a=rtcp-fb:100 nack pli
58+
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
59+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
60+
a=extmap:3 urn:3gpp:video-orientation
61+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
62+
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
63+
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
64+
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
65+
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
66+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
67+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
68+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
69+
a=extmap:12 https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension
70+
a=extmap:14 http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00
71+
a=setup:actpass
72+
a=mid:0
73+
a=msid:52fafc21-b8bb-4f4f-8072-86a29cb6590e 1bd1c5c2-d3cc-4490-ac0c-70b187242232
74+
a=sendonly
75+
a=ice-ufrag:LvRk
76+
a=ice-pwd:IpBRr2Rrg9TkOgayjYqALhPY
77+
a=fingerprint:sha-256 18:DE:8F:ED:E6:A2:0C:99:A8:25:AB:C9:F8:3D:91:4C:3E:9F:B4:1F:22:87:A7:3C:85:8F:F3:51:09:A7:E3:FA
78+
a=ice-options:trickle
79+
a=ssrc:3192778601 cname:yYSN5R+RG2j3luO7
80+
a=ssrc:3192778601 msid:52fafc21-b8bb-4f4f-8072-86a29cb6590e 1bd1c5c2-d3cc-4490-ac0c-70b187242232
81+
a=ssrc:283365205 cname:yYSN5R+RG2j3luO7
82+
a=ssrc:283365205 msid:52fafc21-b8bb-4f4f-8072-86a29cb6590e 1bd1c5c2-d3cc-4490-ac0c-70b187242232
83+
a=ssrc-group:FID 3192778601 283365205
84+
a=rtcp-mux
85+
a=rtcp-rsize`;
86+
const target = preserveCodec(sdp, '0', {
87+
mimeType: 'video/VP9',
88+
clockRate: 90000,
89+
sdpFmtpLine: 'profile-id=0',
90+
});
91+
expect(target).toContain('VP9');
92+
expect(target).not.toContain('profile-id=2');
93+
});
94+
95+
it('handles ios munging', () => {
96+
const sdp = `v=0
97+
o=- 525780719364332676 2 IN IP4 127.0.0.1
98+
s=-
99+
t=0 0
100+
a=group:BUNDLE 0
101+
a=extmap-allow-mixed
102+
a=msid-semantic: WMS BF3AFE62-88F8-4189-99D7-7CAE159205E3
103+
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 103 35 36 104 105 106
104+
c=IN IP4 0.0.0.0
105+
a=rtcp:9 IN IP4 0.0.0.0
106+
a=ice-ufrag:SAkq
107+
a=ice-pwd:FYHHro0VWRO8CjI/M1VG5vRw
108+
a=ice-options:trickle renomination
109+
a=fingerprint:sha-256 03:5B:16:0E:E1:7B:FE:4F:9A:5C:AC:CF:08:21:4B:49:CE:53:79:E6:97:AE:4E:73:F8:43:34:C3:11:F7:6D:E7
110+
a=setup:actpass
111+
a=mid:0
112+
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
113+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
114+
a=extmap:3 urn:3gpp:video-orientation
115+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
116+
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
117+
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
118+
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
119+
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
120+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
121+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
122+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
123+
a=extmap:12 https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension
124+
a=extmap:14 http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00
125+
a=sendonly
126+
a=msid:BF3AFE62-88F8-4189-99D7-7CAE159205E3 6013DC02-A0A5-43A9-9D41-9D4A89648A42
127+
a=rtcp-mux
128+
a=rtcp-rsize
129+
a=rtpmap:96 H264/90000
130+
a=rtcp-fb:96 goog-remb
131+
a=rtcp-fb:96 transport-cc
132+
a=rtcp-fb:96 ccm fir
133+
a=rtcp-fb:96 nack
134+
a=rtcp-fb:96 nack pli
135+
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c29
136+
a=rtpmap:97 rtx/90000
137+
a=fmtp:97 apt=96
138+
a=rtpmap:98 H264/90000
139+
a=rtcp-fb:98 goog-remb
140+
a=rtcp-fb:98 transport-cc
141+
a=rtcp-fb:98 ccm fir
142+
a=rtcp-fb:98 nack
143+
a=rtcp-fb:98 nack pli
144+
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e029
145+
a=rtpmap:99 rtx/90000
146+
a=fmtp:99 apt=98
147+
a=rtpmap:100 VP8/90000
148+
a=rtcp-fb:100 goog-remb
149+
a=rtcp-fb:100 transport-cc
150+
a=rtcp-fb:100 ccm fir
151+
a=rtcp-fb:100 nack
152+
a=rtcp-fb:100 nack pli
153+
a=rtpmap:101 rtx/90000
154+
a=fmtp:101 apt=100
155+
a=rtpmap:127 VP9/90000
156+
a=rtcp-fb:127 goog-remb
157+
a=rtcp-fb:127 transport-cc
158+
a=rtcp-fb:127 ccm fir
159+
a=rtcp-fb:127 nack
160+
a=rtcp-fb:127 nack pli
161+
a=rtpmap:103 rtx/90000
162+
a=fmtp:103 apt=127
163+
a=rtpmap:35 AV1/90000
164+
a=rtcp-fb:35 goog-remb
165+
a=rtcp-fb:35 transport-cc
166+
a=rtcp-fb:35 ccm fir
167+
a=rtcp-fb:35 nack
168+
a=rtcp-fb:35 nack pli
169+
a=rtpmap:36 rtx/90000
170+
a=fmtp:36 apt=35
171+
a=rtpmap:104 red/90000
172+
a=rtpmap:105 rtx/90000
173+
a=fmtp:105 apt=104
174+
a=rtpmap:106 ulpfec/90000
175+
a=rid:q send
176+
a=rid:h send
177+
a=rid:f send
178+
a=simulcast:send q;h;f`;
179+
const target = preserveCodec(sdp, '0', {
180+
mimeType: 'video/H264',
181+
clockRate: 90000,
182+
sdpFmtpLine:
183+
'profile-level-id=42e029;packetization-mode=1;level-asymmetry-allowed=1',
184+
});
185+
expect(target).toContain('H264');
186+
expect(target).toContain('profile-level-id=42e029');
187+
expect(target).not.toContain('profile-level-id=640c29');
188+
expect(target).not.toContain('VP9');
189+
expect(target).not.toContain('AV1');
190+
});
24191
});

packages/client/src/helpers/sdp-munging.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,61 @@ export const toggleDtx = (sdp: string, enable: boolean): string => {
129129
return sdp.replace(opusFmtp.original, newFmtp);
130130
};
131131

132+
/**
133+
* Returns and SDP with all the codecs except the given codec removed.
134+
*/
135+
export const preserveCodec = (
136+
sdp: string,
137+
mid: string,
138+
codec: RTCRtpCodec,
139+
): string => {
140+
const [kind, codecName] = codec.mimeType.toLowerCase().split('/');
141+
142+
const toSet = (fmtpLine: string) =>
143+
new Set(fmtpLine.split(';').map((f) => f.trim().toLowerCase()));
144+
145+
const equal = (a: Set<string>, b: Set<string>) => {
146+
if (a.size !== b.size) return false;
147+
for (const item of a) if (!b.has(item)) return false;
148+
return true;
149+
};
150+
151+
const codecFmtp = toSet(codec.sdpFmtpLine || '');
152+
const parsedSdp = SDP.parse(sdp);
153+
for (const media of parsedSdp.media) {
154+
if (media.type !== kind || String(media.mid) !== mid) continue;
155+
156+
// find the payload id of the desired codec
157+
const payloads = new Set<number>();
158+
for (const rtp of media.rtp) {
159+
if (
160+
rtp.codec.toLowerCase() === codecName &&
161+
media.fmtp.some(
162+
(f) => f.payload === rtp.payload && equal(toSet(f.config), codecFmtp),
163+
)
164+
) {
165+
payloads.add(rtp.payload);
166+
}
167+
}
168+
169+
// find the corresponding rtx codec by matching apt=<preserved-codec-payload>
170+
for (const fmtp of media.fmtp) {
171+
const match = fmtp.config.match(/(apt)=(\d+)/);
172+
if (!match) continue;
173+
const [, , preservedCodecPayload] = match;
174+
if (payloads.has(Number(preservedCodecPayload))) {
175+
payloads.add(fmtp.payload);
176+
}
177+
}
178+
179+
media.rtp = media.rtp.filter((r) => payloads.has(r.payload));
180+
media.fmtp = media.fmtp.filter((f) => payloads.has(f.payload));
181+
media.rtcpFb = media.rtcpFb?.filter((f) => payloads.has(f.payload));
182+
media.payloads = Array.from(payloads).join(' ');
183+
}
184+
return SDP.write(parsedSdp);
185+
};
186+
132187
/**
133188
* Enables high-quality audio through SDP munging for the given trackMid.
134189
*

packages/client/src/rtc/Publisher.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { PublishOptions } from '../types';
2020
import {
2121
enableHighQualityAudio,
2222
extractMid,
23+
preserveCodec,
2324
toggleDtx,
2425
} from '../helpers/sdp-munging';
2526
import { Logger } from '../coordinator/connection/types';
@@ -530,6 +531,12 @@ export class Publisher {
530531
if (this.isPublishing(TrackType.SCREEN_SHARE_AUDIO)) {
531532
offer.sdp = this.enableHighQualityAudio(offer.sdp);
532533
}
534+
if (this.isPublishing(TrackType.VIDEO)) {
535+
// Hotfix for platforms that don't respect the ordered codec list
536+
// (Firefox, Android, Linux, etc...).
537+
// We remove all the codecs from the SDP except the one we want to use.
538+
offer.sdp = this.removeUnpreferredCodecs(offer.sdp, TrackType.VIDEO);
539+
}
533540
}
534541

535542
const trackInfos = this.getAnnouncedTracks(offer.sdp);
@@ -564,6 +571,23 @@ export class Publisher {
564571
);
565572
};
566573

574+
private removeUnpreferredCodecs(sdp: string, trackType: TrackType): string {
575+
const opts = this.publishOptsForTrack.get(trackType);
576+
if (!opts || !opts.forceSingleCodec) return sdp;
577+
578+
const codec = opts.forceCodec || opts.preferredCodec;
579+
const orderedCodecs = this.getCodecPreferences(trackType, codec);
580+
if (!orderedCodecs || orderedCodecs.length === 0) return sdp;
581+
582+
const transceiver = this.transceiverCache.get(trackType);
583+
if (!transceiver) return sdp;
584+
585+
const index = this.transceiverInitOrder.indexOf(trackType);
586+
const mid = extractMid(transceiver, index, sdp);
587+
const [codecToPreserve] = orderedCodecs;
588+
return preserveCodec(sdp, mid, codecToPreserve);
589+
}
590+
567591
private enableHighQualityAudio = (sdp: string) => {
568592
const transceiver = this.transceiverCache.get(TrackType.SCREEN_SHARE_AUDIO);
569593
if (!transceiver) return sdp;

packages/client/src/rtc/codecs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const getPreferredCodecs = (
5050
}
5151

5252
const sdpFmtpLine = codec.sdpFmtpLine;
53-
if (!sdpFmtpLine || !sdpFmtpLine.includes('profile-level-id=42e01f')) {
53+
if (!sdpFmtpLine || !sdpFmtpLine.includes('profile-level-id=42')) {
5454
// this is not the baseline h264 codec, prioritize it lower
5555
partiallyPreferred.push(codec);
5656
continue;

packages/client/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ export type PublishOptions = {
167167
* Use with caution.
168168
*/
169169
forceCodec?: PreferredCodec;
170+
/**
171+
* When using a preferred codec, force the use of a single codec.
172+
* Enabling this, it will remove all other supported codecs from the SDP.
173+
* Defaults to false.
174+
*/
175+
forceSingleCodec?: boolean;
170176
/**
171177
* The preferred scalability to use when publishing the video stream.
172178
* Applicable only for SVC codecs.

packages/react-bindings/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
44

5+
## [1.1.21](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-bindings-1.1.20...@stream-io/video-react-bindings-1.1.21) (2024-11-21)
6+
7+
### Dependency Updates
8+
9+
* `@stream-io/video-client` updated to version `1.11.4`
510
## [1.1.20](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-bindings-1.1.19...@stream-io/video-react-bindings-1.1.20) (2024-11-20)
611

712
### Dependency Updates

packages/react-bindings/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stream-io/video-react-bindings",
3-
"version": "1.1.20",
3+
"version": "1.1.21",
44
"packageManager": "[email protected]",
55
"main": "./dist/index.cjs.js",
66
"module": "./dist/index.es.js",

packages/react-native-sdk/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
44

5+
## [1.3.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.3.5...@stream-io/video-react-native-sdk-1.3.6) (2024-11-21)
6+
7+
### Dependency Updates
8+
9+
* `@stream-io/video-client` updated to version `1.11.4`
10+
* `@stream-io/video-react-bindings` updated to version `1.1.21`
511
## [1.3.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-1.3.4...@stream-io/video-react-native-sdk-1.3.5) (2024-11-21)
612

713
### Dependency Updates

0 commit comments

Comments
 (0)