Skip to content

Commit 39f5637

Browse files
mr comments, event driven
1 parent fd1863c commit 39f5637

5 files changed

Lines changed: 71 additions & 100 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Use this SDK to add realtime video, audio and data features to your Rust app. By
2727
- [x] Simulcast
2828
- [x] SVC codecs (AV1/VP9)
2929
- [ ] Adaptive Streaming
30-
- [ ] Dynacast
30+
- [x] Dynacast
3131
- [x] Hardware video enc/dec
3232
- [x] H.264, H.265 using VideoToolbox (MacOS/iOS)
3333
- [x] H.264, H.265 on NVidia discrete GPUs (Linux)

examples/local_video/src/publisher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1664,7 +1664,7 @@ async fn run_capture_loop(
16641664
"{}({})={}",
16651665
layer.rid,
16661666
layer.quality,
1667-
if layer.active { "ON" } else { "off" }
1667+
if layer.active { "on" } else { "off" }
16681668
)
16691669
})
16701670
.collect::<Vec<_>>()

livekit/src/room/options.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -347,17 +347,15 @@ pub fn spatial_layers_from_scalability_mode(mode: &str) -> u32 {
347347
1
348348
}
349349

350-
pub(crate) fn default_video_quality() -> proto::VideoQuality {
351-
// A single encoding, or one without a recognized RID, represents the full-quality layer.
352-
proto::VideoQuality::High
353-
}
350+
/// A single encoding, or one without a recognized RID, represents the full-quality layer.
351+
pub(crate) const DEFAULT_VIDEO_QUALITY: proto::VideoQuality = proto::VideoQuality::High;
354352

355353
pub(crate) fn video_quality_for_rid_or_default(rid: &str) -> proto::VideoQuality {
356-
video_quality_for_rid(rid).unwrap_or_else(default_video_quality)
354+
video_quality_for_rid(rid).unwrap_or(DEFAULT_VIDEO_QUALITY)
357355
}
358356

359357
pub(crate) fn video_quality_from_i32_or_default(quality: i32) -> proto::VideoQuality {
360-
proto::VideoQuality::try_from(quality).unwrap_or_else(|_| default_video_quality())
358+
proto::VideoQuality::try_from(quality).unwrap_or(DEFAULT_VIDEO_QUALITY)
361359
}
362360

