diff --git a/package-lock.json b/package-lock.json index c70cd94..2047723 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "babel-loader": "^8.2.4", "concurrently": "^7.1.0", "css-loader": "^6.7.1", + "hls.js": "^1.1.5", "html-webpack-plugin": "^5.5.0", "mini-css-extract-plugin": "^2.6.0", "nodemon": "^2.0.15", @@ -5558,6 +5559,12 @@ "he": "bin/he" } }, + "node_modules/hls.js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.1.5.tgz", + "integrity": "sha512-mQX5TSNtJEzGo5HPpvcQgCu+BWoKDQM6YYtg/KbgWkmVAcqOCvSTi0SuqG2ZJLXxIzdnFcKU2z7Mrw/YQWhPOA==", + "dev": true + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -14469,6 +14476,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hls.js": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.1.5.tgz", + "integrity": "sha512-mQX5TSNtJEzGo5HPpvcQgCu+BWoKDQM6YYtg/KbgWkmVAcqOCvSTi0SuqG2ZJLXxIzdnFcKU2z7Mrw/YQWhPOA==", + "dev": true + }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", diff --git a/package.json b/package.json index 2d43479..2865d02 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "babel-loader": "^8.2.4", "concurrently": "^7.1.0", "css-loader": "^6.7.1", + "hls.js": "^1.1.5", "html-webpack-plugin": "^5.5.0", "mini-css-extract-plugin": "^2.6.0", "nodemon": "^2.0.15", diff --git a/src/client/game.ts b/src/client/game.ts index 2340477..3a3821a 100644 --- a/src/client/game.ts +++ b/src/client/game.ts @@ -1,3 +1,4 @@ +import e from 'express'; import { Socket } from 'socket.io-client'; import { isMobileOrTablet } from '../common/helper'; import { CompositePacket } from '../common/types/packet'; @@ -5,6 +6,7 @@ import { IcyNetUser } from '../common/types/user'; import { ThirdPersonCamera } from './object/camera'; import { Chat } from './object/chat'; import { Joystick } from './object/joystick'; +import { VideoPlayer } from './object/other/video-player'; import { Player } from './object/player'; import { PlayerEntity } from './object/player-entity'; import modelLoaderInstance from './object/pony-loader'; @@ -20,6 +22,8 @@ export class Game { public renderer = new Renderer(); + private videoTest = new VideoPlayer(); + constructor(public socket: Socket) {} async initialize(): Promise { @@ -31,7 +35,30 @@ export class Game { this.chat.initialize(); this.socket.connect(); + // experimental + this.videoTest.initialize(); + this.videoTest.mesh.position.set(0, 6, -20); + this.renderer.scene.add(this.videoTest.mesh); + // end of + this.chat.registerSendFunction((message) => { + // experimental + if (message.startsWith('!play')) { + const [cmd, src] = message.split(' '); + if (src) { + this.videoTest.setSource(src, true); + } else { + this.videoTest.play(); + } + return; + } + + if (message.startsWith('!stop')) { + this.videoTest.stop(); + return; + } + // end of + this.socket.emit('chat-send', message); }); diff --git a/src/client/object/other/video-player.ts b/src/client/object/other/video-player.ts new file mode 100644 index 0000000..c1fa7ae --- /dev/null +++ b/src/client/object/other/video-player.ts @@ -0,0 +1,85 @@ +import Hls from 'hls.js'; +import { + FrontSide, + Mesh, + MeshBasicMaterial, + PlaneGeometry, + VideoTexture, +} from 'three'; + +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; + + initialize() { + this.texture = new VideoTexture(this.video); + this.material = new MeshBasicMaterial({ + map: this.texture, + side: FrontSide, + toneMapped: false, + }); + this.geometry = new PlaneGeometry(24, 12); + this.mesh = new Mesh(this.geometry, this.material); + this.video.addEventListener('canplay', () => { + this.playable = true; + }); + } + + public setSource(source: string, autoplay = false) { + if (this.hls) { + this.hls.stopLoad(); + this.hls.destroy(); + } + + if (source.endsWith('m3u8')) { + if (this.video.canPlayType('application/vnd.apple.mpegurl')) { + // Continue + } else 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) => { + this.playable = false; + // if (!d.fatal) return; + }); + 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(); + } + } +}