From 26a75afb3f62f9ad908465efc0ac98e28377f9a6 Mon Sep 17 00:00:00 2001 From: Greg Fodor <gfodor@gmail.com> Date: Sat, 29 Sep 2018 23:58:15 +0000 Subject: [PATCH] Split out big chunk of hub.js into room entry manager --- src/hub.js | 260 ++------------------------------ src/react-components/ui-root.js | 3 + src/room-entry-manager.js | 259 +++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+), 247 deletions(-) create mode 100644 src/room-entry-manager.js diff --git a/src/hub.js b/src/hub.js index 6307c8fc1..07f2ddf44 100644 --- a/src/hub.js +++ b/src/hub.js @@ -7,7 +7,6 @@ import "./utils/logging"; import { patchWebGLRenderingContext } from "./utils/webgl"; patchWebGLRenderingContext(); -import screenfull from "screenfull"; import "three/examples/js/loaders/GLTFLoader"; import "networked-aframe/src/index"; import "naf-janus-adapter"; @@ -27,7 +26,6 @@ import joystick_dpad4 from "./behaviours/joystick-dpad4"; import msft_mr_axis_with_deadzone from "./behaviours/msft-mr-axis-with-deadzone"; import { PressedMove } from "./activators/pressedmove"; import { ReverseY } from "./activators/reversey"; -import { ObjectContentOrigins } from "./object-types"; import "./activators/shortpress"; @@ -77,7 +75,8 @@ import HubChannel from "./utils/hub-channel"; import LinkChannel from "./utils/link-channel"; import { connectToReticulum } from "./utils/phoenix-utils"; import { disableiOSZoom } from "./utils/disable-ios-zoom"; -import { addMedia, resolveMedia } from "./utils/media-utils"; +import { resolveMedia } from "./utils/media-utils"; +import RoomEntryManager from "./room-entry-manager"; import "./systems/nav"; import "./systems/personal-space-bubble"; @@ -98,7 +97,6 @@ const store = window.APP.store; const qs = new URLSearchParams(location.search); const isMobile = AFRAME.utils.device.isMobile(); -const playerHeight = 1.6; window.APP.quality = qs.get("quality") || isMobile ? "low" : "high"; @@ -123,7 +121,7 @@ import "./components/tools/networked-drawing"; import "./components/tools/drawing-manager"; import registerNetworkSchemas from "./network-schemas"; -import { inGameActions, config as inputConfig } from "./input-mappings"; +import { config as inputConfig } from "./input-mappings"; import registerTelemetry from "./telemetry"; import { getAvailableVREntryTypes } from "./utils/vr-caps-detect.js"; @@ -179,10 +177,6 @@ function mountUI(scene, props = {}) { ); } -function requestFullscreen() { - if (screenfull.enabled && !screenfull.isFullscreen) screenfull.request(); -} - const onReady = async () => { const scene = document.querySelector("a-scene"); const hubChannel = new HubChannel(store); @@ -201,17 +195,6 @@ const onReady = async () => { mountUI(scene, uiProps); }; - const applyProfileFromStore = playerRig => { - const displayName = store.state.profile.displayName; - playerRig.setAttribute("player-info", { - displayName, - avatarSrc: "#" + (store.state.profile.avatarId || "botdefault") - }); - const hudController = playerRig.querySelector("[hud-controller]"); - hudController.setAttribute("hud-controller", { showTip: !store.state.activity.hasFoundFreeze }); - document.querySelector("a-scene").emit("username-changed", { username: displayName }); - }; - const pollForSupportAvailability = callback => { let isSupportAvailable = null; const availabilityUrl = getReticulumFetchUrl("/api/v1/support/availability"); @@ -229,227 +212,9 @@ const onReady = async () => { setInterval(updateIfChanged, 30000); }; - const exitScene = () => { - if (NAF.connection.adapter && NAF.connection.adapter.localMediaStream) { - NAF.connection.adapter.localMediaStream.getTracks().forEach(t => t.stop()); - } - if (hubChannel) { - hubChannel.disconnect(); - } - const scene = document.querySelector("a-scene"); - if (scene) { - if (scene.renderer) { - scene.renderer.setAnimationLoop(null); // Stop animation loop, TODO A-Frame should do this - } - document.body.removeChild(scene); - } - document.body.removeEventListener("touchend", requestFullscreen); - }; - - const enterScene = async (mediaStream, enterInVR) => { - const playerCamera = document.querySelector("#player-camera"); - playerCamera.removeAttribute("scene-preview-camera"); - playerCamera.object3D.position.set(0, playerHeight, 0); - - const scene = document.querySelector("a-scene"); - - // Get aframe inspector url using the webpack file-loader. - const aframeInspectorUrl = require("file-loader?name=assets/js/[name]-[hash].[ext]!aframe-inspector/dist/aframe-inspector.min.js"); - // Set the aframe-inspector url to our hosted copy. - scene.setAttribute("inspector", { url: aframeInspectorUrl }); - - if (!isBotMode) { - scene.classList.add("no-cursor"); - } - - const playerRig = document.querySelector("#player-rig"); - - if (enterInVR) { - scene.enterVR(); - } else if (AFRAME.utils.device.isMobile()) { - document.body.addEventListener("touchend", requestFullscreen); - } - - if (!isBotMode) { - hubChannel.sendEntryEvent().then(() => { - store.update({ activity: { lastEnteredAt: new Date().toISOString() } }); - }); - } - - AFRAME.registerInputActions(inGameActions, "default"); - - scene.setAttribute("stats-plus", false); - - if (isMobile || qsTruthy("mobile")) { - playerRig.setAttribute("virtual-gamepad-controls", {}); - } - - const applyProfileOnPlayerRig = applyProfileFromStore.bind(null, playerRig); - applyProfileOnPlayerRig(); - store.addEventListener("statechanged", applyProfileOnPlayerRig); - - const avatarScale = parseInt(qs.get("avatar_scale"), 10); - - if (avatarScale) { - playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); - } - - const videoTracks = mediaStream ? mediaStream.getVideoTracks() : []; - let sharingScreen = videoTracks.length > 0; - - const screenEntityId = `${NAF.clientId}-screen`; - let screenEntity = document.getElementById(screenEntityId); - - scene.addEventListener("action_share_screen", () => { - sharingScreen = !sharingScreen; - if (sharingScreen) { - for (const track of videoTracks) { - mediaStream.addTrack(track); - } - } else { - for (const track of mediaStream.getVideoTracks()) { - mediaStream.removeTrack(track); - } - } - NAF.connection.adapter.setLocalMediaStream(mediaStream); - screenEntity.setAttribute("visible", sharingScreen); - }); - - document.body.addEventListener("blocked", ev => { - NAF.connection.entities.removeEntitiesOfClient(ev.detail.clientId); - }); - - document.body.addEventListener("unblocked", ev => { - NAF.connection.entities.completeSync(ev.detail.clientId); - }); - - const offset = { x: 0, y: 0, z: -1.5 }; - const spawnMediaInfrontOfPlayer = (src, contentOrigin) => { - const { entity, orientation } = addMedia(src, "#interactable-media", contentOrigin, true); - - orientation.then(or => { - entity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset, - orientation: or - }); - }); - }; - - scene.addEventListener("add_media", e => { - const contentOrigin = e.detail instanceof File ? ObjectContentOrigins.FILE : ObjectContentOrigins.URL; - - spawnMediaInfrontOfPlayer(e.detail, contentOrigin); - }); - - scene.addEventListener("action_spawn_camera", () => { - const entity = document.createElement("a-entity"); - entity.setAttribute("networked", { template: "#interactable-camera" }); - entity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset: { x: 0, y: 0, z: -1.5 } - }); - scene.appendChild(entity); - }); - - scene.addEventListener("object_spawned", e => { - if (hubChannel) { - hubChannel.sendObjectSpawnedEvent(e.detail.objectType); - } - }); - - document.addEventListener("paste", e => { - if (e.target.nodeName === "INPUT") return; - - const url = e.clipboardData.getData("text"); - const files = e.clipboardData.files && e.clipboardData.files; - if (url) { - spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); - } else { - for (const file of files) { - spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.CLIPBOARD); - } - } - }); - - document.addEventListener("dragover", e => { - e.preventDefault(); - }); - - document.addEventListener("drop", e => { - e.preventDefault(); - const url = e.dataTransfer.getData("url"); - const files = e.dataTransfer.files; - if (url) { - spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); - } else { - for (const file of files) { - spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.FILE); - } - } - }); - - if (!qsTruthy("offline")) { - if (isDebug) { - NAF.connection.adapter.session.options.verbose = true; - } - - if (isBotMode) { - playerRig.setAttribute("avatar-replay", { - camera: "#player-camera", - leftController: "#player-left-controller", - rightController: "#player-right-controller" - }); - - const audioEl = document.createElement("audio"); - const audioInput = document.querySelector("#bot-audio-input"); - audioInput.onchange = () => { - audioEl.loop = true; - audioEl.muted = true; - audioEl.crossorigin = "anonymous"; - audioEl.src = URL.createObjectURL(audioInput.files[0]); - document.body.appendChild(audioEl); - }; - const dataInput = document.querySelector("#bot-data-input"); - dataInput.onchange = () => { - const url = URL.createObjectURL(dataInput.files[0]); - playerRig.setAttribute("avatar-replay", { recordingUrl: url }); - }; - await new Promise(resolve => audioEl.addEventListener("canplay", resolve)); - mediaStream.addTrack(audioEl.captureStream().getAudioTracks()[0]); - audioEl.play(); - } - - if (mediaStream) { - NAF.connection.adapter.setLocalMediaStream(mediaStream); - - if (screenEntity) { - screenEntity.setAttribute("visible", sharingScreen); - } else if (sharingScreen) { - const sceneEl = document.querySelector("a-scene"); - screenEntity = document.createElement("a-entity"); - screenEntity.id = screenEntityId; - screenEntity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset: "0 0 -2", - on: "action_share_screen" - }); - screenEntity.setAttribute("networked", { template: "#video-template" }); - sceneEl.appendChild(screenEntity); - } - } - - // Spawn avatar & remove preview camera - const rig = document.querySelector("#player-rig"); - rig.setAttribute("networked", "template: #remote-avatar-template; attachTemplateToLocal: false;"); - rig.setAttribute("networked-avatar", ""); - rig.emit("entered"); - - pollForSupportAvailability(isSupportAvailable => { - remountUI({ isSupportAvailable }); - }); - } - }; + pollForSupportAvailability(isSupportAvailable => { + remountUI({ isSupportAvailable }); + }); const getPlatformUnsupportedReason = () => { if (typeof RTCDataChannelEvent === "undefined") { @@ -459,13 +224,14 @@ const onReady = async () => { return null; }; - remountUI({ enterScene, exitScene }); + const entryManager = new RoomEntryManager(hubChannel); + remountUI({ enterScene: entryManager.enterScene, exitScene: entryManager.exitScene }); const platformUnsupportedReason = getPlatformUnsupportedReason(); if (platformUnsupportedReason) { remountUI({ platformUnsupportedReason: platformUnsupportedReason }); - exitScene(); + entryManager.exitScene(); return; } @@ -474,7 +240,7 @@ const onReady = async () => { if (qs.get("required_version") !== buildNumber) { remountUI({ roomUnavailableReason: "version_mismatch" }); setTimeout(() => document.location.reload(), 5000); - exitScene(); + entryManager.exitScene(); return; } } @@ -516,7 +282,7 @@ const onReady = async () => { environmentRoot.appendChild(initialEnvironmentEl); const enterSceneWhenReady = hubId => { - const enterSceneImmediately = () => enterScene(new MediaStream(), false, hubId); + const enterSceneImmediately = () => entryManager.enterScene(new MediaStream(), false, hubId); if (scene.hasLoaded) { enterSceneImmediately(); } else { @@ -626,7 +392,7 @@ const onReady = async () => { const isFull = connectError.error && connectError.error.msg.match(/\bfull\b/i); console.error(connectError); remountUI({ roomUnavailableReason: isFull ? "full" : "connect_error" }); - exitScene(); + entryManager.exitScene(); return; }); @@ -640,7 +406,7 @@ const onReady = async () => { .receive("ok", handleJoinedHubChannel) .receive("error", res => { if (res.reason === "closed") { - exitScene(); + entryManager.exitScene(); remountUI({ roomUnavailableReason: "closed" }); } diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index ddc6608fe..a6e0e0299 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -265,9 +265,12 @@ class UIRoot extends Component { const hasGrantedMic = await this.hasGrantedMicPermissions(); if (hasGrantedMic) { + console.log("a"); await this.setMediaStreamToDefault(); + console.log("b"); this.beginOrSkipAudioSetup(); } else { + console.log("c"); this.setState({ entryStep: ENTRY_STEPS.mic_grant }); } }; diff --git a/src/room-entry-manager.js b/src/room-entry-manager.js new file mode 100644 index 000000000..4b127e001 --- /dev/null +++ b/src/room-entry-manager.js @@ -0,0 +1,259 @@ +const playerHeight = 1.6; +import qsTruthy from "./utils/qs_truthy"; +import screenfull from "screenfull"; +import { inGameActions } from "./input-mappings"; + +const isBotMode = qsTruthy("bot"); +const isMobile = AFRAME.utils.device.isMobile(); +const isDebug = qsTruthy("debug"); +const qs = new URLSearchParams(location.search); +const aframeInspectorUrl = require("file-loader?name=assets/js/[name]-[hash].[ext]!aframe-inspector/dist/aframe-inspector.min.js"); + +import { addMedia } from "./utils/media-utils"; +import { ObjectContentOrigins } from "./object-types"; + +function requestFullscreen() { + if (screenfull.enabled && !screenfull.isFullscreen) screenfull.request(); +} + +export default class RoomEntryManager { + constructor(hubChannel) { + this.hubChannel = hubChannel; + this.store = window.APP.store; + this.scene = document.querySelector("a-scene"); + this.playerRig = document.querySelector("#player-rig"); + } + + enterScene = async (mediaStream, enterInVR) => { + const playerCamera = document.querySelector("#player-camera"); + playerCamera.removeAttribute("scene-preview-camera"); + playerCamera.object3D.position.set(0, playerHeight, 0); + + // Get aframe inspector url using the webpack file-loader. + // Set the aframe-inspector url to our hosted copy. + this.scene.setAttribute("inspector", { url: aframeInspectorUrl }); + + if (isDebug) { + NAF.connection.adapter.session.options.verbose = true; + } + + if (enterInVR) { + this.scene.enterVR(); + } else if (AFRAME.utils.device.isMobile()) { + document.body.addEventListener("touchend", requestFullscreen); + } + + AFRAME.registerInputActions(inGameActions, "default"); + + if (isMobile || qsTruthy("mobile")) { + this.playerRig.setAttribute("virtual-gamepad-controls", {}); + } + + this.setupPlayerRig(); + this.setupScreensharing(mediaStream); + this.setupBlocking(); + this.setupMedia(); + this.setupCamera(); + + if (qsTruthy("offline")) return; + + if (isBotMode) { + this.runBot(mediaStream); + return; + } + + this.scene.classList.add("no-cursor"); + + this.hubChannel.sendEntryEvent().then(() => { + this.store.update({ activity: { lastEnteredAt: new Date().toISOString() } }); + }); + + if (mediaStream) { + NAF.connection.adapter.setLocalMediaStream(mediaStream); + } + + this.spawnAvatar(); + }; + + exitScene = () => { + if (NAF.connection.adapter && NAF.connection.adapter.localMediaStream) { + NAF.connection.adapter.localMediaStream.getTracks().forEach(t => t.stop()); + } + if (this.hubChannel) { + this.hubChannel.disconnect(); + } + if (this.scene.renderer) { + this.scene.renderer.setAnimationLoop(null); // Stop animation loop, TODO A-Frame should do this + } + document.body.removeChild(this.scene); + document.body.removeEventListener("touchend", requestFullscreen); + }; + + setupPlayerRig = () => { + this.updatePlayerRigWithProfile(); + this.store.addEventListener("statechanged", this.updatePlayerRigWithProfile); + + const avatarScale = parseInt(qs.get("avatar_scale"), 10); + + if (avatarScale) { + this.playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); + } + }; + + updatePlayerRigWithProfile = () => { + const displayName = this.store.state.profile.displayName; + this.playerRig.setAttribute("player-info", { + displayName, + avatarSrc: "#" + (this.store.state.profile.avatarId || "botdefault") + }); + const hudController = this.playerRig.querySelector("[hud-controller]"); + hudController.setAttribute("hud-controller", { showTip: !this.store.state.activity.hasFoundFreeze }); + document.querySelector("a-scene").emit("username-changed", { username: displayName }); + }; + + setupScreensharing = mediaStream => { + const videoTracks = mediaStream ? mediaStream.getVideoTracks() : []; + let sharingScreen = videoTracks.length > 0; + + const screenEntityId = `${NAF.clientId}-screen`; + let screenEntity = document.getElementById(screenEntityId); + + if (screenEntity) { + screenEntity.setAttribute("visible", sharingScreen); + } else if (sharingScreen) { + const sceneEl = document.querySelector("a-scene"); + screenEntity = document.createElement("a-entity"); + screenEntity.id = screenEntityId; + screenEntity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset: "0 0 -2", + on: "action_share_screen" + }); + screenEntity.setAttribute("networked", { template: "#video-template" }); + sceneEl.appendChild(screenEntity); + } + + this.scene.addEventListener("action_share_screen", () => { + sharingScreen = !sharingScreen; + if (sharingScreen) { + for (const track of videoTracks) { + mediaStream.addTrack(track); + } + } else { + for (const track of mediaStream.getVideoTracks()) { + mediaStream.removeTrack(track); + } + } + NAF.connection.adapter.setLocalMediaStream(mediaStream); + screenEntity.setAttribute("visible", sharingScreen); + }); + }; + + setupBlocking = () => { + document.body.addEventListener("blocked", ev => { + NAF.connection.entities.removeEntitiesOfClient(ev.detail.clientId); + }); + + document.body.addEventListener("unblocked", ev => { + NAF.connection.entities.completeSync(ev.detail.clientId); + }); + }; + + setupMedia = () => { + const offset = { x: 0, y: 0, z: -1.5 }; + const spawnMediaInfrontOfPlayer = (src, contentOrigin) => { + const { entity, orientation } = addMedia(src, "#interactable-media", contentOrigin, true); + + orientation.then(or => { + entity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset, + orientation: or + }); + }); + }; + + this.scene.addEventListener("add_media", e => { + const contentOrigin = e.detail instanceof File ? ObjectContentOrigins.FILE : ObjectContentOrigins.URL; + + spawnMediaInfrontOfPlayer(e.detail, contentOrigin); + }); + + this.scene.addEventListener("object_spawned", e => { + this.hubChannel.sendObjectSpawnedEvent(e.detail.objectType); + }); + + document.addEventListener("paste", e => { + if (e.target.nodeName === "INPUT") return; + + const url = e.clipboardData.getData("text"); + const files = e.clipboardData.files && e.clipboardData.files; + if (url) { + spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); + } else { + for (const file of files) { + spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.CLIPBOARD); + } + } + }); + + document.addEventListener("dragover", e => e.preventDefault()); + + document.addEventListener("drop", e => { + e.preventDefault(); + const url = e.dataTransfer.getData("url"); + const files = e.dataTransfer.files; + if (url) { + spawnMediaInfrontOfPlayer(url, ObjectContentOrigins.URL); + } else { + for (const file of files) { + spawnMediaInfrontOfPlayer(file, ObjectContentOrigins.FILE); + } + } + }); + }; + + setupCamera = () => { + this.scene.addEventListener("action_spawn_camera", () => { + const entity = document.createElement("a-entity"); + entity.setAttribute("networked", { template: "#interactable-camera" }); + entity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset: { x: 0, y: 0, z: -1.5 } + }); + this.scene.appendChild(entity); + }); + }; + + spawnAvatar = () => { + this.playerRig.setAttribute("networked", "template: #remote-avatar-template; attachTemplateToLocal: false;"); + this.playerRig.setAttribute("networked-avatar", ""); + this.playerRig.emit("entered"); + }; + + runBot = async mediaStream => { + this.playerRig.setAttribute("avatar-replay", { + camera: "#player-camera", + leftController: "#player-left-controller", + rightController: "#player-right-controller" + }); + + const audioEl = document.createElement("audio"); + const audioInput = document.querySelector("#bot-audio-input"); + audioInput.onchange = () => { + audioEl.loop = true; + audioEl.muted = true; + audioEl.crossorigin = "anonymous"; + audioEl.src = URL.createObjectURL(audioInput.files[0]); + document.body.appendChild(audioEl); + }; + const dataInput = document.querySelector("#bot-data-input"); + dataInput.onchange = () => { + const url = URL.createObjectURL(dataInput.files[0]); + this.playerRig.setAttribute("avatar-replay", { recordingUrl: url }); + }; + await new Promise(resolve => audioEl.addEventListener("canplay", resolve)); + mediaStream.addTrack(audioEl.captureStream().getAudioTracks()[0]); + audioEl.play(); + }; +} -- GitLab