Selaa lähdekoodia

Fix a possible distortion of the viewport caused by concurrency issues when changing the orientation of a mobile device. Related to #13

customisations
alemart 3 kuukautta sitten
vanhempi
commit
394e6cda95
1 muutettua tiedostoa jossa 111 lisäystä ja 51 poistoa
  1. 111
    51
      src/core/viewport.ts

+ 111
- 51
src/core/viewport.ts Näytä tiedosto

@@ -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
 {

Loading…
Peruuta
Tallenna