Newer
Older
import qsTruthy from "./utils/qs_truthy";
import screenfull from "screenfull";
import pinnedEntityToGltf from "./utils/pinned-entity-to-gltf";
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();
}
constructor(hubChannel) {
this.hubChannel = hubChannel;
this.store = window.APP.store;
this.scene = document.querySelector("a-scene");
this.cursorController = document.querySelector("#cursor-controller");
this.playerRig = document.querySelector("#player-rig");
init = () => {
this.whenSceneLoaded(() => {
this.cursorController.components["cursor-controller"].enabled = false;
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) {
// HACK - A-Frame calls getVRDisplays at module load, we want to do it here to
// force gamepads to become live.
navigator.getVRDisplays();
this.scene.enterVR();
} else if (AFRAME.utils.device.isMobile()) {
document.body.addEventListener("touchend", requestFullscreen);
}
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;
this.scene.setAttribute("motion-capture-replayer", "enabled", false);
if (mediaStream) {
NAF.connection.adapter.setLocalMediaStream(mediaStream);
}
this.scene.classList.remove("hand-cursor");
this.scene.classList.add("no-cursor");
this.cursorController.components["cursor-controller"].enabled = true;
// Delay sending entry event telemetry until VR display is presenting.
(async () => {
while (enterInVR && !(await navigator.getVRDisplays()).find(d => d.isPresenting)) {
await nextTick();
}
this.hubChannel.sendEntryEvent().then(() => {
this.store.update({ activity: { lastEnteredAt: new Date().toISOString() } });
});
this.scene.addState("entered");
whenSceneLoaded = callback => {
this.scene.addEventListener("loaded", callback);
enterSceneWhenLoaded = (mediaStream, enterInVR) => {
this.whenSceneLoaded(() => this.enterScene(mediaStream, enterInVR));
};
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 });
}
};
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 });
this.scene.emit("username-changed", { username: displayName });
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) {
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" });
}
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);
});
};
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, 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);
});
const networkId = el.components.networked.data.networkId;
const gltfNode = pinnedEntityToGltf(el);
el.setAttribute("networked", { persistent: true });
const networked = components.networked;
el.setAttribute("networked", { persistent: false });
this.scene.addEventListener("object_spawned", e => {
this.hubChannel.sendObjectSpawnedEvent(e.detail.objectType);
});
document.addEventListener("paste", e => {
if (e.target.matches("input, textarea") && document.activeElement === e.target) return;
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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);
}
}
});
};
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);
});
this.scene.addEventListener("photo_taken", e => {
this.hubChannel.sendMessage({ src: e.detail }, "spawn");
});
this.playerRig.setAttribute("networked", "template: #remote-avatar-template; attachTemplateToLocal: false;");
this.playerRig.setAttribute("networked-avatar", "");
this.playerRig.emit("entered");
};
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();
};
}