1515import 'package:flutter_test/flutter_test.dart' ;
1616import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
1717
18- import 'package:livekit_client/livekit_client.dart' as lk ;
18+ import 'package:livekit_client/livekit_client.dart' ;
1919import 'package:livekit_client/src/utils.dart' ;
2020
2121void main () {
@@ -57,22 +57,187 @@ void main() {
5757 );
5858 });
5959
60+ group ('simulcast encodings' , () {
61+ test ('same-resolution lower layer is clamped to top encoding' , () {
62+ final presets = Utils .computeSimulcastPresets (
63+ dimensions: const VideoDimensions (1280 , 720 ),
64+ original: const VideoParameters (
65+ dimensions: VideoDimensions (1280 , 720 ),
66+ encoding: VideoEncoding (maxBitrate: 1500000 , maxFramerate: 24 ),
67+ ),
68+ requestedPresets: const [
69+ VideoParametersPresets .h720_169,
70+ VideoParametersPresets .h360_169,
71+ ],
72+ isScreenShare: false ,
73+ );
74+
75+ expect (presets, hasLength (3 ));
76+
77+ expect (presets[0 ], VideoParametersPresets .h360_169);
78+
79+ expect (presets[1 ].dimensions, VideoDimensionsPresets .h720_169);
80+ expect (presets[1 ].encoding? .maxFramerate, 24 );
81+ expect (presets[1 ].encoding? .maxBitrate, 1500000 );
82+
83+ expect (presets[2 ].dimensions, const VideoDimensions (1280 , 720 ));
84+ expect (presets[2 ].encoding? .maxFramerate, 24 );
85+ expect (presets[2 ].encoding? .maxBitrate, 1500000 );
86+ });
87+
88+ test ('lower-resolution layer clamps framerate but preserves preset bitrate' , () {
89+ final presets = Utils .computeSimulcastPresets (
90+ dimensions: const VideoDimensions (1280 , 720 ),
91+ original: const VideoParameters (
92+ dimensions: VideoDimensions (1280 , 720 ),
93+ encoding: VideoEncoding (maxBitrate: 500000 , maxFramerate: 15 ),
94+ ),
95+ requestedPresets: const [],
96+ isScreenShare: false ,
97+ );
98+
99+ expect (presets, hasLength (3 ));
100+ expect (presets[1 ].dimensions, VideoDimensionsPresets .h360_169);
101+ expect (presets[1 ].encoding? .maxFramerate, 15 );
102+ expect (presets[1 ].encoding? .maxBitrate, 450000 );
103+ });
104+
105+ test ('same-resolution full clamp' , () {
106+ final presets = Utils .computeSimulcastPresets (
107+ dimensions: const VideoDimensions (854 , 480 ),
108+ original: const VideoParameters (
109+ dimensions: VideoDimensions (854 , 480 ),
110+ encoding: VideoEncoding (maxBitrate: 600000 , maxFramerate: 15 ),
111+ ),
112+ requestedPresets: const [
113+ VideoParameters (
114+ dimensions: VideoDimensions (854 , 480 ),
115+ encoding: VideoEncoding (maxBitrate: 2000000 , maxFramerate: 30 ),
116+ ),
117+ ],
118+ isScreenShare: false ,
119+ );
120+
121+ expect (presets, hasLength (2 ));
122+ expect (presets[0 ].encoding? .maxFramerate, 15 );
123+ expect (presets[0 ].encoding? .maxBitrate, 600000 );
124+ expect (presets[1 ].dimensions, const VideoDimensions (854 , 480 ));
125+ expect (presets[1 ].encoding? .maxFramerate, 15 );
126+ expect (presets[1 ].encoding? .maxBitrate, 600000 );
127+ });
128+
129+ test ('ladder length follows the larger output dimension' , () {
130+ const cases = [
131+ (VideoDimensions (320 , 240 ), 1 ),
132+ (VideoDimensions (640 , 480 ), 2 ),
133+ (VideoDimensions (1280 , 720 ), 3 ),
134+ ];
135+
136+ for (final (dimensions, expectedCount) in cases) {
137+ final presets = Utils .computeSimulcastPresets (
138+ dimensions: dimensions,
139+ original: VideoParameters (
140+ dimensions: dimensions,
141+ encoding: const VideoEncoding (maxBitrate: 1000000 , maxFramerate: 30 ),
142+ ),
143+ requestedPresets: const [],
144+ isScreenShare: false ,
145+ );
146+
147+ expect (presets, hasLength (expectedCount), reason: 'dimensions=$dimensions ' );
148+ }
149+ });
150+
151+ test ("presets that don't overshoot are passed through unchanged" , () {
152+ const original = VideoParameters (
153+ dimensions: VideoDimensions (1920 , 1080 ),
154+ encoding: VideoEncoding (maxBitrate: 5000000 , maxFramerate: 30 ),
155+ );
156+
157+ final presets = Utils .computeSimulcastPresets (
158+ dimensions: const VideoDimensions (1920 , 1080 ),
159+ original: original,
160+ requestedPresets: const [
161+ VideoParametersPresets .h360_169,
162+ VideoParametersPresets .h720_169,
163+ ],
164+ isScreenShare: false ,
165+ );
166+
167+ expect (presets, hasLength (3 ));
168+ expect (presets[0 ], VideoParametersPresets .h360_169);
169+ expect (presets[1 ], VideoParametersPresets .h720_169);
170+ expect (presets[2 ], original);
171+ });
172+
173+ test ('clamped layer carries forward per-layer priorities' , () {
174+ const prioritized = VideoParameters (
175+ dimensions: VideoDimensions (1280 , 720 ),
176+ encoding: VideoEncoding (
177+ maxBitrate: 1700000 ,
178+ maxFramerate: 30 ,
179+ bitratePriority: Priority .high,
180+ networkPriority: Priority .high,
181+ ),
182+ );
183+
184+ final presets = Utils .computeSimulcastPresets (
185+ dimensions: const VideoDimensions (1280 , 720 ),
186+ original: const VideoParameters (
187+ dimensions: VideoDimensions (1280 , 720 ),
188+ encoding: VideoEncoding (maxBitrate: 1500000 , maxFramerate: 24 ),
189+ ),
190+ requestedPresets: const [
191+ VideoParametersPresets .h360_169,
192+ prioritized,
193+ ],
194+ isScreenShare: false ,
195+ );
196+
197+ expect (presets, hasLength (3 ));
198+ expect (presets[1 ].encoding? .maxFramerate, 24 );
199+ expect (presets[1 ].encoding? .maxBitrate, 1500000 );
200+ expect (presets[1 ].encoding? .bitratePriority, Priority .high);
201+ expect (presets[1 ].encoding? .networkPriority, Priority .high);
202+ });
203+
204+ test ('computed encodings use clamped presets' , () {
205+ final encodings = Utils .computeVideoEncodings (
206+ isScreenShare: false ,
207+ dimensions: const VideoDimensions (1280 , 720 ),
208+ options: const VideoPublishOptions (
209+ videoEncoding: VideoEncoding (maxBitrate: 1500000 , maxFramerate: 24 ),
210+ videoSimulcastLayers: [
211+ VideoParametersPresets .h720_169,
212+ VideoParametersPresets .h360_169,
213+ ],
214+ ),
215+ );
216+
217+ expect (encodings, hasLength (3 ));
218+ expect (encodings! [1 ].rid, 'h' );
219+ expect (encodings[1 ].maxFramerate, 24 );
220+ expect (encodings[1 ].maxBitrate, 1500000 );
221+ expect (encodings[1 ].scaleResolutionDownBy, 1 );
222+ });
223+ });
224+
60225 group ('screen share simulcast encodings' , () {
61226 test ('screen share preset bitrates match common SDK presets' , () {
62- expect (lk. VideoParametersPresets .screenShareH720FPS5.encoding? .maxBitrate, 800000 );
63- expect (lk. VideoParametersPresets .screenShareH1080FPS30.encoding? .maxBitrate, 5000000 );
227+ expect (VideoParametersPresets .screenShareH720FPS5.encoding? .maxBitrate, 800000 );
228+ expect (VideoParametersPresets .screenShareH1080FPS30.encoding? .maxBitrate, 5000000 );
64229 });
65230
66231 test ('default lower layer follows top layer fps and priorities' , () {
67232 final encodings = Utils .computeVideoEncodings (
68233 isScreenShare: true ,
69- dimensions: const lk. VideoDimensions (1920 , 1080 ),
70- options: const lk. VideoPublishOptions (
71- screenShareEncoding: lk. VideoEncoding (
234+ dimensions: const VideoDimensions (1920 , 1080 ),
235+ options: const VideoPublishOptions (
236+ screenShareEncoding: VideoEncoding (
72237 maxBitrate: 2500000 ,
73238 maxFramerate: 15 ,
74- bitratePriority: lk. Priority .high,
75- networkPriority: lk. Priority .high,
239+ bitratePriority: Priority .high,
240+ networkPriority: Priority .high,
76241 ),
77242 ),
78243 );
@@ -91,7 +256,7 @@ void main() {
91256 test ('default lower layer follows selected screen share preset' , () {
92257 final encodings = Utils .computeVideoEncodings (
93258 isScreenShare: true ,
94- dimensions: const lk. VideoDimensions (1920 , 1080 ),
259+ dimensions: const VideoDimensions (1920 , 1080 ),
95260 );
96261
97262 expect (encodings, hasLength (2 ));
0 commit comments