Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"custom-event-polyfill": "^1.0.6",
"debug": "ngokevin/debug#noTimestamp",
"deep-assign": "^2.0.0",
"document-register-element": "dmarcos/document-register-element#8ccc532b7f3744be954574caf3072a5fd260ca90",
"@ungap/custom-elements": "^1.1.0",
"load-bmfont": "^1.2.3",
"object-assign": "^4.0.1",
"present": "0.0.6",
Expand Down
225 changes: 114 additions & 111 deletions src/core/a-assets.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
var ANode = require('./a-node');
/* global customElements */
var ANode = require('./a-node').ANode;
var bind = require('../utils/bind');
var debug = require('../utils/debug');
var registerElement = require('./a-register-element').registerElement;
var THREE = require('../lib/three');

var fileLoader = new THREE.FileLoader();
Expand All @@ -10,126 +10,129 @@ var warn = debug('core:a-assets:warn');
/**
* Asset management system. Handles blocking on asset loading.
*/
module.exports = registerElement('a-assets', {
prototype: Object.create(ANode.prototype, {
createdCallback: {
value: function () {
this.isAssets = true;
this.fileLoader = fileLoader;
this.timeout = null;
}
},

attachedCallback: {
value: function () {
var self = this;
var i;
var loaded = [];
var mediaEl;
var mediaEls;
var imgEl;
var imgEls;
var timeout;

if (!this.parentNode.isScene) {
throw new Error('<a-assets> must be a child of a <a-scene>.');
}
class AAssets extends ANode {
constructor () {
super();
this.isAssets = true;
this.fileLoader = fileLoader;
this.timeout = null;
}

// Wait for <img>s.
imgEls = this.querySelectorAll('img');
for (i = 0; i < imgEls.length; i++) {
imgEl = fixUpMediaElement(imgEls[i]);
loaded.push(new Promise(function (resolve, reject) {
// Set in cache because we won't be needing to call three.js loader if we have.
// a loaded media element.
THREE.Cache.add(imgEls[i].getAttribute('src'), imgEl);
imgEl.onload = resolve;
imgEl.onerror = reject;
}));
}
connectedCallback () {
// Defer if DOM is not ready.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', this.connectedCallback.bind(this));
return;
}

// Wait for <audio>s and <video>s.
mediaEls = this.querySelectorAll('audio, video');
for (i = 0; i < mediaEls.length; i++) {
mediaEl = fixUpMediaElement(mediaEls[i]);
if (!mediaEl.src && !mediaEl.srcObject) {
warn('Audio/video asset has neither `src` nor `srcObject` attributes.');
}
loaded.push(mediaElementLoaded(mediaEl));
}
this.doConnectedCallback();
}

// Trigger loaded for scene to start rendering.
Promise.allSettled(loaded).then(bind(this.load, this));

// Timeout to start loading anyways.
timeout = parseInt(this.getAttribute('timeout'), 10) || 3000;
this.timeout = setTimeout(function () {
if (self.hasLoaded) { return; }
warn('Asset loading timed out in ', timeout, 'ms');
self.emit('timeout');
self.load();
}, timeout);
}
},
doConnectedCallback () {
var self = this;
var i;
var loaded = [];
var mediaEl;
var mediaEls;
var imgEl;
var imgEls;
var timeout;

detachedCallback: {
value: function () {
if (this.timeout) { clearTimeout(this.timeout); }
}
},
super.connectedCallback();

if (!this.parentNode.isScene) {
throw new Error('<a-assets> must be a child of a <a-scene>.');
}

// Wait for <img>s.
imgEls = this.querySelectorAll('img');
for (i = 0; i < imgEls.length; i++) {
imgEl = fixUpMediaElement(imgEls[i]);
loaded.push(new Promise(function (resolve, reject) {
// Set in cache because we won't be needing to call three.js loader if we have.
// a loaded media element.
THREE.Cache.add(imgEls[i].getAttribute('src'), imgEl);
imgEl.onload = resolve;
imgEl.onerror = reject;
}));
}

load: {
value: function () {
ANode.prototype.load.call(this, null, function waitOnFilter (el) {
return el.isAssetItem && el.hasAttribute('src');
});
// Wait for <audio>s and <video>s.
mediaEls = this.querySelectorAll('audio, video');
for (i = 0; i < mediaEls.length; i++) {
mediaEl = fixUpMediaElement(mediaEls[i]);
if (!mediaEl.src && !mediaEl.srcObject) {
warn('Audio/video asset has neither `src` nor `srcObject` attributes.');
}
loaded.push(mediaElementLoaded(mediaEl));
}
})
});

// Trigger loaded for scene to start rendering.
Promise.allSettled(loaded).then(bind(this.load, this));

// Timeout to start loading anyways.
timeout = parseInt(this.getAttribute('timeout'), 10) || 3000;
this.timeout = setTimeout(function () {
if (self.hasLoaded) { return; }
warn('Asset loading timed out in ', timeout, 'ms');
self.emit('timeout');
self.load();
}, timeout);
}

disconnectedCallback () {
super.disconnectedCallback();
if (this.timeout) { clearTimeout(this.timeout); }
}

load () {
super.load.call(this, null, function waitOnFilter (el) {
return el.isAssetItem && el.hasAttribute('src');
});
}
}

customElements.define('a-assets', AAssets);

/**
* Preload using XHRLoader for any type of asset.
*/
registerElement('a-asset-item', {
prototype: Object.create(ANode.prototype, {
createdCallback: {
value: function () {
this.data = null;
this.isAssetItem = true;
}
},

attachedCallback: {
value: function () {
var self = this;
var src = this.getAttribute('src');
fileLoader.setResponseType(
this.getAttribute('response-type') || inferResponseType(src));
fileLoader.load(src, function handleOnLoad (response) {
self.data = response;
/*
Workaround for a Chrome bug. If another XHR is sent to the same url before the
previous one closes, the second request never finishes.
setTimeout finishes the first request and lets the logic triggered by load open
subsequent requests.
setTimeout can be removed once the fix for the bug below ships:
https://bugs.chromium.org/p/chromium/issues/detail?id=633696&q=component%3ABlink%3ENetwork%3EXHR%20&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
*/
setTimeout(function load () { ANode.prototype.load.call(self); });
}, function handleOnProgress (xhr) {
self.emit('progress', {
loadedBytes: xhr.loaded,
totalBytes: xhr.total,
xhr: xhr
});
}, function handleOnError (xhr) {
self.emit('error', {xhr: xhr});
});
}
}
})
});
class AAssetItem extends ANode {
constructor () {
super();
this.data = null;
this.isAssetItem = true;
}

connectedCallback () {
var self = this;
var src = this.getAttribute('src');
fileLoader.setResponseType(
this.getAttribute('response-type') || inferResponseType(src));
fileLoader.load(src, function handleOnLoad (response) {
self.data = response;
/*
Workaround for a Chrome bug. If another XHR is sent to the same url before the
previous one closes, the second request never finishes.
setTimeout finishes the first request and lets the logic triggered by load open
subsequent requests.
setTimeout can be removed once the fix for the bug below ships:
https://bugs.chromium.org/p/chromium/issues/detail?id=633696&q=component%3ABlink%3ENetwork%3EXHR%20&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
*/
setTimeout(function load () { ANode.prototype.load.call(self); });
}, function handleOnProgress (xhr) {
self.emit('progress', {
loadedBytes: xhr.loaded,
totalBytes: xhr.total,
xhr: xhr
});
}, function handleOnError (xhr) {
self.emit('error', {xhr: xhr});
});
}
}

customElements.define('a-asset-item', AAssetItem);

/**
* Create a Promise that resolves once the media element has finished buffering.
Expand Down
73 changes: 35 additions & 38 deletions src/core/a-cubemap.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,45 @@
/* global customElements, HTMLElement */
var debug = require('../utils/debug');
var registerElement = require('./a-register-element').registerElement;

var warn = debug('core:cubemap:warn');

/**
* Cubemap element that handles validation and exposes list of URLs.
* Does not listen to updates.
*/
module.exports = registerElement('a-cubemap', {
prototype: Object.create(window.HTMLElement.prototype, {
/**
* Calculates this.srcs.
*/
attachedCallback: {
value: function () {
this.srcs = this.validate();
},
writable: window.debug
},
class ACubeMap extends HTMLElement {
/**
* Calculates this.srcs.
*/
constructor (self) {
self = super(self);
self.srcs = self.validate();
return self;
}

/**
* Checks for exactly six elements with [src].
* Does not check explicitly for <img>s in case user does not want
* prefetching.
*
* @returns {Array|null} - six URLs if valid, else null.
*/
validate: {
value: function () {
var elements = this.querySelectorAll('[src]');
var i;
var srcs = [];
if (elements.length === 6) {
for (i = 0; i < elements.length; i++) {
srcs.push(elements[i].getAttribute('src'));
}
return srcs;
}
// Else if there are not six elements, throw a warning.
warn(
'<a-cubemap> did not contain exactly six elements each with a ' +
'`src` attribute.');
},
writable: window.debug
/**
* Checks for exactly six elements with [src].
* Does not check explicitly for <img>s in case user does not want
* prefetching.
*
* @returns {Array|null} - six URLs if valid, else null.
*/
validate () {
var elements = this.querySelectorAll('[src]');
var i;
var srcs = [];
if (elements.length === 6) {
for (i = 0; i < elements.length; i++) {
srcs.push(elements[i].getAttribute('src'));
}
return srcs;
}
})
});
// Else if there are not six elements, throw a warning.
warn(
'<a-cubemap> did not contain exactly six elements each with a ' +
'`src` attribute.');
}
}

customElements.define('a-cubemap', ACubeMap);

Loading