Three.js newbie => Angular panorama viewer website only rendering when accessing from my computer!

Hi,
I am new to three.js and I really need help (and eventually code review)…
Thank you for considering my situation.

I have developed a 360°-image viewer with three.js on Angular.
It’s based on three.js equirectangular demo :
https://threejs.org/examples/?q=equirec#webgl_panorama_equirectangular

The main difficulty for me has been manage source switching (= texture switching).
The result is most of time correctly rendered on the computer I used to develop the app (both locally and online, with both Chrome and Firefox).

But from any other computer, the scene, geometry, material, mesh, texture … are loading (observed thanks to Three.js Developper Tool extension on Firefox) but nothing appears on screen.
The same same thing happens on my computer (the one I manage to view the panorama with when navigating on my website) sometimes for no reason AND systematically when I refresh the web page (instead of navigating to it).

Here is my Angular Typescript code :

import { CustomerService } from './../../../core/services/customer.service';
import { PanoramasService } from './../../services/panoramas.service';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef, HostListener } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { Customer } from 'src/app/core/models/customer';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import * as THREE from 'three';

@Component({
  selector: 'sacha-page-panorama',
  templateUrl: './page-panorama.component.html',
  styleUrls: ['./page-panorama.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PagePanoramaComponent implements OnInit, OnDestroy {
  public container: HTMLElement;

  public pageReloadSubscription: Subscription;
  private customerSubscription: Subscription;
  private panoramasSubscription: Subscription;
  public operationId: string;
  public panoramas: string[];
  public selectedPanorama: string;
  public loading: boolean;
  public error: string;

  // Are the panorama images sources switched to local ?
  local: boolean = false;
  // Paths to local panorama images sources
  locals = [
    '/assets/images/360.jpeg',
    '/assets/images/063.jpeg'
  ]

  private camera: any;
  private scene: THREE.Scene;
  private renderer: THREE.WebGLRenderer;
  private texture: THREE.Texture;
  private material: THREE.MeshBasicMaterial;
  private mesh: THREE.Mesh;
  private geometry: THREE.SphereBufferGeometry;
  private onMouseDownMouseX: number;
  private onMouseDownMouseY: number;
  private isUserInteracting = false;
  private lon = 0;
  private onMouseDownLon = 0;
  private lat = 0;
  private onMouseDownLat = 0;
  private phi = 0;
  private theta = 0;

  // Icons
  public faSpinner = faSpinner;

  constructor(private customerService: CustomerService, public panoramasService: PanoramasService, private route: ActivatedRoute, private cdr: ChangeDetectorRef, private router: Router) { }

  public ngOnInit(): void {
    // Get operation id from URL
    this.route.params.subscribe(params => {
      this.operationId = params['operationId'];
    });
    this.customerSubscription = this.customerService.customer$.subscribe((customer: Customer) => {
      // Load server panorama images sources
      this.panoramasService.subscribePanoramas(this.operationId);
    });
    this.panoramasSubscription = this.panoramasService.panoramas$.subscribe((panoramas: string[]) => {
      // Occurs when server panorama images sources are loaded
      this.panoramas = panoramas
      if (this.panoramas) {
        this.selectedPanorama = this.panoramas[0];
        this.cleanObjects();
        this.cleanScene();
        this.init(this.selectedPanorama);
      }
    });
  }

  public ngOnDestroy(): void {
    this.cleanObjects();
    this.cleanScene();
    this.customerSubscription.unsubscribe();
    this.panoramasSubscription.unsubscribe();
  }

  public onPanoramaSelect(panorama: string): void {
    this.selectedPanorama = panorama;
    this.changeTexture(panorama)
  }

  private changeTexture(panorama: string): void {
    this.cleanObjects();
    this.loading = true;
    this.error = null;
    this.texture = new THREE.TextureLoader().load(panorama,
      (texture) => {
        this.loading = false;
        this.geometry = new THREE.SphereBufferGeometry(500, 60, 40);
        // invert the geometry on the x-axis so that all of the faces point inward
        this.geometry.scale(- 1, 1, 1);
        this.material = new THREE.MeshBasicMaterial({ map: this.texture });
        this.material.map.needsUpdate = true;
        this.mesh = new THREE.Mesh(this.geometry, this.material);
        this.scene.add(this.mesh);
        this.cdr.detectChanges();
      },
      (xhr) => {
      },
      (error) => {
        this.loading = false;
        this.error = "Impossible de lire l'image !";
        this.cdr.detectChanges();
      });
  }

  // Init scene
  private init(panorama: string): void {
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
    this.camera.target = new THREE.Vector3(0, 0, 0);
    this.renderer = new THREE.WebGLRenderer();
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.7);
    this.renderer.autoClear = true;
    this.container = document.querySelector('#container');
    if (this.container) {
      this.container.appendChild(this.renderer.domElement);
    }
    this.changeTexture(panorama);
    this.animate();
  }

  private animate(): void {
    requestAnimationFrame(() => this.animate());
    this.update();
  }

  private update(): void {
    if (this.isUserInteracting === false) {
      this.lon += 0.1;
    }
    this.lat = Math.max(- 85, Math.min(85, this.lat));
    this.phi = THREE.MathUtils.degToRad(90 - this.lat);
    this.theta = THREE.MathUtils.degToRad(this.lon);
    this.camera.target.x = 500 * Math.sin(this.phi) * Math.cos(this.theta);
    this.camera.target.y = 500 * Math.cos(this.phi);
    this.camera.target.z = 500 * Math.sin(this.phi) * Math.sin(this.theta);
    this.camera.lookAt(this.camera.target);
    this.renderer.render(this.scene, this.camera);
  }


  private cleanScene() {
    if (this.renderer) {
      this.renderer.dispose();
    }
    if (this.scene) {
      this.scene.dispose();
    }
  }

  private cleanObjects(): void {
    if (this.geometry) {
      this.geometry.dispose();
    }
    if (this.material) {
      this.material.dispose();
    }
    if (this.mesh) {
      this.scene.remove(this.mesh);
    }
    if (this.texture) {
      this.texture.dispose();
    }
  }

  // FOLLOWING CODE TEMPORARLY COMMENTED AS CONSIDERED UNUSEFUL FOR DEBUGGING MY CURENT PROBLEM (ONLY LISTENERS ON PANORAMA AND WINDOW).

  // public onDragOver(event: any): void {
  //   event.preventDefault();
  //   event.dataTransfer.dropEffect = 'copy';
  // }

  // public onDragCenter(): void {
  //   document.body.style.opacity = '0.5';
  // }

  // public onDragLeave(): void {
  //   document.body.style.opacity = '1';
  // }

  // public onPointerStart(event: any): void {
  //   this.isUserInteracting = true;
  //   let clientX = event.clientX || event.touches[0].clientX;
  //   let clientY = event.clientY || event.touches[0].clientY;
  //   this.onMouseDownMouseX = clientX;
  //   this.onMouseDownMouseY = clientY;
  //   this.onMouseDownLon = this.lon;
  //   this.onMouseDownLat = this.lat;
  // }

  // public onPointerMove(event: any): void {
  //   if (this.isUserInteracting === true) {
  //     let clientX = event.clientX || event.touches[0].clientX;
  //     let clientY = event.clientY || event.touches[0].clientY;
  //     this.lon = (this.onMouseDownMouseX - clientX) * 0.1 + this.onMouseDownLon;
  //     this.lat = (clientY - this.onMouseDownMouseY) * 0.1 + this.onMouseDownLat;
  //   }
  // }

  // public onPointerUp(): void {
  //   this.isUserInteracting = false;
  // }

  // public onDocumentMouseWheel(event: any): void {
  //   const fov = this.camera.fov + event.deltaY * 0.05;
  //   this.camera.fov = THREE.MathUtils.clamp(fov, 10, 75);
  //   this.camera.updateProjectionMatrix();
  // }

  // @HostListener('window:resize')
  // private onWindowResize(): void {
  //   this.camera.aspect = window.innerWidth / window.innerHeight;
  //   this.camera.updateProjectionMatrix();
  //   this.renderer.setSize(window.innerWidth * 0.8, window.innerHeight * 0.7);
  // }

}

And here is my Angular HTML :

<section class="row no-gutters bg-violet-cloudy py-5 px-0 px-sm-2 px-md-3 px-lg-4 px-xl-5 px-xxl-6">
    <ng-container *ngIf="panoramas; else loadingPanoramas">
        <div class="col-12 col-lg-1">
            <ul *ngIf="panoramas.length > 1"
                class="d-flex d-lg-inline-flex justify-content-center flex-row flex-lg-column">
                <ng-container *ngIf="!local; else isLocal">
                    <li *ngFor="let panorama of panoramas; let index = index" (click)="onPanoramaSelect(panorama)"
                        class="btn text-white cursor-pointer m-2"
                        [ngClass]="panorama === selectedPanorama ? 'btn-primary' : 'btn-purple'">
                        api {{index + 1}}
                    </li>
                </ng-container>
                <ng-template #isLocal>
                    <li *ngFor="let local of locals; let index = index" (click)="onPanoramaSelect(local)"
                        class="btn text-white cursor-pointer m-2"
                        [ngClass]="local === selectedPanorama ? 'btn-primary' : 'btn-purple'">
                        local {{index + 1}}
                    </li>
                </ng-template>
                <li class="btn btn-warning text-white my-3"
                    (click)="local = !local; local ? onPanoramaSelect(locals[0]) : onPanoramaSelect(panoramas[0])">source</li>
            </ul>
        </div>
        <div id="container" class="d-flex justify-content-center col-12 col-lg-11" (mousedown)="onPointerStart($event)"
            (mousemove)="onPointerMove($event)" (mouseup)="onPointerUp()" (wheel)="onDocumentMouseWheel($event)"
            (touchstart)="onPointerStart($event)" (touchmove)="onPointerMove($event)" (touchnd)="onPointerUp()"
            (dragOver)="onDragOver($event)" (dragcenter)="onDragCenter()" (dragleave)="onDragLeave()"
            (drop)="$event.preventDefault();">
            <div *ngIf="error || loading" id="panoramaStatusWrap">
                <fa-icon *ngIf="loading" [icon]="faSpinner" size="2x" [pulse]="true" class="panoramaStatus text-grey">
                </fa-icon>
                <div *ngIf="error" class="panoramaStatus bg-danger text-white p-2">{{error}}</div>
            </div>
        </div>
    </ng-container>
    <ng-template #loadingPanoramas>
        <sacha-error-or-loading [observable]="panoramasService.panoramasError$"
            (reload)="panoramasService.subscribePanoramas(operationId)" class="col-12 p-9">
        </sacha-error-or-loading>
    </ng-template>
</section>