Skip to content

Commit cda1dcd

Browse files
committed
feat: preferredCodec hint
1 parent 7ff2221 commit cda1dcd

File tree

9 files changed

+290
-34
lines changed

9 files changed

+290
-34
lines changed

packages/client/src/Call.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { StreamSfuClient } from './StreamSfuClient';
22
import {
33
Dispatcher,
4+
findCodec,
45
getGenericSdp,
56
isSfuEvent,
67
Publisher,
@@ -90,6 +91,7 @@ import { BehaviorSubject, Subject, takeWhile } from 'rxjs';
9091
import { ReconnectDetails } from './gen/video/sfu/event/events';
9192
import {
9293
ClientDetails,
94+
Codec,
9395
PublishOptions,
9496
TrackType,
9597
WebsocketReconnectStrategy,
@@ -120,6 +122,7 @@ import {
120122
import { getSdkSignature } from './stats/utils';
121123
import { withoutConcurrency } from './helpers/concurrency';
122124
import { ensureExhausted } from './helpers/ensureExhausted';
125+
import { getPayloadTypeForCodec } from './helpers/sdp-munging';
123126
import {
124127
PromiseWithResolvers,
125128
promiseWithResolvers,
@@ -805,6 +808,7 @@ export class Call {
805808
clientDetails,
806809
fastReconnect: performingFastReconnect,
807810
reconnectDetails,
811+
preferredCodec: this.getPreferredCodec(publishingCapabilitiesSdp),
808812
});
809813

810814
this.initialPublishOptions = publishOptions;
@@ -902,6 +906,27 @@ export class Call {
902906
};
903907
};
904908

909+
/**
910+
* Prepares the preferred codec for the call.
911+
* This is an experimental client feature and subject to change.
912+
* @internal
913+
*/
914+
private getPreferredCodec = (sdp: string): Codec | undefined => {
915+
const { preferredCodec } = this.clientPublishOptions || {};
916+
if (!preferredCodec) return;
917+
918+
const codec = findCodec(`video/${preferredCodec}`);
919+
if (!codec) return;
920+
921+
const { clockRate, mimeType, sdpFmtpLine } = codec;
922+
return Codec.create({
923+
name: preferredCodec, // e.g. 'vp9'
924+
fmtp: sdpFmtpLine || '',
925+
clockRate: clockRate,
926+
payloadType: getPayloadTypeForCodec(sdp, mimeType, sdpFmtpLine),
927+
});
928+
};
929+
905930
/**
906931
* Performs an ICE restart on both the Publisher and Subscriber Peer Connections.
907932
* Uses the provided SFU client to restore the ICE connection.
@@ -1510,15 +1535,15 @@ export class Call {
15101535
* @internal
15111536
* @param options the options to use.
15121537
*/
1513-
updatePublishOptions(options: ClientPublishOptions) {
1538+
updatePublishOptions = (options: ClientPublishOptions) => {
15141539
if (this.state.callingState === CallingState.JOINED) {
15151540
this.logger(
15161541
'warn',
15171542
'Cannot update publish options after joining the call',
15181543
);
15191544
}
15201545
this.clientPublishOptions = { ...this.clientPublishOptions, ...options };
1521-
}
1546+
};
15221547

15231548
/**
15241549
* Notifies the SFU that a noise cancellation process has started.

packages/client/src/gen/video/sfu/event/events.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@
33
// @generated from protobuf file "video/sfu/event/events.proto" (package "stream.video.sfu.event", syntax proto3)
44
// tslint:disable
55
import { MessageType } from '@protobuf-ts/runtime';
6-
import { CallEndedReason } from '../models/models';
7-
import { GoAwayReason } from '../models/models';
8-
import { CallGrants } from '../models/models';
9-
import { Codec } from '../models/models';
10-
import { ConnectionQuality } from '../models/models';
11-
import { PublishOptions } from '../models/models';
12-
import { CallState } from '../models/models';
6+
import {
7+
CallEndedReason,
8+
CallGrants,
9+
CallState,
10+
ClientDetails,
11+
Codec,
12+
ConnectionQuality,
13+
Error as Error$,
14+
GoAwayReason,
15+
ICETrickle as ICETrickle$,
16+
Participant,
17+
ParticipantCount,
18+
PeerType,
19+
Pin,
20+
PublishOption,
21+
PublishOptions,
22+
TrackInfo,
23+
TrackType,
24+
TrackUnpublishReason,
25+
WebsocketReconnectStrategy,
26+
} from '../models/models';
1327
import { TrackSubscriptionDetails } from '../signal_rpc/signal';
14-
import { TrackInfo } from '../models/models';
15-
import { ClientDetails } from '../models/models';
16-
import { TrackUnpublishReason } from '../models/models';
17-
import { Participant } from '../models/models';
18-
import { TrackType } from '../models/models';
19-
import { ParticipantCount } from '../models/models';
20-
import { PeerType } from '../models/models';
21-
import { WebsocketReconnectStrategy } from '../models/models';
22-
import { Error as Error$ } from '../models/models';
23-
import { Pin } from '../models/models';
24-
import { PublishOption } from '../models/models';
25-
import { ICETrickle as ICETrickle$ } from '../models/models';
28+
2629
/**
2730
* SFUEvent is a message that is sent from the SFU to the client.
2831
*
@@ -504,6 +507,10 @@ export interface JoinRequest {
504507
* @generated from protobuf field: stream.video.sfu.event.ReconnectDetails reconnect_details = 7;
505508
*/
506509
reconnectDetails?: ReconnectDetails;
510+
/**
511+
* @generated from protobuf field: stream.video.sfu.models.Codec preferred_codec = 9;
512+
*/
513+
preferredCodec?: Codec;
507514
}
508515
/**
509516
* @generated from protobuf message stream.video.sfu.event.ReconnectDetails
@@ -1306,6 +1313,7 @@ class JoinRequest$Type extends MessageType<JoinRequest> {
13061313
kind: 'message',
13071314
T: () => ReconnectDetails,
13081315
},
1316+
{ no: 9, name: 'preferred_codec', kind: 'message', T: () => Codec },
13091317
]);
13101318
}
13111319
}

packages/client/src/gen/video/sfu/models/models.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { MessageType } from '@protobuf-ts/runtime';
66
import { Struct } from '../../../google/protobuf/struct';
77
import { Timestamp } from '../../../google/protobuf/timestamp';
8+
89
/**
910
* CallState is the current state of the call
1011
* as seen by an SFU.
@@ -236,7 +237,7 @@ export interface PublishOption {
236237
*/
237238
export interface Codec {
238239
/**
239-
* @generated from protobuf field: uint32 payload_type = 11;
240+
* @generated from protobuf field: uint32 payload_type = 16;
240241
*/
241242
payloadType: number;
242243
/**
@@ -248,7 +249,7 @@ export interface Codec {
248249
*/
249250
clockRate: number;
250251
/**
251-
* @generated from protobuf field: string encoding_parameters = 13;
252+
* @generated from protobuf field: string encoding_parameters = 15;
252253
*/
253254
encodingParameters: string;
254255
/**
@@ -1160,7 +1161,7 @@ class Codec$Type extends MessageType<Codec> {
11601161
constructor() {
11611162
super('stream.video.sfu.models.Codec', [
11621163
{
1163-
no: 11,
1164+
no: 16,
11641165
name: 'payload_type',
11651166
kind: 'scalar',
11661167
T: 13 /*ScalarType.UINT32*/,
@@ -1173,7 +1174,7 @@ class Codec$Type extends MessageType<Codec> {
11731174
T: 13 /*ScalarType.UINT32*/,
11741175
},
11751176
{
1176-
no: 13,
1177+
no: 15,
11771178
name: 'encoding_parameters',
11781179
kind: 'scalar',
11791180
T: 9 /*ScalarType.STRING*/,
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
export const publisherSDP = `
2+
v=0
3+
o=- 6786973741786618802 2 IN IP4 127.0.0.1
4+
s=-
5+
t=0 0
6+
a=group:BUNDLE 0 1
7+
a=extmap-allow-mixed
8+
a=msid-semantic: WMS
9+
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
10+
c=IN IP4 0.0.0.0
11+
a=rtcp:9 IN IP4 0.0.0.0
12+
a=ice-ufrag:PeYM
13+
a=ice-pwd:GDZQ30hwMK+5m/uVP3C0EQvf
14+
a=ice-options:trickle
15+
a=fingerprint:sha-256 E4:D3:D1:D8:70:08:6C:44:51:C5:0C:7E:AB:01:06:FF:10:A7:39:38:5B:43:C4:BA:FA:68:DA:AA:43:95:2C:32
16+
a=setup:actpass
17+
a=mid:0
18+
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
19+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
20+
a=extmap:3 urn:3gpp:video-orientation
21+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
22+
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
23+
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
24+
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
25+
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
26+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
27+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
28+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
29+
a=sendonly
30+
a=msid:- 22bcd911-0a07-4b69-9557-8f4746411089
31+
a=rtcp-mux
32+
a=rtcp-rsize
33+
a=rtpmap:96 VP8/90000
34+
a=rtcp-fb:96 goog-remb
35+
a=rtcp-fb:96 transport-cc
36+
a=rtcp-fb:96 ccm fir
37+
a=rtcp-fb:96 nack
38+
a=rtcp-fb:96 nack pli
39+
a=rtpmap:97 rtx/90000
40+
a=fmtp:97 apt=96
41+
a=rtpmap:102 H264/90000
42+
a=rtcp-fb:102 goog-remb
43+
a=rtcp-fb:102 transport-cc
44+
a=rtcp-fb:102 ccm fir
45+
a=rtcp-fb:102 nack
46+
a=rtcp-fb:102 nack pli
47+
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
48+
a=rtpmap:103 rtx/90000
49+
a=fmtp:103 apt=102
50+
a=rtpmap:104 H264/90000
51+
a=rtcp-fb:104 goog-remb
52+
a=rtcp-fb:104 transport-cc
53+
a=rtcp-fb:104 ccm fir
54+
a=rtcp-fb:104 nack
55+
a=rtcp-fb:104 nack pli
56+
a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
57+
a=rtpmap:105 rtx/90000
58+
a=fmtp:105 apt=104
59+
a=rtpmap:106 H264/90000
60+
a=rtcp-fb:106 goog-remb
61+
a=rtcp-fb:106 transport-cc
62+
a=rtcp-fb:106 ccm fir
63+
a=rtcp-fb:106 nack
64+
a=rtcp-fb:106 nack pli
65+
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
66+
a=rtpmap:107 rtx/90000
67+
a=fmtp:107 apt=106
68+
a=rtpmap:108 H264/90000
69+
a=rtcp-fb:108 goog-remb
70+
a=rtcp-fb:108 transport-cc
71+
a=rtcp-fb:108 ccm fir
72+
a=rtcp-fb:108 nack
73+
a=rtcp-fb:108 nack pli
74+
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
75+
a=rtpmap:109 rtx/90000
76+
a=fmtp:109 apt=108
77+
a=rtpmap:127 H264/90000
78+
a=rtcp-fb:127 goog-remb
79+
a=rtcp-fb:127 transport-cc
80+
a=rtcp-fb:127 ccm fir
81+
a=rtcp-fb:127 nack
82+
a=rtcp-fb:127 nack pli
83+
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
84+
a=rtpmap:125 rtx/90000
85+
a=fmtp:125 apt=127
86+
a=rtpmap:39 H264/90000
87+
a=rtcp-fb:39 goog-remb
88+
a=rtcp-fb:39 transport-cc
89+
a=rtcp-fb:39 ccm fir
90+
a=rtcp-fb:39 nack
91+
a=rtcp-fb:39 nack pli
92+
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
93+
a=rtpmap:40 rtx/90000
94+
a=fmtp:40 apt=39
95+
a=rtpmap:45 AV1/90000
96+
a=rtcp-fb:45 goog-remb
97+
a=rtcp-fb:45 transport-cc
98+
a=rtcp-fb:45 ccm fir
99+
a=rtcp-fb:45 nack
100+
a=rtcp-fb:45 nack pli
101+
a=fmtp:45 level-idx=5;profile=0;tier=0
102+
a=rtpmap:46 rtx/90000
103+
a=fmtp:46 apt=45
104+
a=rtpmap:98 VP9/90000
105+
a=rtcp-fb:98 goog-remb
106+
a=rtcp-fb:98 transport-cc
107+
a=rtcp-fb:98 ccm fir
108+
a=rtcp-fb:98 nack
109+
a=rtcp-fb:98 nack pli
110+
a=fmtp:98 profile-id=0
111+
a=rtpmap:99 rtx/90000
112+
a=fmtp:99 apt=98
113+
a=rtpmap:100 VP9/90000
114+
a=rtcp-fb:100 goog-remb
115+
a=rtcp-fb:100 transport-cc
116+
a=rtcp-fb:100 ccm fir
117+
a=rtcp-fb:100 nack
118+
a=rtcp-fb:100 nack pli
119+
a=fmtp:100 profile-id=2
120+
a=rtpmap:101 rtx/90000
121+
a=fmtp:101 apt=100
122+
a=rtpmap:112 H264/90000
123+
a=rtcp-fb:112 goog-remb
124+
a=rtcp-fb:112 transport-cc
125+
a=rtcp-fb:112 ccm fir
126+
a=rtcp-fb:112 nack
127+
a=rtcp-fb:112 nack pli
128+
a=fmtp:112 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
129+
a=rtpmap:113 rtx/90000
130+
a=fmtp:113 apt=112
131+
a=rtpmap:116 red/90000
132+
a=rtpmap:117 rtx/90000
133+
a=fmtp:117 apt=116
134+
a=rtpmap:118 ulpfec/90000
135+
a=ssrc-group:FID 3856340930 368490694
136+
a=ssrc:3856340930 cname:BnMV9wuzGoHGeFUb
137+
a=ssrc:3856340930 msid:- 22bcd911-0a07-4b69-9557-8f4746411089
138+
a=ssrc:368490694 cname:BnMV9wuzGoHGeFUb
139+
a=ssrc:368490694 msid:- 22bcd911-0a07-4b69-9557-8f4746411089
140+
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126
141+
c=IN IP4 0.0.0.0
142+
a=rtcp:9 IN IP4 0.0.0.0
143+
a=ice-ufrag:PeYM
144+
a=ice-pwd:GDZQ30hwMK+5m/uVP3C0EQvf
145+
a=ice-options:trickle
146+
a=fingerprint:sha-256 E4:D3:D1:D8:70:08:6C:44:51:C5:0C:7E:AB:01:06:FF:10:A7:39:38:5B:43:C4:BA:FA:68:DA:AA:43:95:2C:32
147+
a=setup:actpass
148+
a=mid:1
149+
a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level
150+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
151+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
152+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
153+
a=sendonly
154+
a=msid:- 440e2b2c-de05-4a72-9c30-317a0d18d32c
155+
a=rtcp-mux
156+
a=rtcp-rsize
157+
a=rtpmap:111 opus/48000/2
158+
a=rtcp-fb:111 transport-cc
159+
a=fmtp:111 minptime=10;useinbandfec=1
160+
a=rtpmap:63 red/48000/2
161+
a=fmtp:63 111/111
162+
a=rtpmap:9 G722/8000
163+
a=rtpmap:0 PCMU/8000
164+
a=rtpmap:8 PCMA/8000
165+
a=rtpmap:13 CN/8000
166+
a=rtpmap:110 telephone-event/48000
167+
a=rtpmap:126 telephone-event/8000
168+
a=ssrc:2019539886 cname:BnMV9wuzGoHGeFUb
169+
a=ssrc:2019539886 msid:- 440e2b2c-de05-4a72-9c30-317a0d18d32c
170+
`;
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import { describe, expect, it } from 'vitest';
2-
import { enableHighQualityAudio } from '../sdp-munging';
2+
import { enableHighQualityAudio, getPayloadTypeForCodec } from '../sdp-munging';
33
import { initialSdp as HQAudioSDP } from './hq-audio-sdp';
4+
import { publisherSDP } from './publisher-sdp.mock';
45

56
describe('sdp-munging', () => {
67
it('enables HighQuality audio for Opus', () => {
78
const sdpWithHighQualityAudio = enableHighQualityAudio(HQAudioSDP, '3');
89
expect(sdpWithHighQualityAudio).toContain('maxaveragebitrate=510000');
910
expect(sdpWithHighQualityAudio).toContain('stereo=1');
1011
});
12+
13+
it('extracts payload type for codec', () => {
14+
const payload = getPayloadTypeForCodec(
15+
publisherSDP,
16+
'video/vp9',
17+
'profile-id=2',
18+
);
19+
expect(payload).toBe(100);
20+
});
1121
});

0 commit comments

Comments
 (0)