import Hls from 'hls.js'; import { FrontSide, Mesh, MeshBasicMaterial, PlaneGeometry, VideoTexture, } from 'three'; import { clamp } from 'three/src/math/MathUtils'; export class VideoPlayer { public video = document.createElement('video'); public material!: MeshBasicMaterial; public texture!: VideoTexture; public mesh!: Mesh; public hls?: Hls; public playable: boolean; public geometry!: PlaneGeometry; constructor(public width: number, public height: number) {} initialize() { this.texture = new VideoTexture(this.video); this.material = new MeshBasicMaterial({ map: this.texture, side: FrontSide, toneMapped: false, }); this.geometry = new PlaneGeometry(this.width, this.height); this.mesh = new Mesh(this.geometry, this.material); this.video.addEventListener('canplay', () => { this.playable = true; }); this.video.addEventListener('error', () => { this.playable = false; this.video.pause(); this.video.src = undefined; }); this.video.volume = 0.8; } public setSource(source: string, autoplay = false) { this.video.pause(); if (this.hls) { this.hls.stopLoad(); this.hls.destroy(); } if (source.endsWith('m3u8')) { if (Hls.isSupported()) { this.hls = new Hls(); this.hls.loadSource(source); this.hls.attachMedia(this.video); this.hls.on(Hls.Events.MANIFEST_PARSED, () => { this.playable = true; if (autoplay) { this.video.play(); } else { this.hls.stopLoad(); } }); this.hls.on(Hls.Events.ERROR, (e, d) => { if (d.fatal) { this.playable = false; this.video.pause(); this.hls.stopLoad(); } }); return; } } this.video.src = source; if (autoplay) { this.video.play(); } } public play() { if (this.playable) { if (this.hls) { this.hls.startLoad(-1); } this.video.play(); } } public stop() { this.video.pause(); if (this.hls) { this.hls.stopLoad(); } } public setVolume(percent: number) { const setter = clamp(percent, 0, 100) / 100; this.video.volume = setter; } }