diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index 1b9844c8abf2dc7d9ed7c089f84923faa601fd96..bf513559ad159824e95b16df5984e11899611d25 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -33,7 +33,10 @@ "audio.granted-title": "Mic permissions granted", "audio.granted-subtitle": "You can still mute yourself in-game", "audio.granted-next": "NEXT", - "exit.subtitle": "Your session has ended.", + "exit.subtitle.exited": "Your session has ended.", + "exit.subtitle.closed": "This room is no longer available.", + "exit.subtitle.full": "This room is full, please try again later.", + "exit.subtitle.connect_error": "Unable to connect to this room, please try again later.", "autoexit.title": "Auto-ending session in ", "autoexit.title_units": " seconds", "autoexit.subtitle": "You have started another session.", diff --git a/src/hub.js b/src/hub.js index 75111f9c6b4abdee9ef8ecb771d195e02f17be38..01f21706a2830b6c25d893ccc2c17961ec0e0414 100644 --- a/src/hub.js +++ b/src/hub.js @@ -110,7 +110,6 @@ AFRAME.registerInputMappings(inputConfig, true); const store = new Store(); const concurrentLoadDetector = new ConcurrentLoadDetector(); -const hubChannel = new HubChannel(store); concurrentLoadDetector.start(); @@ -122,110 +121,6 @@ if (!store.state.profile.has_changed_name) { store.update({ profile: { display_name: generateRandomName() } }); } -async function exitScene() { - hubChannel.disconnect(); - const scene = document.querySelector("a-scene"); - scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this - document.body.removeChild(scene); -} - -function applyProfileFromStore(playerRig) { - const displayName = store.state.profile.display_name; - playerRig.setAttribute("player-info", { - displayName, - avatarSrc: "#" + (store.state.profile.avatar_id || "botdefault") - }); - document.querySelector("a-scene").emit("username-changed", { username: displayName }); -} - -async function enterScene(mediaStream, enterInVR, janusRoomId) { - const scene = document.querySelector("a-scene"); - const playerRig = document.querySelector("#player-rig"); - document.querySelector("a-scene canvas").classList.remove("blurred"); - scene.render(); - - if (enterInVR) { - scene.enterVR(); - } - - AFRAME.registerInputActions(inGameActions, "default"); - - document.querySelector("#player-camera").setAttribute("look-controls", ""); - - scene.setAttribute("networked-scene", { - room: janusRoomId, - serverURL: process.env.JANUS_SERVER - }); - - if (!qsTruthy("no_stats")) { - scene.setAttribute("stats", true); - } - - if (isMobile || qsTruthy("mobile")) { - playerRig.setAttribute("virtual-gamepad-controls", {}); - } - - const applyProfileOnPlayerRig = applyProfileFromStore.bind(null, playerRig); - applyProfileOnPlayerRig(); - store.addEventListener("statechanged", applyProfileOnPlayerRig); - - const avatarScale = parseInt(qs.avatar_scale, 10); - - if (avatarScale) { - playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); - } - - const videoTracks = 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); - }); - - if (!qsTruthy("offline")) { - document.body.addEventListener("connected", () => { - hubChannel.sendEntryEvent().then(() => { - store.update({ lastEnteredAt: moment().toJSON() }); - }); - }); - - scene.components["networked-scene"].connect(); - - 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); - } - } - } -} - function mountUI(scene, props = {}) { const disableAutoExitOnConcurrentLoad = qsTruthy("allow_multi"); const forcedVREntryType = qs.vr_entry_type || null; @@ -237,8 +132,6 @@ function mountUI(scene, props = {}) { <UIRoot {...{ scene, - enterScene, - exitScene, concurrentLoadDetector, disableAutoExitOnConcurrentLoad, forcedVREntryType, @@ -255,19 +148,135 @@ function mountUI(scene, props = {}) { const onReady = async () => { const scene = document.querySelector("a-scene"); + const hubChannel = new HubChannel(store); + document.querySelector("a-scene canvas").classList.add("blurred"); window.APP.scene = scene; registerNetworkSchemas(); + let uiProps = {}; + mountUI(scene); - let modifiedProps = {}; const remountUI = props => { - modifiedProps = { ...modifiedProps, ...props }; - mountUI(scene, modifiedProps); + uiProps = { ...uiProps, ...props }; + mountUI(scene, uiProps); + }; + + const applyProfileFromStore = playerRig => { + const displayName = store.state.profile.display_name; + playerRig.setAttribute("player-info", { + displayName, + avatarSrc: "#" + (store.state.profile.avatar_id || "botdefault") + }); + document.querySelector("a-scene").emit("username-changed", { username: displayName }); + }; + + const enterScene = async (mediaStream, enterInVR, janusRoomId) => { + const scene = document.querySelector("a-scene"); + const playerRig = document.querySelector("#player-rig"); + document.querySelector("a-scene canvas").classList.remove("blurred"); + scene.render(); + + if (enterInVR) { + scene.enterVR(); + } + + AFRAME.registerInputActions(inGameActions, "default"); + + document.querySelector("#player-camera").setAttribute("look-controls", ""); + + scene.setAttribute("networked-scene", { + room: janusRoomId, + serverURL: process.env.JANUS_SERVER + }); + + if (!qsTruthy("no_stats")) { + scene.setAttribute("stats", true); + } + + if (isMobile || qsTruthy("mobile")) { + playerRig.setAttribute("virtual-gamepad-controls", {}); + } + + const applyProfileOnPlayerRig = applyProfileFromStore.bind(null, playerRig); + applyProfileOnPlayerRig(); + store.addEventListener("statechanged", applyProfileOnPlayerRig); + + const avatarScale = parseInt(qs.avatar_scale, 10); + + if (avatarScale) { + playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); + } + + const videoTracks = 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); + }); + + if (!qsTruthy("offline")) { + document.body.addEventListener("connected", () => { + hubChannel.sendEntryEvent().then(() => { + store.update({ lastEnteredAt: moment().toJSON() }); + }); + }); + + scene.components["networked-scene"].connect().catch(connectError => { + // hacky until we get return codes + const isFull = connectError.error && connectError.error.msg.match(/\bfull\b/i); + remountUI({ roomUnavailableReason: isFull ? "full" : "connect_error" }); + exitScene(); + + return; + }); + + 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); + } + } + } + }; + + const exitScene = () => { + hubChannel.disconnect(); + const scene = document.querySelector("a-scene"); + scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this + document.body.removeChild(scene); }; + remountUI({ enterScene, exitScene }); + getAvailableVREntryTypes().then(availableVREntryTypes => { remountUI({ availableVREntryTypes }); }); @@ -319,7 +328,14 @@ const onReady = async () => { initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`); hubChannel.setPhoenixChannel(channel); }) - .receive("error", res => console.error(res)); + .receive("error", res => { + if (res.reason === "closed") { + exitScene(); + remountUI({ roomUnavailableReason: "closed" }); + } + + console.error(res); + }); }; document.addEventListener("DOMContentLoaded", onReady); diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 3e22208dacc6219b59cc9276562c71d43f129f03..90d08830d616ecc1a8d0f7b0ebd18eb4241b2e0e 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -64,7 +64,8 @@ class UIRoot extends Component { showProfileEntry: PropTypes.bool, availableVREntryTypes: PropTypes.object, initialEnvironmentLoaded: PropTypes.bool, - janusRoomId: PropTypes.number + janusRoomId: PropTypes.number, + roomUnavailableReason: PropTypes.string }; state = { @@ -499,33 +500,35 @@ class UIRoot extends Component { }; render() { - if (!this.props.initialEnvironmentLoaded || !this.props.availableVREntryTypes || !this.props.janusRoomId) { + if (this.state.exited || this.props.roomUnavailableReason) { + const exitSubtitleId = `exit.subtitle.${this.state.exited ? "exited" : this.props.roomUnavailableReason}`; + return ( <IntlProvider locale={lang} messages={messages}> - <div className="loading-panel"> - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> - </div> - </div> + <div className="exited-panel"> <div className="loading-panel__title"> <b>moz://a</b> duck </div> + <div className="loading-panel__subtitle"> + <FormattedMessage id={exitSubtitleId} /> + </div> </div> </IntlProvider> ); } - if (this.state.exited) { + if (!this.props.initialEnvironmentLoaded || !this.props.availableVREntryTypes || !this.props.janusRoomId) { return ( <IntlProvider locale={lang} messages={messages}> - <div className="exited-panel"> + <div className="loading-panel"> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> <div className="loading-panel__title"> <b>moz://a</b> duck </div> - <div className="loading-panel__subtitle"> - <FormattedMessage id="exit.subtitle" /> - </div> </div> </IntlProvider> );