|
21 | 21 | TestRoomOptions, |
22 | 22 | }, |
23 | 23 | livekit::{ |
24 | | - options::VideoCodec, |
| 24 | + options::{TrackPublishOptions, VideoCodec}, |
25 | 25 | prelude::*, |
26 | 26 | track::{PublishingLayerQuality, VideoQuality}, |
27 | 27 | }, |
@@ -428,3 +428,110 @@ async fn test_dynacast_multiple_subscribers_only_publish_requested_tracks() -> R |
428 | 428 |
|
429 | 429 | Ok(()) |
430 | 430 | } |
| 431 | + |
| 432 | +/// Verifies dynacast behavior for an SVC track (VP9, L3T3_KEY). |
| 433 | +/// |
| 434 | +/// SVC tracks carry all spatial layers in a single encoded stream and the SFU |
| 435 | +/// selects layers server-side, so: |
| 436 | +/// 1. Subscriber quality requests must never deactivate the encoding while at |
| 437 | +/// least one quality is subscribed (any-enabled rule). |
| 438 | +/// 2. Unsubscribing the last subscriber deactivates the encoding. |
| 439 | +/// 3. Resubscribing reactivates it. |
| 440 | +#[cfg(feature = "__lk-e2e-test")] |
| 441 | +#[test_log::test(tokio::test)] |
| 442 | +async fn test_dynacast_svc() -> Result<()> { |
| 443 | + let mut pub_room_opts = RoomOptions::default(); |
| 444 | + pub_room_opts.dynacast = true; |
| 445 | + let pub_options = TestRoomOptions { room: pub_room_opts, ..Default::default() }; |
| 446 | + let sub_options = TestRoomOptions::default(); |
| 447 | + |
| 448 | + let mut rooms = test_rooms_with_options([pub_options, sub_options]).await?; |
| 449 | + let (pub_room, _pub_events) = rooms.remove(0); |
| 450 | + let (_sub_room, mut sub_events) = rooms.remove(0); |
| 451 | + |
| 452 | + let pub_room = Arc::new(pub_room); |
| 453 | + let solid_params = SolidColorParams { width: 1280, height: 720, luma: 128 }; |
| 454 | + let mut solid_track = SolidColorTrack::new(pub_room.clone(), solid_params); |
| 455 | + solid_track |
| 456 | + .publish_with_options(TrackPublishOptions { |
| 457 | + video_codec: VideoCodec::VP9, |
| 458 | + simulcast: false, |
| 459 | + scalability_mode: Some("L3T3_KEY".to_string()), |
| 460 | + ..Default::default() |
| 461 | + }) |
| 462 | + .await?; |
| 463 | + |
| 464 | + let sub_publication: RemoteTrackPublication = timeout(Duration::from_secs(15), async { |
| 465 | + loop { |
| 466 | + let Some(event) = sub_events.recv().await else { |
| 467 | + return Err(anyhow!("Event channel closed before TrackSubscribed")); |
| 468 | + }; |
| 469 | + if let RoomEvent::TrackSubscribed { publication, .. } = event { |
| 470 | + return Ok(publication); |
| 471 | + } |
| 472 | + } |
| 473 | + }) |
| 474 | + .await??; |
| 475 | + |
| 476 | + let pub_video_track = publisher_video_track(&pub_room)?; |
| 477 | + |
| 478 | + // --- Baseline: the single SVC encoding is active --- |
| 479 | + let layers = |
| 480 | + wait_for_layers(&pub_video_track, "svc baseline", Duration::from_secs(15), |layers| { |
| 481 | + layers.len() == 1 && layers[0].active |
| 482 | + }) |
| 483 | + .await?; |
| 484 | + log::info!("dynacast svc baseline layers: {:?}", layers); |
| 485 | + |
| 486 | + // --- Request LOW quality: the SVC encoding must stay active --- |
| 487 | + log::info!("dynacast svc test: requesting LOW quality"); |
| 488 | + sub_publication.set_video_quality(VideoQuality::Low); |
| 489 | + |
| 490 | + // The resulting quality update arrives asynchronously; poll to make sure |
| 491 | + // the encoding never gets deactivated by it. |
| 492 | + let deadline = tokio::time::Instant::now() + Duration::from_secs(10); |
| 493 | + while tokio::time::Instant::now() < deadline { |
| 494 | + let layers = pub_video_track.publishing_layers(); |
| 495 | + assert!( |
| 496 | + !layers.is_empty() && layers.iter().all(|layer| layer.active), |
| 497 | + "SVC encoding must stay active after LOW request, got {:?}", |
| 498 | + layers |
| 499 | + ); |
| 500 | + time::sleep(Duration::from_millis(250)).await; |
| 501 | + } |
| 502 | + |
| 503 | + // --- Request HIGH quality again: still active --- |
| 504 | + log::info!("dynacast svc test: requesting HIGH quality"); |
| 505 | + sub_publication.set_video_quality(VideoQuality::High); |
| 506 | + |
| 507 | + let deadline = tokio::time::Instant::now() + Duration::from_secs(10); |
| 508 | + while tokio::time::Instant::now() < deadline { |
| 509 | + let layers = pub_video_track.publishing_layers(); |
| 510 | + assert!( |
| 511 | + !layers.is_empty() && layers.iter().all(|layer| layer.active), |
| 512 | + "SVC encoding must stay active after HIGH request, got {:?}", |
| 513 | + layers |
| 514 | + ); |
| 515 | + time::sleep(Duration::from_millis(250)).await; |
| 516 | + } |
| 517 | + |
| 518 | + // --- Unsubscribe: with no subscribers left the encoding is deactivated --- |
| 519 | + log::info!("dynacast svc test: unsubscribing"); |
| 520 | + sub_publication.set_subscribed(false); |
| 521 | + |
| 522 | + wait_for_layers(&pub_video_track, "svc unsubscribed", Duration::from_secs(30), |layers| { |
| 523 | + !layers.is_empty() && layers.iter().all(|layer| !layer.active) |
| 524 | + }) |
| 525 | + .await?; |
| 526 | + |
| 527 | + // --- Resubscribe: the encoding comes back --- |
| 528 | + log::info!("dynacast svc test: resubscribing"); |
| 529 | + sub_publication.set_subscribed(true); |
| 530 | + |
| 531 | + wait_for_layers(&pub_video_track, "svc resubscribed", Duration::from_secs(30), |layers| { |
| 532 | + !layers.is_empty() && layers.iter().all(|layer| layer.active) |
| 533 | + }) |
| 534 | + .await?; |
| 535 | + |
| 536 | + Ok(()) |
| 537 | +} |
0 commit comments