1 use std::iter::FromIterator; 2 3 use lazy_static::lazy_static; 4 5 use crate::{ 6 algorithms::{select_settings_candidates, SelectSettingsError}, 7 errors::OverconstrainedError, 8 property::all::{name::*, names as all_properties}, 9 AdvancedMediaTrackConstraints, FacingMode, MandatoryMediaTrackConstraints, 10 MediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints, ResizeMode, 11 ResolvedAdvancedMediaTrackConstraints, ResolvedMandatoryMediaTrackConstraints, 12 ResolvedMediaTrackConstraint, ResolvedMediaTrackConstraintSet, ResolvedMediaTrackConstraints, 13 ResolvedValueConstraint, ResolvedValueRangeConstraint, ResolvedValueSequenceConstraint, 14 SanitizedMediaTrackConstraints, 15 }; 16 17 use super::DeviceInformationExposureMode; 18 19 lazy_static! { 20 static ref VIDEO_IDEAL: MediaTrackSettings = MediaTrackSettings::from_iter([ 21 (&ASPECT_RATIO, 0.5625.into()), 22 (&FACING_MODE, FacingMode::user().into()), 23 (&FRAME_RATE, 60.0.into()), 24 (&WIDTH, 1920.into()), 25 (&HEIGHT, 1080.into()), 26 (&RESIZE_MODE, ResizeMode::none().into()), 27 ]); 28 static ref VIDEO_480P: MediaTrackSettings = MediaTrackSettings::from_iter([ 29 (&DEVICE_ID, "480p".into()), 30 (&ASPECT_RATIO, 0.5625.into()), 31 (&FACING_MODE, FacingMode::user().into()), 32 (&FRAME_RATE, 240.into()), 33 (&WIDTH, 720.into()), 34 (&HEIGHT, 480.into()), 35 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 36 ]); 37 static ref VIDEO_720P: MediaTrackSettings = MediaTrackSettings::from_iter([ 38 (&DEVICE_ID, "720p".into()), 39 (&ASPECT_RATIO, 0.5625.into()), 40 (&FACING_MODE, FacingMode::user().into()), 41 (&FRAME_RATE, 120.into()), 42 (&WIDTH, 1280.into()), 43 (&HEIGHT, 720.into()), 44 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 45 ]); 46 static ref VIDEO_1080P: MediaTrackSettings = MediaTrackSettings::from_iter([ 47 (&DEVICE_ID, "1080p".into()), 48 (&ASPECT_RATIO, 0.5625.into()), 49 (&FACING_MODE, FacingMode::user().into()), 50 (&FRAME_RATE, 60.into()), 51 (&WIDTH, 1920.into()), 52 (&HEIGHT, 1080.into()), 53 (&RESIZE_MODE, ResizeMode::none().into()), 54 ]); 55 static ref VIDEO_1440P: MediaTrackSettings = MediaTrackSettings::from_iter([ 56 (&DEVICE_ID, "1440p".into()), 57 (&ASPECT_RATIO, 0.5625.into()), 58 (&FACING_MODE, FacingMode::user().into()), 59 (&FRAME_RATE, 30.into()), 60 (&WIDTH, 2560.into()), 61 (&HEIGHT, 1440.into()), 62 (&RESIZE_MODE, ResizeMode::none().into()), 63 ]); 64 static ref VIDEO_2160P: MediaTrackSettings = MediaTrackSettings::from_iter([ 65 (&DEVICE_ID, "2160p".into()), 66 (&ASPECT_RATIO, 0.5625.into()), 67 (&FACING_MODE, FacingMode::user().into()), 68 (&FRAME_RATE, 15.into()), 69 (&WIDTH, 3840.into()), 70 (&HEIGHT, 2160.into()), 71 (&RESIZE_MODE, ResizeMode::none().into()), 72 ]); 73 } 74 75 fn default_possible_settings() -> Vec<MediaTrackSettings> { 76 vec![ 77 VIDEO_480P.clone(), 78 VIDEO_720P.clone(), 79 VIDEO_1080P.clone(), 80 VIDEO_1440P.clone(), 81 VIDEO_2160P.clone(), 82 ] 83 } 84 85 fn default_supported_constraints() -> MediaTrackSupportedConstraints { 86 MediaTrackSupportedConstraints::from_iter(all_properties().into_iter().cloned()) 87 } 88 89 fn test_overconstrained( 90 possible_settings: &[MediaTrackSettings], 91 mandatory_constraints: ResolvedMandatoryMediaTrackConstraints, 92 exposure_mode: DeviceInformationExposureMode, 93 ) -> OverconstrainedError { 94 let constraints = ResolvedMediaTrackConstraints { 95 mandatory: mandatory_constraints, 96 advanced: ResolvedAdvancedMediaTrackConstraints::default(), 97 } 98 .to_sanitized(&default_supported_constraints()); 99 100 let result = select_settings_candidates(possible_settings.iter(), &constraints, exposure_mode); 101 102 let actual = result.err().unwrap(); 103 104 let SelectSettingsError::Overconstrained(overconstrained_error) = actual; 105 106 overconstrained_error 107 } 108 109 fn test_constrained( 110 possible_settings: &[MediaTrackSettings], 111 mandatory_constraints: ResolvedMandatoryMediaTrackConstraints, 112 advanced_constraints: ResolvedAdvancedMediaTrackConstraints, 113 ) -> Vec<&MediaTrackSettings> { 114 let constraints = ResolvedMediaTrackConstraints { 115 mandatory: mandatory_constraints, 116 advanced: advanced_constraints, 117 } 118 .to_sanitized(&default_supported_constraints()); 119 120 let result = select_settings_candidates( 121 possible_settings.iter(), 122 &constraints, 123 DeviceInformationExposureMode::Exposed, 124 ); 125 126 result.unwrap() 127 } 128 129 mod unconstrained { 130 use super::*; 131 132 fn default_constraints() -> MediaTrackConstraints { 133 MediaTrackConstraints { 134 mandatory: MandatoryMediaTrackConstraints::default(), 135 advanced: AdvancedMediaTrackConstraints::default(), 136 } 137 } 138 139 fn default_resolved_constraints() -> ResolvedMediaTrackConstraints { 140 default_constraints().into_resolved() 141 } 142 143 fn default_sanitized_constraints() -> SanitizedMediaTrackConstraints { 144 default_resolved_constraints().into_sanitized(&default_supported_constraints()) 145 } 146 147 #[test] 148 fn pass_through() { 149 let possible_settings = default_possible_settings(); 150 let sanitized_constraints = default_sanitized_constraints(); 151 152 let actual = select_settings_candidates( 153 &possible_settings[..], 154 &sanitized_constraints, 155 DeviceInformationExposureMode::Exposed, 156 ) 157 .unwrap(); 158 let expected: Vec<_> = possible_settings.iter().collect(); 159 160 assert_eq!(actual, expected); 161 } 162 } 163 164 mod overconstrained { 165 use crate::MediaTrackProperty; 166 167 use super::*; 168 169 #[test] 170 fn protected() { 171 let error = test_overconstrained( 172 &default_possible_settings(), 173 ResolvedMandatoryMediaTrackConstraints::from_iter([( 174 GROUP_ID.clone(), 175 ResolvedValueConstraint::default() 176 .exact("missing-group".to_owned()) 177 .into(), 178 )]), 179 DeviceInformationExposureMode::Protected, 180 ); 181 182 assert_eq!(error.constraint, MediaTrackProperty::from("")); 183 assert_eq!(error.message, None); 184 } 185 186 mod exposed { 187 use super::*; 188 189 #[test] 190 fn missing() { 191 let error = test_overconstrained( 192 &default_possible_settings(), 193 ResolvedMandatoryMediaTrackConstraints::from_iter([( 194 GROUP_ID.clone(), 195 ResolvedValueConstraint::default() 196 .exact("missing-group".to_owned()) 197 .into(), 198 )]), 199 DeviceInformationExposureMode::Exposed, 200 ); 201 202 let constraint = &error.constraint; 203 let err_message = error.message.as_ref().expect("Error message."); 204 205 assert_eq!(constraint, &GROUP_ID); 206 assert_eq!( 207 err_message, 208 "Setting was missing (does not satisfy (x == \"missing-group\"))." 209 ); 210 } 211 212 #[test] 213 fn mismatch() { 214 let error = test_overconstrained( 215 &default_possible_settings(), 216 ResolvedMandatoryMediaTrackConstraints::from_iter([( 217 DEVICE_ID.clone(), 218 ResolvedValueConstraint::default() 219 .exact("mismatched-device".to_owned()) 220 .into(), 221 )]), 222 DeviceInformationExposureMode::Exposed, 223 ); 224 225 let constraint = &error.constraint; 226 let err_message = error.message.as_ref().expect("Error message."); 227 228 assert_eq!(constraint, &DEVICE_ID); 229 assert_eq!( 230 err_message, 231 "Setting was a mismatch ([\"1080p\", \"1440p\", \"2160p\", \"480p\", \"720p\"] do not satisfy (x == \"mismatched-device\"))." 232 ); 233 } 234 235 #[test] 236 fn too_small() { 237 let error = test_overconstrained( 238 &default_possible_settings(), 239 ResolvedMandatoryMediaTrackConstraints::from_iter([( 240 FRAME_RATE.clone(), 241 ResolvedValueRangeConstraint::default().min(1000).into(), 242 )]), 243 DeviceInformationExposureMode::Exposed, 244 ); 245 246 let constraint = &error.constraint; 247 let err_message = error.message.as_ref().expect("Error message."); 248 249 assert_eq!(constraint, &FRAME_RATE); 250 assert_eq!( 251 err_message, 252 "Setting was too small ([120, 15, 240, 30, 60] do not satisfy (1000 <= x))." 253 ); 254 } 255 256 #[test] 257 fn too_large() { 258 let error = test_overconstrained( 259 &default_possible_settings(), 260 ResolvedMandatoryMediaTrackConstraints::from_iter([( 261 FRAME_RATE.clone(), 262 ResolvedValueRangeConstraint::default().max(10).into(), 263 )]), 264 DeviceInformationExposureMode::Exposed, 265 ); 266 267 let constraint = &error.constraint; 268 let err_message = error.message.as_ref().expect("Error message."); 269 270 assert_eq!(constraint, &FRAME_RATE); 271 assert_eq!( 272 err_message, 273 "Setting was too large ([120, 15, 240, 30, 60] do not satisfy (x <= 10))." 274 ); 275 } 276 } 277 } 278 279 mod constrained { 280 use super::*; 281 282 #[test] 283 fn specific_device_id() { 284 let possible_settings = default_possible_settings(); 285 286 for target_settings in possible_settings.iter() { 287 let setting = match target_settings.get(&DEVICE_ID) { 288 Some(setting) => setting, 289 None => continue, 290 }; 291 292 let actual = test_constrained( 293 &possible_settings, 294 ResolvedMandatoryMediaTrackConstraints::from_iter([( 295 DEVICE_ID.clone(), 296 ResolvedMediaTrackConstraint::exact_from(setting.clone()), 297 )]), 298 ResolvedAdvancedMediaTrackConstraints::default(), 299 ); 300 301 let expected = vec![target_settings]; 302 303 assert_eq!(actual, expected); 304 } 305 } 306 307 mod exact { 308 use super::*; 309 310 #[test] 311 fn value() { 312 let possible_settings = vec![ 313 MediaTrackSettings::from_iter([ 314 (&DEVICE_ID, "a".into()), 315 (&GROUP_ID, "group-0".into()), 316 ]), 317 MediaTrackSettings::from_iter([ 318 (&DEVICE_ID, "b".into()), 319 (&GROUP_ID, "group-1".into()), 320 ]), 321 MediaTrackSettings::from_iter([ 322 (&DEVICE_ID, "c".into()), 323 (&GROUP_ID, "group-2".into()), 324 ]), 325 ]; 326 327 let actual = test_constrained( 328 &possible_settings, 329 ResolvedMandatoryMediaTrackConstraints::from_iter([( 330 &GROUP_ID, 331 ResolvedValueConstraint::default() 332 .exact("group-1".to_owned()) 333 .into(), 334 )]), 335 ResolvedAdvancedMediaTrackConstraints::default(), 336 ); 337 338 let expected = vec![&possible_settings[1]]; 339 340 assert_eq!(actual, expected); 341 } 342 343 #[test] 344 fn value_range() { 345 let possible_settings = vec![ 346 MediaTrackSettings::from_iter([(&DEVICE_ID, "a".into()), (&FRAME_RATE, 15.into())]), 347 MediaTrackSettings::from_iter([(&DEVICE_ID, "b".into()), (&FRAME_RATE, 30.into())]), 348 MediaTrackSettings::from_iter([(&DEVICE_ID, "c".into()), (&FRAME_RATE, 60.into())]), 349 ]; 350 351 let actual = test_constrained( 352 &possible_settings, 353 ResolvedMandatoryMediaTrackConstraints::from_iter([( 354 &FRAME_RATE, 355 ResolvedValueRangeConstraint::default().exact(30).into(), 356 )]), 357 ResolvedAdvancedMediaTrackConstraints::default(), 358 ); 359 360 let expected = vec![&possible_settings[1]]; 361 362 assert_eq!(actual, expected); 363 } 364 365 #[test] 366 fn value_sequence() { 367 let possible_settings = vec![ 368 MediaTrackSettings::from_iter([ 369 (&DEVICE_ID, "a".into()), 370 (&GROUP_ID, "group-0".into()), 371 ]), 372 MediaTrackSettings::from_iter([ 373 (&DEVICE_ID, "b".into()), 374 (&GROUP_ID, "group-1".into()), 375 ]), 376 MediaTrackSettings::from_iter([ 377 (&DEVICE_ID, "c".into()), 378 (&GROUP_ID, "group-2".into()), 379 ]), 380 ]; 381 382 let actual = test_constrained( 383 &possible_settings, 384 ResolvedMandatoryMediaTrackConstraints::from_iter([( 385 &GROUP_ID, 386 ResolvedValueSequenceConstraint::default() 387 .exact(vec!["group-1".to_owned(), "group-3".to_owned()]) 388 .into(), 389 )]), 390 ResolvedAdvancedMediaTrackConstraints::default(), 391 ); 392 393 let expected = vec![&possible_settings[1]]; 394 395 assert_eq!(actual, expected); 396 } 397 } 398 399 mod ideal { 400 use super::*; 401 402 #[test] 403 fn value() { 404 let possible_settings = vec![ 405 MediaTrackSettings::from_iter([ 406 (&DEVICE_ID, "a".into()), 407 (&GROUP_ID, "group-0".into()), 408 ]), 409 MediaTrackSettings::from_iter([ 410 (&DEVICE_ID, "b".into()), 411 (&GROUP_ID, "group-1".into()), 412 ]), 413 MediaTrackSettings::from_iter([ 414 (&DEVICE_ID, "c".into()), 415 (&GROUP_ID, "group-2".into()), 416 ]), 417 ]; 418 419 let actual = test_constrained( 420 &possible_settings, 421 ResolvedMandatoryMediaTrackConstraints::from_iter([( 422 &GROUP_ID, 423 ResolvedValueConstraint::default() 424 .ideal("group-1".to_owned()) 425 .into(), 426 )]), 427 ResolvedAdvancedMediaTrackConstraints::default(), 428 ); 429 430 let expected = vec![&possible_settings[1]]; 431 432 assert_eq!(actual, expected); 433 } 434 435 #[test] 436 fn value_range() { 437 let possible_settings = vec![ 438 MediaTrackSettings::from_iter([(&DEVICE_ID, "a".into()), (&FRAME_RATE, 15.into())]), 439 MediaTrackSettings::from_iter([(&DEVICE_ID, "b".into()), (&FRAME_RATE, 30.into())]), 440 MediaTrackSettings::from_iter([(&DEVICE_ID, "c".into()), (&FRAME_RATE, 60.into())]), 441 ]; 442 443 let actual = test_constrained( 444 &possible_settings, 445 ResolvedMandatoryMediaTrackConstraints::from_iter([( 446 &FRAME_RATE, 447 ResolvedValueRangeConstraint::default().ideal(32).into(), 448 )]), 449 ResolvedAdvancedMediaTrackConstraints::default(), 450 ); 451 452 let expected = vec![&possible_settings[1]]; 453 454 assert_eq!(actual, expected); 455 } 456 457 #[test] 458 fn value_sequence() { 459 let possible_settings = vec![ 460 MediaTrackSettings::from_iter([ 461 (&DEVICE_ID, "a".into()), 462 (&GROUP_ID, "group-0".into()), 463 ]), 464 MediaTrackSettings::from_iter([ 465 (&DEVICE_ID, "b".into()), 466 (&GROUP_ID, "group-1".into()), 467 ]), 468 MediaTrackSettings::from_iter([ 469 (&DEVICE_ID, "c".into()), 470 (&GROUP_ID, "group-2".into()), 471 ]), 472 ]; 473 474 let actual = test_constrained( 475 &possible_settings, 476 ResolvedMandatoryMediaTrackConstraints::from_iter([( 477 &GROUP_ID, 478 ResolvedValueSequenceConstraint::default() 479 .ideal(vec!["group-1".to_owned(), "group-3".to_owned()]) 480 .into(), 481 )]), 482 ResolvedAdvancedMediaTrackConstraints::default(), 483 ); 484 485 let expected = vec![&possible_settings[1]]; 486 487 assert_eq!(actual, expected); 488 } 489 } 490 } 491 492 // ``` 493 // ┌ 494 // mandatory constraints: ┤ ┄───────────────────────────────────────────┤ 495 // └ 496 // ┌ 497 // advanced constraints: ┤ ├─┤ ├────────────────────────────┄ 498 // └ 499 // ┌ 500 // possible settings: ┤ ●─────────────●──────────────●──────────────●─────────────● 501 // └ 480p 720p 1080p 1440p 2160p 502 // └───────┬──────┘ 503 // selected settings: ─────────────────────────────────────────┘ 504 // ``` 505 mod smoke { 506 use super::*; 507 508 #[test] 509 fn native() { 510 let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ 511 &DEVICE_ID, 512 &HEIGHT, 513 &WIDTH, 514 &RESIZE_MODE, 515 ]); 516 517 let possible_settings = vec![ 518 MediaTrackSettings::from_iter([ 519 (&DEVICE_ID, "480p".into()), 520 (&HEIGHT, 480.into()), 521 (&WIDTH, 720.into()), 522 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 523 ]), 524 MediaTrackSettings::from_iter([ 525 (&DEVICE_ID, "720p".into()), 526 (&HEIGHT, 720.into()), 527 (&WIDTH, 1280.into()), 528 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 529 ]), 530 MediaTrackSettings::from_iter([ 531 (&DEVICE_ID, "1080p".into()), 532 (&HEIGHT, 1080.into()), 533 (&WIDTH, 1920.into()), 534 (&RESIZE_MODE, ResizeMode::none().into()), 535 ]), 536 MediaTrackSettings::from_iter([ 537 (&DEVICE_ID, "1440p".into()), 538 (&HEIGHT, 1440.into()), 539 (&WIDTH, 2560.into()), 540 (&RESIZE_MODE, ResizeMode::none().into()), 541 ]), 542 MediaTrackSettings::from_iter([ 543 (&DEVICE_ID, "2160p".into()), 544 (&HEIGHT, 2160.into()), 545 (&WIDTH, 3840.into()), 546 (&RESIZE_MODE, ResizeMode::none().into()), 547 ]), 548 ]; 549 550 let constraints: ResolvedMediaTrackConstraints = ResolvedMediaTrackConstraints { 551 mandatory: ResolvedMandatoryMediaTrackConstraints::from_iter([ 552 ( 553 &WIDTH, 554 ResolvedValueRangeConstraint::default().max(2560).into(), 555 ), 556 ( 557 &HEIGHT, 558 ResolvedValueRangeConstraint::default().max(1440).into(), 559 ), 560 // Unsupported constraint, which should thus get ignored: 561 ( 562 &FRAME_RATE, 563 ResolvedValueRangeConstraint::default().exact(30.0).into(), 564 ), 565 ]), 566 advanced: ResolvedAdvancedMediaTrackConstraints::from_iter([ 567 // The first advanced constraint set of "exact 800p" does not match 568 // any candidate and should thus get ignored by the algorithm: 569 ResolvedMediaTrackConstraintSet::from_iter([( 570 &HEIGHT, 571 ResolvedValueRangeConstraint::default().exact(800).into(), 572 )]), 573 // The second advanced constraint set of "no resizing" does match 574 // candidates and should thus be applied by the algorithm: 575 ResolvedMediaTrackConstraintSet::from_iter([( 576 &RESIZE_MODE, 577 ResolvedValueConstraint::default() 578 .exact(ResizeMode::none()) 579 .into(), 580 )]), 581 ]), 582 }; 583 584 let sanitized_constraints = constraints.to_sanitized(&supported_constraints); 585 586 let actual = select_settings_candidates( 587 &possible_settings, 588 &sanitized_constraints, 589 DeviceInformationExposureMode::Exposed, 590 ) 591 .unwrap(); 592 593 let expected = vec![&possible_settings[2], &possible_settings[3]]; 594 595 assert_eq!(actual, expected); 596 } 597 598 #[cfg(feature = "serde")] 599 #[test] 600 fn json() { 601 let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ 602 &DEVICE_ID, 603 &HEIGHT, 604 &WIDTH, 605 &RESIZE_MODE, 606 ]); 607 608 // Deserialize possible settings from JSON: 609 let possible_settings: Vec<MediaTrackSettings> = { 610 let json = serde_json::json!([ 611 { "deviceId": "480p", "width": 720, "height": 480, "resizeMode": "crop-and-scale" }, 612 { "deviceId": "720p", "width": 1280, "height": 720, "resizeMode": "crop-and-scale" }, 613 { "deviceId": "1080p", "width": 1920, "height": 1080, "resizeMode": "none" }, 614 { "deviceId": "1440p", "width": 2560, "height": 1440, "resizeMode": "none" }, 615 { "deviceId": "2160p", "width": 3840, "height": 2160, "resizeMode": "none" }, 616 ]); 617 serde_json::from_value(json).unwrap() 618 }; 619 620 // Deserialize constraints from JSON: 621 let constraints: MediaTrackConstraints = { 622 let json = serde_json::json!({ 623 "width": { 624 "max": 2560, 625 }, 626 "height": { 627 "max": 1440, 628 }, 629 // Unsupported constraint, which should thus get ignored: 630 "frameRate": { 631 "exact": 30.0 632 }, 633 "advanced": [ 634 // The first advanced constraint set of "exact 800p" does not match 635 // any candidate and should thus get ignored by the algorithm: 636 { "height": 800 }, 637 // The second advanced constraint set of "no resizing" does match 638 // candidates and should thus be applied by the algorithm: 639 { "resizeMode": "none" }, 640 ] 641 }); 642 serde_json::from_value(json).unwrap() 643 }; 644 645 // Resolve bare values to proper constraints: 646 let resolved_constraints = constraints.into_resolved(); 647 648 // Sanitize constraints, removing empty and unsupported constraints: 649 let sanitized_constraints = resolved_constraints.into_sanitized(&supported_constraints); 650 651 let actual = select_settings_candidates( 652 &possible_settings, 653 &sanitized_constraints, 654 DeviceInformationExposureMode::Exposed, 655 ) 656 .unwrap(); 657 658 let expected = vec![&possible_settings[2], &possible_settings[3]]; 659 660 assert_eq!(actual, expected); 661 } 662 } 663