|
@@ -30,7 +30,7 @@ import { Vector2 } from '../geometry/vector2';
|
30
|
30
|
import { Resolution } from '../utils/resolution';
|
31
|
31
|
import { Nullable } from '../utils/utils';
|
32
|
32
|
import { Utils } from '../utils/utils';
|
33
|
|
-import { AREvent, AREventTarget, AREventListener } from '../utils/ar-events';
|
|
33
|
+import { AREvent, AREventTarget } from '../utils/ar-events';
|
34
|
34
|
import { IllegalArgumentError, IllegalOperationError, NotSupportedError, AccessDeniedError } from '../utils/errors';
|
35
|
35
|
|
36
|
36
|
|
|
@@ -106,6 +106,9 @@ const FOREGROUND_ZINDEX = BASE_ZINDEX + 1;
|
106
|
106
|
/** Z-index of the HUD */
|
107
|
107
|
const HUD_ZINDEX = BASE_ZINDEX + 2;
|
108
|
108
|
|
|
109
|
+/** Time in ms used to throttle the resize callback of the window */
|
|
110
|
+const RESIZE_THROTTLE_DELAY = 100;
|
|
111
|
+
|
109
|
112
|
|
110
|
113
|
|
111
|
114
|
|
|
@@ -488,14 +491,8 @@ class ViewportResizer
|
488
|
491
|
/** the viewport to be resized */
|
489
|
492
|
private readonly _viewport: Viewport;
|
490
|
493
|
|
491
|
|
- /** a helper */
|
492
|
|
- private _timeout: Nullable<ReturnType<typeof setTimeout>>;
|
493
|
|
-
|
494
|
|
- /** bound resize method */
|
495
|
|
- private readonly _resize: () => void;
|
496
|
|
-
|
497
|
|
- /** bound event trigger */
|
498
|
|
- private readonly _triggerResize: () => void;
|
|
494
|
+ /** a timer used for throttling */
|
|
495
|
+ private _throttleTimer: Nullable<ReturnType<typeof setTimeout>>;
|
499
|
496
|
|
500
|
497
|
/** resize strategy */
|
501
|
498
|
private _resizeStrategy: ViewportResizeStrategy;
|
|
@@ -510,15 +507,16 @@ class ViewportResizer
|
510
|
507
|
constructor(viewport: Viewport)
|
511
|
508
|
{
|
512
|
509
|
this._viewport = viewport;
|
513
|
|
- this._timeout = null;
|
514
|
|
- this._resize = this._onResize.bind(this);
|
515
|
|
- this._triggerResize = this.triggerResize.bind(this);
|
|
510
|
+ this._throttleTimer = null;
|
|
511
|
+ this._onViewportResize = this._onViewportResize.bind(this);
|
|
512
|
+ this._onWindowResize = this._onWindowResize.bind(this);
|
|
513
|
+ this._onOrientationChange = this._onOrientationChange.bind(this);
|
516
|
514
|
this._resizeStrategy = new InlineResizeStrategy();
|
517
|
515
|
|
518
|
516
|
// initial setup
|
519
|
517
|
// (the size is yet unknown)
|
520
|
|
- this._viewport.addEventListener('resize', this._resize);
|
521
|
|
- this.triggerResize(0);
|
|
518
|
+ this._viewport.addEventListener('resize', this._onViewportResize);
|
|
519
|
+ this._triggerResizeEvent();
|
522
|
520
|
}
|
523
|
521
|
|
524
|
522
|
/**
|
|
@@ -528,17 +526,16 @@ class ViewportResizer
|
528
|
526
|
{
|
529
|
527
|
// Configure the resize listener. We want the viewport to adjust itself
|
530
|
528
|
// if the phone/screen is resized or changes orientation
|
531
|
|
- window.addEventListener('resize', this._triggerResize); // a delay is welcome
|
|
529
|
+ window.addEventListener('resize', this._onWindowResize);
|
532
|
530
|
|
533
|
531
|
// handle changes of orientation
|
534
|
|
- // (is this needed? we already listen to resize events)
|
535
|
|
- if(screen.orientation !== undefined)
|
536
|
|
- screen.orientation.addEventListener('change', this._triggerResize);
|
|
532
|
+ if(typeof screen.orientation === 'object')
|
|
533
|
+ screen.orientation.addEventListener('change', this._onOrientationChange);
|
537
|
534
|
else
|
538
|
|
- window.addEventListener('orientationchange', this._triggerResize); // deprecated
|
|
535
|
+ window.addEventListener('orientationchange', this._onOrientationChange); // deprecated
|
539
|
536
|
|
540
|
537
|
// trigger a resize to setup the sizes / the CSS
|
541
|
|
- this.triggerResize(0);
|
|
538
|
+ this._triggerResizeEvent();
|
542
|
539
|
}
|
543
|
540
|
|
544
|
541
|
/**
|
|
@@ -546,40 +543,18 @@ class ViewportResizer
|
546
|
543
|
*/
|
547
|
544
|
release(): void
|
548
|
545
|
{
|
549
|
|
- if(screen.orientation !== undefined)
|
550
|
|
- screen.orientation.removeEventListener('change', this._triggerResize);
|
|
546
|
+ if(typeof screen.orientation === 'object')
|
|
547
|
+ screen.orientation.removeEventListener('change', this._onOrientationChange);
|
551
|
548
|
else
|
552
|
|
- window.removeEventListener('orientationchange', this._triggerResize);
|
|
549
|
+ window.removeEventListener('orientationchange', this._onOrientationChange);
|
553
|
550
|
|
554
|
|
- window.removeEventListener('resize', this._triggerResize);
|
|
551
|
+ window.removeEventListener('resize', this._onWindowResize);
|
555
|
552
|
|
556
|
|
- this._viewport.removeEventListener('resize', this._resize);
|
|
553
|
+ this._viewport.removeEventListener('resize', this._onViewportResize);
|
557
|
554
|
this._resizeStrategy.clear(this._viewport);
|
558
|
555
|
}
|
559
|
556
|
|
560
|
557
|
/**
|
561
|
|
- * Trigger a resize event after a delay
|
562
|
|
- * @param delay in milliseconds
|
563
|
|
- */
|
564
|
|
- triggerResize(delay: number = 100): void
|
565
|
|
- {
|
566
|
|
- const event = new ViewportEvent('resize');
|
567
|
|
-
|
568
|
|
- if(delay <= 0) {
|
569
|
|
- this._viewport.dispatchEvent(event);
|
570
|
|
- return;
|
571
|
|
- }
|
572
|
|
-
|
573
|
|
- if(this._timeout !== null)
|
574
|
|
- clearTimeout(this._timeout);
|
575
|
|
-
|
576
|
|
- this._timeout = setTimeout(() => {
|
577
|
|
- this._timeout = null;
|
578
|
|
- this._viewport.dispatchEvent(event);
|
579
|
|
- }, delay);
|
580
|
|
- }
|
581
|
|
-
|
582
|
|
- /**
|
583
|
558
|
* Change the resize strategy
|
584
|
559
|
* @param strategy new strategy
|
585
|
560
|
*/
|
|
@@ -587,7 +562,7 @@ class ViewportResizer
|
587
|
562
|
{
|
588
|
563
|
this._resizeStrategy.clear(this._viewport);
|
589
|
564
|
this._resizeStrategy = strategy;
|
590
|
|
- this.triggerResize(0);
|
|
565
|
+ this._triggerResizeEvent();
|
591
|
566
|
}
|
592
|
567
|
|
593
|
568
|
/**
|
|
@@ -615,9 +590,94 @@ class ViewportResizer
|
615
|
590
|
}
|
616
|
591
|
|
617
|
592
|
/**
|
618
|
|
- * Resize callback
|
|
593
|
+ * Trigger a resize event
|
|
594
|
+ */
|
|
595
|
+ private _triggerResizeEvent(): void
|
|
596
|
+ {
|
|
597
|
+ const event = new ViewportEvent('resize');
|
|
598
|
+ this._viewport.dispatchEvent(event);
|
|
599
|
+ }
|
|
600
|
+
|
|
601
|
+ /**
|
|
602
|
+ * Called when the window receives a 'resize' event
|
|
603
|
+ */
|
|
604
|
+ private _onWindowResize(): void
|
|
605
|
+ {
|
|
606
|
+ // throttle the resize callback
|
|
607
|
+ if(this._throttleTimer !== null)
|
|
608
|
+ clearTimeout(this._throttleTimer);
|
|
609
|
+
|
|
610
|
+ this._throttleTimer = setTimeout(() => {
|
|
611
|
+ this._throttleTimer = null;
|
|
612
|
+ this._triggerResizeEvent();
|
|
613
|
+ }, RESIZE_THROTTLE_DELAY);
|
|
614
|
+ }
|
|
615
|
+
|
|
616
|
+ /**
|
|
617
|
+ * Called when a change of screen orientation is detected
|
|
618
|
+ */
|
|
619
|
+ private async _onOrientationChange(): Promise<void>
|
|
620
|
+ {
|
|
621
|
+ /*
|
|
622
|
+
|
|
623
|
+ When changing the orientation of a mobile device, sometimes there will
|
|
624
|
+ be a mismatch between the viewport and the screen: one will be in
|
|
625
|
+ landscape mode and the other in portrait mode.
|
|
626
|
+
|
|
627
|
+ Ultimately, the size of the viewport is dependent on the size of the
|
|
628
|
+ underlying media (typically a camera feed). When the device is rotated,
|
|
629
|
+ the browser should rotate the camera feed automatically*. In addition,
|
|
630
|
+ it will trigger a resize event on the window, which will, in time,
|
|
631
|
+ resize the viewport. If the order of events is such that the viewport
|
|
632
|
+ is resized before the camera feed is rotated, then we will observe a
|
|
633
|
+ distorted video.
|
|
634
|
+
|
|
635
|
+ Let's correct the issue by first detecting the situation and then by
|
|
636
|
+ triggering a new resize event.
|
|
637
|
+
|
|
638
|
+ (*) at the time of this writing, when testing on Android devices, I see
|
|
639
|
+ that Chrome does it. Firefox 139 does it in one device but doesn't
|
|
640
|
+ do it in another (it's a browser bug - see below). Safari/iOS does
|
|
641
|
+ it (tested on the cloud).
|
|
642
|
+
|
|
643
|
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1802849
|
|
644
|
+
|
|
645
|
+ */
|
|
646
|
+
|
|
647
|
+ const MAX_ATTEMPTS = 3;
|
|
648
|
+ const canvas = this._viewport._backgroundCanvas;
|
|
649
|
+
|
|
650
|
+ for(let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
651
|
+
|
|
652
|
+ await Utils.wait(RESIZE_THROTTLE_DELAY);
|
|
653
|
+
|
|
654
|
+ // After one delay, this._viewport.aspectRatio will likely be
|
|
655
|
+ // correct, but the canvas will not reflect that if, instants ago,
|
|
656
|
+ // it was resized using the previous aspect ratio of the media.
|
|
657
|
+ const a = canvas.width / canvas.height;
|
|
658
|
+ const b = screen.width / screen.height;
|
|
659
|
+
|
|
660
|
+ // if there is a mismatch between the aspect ratios, then the last
|
|
661
|
+ // resize of the viewport took place with the previous aspect ratio
|
|
662
|
+ // of the media. This means that canvas is now incorrectly sized,
|
|
663
|
+ // and that the previous viewport resize event was ineffective.
|
|
664
|
+ if((a-1) * (b-1) < 0) { //if((a < 1 && b > 1) || (a > 1 && b < 1))
|
|
665
|
+ this._triggerResizeEvent();
|
|
666
|
+ break;
|
|
667
|
+ }
|
|
668
|
+
|
|
669
|
+ // Note: when using a canvas or a video file as a source of data,
|
|
670
|
+ // its aspect ratio is not expected to change. We will trigger an
|
|
671
|
+ // additional resize event on mobile in this case. Unlikely to be
|
|
672
|
+ // an issue.
|
|
673
|
+
|
|
674
|
+ }
|
|
675
|
+ }
|
|
676
|
+
|
|
677
|
+ /**
|
|
678
|
+ * Called when the viewport receives a 'resize' event
|
619
|
679
|
*/
|
620
|
|
- private _onResize(): void
|
|
680
|
+ private _onViewportResize(): void
|
621
|
681
|
{
|
622
|
682
|
const viewport = this._viewport;
|
623
|
683
|
|
|
@@ -760,7 +820,7 @@ class BestFitResizeStrategy extends ImmersiveResizeStrategy
|
760
|
820
|
|
761
|
821
|
/**
|
762
|
822
|
* Immersive viewport with stretch style: it occupies the entire page and
|
763
|
|
- * fully stretches the media
|
|
823
|
+ * fully stretches the media, possibly distorting it
|
764
|
824
|
*/
|
765
|
825
|
class StretchResizeStrategy extends ImmersiveResizeStrategy
|
766
|
826
|
{
|