diff --git a/package-lock.json b/package-lock.json index b6cc7770d591bbbc02bab06845da018763d8669d..4688884d728cb3ecf1254ae0d3cf2de259860cbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -517,7 +517,9 @@ "requires": { "@tweenjs/tween.js": "^16.8.0", "browserify-css": "^0.8.2", + "debug": "github:ngokevin/debug#ef5f8e66d49ce8bc64c6f282c15f8b7164409e3a", "deep-assign": "^2.0.0", + "document-register-element": "github:dmarcos/document-register-element#8ccc532b7f3744be954574caf3072a5fd260ca90", "envify": "^3.4.1", "load-bmfont": "^1.2.3", "object-assign": "^4.0.1", @@ -531,11 +533,7 @@ "dependencies": { "debug": { "version": "github:ngokevin/debug#ef5f8e66d49ce8bc64c6f282c15f8b7164409e3a", - "from": "github:ngokevin/debug#ef5f8e66d49ce8bc64c6f282c15f8b7164409e3a" - }, - "document-register-element": { - "version": "github:dmarcos/document-register-element#8ccc532b7f3744be954574caf3072a5fd260ca90", - "from": "github:dmarcos/document-register-element#8ccc532b7f3744be954574caf3072a5fd260ca90" + "from": "github:ngokevin/debug#noTimestamp" } } }, @@ -3793,6 +3791,10 @@ "esutils": "^2.0.2" } }, + "document-register-element": { + "version": "github:dmarcos/document-register-element#8ccc532b7f3744be954574caf3072a5fd260ca90", + "from": "github:dmarcos/document-register-element#8ccc532b7" + }, "dom-converter": { "version": "0.1.4", "resolved": "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz", @@ -4183,6 +4185,11 @@ "es6-symbol": "^3.1.1" } }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=" + }, "es6-symbol": { "version": "3.1.1", "resolved": "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz", @@ -5254,14 +5261,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5276,20 +5281,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5406,8 +5408,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5419,7 +5420,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5434,7 +5434,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5442,14 +5441,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5468,7 +5465,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5549,8 +5545,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5562,7 +5557,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5684,7 +5678,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6696,6 +6689,11 @@ "integrity": "sha1-Cpf7h2mG6AgcYxFg+PnziRV/AEM=", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "import-lazy": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", @@ -7633,6 +7631,48 @@ "array-includes": "^3.0.3" } }, + "jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "requires": { + "core-js": "~2.3.0", + "es6-promise": "~3.0.2", + "lie": "~3.1.0", + "pako": "~1.0.2", + "readable-stream": "~2.0.6" + }, + "dependencies": { + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + } + } + }, "keyv": { "version": "3.0.0", "resolved": "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz", @@ -7691,6 +7731,14 @@ "type-check": "~0.3.2" } }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "requires": { + "immediate": "~3.0.5" + } + }, "listr": { "version": "0.14.1", "resolved": "https://registry.yarnpkg.com/listr/-/listr-0.14.1.tgz", @@ -9471,8 +9519,7 @@ "pako": { "version": "1.0.6", "resolved": "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz", - "integrity": "sha1-AQEhG6pwxLykoPY/Igbpe3368lg=", - "dev": true + "integrity": "sha1-AQEhG6pwxLykoPY/Igbpe3368lg=" }, "parallel-transform": { "version": "1.1.0", @@ -13108,8 +13155,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", diff --git a/package.json b/package.json index 95e111f280ee44a55872d5b1e964626e63dccc8e..09ec5d4fae8461b61b25f679bd66933a338a92a1 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "event-target-shim": "^3.0.1", "form-urlencoded": "^2.0.4", "jsonschema": "^1.2.2", + "jszip": "^3.1.5", "moving-average": "^1.0.0", "naf-janus-adapter": "^0.10.1", "networked-aframe": "github:mozillareality/networked-aframe#mr-social-client/master", diff --git a/src/components/gltf-model-plus.js b/src/components/gltf-model-plus.js index 5e54e729e38c6d2360059e04f8fa4cb827d7b86f..b5904f0fc36c9695908f56017772449ee99dd7e3 100644 --- a/src/components/gltf-model-plus.js +++ b/src/components/gltf-model-plus.js @@ -1,3 +1,5 @@ +import SketchfabZipWorker from "../workers/sketchfab-zip.worker.js"; + const GLTFCache = {}; AFRAME.GLTFModelPlus = { @@ -181,14 +183,41 @@ function nextTick() { }); } -function cachedLoadGLTF(src, basePath, preferredTechnique, onProgress) { - // Load the gltf model from the cache if it exists. +function getFilesFromSketchfabZip(src) { + return new Promise((resolve, reject) => { + const worker = new SketchfabZipWorker(); + worker.onmessage = e => { + const [success, fileMapOrError] = e.data; + (success ? resolve : reject)(fileMapOrError); + }; + worker.postMessage(src); + }); +} + +function cachedLoadGLTF(src, basePath, contentType, preferredTechnique, onProgress) { if (!GLTFCache[src]) { - GLTFCache[src] = new Promise((resolve, reject) => { - const gltfLoader = new THREE.GLTFLoader(); - gltfLoader.path = basePath; - gltfLoader.preferredTechnique = preferredTechnique; - gltfLoader.load(src, resolve, onProgress, reject); + GLTFCache[src] = new Promise(async (resolve, reject) => { + try { + let gltfUrl = src; + let onLoad = resolve; + if (contentType === "model/gltf+zip") { + const fileMap = await getFilesFromSketchfabZip(src); + gltfUrl = fileMap["scene.gtlf"]; + onLoad = model => { + // The GLTF is now cached as a THREE object, we can get rid of the original blobs + Object.keys(fileMap).forEach(URL.revokeObjectURL); + resolve(model); + }; + } + + const gltfLoader = new THREE.GLTFLoader(); + gltfLoader.path = basePath; + gltfLoader.preferredTechnique = preferredTechnique; + gltfLoader.load(gltfUrl, onLoad, onProgress, reject); + } catch (e) { + reject(e); + delete GLTFCache[src]; + } }); } return GLTFCache[src].then(cloneGltf); @@ -203,6 +232,7 @@ function cachedLoadGLTF(src, basePath, preferredTechnique, onProgress) { AFRAME.registerComponent("gltf-model-plus", { schema: { src: { type: "string" }, + contentType: { type: "string" }, basePath: { type: "string", default: undefined }, inflate: { default: false } }, @@ -244,7 +274,7 @@ AFRAME.registerComponent("gltf-model-plus", { } const gltfPath = THREE.LoaderUtils.extractUrlBase(src); - const model = await cachedLoadGLTF(src, this.data.basePath, this.preferredTechnique); + const model = await cachedLoadGLTF(src, this.data.basePath, this.data.contentType, this.preferredTechnique); // If we started loading something else already // TODO: there should be a way to cancel loading instead diff --git a/src/components/media-loader.js b/src/components/media-loader.js index ef8ea23cc33f5fb077bddf8ce6d0f269b6d04a36..544d4443a4304e0ed1d3ad75523c89cafa46c979 100644 --- a/src/components/media-loader.js +++ b/src/components/media-loader.js @@ -1,5 +1,5 @@ import { getBox, getScaleCoefficient } from "../utils/auto-box-collider"; -import { resolveFarsparkUrl } from "../utils/media-utils"; +import { resolveMedia } from "../utils/media-utils"; const fetchContentType = async url => fetch(url, { method: "HEAD" }).then(r => r.headers.get("content-type")); @@ -53,7 +53,7 @@ AFRAME.registerComponent("media-loader", { this.setShapeAndScale(true); }, 100); - const { raw, origin, meta } = await resolveFarsparkUrl(url); + const { raw, origin, meta } = await resolveMedia(url); console.log("resolved", url, raw, origin, meta); const contentType = (meta && meta.expected_content_type) || (await fetchContentType(raw)); @@ -79,6 +79,7 @@ AFRAME.registerComponent("media-loader", { this.el.addEventListener("model-error", this.onError, { once: true }); this.el.setAttribute("gltf-model-plus", { src: raw, + contentType, basePath: THREE.LoaderUtils.extractUrlBase(origin), inflate: true }); diff --git a/src/hub.html b/src/hub.html index cdc64e06934826057a8996897e36890e2eea71fd..45a849b2fc267bb1b35cdf84fe8be64be50852a7 100644 --- a/src/hub.html +++ b/src/hub.html @@ -26,7 +26,7 @@ <a-scene renderer="antialias: true" networked-scene="adapter: janus; audio: true; debug: true; connectOnLoad: false;" - physics="gravity: -6;" + physics="gravity: -6; debug: false;" mute-mic="eventSrc: a-scene; toggleEvents: action_mute" freeze-controller="toggleEvent: action_freeze" personal-space-bubble="debug: false;" diff --git a/src/utils/media-utils.js b/src/utils/media-utils.js index e4f812b59dd04d2402905e97e8a6e0f29f4e85f9..e864bfc9fd979ef42b4fd4083cd67325e3465b3a 100644 --- a/src/utils/media-utils.js +++ b/src/utils/media-utils.js @@ -1,20 +1,25 @@ const whitelistedHosts = [/^.*\.reticulum\.io$/, /^.*hubs\.mozilla\.com$/, /^hubs\.local$/]; const isHostWhitelisted = hostname => !!whitelistedHosts.filter(r => r.test(hostname)).length; -let resolveMediaUrl = "/api/v1/media"; +let mediaAPIEndpoint = "/api/v1/media"; if (process.env.NODE_ENV === "development") { - resolveMediaUrl = `https://${process.env.DEV_RETICULUM_SERVER}${resolveMediaUrl}`; + mediaAPIEndpoint = `https://${process.env.DEV_RETICULUM_SERVER}${mediaAPIEndpoint}`; } -export const resolveFarsparkUrl = async url => { +const resolveMediaCache = new Map(); +export const resolveMedia = async url => { const parsedUrl = new URL(url); - if ((parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") || isHostWhitelisted(parsedUrl.hostname)) - return { raw: url, origin: url }; + if (resolveMediaCache.has(url)) return resolveMediaCache.get(url); - return await fetch(resolveMediaUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ media: { url } }) - }).then(r => r.json()); + const resolved = + (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") || isHostWhitelisted(parsedUrl.hostname) + ? { raw: url, origin: url } + : await fetch(mediaAPIEndpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ media: { url } }) + }).then(r => r.json()); + resolveMediaCache.set(url, resolved); + return resolved; }; let interactableId = 0; diff --git a/src/vendor/GLTFLoader.js b/src/vendor/GLTFLoader.js index 5e23d9250e1f917f1206398fa361115e4be77d28..d92e1773df03db6a76566333fd77867c1a49342f 100644 --- a/src/vendor/GLTFLoader.js +++ b/src/vendor/GLTFLoader.js @@ -10,7 +10,7 @@ * @author netpro2k / https://github.com/netpro2k */ - import { resolveFarsparkUrl } from "../utils/media-utils" + import { resolveMedia } from "../utils/media-utils" THREE.GLTFLoader = ( function () { @@ -40,7 +40,7 @@ THREE.GLTFLoader = ( function () { loader.setResponseType( 'arraybuffer' ); - var farsparkURL = (await resolveFarsparkUrl(url)).raw; + var farsparkURL = (await resolveMedia(url)).raw; loader.load( farsparkURL, function ( data ) { @@ -1623,7 +1623,7 @@ THREE.GLTFLoader = ( function () { var options = this.options; - var farsparkURL = (await resolveFarsparkUrl(resolveURL(bufferDef.uri, options.path))).raw; + var farsparkURL = (await resolveMedia(resolveURL(bufferDef.uri, options.path))).raw; return new Promise( function ( resolve, reject ) { @@ -1823,7 +1823,7 @@ THREE.GLTFLoader = ( function () { var urlToLoad = resolveURL(sourceURI, options.path); if (!hasBufferView){ - urlToLoad = (await resolveFarsparkUrl(urlToLoad)).raw; + urlToLoad = (await resolveMedia(urlToLoad)).raw; } return Promise.resolve( sourceURI ).then( function ( sourceURI ) { diff --git a/src/workers/gifparsing.worker.js b/src/workers/gifparsing.worker.js index 643a95ab98e52c048ef551a6249e3ef937191389..1d1400cde35215bc65eba23863c8baa978bcb799 100644 --- a/src/workers/gifparsing.worker.js +++ b/src/workers/gifparsing.worker.js @@ -63,10 +63,12 @@ self.onmessage = e => { new Uint8Array(e.data), (delays, loopcnt, frames, disposals) => { self.postMessage([true, frames, delays, disposals]); + delete self.onmessage; }, err => { console.error("Error in gif parsing worker", err); self.postMessage([false, err]); + delete self.onmessage; } ); }; diff --git a/src/workers/sketchfab-zip.worker.js b/src/workers/sketchfab-zip.worker.js new file mode 100644 index 0000000000000000000000000000000000000000..9108cbb5597669d99f6c62bcfed4798946458fa1 --- /dev/null +++ b/src/workers/sketchfab-zip.worker.js @@ -0,0 +1,33 @@ +import JSZip from "jszip"; + +async function fetchZipAndGetBlobs(src) { + const zip = await fetch(src) + .then(r => r.blob()) + .then(JSZip.loadAsync); + + // Rewrite any url refferences in the GLTF to blob urls + const fileMap = {}; + const files = Object.values(zip.files); + const fileBlobs = await Promise.all(files.map(f => f.async("blob"))); + for (let i = 0; i < fileBlobs.length; i++) { + fileMap[files[i].name] = URL.createObjectURL(fileBlobs[i]); + } + + const gltfJson = JSON.parse(await zip.file("scene.gltf").async("text")); + gltfJson.buffers && gltfJson.buffers.forEach(b => (b.uri = fileMap[b.uri])); + gltfJson.images && gltfJson.images.forEach(i => (i.uri = fileMap[i.uri])); + + fileMap["scene.gtlf"] = URL.createObjectURL(new Blob([JSON.stringify(gltfJson, null, 2)], { type: "text/plain" })); + + return fileMap; +} + +self.onmessage = async e => { + try { + const fileMap = await fetchZipAndGetBlobs(e.data); + self.postMessage([true, fileMap]); + } catch (e) { + self.postMessage([false, e.message]); + } + delete self.onmessage; +};