Commit 3b36d27
authored
feat(realtime): cap capture FPS at 30 via ideal/max constraint (#141)
## Summary
Realtime models now request capture FPS via an `{ ideal: 30, max: 30 }`
constraint instead of a hardcoded per-model rate (was 20 for Lucy 2.1,
22 for Lucy Restyle 2). The camera delivers its native rate (typically
24/25/30) and the server resamples to whatever the model needs.
## Why
- Decouples the SDK from server-side FPS support — bumping a model's
supported rate no longer requires a client release.
- Avoids client-side frame duplication / dropping when the camera's
native rate doesn't match the model's rate. Resampling now happens once,
on the server, where it can be done correctly with PTS-aware logic.
- Keeps a steady frame supply ahead of inference so the GPU isn't
waiting on the next frame.
## User-facing impact
None for the documented pattern — `frameRate: model.fps` in
`getUserMedia` still compiles and works, because
`MediaTrackConstraints["frameRate"]` natively accepts both `number` and
constraint-object forms:
```ts
const model = models.realtime("lucy-2.1");
const stream = await navigator.mediaDevices.getUserMedia({
video: { frameRate: model.fps, width: model.width, height: model.height },
});
```
Video/image model `fps` stays a `number` (it's output-rate metadata, not
a capture constraint); the type narrowing is enforced at the
`models.video(...)` / `models.image(...)` getter boundary so consumers
of those registries see no change.
## Test plan
- [x] `pnpm typecheck` passes
- [x] `pnpm test` — 199/199 unit tests pass (added regression guard that
video/image `fps` stays typed as `number`)
- [ ] `pnpm test:e2e:realtime` against staging across all realtime
models
- [ ] Manual browser check: `track.getSettings().frameRate` is ≤ 30
across cameras with native 24/25/30/60 rates
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes the public `models.realtime(...).fps` shape from a number to a
constraint object, which may break consumers doing numeric operations or
passing it to APIs expecting a scalar. Runtime behavior changes
capture/mirroring FPS handling, so regressions would show up as altered
stream cadence or compatibility issues in edge browsers.
>
> **Overview**
> **Realtime model FPS is now represented as a capture constraint
object** rather than a fixed per-model number. The realtime registry
entries switch to `fps: { ideal: 30, max: 30 }`, and
`modelDefinitionSchema`/`ModelDefinition` are updated to accept either a
number or constraint-object form.
>
> Adds `resolveFpsNumber()` to derive a scalar FPS when needed, and uses
it in `realtime/client.ts` when creating mirrored streams. Tests are
updated to assert the new realtime `fps` shape, add coverage for
`resolveFpsNumber`, and ensure `models.video(...)`/`models.image(...)`
still expose `fps` as a `number`; the realtime E2E synthetic stream now
captures at 30 FPS explicitly.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
49342d1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent da6e227 commit 3b36d27
4 files changed
Lines changed: 82 additions & 31 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
3 | 8 | | |
4 | 9 | | |
5 | 10 | | |
| |||
153 | 158 | | |
154 | 159 | | |
155 | 160 | | |
156 | | - | |
| 161 | + | |
157 | 162 | | |
158 | 163 | | |
159 | 164 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
273 | 273 | | |
274 | 274 | | |
275 | 275 | | |
| 276 | + | |
| 277 | + | |
276 | 278 | | |
277 | 279 | | |
278 | 280 | | |
279 | 281 | | |
280 | | - | |
| 282 | + | |
281 | 283 | | |
282 | 284 | | |
283 | 285 | | |
284 | 286 | | |
285 | 287 | | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
286 | 293 | | |
287 | 294 | | |
288 | 295 | | |
| |||
297 | 304 | | |
298 | 305 | | |
299 | 306 | | |
300 | | - | |
| 307 | + | |
301 | 308 | | |
302 | 309 | | |
303 | 310 | | |
304 | 311 | | |
305 | 312 | | |
306 | 313 | | |
307 | | - | |
| 314 | + | |
308 | 315 | | |
309 | 316 | | |
310 | 317 | | |
311 | 318 | | |
312 | 319 | | |
313 | | - | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
314 | 329 | | |
315 | 330 | | |
316 | 331 | | |
| |||
322 | 337 | | |
323 | 338 | | |
324 | 339 | | |
325 | | - | |
| 340 | + | |
326 | 341 | | |
327 | 342 | | |
328 | 343 | | |
329 | 344 | | |
330 | 345 | | |
331 | 346 | | |
332 | 347 | | |
333 | | - | |
| 348 | + | |
334 | 349 | | |
335 | 350 | | |
336 | 351 | | |
337 | 352 | | |
338 | 353 | | |
339 | 354 | | |
340 | 355 | | |
341 | | - | |
| 356 | + | |
342 | 357 | | |
343 | 358 | | |
344 | 359 | | |
345 | 360 | | |
346 | 361 | | |
347 | 362 | | |
348 | 363 | | |
349 | | - | |
| 364 | + | |
350 | 365 | | |
351 | 366 | | |
352 | 367 | | |
| |||
355 | 370 | | |
356 | 371 | | |
357 | 372 | | |
358 | | - | |
| 373 | + | |
359 | 374 | | |
360 | 375 | | |
361 | 376 | | |
| |||
364 | 379 | | |
365 | 380 | | |
366 | 381 | | |
367 | | - | |
| 382 | + | |
368 | 383 | | |
369 | 384 | | |
370 | 385 | | |
371 | 386 | | |
372 | 387 | | |
373 | 388 | | |
374 | 389 | | |
375 | | - | |
| 390 | + | |
376 | 391 | | |
377 | 392 | | |
378 | 393 | | |
| |||
381 | 396 | | |
382 | 397 | | |
383 | 398 | | |
384 | | - | |
| 399 | + | |
385 | 400 | | |
386 | 401 | | |
387 | 402 | | |
388 | 403 | | |
389 | 404 | | |
390 | 405 | | |
391 | 406 | | |
392 | | - | |
| 407 | + | |
393 | 408 | | |
394 | 409 | | |
395 | 410 | | |
396 | 411 | | |
397 | 412 | | |
398 | 413 | | |
399 | 414 | | |
400 | | - | |
| 415 | + | |
401 | 416 | | |
402 | 417 | | |
403 | 418 | | |
| |||
616 | 631 | | |
617 | 632 | | |
618 | 633 | | |
619 | | - | |
| 634 | + | |
620 | 635 | | |
621 | 636 | | |
622 | 637 | | |
623 | 638 | | |
624 | 639 | | |
625 | | - | |
| 640 | + | |
626 | 641 | | |
627 | 642 | | |
628 | 643 | | |
629 | 644 | | |
630 | 645 | | |
631 | 646 | | |
632 | 647 | | |
633 | | - | |
| 648 | + | |
634 | 649 | | |
635 | 650 | | |
636 | 651 | | |
637 | 652 | | |
638 | 653 | | |
639 | | - | |
| 654 | + | |
640 | 655 | | |
641 | 656 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
| 40 | + | |
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
| |||
1159 | 1160 | | |
1160 | 1161 | | |
1161 | 1162 | | |
1162 | | - | |
| 1163 | + | |
1163 | 1164 | | |
1164 | 1165 | | |
1165 | 1166 | | |
| |||
1168 | 1169 | | |
1169 | 1170 | | |
1170 | 1171 | | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
1171 | 1202 | | |
1172 | 1203 | | |
1173 | 1204 | | |
| |||
3596 | 3627 | | |
3597 | 3628 | | |
3598 | 3629 | | |
3599 | | - | |
| 3630 | + | |
3600 | 3631 | | |
3601 | 3632 | | |
3602 | 3633 | | |
| |||
3605 | 3636 | | |
3606 | 3637 | | |
3607 | 3638 | | |
3608 | | - | |
| 3639 | + | |
3609 | 3640 | | |
3610 | 3641 | | |
3611 | 3642 | | |
| |||
3614 | 3645 | | |
3615 | 3646 | | |
3616 | 3647 | | |
3617 | | - | |
| 3648 | + | |
3618 | 3649 | | |
3619 | 3650 | | |
3620 | 3651 | | |
3621 | 3652 | | |
3622 | 3653 | | |
3623 | 3654 | | |
3624 | 3655 | | |
3625 | | - | |
| 3656 | + | |
3626 | 3657 | | |
3627 | 3658 | | |
3628 | 3659 | | |
| |||
3682 | 3713 | | |
3683 | 3714 | | |
3684 | 3715 | | |
3685 | | - | |
| 3716 | + | |
3686 | 3717 | | |
3687 | 3718 | | |
3688 | 3719 | | |
| |||
3691 | 3722 | | |
3692 | 3723 | | |
3693 | 3724 | | |
3694 | | - | |
| 3725 | + | |
3695 | 3726 | | |
3696 | 3727 | | |
3697 | 3728 | | |
| |||
3700 | 3731 | | |
3701 | 3732 | | |
3702 | 3733 | | |
3703 | | - | |
| 3734 | + | |
3704 | 3735 | | |
3705 | 3736 | | |
3706 | 3737 | | |
| |||
0 commit comments