@desklink/host 0.5.14 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/bin/desklink-host.mjs +9 -3
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +220 -0
- package/dist/doctor.js.map +1 -0
- package/dist/engineProcess.d.ts +5 -0
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +15 -5
- package/dist/engineProcess.js.map +1 -1
- package/docs/PROTOCOL.md +4 -2
- package/engine/Cargo.lock +1 -1
- package/engine/Cargo.toml +1 -1
- package/engine/native/mac_stream.mm +4 -1
- package/engine/src/capture.rs +0 -79
- package/engine/src/diagnose.rs +99 -0
- package/engine/src/encoder.rs +0 -48
- package/engine/src/h264_nvenc.rs +0 -165
- package/engine/src/h264_openh264.rs +0 -245
- package/engine/src/h264_vaapi.rs +0 -97
- package/engine/src/indicator.rs +0 -101
- package/engine/src/input_portal.rs +337 -0
- package/engine/src/mac.rs +229 -6
- package/engine/src/main.rs +7 -88
- package/engine/src/observe.rs +0 -33
- package/engine/src/peer.rs +0 -433
- package/engine/src/portal.rs +0 -75
- package/engine/src/session.rs +112 -1085
- package/examples/reference-client.html +13 -0
- package/package.json +3 -3
package/engine/src/peer.rs
CHANGED
|
@@ -1190,12 +1190,6 @@ impl VideoPeer {
|
|
|
1190
1190
|
lock(&self.path).take()
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
|
-
/// Stand in for transport-wide feedback that reports a path, for a pipeline under test.
|
|
1194
|
-
#[cfg(test)]
|
|
1195
|
-
pub fn report_path(&self, report: PathReport) {
|
|
1196
|
-
*lock(&self.path) = Some(report);
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
1193
|
/// Take the pending "the far end needs a reference frame" request, if any.
|
|
1200
1194
|
pub fn take_keyframe_request(&self) -> bool {
|
|
1201
1195
|
self.wants_keyframe.swap(false, Ordering::SeqCst)
|
|
@@ -1239,118 +1233,6 @@ pub(crate) mod tests {
|
|
|
1239
1233
|
use super::*;
|
|
1240
1234
|
use webrtc::media_stream::track_remote::{TrackRemote, TrackRemoteEvent};
|
|
1241
1235
|
|
|
1242
|
-
#[test]
|
|
1243
|
-
fn a_standing_queue_is_measured_and_one_frames_burst_is_not() {
|
|
1244
|
-
let epoch = Instant::now();
|
|
1245
|
-
let report = Arc::new(Mutex::new(None));
|
|
1246
|
-
let mut estimator = QueueDelay::new(report.clone(), 20_000_000.0);
|
|
1247
|
-
let mut id = 0u64;
|
|
1248
|
-
// One 1200-byte packet every 10 ms, sent at `ms` and `extra` ms late
|
|
1249
|
-
// over a path with 20 ms of base delay.
|
|
1250
|
-
let mut feed = |from: u64, to: u64, extra: &dyn Fn(u64) -> u64| {
|
|
1251
|
-
let reports: Vec<PacketReport> = (from..to)
|
|
1252
|
-
.step_by(10)
|
|
1253
|
-
.map(|ms| {
|
|
1254
|
-
id += 1;
|
|
1255
|
-
PacketReport {
|
|
1256
|
-
ssrc: 1,
|
|
1257
|
-
id,
|
|
1258
|
-
rtp_sequence_number: id as u16,
|
|
1259
|
-
is_twcc: true,
|
|
1260
|
-
twcc_sequence_number: id as u16,
|
|
1261
|
-
size: 1200,
|
|
1262
|
-
arrived: true,
|
|
1263
|
-
departure: epoch + Duration::from_millis(ms),
|
|
1264
|
-
arrival: Some(Duration::from_millis(1_000 + ms + 20 + extra(ms))),
|
|
1265
|
-
ecn: Default::default(),
|
|
1266
|
-
}
|
|
1267
|
-
})
|
|
1268
|
-
.collect();
|
|
1269
|
-
estimator.on_reports(epoch + Duration::from_millis(to + 20), &reports);
|
|
1270
|
-
lock(&report).take().expect("a report")
|
|
1271
|
-
};
|
|
1272
|
-
|
|
1273
|
-
let quiet = feed(0, 1000, &|_| 0);
|
|
1274
|
-
assert_eq!(quiet.queue_delay, Duration::ZERO);
|
|
1275
|
-
// 51 packets of 1200 bytes arrived in the last 500 ms, both ends in.
|
|
1276
|
-
assert_eq!(quiet.delivered_kbps, 979);
|
|
1277
|
-
// A frame's burst lifts the packets at its tail, not the ones before.
|
|
1278
|
-
let burst = feed(1000, 1100, &|ms| if ms >= 1070 { 40 } else { 0 });
|
|
1279
|
-
assert_eq!(burst.queue_delay, Duration::ZERO);
|
|
1280
|
-
// A link that drains slower than it is fed lifts every packet: the
|
|
1281
|
-
// queue is the least any packet of the last 200 ms waited.
|
|
1282
|
-
let standing = feed(1100, 1400, &|ms| 30 + (ms - 1100) / 10);
|
|
1283
|
-
assert_eq!(standing.queue_delay, Duration::from_millis(41));
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
struct NoopHandler;
|
|
1287
|
-
|
|
1288
|
-
impl PeerConnectionEventHandler for NoopHandler {}
|
|
1289
|
-
|
|
1290
|
-
async fn answerer(offer: &str) -> Arc<dyn PeerConnection> {
|
|
1291
|
-
answerer_with(offer, vec![vp9_codec()]).await
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
/// A receiver that decodes `codecs`, in its order of preference.
|
|
1295
|
-
pub(crate) async fn answerer_with(
|
|
1296
|
-
offer: &str,
|
|
1297
|
-
codecs: Vec<RTCRtpCodecParameters>,
|
|
1298
|
-
) -> Arc<dyn PeerConnection> {
|
|
1299
|
-
let mut media_engine = MediaEngine::default();
|
|
1300
|
-
for codec in codecs {
|
|
1301
|
-
media_engine
|
|
1302
|
-
.register_codec(codec, RtpCodecKind::Video)
|
|
1303
|
-
.expect("the codec registers");
|
|
1304
|
-
}
|
|
1305
|
-
let registry = register_default_interceptors(Registry::new(), &mut media_engine)
|
|
1306
|
-
.expect("interceptors register");
|
|
1307
|
-
let peer = PeerConnectionBuilder::<std::net::SocketAddr>::new()
|
|
1308
|
-
.with_media_engine(media_engine)
|
|
1309
|
-
.with_interceptor_registry(registry)
|
|
1310
|
-
.with_runtime(default_runtime().expect("a runtime"))
|
|
1311
|
-
.with_handler(Arc::new(NoopHandler))
|
|
1312
|
-
.with_udp_addrs(udp_addrs())
|
|
1313
|
-
.build()
|
|
1314
|
-
.await
|
|
1315
|
-
.expect("an answering peer");
|
|
1316
|
-
peer.set_remote_description(
|
|
1317
|
-
RTCSessionDescription::offer(offer.to_owned()).expect("a valid offer"),
|
|
1318
|
-
)
|
|
1319
|
-
.await
|
|
1320
|
-
.expect("the offer is accepted");
|
|
1321
|
-
Arc::new(peer)
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
#[tokio::test]
|
|
1325
|
-
async fn an_encoded_offer_carries_the_streams_own_profile_and_level() {
|
|
1326
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1327
|
-
let (_peer, offer) = VideoPeer::offer(
|
|
1328
|
-
TransportOptions {
|
|
1329
|
-
ice_servers: Vec::new(),
|
|
1330
|
-
loopback_tcp: false,
|
|
1331
|
-
pace_bps: 20_000_000.0,
|
|
1332
|
-
},
|
|
1333
|
-
events,
|
|
1334
|
-
VideoCodec::H264 {
|
|
1335
|
-
profile_level_id: String::from("42c029"),
|
|
1336
|
-
},
|
|
1337
|
-
)
|
|
1338
|
-
.await
|
|
1339
|
-
.expect("a peer connection");
|
|
1340
|
-
assert!(
|
|
1341
|
-
offer.contains("H264/90000"),
|
|
1342
|
-
"the encoded track must be offered as H.264: {offer}",
|
|
1343
|
-
);
|
|
1344
|
-
assert!(
|
|
1345
|
-
offer.contains("packetization-mode=1;profile-level-id=42c029"),
|
|
1346
|
-
"the offer must name the fed SPS's own profile-level-id: {offer}",
|
|
1347
|
-
);
|
|
1348
|
-
assert!(
|
|
1349
|
-
!offer.contains("VP9"),
|
|
1350
|
-
"an encoded source must not also offer the codec it cannot encode: {offer}",
|
|
1351
|
-
);
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
1236
|
fn transport() -> TransportOptions {
|
|
1355
1237
|
TransportOptions {
|
|
1356
1238
|
ice_servers: Vec::new(),
|
|
@@ -1359,321 +1241,6 @@ pub(crate) mod tests {
|
|
|
1359
1241
|
}
|
|
1360
1242
|
}
|
|
1361
1243
|
|
|
1362
|
-
/// The answer `codecs` make to a `Vp9OrH264` offer, applied to its peer.
|
|
1363
|
-
pub(crate) async fn answered(codecs: Vec<RTCRtpCodecParameters>) -> (VideoPeer, String) {
|
|
1364
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1365
|
-
let (peer, offer) = VideoPeer::offer(transport(), events, VideoCodec::Vp9OrH264)
|
|
1366
|
-
.await
|
|
1367
|
-
.expect("a peer connection");
|
|
1368
|
-
let other = answerer_with(&offer, codecs).await;
|
|
1369
|
-
let answer = other.create_answer(None).await.expect("an answer");
|
|
1370
|
-
other
|
|
1371
|
-
.set_local_description(answer.clone())
|
|
1372
|
-
.await
|
|
1373
|
-
.expect("a local description");
|
|
1374
|
-
peer.accept_answer(answer.sdp.clone())
|
|
1375
|
-
.await
|
|
1376
|
-
.expect("the answer is accepted");
|
|
1377
|
-
(peer, answer.sdp)
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
pub(crate) fn h264_receiver_codec() -> RTCRtpCodecParameters {
|
|
1381
|
-
h264_codec(crate::h264::PROFILE_LEVEL_ID)
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
#[tokio::test]
|
|
1385
|
-
async fn a_captured_offer_puts_vp9_first_and_h264_second() {
|
|
1386
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1387
|
-
let (_peer, offer) = VideoPeer::offer(transport(), events, VideoCodec::Vp9OrH264)
|
|
1388
|
-
.await
|
|
1389
|
-
.expect("a peer connection");
|
|
1390
|
-
let video = offer
|
|
1391
|
-
.lines()
|
|
1392
|
-
.find(|line| line.starts_with("m=video "))
|
|
1393
|
-
.expect("a video section");
|
|
1394
|
-
assert!(
|
|
1395
|
-
video.ends_with(&format!(" {VP9_PAYLOAD_TYPE} {H264_PAYLOAD_TYPE}")),
|
|
1396
|
-
"VP9 is the default a receiver with no preference answers with: {video}",
|
|
1397
|
-
);
|
|
1398
|
-
assert!(
|
|
1399
|
-
offer.contains(&format!(
|
|
1400
|
-
"a=fmtp:{H264_PAYLOAD_TYPE} level-asymmetry-allowed=1;packetization-mode=1;profile-level-id={}",
|
|
1401
|
-
crate::h264::PROFILE_LEVEL_ID
|
|
1402
|
-
)),
|
|
1403
|
-
"H.264 is offered as the Constrained Baseline every backend emits: {offer}",
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
#[tokio::test]
|
|
1408
|
-
async fn the_receivers_first_codec_is_the_one_sent() {
|
|
1409
|
-
let (peer, answer) = answered(vec![vp9_codec(), h264_receiver_codec()]).await;
|
|
1410
|
-
assert!(
|
|
1411
|
-
!peer.sends_h264(),
|
|
1412
|
-
"a receiver that keeps the offer's order gets VP9: {answer}"
|
|
1413
|
-
);
|
|
1414
|
-
let (peer, answer) = answered(vec![h264_receiver_codec()]).await;
|
|
1415
|
-
assert!(peer.sends_h264(), "an H.264 receiver gets H.264: {answer}");
|
|
1416
|
-
assert_eq!(
|
|
1417
|
-
peer.payload_type.load(Ordering::SeqCst),
|
|
1418
|
-
H264_PAYLOAD_TYPE,
|
|
1419
|
-
"frames go out on the payload type the answer kept",
|
|
1420
|
-
);
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
#[test]
|
|
1424
|
-
fn an_answer_is_read_for_its_video_sections_first_known_codec() {
|
|
1425
|
-
let answer = |formats: &str| {
|
|
1426
|
-
format!(
|
|
1427
|
-
"v=0\r\no=- 1 1 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\n\
|
|
1428
|
-
m=video 9 UDP/TLS/RTP/SAVPF {formats}\r\nc=IN IP4 0.0.0.0\r\n\
|
|
1429
|
-
a=rtpmap:96 rtx/90000\r\na=rtpmap:98 VP9/90000\r\na=rtpmap:102 H264/90000\r\n\
|
|
1430
|
-
m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\na=rtpmap:5 H264/90000\r\n"
|
|
1431
|
-
)
|
|
1432
|
-
};
|
|
1433
|
-
assert!(answer_prefers_h264(&answer("102 98")));
|
|
1434
|
-
assert!(answer_prefers_h264(&answer("96 102 98")), "rtx is skipped");
|
|
1435
|
-
assert!(!answer_prefers_h264(&answer("98 102")));
|
|
1436
|
-
assert!(!answer_prefers_h264(&answer("98")));
|
|
1437
|
-
assert!(!answer_prefers_h264("v=0\r\n"), "no video, no H.264");
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
#[tokio::test]
|
|
1441
|
-
async fn the_offer_asks_the_receiver_to_show_frames_without_playout_delay() {
|
|
1442
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1443
|
-
let (_peer, offer) = VideoPeer::offer(
|
|
1444
|
-
TransportOptions {
|
|
1445
|
-
ice_servers: Vec::new(),
|
|
1446
|
-
loopback_tcp: false,
|
|
1447
|
-
pace_bps: 20_000_000.0,
|
|
1448
|
-
},
|
|
1449
|
-
events,
|
|
1450
|
-
VideoCodec::Vp9,
|
|
1451
|
-
)
|
|
1452
|
-
.await
|
|
1453
|
-
.expect("a peer connection");
|
|
1454
|
-
assert!(
|
|
1455
|
-
offer
|
|
1456
|
-
.lines()
|
|
1457
|
-
.any(|line| line.starts_with("a=extmap:") && line.ends_with(PLAYOUT_DELAY_URI)),
|
|
1458
|
-
"the video must negotiate the playout-delay extension its packets carry: {offer}",
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
#[test]
|
|
1463
|
-
fn the_profile_level_id_comes_from_the_sps_in_the_stream() {
|
|
1464
|
-
// A leading zero byte, a four-byte start code and a three-byte one, so
|
|
1465
|
-
// the scanner is exercised on both forms.
|
|
1466
|
-
let mut stream = vec![0x00, 0x00, 0x00, 0x01];
|
|
1467
|
-
stream.extend_from_slice(&[0x67, 0x42, 0xc0, 0x29, 0x8c, 0x8d]);
|
|
1468
|
-
stream.extend_from_slice(&[0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80]);
|
|
1469
|
-
stream.extend_from_slice(&[0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x00]);
|
|
1470
|
-
assert_eq!(
|
|
1471
|
-
h264_profile_level_id(&stream).as_deref(),
|
|
1472
|
-
Some("42c029"),
|
|
1473
|
-
"profile_idc, constraint flags and level_idc, as the SDP needs them",
|
|
1474
|
-
);
|
|
1475
|
-
assert_eq!(
|
|
1476
|
-
h264_profile_level_id(&[0x00, 0x00, 0x01, 0x65, 0x88]),
|
|
1477
|
-
None,
|
|
1478
|
-
"a stream with no SPS cannot start a session",
|
|
1479
|
-
);
|
|
1480
|
-
let mut adjacent = vec![0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01];
|
|
1481
|
-
adjacent.extend_from_slice(&[0x67, 0x42, 0xc0, 0x29]);
|
|
1482
|
-
assert_eq!(
|
|
1483
|
-
h264_profile_level_id(&adjacent).as_deref(),
|
|
1484
|
-
Some("42c029"),
|
|
1485
|
-
"an empty NAL between start codes is skipped, not fatal to the scan",
|
|
1486
|
-
);
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
#[test]
|
|
1490
|
-
fn a_fed_keyframe_is_packetized_as_parameter_sets_then_a_slice() {
|
|
1491
|
-
let mut stream = vec![0x00, 0x00, 0x00, 0x01];
|
|
1492
|
-
stream.extend_from_slice(&[0x67, 0x42, 0xc0, 0x29, 0x8c, 0x8d]);
|
|
1493
|
-
stream.extend_from_slice(&[0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80]);
|
|
1494
|
-
stream.extend_from_slice(&[0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x00]);
|
|
1495
|
-
let payloader = h264_codec("42c029")
|
|
1496
|
-
.rtp_codec
|
|
1497
|
-
.payloader()
|
|
1498
|
-
.expect("the media engine has an H.264 payloader");
|
|
1499
|
-
let mut packetizer = H264Packetizer::new(payloader);
|
|
1500
|
-
let packets = packetizer
|
|
1501
|
-
.packetize(&stream, Instant::now(), 7, H264_PAYLOAD_TYPE)
|
|
1502
|
-
.expect("a keyframe packetizes");
|
|
1503
|
-
assert_eq!(
|
|
1504
|
-
packets.len(),
|
|
1505
|
-
2,
|
|
1506
|
-
"one STAP-A for the parameter sets, one slice"
|
|
1507
|
-
);
|
|
1508
|
-
assert_eq!(
|
|
1509
|
-
packets[0].payload[0], 0x78,
|
|
1510
|
-
"the parameter sets travel as a STAP-A",
|
|
1511
|
-
);
|
|
1512
|
-
assert_eq!(packets[1].payload[0], 0x65, "then the IDR slice itself");
|
|
1513
|
-
assert!(
|
|
1514
|
-
!packets[0].header.marker && packets[1].header.marker,
|
|
1515
|
-
"the marker ends the access unit, not the first fragment of it",
|
|
1516
|
-
);
|
|
1517
|
-
assert_eq!(
|
|
1518
|
-
packets[0].header.timestamp, packets[1].header.timestamp,
|
|
1519
|
-
"an access unit is one instant",
|
|
1520
|
-
);
|
|
1521
|
-
assert_eq!(packets[0].header.payload_type, H264_PAYLOAD_TYPE);
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
#[tokio::test]
|
|
1525
|
-
async fn a_candidate_that_arrives_before_the_answer_is_not_refused() {
|
|
1526
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1527
|
-
let (peer, _offer) = VideoPeer::offer(
|
|
1528
|
-
TransportOptions {
|
|
1529
|
-
ice_servers: Vec::new(),
|
|
1530
|
-
loopback_tcp: false,
|
|
1531
|
-
pace_bps: 20_000_000.0,
|
|
1532
|
-
},
|
|
1533
|
-
events,
|
|
1534
|
-
VideoCodec::Vp9,
|
|
1535
|
-
)
|
|
1536
|
-
.await
|
|
1537
|
-
.expect("a peer connection");
|
|
1538
|
-
|
|
1539
|
-
// The engine is the offerer, so the remote description only arrives with
|
|
1540
|
-
// the answer; this candidate must be held, not refused.
|
|
1541
|
-
peer.add_candidate(
|
|
1542
|
-
String::from("candidate:1 1 udp 2113937151 192.0.2.1 40000 typ host"),
|
|
1543
|
-
Some(String::from("0")),
|
|
1544
|
-
Some(0),
|
|
1545
|
-
)
|
|
1546
|
-
.await
|
|
1547
|
-
.expect("a candidate that arrives before the answer is buffered");
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
#[tokio::test]
|
|
1551
|
-
async fn packets_ask_for_no_playout_delay_only_when_the_answer_accepts_it() {
|
|
1552
|
-
for accepts in [false, true] {
|
|
1553
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1554
|
-
let (peer, offer) = VideoPeer::offer(
|
|
1555
|
-
TransportOptions {
|
|
1556
|
-
ice_servers: Vec::new(),
|
|
1557
|
-
loopback_tcp: false,
|
|
1558
|
-
pace_bps: 20_000_000.0,
|
|
1559
|
-
},
|
|
1560
|
-
events,
|
|
1561
|
-
VideoCodec::Vp9,
|
|
1562
|
-
)
|
|
1563
|
-
.await
|
|
1564
|
-
.expect("a peer connection");
|
|
1565
|
-
// This answerer knows no playout-delay extension, so it declines it.
|
|
1566
|
-
let other = answerer(&offer).await;
|
|
1567
|
-
let answer = other.create_answer(None).await.expect("an answer");
|
|
1568
|
-
let sdp = if accepts {
|
|
1569
|
-
answer.sdp.replacen(
|
|
1570
|
-
"a=mid:0\r\n",
|
|
1571
|
-
&format!("a=mid:0\r\na=extmap:1 {PLAYOUT_DELAY_URI}\r\n"),
|
|
1572
|
-
1,
|
|
1573
|
-
)
|
|
1574
|
-
} else {
|
|
1575
|
-
answer.sdp
|
|
1576
|
-
};
|
|
1577
|
-
assert_eq!(sdp.contains(PLAYOUT_DELAY_URI), accepts);
|
|
1578
|
-
peer.accept_answer(sdp)
|
|
1579
|
-
.await
|
|
1580
|
-
.expect("the answer is accepted");
|
|
1581
|
-
assert_eq!(
|
|
1582
|
-
peer.playout_delay.load(Ordering::SeqCst),
|
|
1583
|
-
accepts,
|
|
1584
|
-
"a packet may carry the extension only when the answer accepted it",
|
|
1585
|
-
);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
#[tokio::test]
|
|
1590
|
-
async fn held_candidates_reach_the_peer_when_the_answer_arrives() {
|
|
1591
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1592
|
-
let (peer, offer) = VideoPeer::offer(
|
|
1593
|
-
TransportOptions {
|
|
1594
|
-
ice_servers: Vec::new(),
|
|
1595
|
-
loopback_tcp: false,
|
|
1596
|
-
pace_bps: 20_000_000.0,
|
|
1597
|
-
},
|
|
1598
|
-
events,
|
|
1599
|
-
VideoCodec::Vp9,
|
|
1600
|
-
)
|
|
1601
|
-
.await
|
|
1602
|
-
.expect("a peer connection");
|
|
1603
|
-
|
|
1604
|
-
let other = answerer(&offer).await;
|
|
1605
|
-
let answer = other.create_answer(None).await.expect("an answer");
|
|
1606
|
-
other
|
|
1607
|
-
.set_local_description(answer.clone())
|
|
1608
|
-
.await
|
|
1609
|
-
.expect("a local description");
|
|
1610
|
-
|
|
1611
|
-
peer.add_candidate(
|
|
1612
|
-
String::from("candidate:1 1 udp 2113937151 192.0.2.1 40000 typ host"),
|
|
1613
|
-
Some(String::from("0")),
|
|
1614
|
-
Some(0),
|
|
1615
|
-
)
|
|
1616
|
-
.await
|
|
1617
|
-
.expect("a candidate before the answer is buffered");
|
|
1618
|
-
|
|
1619
|
-
let applied = peer
|
|
1620
|
-
.accept_answer(answer.sdp)
|
|
1621
|
-
.await
|
|
1622
|
-
.expect("the answer is accepted");
|
|
1623
|
-
assert_eq!(applied, 1, "the held candidate must reach the peer");
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
#[tokio::test]
|
|
1627
|
-
async fn an_ice_restart_offers_fresh_credentials_on_the_same_peer() {
|
|
1628
|
-
let (events, _events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1629
|
-
let (peer, offer) = VideoPeer::offer(
|
|
1630
|
-
TransportOptions {
|
|
1631
|
-
ice_servers: Vec::new(),
|
|
1632
|
-
loopback_tcp: false,
|
|
1633
|
-
pace_bps: 20_000_000.0,
|
|
1634
|
-
},
|
|
1635
|
-
events,
|
|
1636
|
-
VideoCodec::Vp9,
|
|
1637
|
-
)
|
|
1638
|
-
.await
|
|
1639
|
-
.expect("a peer connection");
|
|
1640
|
-
|
|
1641
|
-
let other = answerer(&offer).await;
|
|
1642
|
-
let answer = other.create_answer(None).await.expect("an answer");
|
|
1643
|
-
other
|
|
1644
|
-
.set_local_description(answer.clone())
|
|
1645
|
-
.await
|
|
1646
|
-
.expect("a local description");
|
|
1647
|
-
peer.accept_answer(answer.sdp)
|
|
1648
|
-
.await
|
|
1649
|
-
.expect("the answer is accepted");
|
|
1650
|
-
|
|
1651
|
-
let ufrag = |sdp: &str| {
|
|
1652
|
-
sdp.lines()
|
|
1653
|
-
.find_map(|line| line.strip_prefix("a=ice-ufrag:"))
|
|
1654
|
-
.expect("an ICE username fragment")
|
|
1655
|
-
.to_owned()
|
|
1656
|
-
};
|
|
1657
|
-
let restart = peer.restart_ice().await.expect("a restart offer");
|
|
1658
|
-
assert!(
|
|
1659
|
-
!restart.is_empty() && ufrag(&restart) != ufrag(&offer),
|
|
1660
|
-
"the restart must turn over the ICE generation"
|
|
1661
|
-
);
|
|
1662
|
-
// The far end answers the restart as usual, on the same peer.
|
|
1663
|
-
other
|
|
1664
|
-
.set_remote_description(RTCSessionDescription::offer(restart).expect("a valid offer"))
|
|
1665
|
-
.await
|
|
1666
|
-
.expect("the restart offer applies");
|
|
1667
|
-
let reanswer = other.create_answer(None).await.expect("an answer");
|
|
1668
|
-
other
|
|
1669
|
-
.set_local_description(reanswer.clone())
|
|
1670
|
-
.await
|
|
1671
|
-
.expect("a local description");
|
|
1672
|
-
peer.accept_answer(reanswer.sdp)
|
|
1673
|
-
.await
|
|
1674
|
-
.expect("the restart answer is accepted");
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
1244
|
/// How long the restarted generation may take to nominate and reconnect,
|
|
1678
1245
|
/// measured from the answered offer: two `check_interval` ticks. The vendored
|
|
1679
1246
|
/// agent ticks every 200 ms (`vendor/rtc-ice/src/agent/agent_config.rs`), and
|
package/engine/src/portal.rs
CHANGED
|
@@ -149,78 +149,3 @@ fn choose_cursor_mode(available: BitFlags<CursorMode>) -> Result<CursorMode> {
|
|
|
149
149
|
.find(|mode| available.contains(*mode))
|
|
150
150
|
.context("cursor-unavailable: portal cannot leave the cursor out of capture")
|
|
151
151
|
}
|
|
152
|
-
|
|
153
|
-
#[cfg(all(test, target_os = "linux"))]
|
|
154
|
-
mod tests {
|
|
155
|
-
use super::*;
|
|
156
|
-
use std::process::{Child, Command, Stdio};
|
|
157
|
-
use zbus::{connection::Builder, message::Type, MessageStream};
|
|
158
|
-
|
|
159
|
-
struct Bus(Child);
|
|
160
|
-
|
|
161
|
-
impl Drop for Bus {
|
|
162
|
-
fn drop(&mut self) {
|
|
163
|
-
let _ = self.0.kill();
|
|
164
|
-
let _ = self.0.wait();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/// A private session bus whose `org.freedesktop.portal.Desktop` owner
|
|
169
|
-
/// answers every property read the way a stale xdg-desktop-portal answers
|
|
170
|
-
/// for ScreenCast: no such interface.
|
|
171
|
-
#[tokio::test]
|
|
172
|
-
async fn open_names_the_fix_when_the_portal_exports_no_screencast() {
|
|
173
|
-
use zbus::export::futures_core::Stream;
|
|
174
|
-
|
|
175
|
-
let mut daemon = Command::new("dbus-daemon")
|
|
176
|
-
.args(["--session", "--nofork", "--print-address=1"])
|
|
177
|
-
.stdout(Stdio::piped())
|
|
178
|
-
.spawn()
|
|
179
|
-
.expect("dbus-daemon");
|
|
180
|
-
let mut address = String::new();
|
|
181
|
-
std::io::BufRead::read_line(
|
|
182
|
-
&mut std::io::BufReader::new(daemon.stdout.take().unwrap()),
|
|
183
|
-
&mut address,
|
|
184
|
-
)
|
|
185
|
-
.unwrap();
|
|
186
|
-
let _bus = Bus(daemon);
|
|
187
|
-
let address = address.trim().to_owned();
|
|
188
|
-
|
|
189
|
-
let fake = Builder::address(address.as_str())
|
|
190
|
-
.unwrap()
|
|
191
|
-
.name("org.freedesktop.portal.Desktop")
|
|
192
|
-
.unwrap()
|
|
193
|
-
.build()
|
|
194
|
-
.await
|
|
195
|
-
.unwrap();
|
|
196
|
-
let service = fake.clone();
|
|
197
|
-
let answering = tokio::spawn(async move {
|
|
198
|
-
let mut messages = MessageStream::from(&service);
|
|
199
|
-
while let Some(Ok(message)) =
|
|
200
|
-
std::future::poll_fn(|cx| std::pin::Pin::new(&mut messages).poll_next(cx)).await
|
|
201
|
-
{
|
|
202
|
-
if message.message_type() != Type::MethodCall {
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
let _ = service
|
|
206
|
-
.reply_dbus_error(
|
|
207
|
-
&message.header(),
|
|
208
|
-
zbus::fdo::Error::InvalidArgs(
|
|
209
|
-
"No such interface \u{201c}org.freedesktop.portal.ScreenCast\u{201d}".into(),
|
|
210
|
-
),
|
|
211
|
-
)
|
|
212
|
-
.await;
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
// Screencast::new reads the session bus from the environment.
|
|
216
|
-
std::env::set_var("DBUS_SESSION_BUS_ADDRESS", &address);
|
|
217
|
-
|
|
218
|
-
let error = match open(None, crate::protocol::CursorMode::Embedded).await {
|
|
219
|
-
Ok(_) => panic!("a portal without ScreenCast must not open a session"),
|
|
220
|
-
Err(error) => format!("{error:#}"),
|
|
221
|
-
};
|
|
222
|
-
answering.abort();
|
|
223
|
-
assert!(error.starts_with("compositor has no ScreenCast portal"), "{error}");
|
|
224
|
-
assert!(error.contains("systemctl --user restart xdg-desktop-portal"), "{error}");
|
|
225
|
-
}
|
|
226
|
-
}
|