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