import { getBox, getScaleCoefficient } from "../utils/auto-box-collider"; import { resolveMedia } from "../utils/media-utils"; const fetchContentType = async url => fetch(url, { method: "HEAD" }).then(r => r.headers.get("content-type")); AFRAME.registerComponent("media-loader", { schema: { src: { type: "string" }, token: { type: "string" }, contentType: { type: "string" }, resize: { default: false } }, init() { this.onError = this.onError.bind(this); }, setShapeAndScale(resize) { const mesh = this.el.getObject3D("mesh"); const box = getBox(this.el, mesh); const scaleCoefficient = resize ? getScaleCoefficient(0.5, box) : 1; this.el.object3DMap.mesh.scale.multiplyScalar(scaleCoefficient); if (this.el.body && this.el.body.shapes.length > 1) { this.el.removeAttribute("shape"); } else { const center = new THREE.Vector3(); const { min, max } = box; const halfExtents = { x: (Math.abs(min.x - max.x) / 2) * scaleCoefficient, y: (Math.abs(min.y - max.y) / 2) * scaleCoefficient, z: (Math.abs(min.z - max.z) / 2) * scaleCoefficient }; center.addVectors(min, max).multiplyScalar(0.5 * scaleCoefficient); mesh.position.sub(center); this.el.setAttribute("shape", { shape: "box", halfExtents: halfExtents }); } }, onError() { this.el.setAttribute("image-plus", { src: "error" }); clearTimeout(this.showLoaderTimeout); }, // TODO: correctly handle case where src changes async update() { try { const url = this.data.src; const token = this.data.token; this.showLoaderTimeout = this.showLoaderTimeout || setTimeout(() => { const loadingObj = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial()); this.el.setObject3D("mesh", loadingObj); this.setShapeAndScale(true); }, 100); if (!url) return; const { raw, origin, meta } = await resolveMedia(url); console.log("resolved", url, raw, origin, meta); const contentType = this.data.contentType || (meta && meta.expected_content_type) || (await fetchContentType(raw)); if (contentType.startsWith("image/") || contentType.startsWith("video/") || contentType.startsWith("audio/")) { this.el.addEventListener( "image-loaded", () => { clearTimeout(this.showLoaderTimeout); }, { once: true } ); let blobUrl; if (token) { const imageResponse = await fetch(raw, { method: "GET", headers: { Authorization: `Token ${token}` } }); const blob = await imageResponse.blob(); blobUrl = window.URL.createObjectURL(blob); } this.el.setAttribute("image-plus", { src: blobUrl || raw, contentType }); this.el.setAttribute("position-at-box-shape-border", { target: ".delete-button", dirs: ["forward", "back"] }); } else if ( contentType.indexOf("x-zip-compressed") !== -1 || contentType.startsWith("model/gltf") || url.endsWith(".gltf") || url.endsWith(".glb") ) { this.el.addEventListener( "model-loaded", () => { clearTimeout(this.showLoaderTimeout); this.setShapeAndScale(this.data.resize); }, { once: true } ); this.el.addEventListener("model-error", this.onError, { once: true }); let blobUrl; if (token) { const modelResponse = await fetch(raw, { method: "GET", headers: { Authorization: `Token ${token}` } }); const blob = await modelResponse.blob(); blobUrl = window.URL.createObjectURL(blob); } this.el.setAttribute("gltf-model-plus", { src: blobUrl || raw, contentType, basePath: THREE.LoaderUtils.extractUrlBase(origin), inflate: true }); } else { throw new Error(`Unsupported content type: ${contentType}`); } } catch (e) { console.error("Error adding media", e); this.onError(); } } });