363361
pub fn video_layers_from_encodings(
@@ -367,7 +365,7 @@ pub fn video_layers_from_encodings(
367365
) -> Vec<proto::VideoLayer> {
368366
if encodings.is_empty() {
369367
return vec![proto::VideoLayer {
370-
quality: default_video_quality() as i32,
368+
quality: DEFAULT_VIDEO_QUALITY as i32,
371369
width,
372370
height,
373371
bitrate: 0,

livekit/src/room/track/local_video_track.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ impl From<PublishingLayerQuality> for proto::VideoQuality {
8686

8787
impl Display for PublishingLayerQuality {
8888
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89-
write!(f, "{:?}", self)
89+
match self {
90+
Self::Low => write!(f, "low"),
91+
Self::Medium => write!(f, "medium"),
92+
Self::High => write!(f, "high"),
93+
Self::Off => write!(f, "off"),
94+
}
9095
}
9196
}
9297

@@ -388,7 +393,7 @@ impl LocalVideoTrack {
388393
.map_err(|e| RoomError::Internal(format!("failed to set sender parameters: {}", e)))?;
389394

390395
if changed {
391-
log::info!("dynacast: layers changed -> [{}]", layers.join(", "));
396+
log::debug!("dynacast: layers changed -> [{}]", layers.join(", "));
392397
} else {
393398
log::debug!("dynacast: layers unchanged [{}]", layers.join(", "));
394399
}

livekit/tests/dynacast_test.rs

Lines changed: 57 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,6 @@ fn publisher_video_track(room: &Room) -> Result<LocalVideoTrack> {
4545
Err(anyhow!("No local video track publication found"))
4646
}
4747

48-
/// Returns the publisher's local video tracks keyed by track SID.
49-
#[cfg(feature = "__lk-e2e-test")]
50-
fn publisher_video_tracks(room: &Room) -> HashMap<TrackSid, LocalVideoTrack> {
51-
room.local_participant()
52-
.track_publications()
53-
.into_values()
54-
.filter_map(|publication| {
55-
let sid = publication.sid();
56-
let Some(LocalTrack::Video(track)) = publication.track() else {
57-
return None;
58-
};
59-
Some((sid, track))
60-
})
61-
.collect()
62-
}
63-
6448
/// Polls `publishing_layers()` until the `check` predicate returns true, or times out.
6549
#[cfg(feature = "__lk-e2e-test")]
6650
async fn wait_for_layers(
@@ -87,62 +71,68 @@ async fn wait_for_layers(
8771
}
8872
}
8973

90-
/// Waits for the publisher to expose `expected_count` local video tracks.
74+
/// Waits for the publisher's next local video track publication.
9175
#[cfg(feature = "__lk-e2e-test")]
92-
async fn wait_for_publisher_video_tracks(
93-
room: &Room,
94-
expected_count: usize,
76+
async fn wait_for_next_publisher_video_track(
77+
events: &mut UnboundedReceiver<RoomEvent>,
9578
label: &str,
9679
max_wait: Duration,
97-
) -> Result<HashMap<TrackSid, LocalVideoTrack>> {
98-
let deadline = tokio::time::Instant::now() + max_wait;
99-
loop {
100-
let tracks = publisher_video_tracks(room);
101-
if tracks.len() == expected_count {
102-
return Ok(tracks);
103-
}
104-
if tokio::time::Instant::now() >= deadline {
105-
return Err(anyhow!(
106-
"dynacast test [{}]: timed out waiting for {} publisher video tracks, got {}",
107-
label,
108-
expected_count,
109-
tracks.len()
110-
));
80+
) -> Result<(TrackSid, LocalVideoTrack)> {
81+
timeout(max_wait, async {
82+
while let Some(event) = events.recv().await {
83+
if let RoomEvent::LocalTrackPublished {
84+
publication,
85+
track: LocalTrack::Video(track),
86+
..
87+
} = event
88+
{
89+
return Ok((publication.sid(), track));
90+
}
11191
}
112-
time::sleep(Duration::from_millis(250)).await;
113-
}
92+
Err(anyhow!("dynacast test [{}]: event channel closed before video track published", label))
93+
})
94+
.await
95+
.map_err(|_| {
96+
anyhow!("dynacast test [{}]: timed out waiting for publisher video track", label)
97+
})?
11498
}
11599

116100
/// Waits for a subscriber to observe all expected remote track publications.
117101
#[cfg(feature = "__lk-e2e-test")]
118102
async fn wait_for_remote_publications(
119-
room: &Room,
103+
events: &mut UnboundedReceiver<RoomEvent>,
120104
track_sids: &[TrackSid],
121105
label: &str,
122106
max_wait: Duration,
123107
) -> Result<HashMap<TrackSid, RemoteTrackPublication>> {
124-
let deadline = tokio::time::Instant::now() + max_wait;
125-
loop {
126-
let publications: HashMap<_, _> = room
127-
.remote_participants()
128-
.into_values()
129-
.flat_map(|participant| participant.track_publications())
130-
.filter(|(sid, _)| track_sids.contains(sid))
131-
.collect();
132-
133-
if publications.len() == track_sids.len() {
134-
return Ok(publications);
135-
}
136-
if tokio::time::Instant::now() >= deadline {
137-
return Err(anyhow!(
138-
"dynacast test [{}]: timed out waiting for remote publications, got {}/{}",
139-
label,
140-
publications.len(),
141-
track_sids.len()
142-
));
108+
let mut publications: HashMap<TrackSid, RemoteTrackPublication> = HashMap::new();
109+
timeout(max_wait, async {
110+
while publications.len() < track_sids.len() {
111+
let Some(event) = events.recv().await else {
112+
return Err(anyhow!(
113+
"dynacast test [{}]: event channel closed before all remote publications observed",
114+
label
115+
));
116+
};
117+
if let RoomEvent::TrackPublished { publication, .. } = event {
118+
let sid = publication.sid();
119+
if track_sids.contains(&sid) {
120+
publications.insert(sid, publication);
121+
}
122+
}
143123
}
144-
time::sleep(Duration::from_millis(250)).await;
145-
}
124+
Ok(())
125+
})
126+
.await
127+
.map_err(|_| {
128+
anyhow!(
129+
"dynacast test [{}]: timed out waiting for remote publications, got {}/{}",
130+
label,
131+
publications.len(),
132+
track_sids.len()
133+
)
134+
})??;
135+
Ok(publications)
146136
}
147137

148138
/// Subscribes to exactly one of the provided publications and waits for it to attach a track.
@@ -363,63 +353,41 @@ async fn test_dynacast_multiple_subscribers_only_publish_requested_tracks() -> R
363353

364354
let mut rooms =
365355
test_rooms_with_options([pub_options, sub_options.clone(), sub_options]).await?;
366-
let (pub_room, _pub_events) = rooms.remove(0);
367-
let (sub1_room, mut sub1_events) = rooms.remove(0);
368-
let (sub2_room, mut sub2_events) = rooms.remove(0);
356+
let (pub_room, mut pub_events) = rooms.remove(0);
357+
let (_sub1_room, mut sub1_events) = rooms.remove(0);
358+
let (_sub2_room, mut sub2_events) = rooms.remove(0);
369359

370360
let pub_room = Arc::new(pub_room);
371361
let mut solid_tracks = Vec::new();
372362
let mut track_sids: Vec<TrackSid> = Vec::new();
363+
let mut publisher_tracks: Vec<(TrackSid, LocalVideoTrack)> = Vec::new();
373364

374365
for (index, luma) in [64, 128, 192].into_iter().enumerate() {
375366
let solid_params = SolidColorParams { width: 1280, height: 720, luma };
376367
let mut solid_track = SolidColorTrack::new(pub_room.clone(), solid_params);
377368
solid_track.publish(VideoCodec::VP8, true).await?;
378369

379-
let published_tracks = wait_for_publisher_video_tracks(
380-
&pub_room,
381-
index + 1,
370+
let (new_sid, track) = wait_for_next_publisher_video_track(
371+
&mut pub_events,
382372
&format!("publish track {}", index + 1),
383373
Duration::from_secs(15),
384374
)
385375
.await?;
386-
let Some(new_sid) = published_tracks
387-
.keys()
388-
.find(|sid| !track_sids.iter().any(|published_sid| published_sid == *sid))
389-
else {
390-
return Err(anyhow!("No new track SID found after publishing track {}", index + 1));
391-
};
392376
log::info!("dynacast multi: published track {} as {}", index + 1, new_sid);
393377
track_sids.push(new_sid.clone());
378+
publisher_tracks.push((new_sid, track));
394379
solid_tracks.push(solid_track);
395380
}
396381

397-
let published_tracks = wait_for_publisher_video_tracks(
398-
&pub_room,
399-
3,
400-
"all published tracks",
401-
Duration::from_secs(5),
402-
)
403-
.await?;
404-
let publisher_tracks: Vec<_> = track_sids
405-
.iter()
406-
.map(|sid| {
407-
let track = published_tracks
408-
.get(sid)
409-
.ok_or_else(|| anyhow!("Missing local video track {}", sid))?;
410-
Ok((sid.clone(), track.clone()))
411-
})
412-
.collect::<Result<_>>()?;
413-
414382
let sub1_publications = wait_for_remote_publications(
415-
&sub1_room,
383+
&mut sub1_events,
416384
&track_sids,
417385
"subscriber 1",
418386
Duration::from_secs(15),
419387
)
420388
.await?;
421389
let sub2_publications = wait_for_remote_publications(
422-
&sub2_room,
390+
&mut sub2_events,
423391
&track_sids,
424392
"subscriber 2",
425393
Duration::from_secs(15),

0 commit comments

Comments
 (0)