Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source. Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately.
7295 lines
No EOL
264 KiB
JavaScript
7295 lines
No EOL
264 KiB
JavaScript
var requirejs, require, define;
|
|
(function (global) {
|
|
var req, s, head, baseElement, dataMain, src, interactiveScript, currentlyAddingScript, mainScript, subPath, version = '2.1.14', commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, jsSuffixRegExp = /\.js$/, currDirRegExp = /^\.\//, op = Object.prototype, ostring = op.toString, hasOwn = op.hasOwnProperty, ap = Array.prototype, apsp = ap.splice, isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document), isWebWorker = !isBrowser && typeof importScripts !== 'undefined', readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ? /^complete$/ : /^(complete|loaded)$/, defContextName = '_', isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]', contexts = {}, cfg = {}, globalDefQueue = [], useInteractive = false;
|
|
function isFunction(it) {
|
|
return ostring.call(it) === '[object Function]';
|
|
}
|
|
function isArray(it) {
|
|
return ostring.call(it) === '[object Array]';
|
|
}
|
|
function each(ary, func) {
|
|
if (ary) {
|
|
var i;
|
|
for (i = 0; i < ary.length; i += 1) {
|
|
if (ary[i] && func(ary[i], i, ary)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function eachReverse(ary, func) {
|
|
if (ary) {
|
|
var i;
|
|
for (i = ary.length - 1; i > -1; i -= 1) {
|
|
if (ary[i] && func(ary[i], i, ary)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function hasProp(obj, prop) {
|
|
return hasOwn.call(obj, prop);
|
|
}
|
|
function getOwn(obj, prop) {
|
|
return hasProp(obj, prop) && obj[prop];
|
|
}
|
|
function eachProp(obj, func) {
|
|
var prop;
|
|
for (prop in obj) {
|
|
if (hasProp(obj, prop)) {
|
|
if (func(obj[prop], prop)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function mixin(target, source, force, deepStringMixin) {
|
|
if (source) {
|
|
eachProp(source, function (value, prop) {
|
|
if (force || !hasProp(target, prop)) {
|
|
if (deepStringMixin && typeof value === 'object' && value && !isArray(value) && !isFunction(value) && !(value instanceof RegExp)) {
|
|
if (!target[prop]) {
|
|
target[prop] = {};
|
|
}
|
|
mixin(target[prop], value, force, deepStringMixin);
|
|
} else {
|
|
target[prop] = value;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return target;
|
|
}
|
|
function bind(obj, fn) {
|
|
return function () {
|
|
return fn.apply(obj, arguments);
|
|
};
|
|
}
|
|
function scripts() {
|
|
return document.getElementsByTagName('script');
|
|
}
|
|
function defaultOnError(err) {
|
|
throw err;
|
|
}
|
|
function getGlobal(value) {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
var g = global;
|
|
each(value.split('.'), function (part) {
|
|
g = g[part];
|
|
});
|
|
return g;
|
|
}
|
|
function makeError(id, msg, err, requireModules) {
|
|
var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
|
|
e.requireType = id;
|
|
e.requireModules = requireModules;
|
|
if (err) {
|
|
e.originalError = err;
|
|
}
|
|
return e;
|
|
}
|
|
if (typeof define !== 'undefined') {
|
|
return;
|
|
}
|
|
if (typeof requirejs !== 'undefined') {
|
|
if (isFunction(requirejs)) {
|
|
return;
|
|
}
|
|
cfg = requirejs;
|
|
requirejs = undefined;
|
|
}
|
|
if (typeof require !== 'undefined' && !isFunction(require)) {
|
|
cfg = require;
|
|
require = undefined;
|
|
}
|
|
function newContext(contextName) {
|
|
var inCheckLoaded, Module, context, handlers, checkLoadedTimeoutId, config = {
|
|
waitSeconds: 7,
|
|
baseUrl: './',
|
|
paths: {},
|
|
bundles: {},
|
|
pkgs: {},
|
|
shim: {},
|
|
config: {}
|
|
}, registry = {}, enabledRegistry = {}, undefEvents = {}, defQueue = [], defined = {}, urlFetched = {}, bundlesMap = {}, requireCounter = 1, unnormalizedCounter = 1;
|
|
function trimDots(ary) {
|
|
var i, part;
|
|
for (i = 0; i < ary.length; i++) {
|
|
part = ary[i];
|
|
if (part === '.') {
|
|
ary.splice(i, 1);
|
|
i -= 1;
|
|
} else if (part === '..') {
|
|
if (i === 0 || i == 1 && ary[2] === '..' || ary[i - 1] === '..') {
|
|
continue;
|
|
} else if (i > 0) {
|
|
ary.splice(i - 1, 2);
|
|
i -= 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function normalize(name, baseName, applyMap) {
|
|
var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex, foundMap, foundI, foundStarMap, starI, normalizedBaseParts, baseParts = baseName && baseName.split('/'), map = config.map, starMap = map && map['*'];
|
|
if (name) {
|
|
name = name.split('/');
|
|
lastIndex = name.length - 1;
|
|
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
|
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
|
}
|
|
if (name[0].charAt(0) === '.' && baseParts) {
|
|
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
|
|
name = normalizedBaseParts.concat(name);
|
|
}
|
|
trimDots(name);
|
|
name = name.join('/');
|
|
}
|
|
if (applyMap && map && (baseParts || starMap)) {
|
|
nameParts = name.split('/');
|
|
outerLoop:
|
|
for (i = nameParts.length; i > 0; i -= 1) {
|
|
nameSegment = nameParts.slice(0, i).join('/');
|
|
if (baseParts) {
|
|
for (j = baseParts.length; j > 0; j -= 1) {
|
|
mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
|
|
if (mapValue) {
|
|
mapValue = getOwn(mapValue, nameSegment);
|
|
if (mapValue) {
|
|
foundMap = mapValue;
|
|
foundI = i;
|
|
break outerLoop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
|
|
foundStarMap = getOwn(starMap, nameSegment);
|
|
starI = i;
|
|
}
|
|
}
|
|
if (!foundMap && foundStarMap) {
|
|
foundMap = foundStarMap;
|
|
foundI = starI;
|
|
}
|
|
if (foundMap) {
|
|
nameParts.splice(0, foundI, foundMap);
|
|
name = nameParts.join('/');
|
|
}
|
|
}
|
|
pkgMain = getOwn(config.pkgs, name);
|
|
return pkgMain ? pkgMain : name;
|
|
}
|
|
function removeScript(name) {
|
|
if (isBrowser) {
|
|
each(scripts(), function (scriptNode) {
|
|
if (scriptNode.getAttribute('data-requiremodule') === name && scriptNode.getAttribute('data-requirecontext') === context.contextName) {
|
|
scriptNode.parentNode.removeChild(scriptNode);
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
function hasPathFallback(id) {
|
|
var pathConfig = getOwn(config.paths, id);
|
|
if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
|
|
pathConfig.shift();
|
|
context.require.undef(id);
|
|
context.makeRequire(null, { skipMap: true })([id]);
|
|
return true;
|
|
}
|
|
}
|
|
function splitPrefix(name) {
|
|
var prefix, index = name ? name.indexOf('!') : -1;
|
|
if (index > -1) {
|
|
prefix = name.substring(0, index);
|
|
name = name.substring(index + 1, name.length);
|
|
}
|
|
return [
|
|
prefix,
|
|
name
|
|
];
|
|
}
|
|
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
|
|
var url, pluginModule, suffix, nameParts, prefix = null, parentName = parentModuleMap ? parentModuleMap.name : null, originalName = name, isDefine = true, normalizedName = '';
|
|
if (!name) {
|
|
isDefine = false;
|
|
name = '_@r' + (requireCounter += 1);
|
|
}
|
|
nameParts = splitPrefix(name);
|
|
prefix = nameParts[0];
|
|
name = nameParts[1];
|
|
if (prefix) {
|
|
prefix = normalize(prefix, parentName, applyMap);
|
|
pluginModule = getOwn(defined, prefix);
|
|
}
|
|
if (name) {
|
|
if (prefix) {
|
|
if (pluginModule && pluginModule.normalize) {
|
|
normalizedName = pluginModule.normalize(name, function (name) {
|
|
return normalize(name, parentName, applyMap);
|
|
});
|
|
} else {
|
|
normalizedName = name.indexOf('!') === -1 ? normalize(name, parentName, applyMap) : name;
|
|
}
|
|
} else {
|
|
normalizedName = normalize(name, parentName, applyMap);
|
|
nameParts = splitPrefix(normalizedName);
|
|
prefix = nameParts[0];
|
|
normalizedName = nameParts[1];
|
|
isNormalized = true;
|
|
url = context.nameToUrl(normalizedName);
|
|
}
|
|
}
|
|
suffix = prefix && !pluginModule && !isNormalized ? '_unnormalized' + (unnormalizedCounter += 1) : '';
|
|
return {
|
|
prefix: prefix,
|
|
name: normalizedName,
|
|
parentMap: parentModuleMap,
|
|
unnormalized: !!suffix,
|
|
url: url,
|
|
originalName: originalName,
|
|
isDefine: isDefine,
|
|
id: (prefix ? prefix + '!' + normalizedName : normalizedName) + suffix
|
|
};
|
|
}
|
|
function getModule(depMap) {
|
|
var id = depMap.id, mod = getOwn(registry, id);
|
|
if (!mod) {
|
|
mod = registry[id] = new context.Module(depMap);
|
|
}
|
|
return mod;
|
|
}
|
|
function on(depMap, name, fn) {
|
|
var id = depMap.id, mod = getOwn(registry, id);
|
|
if (hasProp(defined, id) && (!mod || mod.defineEmitComplete)) {
|
|
if (name === 'defined') {
|
|
fn(defined[id]);
|
|
}
|
|
} else {
|
|
mod = getModule(depMap);
|
|
if (mod.error && name === 'error') {
|
|
fn(mod.error);
|
|
} else {
|
|
mod.on(name, fn);
|
|
}
|
|
}
|
|
}
|
|
function onError(err, errback) {
|
|
var ids = err.requireModules, notified = false;
|
|
if (errback) {
|
|
errback(err);
|
|
} else {
|
|
each(ids, function (id) {
|
|
var mod = getOwn(registry, id);
|
|
if (mod) {
|
|
mod.error = err;
|
|
if (mod.events.error) {
|
|
notified = true;
|
|
mod.emit('error', err);
|
|
}
|
|
}
|
|
});
|
|
if (!notified) {
|
|
req.onError(err);
|
|
}
|
|
}
|
|
}
|
|
function takeGlobalQueue() {
|
|
if (globalDefQueue.length) {
|
|
apsp.apply(defQueue, [
|
|
defQueue.length,
|
|
0
|
|
].concat(globalDefQueue));
|
|
globalDefQueue = [];
|
|
}
|
|
}
|
|
handlers = {
|
|
'require': function (mod) {
|
|
if (mod.require) {
|
|
return mod.require;
|
|
} else {
|
|
return mod.require = context.makeRequire(mod.map);
|
|
}
|
|
},
|
|
'exports': function (mod) {
|
|
mod.usingExports = true;
|
|
if (mod.map.isDefine) {
|
|
if (mod.exports) {
|
|
return defined[mod.map.id] = mod.exports;
|
|
} else {
|
|
return mod.exports = defined[mod.map.id] = {};
|
|
}
|
|
}
|
|
},
|
|
'module': function (mod) {
|
|
if (mod.module) {
|
|
return mod.module;
|
|
} else {
|
|
return mod.module = {
|
|
id: mod.map.id,
|
|
uri: mod.map.url,
|
|
config: function () {
|
|
return getOwn(config.config, mod.map.id) || {};
|
|
},
|
|
exports: mod.exports || (mod.exports = {})
|
|
};
|
|
}
|
|
}
|
|
};
|
|
function cleanRegistry(id) {
|
|
delete registry[id];
|
|
delete enabledRegistry[id];
|
|
}
|
|
function breakCycle(mod, traced, processed) {
|
|
var id = mod.map.id;
|
|
if (mod.error) {
|
|
mod.emit('error', mod.error);
|
|
} else {
|
|
traced[id] = true;
|
|
each(mod.depMaps, function (depMap, i) {
|
|
var depId = depMap.id, dep = getOwn(registry, depId);
|
|
if (dep && !mod.depMatched[i] && !processed[depId]) {
|
|
if (getOwn(traced, depId)) {
|
|
mod.defineDep(i, defined[depId]);
|
|
mod.check();
|
|
} else {
|
|
breakCycle(dep, traced, processed);
|
|
}
|
|
}
|
|
});
|
|
processed[id] = true;
|
|
}
|
|
}
|
|
function checkLoaded() {
|
|
var err, usingPathFallback, waitInterval = config.waitSeconds * 1000, expired = waitInterval && context.startTime + waitInterval < new Date().getTime(), noLoads = [], reqCalls = [], stillLoading = false, needCycleCheck = true;
|
|
if (inCheckLoaded) {
|
|
return;
|
|
}
|
|
inCheckLoaded = true;
|
|
eachProp(enabledRegistry, function (mod) {
|
|
var map = mod.map, modId = map.id;
|
|
if (!mod.enabled) {
|
|
return;
|
|
}
|
|
if (!map.isDefine) {
|
|
reqCalls.push(mod);
|
|
}
|
|
if (!mod.error) {
|
|
if (!mod.inited && expired) {
|
|
if (hasPathFallback(modId)) {
|
|
usingPathFallback = true;
|
|
stillLoading = true;
|
|
} else {
|
|
noLoads.push(modId);
|
|
removeScript(modId);
|
|
}
|
|
} else if (!mod.inited && mod.fetched && map.isDefine) {
|
|
stillLoading = true;
|
|
if (!map.prefix) {
|
|
return needCycleCheck = false;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (expired && noLoads.length) {
|
|
err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
|
|
err.contextName = context.contextName;
|
|
return onError(err);
|
|
}
|
|
if (needCycleCheck) {
|
|
each(reqCalls, function (mod) {
|
|
breakCycle(mod, {}, {});
|
|
});
|
|
}
|
|
if ((!expired || usingPathFallback) && stillLoading) {
|
|
if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
|
|
checkLoadedTimeoutId = setTimeout(function () {
|
|
checkLoadedTimeoutId = 0;
|
|
checkLoaded();
|
|
}, 50);
|
|
}
|
|
}
|
|
inCheckLoaded = false;
|
|
}
|
|
Module = function (map) {
|
|
this.events = getOwn(undefEvents, map.id) || {};
|
|
this.map = map;
|
|
this.shim = getOwn(config.shim, map.id);
|
|
this.depExports = [];
|
|
this.depMaps = [];
|
|
this.depMatched = [];
|
|
this.pluginMaps = {};
|
|
this.depCount = 0;
|
|
};
|
|
Module.prototype = {
|
|
init: function (depMaps, factory, errback, options) {
|
|
options = options || {};
|
|
if (this.inited) {
|
|
return;
|
|
}
|
|
this.factory = factory;
|
|
if (errback) {
|
|
this.on('error', errback);
|
|
} else if (this.events.error) {
|
|
errback = bind(this, function (err) {
|
|
this.emit('error', err);
|
|
});
|
|
}
|
|
this.depMaps = depMaps && depMaps.slice(0);
|
|
this.errback = errback;
|
|
this.inited = true;
|
|
this.ignore = options.ignore;
|
|
if (options.enabled || this.enabled) {
|
|
this.enable();
|
|
} else {
|
|
this.check();
|
|
}
|
|
},
|
|
defineDep: function (i, depExports) {
|
|
if (!this.depMatched[i]) {
|
|
this.depMatched[i] = true;
|
|
this.depCount -= 1;
|
|
this.depExports[i] = depExports;
|
|
}
|
|
},
|
|
fetch: function () {
|
|
if (this.fetched) {
|
|
return;
|
|
}
|
|
this.fetched = true;
|
|
context.startTime = new Date().getTime();
|
|
var map = this.map;
|
|
if (this.shim) {
|
|
context.makeRequire(this.map, { enableBuildCallback: true })(this.shim.deps || [], bind(this, function () {
|
|
return map.prefix ? this.callPlugin() : this.load();
|
|
}));
|
|
} else {
|
|
return map.prefix ? this.callPlugin() : this.load();
|
|
}
|
|
},
|
|
load: function () {
|
|
var url = this.map.url;
|
|
if (!urlFetched[url]) {
|
|
urlFetched[url] = true;
|
|
context.load(this.map.id, url);
|
|
}
|
|
},
|
|
check: function () {
|
|
if (!this.enabled || this.enabling) {
|
|
return;
|
|
}
|
|
var err, cjsModule, id = this.map.id, depExports = this.depExports, exports = this.exports, factory = this.factory;
|
|
if (!this.inited) {
|
|
this.fetch();
|
|
} else if (this.error) {
|
|
this.emit('error', this.error);
|
|
} else if (!this.defining) {
|
|
this.defining = true;
|
|
if (this.depCount < 1 && !this.defined) {
|
|
if (isFunction(factory)) {
|
|
if (this.events.error && this.map.isDefine || req.onError !== defaultOnError) {
|
|
try {
|
|
exports = context.execCb(id, factory, depExports, exports);
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
} else {
|
|
exports = context.execCb(id, factory, depExports, exports);
|
|
}
|
|
if (this.map.isDefine && exports === undefined) {
|
|
cjsModule = this.module;
|
|
if (cjsModule) {
|
|
exports = cjsModule.exports;
|
|
} else if (this.usingExports) {
|
|
exports = this.exports;
|
|
}
|
|
}
|
|
if (err) {
|
|
err.requireMap = this.map;
|
|
err.requireModules = this.map.isDefine ? [this.map.id] : null;
|
|
err.requireType = this.map.isDefine ? 'define' : 'require';
|
|
return onError(this.error = err);
|
|
}
|
|
} else {
|
|
exports = factory;
|
|
}
|
|
this.exports = exports;
|
|
if (this.map.isDefine && !this.ignore) {
|
|
defined[id] = exports;
|
|
if (req.onResourceLoad) {
|
|
req.onResourceLoad(context, this.map, this.depMaps);
|
|
}
|
|
}
|
|
cleanRegistry(id);
|
|
this.defined = true;
|
|
}
|
|
this.defining = false;
|
|
if (this.defined && !this.defineEmitted) {
|
|
this.defineEmitted = true;
|
|
this.emit('defined', this.exports);
|
|
this.defineEmitComplete = true;
|
|
}
|
|
}
|
|
},
|
|
callPlugin: function () {
|
|
var map = this.map, id = map.id, pluginMap = makeModuleMap(map.prefix);
|
|
this.depMaps.push(pluginMap);
|
|
on(pluginMap, 'defined', bind(this, function (plugin) {
|
|
var load, normalizedMap, normalizedMod, bundleId = getOwn(bundlesMap, this.map.id), name = this.map.name, parentName = this.map.parentMap ? this.map.parentMap.name : null, localRequire = context.makeRequire(map.parentMap, { enableBuildCallback: true });
|
|
if (this.map.unnormalized) {
|
|
if (plugin.normalize) {
|
|
name = plugin.normalize(name, function (name) {
|
|
return normalize(name, parentName, true);
|
|
}) || '';
|
|
}
|
|
normalizedMap = makeModuleMap(map.prefix + '!' + name, this.map.parentMap);
|
|
on(normalizedMap, 'defined', bind(this, function (value) {
|
|
this.init([], function () {
|
|
return value;
|
|
}, null, {
|
|
enabled: true,
|
|
ignore: true
|
|
});
|
|
}));
|
|
normalizedMod = getOwn(registry, normalizedMap.id);
|
|
if (normalizedMod) {
|
|
this.depMaps.push(normalizedMap);
|
|
if (this.events.error) {
|
|
normalizedMod.on('error', bind(this, function (err) {
|
|
this.emit('error', err);
|
|
}));
|
|
}
|
|
normalizedMod.enable();
|
|
}
|
|
return;
|
|
}
|
|
if (bundleId) {
|
|
this.map.url = context.nameToUrl(bundleId);
|
|
this.load();
|
|
return;
|
|
}
|
|
load = bind(this, function (value) {
|
|
this.init([], function () {
|
|
return value;
|
|
}, null, { enabled: true });
|
|
});
|
|
load.error = bind(this, function (err) {
|
|
this.inited = true;
|
|
this.error = err;
|
|
err.requireModules = [id];
|
|
eachProp(registry, function (mod) {
|
|
if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
|
|
cleanRegistry(mod.map.id);
|
|
}
|
|
});
|
|
onError(err);
|
|
});
|
|
load.fromText = bind(this, function (text, textAlt) {
|
|
var moduleName = map.name, moduleMap = makeModuleMap(moduleName), hasInteractive = useInteractive;
|
|
if (textAlt) {
|
|
text = textAlt;
|
|
}
|
|
if (hasInteractive) {
|
|
useInteractive = false;
|
|
}
|
|
getModule(moduleMap);
|
|
if (hasProp(config.config, id)) {
|
|
config.config[moduleName] = config.config[id];
|
|
}
|
|
try {
|
|
req.exec(text);
|
|
} catch (e) {
|
|
return onError(makeError('fromtexteval', 'fromText eval for ' + id + ' failed: ' + e, e, [id]));
|
|
}
|
|
if (hasInteractive) {
|
|
useInteractive = true;
|
|
}
|
|
this.depMaps.push(moduleMap);
|
|
context.completeLoad(moduleName);
|
|
localRequire([moduleName], load);
|
|
});
|
|
plugin.load(map.name, localRequire, load, config);
|
|
}));
|
|
context.enable(pluginMap, this);
|
|
this.pluginMaps[pluginMap.id] = pluginMap;
|
|
},
|
|
enable: function () {
|
|
enabledRegistry[this.map.id] = this;
|
|
this.enabled = true;
|
|
this.enabling = true;
|
|
each(this.depMaps, bind(this, function (depMap, i) {
|
|
var id, mod, handler;
|
|
if (typeof depMap === 'string') {
|
|
depMap = makeModuleMap(depMap, this.map.isDefine ? this.map : this.map.parentMap, false, !this.skipMap);
|
|
this.depMaps[i] = depMap;
|
|
handler = getOwn(handlers, depMap.id);
|
|
if (handler) {
|
|
this.depExports[i] = handler(this);
|
|
return;
|
|
}
|
|
this.depCount += 1;
|
|
on(depMap, 'defined', bind(this, function (depExports) {
|
|
this.defineDep(i, depExports);
|
|
this.check();
|
|
}));
|
|
if (this.errback) {
|
|
on(depMap, 'error', bind(this, this.errback));
|
|
}
|
|
}
|
|
id = depMap.id;
|
|
mod = registry[id];
|
|
if (!hasProp(handlers, id) && mod && !mod.enabled) {
|
|
context.enable(depMap, this);
|
|
}
|
|
}));
|
|
eachProp(this.pluginMaps, bind(this, function (pluginMap) {
|
|
var mod = getOwn(registry, pluginMap.id);
|
|
if (mod && !mod.enabled) {
|
|
context.enable(pluginMap, this);
|
|
}
|
|
}));
|
|
this.enabling = false;
|
|
this.check();
|
|
},
|
|
on: function (name, cb) {
|
|
var cbs = this.events[name];
|
|
if (!cbs) {
|
|
cbs = this.events[name] = [];
|
|
}
|
|
cbs.push(cb);
|
|
},
|
|
emit: function (name, evt) {
|
|
each(this.events[name], function (cb) {
|
|
cb(evt);
|
|
});
|
|
if (name === 'error') {
|
|
delete this.events[name];
|
|
}
|
|
}
|
|
};
|
|
function callGetModule(args) {
|
|
if (!hasProp(defined, args[0])) {
|
|
getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
|
|
}
|
|
}
|
|
function removeListener(node, func, name, ieName) {
|
|
if (node.detachEvent && !isOpera) {
|
|
if (ieName) {
|
|
node.detachEvent(ieName, func);
|
|
}
|
|
} else {
|
|
node.removeEventListener(name, func, false);
|
|
}
|
|
}
|
|
function getScriptData(evt) {
|
|
var node = evt.currentTarget || evt.srcElement;
|
|
removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
|
|
removeListener(node, context.onScriptError, 'error');
|
|
return {
|
|
node: node,
|
|
id: node && node.getAttribute('data-requiremodule')
|
|
};
|
|
}
|
|
function intakeDefines() {
|
|
var args;
|
|
takeGlobalQueue();
|
|
while (defQueue.length) {
|
|
args = defQueue.shift();
|
|
if (args[0] === null) {
|
|
return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
|
|
} else {
|
|
callGetModule(args);
|
|
}
|
|
}
|
|
}
|
|
context = {
|
|
config: config,
|
|
contextName: contextName,
|
|
registry: registry,
|
|
defined: defined,
|
|
urlFetched: urlFetched,
|
|
defQueue: defQueue,
|
|
Module: Module,
|
|
makeModuleMap: makeModuleMap,
|
|
nextTick: req.nextTick,
|
|
onError: onError,
|
|
configure: function (cfg) {
|
|
if (cfg.baseUrl) {
|
|
if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
|
|
cfg.baseUrl += '/';
|
|
}
|
|
}
|
|
var shim = config.shim, objs = {
|
|
paths: true,
|
|
bundles: true,
|
|
config: true,
|
|
map: true
|
|
};
|
|
eachProp(cfg, function (value, prop) {
|
|
if (objs[prop]) {
|
|
if (!config[prop]) {
|
|
config[prop] = {};
|
|
}
|
|
mixin(config[prop], value, true, true);
|
|
} else {
|
|
config[prop] = value;
|
|
}
|
|
});
|
|
if (cfg.bundles) {
|
|
eachProp(cfg.bundles, function (value, prop) {
|
|
each(value, function (v) {
|
|
if (v !== prop) {
|
|
bundlesMap[v] = prop;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
if (cfg.shim) {
|
|
eachProp(cfg.shim, function (value, id) {
|
|
if (isArray(value)) {
|
|
value = { deps: value };
|
|
}
|
|
if ((value.exports || value.init) && !value.exportsFn) {
|
|
value.exportsFn = context.makeShimExports(value);
|
|
}
|
|
shim[id] = value;
|
|
});
|
|
config.shim = shim;
|
|
}
|
|
if (cfg.packages) {
|
|
each(cfg.packages, function (pkgObj) {
|
|
var location, name;
|
|
pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
|
|
name = pkgObj.name;
|
|
location = pkgObj.location;
|
|
if (location) {
|
|
config.paths[name] = pkgObj.location;
|
|
}
|
|
config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main').replace(currDirRegExp, '').replace(jsSuffixRegExp, '');
|
|
});
|
|
}
|
|
eachProp(registry, function (mod, id) {
|
|
if (!mod.inited && !mod.map.unnormalized) {
|
|
mod.map = makeModuleMap(id);
|
|
}
|
|
});
|
|
if (cfg.deps || cfg.callback) {
|
|
context.require(cfg.deps || [], cfg.callback);
|
|
}
|
|
},
|
|
makeShimExports: function (value) {
|
|
function fn() {
|
|
var ret;
|
|
if (value.init) {
|
|
ret = value.init.apply(global, arguments);
|
|
}
|
|
return ret || value.exports && getGlobal(value.exports);
|
|
}
|
|
return fn;
|
|
},
|
|
makeRequire: function (relMap, options) {
|
|
options = options || {};
|
|
function localRequire(deps, callback, errback) {
|
|
var id, map, requireMod;
|
|
if (options.enableBuildCallback && callback && isFunction(callback)) {
|
|
callback.__requireJsBuild = true;
|
|
}
|
|
if (typeof deps === 'string') {
|
|
if (isFunction(callback)) {
|
|
return onError(makeError('requireargs', 'Invalid require call'), errback);
|
|
}
|
|
if (relMap && hasProp(handlers, deps)) {
|
|
return handlers[deps](registry[relMap.id]);
|
|
}
|
|
if (req.get) {
|
|
return req.get(context, deps, relMap, localRequire);
|
|
}
|
|
map = makeModuleMap(deps, relMap, false, true);
|
|
id = map.id;
|
|
if (!hasProp(defined, id)) {
|
|
return onError(makeError('notloaded', 'Module name "' + id + '" has not been loaded yet for context: ' + contextName + (relMap ? '' : '. Use require([])')));
|
|
}
|
|
return defined[id];
|
|
}
|
|
intakeDefines();
|
|
context.nextTick(function () {
|
|
intakeDefines();
|
|
requireMod = getModule(makeModuleMap(null, relMap));
|
|
requireMod.skipMap = options.skipMap;
|
|
requireMod.init(deps, callback, errback, { enabled: true });
|
|
checkLoaded();
|
|
});
|
|
return localRequire;
|
|
}
|
|
mixin(localRequire, {
|
|
isBrowser: isBrowser,
|
|
toUrl: function (moduleNamePlusExt) {
|
|
var ext, index = moduleNamePlusExt.lastIndexOf('.'), segment = moduleNamePlusExt.split('/')[0], isRelative = segment === '.' || segment === '..';
|
|
if (index !== -1 && (!isRelative || index > 1)) {
|
|
ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
|
|
moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
|
|
}
|
|
return context.nameToUrl(normalize(moduleNamePlusExt, relMap && relMap.id, true), ext, true);
|
|
},
|
|
defined: function (id) {
|
|
return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
|
|
},
|
|
specified: function (id) {
|
|
id = makeModuleMap(id, relMap, false, true).id;
|
|
return hasProp(defined, id) || hasProp(registry, id);
|
|
}
|
|
});
|
|
if (!relMap) {
|
|
localRequire.undef = function (id) {
|
|
takeGlobalQueue();
|
|
var map = makeModuleMap(id, relMap, true), mod = getOwn(registry, id);
|
|
removeScript(id);
|
|
delete defined[id];
|
|
delete urlFetched[map.url];
|
|
delete undefEvents[id];
|
|
eachReverse(defQueue, function (args, i) {
|
|
if (args[0] === id) {
|
|
defQueue.splice(i, 1);
|
|
}
|
|
});
|
|
if (mod) {
|
|
if (mod.events.defined) {
|
|
undefEvents[id] = mod.events;
|
|
}
|
|
cleanRegistry(id);
|
|
}
|
|
};
|
|
}
|
|
return localRequire;
|
|
},
|
|
enable: function (depMap) {
|
|
var mod = getOwn(registry, depMap.id);
|
|
if (mod) {
|
|
getModule(depMap).enable();
|
|
}
|
|
},
|
|
completeLoad: function (moduleName) {
|
|
var found, args, mod, shim = getOwn(config.shim, moduleName) || {}, shExports = shim.exports;
|
|
takeGlobalQueue();
|
|
while (defQueue.length) {
|
|
args = defQueue.shift();
|
|
if (args[0] === null) {
|
|
args[0] = moduleName;
|
|
if (found) {
|
|
break;
|
|
}
|
|
found = true;
|
|
} else if (args[0] === moduleName) {
|
|
found = true;
|
|
}
|
|
callGetModule(args);
|
|
}
|
|
mod = getOwn(registry, moduleName);
|
|
if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {
|
|
if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
|
|
if (hasPathFallback(moduleName)) {
|
|
return;
|
|
} else {
|
|
return onError(makeError('nodefine', 'No define call for ' + moduleName, null, [moduleName]));
|
|
}
|
|
} else {
|
|
callGetModule([
|
|
moduleName,
|
|
shim.deps || [],
|
|
shim.exportsFn
|
|
]);
|
|
}
|
|
}
|
|
checkLoaded();
|
|
},
|
|
nameToUrl: function (moduleName, ext, skipExt) {
|
|
var paths, syms, i, parentModule, url, parentPath, bundleId, pkgMain = getOwn(config.pkgs, moduleName);
|
|
if (pkgMain) {
|
|
moduleName = pkgMain;
|
|
}
|
|
bundleId = getOwn(bundlesMap, moduleName);
|
|
if (bundleId) {
|
|
return context.nameToUrl(bundleId, ext, skipExt);
|
|
}
|
|
if (req.jsExtRegExp.test(moduleName)) {
|
|
url = moduleName + (ext || '');
|
|
} else {
|
|
paths = config.paths;
|
|
syms = moduleName.split('/');
|
|
for (i = syms.length; i > 0; i -= 1) {
|
|
parentModule = syms.slice(0, i).join('/');
|
|
parentPath = getOwn(paths, parentModule);
|
|
if (parentPath) {
|
|
if (isArray(parentPath)) {
|
|
parentPath = parentPath[0];
|
|
}
|
|
syms.splice(0, i, parentPath);
|
|
break;
|
|
}
|
|
}
|
|
url = syms.join('/');
|
|
url += ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js');
|
|
url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
|
|
}
|
|
return config.urlArgs ? url + ((url.indexOf('?') === -1 ? '?' : '&') + config.urlArgs) : url;
|
|
},
|
|
load: function (id, url) {
|
|
req.load(context, id, url);
|
|
},
|
|
execCb: function (name, callback, args, exports) {
|
|
return callback.apply(exports, args);
|
|
},
|
|
onScriptLoad: function (evt) {
|
|
if (evt.type === 'load' || readyRegExp.test((evt.currentTarget || evt.srcElement).readyState)) {
|
|
interactiveScript = null;
|
|
var data = getScriptData(evt);
|
|
context.completeLoad(data.id);
|
|
}
|
|
},
|
|
onScriptError: function (evt) {
|
|
var data = getScriptData(evt);
|
|
if (!hasPathFallback(data.id)) {
|
|
return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id]));
|
|
}
|
|
}
|
|
};
|
|
context.require = context.makeRequire();
|
|
return context;
|
|
}
|
|
req = requirejs = function (deps, callback, errback, optional) {
|
|
var context, config, contextName = defContextName;
|
|
if (!isArray(deps) && typeof deps !== 'string') {
|
|
config = deps;
|
|
if (isArray(callback)) {
|
|
deps = callback;
|
|
callback = errback;
|
|
errback = optional;
|
|
} else {
|
|
deps = [];
|
|
}
|
|
}
|
|
if (config && config.context) {
|
|
contextName = config.context;
|
|
}
|
|
context = getOwn(contexts, contextName);
|
|
if (!context) {
|
|
context = contexts[contextName] = req.s.newContext(contextName);
|
|
}
|
|
if (config) {
|
|
context.configure(config);
|
|
}
|
|
return context.require(deps, callback, errback);
|
|
};
|
|
req.config = function (config) {
|
|
return req(config);
|
|
};
|
|
req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
|
|
setTimeout(fn, 4);
|
|
} : function (fn) {
|
|
fn();
|
|
};
|
|
if (!require) {
|
|
require = req;
|
|
}
|
|
req.version = version;
|
|
req.jsExtRegExp = /^\/|:|\?|\.js$/;
|
|
req.isBrowser = isBrowser;
|
|
s = req.s = {
|
|
contexts: contexts,
|
|
newContext: newContext
|
|
};
|
|
req({});
|
|
each([
|
|
'toUrl',
|
|
'undef',
|
|
'defined',
|
|
'specified'
|
|
], function (prop) {
|
|
req[prop] = function () {
|
|
var ctx = contexts[defContextName];
|
|
return ctx.require[prop].apply(ctx, arguments);
|
|
};
|
|
});
|
|
if (isBrowser) {
|
|
head = s.head = document.getElementsByTagName('head')[0];
|
|
baseElement = document.getElementsByTagName('base')[0];
|
|
if (baseElement) {
|
|
head = s.head = baseElement.parentNode;
|
|
}
|
|
}
|
|
req.onError = defaultOnError;
|
|
req.createNode = function (config, moduleName, url) {
|
|
var node = config.xhtml ? document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : document.createElement('script');
|
|
node.type = config.scriptType || 'text/javascript';
|
|
node.charset = 'utf-8';
|
|
node.async = true;
|
|
return node;
|
|
};
|
|
req.load = function (context, moduleName, url) {
|
|
var config = context && context.config || {}, node;
|
|
if (isBrowser) {
|
|
node = req.createNode(config, moduleName, url);
|
|
node.setAttribute('data-requirecontext', context.contextName);
|
|
node.setAttribute('data-requiremodule', moduleName);
|
|
if (node.attachEvent && !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && !isOpera) {
|
|
useInteractive = true;
|
|
node.attachEvent('onreadystatechange', context.onScriptLoad);
|
|
} else {
|
|
node.addEventListener('load', context.onScriptLoad, false);
|
|
node.addEventListener('error', context.onScriptError, false);
|
|
}
|
|
node.src = url;
|
|
currentlyAddingScript = node;
|
|
if (baseElement) {
|
|
head.insertBefore(node, baseElement);
|
|
} else {
|
|
head.appendChild(node);
|
|
}
|
|
currentlyAddingScript = null;
|
|
return node;
|
|
} else if (isWebWorker) {
|
|
try {
|
|
importScripts(url);
|
|
context.completeLoad(moduleName);
|
|
} catch (e) {
|
|
context.onError(makeError('importscripts', 'importScripts failed for ' + moduleName + ' at ' + url, e, [moduleName]));
|
|
}
|
|
}
|
|
};
|
|
function getInteractiveScript() {
|
|
if (interactiveScript && interactiveScript.readyState === 'interactive') {
|
|
return interactiveScript;
|
|
}
|
|
eachReverse(scripts(), function (script) {
|
|
if (script.readyState === 'interactive') {
|
|
return interactiveScript = script;
|
|
}
|
|
});
|
|
return interactiveScript;
|
|
}
|
|
if (isBrowser && !cfg.skipDataMain) {
|
|
eachReverse(scripts(), function (script) {
|
|
if (!head) {
|
|
head = script.parentNode;
|
|
}
|
|
dataMain = script.getAttribute('data-main');
|
|
if (dataMain) {
|
|
mainScript = dataMain;
|
|
if (!cfg.baseUrl) {
|
|
src = mainScript.split('/');
|
|
mainScript = src.pop();
|
|
subPath = src.length ? src.join('/') + '/' : './';
|
|
cfg.baseUrl = subPath;
|
|
}
|
|
mainScript = mainScript.replace(jsSuffixRegExp, '');
|
|
if (req.jsExtRegExp.test(mainScript)) {
|
|
mainScript = dataMain;
|
|
}
|
|
cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
define = function (name, deps, callback) {
|
|
var node, context;
|
|
if (typeof name !== 'string') {
|
|
callback = deps;
|
|
deps = name;
|
|
name = null;
|
|
}
|
|
if (!isArray(deps)) {
|
|
callback = deps;
|
|
deps = null;
|
|
}
|
|
if (!deps && isFunction(callback)) {
|
|
deps = [];
|
|
if (callback.length) {
|
|
callback.toString().replace(commentRegExp, '').replace(cjsRequireRegExp, function (match, dep) {
|
|
deps.push(dep);
|
|
});
|
|
deps = (callback.length === 1 ? ['require'] : [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
]).concat(deps);
|
|
}
|
|
}
|
|
if (useInteractive) {
|
|
node = currentlyAddingScript || getInteractiveScript();
|
|
if (node) {
|
|
if (!name) {
|
|
name = node.getAttribute('data-requiremodule');
|
|
}
|
|
context = contexts[node.getAttribute('data-requirecontext')];
|
|
}
|
|
}
|
|
(context ? context.defQueue : globalDefQueue).push([
|
|
name,
|
|
deps,
|
|
callback
|
|
]);
|
|
};
|
|
define.amd = { jQuery: true };
|
|
req.exec = function (text) {
|
|
return eval(text);
|
|
};
|
|
req(cfg);
|
|
}(this));
|
|
define('../bower_components/requirejs/index', function () {
|
|
});
|
|
!function (e) {
|
|
if ('object' == typeof exports && 'undefined' != typeof module)
|
|
module.exports = e();
|
|
else if ('function' == typeof define && define.amd)
|
|
define('debug', [], e);
|
|
else {
|
|
var f;
|
|
'undefined' != typeof window ? f = window : 'undefined' != typeof global ? f = global : 'undefined' != typeof self && (f = self), f.debug = e();
|
|
}
|
|
}(function () {
|
|
var define, module, exports;
|
|
return function e(t, n, r) {
|
|
function s(o, u) {
|
|
if (!n[o]) {
|
|
if (!t[o]) {
|
|
var a = typeof require == 'function' && require;
|
|
if (!u && a)
|
|
return a(o, !0);
|
|
if (i)
|
|
return i(o, !0);
|
|
throw new Error('Cannot find module \'' + o + '\'');
|
|
}
|
|
var f = n[o] = { exports: {} };
|
|
t[o][0].call(f.exports, function (e) {
|
|
var n = t[o][1][e];
|
|
return s(n ? n : e);
|
|
}, f, f.exports, e, t, n, r);
|
|
}
|
|
return n[o].exports;
|
|
}
|
|
var i = typeof require == 'function' && require;
|
|
for (var o = 0; o < r.length; o++)
|
|
s(r[o]);
|
|
return s;
|
|
}({
|
|
1: [
|
|
function (_dereq_, module, exports) {
|
|
exports = module.exports = _dereq_('./debug');
|
|
exports.log = log;
|
|
exports.formatArgs = formatArgs;
|
|
exports.save = save;
|
|
exports.load = load;
|
|
exports.useColors = useColors;
|
|
exports.colors = [
|
|
'lightseagreen',
|
|
'forestgreen',
|
|
'goldenrod',
|
|
'dodgerblue',
|
|
'darkorchid',
|
|
'crimson'
|
|
];
|
|
function useColors() {
|
|
return 'WebkitAppearance' in document.documentElement.style || window.console && (console.firebug || console.exception && console.table);
|
|
}
|
|
exports.formatters.j = function (v) {
|
|
return JSON.stringify(v);
|
|
};
|
|
function formatArgs() {
|
|
var args = arguments;
|
|
var useColors = this.useColors;
|
|
args[0] = (useColors ? '%c' : '') + this.namespace + (useColors ? '%c ' : ' ') + args[0] + (useColors ? '%c ' : ' ') + '+' + exports.humanize(this.diff);
|
|
if (!useColors)
|
|
return args;
|
|
var c = 'color: ' + this.color;
|
|
args = [
|
|
args[0],
|
|
c,
|
|
''
|
|
].concat(Array.prototype.slice.call(args, 1));
|
|
var index = 0;
|
|
var lastC = 0;
|
|
args[0].replace(/%[a-z%]/g, function (match) {
|
|
if ('%%' === match)
|
|
return;
|
|
index++;
|
|
if ('%c' === match) {
|
|
lastC = index;
|
|
}
|
|
});
|
|
args.splice(lastC, 0, c);
|
|
return args;
|
|
}
|
|
function log() {
|
|
return 'object' == typeof console && 'function' == typeof console.log && Function.prototype.apply.call(console.log, console, arguments);
|
|
}
|
|
function save(namespaces) {
|
|
try {
|
|
if (null == namespaces) {
|
|
localStorage.removeItem('debug');
|
|
} else {
|
|
localStorage.debug = namespaces;
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
function load() {
|
|
var r;
|
|
try {
|
|
r = localStorage.debug;
|
|
} catch (e) {
|
|
}
|
|
return r;
|
|
}
|
|
exports.enable(load());
|
|
},
|
|
{ './debug': 2 }
|
|
],
|
|
2: [
|
|
function (_dereq_, module, exports) {
|
|
exports = module.exports = debug;
|
|
exports.coerce = coerce;
|
|
exports.disable = disable;
|
|
exports.enable = enable;
|
|
exports.enabled = enabled;
|
|
exports.humanize = _dereq_('ms');
|
|
exports.names = [];
|
|
exports.skips = [];
|
|
exports.formatters = {};
|
|
var prevColor = 0;
|
|
var prevTime;
|
|
function selectColor() {
|
|
return exports.colors[prevColor++ % exports.colors.length];
|
|
}
|
|
function debug(namespace) {
|
|
function disabled() {
|
|
}
|
|
disabled.enabled = false;
|
|
function enabled() {
|
|
var self = enabled;
|
|
var curr = +new Date();
|
|
var ms = curr - (prevTime || curr);
|
|
self.diff = ms;
|
|
self.prev = prevTime;
|
|
self.curr = curr;
|
|
prevTime = curr;
|
|
if (null == self.useColors)
|
|
self.useColors = exports.useColors();
|
|
if (null == self.color && self.useColors)
|
|
self.color = selectColor();
|
|
var args = Array.prototype.slice.call(arguments);
|
|
args[0] = exports.coerce(args[0]);
|
|
if ('string' !== typeof args[0]) {
|
|
args = ['%o'].concat(args);
|
|
}
|
|
var index = 0;
|
|
args[0] = args[0].replace(/%([a-z%])/g, function (match, format) {
|
|
if (match === '%%')
|
|
return match;
|
|
index++;
|
|
var formatter = exports.formatters[format];
|
|
if ('function' === typeof formatter) {
|
|
var val = args[index];
|
|
match = formatter.call(self, val);
|
|
args.splice(index, 1);
|
|
index--;
|
|
}
|
|
return match;
|
|
});
|
|
if ('function' === typeof exports.formatArgs) {
|
|
args = exports.formatArgs.apply(self, args);
|
|
}
|
|
var logFn = exports.log || enabled.log || console.log.bind(console);
|
|
logFn.apply(self, args);
|
|
}
|
|
enabled.enabled = true;
|
|
var fn = exports.enabled(namespace) ? enabled : disabled;
|
|
fn.namespace = namespace;
|
|
return fn;
|
|
}
|
|
function enable(namespaces) {
|
|
exports.save(namespaces);
|
|
var split = (namespaces || '').split(/[\s,]+/);
|
|
var len = split.length;
|
|
for (var i = 0; i < len; i++) {
|
|
if (!split[i])
|
|
continue;
|
|
namespaces = split[i].replace('*', '.*?');
|
|
if (namespaces[0] === '-') {
|
|
exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
|
|
} else {
|
|
exports.names.push(new RegExp('^' + namespaces + '$'));
|
|
}
|
|
}
|
|
}
|
|
function disable() {
|
|
exports.enable('');
|
|
}
|
|
function enabled(name) {
|
|
var i, len;
|
|
for (i = 0, len = exports.skips.length; i < len; i++) {
|
|
if (exports.skips[i].test(name)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (i = 0, len = exports.names.length; i < len; i++) {
|
|
if (exports.names[i].test(name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function coerce(val) {
|
|
if (val instanceof Error)
|
|
return val.stack || val.message;
|
|
return val;
|
|
}
|
|
},
|
|
{ 'ms': 3 }
|
|
],
|
|
3: [
|
|
function (_dereq_, module, exports) {
|
|
var s = 1000;
|
|
var m = s * 60;
|
|
var h = m * 60;
|
|
var d = h * 24;
|
|
var y = d * 365.25;
|
|
module.exports = function (val, options) {
|
|
options = options || {};
|
|
if ('string' == typeof val)
|
|
return parse(val);
|
|
return options.long ? long(val) : short(val);
|
|
};
|
|
function parse(str) {
|
|
var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);
|
|
if (!match)
|
|
return;
|
|
var n = parseFloat(match[1]);
|
|
var type = (match[2] || 'ms').toLowerCase();
|
|
switch (type) {
|
|
case 'years':
|
|
case 'year':
|
|
case 'y':
|
|
return n * y;
|
|
case 'days':
|
|
case 'day':
|
|
case 'd':
|
|
return n * d;
|
|
case 'hours':
|
|
case 'hour':
|
|
case 'h':
|
|
return n * h;
|
|
case 'minutes':
|
|
case 'minute':
|
|
case 'm':
|
|
return n * m;
|
|
case 'seconds':
|
|
case 'second':
|
|
case 's':
|
|
return n * s;
|
|
case 'ms':
|
|
return n;
|
|
}
|
|
}
|
|
function short(ms) {
|
|
if (ms >= d)
|
|
return Math.round(ms / d) + 'd';
|
|
if (ms >= h)
|
|
return Math.round(ms / h) + 'h';
|
|
if (ms >= m)
|
|
return Math.round(ms / m) + 'm';
|
|
if (ms >= s)
|
|
return Math.round(ms / s) + 's';
|
|
return ms + 'ms';
|
|
}
|
|
function long(ms) {
|
|
return plural(ms, d, 'day') || plural(ms, h, 'hour') || plural(ms, m, 'minute') || plural(ms, s, 'second') || ms + ' ms';
|
|
}
|
|
function plural(ms, n, name) {
|
|
if (ms < n)
|
|
return;
|
|
if (ms < n * 1.5)
|
|
return Math.floor(ms / n) + ' ' + name;
|
|
return Math.ceil(ms / n) + ' ' + name + 's';
|
|
}
|
|
},
|
|
{}
|
|
]
|
|
}, {}, [1])(1);
|
|
});
|
|
define('lib/setting-alias', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('setting-alias');
|
|
var forwardMethods = [
|
|
'filterOptions',
|
|
'resetOptions',
|
|
'supported',
|
|
'selected',
|
|
'select',
|
|
'next',
|
|
'get',
|
|
'set'
|
|
];
|
|
module.exports = SettingAlias;
|
|
function SettingAlias(options) {
|
|
debug('initialize');
|
|
this.key = options.key;
|
|
this.settings = options.settings;
|
|
this.current = options.get.bind(this);
|
|
forwardMethods.forEach(this.forward, this);
|
|
debug('initialized');
|
|
}
|
|
SettingAlias.prototype.each = function (fn) {
|
|
for (var key in this.settings) {
|
|
fn(this.settings[key]);
|
|
}
|
|
};
|
|
SettingAlias.prototype.is = function (key) {
|
|
return this.get('key') === key;
|
|
};
|
|
SettingAlias.prototype.forward = function (method) {
|
|
this[method] = function () {
|
|
var setting = this.current();
|
|
return setting[method].apply(setting, arguments);
|
|
};
|
|
};
|
|
SettingAlias.prototype.on = function (name, fn) {
|
|
var alias = this;
|
|
var wrapped = function () {
|
|
if (!alias.is(this.key)) {
|
|
return;
|
|
}
|
|
fn.apply(this, arguments);
|
|
};
|
|
this.each(function (setting) {
|
|
setting.on(name, wrapped);
|
|
});
|
|
fn._settingAliasCallback = wrapped;
|
|
};
|
|
SettingAlias.prototype.off = function (name, fn) {
|
|
var wrapped = fn._settingAliasCallback;
|
|
this.each(function (setting) {
|
|
setting.off(name, wrapped);
|
|
});
|
|
delete fn._settingAliasCallback;
|
|
};
|
|
});
|
|
;
|
|
(function () {
|
|
var proto = Events.prototype;
|
|
var slice = [].slice;
|
|
function Events(obj) {
|
|
if (!(this instanceof Events))
|
|
return new Events(obj);
|
|
if (obj)
|
|
return mixin(obj, proto);
|
|
}
|
|
proto.on = function (name, cb) {
|
|
this._cbs = this._cbs || {};
|
|
(this._cbs[name] || (this._cbs[name] = [])).push(cb);
|
|
return this;
|
|
};
|
|
proto.once = function (name, cb) {
|
|
this.on(name, one);
|
|
function one() {
|
|
cb.apply(this, arguments);
|
|
this.off(name, one);
|
|
}
|
|
};
|
|
proto.off = function (name, cb) {
|
|
this._cbs = this._cbs || {};
|
|
if (!name) {
|
|
this._cbs = {};
|
|
return;
|
|
}
|
|
if (!cb) {
|
|
return delete this._cbs[name];
|
|
}
|
|
var cbs = this._cbs[name] || [];
|
|
var i;
|
|
while (cbs && ~(i = cbs.indexOf(cb))) {
|
|
cbs.splice(i, 1);
|
|
}
|
|
return this;
|
|
};
|
|
proto.fire = proto.emit = function (options) {
|
|
var cbs = this._cbs = this._cbs || {};
|
|
var name = options.name || options;
|
|
var batch = (cbs[name] || []).concat(cbs['*'] || []);
|
|
var ctx = options.ctx || this;
|
|
if (batch.length) {
|
|
this._fireArgs = arguments;
|
|
var args = slice.call(arguments, 1);
|
|
while (batch.length) {
|
|
batch.shift().apply(ctx, args);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
proto.firer = function (name) {
|
|
var self = this;
|
|
return function () {
|
|
var args = slice.call(arguments);
|
|
args.unshift(name);
|
|
self.fire.apply(self, args);
|
|
};
|
|
};
|
|
function mixin(a, b) {
|
|
for (var key in b)
|
|
a[key] = b[key];
|
|
return a;
|
|
}
|
|
if (typeof exports === 'object') {
|
|
module.exports = Events;
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
define('evt', [], function () {
|
|
return Events;
|
|
});
|
|
} else {
|
|
window.evt = Events;
|
|
}
|
|
}());
|
|
(function (define) {
|
|
define('model', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'evt'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var events = require('evt');
|
|
module.exports = Model;
|
|
function Model(obj) {
|
|
if (!(this instanceof Model)) {
|
|
return mix(obj, Model.prototype);
|
|
}
|
|
this.reset(obj, { silent: true });
|
|
this.id = obj.id || obj.key;
|
|
}
|
|
Model.prototype = events({
|
|
get: function (key) {
|
|
var data = this._getData();
|
|
return arguments.length ? data[key] : mix({}, data);
|
|
},
|
|
set: function (key, value, options) {
|
|
options = typeof key === 'object' ? value : options;
|
|
var silent = options && options.silent;
|
|
var data = this._getData();
|
|
var keys;
|
|
switch (typeof key) {
|
|
case 'string':
|
|
data[key] = value;
|
|
if (!silent) {
|
|
this.onKeyChange(key);
|
|
this.emit('change', [key]);
|
|
}
|
|
return;
|
|
case 'object':
|
|
mix(data, key);
|
|
if (!silent) {
|
|
keys = Object.keys(key);
|
|
keys.forEach(this.onKeyChange, this);
|
|
this.emit('change', keys);
|
|
}
|
|
return;
|
|
}
|
|
},
|
|
setter: function (key, value1) {
|
|
return function (value2) {
|
|
this.set(key, value1 || value2);
|
|
}.bind(this);
|
|
},
|
|
reset: function (data, options) {
|
|
if (!data) {
|
|
return;
|
|
}
|
|
var silent = options && options.silent;
|
|
var isArray = data instanceof Array;
|
|
this._data = !isArray ? mix({}, data) : data;
|
|
if (!silent) {
|
|
this.emit('reset');
|
|
}
|
|
},
|
|
onKeyChange: function (key) {
|
|
var data = this._getData();
|
|
this.emit('change:' + key, data[key]);
|
|
},
|
|
_getData: function () {
|
|
this._data = this._data || {};
|
|
return this._data;
|
|
}
|
|
});
|
|
function mix(a, b) {
|
|
for (var key in b) {
|
|
a[key] = b[key];
|
|
}
|
|
return a;
|
|
}
|
|
});
|
|
}(function (n, w) {
|
|
return typeof define == 'function' && define.amd ? define : typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} }, r = function (n) {
|
|
return w[n];
|
|
};
|
|
w[n] = c(r, m.exports, m) || m.exports;
|
|
};
|
|
}('model', this)));
|
|
define('lib/setting', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'model'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('setting');
|
|
var model = require('model');
|
|
module.exports = Setting;
|
|
model(Setting.prototype);
|
|
function Setting(data) {
|
|
this.key = data.key;
|
|
this.storage = data.storage || localStorage;
|
|
this.reset(data, { silent: true });
|
|
this.select = this.select.bind(this);
|
|
this.next = this.next.bind(this);
|
|
this.configure(data);
|
|
if (data.persistent) {
|
|
this.on('change:selected', this.save);
|
|
}
|
|
}
|
|
Setting.prototype.configure = function (data) {
|
|
var optionsDefined = !!(data.options && data.options.length);
|
|
this.options = { defined: optionsDefined };
|
|
this.resetOptions(data.options);
|
|
};
|
|
Setting.prototype.resetOptions = function (list) {
|
|
list = list || [];
|
|
var hash = this.optionsToHash(list);
|
|
this.options.all = hash;
|
|
this.options.available = hash;
|
|
this.set('options', list, { silent: true });
|
|
this.updateSelected({ silent: true });
|
|
};
|
|
Setting.prototype.filterOptions = function (keys, options) {
|
|
var available = this.options.available = {};
|
|
var hash = this.options.all;
|
|
var filtered = [];
|
|
(keys || []).forEach(function (key) {
|
|
var option = hash[key];
|
|
if (option !== undefined) {
|
|
filtered.push(option);
|
|
available[key] = option;
|
|
}
|
|
});
|
|
this.sortByIndex(filtered);
|
|
this.set('options', filtered, options);
|
|
this.updateSelected({ silent: true });
|
|
};
|
|
Setting.prototype.optionsToHash = function (options) {
|
|
var hash = {};
|
|
options.forEach(function (option, index) {
|
|
option.index = index;
|
|
hash[option.key] = option;
|
|
});
|
|
return hash;
|
|
};
|
|
Setting.prototype.selected = function (key) {
|
|
var hash = this.options.available;
|
|
var option = hash[this.get('selected')];
|
|
return key ? option && option[key] : option;
|
|
};
|
|
Setting.prototype.select = function (key, options) {
|
|
var isIndex = typeof key === 'number';
|
|
var list = this.get('options');
|
|
var available = this.options.available;
|
|
var selected = isIndex ? list[key] : available[key];
|
|
if (!list.length) {
|
|
return;
|
|
}
|
|
if (!selected) {
|
|
return this.select(0, options);
|
|
}
|
|
this.set('selected', selected.key, options);
|
|
};
|
|
Setting.prototype.sortByIndex = function (list) {
|
|
return list.sort(function (a, b) {
|
|
return a.index - b.index;
|
|
});
|
|
};
|
|
Setting.prototype.updateSelected = function (options) {
|
|
this.select(this.get('selected') || this.fetched, options);
|
|
};
|
|
Setting.prototype.next = function () {
|
|
var options = this.get('options');
|
|
var selected = this.selected();
|
|
var index = options.indexOf(selected);
|
|
var newIndex = (index + 1) % options.length;
|
|
this.select(newIndex);
|
|
debug('set \'%s\' to index: %s', this.key, newIndex);
|
|
};
|
|
Setting.prototype.save = function () {
|
|
var selected = this.get('selected');
|
|
debug('saving key: %s, selected: %s', this.key, selected);
|
|
this.storage.setItem('setting:' + this.key, selected);
|
|
debug('saved key: %s', selected);
|
|
};
|
|
Setting.prototype.fetch = function () {
|
|
if (!this.get('persistent')) {
|
|
return;
|
|
}
|
|
debug('fetch value key: %s', this.key);
|
|
this.fetched = this.storage.getItem('setting:' + this.key);
|
|
debug('fetched %s value: %s', this.key, this.fetched);
|
|
if (this.fetched) {
|
|
this.select(this.fetched, { silent: true });
|
|
}
|
|
};
|
|
Setting.prototype.supported = function () {
|
|
return this.enabled() && !!this.get('options').length;
|
|
};
|
|
Setting.prototype.enabled = function () {
|
|
return !this.get('disabled');
|
|
};
|
|
});
|
|
define('lib/settings', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'./setting-alias',
|
|
'debug',
|
|
'./setting'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var SettingAlias = require('./setting-alias');
|
|
var debug = require('debug')('settings');
|
|
var Setting = require('./setting');
|
|
module.exports = Settings;
|
|
function Settings(items) {
|
|
this.ids = {};
|
|
this.items = [];
|
|
this.aliases = {};
|
|
this.SettingAlias = SettingAlias;
|
|
this.dontSave = this.dontSave.bind(this);
|
|
this.addEach(items);
|
|
}
|
|
Settings.prototype.addEach = function (items) {
|
|
if (!items) {
|
|
return;
|
|
}
|
|
var item;
|
|
var key;
|
|
for (key in items) {
|
|
item = items[key];
|
|
item.key = item.key || key;
|
|
this.add(items[key]);
|
|
}
|
|
};
|
|
Settings.prototype.add = function (data) {
|
|
var setting = new Setting(data);
|
|
this.items.push(setting);
|
|
this.ids[setting.key] = this[setting.key] = setting;
|
|
debug('added setting: %s', setting.key);
|
|
};
|
|
Settings.prototype.fetch = function () {
|
|
this.items.forEach(function (setting) {
|
|
setting.fetch();
|
|
});
|
|
};
|
|
Settings.prototype.dontSave = function () {
|
|
this.items.forEach(function (setting) {
|
|
setting.off('change:selected', setting.save);
|
|
});
|
|
};
|
|
Settings.prototype.alias = function (options) {
|
|
var alias = new this.SettingAlias(options);
|
|
this.aliases[options.key] = alias;
|
|
this[options.key] = alias;
|
|
};
|
|
Settings.prototype.removeAlias = function (key) {
|
|
};
|
|
});
|
|
define('lib/geo-location', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('geolocation');
|
|
module.exports = GeoLocation;
|
|
function GeoLocation() {
|
|
this.watcher = null;
|
|
this.position = null;
|
|
this.setPosition = this.setPosition.bind(this);
|
|
this.watch = this.watch.bind(this);
|
|
}
|
|
GeoLocation.prototype.watch = function () {
|
|
if (!this.watcher) {
|
|
this.watcher = navigator.geolocation.watchPosition(this.setPosition);
|
|
debug('started watching');
|
|
}
|
|
};
|
|
GeoLocation.prototype.stopWatching = function () {
|
|
navigator.geolocation.clearWatch(this.watcher);
|
|
this.watcher = null;
|
|
debug('stopped watching');
|
|
};
|
|
GeoLocation.prototype.setPosition = function (position) {
|
|
this.position = {
|
|
timestamp: position.timestamp,
|
|
altitude: position.coords.altitude,
|
|
latitude: position.coords.latitude,
|
|
longitude: position.coords.longitude
|
|
};
|
|
};
|
|
});
|
|
define('config/config', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
module.exports = {
|
|
globals: {
|
|
CONFIG_MAX_IMAGE_PIXEL_SIZE: 24 * 1024 * 1024,
|
|
CONFIG_MAX_SNAPSHOT_PIXEL_SIZE: 24 * 1024 * 1024,
|
|
CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH: 0,
|
|
CONFIG_REQUIRED_EXIF_PREVIEW_HEIGHT: 0
|
|
},
|
|
zoom: {
|
|
disabled: false,
|
|
useZoomPreviewAdjustment: false
|
|
},
|
|
focus: {
|
|
continuousAutoFocus: true,
|
|
touchFocus: true,
|
|
faceDetection: true
|
|
},
|
|
previewGallery: {
|
|
thumbnailWidth: 54,
|
|
thumbnailHeight: 54
|
|
},
|
|
viewfinder: {
|
|
scaleType: 'fill',
|
|
zoomGestureSensitivity: 0.425
|
|
},
|
|
battery: {
|
|
levels: {
|
|
low: 15,
|
|
verylow: 10,
|
|
critical: 6,
|
|
shutdown: 5,
|
|
healthy: 100
|
|
}
|
|
},
|
|
sounds: {
|
|
list: [
|
|
{
|
|
name: 'shutter',
|
|
url: './resources/sounds/shutter.opus',
|
|
setting: 'camera.sound.enabled'
|
|
},
|
|
{
|
|
name: 'countdown',
|
|
url: './resources/sounds/countdown.opus',
|
|
setting: 'camera.sound.enabled'
|
|
},
|
|
{
|
|
name: 'recordingStart',
|
|
url: './resources/sounds/camcorder_start.opus',
|
|
setting: 'camera.sound.enabled'
|
|
},
|
|
{
|
|
name: 'recordingEnd',
|
|
url: './resources/sounds/camcorder_end.opus',
|
|
setting: 'camera.sound.enabled'
|
|
}
|
|
]
|
|
},
|
|
keyDownEvents: {
|
|
camera: 'capture',
|
|
volumeup: 'capture',
|
|
volumedown: 'capture',
|
|
mozcamerafocusadjust: 'focus'
|
|
},
|
|
activity: {
|
|
maxPixelSizeScaleFactor: 2.5,
|
|
maxPickPixelSize: 0,
|
|
maxSharePixelSize: 0
|
|
},
|
|
spinnerTimeouts: {
|
|
takingPicture: 1650,
|
|
requestingCamera: 850,
|
|
loadingVideo: 100,
|
|
resizingImage: 100,
|
|
lazyLoading: 300
|
|
},
|
|
mode: {
|
|
options: [
|
|
{ key: 'picture' },
|
|
{ key: 'video' }
|
|
],
|
|
persistent: false
|
|
},
|
|
isoModes: {
|
|
disabled: false,
|
|
options: [{ key: 'auto' }],
|
|
selected: 'auto'
|
|
},
|
|
whiteBalance: {
|
|
disabled: false,
|
|
options: [{ key: 'auto' }],
|
|
selected: 'auto'
|
|
},
|
|
cameras: {
|
|
options: [
|
|
{
|
|
key: 'back',
|
|
icon: 'toggle-camera-rear',
|
|
title: 'toggle-camera-rear'
|
|
},
|
|
{
|
|
key: 'front',
|
|
icon: 'toggle-camera-front',
|
|
title: 'toggle-camera-front'
|
|
}
|
|
],
|
|
persistent: false
|
|
},
|
|
pictureSizesFront: {
|
|
title: 'camera-resolution',
|
|
header: 'camera-resolution-header',
|
|
icon: 'picture-size',
|
|
options: [],
|
|
include: { aspects: ['4:3'] },
|
|
persistent: true,
|
|
optionsLocalizable: false
|
|
},
|
|
pictureSizesBack: {
|
|
title: 'camera-resolution',
|
|
header: 'camera-resolution-header',
|
|
icon: 'picture-size',
|
|
options: [],
|
|
include: { aspects: ['4:3'] },
|
|
persistent: true,
|
|
optionsLocalizable: false
|
|
},
|
|
recorderProfilesBack: {
|
|
title: 'video-resolution',
|
|
header: 'video-resolution-header',
|
|
icon: 'video-size',
|
|
exclude: [
|
|
'low',
|
|
'high',
|
|
'default'
|
|
],
|
|
options: [],
|
|
persistent: true,
|
|
optionsLocalizable: false
|
|
},
|
|
recorderProfilesFront: {
|
|
title: 'video-resolution',
|
|
header: 'video-resolution-header',
|
|
icon: 'video-size',
|
|
exclude: [
|
|
'low',
|
|
'high',
|
|
'default'
|
|
],
|
|
options: [],
|
|
persistent: true,
|
|
optionsLocalizable: false
|
|
},
|
|
flashModesPicture: {
|
|
title: 'flash',
|
|
options: [
|
|
{
|
|
key: 'auto',
|
|
icon: 'flash-auto',
|
|
title: 'flash-auto'
|
|
},
|
|
{
|
|
key: 'on',
|
|
icon: 'flash-on',
|
|
title: 'flash-on'
|
|
},
|
|
{
|
|
key: 'off',
|
|
icon: 'flash-off',
|
|
title: 'flash-off'
|
|
}
|
|
],
|
|
persistent: true
|
|
},
|
|
flashModesVideo: {
|
|
title: 'flash',
|
|
options: [
|
|
{
|
|
key: 'off',
|
|
icon: 'flash-off',
|
|
title: 'flash-off'
|
|
},
|
|
{
|
|
key: 'torch',
|
|
icon: 'flash-on',
|
|
title: 'flash-on'
|
|
}
|
|
],
|
|
persistent: true
|
|
},
|
|
countdown: {
|
|
title: 'self-timer',
|
|
header: 'self-timer-header',
|
|
icon: 'self-timer',
|
|
options: [
|
|
{
|
|
key: 'off',
|
|
title: 'self-timer-off',
|
|
value: 0
|
|
},
|
|
{
|
|
key: 'secs2',
|
|
value: 2,
|
|
title: 'self-timer-2-seconds'
|
|
},
|
|
{
|
|
key: 'secs5',
|
|
value: 5,
|
|
title: 'self-timer-5-seconds'
|
|
},
|
|
{
|
|
key: 'secs10',
|
|
value: 10,
|
|
title: 'self-timer-10-seconds'
|
|
}
|
|
],
|
|
persistent: false
|
|
},
|
|
hdr: {
|
|
title: 'hdr',
|
|
header: 'hdr-header',
|
|
icon: 'hdr-boxed',
|
|
disabled: false,
|
|
options: [
|
|
{
|
|
key: 'off',
|
|
title: 'hdr-off'
|
|
},
|
|
{
|
|
key: 'on',
|
|
title: 'hdr-on'
|
|
}
|
|
],
|
|
persistent: true
|
|
},
|
|
scene: {
|
|
title: 'scene-mode',
|
|
header: 'scene-mode-header',
|
|
icon: 'scene',
|
|
options: [
|
|
{
|
|
key: 'normal',
|
|
title: 'scene-mode-normal'
|
|
},
|
|
{
|
|
key: 'pano',
|
|
title: 'scene-mode-panorama'
|
|
},
|
|
{
|
|
key: 'beauty',
|
|
title: 'scene-mode-beauty'
|
|
}
|
|
],
|
|
persistent: true
|
|
},
|
|
grid: {
|
|
title: 'grid',
|
|
header: 'grid-header',
|
|
icon: 'grid-circular',
|
|
options: [
|
|
{
|
|
key: 'off',
|
|
title: 'grid-off'
|
|
},
|
|
{
|
|
key: 'on',
|
|
title: 'grid-on'
|
|
}
|
|
],
|
|
selected: 'off',
|
|
persistent: true,
|
|
notifications: false
|
|
},
|
|
settingsMenu: {
|
|
items: [
|
|
{ key: 'hdr' },
|
|
{ key: 'countdown' },
|
|
{ key: 'pictureSizes' },
|
|
{ key: 'recorderProfiles' },
|
|
{ key: 'grid' }
|
|
]
|
|
}
|
|
};
|
|
});
|
|
define('lib/camera-utils', ['require'], function (require) {
|
|
'use strict';
|
|
var CameraUtils = {};
|
|
CameraUtils.scaleSizeToFitViewport = function (viewportSize, imageSize) {
|
|
var sw = viewportSize.width / imageSize.width, sh = viewportSize.height / imageSize.height, scale;
|
|
scale = Math.min(sw, sh);
|
|
return {
|
|
width: imageSize.width * scale,
|
|
height: imageSize.height * scale
|
|
};
|
|
};
|
|
CameraUtils.scaleSizeToFillViewport = function (viewportSize, imageSize) {
|
|
var sw = viewportSize.width / imageSize.width, sh = viewportSize.height / imageSize.height, scale;
|
|
scale = Math.max(sw, sh);
|
|
return {
|
|
width: imageSize.width * scale,
|
|
height: imageSize.height * scale
|
|
};
|
|
};
|
|
CameraUtils.getMaximumPreviewSize = function (previewSizes, aspectRatio) {
|
|
const ASPECT_TOLERANCE = 0.001;
|
|
var maximumArea = 0;
|
|
var maximumPreviewSize = null;
|
|
previewSizes.forEach(function (previewSize) {
|
|
var area = previewSize.width * previewSize.height;
|
|
if (aspectRatio) {
|
|
var ratio = previewSize.width / previewSize.height;
|
|
if (Math.abs(ratio - aspectRatio) > ASPECT_TOLERANCE) {
|
|
return;
|
|
}
|
|
}
|
|
if (area > maximumArea) {
|
|
maximumArea = area;
|
|
maximumPreviewSize = previewSize;
|
|
}
|
|
});
|
|
return maximumPreviewSize;
|
|
};
|
|
return CameraUtils;
|
|
});
|
|
(function (define) {
|
|
define('device-orientation', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
const ORIENTATION_CHANGE_INTERVAL = 300;
|
|
const MAX_MOTION_FILTER_TIME = 1000;
|
|
const MOTION_FILTER_TIME_CONSTANT = 200;
|
|
var lastMotionFilteredTime = 0;
|
|
var lastMotionData = {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
t: 0
|
|
};
|
|
var pendingOrientation = null;
|
|
var orientationChangeTimer = 0;
|
|
var eventListeners = { 'orientation': [] };
|
|
function applyFilter(x, y, z) {
|
|
var now = new Date().getTime();
|
|
var filterReset = false;
|
|
if (now > lastMotionData.t + MAX_MOTION_FILTER_TIME) {
|
|
lastMotionData.x = 0;
|
|
lastMotionData.y = 0;
|
|
lastMotionData.z = 0;
|
|
filterReset = true;
|
|
}
|
|
if (lastMotionData.x || lastMotionData.y || lastMotionData.z) {
|
|
var diff = now - lastMotionFilteredTime;
|
|
var alpha = diff / (MOTION_FILTER_TIME_CONSTANT + diff);
|
|
x = alpha * (x - lastMotionData.x) + lastMotionData.x;
|
|
y = alpha * (y - lastMotionData.y) + lastMotionData.y;
|
|
z = alpha * (z - lastMotionData.z) + lastMotionData.z;
|
|
}
|
|
lastMotionData.x = x;
|
|
lastMotionData.y = y;
|
|
lastMotionData.z = z;
|
|
lastMotionData.t = now;
|
|
return filterReset;
|
|
}
|
|
function calcOrientation(x, y) {
|
|
var orientationAngle = Math.atan2(-x, y) * 180 / Math.PI;
|
|
if (orientationAngle < 0) {
|
|
orientationAngle += 360;
|
|
}
|
|
var orientation = ((orientationAngle + 45) / 90 >> 0) % 4 * 90;
|
|
return orientation;
|
|
}
|
|
function handleMotionEvent(e) {
|
|
if (!e.accelerationIncludingGravity) {
|
|
return;
|
|
}
|
|
var filterReset = applyFilter(e.accelerationIncludingGravity.x, e.accelerationIncludingGravity.y, e.accelerationIncludingGravity.z);
|
|
if (filterReset) {
|
|
return;
|
|
}
|
|
var x = lastMotionData.x;
|
|
var y = lastMotionData.y;
|
|
var z = lastMotionData.z;
|
|
if (x * x + y * y + z * z > 110) {
|
|
return;
|
|
}
|
|
if (z > 9.2 || z < -9.2) {
|
|
return;
|
|
}
|
|
var orientation = calcOrientation(x, y);
|
|
if (orientation === pendingOrientation) {
|
|
return;
|
|
}
|
|
if (orientationChangeTimer) {
|
|
window.clearTimeout(orientationChangeTimer);
|
|
}
|
|
if (pendingOrientation === null) {
|
|
pendingOrientation = orientation;
|
|
fireOrientationChangeEvent(pendingOrientation);
|
|
} else {
|
|
pendingOrientation = orientation;
|
|
orientationChangeTimer = window.setTimeout(function doOrient() {
|
|
fireOrientationChangeEvent(pendingOrientation);
|
|
orientationChangeTimer = 0;
|
|
}, ORIENTATION_CHANGE_INTERVAL);
|
|
}
|
|
}
|
|
function start() {
|
|
pendingOrientation = null;
|
|
window.addEventListener('devicemotion', handleMotionEvent);
|
|
}
|
|
function stop() {
|
|
window.removeEventListener('devicemotion', handleMotionEvent);
|
|
if (orientationChangeTimer) {
|
|
clearTimeout(orientationChangeTimer);
|
|
orientationChangeTimer = 0;
|
|
}
|
|
}
|
|
function addEventListener(type, listener) {
|
|
if (eventListeners[type] && listener) {
|
|
eventListeners[type].push(listener);
|
|
}
|
|
}
|
|
function removeEventListener(type, listener) {
|
|
if (!eventListeners[type]) {
|
|
return;
|
|
}
|
|
var idx = eventListeners[type].indexOf(listener);
|
|
if (idx > -1) {
|
|
eventListeners.slice(idx, 1);
|
|
}
|
|
}
|
|
function fireOrientationChangeEvent(orientation) {
|
|
eventListeners.orientation.forEach(function (listener) {
|
|
if (listener.handleEvent) {
|
|
listener.handleEvent(orientation);
|
|
} else if (typeof listener === 'function') {
|
|
listener(orientation);
|
|
}
|
|
});
|
|
}
|
|
module.exports = {
|
|
start: start,
|
|
stop: stop,
|
|
on: addEventListener,
|
|
off: removeEventListener
|
|
};
|
|
});
|
|
}(function (n, w) {
|
|
return typeof define == 'function' && define.amd ? define : typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} }, r = function (n) {
|
|
return w[n];
|
|
};
|
|
w[n] = c(r, m.exports, m) || m.exports;
|
|
};
|
|
}('device-orientation', this)));
|
|
define('lib/orientation', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'device-orientation'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var listener = require('device-orientation');
|
|
var classes = document.body.classList;
|
|
var current = 0;
|
|
listener.on('orientation', onOrientationChange);
|
|
listener.start();
|
|
function onOrientationChange(degrees) {
|
|
classes.remove('deg' + current);
|
|
classes.add('deg' + degrees);
|
|
current = degrees;
|
|
}
|
|
function unlock() {
|
|
screen.mozUnlockOrientation();
|
|
listener.stop();
|
|
}
|
|
function lock() {
|
|
screen.mozLockOrientation('default');
|
|
listener.start();
|
|
}
|
|
function rationalize(sensorAngle, angle) {
|
|
if (typeof angle === 'undefined') {
|
|
angle = current;
|
|
}
|
|
var r = angle + sensorAngle;
|
|
if (r >= 0) {
|
|
r += 45;
|
|
} else {
|
|
r -= 45;
|
|
}
|
|
r /= 90;
|
|
if (r >= 0) {
|
|
r = Math.floor(r);
|
|
} else {
|
|
r = Math.ceil(r);
|
|
}
|
|
r %= 4;
|
|
r *= 90;
|
|
if (r < 0) {
|
|
r += 360;
|
|
}
|
|
return r;
|
|
}
|
|
module.exports = {
|
|
on: listener.on,
|
|
off: listener.off,
|
|
start: listener.start,
|
|
stop: listener.stop,
|
|
unlock: unlock,
|
|
lock: lock,
|
|
rationalize: rationalize,
|
|
get: function () {
|
|
return current;
|
|
}
|
|
};
|
|
});
|
|
define('lib/bind-all', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
module.exports = function (object) {
|
|
var key;
|
|
var fn;
|
|
for (key in object) {
|
|
fn = object[key];
|
|
if (typeof fn === 'function') {
|
|
object[key] = fn.bind(object);
|
|
}
|
|
}
|
|
};
|
|
});
|
|
define('lib/camera/focus', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'lib/bind-all'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var bindAll = require('lib/bind-all');
|
|
module.exports = Focus;
|
|
function Focus(options) {
|
|
bindAll(this);
|
|
this.userPreferences = options || {};
|
|
this.detectedFaces = [];
|
|
}
|
|
Focus.prototype.configure = function (mozCamera, mode) {
|
|
var focusModes = mozCamera.capabilities.focusModes;
|
|
var focusMode;
|
|
this.mozCamera = mozCamera;
|
|
this.configureFocusModes(mode);
|
|
if (this.continuousAutoFocus) {
|
|
if (mode === 'picture') {
|
|
focusMode = 'continuous-picture';
|
|
} else if (mode === 'video') {
|
|
focusMode = 'continuous-video';
|
|
}
|
|
}
|
|
focusMode = focusMode || 'auto';
|
|
if (focusModes.indexOf(focusMode) === -1) {
|
|
focusMode = focusModes[0];
|
|
}
|
|
this.suspendedMode = focusMode;
|
|
mozCamera.focusMode = focusMode;
|
|
this.reboot();
|
|
};
|
|
Focus.prototype.getMode = function () {
|
|
var mozCamera = this.mozCamera;
|
|
return this.suspendedMode || mozCamera.focusMode;
|
|
};
|
|
Focus.prototype.configureFocusModes = function (mode) {
|
|
var userPreferences = this.userPreferences;
|
|
var continuousAutoFocusUserEnabled = userPreferences.continuousAutoFocus !== false;
|
|
var touchFocusUserEnabled = userPreferences.touchFocus;
|
|
var touchFocusSupported = this.isTouchFocusSupported();
|
|
var faceDetectionUserEnabled = userPreferences.faceDetection;
|
|
var faceDetectionSupported = this.isFaceDetectionSupported();
|
|
this.continuousAutoFocus = continuousAutoFocusUserEnabled;
|
|
this.touchFocus = touchFocusUserEnabled && touchFocusSupported;
|
|
this.faceDetection = faceDetectionUserEnabled && faceDetectionSupported && mode === 'picture';
|
|
this.mozCamera.addEventListener('focus', this.onAutoFocusStateChange);
|
|
};
|
|
Focus.prototype.startFaceDetection = function () {
|
|
if (!this.faceDetection) {
|
|
return;
|
|
}
|
|
this.mozCamera.addEventListener('facesdetected', this.handleFaceDetectionEvent);
|
|
this.mozCamera.startFaceDetection();
|
|
};
|
|
Focus.prototype.stopFaceDetection = function () {
|
|
clearTimeout(this.faceDetectionSuspended);
|
|
clearTimeout(this.faceDetectionSuspensionTimer);
|
|
if (this.mozCamera.stopFaceDetection) {
|
|
this.mozCamera.removeEventListener('facesdetected', this.handleFaceDetectionEvent);
|
|
this.mozCamera.stopFaceDetection();
|
|
}
|
|
this.clearFaceDetection();
|
|
};
|
|
Focus.prototype.handleFaceDetectionEvent = function (e) {
|
|
this.focusOnLargestFace(e.faces);
|
|
};
|
|
Focus.prototype.clearFaceDetection = function () {
|
|
this.focusOnLargestFace([]);
|
|
};
|
|
Focus.prototype.suspendFaceDetection = function (ms, delay) {
|
|
if (!this.faceDetection) {
|
|
return;
|
|
}
|
|
var self = this;
|
|
delay = delay || 0;
|
|
clearTimeout(this.faceDetectionSuspended);
|
|
clearTimeout(this.faceDetectionSuspensionTimer);
|
|
this.faceDetectionSuspensionTimer = setTimeout(suspendFaceDetection, delay);
|
|
function suspendFaceDetection() {
|
|
self.faceDetectionSuspended = setTimeout(clearTimer, ms);
|
|
}
|
|
function clearTimer() {
|
|
self.faceFocused = false;
|
|
self.faceDetectionSuspended = undefined;
|
|
}
|
|
};
|
|
Focus.prototype.stopContinuousFocus = function () {
|
|
var focusMode = this.mozCamera.focusMode;
|
|
clearTimeout(this.continuousModeTimer);
|
|
if (focusMode === 'continuous-picture' || focusMode === 'continuous-video') {
|
|
this.suspendedMode = this.mozCamera.focusMode;
|
|
this.mozCamera.focusMode = 'auto';
|
|
}
|
|
};
|
|
Focus.prototype.resumeContinuousFocus = function () {
|
|
this.mozCamera.focusMode = this.suspendedMode;
|
|
this.suspendedMode = null;
|
|
this.resetFocusAreas();
|
|
this.mozCamera.resumeContinuousFocus();
|
|
};
|
|
Focus.prototype.suspendContinuousFocus = function (ms) {
|
|
clearTimeout(this.continuousModeTimer);
|
|
this.stopContinuousFocus();
|
|
this.continuousModeTimer = setTimeout(this.resumeContinuousFocus, ms);
|
|
};
|
|
Focus.prototype.updateFocusState = function (state) {
|
|
if (this.focusState !== state && (this.focusState === 'focusing' || state === 'focusing')) {
|
|
this.focusState = state;
|
|
this.onAutoFocusChanged(state);
|
|
}
|
|
};
|
|
Focus.prototype.onAutoFocusStateChange = function (e) {
|
|
var state = e.newState;
|
|
if (state === 'unfocused') {
|
|
state = 'fail';
|
|
}
|
|
this.updateFocusState(state);
|
|
};
|
|
Focus.prototype.onAutoFocusChanged = function (state) {
|
|
};
|
|
Focus.prototype.onFacesDetected = function (faces) {
|
|
};
|
|
Focus.prototype.focusOnLargestFace = function (faces) {
|
|
if (this.faceDetectionSuspended) {
|
|
this.onFacesDetected([]);
|
|
return;
|
|
}
|
|
this.detectedFaces = faces;
|
|
this.onFacesDetected(this.detectedFaces);
|
|
};
|
|
Focus.prototype.focus = function (done) {
|
|
if (!this.mozCamera) {
|
|
return;
|
|
}
|
|
done = done || function () {
|
|
};
|
|
var self = this;
|
|
this.suspendContinuousFocus(10000);
|
|
if (this.mozCamera.focusMode !== 'auto') {
|
|
done();
|
|
return;
|
|
}
|
|
this.updateFocusState('focusing');
|
|
this.mozCamera.autoFocus().then(onSuccess, onError);
|
|
function onError(err) {
|
|
self.focused = false;
|
|
if (err.name === 'NS_ERROR_IN_PROGRESS') {
|
|
done('interrupted');
|
|
} else {
|
|
done('error');
|
|
}
|
|
}
|
|
function onSuccess(success) {
|
|
if (success) {
|
|
self.focused = true;
|
|
done('focused');
|
|
} else {
|
|
self.focused = false;
|
|
done('failed');
|
|
}
|
|
}
|
|
};
|
|
Focus.prototype.resetFocusAreas = function () {
|
|
if (!this.touchFocus) {
|
|
return;
|
|
}
|
|
this.mozCamera.setFocusAreas([]);
|
|
this.mozCamera.setMeteringAreas([]);
|
|
};
|
|
Focus.prototype.pause = function () {
|
|
if (this.paused) {
|
|
return;
|
|
}
|
|
this.stopContinuousFocus();
|
|
this.stopFaceDetection();
|
|
this.paused = true;
|
|
delete this.focusState;
|
|
};
|
|
Focus.prototype.resume = function () {
|
|
if (!this.paused) {
|
|
return;
|
|
}
|
|
this.resumeContinuousFocus();
|
|
this.startFaceDetection();
|
|
this.paused = false;
|
|
};
|
|
Focus.prototype.reboot = function () {
|
|
this.pause();
|
|
this.resume();
|
|
};
|
|
Focus.prototype.isTouchFocusSupported = function () {
|
|
var maxFocusAreas = this.mozCamera.capabilities.maxFocusAreas;
|
|
return maxFocusAreas > 0;
|
|
};
|
|
Focus.prototype.isFaceDetectionSupported = function () {
|
|
var cameraDetectsFaces = this.mozCamera.capabilities.maxDetectedFaces > 0;
|
|
var apiAvailable = !!this.mozCamera.startFaceDetection;
|
|
this.maxDetectedFaces = this.mozCamera.capabilities.maxDetectedFaces;
|
|
return cameraDetectsFaces && apiAvailable;
|
|
};
|
|
Focus.prototype.updateFocusArea = function (rect, done) {
|
|
done = done || function () {
|
|
};
|
|
if (!this.touchFocus) {
|
|
done('touchToFocusNotAvailable');
|
|
return;
|
|
}
|
|
this.updateFocusState('focusing');
|
|
this.stopContinuousFocus();
|
|
this.suspendFaceDetection(10000);
|
|
this.mozCamera.setFocusAreas([rect]);
|
|
this.mozCamera.setMeteringAreas([rect]);
|
|
this.focus(done);
|
|
};
|
|
});
|
|
define('lib/debounce', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
module.exports = function (func, wait, immediate) {
|
|
var timeout;
|
|
return function () {
|
|
var context = this, args = arguments;
|
|
var later = function () {
|
|
timeout = null;
|
|
if (!immediate) {
|
|
func.apply(context, args);
|
|
}
|
|
};
|
|
var callNow = immediate && !timeout;
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
if (callNow) {
|
|
func.apply(context, args);
|
|
}
|
|
};
|
|
};
|
|
});
|
|
define('lib/mixin', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
module.exports = function (a, b) {
|
|
for (var key in b) {
|
|
a[key] = b[key];
|
|
}
|
|
return a;
|
|
};
|
|
});
|
|
define('lib/camera/camera', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'lib/camera-utils',
|
|
'lib/orientation',
|
|
'lib/camera/focus',
|
|
'debug',
|
|
'lib/debounce',
|
|
'lib/bind-all',
|
|
'lib/mixin',
|
|
'model'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var CameraUtils = require('lib/camera-utils');
|
|
var orientation = require('lib/orientation');
|
|
var Focus = require('lib/camera/focus');
|
|
var debug = require('debug')('camera');
|
|
var debounce = require('lib/debounce');
|
|
var bindAll = require('lib/bind-all');
|
|
var mix = require('lib/mixin');
|
|
var model = require('model');
|
|
model(Camera.prototype);
|
|
module.exports = Camera;
|
|
function Camera(options) {
|
|
debug('initializing');
|
|
bindAll(this);
|
|
options = options || {};
|
|
this.cacheConfig = options.cacheConfig !== false;
|
|
this.minRecordingTime = options.minRecordingTime || 1500;
|
|
this.recordSpacePadding = options.recordSpacePadding || 1024 * 1024 * 1;
|
|
this.recordSpaceMin = options.recordSpaceMin || 1024 * 1024 * 2;
|
|
this.requestAttempts = options.requestAttempts || 3;
|
|
this.orientation = options.orientation || orientation;
|
|
this.configStorage = options.configStorage || localStorage;
|
|
this.cameraList = navigator.mozCameras.getListOfCameras();
|
|
this.mozCamera = null;
|
|
this.storage = options.storage || {};
|
|
this.video = {
|
|
filepath: null,
|
|
minSpace: this.recordSpaceMin,
|
|
spacePadding: this.recordSpacePadding,
|
|
poster: { filepath: null }
|
|
};
|
|
this.focus = new Focus(options.focus);
|
|
this.suspendedFlashCount = 0;
|
|
this.mode = 'picture';
|
|
this.selectedCamera = 'back';
|
|
this.configure = debounce(this.configure);
|
|
debug('initialized');
|
|
}
|
|
Camera.prototype.load = function () {
|
|
debug('load camera');
|
|
var loadingNewCamera = this.selectedCamera !== this.lastLoadedCamera;
|
|
var self = this;
|
|
if (this.releasing) {
|
|
debug('wait for camera release');
|
|
this.once('released', function () {
|
|
self.load();
|
|
});
|
|
return;
|
|
}
|
|
if (this.mozCamera && !loadingNewCamera) {
|
|
debug('camera not changed');
|
|
this.ready();
|
|
return;
|
|
}
|
|
if (this.mozCamera) {
|
|
this.release(ready);
|
|
} else {
|
|
ready();
|
|
}
|
|
function ready() {
|
|
self.requestCamera(self.selectedCamera);
|
|
self.lastLoadedCamera = self.selectedCamera;
|
|
}
|
|
};
|
|
Camera.prototype.requestCamera = function (camera, config) {
|
|
if (!config) {
|
|
config = { mode: this.mode };
|
|
}
|
|
debug('request camera', camera, config);
|
|
if (this.isBusy) {
|
|
return;
|
|
}
|
|
var attempts = this.requestAttempts;
|
|
var self = this;
|
|
this.configured = false;
|
|
this.busy('requestingCamera');
|
|
request();
|
|
function request() {
|
|
navigator.mozCameras.getCamera(camera, config).then(onSuccess, onError);
|
|
self.emit('requesting');
|
|
debug('camera requested', camera, config);
|
|
attempts--;
|
|
}
|
|
function onSuccess(result) {
|
|
debug('successfully got mozCamera', result);
|
|
if (document.hidden) {
|
|
self.mozCamera = result.camera;
|
|
self.release();
|
|
return;
|
|
}
|
|
self.updateConfig(result.configuration);
|
|
self.setupNewCamera(result.camera);
|
|
self.configureFocus();
|
|
self.emit('focusconfigured', {
|
|
mode: self.mozCamera.focusMode,
|
|
touchFocus: self.focus.touchFocus,
|
|
faceDetection: self.focus.faceDetection,
|
|
maxDetectedFaces: self.focus.maxDetectedFaces
|
|
});
|
|
if (self.configured) {
|
|
self.emit('configured');
|
|
self.onPreviewStateChange({ newState: 'started' });
|
|
}
|
|
self.ready();
|
|
}
|
|
function onError(err) {
|
|
debug('error requesting camera', err);
|
|
if (err.name === 'NS_ERROR_NOT_INITIALIZED' && attempts) {
|
|
self.cameraRequestTimeout = setTimeout(request, 1000);
|
|
return;
|
|
}
|
|
self.emit('error', 'request-fail');
|
|
self.ready();
|
|
}
|
|
};
|
|
Camera.prototype.updateConfig = function (config) {
|
|
this.givenPreviewSize = config.previewSize;
|
|
this.pictureSize = config.pictureSize;
|
|
this.recorderProfile = config.recorderProfile;
|
|
this.configured = true;
|
|
};
|
|
Camera.prototype.setupNewCamera = function (mozCamera) {
|
|
debug('configuring camera');
|
|
var capabilities = mozCamera.capabilities;
|
|
this.mozCamera = mozCamera;
|
|
this.mozCamera.addEventListener('shutter', this.onShutter);
|
|
this.mozCamera.addEventListener('close', this.onClosed);
|
|
this.mozCamera.addEventListener('previewstatechange', this.onPreviewStateChange);
|
|
this.mozCamera.addEventListener('recorderstatechange', this.onRecorderStateChange);
|
|
this.mozCamera.addEventListener('poster', this.onPoster);
|
|
this.capabilities = this.formatCapabilities(capabilities);
|
|
this.emit('newcamera', {
|
|
capabilities: this.capabilities,
|
|
pictureSize: this.pictureSize,
|
|
recorderProfile: this.recorderProfile
|
|
});
|
|
debug('configured new camera');
|
|
};
|
|
Camera.prototype.formatCapabilities = function (capabilities) {
|
|
var hasHDR = capabilities.sceneModes.indexOf('hdr') > -1;
|
|
var hdr = hasHDR ? [
|
|
'on',
|
|
'off'
|
|
] : undefined;
|
|
return mix({ hdr: hdr }, capabilities);
|
|
};
|
|
Camera.prototype.configure = function () {
|
|
debug('configuring hardware...');
|
|
var self = this;
|
|
this.configured = false;
|
|
if (this.isBusy) {
|
|
debug('defering configuration');
|
|
this.once('ready', this.configure);
|
|
return;
|
|
}
|
|
if (!this.mozCamera) {
|
|
debug('no mozCamera');
|
|
return;
|
|
}
|
|
this.stopRecording();
|
|
this.busy();
|
|
var mozCameraConfig = {
|
|
mode: this.mode,
|
|
pictureSize: this.pictureSize,
|
|
recorderProfile: this.recorderProfile
|
|
};
|
|
this.mozCamera.setConfiguration(mozCameraConfig).then(onSuccess, onError);
|
|
debug('mozCamera configuring', mozCameraConfig);
|
|
function onSuccess(config) {
|
|
debug('configuration success');
|
|
if (!self.mozCamera) {
|
|
return;
|
|
}
|
|
self.updateConfig(config);
|
|
self.configureFocus();
|
|
self.emit('configured');
|
|
self.ready();
|
|
}
|
|
function onError(err) {
|
|
debug('Error configuring camera');
|
|
self.configured = true;
|
|
self.ready();
|
|
}
|
|
};
|
|
Camera.prototype.configureFocus = function () {
|
|
this.focus.configure(this.mozCamera, this.mode);
|
|
this.focus.onFacesDetected = this.onFacesDetected;
|
|
this.focus.onAutoFocusChanged = this.onAutoFocusChanged;
|
|
};
|
|
Camera.prototype.shutdown = function () {
|
|
this.stopRecording();
|
|
this.set('previewActive', false);
|
|
this.set('focus', 'none');
|
|
this.release();
|
|
};
|
|
Camera.prototype.onAutoFocusChanged = function (state) {
|
|
this.set('focus', state);
|
|
};
|
|
Camera.prototype.onFacesDetected = function (faces) {
|
|
this.emit('facesdetected', faces);
|
|
};
|
|
Camera.prototype.loadStreamInto = function (videoElement) {
|
|
debug('loading stream into element');
|
|
if (!this.mozCamera) {
|
|
debug('error - `mozCamera` is undefined or null');
|
|
return;
|
|
}
|
|
if (!videoElement) {
|
|
debug('error - `videoElement` is undefined or null');
|
|
return;
|
|
}
|
|
var isCurrent = videoElement.mozSrcObject === this.mozCamera;
|
|
if (isCurrent) {
|
|
return debug('camera didn\'t change');
|
|
}
|
|
videoElement.mozSrcObject = this.mozCamera;
|
|
videoElement.play();
|
|
debug('stream loaded into video');
|
|
};
|
|
Camera.prototype.previewSizes = function () {
|
|
if (!this.mozCamera) {
|
|
return;
|
|
}
|
|
return this.mozCamera.capabilities.previewSizes;
|
|
};
|
|
Camera.prototype.previewSize = function () {
|
|
return this.givenPreviewSize;
|
|
};
|
|
Camera.prototype.resolution = function () {
|
|
switch (this.mode) {
|
|
case 'picture':
|
|
return this.pictureSize;
|
|
case 'video':
|
|
return this.getRecorderProfile().video;
|
|
}
|
|
};
|
|
Camera.prototype.setPictureSize = function (size, options) {
|
|
debug('set picture size', size);
|
|
if (!size) {
|
|
return;
|
|
}
|
|
var configure = !(options && options.configure === false);
|
|
if (this.isPictureSize(size)) {
|
|
debug('pictureSize didn\'t change');
|
|
return;
|
|
}
|
|
this.pictureSize = size;
|
|
if (configure) {
|
|
this.configure();
|
|
} else {
|
|
this.mozCamera.setPictureSize(size);
|
|
}
|
|
this.setThumbnailSize();
|
|
debug('pictureSize changed');
|
|
return this;
|
|
};
|
|
Camera.prototype.isPictureSize = function (size) {
|
|
if (!this.pictureSize) {
|
|
return false;
|
|
}
|
|
var sameWidth = size.width === this.pictureSize.width;
|
|
var sameHeight = size.height === this.pictureSize.height;
|
|
return sameWidth && sameHeight;
|
|
};
|
|
Camera.prototype.setRecorderProfile = function (key, options) {
|
|
debug('set recorderProfile: %s', key);
|
|
if (!key) {
|
|
return;
|
|
}
|
|
var configure = !(options && options.configure === false);
|
|
if (this.isRecorderProfile(key)) {
|
|
debug('recorderProfile didn\'t change');
|
|
return;
|
|
}
|
|
this.recorderProfile = key;
|
|
if (configure) {
|
|
this.configure();
|
|
}
|
|
debug('recorderProfile changed: %s', key);
|
|
return this;
|
|
};
|
|
Camera.prototype.isRecorderProfile = function (key) {
|
|
return key === this.recorderProfile;
|
|
};
|
|
Camera.prototype.getRecorderProfile = function () {
|
|
var key = this.recorderProfile;
|
|
return this.mozCamera.capabilities.recorderProfiles[key];
|
|
};
|
|
Camera.prototype.setThumbnailSize = function () {
|
|
var sizes = this.mozCamera.capabilities.thumbnailSizes;
|
|
var pictureSize = this.mozCamera.getPictureSize();
|
|
var picked = this.pickThumbnailSize(sizes, pictureSize);
|
|
if (picked) {
|
|
this.mozCamera.setThumbnailSize(picked);
|
|
}
|
|
};
|
|
Camera.prototype.setFlashMode = function (key) {
|
|
if (this.mozCamera) {
|
|
key = key || 'off';
|
|
if (this.suspendedFlashCount > 0) {
|
|
this.suspendedFlashMode = key;
|
|
debug('flash mode set while suspended: %s', key);
|
|
} else {
|
|
this.mozCamera.flashMode = key;
|
|
debug('flash mode set: %s', key);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
Camera.prototype.release = function (done) {
|
|
debug('release');
|
|
done = done || function () {
|
|
};
|
|
var self = this;
|
|
clearTimeout(this.cameraRequestTimeout);
|
|
if (!this.mozCamera) {
|
|
done();
|
|
return;
|
|
}
|
|
this.busy();
|
|
this.stopRecording();
|
|
this.set('focus', 'none');
|
|
this.mozCamera.release().then(onSuccess, onError);
|
|
this.releasing = true;
|
|
this.mozCamera = null;
|
|
delete this.pictureSize;
|
|
delete this.recorderProfile;
|
|
delete this.givenPreviewSize;
|
|
function onSuccess() {
|
|
debug('successfully released');
|
|
self.ready();
|
|
self.releasing = false;
|
|
self.emit('released');
|
|
done();
|
|
}
|
|
function onError(err) {
|
|
debug('failed to release hardware');
|
|
self.ready();
|
|
self.releasing = false;
|
|
done(err);
|
|
}
|
|
};
|
|
Camera.prototype.pickThumbnailSize = function (thumbnailSizes, pictureSize) {
|
|
var screenWidth = window.innerWidth * window.devicePixelRatio;
|
|
var screenHeight = window.innerHeight * window.devicePixelRatio;
|
|
var pictureAspectRatio = pictureSize.width / pictureSize.height;
|
|
var currentThumbnailSize;
|
|
var i;
|
|
thumbnailSizes = thumbnailSizes.slice(0);
|
|
if (!thumbnailSizes || !pictureSize) {
|
|
return;
|
|
}
|
|
function imageSizeFillsScreen(pixelsWidth, pixelsHeight) {
|
|
return (pixelsWidth >= screenWidth || pixelsHeight >= screenHeight) && (pixelsWidth >= screenHeight || pixelsHeight >= screenWidth);
|
|
}
|
|
thumbnailSizes = thumbnailSizes.filter(function (thumbnailSize) {
|
|
var thumbnailAspectRatio = thumbnailSize.width / thumbnailSize.height;
|
|
return Math.abs(thumbnailAspectRatio - pictureAspectRatio) < 0.05;
|
|
});
|
|
if (thumbnailSizes.length === 0) {
|
|
console.error('Error while selecting thumbnail size. ' + 'There are no thumbnail sizes that match the ratio of ' + 'the selected picture size: ' + JSON.stringify(pictureSize));
|
|
return;
|
|
}
|
|
thumbnailSizes.sort(function (a, b) {
|
|
return a.width * a.height - b.width * b.height;
|
|
});
|
|
for (i = 0; i < thumbnailSizes.length; ++i) {
|
|
currentThumbnailSize = thumbnailSizes[i];
|
|
if (imageSizeFillsScreen(currentThumbnailSize.width, currentThumbnailSize.height)) {
|
|
return currentThumbnailSize;
|
|
}
|
|
}
|
|
return thumbnailSizes[thumbnailSizes.length - 1];
|
|
};
|
|
Camera.prototype.capture = function (options) {
|
|
if (!this.mozCamera) {
|
|
return false;
|
|
}
|
|
switch (this.mode) {
|
|
case 'picture':
|
|
this.takePicture(options);
|
|
break;
|
|
case 'video':
|
|
this.toggleRecording(options);
|
|
break;
|
|
}
|
|
};
|
|
Camera.prototype.takePicture = function (options) {
|
|
debug('take picture', options);
|
|
this.busy();
|
|
var rotation = this.orientation.get();
|
|
var selectedCamera = this.selectedCamera;
|
|
var self = this;
|
|
var position = options && options.position;
|
|
var config = {
|
|
dateTime: Date.now() / 1000,
|
|
pictureSize: self.pictureSize,
|
|
fileFormat: 'jpeg'
|
|
};
|
|
if (position) {
|
|
config.position = position;
|
|
}
|
|
config.rotation = selectedCamera === 'front' ? -rotation : rotation;
|
|
if (this.focus.getMode() === 'auto') {
|
|
this.focus.focus(onFocused);
|
|
} else {
|
|
takePicture();
|
|
}
|
|
function onFocused(state) {
|
|
takePicture();
|
|
}
|
|
function takePicture() {
|
|
self.busy('takingPicture');
|
|
self.mozCamera.takePicture(config).then(onSuccess, onError);
|
|
}
|
|
function onError(error) {
|
|
if (error.name === 'NS_ERROR_IN_PROGRESS') {
|
|
complete();
|
|
} else {
|
|
document.l10n.formatValue('error-saving').then(value => {
|
|
alert(value);
|
|
complete();
|
|
});
|
|
}
|
|
}
|
|
function onSuccess(blob) {
|
|
var image = { blob: blob };
|
|
self.resumePreview();
|
|
self.set('focus', 'none');
|
|
self.emit('newimage', image);
|
|
debug('success taking picture');
|
|
complete();
|
|
}
|
|
function complete() {
|
|
self.set('focus', 'none');
|
|
self.ready();
|
|
}
|
|
};
|
|
Camera.prototype.updateFocusArea = function (rect, done) {
|
|
this.focus.updateFocusArea(rect, focusDone);
|
|
function focusDone(state) {
|
|
if (done) {
|
|
done(state);
|
|
}
|
|
}
|
|
};
|
|
Camera.prototype.toggleRecording = function (options) {
|
|
var state = this.get('recording');
|
|
if (state && state !== 'stopped') {
|
|
this.stopRecording();
|
|
} else {
|
|
this.startRecording(options);
|
|
}
|
|
};
|
|
Camera.prototype.setStorage = function (storage) {
|
|
this.storage.video = storage.video;
|
|
};
|
|
Camera.prototype.startRecording = function (options) {
|
|
debug('start recording');
|
|
var frontCamera = this.selectedCamera === 'front';
|
|
var rotation = this.orientation.get();
|
|
var storage = this.storage.video;
|
|
var video = this.video;
|
|
var self = this;
|
|
if (frontCamera) {
|
|
rotation = -rotation;
|
|
}
|
|
this.set('recording', 'starting');
|
|
this.busy();
|
|
this.orientation.stop();
|
|
var profile = this.resolution();
|
|
video.width = profile.width;
|
|
video.height = profile.height;
|
|
video.rotation = this.orientation.rationalize(this.getSensorAngle(), rotation);
|
|
var previewSize = this.previewSize();
|
|
video.poster.width = previewSize.width;
|
|
video.poster.height = previewSize.height;
|
|
video.poster.rotation = video.rotation;
|
|
this.getFreeVideoStorageSpace(gotStorageSpace);
|
|
function gotStorageSpace(err, freeBytes) {
|
|
if (self.get('recording') === 'stopping') {
|
|
debug('start recording interrupted (getFreeVideoStorageSpace)');
|
|
return self.stoppedRecording();
|
|
}
|
|
if (err) {
|
|
return self.onStartRecordingError();
|
|
}
|
|
var notEnoughSpace = freeBytes < self.video.minSpace;
|
|
var remaining = freeBytes - self.video.spacePadding;
|
|
var targetFileSize = self.get('maxFileSizeBytes');
|
|
var maxFileSizeBytes = targetFileSize || remaining;
|
|
if (notEnoughSpace) {
|
|
self.onStartRecordingError('nospace2');
|
|
return;
|
|
}
|
|
var config = {
|
|
rotation: rotation,
|
|
maxFileSizeBytes: maxFileSizeBytes,
|
|
createPoster: true
|
|
};
|
|
self.createVideoFilepath(createVideoFilepathDone);
|
|
function createVideoFilepathDone(errorMsg, filepath) {
|
|
if (self.get('recording') === 'stopping') {
|
|
debug('start recording interrupted (createVideoFilepath)');
|
|
return self.stoppedRecording();
|
|
}
|
|
if (typeof filepath === 'undefined') {
|
|
debug(errorMsg);
|
|
return self.onStartRecordingError('error-video-file-path');
|
|
}
|
|
video.filepath = filepath;
|
|
self.emit('willrecord');
|
|
self.mozCamera.startRecording(config, storage, filepath).then(onSuccess, onError);
|
|
}
|
|
}
|
|
function onError(err) {
|
|
storage.delete(video.filepath);
|
|
if (err.name === 'NS_ERROR_IN_PROGRESS') {
|
|
debug('start recording error (in progress)');
|
|
return;
|
|
}
|
|
if (err.name === 'NS_ERROR_ABORT') {
|
|
debug('start recording error (abort)');
|
|
return self.stoppedRecording();
|
|
}
|
|
self.onStartRecordingError();
|
|
}
|
|
function onSuccess() {
|
|
self.ready();
|
|
if (document.hidden) {
|
|
self.stopRecording();
|
|
}
|
|
}
|
|
};
|
|
Camera.prototype.startedRecording = function () {
|
|
debug('started recording');
|
|
this.startVideoTimer();
|
|
this.set('recording', 'started');
|
|
};
|
|
Camera.prototype.stopRecording = function () {
|
|
debug('stop recording');
|
|
var state = this.get('recording');
|
|
if (!state || state === 'stopping' || state === 'stopped' || state === 'error') {
|
|
debug('not recording or stop pending');
|
|
return;
|
|
}
|
|
this.set('recording', 'stopping');
|
|
this.busy();
|
|
this.mozCamera.stopRecording();
|
|
};
|
|
Camera.prototype.stoppedRecording = function (recorded) {
|
|
debug('stopped recording');
|
|
this.stopVideoTimer();
|
|
if (!recorded) {
|
|
this.set('recording', 'error');
|
|
}
|
|
this.set('recording', 'stopped');
|
|
this.orientation.start();
|
|
var self = this;
|
|
var video;
|
|
if (recorded) {
|
|
video = mix({}, this.video);
|
|
video.poster = mix({}, video.poster);
|
|
this.storage.video.get(video.filepath).then(function (blob) {
|
|
video.blob = blob;
|
|
self.emit('newvideo', video);
|
|
self.ready();
|
|
}, function () {
|
|
if (self) {
|
|
self.onStopRecordingError(video);
|
|
self = null;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
Camera.prototype.onRecordingError = function (id) {
|
|
if (id) {
|
|
document.l10n.formatValue(id).then(value => {
|
|
alert(value);
|
|
});
|
|
}
|
|
this.ready();
|
|
};
|
|
Camera.prototype.onStartRecordingError = function (id) {
|
|
debug('start record error');
|
|
this.stoppedRecording();
|
|
this.onRecordingError(id);
|
|
};
|
|
Camera.prototype.onStopRecordingError = function (video) {
|
|
debug('stop record error');
|
|
this.storage.video.delete(video.filepath);
|
|
var elapsedTime = this.get('videoElapsed');
|
|
if (elapsedTime < this.minRecordingTime) {
|
|
this.ready();
|
|
} else {
|
|
this.onRecordingError();
|
|
}
|
|
};
|
|
Camera.prototype.onShutter = function () {
|
|
this.emit('shutter');
|
|
};
|
|
Camera.prototype.onClosed = function (e) {
|
|
this.shutdown();
|
|
this.emit('closed', e.reason);
|
|
};
|
|
Camera.prototype.onPreviewStateChange = function (e) {
|
|
var state = e.newState;
|
|
debug('preview state change: %s', state);
|
|
if (state === this.previewState) {
|
|
return;
|
|
}
|
|
this.previewState = state;
|
|
this.emit('preview:' + state);
|
|
};
|
|
Camera.prototype.onRecorderStateChange = function (e) {
|
|
var msg = e.newState;
|
|
debug('recorder state change: %s', msg);
|
|
if (msg === 'FileSizeLimitReached') {
|
|
this.emit('filesizelimitreached');
|
|
} else if (msg === 'Started') {
|
|
this.startedRecording();
|
|
} else if (msg === 'Stopped') {
|
|
if (this.stopRecordError) {
|
|
this.onStopRecordingError(this.video);
|
|
}
|
|
this.stoppedRecording(!this.stopRecordError);
|
|
this.stopRecordError = false;
|
|
} else if (msg === 'PosterFailed' || msg === 'TrackFailed' || msg === 'MediaRecorderFailed' || msg === 'MediaServerFailed') {
|
|
this.stopRecordError = true;
|
|
this.stopRecording();
|
|
}
|
|
};
|
|
Camera.prototype.onPoster = function (e) {
|
|
debug('poster created');
|
|
this.video.poster.blob = e.data;
|
|
};
|
|
Camera.prototype.getFreeVideoStorageSpace = function (done) {
|
|
debug('get free storage space');
|
|
var storage = this.storage.video;
|
|
var req = storage.freeSpace();
|
|
req.onerror = onError;
|
|
req.onsuccess = onSuccess;
|
|
function onSuccess() {
|
|
var freeBytes = req.result;
|
|
debug('%d free space found', freeBytes);
|
|
done(null, freeBytes);
|
|
}
|
|
function onError() {
|
|
done('error');
|
|
}
|
|
};
|
|
Camera.prototype.createVideoFilepath = function (done) {
|
|
done(null, Date.now() + '_tmp.3gp');
|
|
};
|
|
Camera.prototype.resumePreview = function () {
|
|
this.mozCamera.resumePreview();
|
|
this.focus.startFaceDetection();
|
|
this.emit('previewresumed');
|
|
};
|
|
Camera.prototype.setCamera = function (camera) {
|
|
debug('set camera: %s', camera);
|
|
if (this.selectedCamera === camera) {
|
|
return;
|
|
}
|
|
this.selectedCamera = camera;
|
|
this.load();
|
|
};
|
|
Camera.prototype.setMode = function (mode) {
|
|
debug('setting mode to: %s', mode);
|
|
if (this.isMode(mode)) {
|
|
return;
|
|
}
|
|
this.mode = mode;
|
|
this.configure();
|
|
return this;
|
|
};
|
|
Camera.prototype.isMode = function (mode) {
|
|
return this.mode === mode;
|
|
};
|
|
Camera.prototype.startVideoTimer = function () {
|
|
this.set('videoStart', new Date().getTime());
|
|
this.videoTimer = setInterval(this.updateVideoElapsed, 1000);
|
|
this.updateVideoElapsed();
|
|
};
|
|
Camera.prototype.stopVideoTimer = function () {
|
|
clearInterval(this.videoTimer);
|
|
this.videoTimer = null;
|
|
this.updateVideoElapsed();
|
|
};
|
|
Camera.prototype.updateVideoElapsed = function () {
|
|
var now = new Date().getTime();
|
|
var start = this.get('videoStart');
|
|
this.set('videoElapsed', now - start);
|
|
};
|
|
Camera.prototype.setISOMode = function (value) {
|
|
var isoModes = this.mozCamera.capabilities.isoModes;
|
|
if (isoModes && isoModes.indexOf(value) > -1) {
|
|
this.mozCamera.isoMode = value;
|
|
}
|
|
};
|
|
Camera.prototype.setWhiteBalance = function (value) {
|
|
var capabilities = this.mozCamera.capabilities;
|
|
var modes = capabilities.whiteBalanceModes;
|
|
if (modes && modes.indexOf(value) > -1) {
|
|
this.mozCamera.whiteBalanceMode = value;
|
|
}
|
|
};
|
|
Camera.prototype.setHDR = function (value) {
|
|
debug('set hdr: %s', value);
|
|
if (!value) {
|
|
return;
|
|
}
|
|
var scene = value === 'on' ? 'hdr' : 'auto';
|
|
this.setSceneMode(scene);
|
|
};
|
|
Camera.prototype.setSceneMode = function (value) {
|
|
var modes = this.mozCamera.capabilities.sceneModes;
|
|
if (modes.indexOf(value) > -1) {
|
|
this.mozCamera.sceneMode = value;
|
|
}
|
|
};
|
|
Camera.prototype.isZoomSupported = function () {
|
|
if (!this.mozCamera) {
|
|
return false;
|
|
}
|
|
return this.mozCamera.capabilities.zoomRatios.length > 1;
|
|
};
|
|
Camera.prototype.configureZoom = function () {
|
|
var previewSize = this.previewSize();
|
|
var maxPreviewSize = CameraUtils.getMaximumPreviewSize(this.previewSizes());
|
|
var maxHardwareZoom = maxPreviewSize.width / previewSize.width;
|
|
this.set('maxHardwareZoom', maxHardwareZoom);
|
|
this.setZoom(this.getMinimumZoom());
|
|
this.emit('zoomconfigured', this.getZoom());
|
|
return this;
|
|
};
|
|
Camera.prototype.getMinimumZoom = function () {
|
|
var zoomRatios = this.mozCamera.capabilities.zoomRatios;
|
|
if (zoomRatios.length === 0) {
|
|
return 1;
|
|
}
|
|
return zoomRatios[0];
|
|
};
|
|
Camera.prototype.getMaximumZoom = function () {
|
|
var zoomRatios = this.mozCamera.capabilities.zoomRatios;
|
|
if (zoomRatios.length === 0) {
|
|
return 1;
|
|
}
|
|
return zoomRatios[zoomRatios.length - 1];
|
|
};
|
|
Camera.prototype.getZoom = function () {
|
|
return this.mozCamera.zoom;
|
|
};
|
|
Camera.prototype.setZoom = function (zoom) {
|
|
this.zoom = zoom;
|
|
this.emit('zoomchanged', this.zoom);
|
|
if (this.zoomChangeTimeout) {
|
|
return;
|
|
}
|
|
var self = this;
|
|
this.zoomChangeTimeout = window.setTimeout(function () {
|
|
self.zoomChangeTimeout = null;
|
|
self.mozCamera.zoom = self.zoom;
|
|
}, 150);
|
|
};
|
|
Camera.prototype.getZoomPreviewAdjustment = function () {
|
|
var zoom = this.mozCamera.zoom;
|
|
var maxHardwareZoom = this.get('maxHardwareZoom');
|
|
if (zoom <= maxHardwareZoom) {
|
|
return 1;
|
|
}
|
|
return zoom / maxHardwareZoom;
|
|
};
|
|
Camera.prototype.getSensorAngle = function () {
|
|
return this.mozCamera && this.mozCamera.sensorAngle;
|
|
};
|
|
Camera.prototype.busy = function (type) {
|
|
debug('busy %s', type || '');
|
|
this.isBusy = true;
|
|
this.emit('busy', type);
|
|
clearTimeout(this.readyTimeout);
|
|
};
|
|
Camera.prototype.ready = function () {
|
|
var self = this;
|
|
this.isBusy = false;
|
|
clearTimeout(this.readyTimeout);
|
|
this.readyTimeout = setTimeout(function () {
|
|
debug('ready');
|
|
self.emit('ready');
|
|
}, 150);
|
|
};
|
|
});
|
|
(function (exports) {
|
|
'use strict';
|
|
const stopRecordingKey = 'private.broadcast.stop_recording';
|
|
const attentionScreenKey = 'private.broadcast.attention_screen_opening';
|
|
function start() {
|
|
if (!document.hidden) {
|
|
listen();
|
|
}
|
|
window.addEventListener('visibilitychange', visibilityChangeHandler);
|
|
}
|
|
function stop() {
|
|
window.removeEventListener('visibilitychange', visibilityChangeHandler);
|
|
unlisten();
|
|
}
|
|
function visibilityChangeHandler() {
|
|
if (document.hidden) {
|
|
unlisten();
|
|
} else {
|
|
listen();
|
|
}
|
|
}
|
|
function listen() {
|
|
navigator.mozSettings.addObserver(stopRecordingKey, stopRecordingObserver);
|
|
navigator.mozSettings.addObserver(attentionScreenKey, stopRecordingObserver);
|
|
}
|
|
function unlisten() {
|
|
navigator.mozSettings.removeObserver(stopRecordingKey, stopRecordingObserver);
|
|
navigator.mozSettings.removeObserver(attentionScreenKey, stopRecordingObserver);
|
|
}
|
|
function stopRecordingObserver(event) {
|
|
if (event.settingValue) {
|
|
window.dispatchEvent(new CustomEvent('stoprecording'));
|
|
}
|
|
}
|
|
exports.StopRecordingEvent = {
|
|
start: start,
|
|
stop: stop
|
|
};
|
|
}(window));
|
|
define('stop-recording-event', function (global) {
|
|
return function () {
|
|
var ret, fn;
|
|
return ret || global.StopRecordingEvent;
|
|
};
|
|
}(this));
|
|
(function (define) {
|
|
define('view', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'evt'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var events = require('evt');
|
|
var counter = 1;
|
|
var noop = function () {
|
|
};
|
|
module.exports = View;
|
|
function View(options) {
|
|
options = options || {};
|
|
this.el = options.el || this.el || document.createElement(this.tag);
|
|
this.el.id = this.el.id || 'view' + counter++;
|
|
this.name = options.name || this.name;
|
|
this.els = {};
|
|
if (!this.el.className) {
|
|
if (this.name)
|
|
this.el.className = this.name;
|
|
if (this.className)
|
|
this.el.className += ' ' + this.className;
|
|
}
|
|
bindAll(this);
|
|
this.initialize.apply(this, arguments);
|
|
}
|
|
events(View.prototype);
|
|
View.prototype.fire = View.prototype.fire || View.prototype.emit;
|
|
View.prototype.tag = 'div';
|
|
View.prototype.name = 'noname';
|
|
View.prototype.appendTo = function (parent) {
|
|
if (!parent)
|
|
return this;
|
|
parent.appendChild(this.el);
|
|
this.fire('inserted');
|
|
return this;
|
|
};
|
|
View.prototype.prependTo = function (parent) {
|
|
if (!parent)
|
|
return this;
|
|
var first = parent.firstChild;
|
|
if (first)
|
|
parent.insertBefore(this.el, first);
|
|
else
|
|
this.appendTo(parent);
|
|
this.fire('inserted');
|
|
return this;
|
|
};
|
|
View.prototype.find = function (query) {
|
|
return this.el.querySelector(query);
|
|
};
|
|
View.prototype.remove = function (options) {
|
|
var silent = options && options.silent;
|
|
var parent = this.el.parentNode;
|
|
if (!parent)
|
|
return this;
|
|
parent.removeChild(this.el);
|
|
if (!silent)
|
|
this.fire('remove');
|
|
return this;
|
|
};
|
|
View.prototype.set = function (key, value) {
|
|
value = value === undefined ? '' : value;
|
|
this.el.setAttribute(toDashed(key), value);
|
|
};
|
|
View.prototype.get = function (key) {
|
|
return this.el.getAttribute(key);
|
|
};
|
|
View.prototype.setter = function (key, forced) {
|
|
var self = this;
|
|
return function (passed) {
|
|
var value = forced !== undefined ? forced : passed;
|
|
self.set(key, value);
|
|
};
|
|
};
|
|
View.prototype.enable = function (key, value) {
|
|
switch (arguments.length) {
|
|
case 0:
|
|
value = true;
|
|
key = 'enabled';
|
|
break;
|
|
case 1:
|
|
if (typeof key === 'boolean') {
|
|
value = key;
|
|
key = 'enabled';
|
|
} else {
|
|
value = true;
|
|
key = key ? key + '-enabled' : 'enabled';
|
|
}
|
|
break;
|
|
default:
|
|
key = key ? key + '-enabled' : 'enabled';
|
|
}
|
|
this.set(key, !!value);
|
|
};
|
|
View.prototype.disable = function (key) {
|
|
this.enable(key, false);
|
|
};
|
|
View.prototype.enabler = function (key) {
|
|
return function (value) {
|
|
this.enable(key, value);
|
|
}.bind(this);
|
|
};
|
|
View.prototype.hide = function (key) {
|
|
this.toggle(key, false);
|
|
};
|
|
View.prototype.show = function (key) {
|
|
this.toggle(key, true);
|
|
};
|
|
View.prototype.toggle = function (key, value) {
|
|
if (arguments.length === 1 && typeof key === 'boolean') {
|
|
value = key;
|
|
key = '';
|
|
} else {
|
|
key = key ? key + '-' : '';
|
|
}
|
|
this.el.classList.toggle(key + 'hidden', !value);
|
|
this.el.classList.toggle(key + 'visible', value);
|
|
};
|
|
View.prototype.destroy = function (options) {
|
|
var noRemove = options && options.noRemove;
|
|
if (!noRemove)
|
|
this.remove();
|
|
this.fire('destroy');
|
|
this.el = null;
|
|
};
|
|
View.prototype.toString = function () {
|
|
return '[object View]';
|
|
};
|
|
View.prototype.initialize = noop;
|
|
View.prototype.template = function () {
|
|
return '';
|
|
};
|
|
View.extend = function (props) {
|
|
var Parent = this;
|
|
var Child = function () {
|
|
Parent.apply(this, arguments);
|
|
};
|
|
Child.prototype = Object.create(Parent.prototype);
|
|
Child.extend = View.extend;
|
|
mixin(Child.prototype, props);
|
|
return Child;
|
|
};
|
|
function toDashed(s) {
|
|
return s.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
|
|
}
|
|
function mixin(a, b) {
|
|
for (var key in b) {
|
|
a[key] = b[key];
|
|
}
|
|
return a;
|
|
}
|
|
function bindAll(object) {
|
|
var key;
|
|
var fn;
|
|
for (key in object) {
|
|
fn = object[key];
|
|
if (typeof fn === 'function') {
|
|
object[key] = fn.bind(object);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}(function (n, w) {
|
|
return typeof define == 'function' && define.amd ? define : typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} }, r = function (n) {
|
|
return w[n];
|
|
};
|
|
w[n] = c(r, m.exports, m) || m.exports;
|
|
};
|
|
}('view', this)));
|
|
define('views/notification', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'lib/mixin',
|
|
'view'
|
|
], function (require, exports, module) {
|
|
var mix = require('lib/mixin');
|
|
var View = require('view');
|
|
module.exports = View.extend({
|
|
name: 'notification',
|
|
tag: 'ul',
|
|
time: 3000,
|
|
initialize: function () {
|
|
this.counter = 0;
|
|
this.hash = {};
|
|
this.el.setAttribute('role', 'presentation');
|
|
},
|
|
display: function (options) {
|
|
var item = mix({}, options);
|
|
var id = ++this.counter;
|
|
var self = this;
|
|
item.el = document.createElement('li');
|
|
item.el.className = options.className || '';
|
|
var span = document.createElement('span');
|
|
if (typeof options.text === 'string') {
|
|
span.setAttribute('data-l10n-id', options.text);
|
|
} else {
|
|
if (options.text.html) {
|
|
span.innerHTML = options.text.html;
|
|
}
|
|
}
|
|
item.el.appendChild(span);
|
|
if (options.attrs) {
|
|
this.setAttributes(item.el, options.attrs);
|
|
}
|
|
item.el.setAttribute('role', 'status');
|
|
item.el.setAttribute('aria-live', 'assertive');
|
|
this.el.appendChild(item.el);
|
|
this.clear(this.temporary);
|
|
if (!item.persistent) {
|
|
this.temporary = id;
|
|
this.hide(this.persistent);
|
|
item.clearTimeout = setTimeout(function () {
|
|
self.clear(id);
|
|
}, this.time);
|
|
}
|
|
if (item.persistent) {
|
|
this.clear(this.persistent);
|
|
this.persistent = id;
|
|
}
|
|
this.hash[id] = item;
|
|
return id;
|
|
},
|
|
setAttributes: function (el, attrs) {
|
|
for (var key in attrs) {
|
|
el.setAttribute(key, attrs[key]);
|
|
}
|
|
},
|
|
clear: function (id) {
|
|
var item = this.hash[id];
|
|
if (!item || item.cleared) {
|
|
return;
|
|
}
|
|
this.el.removeChild(item.el);
|
|
clearTimeout(item.clearTimeout);
|
|
item.cleared = true;
|
|
if (item === this.temporary) {
|
|
this.temporary = null;
|
|
}
|
|
if (item === this.persistent) {
|
|
this.persistent = null;
|
|
}
|
|
delete this.hash[id];
|
|
this.show(this.persistent);
|
|
},
|
|
hide: function (id) {
|
|
var item = this.hash[id];
|
|
if (!item) {
|
|
return;
|
|
}
|
|
item.el.classList.add('hidden');
|
|
},
|
|
show: function (id) {
|
|
var item = this.hash[id];
|
|
if (!item) {
|
|
return;
|
|
}
|
|
item.el.classList.remove('hidden');
|
|
}
|
|
});
|
|
});
|
|
define('views/loading-screen', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'view'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('view:loading-screen');
|
|
var View = require('view');
|
|
module.exports = View.extend({
|
|
name: 'loading-screen',
|
|
fadeTime: 300,
|
|
initialize: function () {
|
|
this.render();
|
|
},
|
|
render: function () {
|
|
this.el.innerHTML = this.template;
|
|
delete this.template;
|
|
debug('rendered');
|
|
return this;
|
|
},
|
|
show: function (done) {
|
|
this.reflow = this.el.offsetTop;
|
|
View.prototype.show.call(this);
|
|
},
|
|
hide: function (done) {
|
|
View.prototype.hide.call(this);
|
|
if (done) {
|
|
setTimeout(done, this.fadeTime);
|
|
}
|
|
},
|
|
template: '<progress></progress>'
|
|
});
|
|
});
|
|
define('lib/bind', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
exports = module.exports = bind;
|
|
function bind(el, name, fn, capture) {
|
|
el.addEventListener(name, fn, capture || false);
|
|
}
|
|
exports.unbind = function (el, name, fn, capture) {
|
|
el.removeEventListener(name, fn, capture || false);
|
|
};
|
|
});
|
|
define('lib/pinch', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'lib/bind',
|
|
'lib/bind-all',
|
|
'lib/bind',
|
|
'evt'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var unbind = require('lib/bind').unbind;
|
|
var bindAll = require('lib/bind-all');
|
|
var bind = require('lib/bind');
|
|
var events = require('evt');
|
|
events(Pinch.prototype);
|
|
module.exports = Pinch;
|
|
function Pinch(el) {
|
|
bindAll(this);
|
|
this.attach(el);
|
|
}
|
|
Pinch.prototype.attach = function (el) {
|
|
this.el = el;
|
|
bind(this.el, 'touchstart', this.onTouchStart);
|
|
bind(window, 'touchmove', this.onTouchMove);
|
|
bind(window, 'touchend', this.onTouchEnd);
|
|
};
|
|
Pinch.prototype.detach = function () {
|
|
unbind(this.el, 'touchstart', this.onTouchStart);
|
|
unbind(window, 'touchmove', this.onTouchMove);
|
|
unbind(window, 'touchend', this.onTouchEnd);
|
|
this.el = null;
|
|
};
|
|
Pinch.prototype.enable = function () {
|
|
this.disabled = false;
|
|
};
|
|
Pinch.prototype.disable = function () {
|
|
this.disabled = true;
|
|
};
|
|
Pinch.prototype.onTouchStart = function (evt) {
|
|
if (evt.touches.length !== 2 || this.disabled) {
|
|
return;
|
|
}
|
|
this.lastTouchA = evt.touches[0];
|
|
this.lastTouchB = evt.touches[1];
|
|
this.isPinching = true;
|
|
this.emit('started');
|
|
};
|
|
Pinch.prototype.onTouchMove = function (evt) {
|
|
if (!this.isPinching || this.disabled) {
|
|
return;
|
|
}
|
|
var touchA = getNewTouchA(this, evt.touches);
|
|
var touchB = getNewTouchB(this, evt.touches);
|
|
var deltaPinch = getDeltaPinch(this, touchA, touchB);
|
|
this.emit('changed', deltaPinch);
|
|
this.lastTouchA = touchA;
|
|
this.lastTouchB = touchB;
|
|
};
|
|
Pinch.prototype.onTouchEnd = function (evt) {
|
|
if (!this.isPinching || this.disabled) {
|
|
return;
|
|
}
|
|
if (evt.touches.length < 2) {
|
|
this.isPinching = false;
|
|
this.emit('ended');
|
|
}
|
|
};
|
|
function getNewTouchA(pinch, touches) {
|
|
if (!pinch.lastTouchA) {
|
|
return null;
|
|
}
|
|
for (var i = 0, length = touches.length, touch; i < length; i++) {
|
|
touch = touches[i];
|
|
if (touch.identifier === pinch.lastTouchA.identifier) {
|
|
return touch;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function getNewTouchB(pinch, touches) {
|
|
if (!pinch.lastTouchB) {
|
|
return null;
|
|
}
|
|
for (var i = 0, length = touches.length, touch; i < length; i++) {
|
|
touch = touches[i];
|
|
if (touch.identifier === pinch.lastTouchB.identifier) {
|
|
return touch;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function getDeltaPinch(pinch, touchA, touchB) {
|
|
var lastTouchA = pinch.lastTouchA;
|
|
var lastTouchB = pinch.lastTouchB;
|
|
if (!touchA || !lastTouchA || !touchB || !lastTouchB) {
|
|
return 0;
|
|
}
|
|
var oldDistance = Math.sqrt(Math.pow(lastTouchB.pageX - lastTouchA.pageX, 2) + Math.pow(lastTouchB.pageY - lastTouchA.pageY, 2));
|
|
var newDistance = Math.sqrt(Math.pow(touchB.pageX - touchA.pageX, 2) + Math.pow(touchB.pageY - touchA.pageY, 2));
|
|
return newDistance - oldDistance;
|
|
}
|
|
});
|
|
define('app', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'stop-recording-event',
|
|
'views/notification',
|
|
'views/loading-screen',
|
|
'lib/orientation',
|
|
'lib/bind-all',
|
|
'debug',
|
|
'lib/pinch',
|
|
'lib/bind',
|
|
'model'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var stopRecordingEvent = require('stop-recording-event');
|
|
var NotificationView = require('views/notification');
|
|
var LoadingView = require('views/loading-screen');
|
|
var orientation = require('lib/orientation');
|
|
var bindAll = require('lib/bind-all');
|
|
var debug = require('debug')('app');
|
|
var Pinch = require('lib/pinch');
|
|
var bind = require('lib/bind');
|
|
var model = require('model');
|
|
module.exports = App;
|
|
model(App.prototype);
|
|
function App(options) {
|
|
debug('initialize');
|
|
bindAll(this);
|
|
this.views = {};
|
|
this.dynamicLazy = {};
|
|
this.el = options.el;
|
|
this.win = options.win;
|
|
this.doc = options.doc;
|
|
this.perf = options.perf || {};
|
|
this.pinch = options.pinch || new Pinch(this.el);
|
|
this.require = options.require || window.requirejs;
|
|
this.LoadingView = options.LoadingView || LoadingView;
|
|
this.orientation = options.orientation || orientation;
|
|
this.inSecureMode = this.win.location.hash === '#secure';
|
|
this.controllers = options.controllers;
|
|
this.geolocation = options.geolocation;
|
|
this.settings = options.settings;
|
|
this.camera = options.camera;
|
|
this.activity = {};
|
|
this.sounds = options.sounds;
|
|
debug('initialized');
|
|
}
|
|
App.prototype.boot = function () {
|
|
debug('boot');
|
|
if (this.booted) {
|
|
return;
|
|
}
|
|
this.showSpinner('requestingCamera');
|
|
this.bindEvents();
|
|
this.initializeViews();
|
|
this.runControllers();
|
|
window.performance.mark('navigationLoaded');
|
|
window.performance.mark('navigationInteractive');
|
|
this.injectViews();
|
|
this.booted = true;
|
|
debug('booted');
|
|
};
|
|
App.prototype.dispatchEvent = function (name) {
|
|
this.win.dispatchEvent(new CustomEvent(name));
|
|
};
|
|
App.prototype.runControllers = function () {
|
|
debug('run controllers');
|
|
this.controllers.overlay(this);
|
|
this.controllers.battery(this);
|
|
this.controllers.settings(this);
|
|
this.controllers.activity(this);
|
|
this.controllers.camera(this);
|
|
this.controllers.viewfinder(this);
|
|
this.controllers.hud(this);
|
|
this.controllers.controls(this);
|
|
this.controllers.storage(this);
|
|
debug('controllers run');
|
|
};
|
|
App.prototype.loadLazyController = function (controller) {
|
|
if (!this.criticalPathDone) {
|
|
this.controllers.lazy.push(controller);
|
|
return;
|
|
}
|
|
if (!this.dynamicLazy[controller]) {
|
|
this.dynamicLazy[controller] = this.loadLazyControllers([controller]);
|
|
}
|
|
return this.dynamicLazy[controller];
|
|
};
|
|
App.prototype.loadLazyControllers = function (controllers) {
|
|
debug('load lazy controllers');
|
|
var self = this;
|
|
return new Promise(function (resolve, reject) {
|
|
self.require(controllers, function () {
|
|
[].forEach.call(arguments, function (controller) {
|
|
controller(self);
|
|
});
|
|
debug('controllers loaded');
|
|
self.emit('lazyloaded');
|
|
resolve();
|
|
});
|
|
});
|
|
};
|
|
App.prototype.initializeViews = function () {
|
|
debug('initializing views');
|
|
this.views.notification = new NotificationView();
|
|
debug('views initialized');
|
|
};
|
|
App.prototype.injectViews = function () {
|
|
debug('injecting views');
|
|
this.views.notification.appendTo(this.el);
|
|
debug('views injected');
|
|
};
|
|
App.prototype.bindEvents = function () {
|
|
debug('binding events');
|
|
this.once('storage:checked:healthy', this.geolocationWatch);
|
|
this.once('viewfinder:visible', this.onCriticalPathDone);
|
|
this.once('camera:error', this.onCriticalPathDone);
|
|
this.once('activity', this.onActivity);
|
|
this.once('newthumbnail', this.onNewThumbnail);
|
|
this.once('preview', this.onPreview);
|
|
this.once('camera:willchange', this.onWillChange);
|
|
this.on('camera:willchange', this.firer('busy'));
|
|
this.on('ready', this.clearSpinner);
|
|
this.on('visible', this.onVisible);
|
|
this.on('hidden', this.onHidden);
|
|
this.on('reboot', this.onReboot);
|
|
this.on('busy', this.onBusy);
|
|
if (this.settings.countdown.selected('key') !== 'off') {
|
|
this.onCountdown();
|
|
} else {
|
|
this.settings.countdown.once('change:selected', this.onCountdown);
|
|
}
|
|
this.pinch.on('changed', this.firer('pinch:changed'));
|
|
this.on('previewgallery:opened', this.pinch.disable);
|
|
this.on('previewgallery:closed', this.pinch.enable);
|
|
this.on('settings:opened', this.pinch.disable);
|
|
this.on('settings:closed', this.pinch.enable);
|
|
bind(this.doc, 'visibilitychange', this.onVisibilityChange);
|
|
bind(this.doc, 'DOMRetranslated', this.firer('localized'));
|
|
bind(this.win, 'beforeunload', this.onBeforeUnload);
|
|
bind(this.win, 'keydown', this.onKeyDown);
|
|
bind(this.el, 'click', this.onClick);
|
|
debug('events bound');
|
|
};
|
|
App.prototype.onVisible = function () {
|
|
this.geolocationWatch();
|
|
this.orientation.start();
|
|
this.orientation.lock();
|
|
debug('visible');
|
|
};
|
|
App.prototype.onHidden = function () {
|
|
this.geolocation.stopWatching();
|
|
this.orientation.stop();
|
|
debug('hidden');
|
|
};
|
|
App.prototype.onReboot = function () {
|
|
debug('reboot');
|
|
window.location.reload();
|
|
};
|
|
App.prototype.onClick = function () {
|
|
debug('click');
|
|
this.emit('click');
|
|
};
|
|
App.prototype.onCriticalPathDone = function () {
|
|
if (this.criticalPathDone) {
|
|
return;
|
|
}
|
|
debug('critical path done');
|
|
window.performance.mark('visuallyLoaded');
|
|
this.listenForStopRecordingEvent();
|
|
this.loadLazyModules();
|
|
this.perf.criticalPath = Date.now();
|
|
this.criticalPathDone = true;
|
|
this.emit('criticalpathdone');
|
|
};
|
|
App.prototype.onActivity = function () {
|
|
this.loadLazyController('controllers/confirm');
|
|
};
|
|
App.prototype.onCountdown = function () {
|
|
this.loadLazyController('controllers/countdown');
|
|
};
|
|
App.prototype.onNewThumbnail = function () {
|
|
this.loadLazyController('controllers/preview-gallery');
|
|
};
|
|
App.prototype.onPreview = function () {
|
|
var self = this;
|
|
this.emit('busy', 'lazyLoading');
|
|
this.loadLazyController('controllers/preview-gallery').then(function () {
|
|
self.emit('preview');
|
|
self.emit('ready');
|
|
});
|
|
};
|
|
App.prototype.onWillChange = function () {
|
|
this.loadLazyController('controllers/recording-timer');
|
|
};
|
|
App.prototype.loadLazyModules = function () {
|
|
debug('load lazy modules');
|
|
var self = this;
|
|
var load = this.loadLazyControllers(this.controllers.lazy);
|
|
var storage = new Promise(function (resolve, reject) {
|
|
self.once('storage:checked', resolve);
|
|
});
|
|
Promise.all([
|
|
load,
|
|
storage
|
|
]).then(function () {
|
|
debug('app fully loaded');
|
|
window.performance.mark('contentInteractive');
|
|
self.loaded = true;
|
|
self.emit('loaded');
|
|
self.perf.loaded = Date.now();
|
|
self.logPerf();
|
|
window.performance.mark('fullyLoaded');
|
|
});
|
|
};
|
|
App.prototype.logPerf = function () {
|
|
var timing = window.performance.timing;
|
|
console.log('first module: %s', this.perf.firstModule - this.perf.jsStarted + 'ms');
|
|
console.log('critical-path: %s', this.perf.criticalPath - timing.domLoading + 'ms');
|
|
console.log('app-fully-loaded: %s', this.perf.loaded - timing.domLoading + 'ms');
|
|
};
|
|
App.prototype.geolocationWatch = function () {
|
|
var shouldWatch = !this.activity.pick && !this.hidden;
|
|
if (shouldWatch) {
|
|
this.geolocation.watch();
|
|
}
|
|
};
|
|
App.prototype.onVisibilityChange = function () {
|
|
this.hidden = this.doc.hidden;
|
|
this.emit(this.hidden ? 'hidden' : 'visible');
|
|
};
|
|
App.prototype.onBeforeUnload = function () {
|
|
this.emit('beforeunload');
|
|
debug('beforeunload');
|
|
};
|
|
App.prototype.showSpinner = function (key) {
|
|
debug('show loading type: %s', key);
|
|
this.busy = true;
|
|
var view = this.views.loading;
|
|
if (view) {
|
|
return;
|
|
}
|
|
var ms = this.settings.spinnerTimeouts.get(key) || 0;
|
|
var self = this;
|
|
clearTimeout(this.spinnerTimeout);
|
|
this.spinnerTimeout = setTimeout(function () {
|
|
self.views.loading = new self.LoadingView();
|
|
self.views.loading.appendTo(self.el).show();
|
|
debug('loading shown');
|
|
}, ms);
|
|
};
|
|
App.prototype.clearSpinner = function () {
|
|
debug('clear loading');
|
|
this.busy = false;
|
|
var view = this.views.loading;
|
|
clearTimeout(this.spinnerTimeout);
|
|
if (!view) {
|
|
return;
|
|
}
|
|
view.hide(view.destroy);
|
|
this.views.loading = null;
|
|
};
|
|
App.prototype.onBusy = function (type) {
|
|
debug('camera busy, type: %s', type);
|
|
this.busy = true;
|
|
var delay = this.settings.spinnerTimeouts.get(type);
|
|
if (delay) {
|
|
this.showSpinner(type);
|
|
}
|
|
};
|
|
App.prototype.listenForStopRecordingEvent = function () {
|
|
debug('listen for stop recording events');
|
|
stopRecordingEvent.start();
|
|
addEventListener('stoprecording', this.firer('stoprecording'));
|
|
};
|
|
App.prototype.onKeyDown = function (e) {
|
|
var key = e.key.toLowerCase();
|
|
var type = this.settings.keyDownEvents.get(key);
|
|
if (type) {
|
|
this.emit('keydown:' + type, e);
|
|
}
|
|
};
|
|
});
|
|
define('controllers/overlay', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bind-all'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('controller:overlay');
|
|
var bindAll = require('lib/bind-all');
|
|
module.exports = function (app) {
|
|
return new OverlayController(app);
|
|
};
|
|
module.exports.OverlayController = OverlayController;
|
|
function OverlayController(app) {
|
|
bindAll(this);
|
|
this.overlays = {};
|
|
this.app = app;
|
|
this.require = app.require;
|
|
this.activity = app.activity;
|
|
this.bindEvents();
|
|
debug('initialized');
|
|
}
|
|
OverlayController.prototype.bindEvents = function () {
|
|
this.app.on('storage:changed', this.onStorageChanged);
|
|
this.app.on('change:batteryStatus', this.onBatteryChanged);
|
|
this.app.on('camera:requesting', this.onCameraRequesting);
|
|
this.app.on('camera:error', this.onCameraError);
|
|
};
|
|
OverlayController.prototype.onStorageChanged = function (state) {
|
|
this.updateOverlay('storage', state !== 'available', state);
|
|
};
|
|
OverlayController.prototype.onBatteryChanged = function (state) {
|
|
this.updateOverlay('battery', state === 'shutdown', state);
|
|
};
|
|
OverlayController.prototype.onCameraRequesting = function () {
|
|
this.updateOverlay('cameraError', false);
|
|
};
|
|
OverlayController.prototype.onCameraError = function (type) {
|
|
this.updateOverlay('cameraError', true, type);
|
|
};
|
|
OverlayController.prototype.updateOverlay = function (type, enabled, reason) {
|
|
debug('\'%s/%s\' overlay %s', type, reason, enabled);
|
|
var overlay = this.overlays[type];
|
|
if (!overlay) {
|
|
overlay = this.overlays[type] = { id: 0 };
|
|
}
|
|
if (overlay.view) {
|
|
overlay.view.destroy();
|
|
delete overlay.view;
|
|
}
|
|
var id = ++overlay.id;
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
var self = this;
|
|
this.require(['views/overlay'], function (OverlayView) {
|
|
if (id !== overlay.id) {
|
|
return;
|
|
}
|
|
var closable = self.activity.pick && type !== 'request-fail';
|
|
var view = new OverlayView({
|
|
type: reason,
|
|
closable: closable
|
|
});
|
|
if (!view.rendered()) {
|
|
return;
|
|
}
|
|
overlay.view = view.appendTo(document.body).on('click:close-btn', function () {
|
|
self.app.emit('activitycanceled');
|
|
});
|
|
debug('inserted \'%s/%s\' overlay', type, reason);
|
|
});
|
|
};
|
|
});
|
|
define('controllers/battery', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bind-all',
|
|
'lib/bind'
|
|
], function (require, exports, module) {
|
|
var debug = require('debug')('controller:battery');
|
|
var bindAll = require('lib/bind-all');
|
|
var bind = require('lib/bind');
|
|
module.exports = function (app) {
|
|
return new BatteryController(app);
|
|
};
|
|
module.exports.BatteryController = BatteryController;
|
|
function BatteryController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.battery = app.battery || navigator.battery || navigator.mozBattery;
|
|
this.levels = app.settings.battery.get('levels');
|
|
this.notification = app.views.notification;
|
|
this.bindEvents();
|
|
this.updateStatus();
|
|
debug('initialized');
|
|
}
|
|
BatteryController.prototype.bindEvents = function () {
|
|
bind(this.battery, 'levelchange', this.updateStatus);
|
|
bind(this.battery, 'chargingchange', this.updateStatus);
|
|
this.app.on('change:batteryStatus', this.onStatusChange);
|
|
this.app.on('change:recording', this.updatePowerSave);
|
|
var mozSettings = navigator.mozSettings;
|
|
mozSettings.addObserver('powersave.enabled', this.onPowerSaveChange);
|
|
mozSettings.createLock().get('powersave.enabled').then(this.onPowerSaveChange);
|
|
};
|
|
BatteryController.prototype.onPowerSaveChange = function (values) {
|
|
var value;
|
|
if (values.settingValue !== undefined) {
|
|
value = values.settingValue;
|
|
} else {
|
|
value = values['powersave.enabled'];
|
|
}
|
|
this.powerSaveEnabled = value;
|
|
this.updatePowerSave();
|
|
};
|
|
BatteryController.prototype.updatePowerSave = function () {
|
|
var state = this.powerSaveEnabled && !this.app.get('recording');
|
|
if (this.powerSave === state) {
|
|
return;
|
|
}
|
|
this.powerSave = state;
|
|
debug('power save: ' + state);
|
|
this.app.emit('battery:powersave', state);
|
|
};
|
|
BatteryController.prototype.notifications = {
|
|
low: {
|
|
text: 'battery-low-text',
|
|
attrs: {
|
|
'data-icon': 'battery-3',
|
|
'data-l10n-id': 'battery-low-indicator'
|
|
}
|
|
},
|
|
verylow: {
|
|
text: 'battery-verylow-text',
|
|
attrs: {
|
|
'data-icon': 'battery-1',
|
|
'data-l10n-id': 'battery-verylow-indicator'
|
|
}
|
|
},
|
|
critical: {
|
|
text: 'battery-critical-text',
|
|
attrs: {
|
|
'data-icon': 'battery-1',
|
|
'data-l10n-id': 'battery-critical-indicator'
|
|
},
|
|
persistent: true
|
|
}
|
|
};
|
|
BatteryController.prototype.updateStatus = function () {
|
|
var previous = this.app.get('batteryStatus');
|
|
var current = this.getStatus(this.battery);
|
|
if (current !== previous) {
|
|
this.app.set('batteryStatus', current);
|
|
}
|
|
};
|
|
BatteryController.prototype.getStatus = function (battery) {
|
|
var level = Math.round(battery.level * 100);
|
|
var levels = this.levels;
|
|
if (battery.charging) {
|
|
return 'charging';
|
|
} else if (level <= levels.shutdown) {
|
|
return 'shutdown';
|
|
} else if (level <= levels.critical) {
|
|
return 'critical';
|
|
} else if (level <= levels.verylow) {
|
|
return 'verylow';
|
|
} else if (level <= levels.low) {
|
|
return 'low';
|
|
} else {
|
|
return 'healthy';
|
|
}
|
|
};
|
|
BatteryController.prototype.onStatusChange = function (status) {
|
|
this.clearLastNotification();
|
|
this.displayNotification(status);
|
|
};
|
|
BatteryController.prototype.displayNotification = function (status) {
|
|
var notification = this.notifications[status];
|
|
if (!notification) {
|
|
return;
|
|
}
|
|
this.lastNotification = this.notification.display({
|
|
text: notification.text,
|
|
className: notification.className,
|
|
attrs: notification.attrs,
|
|
persistent: notification.persistent
|
|
});
|
|
};
|
|
BatteryController.prototype.clearLastNotification = function () {
|
|
this.notification.clear(this.lastNotification);
|
|
};
|
|
});
|
|
define('views/hud', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bind',
|
|
'view'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('view:hud');
|
|
var bind = require('lib/bind');
|
|
var View = require('view');
|
|
module.exports = View.extend({
|
|
name: 'hud',
|
|
initialize: function () {
|
|
this.render();
|
|
},
|
|
render: function () {
|
|
this.el.innerHTML = this.template();
|
|
this.els.flash = this.find('.js-flash');
|
|
this.els.camera = this.find('.js-camera');
|
|
this.els.settings = this.find('.js-settings');
|
|
delete this.template;
|
|
debug('rendered');
|
|
return this.bindEvents();
|
|
},
|
|
bindEvents: function () {
|
|
bind(this.els.flash, 'click', this.onFlashClick);
|
|
bind(this.els.camera, 'click', this.onCameraClick);
|
|
bind(this.els.settings, 'click', this.onSettingsClick, true);
|
|
return this;
|
|
},
|
|
_setLabel: function (element, mode) {
|
|
if (mode) {
|
|
this.els[element].setAttribute('data-l10n-id', mode.title + '-button');
|
|
} else {
|
|
this.els[element].removeAttribute('data-l10n-id');
|
|
this.els[element].removeAttribute('aria-label');
|
|
}
|
|
},
|
|
setFlashModeLabel: function (mode) {
|
|
this._setLabel('flash', mode);
|
|
},
|
|
setFlashMode: function (mode) {
|
|
if (!mode) {
|
|
return;
|
|
}
|
|
this.els.flash.dataset.icon = mode.icon;
|
|
this.setFlashModeLabel(mode);
|
|
},
|
|
setCameraLabel: function (camera) {
|
|
this._setLabel('camera', camera);
|
|
},
|
|
setCamera: function (camera) {
|
|
if (!camera) {
|
|
return;
|
|
}
|
|
this.els.camera.dataset.icon = camera.icon;
|
|
this.setCameraLabel(camera);
|
|
},
|
|
setMenuLabel: function () {
|
|
this._setLabel('settings', { title: 'menu' });
|
|
},
|
|
onFlashClick: function (event) {
|
|
event.stopPropagation();
|
|
this.emit('click:flash');
|
|
},
|
|
onCameraClick: function (event) {
|
|
event.stopPropagation();
|
|
this.emit('click:camera');
|
|
},
|
|
onSettingsClick: function (event) {
|
|
event.stopPropagation();
|
|
this.emit('click:settings');
|
|
},
|
|
template: function () {
|
|
return '<div role="button" class="hud_btn hud_camera rotates ' + 'test-camera-toggle js-camera"></div>' + '<div role="button" class="hud_btn hud_flash rotates test-flash-button ' + 'js-flash"></div>' + '<div role="button" class="hud_btn hud_settings rotates ' + 'test-settings-toggle js-settings" data-icon="menu" ' + 'data-l10n-id="menu-button"></div>';
|
|
}
|
|
});
|
|
});
|
|
define('controllers/hud', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/debounce',
|
|
'lib/bind-all',
|
|
'views/hud'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('controller:hud');
|
|
var debounce = require('lib/debounce');
|
|
var bindAll = require('lib/bind-all');
|
|
var HudView = require('views/hud');
|
|
module.exports = function (app) {
|
|
return new HudController(app);
|
|
};
|
|
module.exports.HudController = HudController;
|
|
function HudController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.settings = app.settings;
|
|
this.notification = app.views.notification;
|
|
this.createView();
|
|
this.bindEvents();
|
|
debug('initialized');
|
|
}
|
|
HudController.prototype.createView = function () {
|
|
var hasDualCamera = this.settings.cameras.get('options').length > 1;
|
|
this.view = this.app.views.hud || new HudView();
|
|
this.view.enable('camera', hasDualCamera);
|
|
this.view.disable('flash');
|
|
this.view.hide();
|
|
this.updateCamera();
|
|
this.view.appendTo(this.app.el);
|
|
};
|
|
HudController.prototype.bindEvents = function () {
|
|
this.app.on('change:recording', this.view.setter('recording'));
|
|
this.app.on('ready', this.view.setter('camera', 'ready'));
|
|
this.app.on('busy', this.view.setter('camera', 'busy'));
|
|
this.app.on('localized', this.localize);
|
|
this.app.once('settings:configured', this.view.show);
|
|
this.app.on('settings:configured', this.updateFlashSupport);
|
|
this.app.settings.flashModes.on('change:selected', this.updateFlashMode);
|
|
this.app.settings.mode.on('change:selected', this.updateFlashMode);
|
|
this.app.settings.cameras.on('change:selected', this.updateCamera);
|
|
this.view.on('click:camera', debounce(this.onCameraClick, 500, true));
|
|
this.view.on('click:settings', this.app.firer('settings:toggle'));
|
|
this.view.on('click:flash', this.onFlashClick);
|
|
this.app.on('countdown:started', this.view.setter('countdown', 'active'));
|
|
this.app.on('countdown:ended', this.view.setter('countdown', 'inactive'));
|
|
this.app.on('settings:opened', this.view.hide);
|
|
this.app.on('settings:closed', this.view.show);
|
|
this.app.on('previewgallery:opened', this.view.hide);
|
|
this.app.on('previewgallery:closed', this.view.show);
|
|
};
|
|
HudController.prototype.onCameraClick = function () {
|
|
debug('camera clicked');
|
|
this.clearNotifications();
|
|
this.app.settings.cameras.next();
|
|
};
|
|
HudController.prototype.clearNotifications = function () {
|
|
this.notification.clear(this.flashNotification);
|
|
};
|
|
HudController.prototype.onFlashClick = function () {
|
|
var setting = this.settings.flashModes;
|
|
var ishdrOn = this.settings.hdr.selected('key') === 'on';
|
|
setting.next();
|
|
this.view.set('flashMode', setting.selected('key'));
|
|
this.notify(setting, ishdrOn);
|
|
};
|
|
HudController.prototype.notify = function (setting, hdrDeactivated) {
|
|
var optionTitle = '<span data-l10n-id="' + setting.selected('title') + '"></span>';
|
|
var title = '<span data-l10n-id="' + setting.get('title') + '"></span>';
|
|
var html;
|
|
if (hdrDeactivated) {
|
|
html = title + ' ' + optionTitle + '<br/>' + '<span data-l10n-id="hdr-deactivated"></span>';
|
|
} else {
|
|
html = title + '<br/>' + optionTitle;
|
|
}
|
|
this.flashNotification = this.notification.display({ text: { html: html } });
|
|
};
|
|
HudController.prototype.localize = function () {
|
|
this.view.setFlashModeLabel(this.settings.flashModes.selected());
|
|
this.view.setCameraLabel(this.settings.cameras.selected());
|
|
this.view.setMenuLabel();
|
|
};
|
|
HudController.prototype.updateFlashMode = function () {
|
|
var selected = this.settings.flashModes.selected();
|
|
if (!selected) {
|
|
return;
|
|
}
|
|
this.view.setFlashMode(selected);
|
|
debug('updated flash mode: %s', selected.key);
|
|
};
|
|
HudController.prototype.updateFlashSupport = function () {
|
|
var supported = this.settings.flashModes.supported();
|
|
this.view.enable('flash', supported);
|
|
this.updateFlashMode();
|
|
debug('flash supported: %s', supported);
|
|
};
|
|
HudController.prototype.updateCamera = function () {
|
|
var selected = this.settings.cameras.selected();
|
|
if (!selected) {
|
|
return;
|
|
}
|
|
this.view.setCamera(selected);
|
|
debug('updated camera: %s', selected.key);
|
|
};
|
|
});
|
|
(function (define) {
|
|
'use strict';
|
|
define('drag', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'evt'
|
|
], function (require, exports, module) {
|
|
var events = require('evt');
|
|
module.exports = Drag;
|
|
events(Drag.prototype);
|
|
var pointer = [
|
|
{
|
|
down: 'touchstart',
|
|
up: 'touchend',
|
|
move: 'touchmove'
|
|
},
|
|
{
|
|
down: 'mousedown',
|
|
up: 'mouseup',
|
|
move: 'mousemove'
|
|
}
|
|
]['ontouchstart' in window ? 0 : 1];
|
|
function Drag(options) {
|
|
this.container = { el: options.container };
|
|
this.handle = { el: options.handle };
|
|
this.onTouchStart = this.onTouchStart.bind(this);
|
|
this.onTouchMove = this.onTouchMove.bind(this);
|
|
this.onTouchEnd = this.onTouchEnd.bind(this);
|
|
this.slideDuration = options.slideDuration || 140;
|
|
this.tapTime = options.tapTime || 180;
|
|
this.bindEvents();
|
|
}
|
|
Drag.prototype.bindEvents = function () {
|
|
this.container.el.addEventListener(pointer.down, this.onTouchStart);
|
|
};
|
|
Drag.prototype.onTouchStart = function (e) {
|
|
this.updateDimensions();
|
|
this.touch = ~e.type.indexOf('mouse') ? e : e.touches[0];
|
|
this.firstTouch = this.touch;
|
|
this.startTime = e.timeStamp;
|
|
addEventListener(pointer.move, this.onTouchMove);
|
|
addEventListener(pointer.up, this.onTouchEnd);
|
|
};
|
|
Drag.prototype.onTouchMove = function (e) {
|
|
e.preventDefault();
|
|
e = ~e.type.indexOf('mouse') ? e : e.touches[0];
|
|
var delta = {
|
|
x: e.clientX - this.touch.clientX,
|
|
y: e.clientY - this.touch.clientY
|
|
};
|
|
this.dragging = true;
|
|
this.move(delta);
|
|
this.touch = e;
|
|
};
|
|
Drag.prototype.onTouchEnd = function (e) {
|
|
var tapped = e.timeStamp - this.startTime < this.tapTime;
|
|
this.dragging = false;
|
|
removeEventListener(pointer.move, this.onTouchMove);
|
|
removeEventListener(pointer.up, this.onTouchEnd);
|
|
if (tapped) {
|
|
this.emit('tapped', e);
|
|
} else {
|
|
this.emit('ended', e);
|
|
}
|
|
};
|
|
Drag.prototype.move = function (delta) {
|
|
this.translate({
|
|
x: this.handle.position.x + delta.x,
|
|
y: this.handle.position.y + delta.y
|
|
});
|
|
};
|
|
Drag.prototype.set = function (pos) {
|
|
if (!this.edges) {
|
|
this.pendingSet = pos;
|
|
return;
|
|
}
|
|
var x = typeof pos.x === 'string' ? this.edges[pos.x] : pos.x || 0;
|
|
var y = typeof pos.y === 'string' ? this.edges[pos.y] : pos.y || 0;
|
|
this.translate({
|
|
x: x,
|
|
y: y
|
|
});
|
|
};
|
|
Drag.prototype.snapToClosestEdge = function () {
|
|
var edges = this.getClosestEdges();
|
|
this.translate({
|
|
x: this.edges[edges.x],
|
|
y: this.edges[edges.y]
|
|
});
|
|
this.emit('snapped', edges);
|
|
};
|
|
Drag.prototype.translate = function (options) {
|
|
var position = this.clamp(options);
|
|
var translate = 'translate(' + position.x + 'px,' + position.y + 'px)';
|
|
var ratio = {
|
|
x: position.x / this.max.x || 0,
|
|
y: position.y / this.max.y || 0
|
|
};
|
|
this.setTransition(position);
|
|
this.handle.el.style.transform = translate;
|
|
this.handle.position = position;
|
|
this.emit('translate', {
|
|
position: {
|
|
px: position,
|
|
ratio: ratio
|
|
}
|
|
});
|
|
};
|
|
Drag.prototype.clamp = function (position) {
|
|
return {
|
|
x: Math.max(this.min.x, Math.min(this.max.x, position.x)),
|
|
y: Math.max(this.min.y, Math.min(this.max.y, position.y))
|
|
};
|
|
};
|
|
Drag.prototype.setTransition = function (position) {
|
|
var duration = !this.dragging ? this.transitionDuration(position) : 0;
|
|
this.handle.el.style.transitionDuration = duration + 'ms';
|
|
};
|
|
Drag.prototype.transitionDuration = function (position) {
|
|
var current = this.handle.position;
|
|
var distanceX = Math.abs(current.x - position.x);
|
|
var distanceY = Math.abs(current.y - position.y);
|
|
var distance = Math.max(distanceX, distanceY);
|
|
var axis = distanceY > distanceX ? 'y' : 'x';
|
|
var ratio = distance / this.max[axis];
|
|
return this.slideDuration * ratio;
|
|
};
|
|
Drag.prototype.getClosestEdges = function () {
|
|
return {
|
|
x: this.handle.position.x <= this.max.x / 2 ? 'left' : 'right',
|
|
y: this.handle.position.y <= this.max.y / 2 ? 'top' : 'bottom'
|
|
};
|
|
};
|
|
Drag.prototype.updateDimensions = function () {
|
|
var container = this.container.el.getBoundingClientRect();
|
|
var handle = this.handle.el.getBoundingClientRect();
|
|
this.min = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
this.max = {
|
|
x: container.width - handle.width,
|
|
y: container.height - handle.height
|
|
};
|
|
this.edges = {
|
|
top: this.min.y,
|
|
right: this.max.x,
|
|
bottom: this.max.y,
|
|
left: this.min.x
|
|
};
|
|
this.handle.position = {
|
|
x: handle.left - container.left,
|
|
y: handle.top - container.top
|
|
};
|
|
this.clearPendingSet();
|
|
};
|
|
Drag.prototype.clearPendingSet = function () {
|
|
if (!this.pendingSet) {
|
|
return;
|
|
}
|
|
this.set(this.pendingSet);
|
|
delete this.pendingSet;
|
|
};
|
|
});
|
|
}(function (n, w) {
|
|
'use strict';
|
|
return typeof define == 'function' && define.amd ? define : typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} }, r = function (n) {
|
|
return w[n];
|
|
};
|
|
w[n] = c(r, m.exports, m) || m.exports;
|
|
};
|
|
}('drag', this)));
|
|
define('views/controls', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/debounce',
|
|
'lib/bind',
|
|
'view',
|
|
'drag'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('view:controls');
|
|
var debounce = require('lib/debounce');
|
|
var bind = require('lib/bind');
|
|
var View = require('view');
|
|
var Drag = require('drag');
|
|
module.exports = View.extend({
|
|
name: 'controls',
|
|
className: 'test-controls',
|
|
initialize: function (options) {
|
|
this.drag = options && options.drag;
|
|
this.once('inserted', this.setupSwitch);
|
|
this.render();
|
|
},
|
|
switchPositions: {
|
|
left: 'picture',
|
|
right: 'video',
|
|
picture: 'left',
|
|
video: 'right'
|
|
},
|
|
elsL10n: {
|
|
cancel: 'controls-button-close',
|
|
thumbnail: 'preview-button',
|
|
capture: 'capture-button'
|
|
},
|
|
render: function () {
|
|
this.el.innerHTML = this.template();
|
|
this.els.switchHandle = this.find('.js-switch-handle');
|
|
this.els.thumbnail = this.find('.js-thumbnail');
|
|
this.els.capture = this.find('.js-capture');
|
|
this.els.cancel = this.find('.js-cancel');
|
|
this.els.switch = this.find('.js-switch');
|
|
this.els.icons = {
|
|
camera: this.find('.js-icon-camera'),
|
|
video: this.find('.js-icon-video')
|
|
};
|
|
delete this.template;
|
|
debug('rendered');
|
|
return this.bindEvents();
|
|
},
|
|
bindEvents: function () {
|
|
this.onButtonClick = debounce(this.onButtonClick, 300, true);
|
|
bind(this.els.thumbnail, 'click', this.onButtonClick);
|
|
bind(this.els.capture, 'click', this.onButtonClick);
|
|
bind(this.els.cancel, 'click', this.onButtonClick);
|
|
return this;
|
|
},
|
|
setupSwitch: function () {
|
|
debug('setup dragger');
|
|
if (document.readyState !== 'complete') {
|
|
window.addEventListener('load', this.setupSwitch);
|
|
debug('deferred switch setup till after load');
|
|
return;
|
|
}
|
|
this.drag = this.drag || new Drag({
|
|
handle: this.els.switchHandle,
|
|
container: this.els.switch
|
|
});
|
|
this.drag.on('tapped', debounce(this.onSwitchTapped, 300, true));
|
|
this.drag.on('ended', this.drag.snapToClosestEdge);
|
|
this.drag.on('translate', this.onSwitchTranslate);
|
|
this.drag.on('snapped', this.onSwitchSnapped);
|
|
this.drag.updateDimensions();
|
|
this.updateSwitchPosition();
|
|
window.removeEventListener('load', this.setupSwitch);
|
|
},
|
|
setCaptureLabel: function (recording) {
|
|
this.els.capture.setAttribute('data-l10n-id', recording ? 'stop-capture-button' : 'capture-button');
|
|
},
|
|
onSwitchSnapped: function (edges) {
|
|
var mode = this.switchPositions[edges.x];
|
|
var changed = mode !== this.get('mode');
|
|
if (changed) {
|
|
this.onSwitchChanged();
|
|
}
|
|
},
|
|
onSwitchChanged: function () {
|
|
this.emit('modechanged');
|
|
},
|
|
onSwitchTapped: function (e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
debug('switch tapped');
|
|
this.onSwitchChanged();
|
|
},
|
|
onSwitchTranslate: function (e) {
|
|
this.setSwitchIcon(e.position.ratio.x);
|
|
},
|
|
setSwitchIcon: function (ratio) {
|
|
var skew = 2;
|
|
var ratioSkewed = ratio * skew;
|
|
var camera = Math.max(0, 1 - ratioSkewed);
|
|
var video = Math.max(0, -1 + ratioSkewed);
|
|
this.els.icons.camera.style.opacity = camera;
|
|
this.els.icons.video.style.opacity = video;
|
|
debug('set switch icon camera: %s, video: %s', camera, video);
|
|
},
|
|
setScreenReaderVisible: function (visible) {
|
|
this.el.setAttribute('aria-hidden', !visible);
|
|
},
|
|
onButtonClick: function (e) {
|
|
e.stopPropagation();
|
|
debug('button click');
|
|
var name = e.currentTarget.getAttribute('name');
|
|
this.emit('click:' + name, e);
|
|
},
|
|
suspendModeSwitch: function (suspended) {
|
|
if (suspended) {
|
|
this.set('switch-toggle-disabled');
|
|
} else {
|
|
this.unset('switch-toggle-disabled');
|
|
}
|
|
},
|
|
setMode: function (mode) {
|
|
debug('set mode: %s', mode);
|
|
this.set('mode', mode);
|
|
this.switchPosition = this.switchPositions[mode];
|
|
var ratio = {
|
|
left: 0,
|
|
right: 1
|
|
}[this.switchPosition];
|
|
this.updateSwitchPosition();
|
|
this.setSwitchIcon(ratio);
|
|
this.els.switch.setAttribute('data-l10n-id', mode + '-mode-button');
|
|
debug('mode set pos: %s', this.switchPosition);
|
|
},
|
|
updateSwitchPosition: function () {
|
|
debug('updateSwitchPosition');
|
|
if (!this.drag) {
|
|
return;
|
|
}
|
|
this.drag.set({ x: this.switchPosition });
|
|
debug('updated switch position: %s', this.switchPosition);
|
|
},
|
|
setThumbnail: function (blob) {
|
|
if (!this.els.image) {
|
|
this.els.image = new Image();
|
|
this.els.image.classList.add('test-thumbnail');
|
|
this.els.thumbnail.appendChild(this.els.image);
|
|
this.set('thumbnail', true);
|
|
} else {
|
|
window.URL.revokeObjectURL(this.els.image.src);
|
|
}
|
|
this.els.image.src = window.URL.createObjectURL(blob);
|
|
debug('thumbnail set');
|
|
},
|
|
removeThumbnail: function () {
|
|
if (this.els.image) {
|
|
this.els.thumbnail.removeChild(this.els.image);
|
|
window.URL.revokeObjectURL(this.els.image.src);
|
|
this.els.image = null;
|
|
}
|
|
this.set('thumbnail', false);
|
|
},
|
|
set: function (key, value) {
|
|
if (typeof key !== 'string') {
|
|
return;
|
|
}
|
|
if (arguments.length === 1) {
|
|
value = true;
|
|
}
|
|
if (!value) {
|
|
return this.unset(key);
|
|
}
|
|
var attr = 'data-' + key;
|
|
var oldValue = this.el.getAttribute(attr);
|
|
var oldClass = oldValue && classFrom(key, oldValue);
|
|
var newClass = classFrom(key, value);
|
|
if (oldClass) {
|
|
this.el.classList.remove(oldClass);
|
|
}
|
|
if (newClass) {
|
|
this.el.classList.add(newClass);
|
|
}
|
|
this.el.setAttribute(attr, value);
|
|
debug('remove: %s, add: %s', oldClass, newClass);
|
|
debug('attr key: %s, value: %s', attr, value);
|
|
},
|
|
get: function (key) {
|
|
var attr = 'data-' + key;
|
|
return this.el.getAttribute(attr);
|
|
},
|
|
unset: function (key) {
|
|
var attr = 'data-' + key;
|
|
var value = this.el.getAttribute(attr);
|
|
this.el.classList.remove(classFrom(key, value));
|
|
this.el.removeAttribute(attr);
|
|
},
|
|
enable: function (key) {
|
|
this.set(key ? key + '-enabled' : 'enabled');
|
|
this.unset(key ? key + '-disabled' : 'disabled');
|
|
},
|
|
disable: function (key) {
|
|
this.set(key ? key + '-disabled' : 'disabled');
|
|
this.unset(key ? key + '-enabled' : 'enabled');
|
|
},
|
|
localize: function () {
|
|
var mode = this.get('mode') || 'picture';
|
|
this.els.switch.setAttribute('data-l10n-id', mode + '-mode-button');
|
|
},
|
|
template: function () {
|
|
return '<div class="controls-left">' + '<div class="controls-button controls-thumbnail-button test-thumbnail js-thumbnail rotates" ' + 'name="thumbnail" role="button" data-l10n-id="preview-button"></div>' + '<div class="controls-button controls-cancel-pick-button test-cancel-pick rotates js-cancel" ' + 'name="cancel" data-icon="close" role="button" data-l10n-id="controls-button-close"></div>' + '</div>' + '<div class="controls-middle">' + '<div class="capture-button test-capture rotates js-capture" name="capture" ' + 'data-l10n-id="capture-button" role="button">' + '<div class="circle outer-circle"></div>' + '<div class="circle inner-circle"></div>' + '<div class="center" data-icon="camera" aria-hidden="true"></div>' + '</div>' + '</div>' + '<div class="controls-right">' + '<div class="mode-switch test-switch" name="switch">' + '<div class="inner js-switch" role="button">' + '<div class="mode-switch_bg-icon rotates" data-icon="camera" aria-hidden="true"></div>' + '<div class="mode-switch_bg-icon rotates" data-icon="video" aria-hidden="true"></div>' + '<div class="mode-switch_handle js-switch-handle" aria-hidden="true">' + '<div class="mode-switch_current-icon camera rotates js-icon-camera" data-icon="camera" aria-hidden="true"></div>' + '<div class="mode-switch_current-icon video rotates js-icon-video" data-icon="video" aria-hidden="true"></div>' + '</div>' + '</div>' + '</div>' + '</div>';
|
|
}
|
|
});
|
|
function classFrom(key, value) {
|
|
value = detectBooleans(value);
|
|
if (typeof value === 'boolean') {
|
|
return value ? key : '';
|
|
} else if (value) {
|
|
return key + '-' + value;
|
|
} else {
|
|
return key;
|
|
}
|
|
}
|
|
function detectBooleans(value) {
|
|
if (typeof value === 'boolean') {
|
|
return value;
|
|
} else if (value === 'true') {
|
|
return true;
|
|
} else if (value === 'false') {
|
|
return false;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
});
|
|
define('controllers/controls', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'views/controls',
|
|
'lib/bind-all'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('controller:controls');
|
|
var ControlsView = require('views/controls');
|
|
var bindAll = require('lib/bind-all');
|
|
module.exports = function (app) {
|
|
return new ControlsController(app);
|
|
};
|
|
module.exports.ControlsController = ControlsController;
|
|
function ControlsController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.activity = app.activity;
|
|
this.createView();
|
|
this.bindEvents();
|
|
debug('initialized');
|
|
}
|
|
ControlsController.prototype.bindEvents = function () {
|
|
this.app.settings.mode.on('change:selected', this.view.setMode);
|
|
this.app.settings.mode.on('change:options', this.configureMode);
|
|
this.app.on('change:recording', this.onRecordingChange);
|
|
this.app.on('camera:shutter', this.captureHighlightOff);
|
|
this.app.on('camera:willchange', this.onCameraWillChange);
|
|
this.app.on('camera:configured', this.onCameraConfigured);
|
|
this.app.on('newthumbnail', this.onNewThumbnail);
|
|
this.app.once('loaded', this.onceAppLoaded);
|
|
this.app.on('busy', this.onCameraBusy);
|
|
this.app.on('localized', this.view.localize);
|
|
this.view.on('modechanged', this.onViewModeChanged);
|
|
this.view.on('click:thumbnail', this.app.firer('preview'));
|
|
this.view.on('click:cancel', this.onCancelButtonClick);
|
|
this.view.on('click:capture', this.onCaptureClick);
|
|
this.app.on('countdown:started', this.onCountdownStarted);
|
|
this.app.on('countdown:ended', this.onCountdownStopped);
|
|
this.app.on('settings:opened', this.onSettingsOpened);
|
|
this.app.on('settings:closed', this.onSettingsClosed);
|
|
this.app.on('previewgallery:opened', this.view.hide);
|
|
this.app.on('previewgallery:closed', this.view.show);
|
|
debug('events bound');
|
|
};
|
|
ControlsController.prototype.createView = function () {
|
|
var initialMode = this.app.settings.mode.selected('key');
|
|
var cancellable = !!this.app.activity.pick;
|
|
this.view = this.app.views.controls || new ControlsView();
|
|
this.view.set('cancel', cancellable);
|
|
this.view.setMode(initialMode);
|
|
this.view.disable();
|
|
this.view.appendTo(this.app.el);
|
|
debug('cancelable: %s', cancellable);
|
|
debug('mode: %s', initialMode);
|
|
};
|
|
ControlsController.prototype.configureMode = function () {
|
|
var switchable = this.app.settings.mode.get('options').length > 1;
|
|
if (!switchable) {
|
|
this.view.disable('switch');
|
|
}
|
|
};
|
|
ControlsController.prototype.onceAppLoaded = function () {
|
|
this.app.on('ready', this.restore);
|
|
this.view.enable();
|
|
};
|
|
ControlsController.prototype.onCaptureClick = function () {
|
|
this.captureHighlightOn();
|
|
this.app.emit('capture');
|
|
};
|
|
ControlsController.prototype.onCameraWillChange = function () {
|
|
this.view.suspendModeSwitch(true);
|
|
};
|
|
ControlsController.prototype.onCameraConfigured = function () {
|
|
this.view.suspendModeSwitch(false);
|
|
};
|
|
ControlsController.prototype.onRecordingChange = function (recording) {
|
|
this.view.set('recording', recording);
|
|
if (!recording) {
|
|
this.onRecordingEnd();
|
|
}
|
|
this.view.setCaptureLabel(recording);
|
|
};
|
|
ControlsController.prototype.onRecordingEnd = function () {
|
|
this.captureHighlightOff();
|
|
};
|
|
ControlsController.prototype.onNewThumbnail = function (thumbnailBlob) {
|
|
if (thumbnailBlob) {
|
|
this.view.setThumbnail(thumbnailBlob);
|
|
} else {
|
|
this.view.removeThumbnail();
|
|
}
|
|
};
|
|
ControlsController.prototype.onCountdownStarted = function () {
|
|
this.captureHighlightOn();
|
|
this.view.set('countdown', 'active');
|
|
};
|
|
ControlsController.prototype.onCountdownStopped = function () {
|
|
this.captureHighlightOff();
|
|
this.view.set('countdown', 'inactive');
|
|
};
|
|
ControlsController.prototype.onSettingsOpened = function () {
|
|
this.view.setScreenReaderVisible(false);
|
|
};
|
|
ControlsController.prototype.onSettingsClosed = function () {
|
|
this.view.setScreenReaderVisible(true);
|
|
};
|
|
ControlsController.prototype.onCameraBusy = function () {
|
|
this.view.disable();
|
|
};
|
|
ControlsController.prototype.restore = function () {
|
|
debug('restore');
|
|
this.captureHighlightOff();
|
|
this.view.enable();
|
|
};
|
|
ControlsController.prototype.captureHighlightOn = function () {
|
|
this.view.set('capture-active');
|
|
};
|
|
ControlsController.prototype.captureHighlightOff = function () {
|
|
this.view.unset('capture-active');
|
|
};
|
|
ControlsController.prototype.onViewModeChanged = function () {
|
|
debug('view mode changed');
|
|
this.app.settings.mode.next();
|
|
};
|
|
ControlsController.prototype.onCancelButtonClick = function () {
|
|
this.app.emit('activitycanceled');
|
|
};
|
|
ControlsController.prototype.onGalleryButtonClick = function (event) {
|
|
event.stopPropagation();
|
|
var MozActivity = window.MozActivity;
|
|
if (this.app.inSecureMode) {
|
|
return;
|
|
}
|
|
this.mozActivity = new MozActivity({
|
|
name: 'browse',
|
|
data: { type: 'photos' }
|
|
});
|
|
this.view.disable();
|
|
setTimeout(this.view.enable, 2000);
|
|
};
|
|
});
|
|
define('lib/camera-coordinates', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
function toPixels(x, y, viewportWidth, viewportHeight) {
|
|
var cameraCoordinatesRange = 2000;
|
|
var pixelsPerCameraUnitWidth = viewportWidth / cameraCoordinatesRange;
|
|
var pixelsPerCameraUnitHeight = viewportHeight / cameraCoordinatesRange;
|
|
var xCameraCoordinates = x + 1000;
|
|
var yCameraCoordinates = y + 1000;
|
|
var xPixelCoordinates = xCameraCoordinates * pixelsPerCameraUnitWidth;
|
|
var yPixelCoordinates = yCameraCoordinates * pixelsPerCameraUnitHeight;
|
|
return {
|
|
x: Math.round(xPixelCoordinates),
|
|
y: Math.round(yPixelCoordinates)
|
|
};
|
|
}
|
|
function toCamera(x, y, viewportWidth, viewportHeight) {
|
|
var cameraCoordinatesRange = 2000;
|
|
var cameraUnitsPerPixelWidth = cameraCoordinatesRange / viewportWidth;
|
|
var cameraUnitsPerPixelHeight = cameraCoordinatesRange / viewportHeight;
|
|
return {
|
|
x: Math.round(x * cameraUnitsPerPixelWidth) - 1000,
|
|
y: Math.round(y * cameraUnitsPerPixelHeight) - 1000
|
|
};
|
|
}
|
|
function rotatePoint(x, y, angle) {
|
|
angle = Math.round(angle % 360 / 90) % 4 * 90;
|
|
switch (angle) {
|
|
case 0:
|
|
return {
|
|
x: x,
|
|
y: y
|
|
};
|
|
case 90:
|
|
case -270:
|
|
return {
|
|
x: -y,
|
|
y: x
|
|
};
|
|
case 180:
|
|
case -180:
|
|
return {
|
|
x: -x,
|
|
y: -y
|
|
};
|
|
case 270:
|
|
case -90:
|
|
return {
|
|
x: y,
|
|
y: -x
|
|
};
|
|
default:
|
|
console.error('wrong angle value');
|
|
}
|
|
}
|
|
function rotateArea(area, sensorOrientation) {
|
|
var topLeft = rotatePoint(area.left, area.top, sensorOrientation);
|
|
var bottomRight = rotatePoint(area.right, area.bottom, sensorOrientation);
|
|
return {
|
|
top: Math.min(topLeft.y, bottomRight.y),
|
|
left: Math.min(topLeft.x, bottomRight.x),
|
|
bottom: Math.max(topLeft.y, bottomRight.y),
|
|
right: Math.max(topLeft.x, bottomRight.x),
|
|
width: Math.abs(topLeft.x - bottomRight.x),
|
|
height: Math.abs(topLeft.y - bottomRight.y)
|
|
};
|
|
}
|
|
function mirrorAreaCamera(area) {
|
|
return {
|
|
top: area.top,
|
|
left: -area.right,
|
|
bottom: area.bottom,
|
|
right: -area.left,
|
|
width: area.width,
|
|
height: area.height
|
|
};
|
|
}
|
|
function mirrorAreaPixels(area, viewportWidth) {
|
|
return {
|
|
top: area.top,
|
|
left: viewportWidth - area.right,
|
|
bottom: area.bottom,
|
|
right: viewportWidth - area.left,
|
|
width: area.width,
|
|
height: area.height
|
|
};
|
|
}
|
|
function sizeToCamera(width, height, viewportWidth, viewportHeight) {
|
|
var cameraCoordinatesRange = 2000;
|
|
var cameraUnitsPerPixelWidth = cameraCoordinatesRange / viewportWidth;
|
|
var cameraUnitsPerPixelHeight = cameraCoordinatesRange / viewportHeight;
|
|
return {
|
|
width: Math.round(width * cameraUnitsPerPixelWidth),
|
|
height: Math.round(height * cameraUnitsPerPixelHeight)
|
|
};
|
|
}
|
|
function sizeToPixels(width, height, viewportWidth, viewportHeight) {
|
|
var cameraCoordinatesRange = 2000;
|
|
var pixelsPerCameraUnitWidth = viewportWidth / cameraCoordinatesRange;
|
|
var pixelsPerCameraUnitHeight = viewportHeight / cameraCoordinatesRange;
|
|
return {
|
|
width: Math.round(width * pixelsPerCameraUnitWidth),
|
|
height: Math.round(height * pixelsPerCameraUnitHeight)
|
|
};
|
|
}
|
|
function areaToPixels(area, viewportWidth, viewportHeight) {
|
|
var areaPixels = toPixels(area.left, area.top, viewportWidth, viewportHeight);
|
|
var areaPixelSize = sizeToPixels(area.width, area.height, viewportWidth, viewportHeight);
|
|
var width = areaPixelSize.width;
|
|
var height = areaPixelSize.height;
|
|
return {
|
|
top: areaPixels.y,
|
|
left: areaPixels.x,
|
|
bottom: areaPixels.y + height,
|
|
right: areaPixels.x + width,
|
|
width: width,
|
|
height: height
|
|
};
|
|
}
|
|
function areaToCamera(area, viewportWidth, viewportHeight) {
|
|
var topLeft = toCamera(area.left, area.top, viewportWidth, viewportHeight);
|
|
var areaCameraUnits = sizeToCamera(area.width, area.height, viewportWidth, viewportHeight);
|
|
var width = areaCameraUnits.width;
|
|
var height = areaCameraUnits.height;
|
|
var areaCamera = {
|
|
top: topLeft.y,
|
|
left: topLeft.x,
|
|
bottom: topLeft.y + height,
|
|
right: topLeft.x + width,
|
|
height: height,
|
|
width: width
|
|
};
|
|
return areaCamera;
|
|
}
|
|
function faceToCamera(face, viewportWidth, viewportHeight, sensorOrientation, mirrored) {
|
|
if (mirrored) {
|
|
face = mirrorAreaPixels(face, viewportWidth);
|
|
}
|
|
face = areaToCamera(face, viewportWidth, viewportHeight);
|
|
if (sensorOrientation) {
|
|
face = rotateArea(face, -sensorOrientation);
|
|
}
|
|
return face;
|
|
}
|
|
function faceToPixels(face, viewportWidth, viewportHeight, sensorOrientation, mirrored) {
|
|
if (sensorOrientation) {
|
|
face = rotateArea(face, sensorOrientation);
|
|
}
|
|
if (mirrored) {
|
|
face = mirrorAreaCamera(face);
|
|
}
|
|
return areaToPixels(face, viewportWidth, viewportHeight);
|
|
}
|
|
return {
|
|
faceToCamera: faceToCamera,
|
|
faceToPixels: faceToPixels,
|
|
private: {
|
|
areaToCamera: areaToCamera,
|
|
areaToPixels: areaToPixels,
|
|
mirrorAreaCamera: mirrorAreaCamera,
|
|
mirrorAreaPixels: mirrorAreaPixels,
|
|
rotateArea: rotateArea,
|
|
sizeToCamera: sizeToCamera,
|
|
sizeToPixels: sizeToPixels,
|
|
toCamera: toCamera,
|
|
toPixels: toPixels,
|
|
rotatePoint: rotatePoint
|
|
}
|
|
};
|
|
});
|
|
define('views/viewfinder', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bind',
|
|
'lib/camera-utils',
|
|
'view'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('view:viewfinder');
|
|
var bind = require('lib/bind');
|
|
var CameraUtils = require('lib/camera-utils');
|
|
var View = require('view');
|
|
var isZoomEnabled = false;
|
|
var scaleSizeTo = {
|
|
fill: CameraUtils.scaleSizeToFillViewport,
|
|
fit: CameraUtils.scaleSizeToFitViewport
|
|
};
|
|
var clamp = function (value, minimum, maximum) {
|
|
return Math.min(Math.max(value, minimum), maximum);
|
|
};
|
|
module.exports = View.extend({
|
|
name: 'viewfinder',
|
|
className: 'js-viewfinder',
|
|
fadeTime: 360,
|
|
initialize: function () {
|
|
this.render();
|
|
this.getSize();
|
|
},
|
|
render: function () {
|
|
this.el.innerHTML = this.template();
|
|
this.els.frame = this.find('.js-frame');
|
|
this.els.video = this.find('.js-video');
|
|
this.els.videoContainer = this.find('.js-video-container');
|
|
delete this.template;
|
|
debug('rendered');
|
|
return this.bindEvents();
|
|
},
|
|
bindEvents: function () {
|
|
bind(this.el, 'click', this.onClick);
|
|
bind(this.el, 'animationend', this.onShutterEnd);
|
|
return this;
|
|
},
|
|
getSize: function () {
|
|
this.width = window.innerWidth;
|
|
this.height = window.innerHeight;
|
|
return {
|
|
width: this.width,
|
|
height: this.height
|
|
};
|
|
},
|
|
onClick: function (e) {
|
|
this.emit('click', e);
|
|
},
|
|
enableZoom: function (minimumZoom, maximumZoom) {
|
|
if (minimumZoom) {
|
|
this._minimumZoom = minimumZoom;
|
|
}
|
|
if (maximumZoom) {
|
|
this._maximumZoom = maximumZoom;
|
|
}
|
|
isZoomEnabled = true;
|
|
},
|
|
disableZoom: function () {
|
|
this._minimumZoom = 1;
|
|
this._maximumZoom = 1;
|
|
this.setZoom(1);
|
|
isZoomEnabled = false;
|
|
},
|
|
_minimumZoom: 1,
|
|
setMinimumZoom: function (minimumZoom) {
|
|
this._minimumZoom = minimumZoom;
|
|
},
|
|
_maximumZoom: 1,
|
|
setMaximumZoom: function (maximumZoom) {
|
|
this._maximumZoom = maximumZoom;
|
|
},
|
|
_zoom: 1,
|
|
setZoom: function (zoom) {
|
|
if (!isZoomEnabled) {
|
|
return;
|
|
}
|
|
this._zoom = clamp(zoom, this._minimumZoom, this._maximumZoom);
|
|
},
|
|
_useZoomPreviewAdjustment: false,
|
|
enableZoomPreviewAdjustment: function () {
|
|
this._useZoomPreviewAdjustment = true;
|
|
},
|
|
disableZoomPreviewAdjustment: function () {
|
|
this._useZoomPreviewAdjustment = false;
|
|
},
|
|
setZoomPreviewAdjustment: function (zoomPreviewAdjustment) {
|
|
if (this._useZoomPreviewAdjustment) {
|
|
this.els.video.style.transform = 'scale(' + zoomPreviewAdjustment + ')';
|
|
}
|
|
},
|
|
stopStream: function () {
|
|
this.els.video.mozSrcObject = null;
|
|
},
|
|
fadeOut: function () {
|
|
debug('fade-out');
|
|
var self = this;
|
|
this.hide();
|
|
clearTimeout(this.fadeTimeout);
|
|
this.fadeTimeout = setTimeout(function () {
|
|
self.emit('fadedout');
|
|
}, this.fadeTime);
|
|
},
|
|
fadeIn: function (firstRun) {
|
|
debug('fade-in');
|
|
this.show();
|
|
if (firstRun) {
|
|
this.emit('fadedin');
|
|
requestAnimationFrame(() => {
|
|
this.el.style.transitionDuration = this.fadeTime + 'ms';
|
|
});
|
|
} else {
|
|
this.fadeTimeout = setTimeout(this.firer('fadedin'), this.fadeTime);
|
|
}
|
|
},
|
|
shutter: function () {
|
|
this.el.classList.add('shutter');
|
|
},
|
|
onShutterEnd: function () {
|
|
this.reflow = this.el.offsetTop;
|
|
this.el.classList.remove('shutter');
|
|
},
|
|
updatePreview: function (preview, sensorAngle, mirrored) {
|
|
if (!preview) {
|
|
return;
|
|
}
|
|
// Recompute when the embedded app becomes visible or rotates.
|
|
this.getSize();
|
|
if (!this.width || !this.height) { return; }
|
|
var aspect;
|
|
if (sensorAngle % 180 === 0) {
|
|
this.container = {
|
|
width: this.width,
|
|
height: this.height,
|
|
aspect: this.width / this.height
|
|
};
|
|
aspect = preview.height / preview.width;
|
|
} else {
|
|
this.container = {
|
|
width: this.height,
|
|
height: this.width,
|
|
aspect: this.height / this.width
|
|
};
|
|
aspect = preview.width / preview.height;
|
|
}
|
|
var shouldFill = aspect > this.container.aspect;
|
|
var scaleType = this.scaleType || (shouldFill ? 'fill' : 'fit');
|
|
this.updatePreviewMetrics(preview, sensorAngle, mirrored, scaleType);
|
|
},
|
|
updatePreviewMetrics: function (preview, sensorAngle, mirrored, scaleType) {
|
|
debug('update preview scaleType: %s', scaleType, preview);
|
|
var landscape = scaleSizeTo[scaleType](this.container, preview);
|
|
var portrait = {
|
|
width: landscape.height,
|
|
height: landscape.width
|
|
};
|
|
this.els.frame.style.width = portrait.width + 'px';
|
|
this.els.frame.style.height = portrait.height + 'px';
|
|
var transform = '';
|
|
if (mirrored) {
|
|
transform += 'scale(-1, 1) ';
|
|
}
|
|
transform += 'rotate(' + sensorAngle + 'deg)';
|
|
this.els.videoContainer.style.width = landscape.width + 'px';
|
|
this.els.videoContainer.style.height = landscape.height + 'px';
|
|
this.els.videoContainer.style.transform = transform;
|
|
this.set('scaleType', scaleType);
|
|
debug('updated preview size/position', landscape);
|
|
},
|
|
template: function () {
|
|
return '<div class="viewfinder-frame js-frame">' + '<div class="viewfinder-video-container js-video-container" ' + 'aria-hidden="true">' + '<video class="viewfinder-video js-video"></video>' + '</div>' + '<div class="viewfinder-grid">' + '<div class="row"></div>' + '<div class="row middle"></div>' + '<div class="row"></div>' + '<div class="column left">' + '<div class="cell top"></div>' + '<div class="cell middle"></div>' + '<div class="cell bottom"></div>' + '</div>' + '<div class="column middle">' + '<div class="cell top"></div>' + '<div class="cell middle"></div>' + '<div class="cell bottom"></div>' + '</div>' + '<div class="column right">' + '<div class="cell top"></div>' + '<div class="cell middle"></div>' + '<div class="cell bottom"></div>' + '</div>' + '</div>' + '</div>' + '</div>';
|
|
}
|
|
});
|
|
});
|
|
define('views/focus', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'view'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('view:focus');
|
|
var View = require('view');
|
|
module.exports = View.extend({
|
|
name: 'focus',
|
|
fadeTime: 500,
|
|
initialize: function () {
|
|
this.render();
|
|
this.setFocusState('none');
|
|
},
|
|
render: function () {
|
|
this.el.innerHTML = this.template();
|
|
delete this.template;
|
|
debug('rendered');
|
|
return this;
|
|
},
|
|
setFocusState: function (state) {
|
|
this.set('state', state);
|
|
if (state !== 'focusing') {
|
|
this.fadeOut();
|
|
}
|
|
},
|
|
setFocusMode: function (mode) {
|
|
this.reset();
|
|
this.set('mode', mode);
|
|
},
|
|
setPosition: function (x, y) {
|
|
if (this.fadeOutTimer) {
|
|
clearTimeout(this.fadeOutTimer);
|
|
}
|
|
this.el.style.left = x + 'px';
|
|
this.el.style.top = y + 'px';
|
|
},
|
|
reset: function () {
|
|
this.el.style.left = '50%';
|
|
this.el.style.top = '50%';
|
|
this.set('state', 'none');
|
|
},
|
|
fadeOut: function () {
|
|
var self = this;
|
|
this.fadeOutTimer = setTimeout(hide, this.fadeTime);
|
|
function hide() {
|
|
self.reset();
|
|
}
|
|
},
|
|
template: function () {
|
|
return '<div class="focus_locking" data-icon="focus-locking" ' + 'aria-hidden="true"></div>' + '<div class="focus_locked" data-icon="focus-locked" aria-hidden="true">' + '</div>';
|
|
}
|
|
});
|
|
});
|
|
define('views/face', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'view'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var View = require('view');
|
|
module.exports = View.extend({
|
|
name: 'face',
|
|
initialize: function () {
|
|
this.render();
|
|
},
|
|
render: function () {
|
|
this.el.innerHTML = this.template();
|
|
this.el.classList.add('js-face');
|
|
},
|
|
setPosition: function (x, y) {
|
|
this.el.style.left = x + 'px';
|
|
this.el.style.top = y + 'px';
|
|
},
|
|
setDiameter: function (diameter) {
|
|
this.el.style.width = diameter + 'px';
|
|
this.el.style.height = diameter + 'px';
|
|
}
|
|
});
|
|
});
|
|
define('views/faces', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'views/face',
|
|
'view'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var FaceView = require('views/face');
|
|
var View = require('view');
|
|
module.exports = View.extend({
|
|
name: 'faces',
|
|
faces: [],
|
|
initialize: function (options) {
|
|
options = options || {};
|
|
this.el.innerHTML = this.template();
|
|
this.FaceView = options.FaceView || FaceView;
|
|
},
|
|
createFaces: function (maxNumberFaces) {
|
|
var faceView;
|
|
var i;
|
|
for (i = 0; i < maxNumberFaces; ++i) {
|
|
faceView = new this.FaceView();
|
|
faceView.hide();
|
|
this.faces.push(faceView);
|
|
faceView.appendTo(this.el);
|
|
}
|
|
},
|
|
render: function (faces) {
|
|
var self = this;
|
|
this.hideFaces();
|
|
if (faces.length > this.faces.length) {
|
|
this.createFaces(faces.length - this.faces.length);
|
|
}
|
|
faces.forEach(function (face, index) {
|
|
var faceView = self.faces[index];
|
|
self.renderFace(face, faceView);
|
|
});
|
|
},
|
|
renderFace: function (face, faceView) {
|
|
var diameter = Math.min(300, face.diameter);
|
|
faceView.setPosition(face.x, face.y);
|
|
faceView.setDiameter(diameter);
|
|
faceView.show();
|
|
},
|
|
hideFaces: function () {
|
|
this.faces.forEach(function (faceView) {
|
|
faceView.hide();
|
|
});
|
|
},
|
|
clear: function () {
|
|
var self = this;
|
|
this.faces.forEach(function (faceView) {
|
|
self.el.removeChild(faceView.el);
|
|
});
|
|
this.faces = [];
|
|
}
|
|
});
|
|
});
|
|
define('controllers/viewfinder', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'lib/camera-coordinates',
|
|
'debug',
|
|
'views/viewfinder',
|
|
'views/focus',
|
|
'views/faces',
|
|
'lib/bind-all'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var cameraCoordinates = require('lib/camera-coordinates');
|
|
var debug = require('debug')('controller:viewfinder');
|
|
var ViewfinderView = require('views/viewfinder');
|
|
var FocusView = require('views/focus');
|
|
var FacesView = require('views/faces');
|
|
var bindAll = require('lib/bind-all');
|
|
module.exports = function (app) {
|
|
return new ViewfinderController(app);
|
|
};
|
|
module.exports.ViewfinderController = ViewfinderController;
|
|
function ViewfinderController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.camera = app.camera;
|
|
this.activity = app.activity;
|
|
this.settings = app.settings;
|
|
this.createViews();
|
|
this.bindEvents();
|
|
this.configure();
|
|
debug('initialized');
|
|
}
|
|
ViewfinderController.prototype.createViews = function () {
|
|
this.views = {};
|
|
this.views.viewfinder = this.app.views.viewfinder || new ViewfinderView();
|
|
this.views.focus = this.app.views.focus || new FocusView();
|
|
this.views.faces = this.app.views.faces || new FacesView();
|
|
this.views.focus.appendTo(this.views.viewfinder.el);
|
|
this.views.faces.appendTo(this.views.viewfinder.el);
|
|
this.views.viewfinder.appendTo(this.app.el);
|
|
};
|
|
ViewfinderController.prototype.configure = function () {
|
|
var settings = this.app.settings;
|
|
var zoomSensitivity = settings.viewfinder.get('zoomGestureSensitivity');
|
|
this.sensitivity = zoomSensitivity * window.innerWidth;
|
|
this.configureScaleType();
|
|
this.configureGrid();
|
|
};
|
|
ViewfinderController.prototype.configureScaleType = function () {
|
|
var scaleType = this.app.settings.viewfinder.get('scaleType');
|
|
this.views.viewfinder.scaleType = scaleType;
|
|
debug('set scale type: %s', scaleType);
|
|
};
|
|
ViewfinderController.prototype.configureGrid = function () {
|
|
var grid = this.app.settings.grid.selected('key');
|
|
this.views.viewfinder.set('grid', grid);
|
|
};
|
|
ViewfinderController.prototype.hideGrid = function () {
|
|
this.views.viewfinder.set('grid', 'off');
|
|
};
|
|
ViewfinderController.prototype.bindEvents = function () {
|
|
window.addEventListener('resize', this.configurePreview);
|
|
this.views.viewfinder.on('fadedin', this.app.firer('viewfinder:visible'));
|
|
this.views.viewfinder.on('fadedout', this.app.firer('viewfinder:hidden'));
|
|
this.views.viewfinder.on('click', this.app.firer('viewfinder:click'));
|
|
this.views.viewfinder.on('click', this.onViewfinderClicked);
|
|
this.camera.on('zoomconfigured', this.onZoomConfigured);
|
|
this.camera.on('zoomchanged', this.onZoomChanged);
|
|
this.camera.on('preview:started', this.show);
|
|
this.app.on('camera:autofocuschanged', this.views.focus.showAutoFocusRing);
|
|
this.app.on('camera:focusstatechanged', this.views.focus.setFocusState);
|
|
this.app.on('camera:focusconfigured', this.onFocusConfigured);
|
|
this.app.on('camera:shutter', this.views.viewfinder.shutter);
|
|
this.app.on('camera:facesdetected', this.onFacesDetected);
|
|
this.app.on('camera:configured', this.onCameraConfigured);
|
|
this.app.on('camera:previewactive', this.onPreviewActive);
|
|
this.app.on('busy', this.views.viewfinder.disable);
|
|
this.app.on('ready', this.views.viewfinder.enable);
|
|
this.app.on('camera:willchange', this.hide);
|
|
this.app.on('previewgallery:opened', this.onGalleryOpened);
|
|
this.app.on('previewgallery:closed', this.onGalleryClosed);
|
|
this.app.on('settings:closed', this.onSettingsClosed);
|
|
this.app.on('settings:opened', this.onSettingsOpened);
|
|
this.app.settings.grid.on('change:selected', this.views.viewfinder.setter('grid'));
|
|
this.app.on('battery:powersave', this.onPowerSave);
|
|
this.app.on('pinch:changed', this.onPinchChanged);
|
|
this.app.on('hidden', this.stopStream);
|
|
};
|
|
ViewfinderController.prototype.onCameraConfigured = function () {
|
|
debug('configuring');
|
|
this.loadStream();
|
|
this.configurePreview();
|
|
};
|
|
ViewfinderController.prototype.show = function () {
|
|
debug('show');
|
|
if (!this.app.criticalPathDone) {
|
|
this.views.viewfinder.fadeIn(true);
|
|
return;
|
|
}
|
|
clearTimeout(this.showTimeout);
|
|
this.showTimeout = setTimeout(this.views.viewfinder.fadeIn, 280);
|
|
debug('schedule delayed fade-in');
|
|
};
|
|
ViewfinderController.prototype.hide = function () {
|
|
debug('hide');
|
|
clearTimeout(this.showTimeout);
|
|
this.views.viewfinder.fadeOut();
|
|
};
|
|
ViewfinderController.prototype.onFocusConfigured = function (config) {
|
|
this.views.focus.setFocusMode(config.mode);
|
|
this.touchFocusEnabled = config.touchFocus;
|
|
this.views.faces.clear();
|
|
};
|
|
ViewfinderController.prototype.onFacesDetected = function (faces) {
|
|
var self = this;
|
|
var faceCircles = [];
|
|
var viewfinderSize = this.views.viewfinder.getSize();
|
|
var viewportHeight = viewfinderSize.height;
|
|
var viewportWidth = viewfinderSize.width;
|
|
var sensorAngle = this.camera.getSensorAngle();
|
|
var camera = this.app.settings.cameras.selected('key');
|
|
var isFrontCamera = camera === 'front';
|
|
faces.forEach(function (face, index) {
|
|
var faceInPixels = cameraCoordinates.faceToPixels(face.bounds, viewportWidth, viewportHeight, sensorAngle, isFrontCamera);
|
|
var faceCircle = self.calculateFaceCircle(faceInPixels);
|
|
faceCircles.push(faceCircle);
|
|
});
|
|
this.views.faces.show();
|
|
this.views.faces.render(faceCircles);
|
|
};
|
|
ViewfinderController.prototype.calculateFaceCircle = function (face) {
|
|
var diameter = Math.max(face.width, face.height);
|
|
var radius = diameter / 2;
|
|
return {
|
|
x: Math.round(face.left + face.width / 2 - radius),
|
|
y: Math.round(face.top + face.height / 2 - radius),
|
|
diameter: diameter
|
|
};
|
|
};
|
|
ViewfinderController.prototype.loadStream = function () {
|
|
this.camera.loadStreamInto(this.views.viewfinder.els.video);
|
|
debug('stream started');
|
|
};
|
|
ViewfinderController.prototype.stopStream = function () {
|
|
this.views.viewfinder.stopStream();
|
|
debug('stream stopped');
|
|
};
|
|
ViewfinderController.prototype.onPowerSave = function (powerSave) {
|
|
this.views.viewfinder.els.video.mozUseScreenWakeLock = !powerSave;
|
|
};
|
|
ViewfinderController.prototype.configurePreview = function () {
|
|
var camera = this.app.settings.cameras.selected('key');
|
|
var isFrontCamera = camera === 'front';
|
|
var sensorAngle = this.camera.getSensorAngle();
|
|
var previewSize = this.camera.previewSize();
|
|
this.views.viewfinder.updatePreview(previewSize, sensorAngle, isFrontCamera);
|
|
};
|
|
ViewfinderController.prototype.onZoomConfigured = function () {
|
|
var zoomSupported = this.camera.isZoomSupported();
|
|
var zoomEnabled = this.app.settings.zoom.enabled();
|
|
var enableZoom = zoomSupported && zoomEnabled;
|
|
if (!enableZoom) {
|
|
this.views.viewfinder.disableZoom();
|
|
return;
|
|
}
|
|
if (this.app.settings.zoom.get('useZoomPreviewAdjustment')) {
|
|
this.views.viewfinder.enableZoomPreviewAdjustment();
|
|
} else {
|
|
this.views.viewfinder.disableZoomPreviewAdjustment();
|
|
}
|
|
var minimumZoom = this.camera.getMinimumZoom();
|
|
var maximumZoom = this.camera.getMaximumZoom();
|
|
this.views.viewfinder.enableZoom(minimumZoom, maximumZoom);
|
|
};
|
|
ViewfinderController.prototype.onPinchChanged = function (deltaPinch) {
|
|
var zoom = this.views.viewfinder._zoom * (1 + deltaPinch / this.sensitivity);
|
|
this.views.viewfinder.setZoom(zoom);
|
|
this.camera.setZoom(zoom);
|
|
};
|
|
ViewfinderController.prototype.onZoomChanged = function (zoom) {
|
|
var zoomPreviewAdjustment = this.camera.getZoomPreviewAdjustment();
|
|
this.views.viewfinder.setZoomPreviewAdjustment(zoomPreviewAdjustment);
|
|
this.views.viewfinder.setZoom(zoom);
|
|
};
|
|
ViewfinderController.prototype.onViewfinderClicked = function (e) {
|
|
if (!this.touchFocusEnabled) {
|
|
return;
|
|
}
|
|
this.views.faces.hide();
|
|
this.changeFocusPoint(e.pageX, e.pageY);
|
|
};
|
|
ViewfinderController.prototype.changeFocusPoint = function (x, y) {
|
|
var viewfinderSize = this.views.viewfinder.getSize();
|
|
var viewportHeight = viewfinderSize.height;
|
|
var viewportWidth = viewfinderSize.width;
|
|
var sensorAngle = this.camera.getSensorAngle();
|
|
var focusAreaSize = 10;
|
|
var focusAreaHalfSide = Math.round(focusAreaSize / 2);
|
|
var focusAreaPixels = {
|
|
left: x - focusAreaHalfSide,
|
|
top: y - focusAreaHalfSide,
|
|
right: x + focusAreaHalfSide,
|
|
bottom: y + focusAreaHalfSide,
|
|
width: focusAreaSize,
|
|
height: focusAreaSize
|
|
};
|
|
var camera = this.app.settings.cameras.selected('key');
|
|
var isFrontCamera = camera === 'front';
|
|
var focusArea = cameraCoordinates.faceToCamera(focusAreaPixels, viewportWidth, viewportHeight, sensorAngle, isFrontCamera);
|
|
var focusPoint = {
|
|
x: x,
|
|
y: y,
|
|
area: focusArea
|
|
};
|
|
this.views.focus.setPosition(x, y);
|
|
this.app.emit('viewfinder:focuspointchanged', focusPoint);
|
|
};
|
|
ViewfinderController.prototype.onSettingsOpened = function () {
|
|
this.hideGrid();
|
|
this.views.viewfinder.set('ariaHidden', true);
|
|
};
|
|
ViewfinderController.prototype.onSettingsClosed = function () {
|
|
this.configureGrid();
|
|
this.views.viewfinder.set('ariaHidden', false);
|
|
};
|
|
ViewfinderController.prototype.onGalleryOpened = function () {
|
|
this.views.viewfinder.disable();
|
|
this.views.viewfinder.set('ariaHidden', true);
|
|
};
|
|
ViewfinderController.prototype.onGalleryClosed = function () {
|
|
this.views.viewfinder.enable();
|
|
this.views.viewfinder.set('ariaHidden', false);
|
|
};
|
|
ViewfinderController.prototype.onPreviewActive = function (active) {
|
|
if (!active) {
|
|
this.stopStream();
|
|
}
|
|
};
|
|
});
|
|
define('lib/get-aspect', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
module.exports = function (w, h) {
|
|
var gcd = function (a, b) {
|
|
return b === 0 ? a : gcd(b, a % b);
|
|
};
|
|
var divisor = gcd(w, h);
|
|
return w / divisor + ':' + h / divisor;
|
|
};
|
|
});
|
|
define('lib/format-recorder-profiles', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'./get-aspect'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var getAspect = require('./get-aspect');
|
|
module.exports = function (profiles, options) {
|
|
var exclude = options && options.exclude || [];
|
|
var items = [];
|
|
var hash = {};
|
|
for (var key in profiles) {
|
|
if (!profiles.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
var profile = profiles[key];
|
|
var video = profile.video;
|
|
var sizeKey = video.width + 'x' + video.height;
|
|
if (hash[sizeKey]) {
|
|
continue;
|
|
}
|
|
if (exclude.indexOf(key) > -1) {
|
|
continue;
|
|
}
|
|
var pixelSize = video.width * video.height;
|
|
var aspect = getAspect(video.width, video.height);
|
|
hash[sizeKey] = key;
|
|
items.push({
|
|
key: key,
|
|
title: key + ' ' + sizeKey + ' ' + aspect,
|
|
pixelSize: pixelSize,
|
|
raw: profile
|
|
});
|
|
}
|
|
items.sort(function (a, b) {
|
|
return b.pixelSize - a.pixelSize;
|
|
});
|
|
return items;
|
|
};
|
|
});
|
|
define('lib/format-picture-sizes', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'./get-aspect'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var getAspect = require('./get-aspect');
|
|
module.exports = function (sizes, options) {
|
|
var maxPixelSize = options && options.maxPixelSize;
|
|
var exclude = options && options.exclude || {};
|
|
var include = options && options.include;
|
|
var formatted = [];
|
|
var hash = {};
|
|
exclude.aspects = exclude.aspects || [];
|
|
exclude.keys = exclude.keys || [];
|
|
sizes.forEach(function (size) {
|
|
var w = size.width;
|
|
var h = size.height;
|
|
var key = w + 'x' + h;
|
|
var pixelSize = w * h;
|
|
if (hash[key]) {
|
|
return;
|
|
}
|
|
size.aspect = getAspect(w, h);
|
|
if (maxPixelSize && pixelSize > maxPixelSize) {
|
|
return;
|
|
}
|
|
if (include) {
|
|
if (include.keys && !~include.keys.indexOf(key)) {
|
|
return;
|
|
}
|
|
if (include.aspects && !~include.aspects.indexOf(size.aspect)) {
|
|
return;
|
|
}
|
|
}
|
|
if (exclude.keys.indexOf(key) > -1) {
|
|
return;
|
|
}
|
|
if (exclude.aspects.indexOf(size.aspect) > -1) {
|
|
return;
|
|
}
|
|
size.mp = getMP(w, h);
|
|
hash[key] = true;
|
|
formatted.push({
|
|
key: key,
|
|
pixelSize: pixelSize,
|
|
data: size
|
|
});
|
|
});
|
|
formatted.sort(function (a, b) {
|
|
return b.pixelSize - a.pixelSize;
|
|
});
|
|
return formatted;
|
|
};
|
|
function getMP(w, h) {
|
|
return Math.round(w * h / 1000000);
|
|
}
|
|
});
|
|
define('controllers/settings', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'lib/format-recorder-profiles',
|
|
'lib/format-picture-sizes',
|
|
'debug',
|
|
'lib/bind-all'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var formatRecorderProfiles = require('lib/format-recorder-profiles');
|
|
var formatPictureSizes = require('lib/format-picture-sizes');
|
|
var debug = require('debug')('controller:settings');
|
|
var bindAll = require('lib/bind-all');
|
|
module.exports = function (app) {
|
|
return new SettingsController(app);
|
|
};
|
|
module.exports.SettingsController = SettingsController;
|
|
function SettingsController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.require = app.require;
|
|
this.settings = app.settings;
|
|
this.activity = app.activity;
|
|
this.notification = app.views.notification;
|
|
this.nav = app.nav || navigator;
|
|
this.SettingsView = app.SettingsView;
|
|
this.formatPictureSizes = app.formatPictureSizes || formatPictureSizes;
|
|
this.formatRecorderProfiles = app.formatRecorderProfiles || formatRecorderProfiles;
|
|
this.configure();
|
|
this.bindEvents();
|
|
debug('initialized');
|
|
}
|
|
SettingsController.prototype.configure = function () {
|
|
this.setupRecorderProfilesAlias();
|
|
this.setupPictureSizesAlias();
|
|
this.setupFlashModesAlias();
|
|
};
|
|
SettingsController.prototype.setupRecorderProfilesAlias = function () {
|
|
var settings = this.settings;
|
|
this.settings.alias({
|
|
key: 'recorderProfiles',
|
|
settings: {
|
|
back: this.settings.recorderProfilesBack,
|
|
front: this.settings.recorderProfilesFront
|
|
},
|
|
get: function () {
|
|
var camera = settings.cameras.selected('key');
|
|
return this.settings[camera];
|
|
}
|
|
});
|
|
};
|
|
SettingsController.prototype.setupPictureSizesAlias = function () {
|
|
var settings = this.settings;
|
|
this.settings.alias({
|
|
key: 'pictureSizes',
|
|
settings: {
|
|
back: this.settings.pictureSizesBack,
|
|
front: this.settings.pictureSizesFront
|
|
},
|
|
get: function () {
|
|
var camera = settings.cameras.selected('key');
|
|
return this.settings[camera];
|
|
}
|
|
});
|
|
};
|
|
SettingsController.prototype.setupFlashModesAlias = function () {
|
|
var settings = this.settings;
|
|
this.settings.alias({
|
|
key: 'flashModes',
|
|
settings: {
|
|
picture: this.settings.flashModesPicture,
|
|
video: this.settings.flashModesVideo
|
|
},
|
|
get: function () {
|
|
var mode = settings.mode.selected('key');
|
|
return this.settings[mode];
|
|
}
|
|
});
|
|
};
|
|
SettingsController.prototype.bindEvents = function () {
|
|
this.app.on('localized', this.formatPictureSizeTitles);
|
|
this.app.on('settings:toggle', this.toggleSettings);
|
|
this.app.on('camera:newcamera', this.onNewCamera);
|
|
this.app.on('activity:pick', this.onPickActivity);
|
|
};
|
|
SettingsController.prototype.toggleSettings = function () {
|
|
if (this.view) {
|
|
this.closeSettings();
|
|
} else {
|
|
this.openSettings();
|
|
}
|
|
};
|
|
SettingsController.prototype.openSettings = function () {
|
|
debug('open settings');
|
|
var self = this;
|
|
this.app.emit('busy', 'lazyLoading');
|
|
this.require(['views/settings'], function (SettingsView) {
|
|
self.app.emit('ready');
|
|
if (!self.SettingsView) {
|
|
self.SettingsView = SettingsView;
|
|
}
|
|
if (self.view) {
|
|
return;
|
|
}
|
|
var items = self.menuItems();
|
|
self.view = new self.SettingsView({ items: items }).render().appendTo(self.app.el).on('click:close', self.closeSettings).on('click:option', self.onOptionTap);
|
|
self.view.hide();
|
|
self.view.fadeIn();
|
|
self.app.emit('settings:opened');
|
|
debug('settings opened');
|
|
});
|
|
};
|
|
SettingsController.prototype.closeSettings = function (done) {
|
|
debug('close settings');
|
|
if (!this.view) {
|
|
return;
|
|
}
|
|
var self = this;
|
|
this.view.fadeOut(function () {
|
|
self.view.destroy();
|
|
self.view = null;
|
|
self.app.emit('settings:closed');
|
|
debug('settings closed');
|
|
if (typeof done === 'function') {
|
|
done();
|
|
}
|
|
});
|
|
};
|
|
SettingsController.prototype.onOptionTap = function (key, setting) {
|
|
var flashMode = this.settings.flashModesPicture.selected('key');
|
|
var ishdrOn = setting.key === 'hdr' && key === 'on';
|
|
var flashDeactivated = flashMode !== 'off' && ishdrOn;
|
|
var self = this;
|
|
self.closeSettings(function () {
|
|
setting.select(key);
|
|
self.notify(setting, flashDeactivated);
|
|
});
|
|
};
|
|
SettingsController.prototype.onPickActivity = function (data) {
|
|
debug('pick activity', data);
|
|
var setting;
|
|
var options;
|
|
var updated = false;
|
|
var maxFileSize = data.maxFileSizeBytes;
|
|
var maxPixelSize = data.maxPixelSize;
|
|
this.settings.dontSave();
|
|
if (maxPixelSize) {
|
|
setting = this.settings.pictureSizes;
|
|
var lastMaxPixelSize = setting.get('maxPixelSize');
|
|
this.settings.pictureSizesFront.set('maxPixelSize', maxPixelSize);
|
|
this.settings.pictureSizesBack.set('maxPixelSize', maxPixelSize);
|
|
debug('set maxPixelSize: %s', maxPixelSize);
|
|
if (lastMaxPixelSize !== maxPixelSize) {
|
|
options = setting.get('options');
|
|
var restricted = [];
|
|
if (options && options.length > 0) {
|
|
options.forEach(function (option) {
|
|
if (option.pixelSize <= maxPixelSize) {
|
|
restricted.push(option);
|
|
}
|
|
});
|
|
setting.resetOptions(restricted);
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
if (maxFileSize) {
|
|
setting = this.settings.recorderProfiles;
|
|
var lastMaxFileSize = setting.get('maxFileSizeBytes');
|
|
this.settings.recorderProfilesFront.set('maxFileSizeBytes', maxFileSize);
|
|
this.settings.recorderProfilesBack.set('maxFileSizeBytes', maxFileSize);
|
|
debug('set maxFileSize: %s', maxFileSize);
|
|
if (lastMaxFileSize !== maxFileSize) {
|
|
options = setting.get('options');
|
|
if (options && options.length > 1) {
|
|
setting.resetOptions([options[options.length - 1]]);
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
if (updated) {
|
|
this.app.emit('settings:configured');
|
|
}
|
|
};
|
|
SettingsController.prototype.notify = function (setting, flashDeactivated) {
|
|
var dontNotify = setting.get('notifications') === false;
|
|
if (dontNotify) {
|
|
return;
|
|
}
|
|
var localizable = setting.get('optionsLocalizable') !== false;
|
|
var title = '<span data-l10n-id="' + setting.get('title') + '"></span>';
|
|
var html;
|
|
var optionTitle = localizable ? '<span data-l10n-id="' + setting.selected('title') + '"></span>' : '<span>' + setting.selected('title') + '</span>';
|
|
if (flashDeactivated) {
|
|
html = title + ' ' + optionTitle + '<br/>' + '<span data-l10n-id="flash-deactivated"></span>';
|
|
} else {
|
|
html = title + '<br/>' + optionTitle;
|
|
}
|
|
this.notification.display({ text: { html: html } });
|
|
};
|
|
SettingsController.prototype.onNewCamera = function (camera) {
|
|
debug('new capabilities');
|
|
var capabilities = camera.capabilities;
|
|
this.settings.hdr.filterOptions(capabilities.hdr);
|
|
this.settings.flashModesPicture.filterOptions(capabilities.flashModes);
|
|
this.settings.flashModesVideo.filterOptions(capabilities.flashModes);
|
|
this.configurePictureSizes(camera);
|
|
this.configureRecorderProfiles(camera);
|
|
this.app.emit('settings:configured');
|
|
debug('settings configured to new capabilities');
|
|
};
|
|
SettingsController.prototype.configurePictureSizes = function (camera) {
|
|
debug('configuring picture sizes');
|
|
var maxPixelSize = window.CONFIG_MAX_IMAGE_PIXEL_SIZE;
|
|
var sizes = camera.capabilities.pictureSizes;
|
|
var setting = this.settings.pictureSizes;
|
|
var currentSize = camera.pictureSize;
|
|
var currentSizeKey = currentSize.width + 'x' + currentSize.height;
|
|
var options = {
|
|
exclude: setting.get('exclude'),
|
|
include: setting.get('include'),
|
|
maxPixelSize: maxPixelSize
|
|
};
|
|
var formatted = this.formatPictureSizes(sizes, options);
|
|
setting.resetOptions(formatted);
|
|
if (!setting.current().fetched) {
|
|
setting.select(currentSizeKey, { silent: true });
|
|
}
|
|
this.formatPictureSizeTitles();
|
|
debug('configured pictureSizes', setting.selected('key'));
|
|
};
|
|
SettingsController.prototype.configureRecorderProfiles = function (camera) {
|
|
var sizes = camera.capabilities.recorderProfiles;
|
|
var currentProfile = camera.recorderProfile;
|
|
var setting = this.settings.recorderProfiles;
|
|
var maxFileSize = setting.get('maxFileSizeBytes');
|
|
var exclude = setting.get('exclude');
|
|
var options = { exclude: exclude };
|
|
var items = this.formatRecorderProfiles(sizes, options);
|
|
if (maxFileSize) {
|
|
items = [items[items.length - 1]];
|
|
}
|
|
setting.resetOptions(items);
|
|
if (!maxFileSize && !setting.current().fetched) {
|
|
setting.select(currentProfile, { silent: true });
|
|
}
|
|
};
|
|
SettingsController.prototype.formatPictureSizeTitles = function () {
|
|
return document.l10n.formatValue('mp').then(value => {
|
|
var options = this.settings.pictureSizes.get('options');
|
|
options.forEach(function (size) {
|
|
var data = size.data;
|
|
var mp = data.mp ? data.mp + value + ' ' : '';
|
|
size.title = mp + size.key;
|
|
});
|
|
debug('picture size titles formatted');
|
|
});
|
|
};
|
|
SettingsController.prototype.menuItems = function () {
|
|
var items = this.settings.settingsMenu.get('items');
|
|
return items.filter(this.validMenuItem, this).map(function (item) {
|
|
return this.settings[item.key];
|
|
}, this);
|
|
};
|
|
SettingsController.prototype.validMenuItem = function (item) {
|
|
var setting = this.settings[item.key];
|
|
return !!setting && setting.supported();
|
|
};
|
|
});
|
|
define('lib/bytes-to-pixels', [
|
|
'require',
|
|
'exports',
|
|
'module'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
module.exports = function (bytes) {
|
|
var bytesPerPixel = 3;
|
|
var avgJpegCompression = window.CONFIG_AVG_JPEG_COMPRESSION_RATIO || 8;
|
|
var uncompressedBytes = bytes * avgJpegCompression;
|
|
return Math.round(uncompressedBytes / bytesPerPixel);
|
|
};
|
|
});
|
|
define('controllers/activity', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bytes-to-pixels',
|
|
'lib/bind-all'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('controller:activity');
|
|
var bytesToPixels = require('lib/bytes-to-pixels');
|
|
var bindAll = require('lib/bind-all');
|
|
module.exports = function (app) {
|
|
return new ActivityController(app);
|
|
};
|
|
module.exports.ActivityController = ActivityController;
|
|
function ActivityController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.win = app.win;
|
|
this.settings = app.settings;
|
|
this.configure();
|
|
this.bindEvents();
|
|
debug('initialized');
|
|
}
|
|
ActivityController.prototype.types = {
|
|
pick: 'pick',
|
|
record: 'record'
|
|
};
|
|
ActivityController.prototype.configure = function () {
|
|
this.name = this.getName();
|
|
this.app.activity[this.name] = true;
|
|
};
|
|
ActivityController.prototype.bindEvents = function () {
|
|
this.app.on('activitycanceled', this.onActivityCanceled);
|
|
this.app.on('confirm:selected', this.onActivityConfirmed);
|
|
if (this.name) {
|
|
this.setupListener();
|
|
} else {
|
|
this.app.once('criticalpathdone', this.setupListener);
|
|
}
|
|
};
|
|
ActivityController.prototype.setupListener = function () {
|
|
debug('setup listener');
|
|
navigator.mozSetMessageHandler('activity', this.onMessage);
|
|
debug('listener setup');
|
|
};
|
|
ActivityController.prototype.getName = function () {
|
|
var hash = this.win.location.hash;
|
|
var name = hash && hash.substr(1);
|
|
return this.types[name];
|
|
};
|
|
ActivityController.prototype.onMessage = function (activity) {
|
|
debug('incoming activity', activity);
|
|
var name = activity.source.name;
|
|
var supported = this.types[name];
|
|
if (!supported) {
|
|
return;
|
|
}
|
|
var data = {
|
|
name: name,
|
|
maxPixelSize: this.getMaxPixelSize(activity),
|
|
maxFileSizeBytes: activity.source.data.maxFileSizeBytes
|
|
};
|
|
this.activity = activity;
|
|
this.configureMode(activity);
|
|
this.app.emit('activity', data);
|
|
this.app.emit('activity:' + name, data);
|
|
};
|
|
ActivityController.prototype.configureMode = function (activity) {
|
|
var type = activity.source.data.type;
|
|
var name = activity.source.name;
|
|
var modes = name === 'pick' ? this.getModesForPick(type) : this.getModesForRecord(type);
|
|
this.settings.mode.filterOptions(modes);
|
|
this.settings.mode.select(modes[0]);
|
|
debug('configured mode', modes);
|
|
};
|
|
ActivityController.prototype.getMaxPixelSize = function (activity) {
|
|
var data = activity.source.data;
|
|
var bytes = data.maxFileSizeBytes;
|
|
var maxPickPixelSize = this.settings.activity.get('maxPickPixelSize') || 0;
|
|
var maxPixelSize;
|
|
if (bytes) {
|
|
maxPixelSize = bytesToPixels(bytes);
|
|
} else if (data.width || data.height) {
|
|
maxPixelSize = this.getMaxPixelsFromSize(data);
|
|
} else {
|
|
maxPixelSize = maxPickPixelSize;
|
|
}
|
|
if (maxPickPixelSize > 0) {
|
|
maxPixelSize = Math.min(maxPixelSize, maxPickPixelSize);
|
|
}
|
|
debug('maxPixelsSize: %s', maxPixelSize);
|
|
return maxPixelSize;
|
|
};
|
|
ActivityController.prototype.getMaxPixelsFromSize = function (size) {
|
|
var scale = this.settings.activity.get('maxPixelSizeScaleFactor');
|
|
var aspect = 4 / 3;
|
|
var width = size.width || size.height * aspect;
|
|
var height = size.height || size.width * aspect;
|
|
var pixels = width * height;
|
|
return pixels * scale;
|
|
};
|
|
ActivityController.prototype.getModesForPick = function (types) {
|
|
types = [].concat(types || []);
|
|
var modes = [];
|
|
types.forEach(function (item) {
|
|
var type = item.split('/')[0];
|
|
var mode = type === 'image' ? 'picture' : type;
|
|
if (modes.indexOf(mode) === -1) {
|
|
modes.push(mode);
|
|
}
|
|
});
|
|
if (modes.length === 0) {
|
|
modes = [
|
|
'picture',
|
|
'video'
|
|
];
|
|
}
|
|
return modes;
|
|
};
|
|
ActivityController.prototype.getModesForRecord = function (type) {
|
|
return type === 'videos' ? [
|
|
'video',
|
|
'picture'
|
|
] : [
|
|
'picture',
|
|
'video'
|
|
];
|
|
};
|
|
ActivityController.prototype.onActivityCanceled = function () {
|
|
if (!this.activity) {
|
|
return;
|
|
}
|
|
this.activity.postError('pick cancelled');
|
|
};
|
|
ActivityController.prototype.onActivityConfirmed = function (newMedia) {
|
|
var self = this;
|
|
var activity = this.activity;
|
|
var media = { blob: newMedia.blob };
|
|
this.app.showSpinner();
|
|
if (newMedia.isVideo) {
|
|
media.type = 'video/3gpp';
|
|
media.poster = newMedia.poster.blob;
|
|
activity.postResult(media);
|
|
this.app.clearSpinner();
|
|
} else {
|
|
media.type = 'image/jpeg';
|
|
require(['lib/resize-image-and-save'], function (resizeImageAndSave) {
|
|
resizeImageAndSave({
|
|
blob: newMedia.blob,
|
|
width: activity.source.data.width,
|
|
height: activity.source.data.height
|
|
}, function onImageResized(resizedBlob) {
|
|
media.blob = resizedBlob;
|
|
activity.postResult(media);
|
|
self.app.clearSpinner();
|
|
});
|
|
});
|
|
}
|
|
};
|
|
});
|
|
define('controllers/camera', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bind-all'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('controller:camera');
|
|
var bindAll = require('lib/bind-all');
|
|
module.exports = function (app) {
|
|
return new CameraController(app);
|
|
};
|
|
module.exports.CameraController = CameraController;
|
|
function CameraController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.camera = app.camera;
|
|
this.settings = app.settings;
|
|
this.activity = app.activity;
|
|
this.hdrDisabled = this.settings.hdr.get('disabled');
|
|
this.notification = app.views.notification;
|
|
this.lowBattery = this.app.get('batteryStatus') === 'shutdown';
|
|
this.configure();
|
|
this.bindEvents();
|
|
debug('initialized');
|
|
}
|
|
CameraController.prototype.bindEvents = function () {
|
|
var settings = this.settings;
|
|
var camera = this.camera;
|
|
var app = this.app;
|
|
camera.on('change:previewActive', this.app.firer('camera:previewactive'));
|
|
camera.on('change:videoElapsed', app.firer('camera:recorderTimeUpdate'));
|
|
camera.on('autofocuschanged', app.firer('camera:autofocuschanged'));
|
|
camera.on('focusconfigured', app.firer('camera:focusconfigured'));
|
|
camera.on('change:focus', app.firer('camera:focusstatechanged'));
|
|
camera.on('filesizelimitreached', this.onFileSizeLimitReached);
|
|
camera.on('facesdetected', app.firer('camera:facesdetected'));
|
|
camera.on('willrecord', app.firer('camera:willrecord'));
|
|
camera.on('configured', app.firer('camera:configured'));
|
|
camera.on('requesting', app.firer('camera:requesting'));
|
|
camera.on('change:recording', this.onRecordingChange);
|
|
camera.on('newcamera', app.firer('camera:newcamera'));
|
|
camera.on('newimage', app.firer('camera:newimage'));
|
|
camera.on('newvideo', app.firer('camera:newvideo'));
|
|
camera.on('shutter', app.firer('camera:shutter'));
|
|
camera.on('loaded', app.firer('camera:loaded'));
|
|
camera.on('closed', this.onCameraClosed);
|
|
camera.on('error', app.firer('camera:error'));
|
|
camera.on('ready', app.firer('ready'));
|
|
camera.on('busy', app.firer('busy'));
|
|
app.on('viewfinder:focuspointchanged', this.onFocusPointChanged);
|
|
app.on('change:batteryStatus', this.onBatteryStatusChange);
|
|
app.on('settings:configured', this.onSettingsConfigured);
|
|
app.on('previewgallery:opened', this.onGalleryOpened);
|
|
app.on('previewgallery:closed', this.onGalleryClosed);
|
|
app.on('stoprecording', this.camera.stopRecording);
|
|
app.on('storage:volumechanged', this.onStorageVolumeChanged);
|
|
app.on('storage:changed', this.onStorageChanged);
|
|
app.on('activity:pick', this.onPickActivity);
|
|
app.on('keydown:capture', this.onCaptureKey);
|
|
app.on('keydown:focus', this.onFocusKey);
|
|
app.on('hidden', this.shutdownCamera);
|
|
app.on('click', this.clearCountdown);
|
|
app.on('visible', this.loadCamera);
|
|
app.on('capture', this.capture);
|
|
settings.recorderProfiles.on('change:selected', this.updateRecorderProfile);
|
|
settings.pictureSizes.on('change:selected', this.updatePictureSize);
|
|
settings.flashModes.on('change:selected', this.onFlashModeChange);
|
|
settings.flashModes.on('change:selected', this.setFlashMode);
|
|
settings.cameras.on('change:selected', this.setCamera);
|
|
settings.mode.on('change:selected', this.setMode);
|
|
settings.hdr.on('change:selected', this.setHDR);
|
|
settings.hdr.on('change:selected', this.onHDRChange);
|
|
debug('events bound');
|
|
};
|
|
CameraController.prototype.onRecordingChange = function (recording) {
|
|
var active;
|
|
if (recording === 'started') {
|
|
active = true;
|
|
} else if (recording === 'stopped') {
|
|
active = false;
|
|
} else {
|
|
return;
|
|
}
|
|
this.app.set('recording', active);
|
|
};
|
|
CameraController.prototype.onCaptureKey = function (e) {
|
|
debug('on capture key', e);
|
|
if (this.capture() !== false) {
|
|
e.preventDefault();
|
|
}
|
|
};
|
|
CameraController.prototype.onFocusKey = function (e) {
|
|
debug('on focus key', e);
|
|
if (!this.shouldCapture()) {
|
|
return;
|
|
}
|
|
this.camera.focus.focus();
|
|
};
|
|
CameraController.prototype.configure = function () {
|
|
this.settings.cameras.filterOptions(this.camera.cameraList);
|
|
debug('configured');
|
|
};
|
|
CameraController.prototype.onSettingsConfigured = function () {
|
|
var recorderProfile = this.settings.recorderProfiles.selected('key');
|
|
var pictureSize = this.settings.pictureSizes.selected('data');
|
|
this.setWhiteBalance();
|
|
this.setFlashMode();
|
|
this.setISO();
|
|
this.setHDR();
|
|
this.camera.setRecorderProfile(recorderProfile);
|
|
this.camera.setPictureSize(pictureSize);
|
|
this.camera.configureZoom();
|
|
setTimeout(this.updateZoomForMako);
|
|
debug('camera configured with final settings');
|
|
};
|
|
CameraController.prototype.onPickActivity = function (data) {
|
|
this.camera.set('maxFileSizeBytes', data.maxFileSizeBytes);
|
|
this.camera.cacheConfig = false;
|
|
};
|
|
CameraController.prototype.capture = function (options = {}) {
|
|
var force = options.force;
|
|
if (!this.shouldCapture()) {
|
|
return false;
|
|
}
|
|
if (!force && this.shouldCountdown()) {
|
|
return this.startCountdown();
|
|
}
|
|
if (this.countdown) {
|
|
return this.clearCountdown();
|
|
}
|
|
var position = this.app.geolocation.position;
|
|
return this.camera.capture({ position: position });
|
|
};
|
|
CameraController.prototype.shouldCapture = function () {
|
|
return !this.app.get('confirmViewVisible') && !this.app.hidden && !this.app.busy && !this.galleryOpen && !this.lowBattery;
|
|
};
|
|
CameraController.prototype.shouldCountdown = function () {
|
|
var countdownSet = this.settings.countdown.selected('value');
|
|
var recording = this.app.get('recording');
|
|
return countdownSet && !this.countdown && !recording;
|
|
};
|
|
CameraController.prototype.startCountdown = function () {
|
|
if (this.countdown) {
|
|
return;
|
|
}
|
|
var seconds = this.settings.countdown.selected('value');
|
|
var self = this;
|
|
if (!seconds) {
|
|
return;
|
|
}
|
|
this.app.emit('countdown:started', seconds);
|
|
(function scheduleTick() {
|
|
self.countdown = setTimeout(() => {
|
|
if (--seconds <= 0) {
|
|
self.clearCountdown();
|
|
self.capture({ force: true });
|
|
return;
|
|
}
|
|
self.app.emit('countdown:tick', seconds);
|
|
scheduleTick();
|
|
}, 1000);
|
|
}());
|
|
};
|
|
CameraController.prototype.clearCountdown = function () {
|
|
if (!this.countdown) {
|
|
return;
|
|
}
|
|
clearTimeout(this.countdown);
|
|
this.countdown = null;
|
|
this.app.emit('countdown:ended');
|
|
};
|
|
CameraController.prototype.onFileSizeLimitReached = function () {
|
|
this.camera.stopRecording();
|
|
this.showSizeLimitAlert();
|
|
};
|
|
CameraController.prototype.showSizeLimitAlert = function () {
|
|
if (this.sizeLimitAlertActive) {
|
|
return;
|
|
}
|
|
this.sizeLimitAlertActive = true;
|
|
var alertL10nId = this.activity.pick ? 'activity-size-limit-reached' : 'storage-size-limit-reached';
|
|
document.l10n.formatValue(alertL10nId).then(alert);
|
|
this.sizeLimitAlertActive = false;
|
|
};
|
|
CameraController.prototype.setMode = function (mode) {
|
|
debug('set mode: %s', mode);
|
|
var self = this;
|
|
if (this.camera.isMode(mode)) {
|
|
debug('mode didn\'t change');
|
|
return;
|
|
}
|
|
var l10nId = mode == 'video' ? 'Video-Mode' : 'Photo-Mode';
|
|
this.notification.display({ text: l10nId });
|
|
this.setFlashMode();
|
|
this.app.emit('camera:willchange');
|
|
this.app.once('viewfinder:hidden', function () {
|
|
self.camera.setMode(mode);
|
|
});
|
|
};
|
|
CameraController.prototype.updatePictureSize = function () {
|
|
debug('update picture-size');
|
|
var pictureMode = this.settings.mode.selected('key') === 'picture';
|
|
var value = this.settings.pictureSizes.selected('data');
|
|
var self = this;
|
|
if (this.camera.isPictureSize(value)) {
|
|
return;
|
|
}
|
|
if (!pictureMode) {
|
|
this.camera.setPictureSize(value, { configure: false });
|
|
return;
|
|
}
|
|
this.app.emit('camera:willchange');
|
|
this.app.once('viewfinder:hidden', function () {
|
|
self.camera.setPictureSize(value);
|
|
});
|
|
};
|
|
CameraController.prototype.updateRecorderProfile = function () {
|
|
debug('update recorder-profile');
|
|
var videoMode = this.settings.mode.selected('key') === 'video';
|
|
var key = this.settings.recorderProfiles.selected('key');
|
|
var self = this;
|
|
if (this.camera.isRecorderProfile(key)) {
|
|
return;
|
|
}
|
|
if (!videoMode) {
|
|
this.camera.setRecorderProfile(key, { configure: false });
|
|
return;
|
|
}
|
|
this.app.emit('camera:willchange');
|
|
this.app.once('viewfinder:hidden', function () {
|
|
self.camera.setRecorderProfile(key);
|
|
});
|
|
};
|
|
CameraController.prototype.setCamera = function (camera) {
|
|
debug('set camera: %s', camera);
|
|
var self = this;
|
|
this.app.emit('camera:willchange');
|
|
this.app.once('viewfinder:hidden', function () {
|
|
self.camera.setCamera(camera);
|
|
});
|
|
};
|
|
CameraController.prototype.setFlashMode = function () {
|
|
var flashSetting = this.settings.flashModes;
|
|
this.camera.setFlashMode(flashSetting.selected('key'));
|
|
};
|
|
CameraController.prototype.setISO = function () {
|
|
if (!this.settings.isoModes.get('disabled')) {
|
|
this.camera.setISOMode(this.settings.isoModes.selected('key'));
|
|
}
|
|
};
|
|
CameraController.prototype.setWhiteBalance = function () {
|
|
if (!this.settings.whiteBalance.get('disabled')) {
|
|
this.camera.setWhiteBalance(this.settings.whiteBalance.selected('key'));
|
|
}
|
|
};
|
|
CameraController.prototype.setHDR = function () {
|
|
if (this.hdrDisabled) {
|
|
return;
|
|
}
|
|
this.camera.setHDR(this.settings.hdr.selected('key'));
|
|
};
|
|
CameraController.prototype.onFlashModeChange = function (flashModes) {
|
|
if (this.hdrDisabled) {
|
|
return;
|
|
}
|
|
var ishdrOn = this.settings.hdr.selected('key') === 'on';
|
|
if (ishdrOn && flashModes !== 'off') {
|
|
this.settings.hdr.select('off');
|
|
}
|
|
};
|
|
CameraController.prototype.onHDRChange = function (hdr) {
|
|
var flashMode = this.settings.flashModesPicture.selected('key');
|
|
var ishdrOn = hdr === 'on';
|
|
if (ishdrOn && flashMode !== 'off') {
|
|
this.settings.flashModesPicture.select('off');
|
|
}
|
|
};
|
|
CameraController.prototype.onBatteryStatusChange = function (status) {
|
|
this.lowBattery = status === 'shutdown';
|
|
if (this.lowBattery) {
|
|
this.shutdownCamera();
|
|
} else {
|
|
this.loadCamera();
|
|
}
|
|
};
|
|
CameraController.prototype.onStorageChanged = function (state) {
|
|
if (state !== 'available') {
|
|
this.camera.stopRecording();
|
|
}
|
|
};
|
|
CameraController.prototype.onStorageVolumeChanged = function (storage) {
|
|
this.camera.setStorage(storage);
|
|
};
|
|
CameraController.prototype.onFocusPointChanged = function (focusPoint) {
|
|
if (this.countdown) {
|
|
return;
|
|
}
|
|
this.camera.updateFocusArea(focusPoint.area);
|
|
};
|
|
CameraController.prototype.loadCamera = function (showSpinner) {
|
|
if (this.lowBattery || this.galleryOpen || this.app.hidden) {
|
|
return;
|
|
}
|
|
if (showSpinner) {
|
|
this.app.showSpinner();
|
|
}
|
|
this.camera.load();
|
|
};
|
|
CameraController.prototype.shutdownCamera = function () {
|
|
this.clearCountdown();
|
|
this.camera.shutdown();
|
|
};
|
|
CameraController.prototype.onCameraClosed = function (reason) {
|
|
reason = reason || 'SystemFailure';
|
|
if (reason === 'SystemFailure') {
|
|
this.app.emit('reboot');
|
|
}
|
|
};
|
|
CameraController.prototype.onGalleryOpened = function () {
|
|
this.galleryOpen = true;
|
|
this.shutdownCamera();
|
|
};
|
|
CameraController.prototype.onGalleryClosed = function (reason) {
|
|
this.galleryOpen = false;
|
|
if (this.app.hidden) {
|
|
return;
|
|
}
|
|
this.loadCamera(true);
|
|
};
|
|
CameraController.prototype.updateZoomForMako = function () {
|
|
debug('update zoom for mako');
|
|
var self = this;
|
|
navigator.mozSettings.createLock().get('deviceinfo.hardware').onsuccess = onSuccess;
|
|
debug('settings request made');
|
|
function onSuccess(e) {
|
|
var device = e.target.result['deviceinfo.hardware'];
|
|
if (device !== 'mako') {
|
|
return;
|
|
}
|
|
var frontCamera = self.camera.selectedCamera === 'front';
|
|
var maxHardwareZoom = frontCamera ? 1 : 1.25;
|
|
self.settings.zoom.set('useZoomPreviewAdjustment', true);
|
|
self.camera.set('maxHardwareZoom', maxHardwareZoom);
|
|
self.camera.emit('zoomconfigured', self.camera.getZoom());
|
|
debug('zoom reconfigured for mako');
|
|
}
|
|
};
|
|
});
|
|
define('lib/storage', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bind-all',
|
|
'evt'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('storage');
|
|
var bindAll = require('lib/bind-all');
|
|
var events = require('evt');
|
|
var storageSingleton;
|
|
module.exports = Storage;
|
|
events(Storage.prototype);
|
|
function Storage(options) {
|
|
if (storageSingleton) {
|
|
return storageSingleton;
|
|
}
|
|
storageSingleton = this;
|
|
bindAll(this);
|
|
this.maxFileSize = 0;
|
|
options = options || {};
|
|
this.dcf = options.dcf;
|
|
if (this.dcf) {
|
|
this.dcf.init();
|
|
}
|
|
this.require = options.require || require;
|
|
navigator.mozSettings.addObserver('device.storage.writable.name', this.onStorageVolumeChanged);
|
|
this.configure();
|
|
debug('initialized');
|
|
}
|
|
Storage.prototype.createFilename = function (storage, type, done) {
|
|
var self = this;
|
|
this.require([
|
|
'asyncStorage',
|
|
'lib/dcf'
|
|
], function (as, dcf) {
|
|
if (!self.dcf) {
|
|
self.dcf = dcf;
|
|
self.dcf.init();
|
|
}
|
|
self.dcf.createDCFFilename(storage, type, done);
|
|
});
|
|
};
|
|
Storage.prototype.addPicture = function (blob, options, done) {
|
|
if (typeof options === 'function') {
|
|
done = options;
|
|
options = {};
|
|
}
|
|
done = done || function () {
|
|
};
|
|
var filepath = options && options.filepath;
|
|
var self = this;
|
|
debug('add picture', filepath);
|
|
if (!filepath) {
|
|
debug('creating filename');
|
|
this.createFilename(this.picture, 'image', onCreated);
|
|
} else {
|
|
onCreated(filepath);
|
|
}
|
|
function onCreated(filepath) {
|
|
var req = self.picture.addNamed(blob, filepath);
|
|
req.onerror = function () {
|
|
self.emit('error');
|
|
};
|
|
req.onsuccess = function (e) {
|
|
debug('image stored', filepath);
|
|
var absolutePath = e.target.result;
|
|
refetchFile(filepath, absolutePath);
|
|
};
|
|
}
|
|
function refetchFile(filepath, absolutePath) {
|
|
var req = self.picture.get(filepath);
|
|
req.onerror = function () {
|
|
self.emit('error');
|
|
done('Error adding picture to storage');
|
|
};
|
|
req.onsuccess = function (e) {
|
|
debug('image file blob handle retrieved');
|
|
var fileBlob = e.target.result;
|
|
done(null, filepath, absolutePath, fileBlob);
|
|
};
|
|
}
|
|
};
|
|
Storage.prototype.createVideoFilepath = function (done) {
|
|
var videoStorage = this.video;
|
|
var self = this;
|
|
this.createFilename(this.video, 'video', function (filepath) {
|
|
var dummyFilepath = getDir(filepath) + '.tmp.3gp';
|
|
var blob = new Blob([''], { type: 'video/3gpp' });
|
|
var req = videoStorage.addNamed(blob, dummyFilepath);
|
|
req.onerror = function (e) {
|
|
debug('Failed to add ' + filepath + ' to DeviceStorage', e);
|
|
var req = videoStorage.delete(dummyFilepath);
|
|
req.onerror = function () {
|
|
done('Error creating video file path');
|
|
};
|
|
req.onsuccess = function () {
|
|
self.createVideoFilepath(done);
|
|
};
|
|
};
|
|
req.onsuccess = function (e) {
|
|
videoStorage.delete(e.target.result);
|
|
done(null, filepath);
|
|
};
|
|
});
|
|
};
|
|
Storage.prototype.onStorageChange = function (e) {
|
|
debug('state change: %s', e.reason);
|
|
var value = e.reason;
|
|
switch (value) {
|
|
case 'deleted':
|
|
var filepath = this.checkFilepath(e.path);
|
|
this.emit('itemdeleted', { path: filepath });
|
|
break;
|
|
case 'available':
|
|
case 'shared':
|
|
case 'unavailable':
|
|
this.setState(value);
|
|
break;
|
|
}
|
|
this.check();
|
|
};
|
|
Storage.prototype.configure = function (storageVolumeName) {
|
|
var i;
|
|
var videosStorages;
|
|
var picturesStorages;
|
|
if (this.picture) {
|
|
this.picture.removeEventListener('change', this.onStorageChange);
|
|
}
|
|
if (!storageVolumeName) {
|
|
this.video = navigator.getDeviceStorage('videos');
|
|
this.picture = navigator.getDeviceStorage('pictures');
|
|
} else {
|
|
videosStorages = navigator.getDeviceStorages('videos');
|
|
this.video = videosStorages[0];
|
|
for (i = 0; i < videosStorages.length; ++i) {
|
|
if (videosStorages[i].storageName === storageVolumeName) {
|
|
this.video = videosStorages[i];
|
|
break;
|
|
}
|
|
}
|
|
picturesStorages = navigator.getDeviceStorages('pictures');
|
|
this.picture = picturesStorages[0];
|
|
for (i = 0; i < picturesStorages.length; ++i) {
|
|
if (picturesStorages[i].storageName === storageVolumeName) {
|
|
this.picture = picturesStorages[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!this.picture) {
|
|
this.setState('unavailable');
|
|
return;
|
|
}
|
|
this.picture.addEventListener('change', this.onStorageChange);
|
|
this.emit('volumechanged', {
|
|
video: this.video,
|
|
picture: this.picture
|
|
});
|
|
};
|
|
Storage.prototype.onStorageVolumeChanged = function (setting) {
|
|
debug('default storage volume change: %s', setting.settingValue);
|
|
this.configure(setting.settingValue);
|
|
};
|
|
Storage.prototype.checkFilepath = function (filepath) {
|
|
var startString = filepath.indexOf('DCIM/');
|
|
if (startString < -1) {
|
|
return;
|
|
} else if (startString > 0) {
|
|
filepath = filepath.substr(startString);
|
|
}
|
|
if (filepath.indexOf('VID') != -1 && filepath.lastIndexOf('.jpg') === filepath.length - 4) {
|
|
filepath = filepath.replace('.jpg', '.3gp');
|
|
}
|
|
return filepath;
|
|
};
|
|
Storage.prototype.setState = function (value) {
|
|
this.state = value;
|
|
debug('set state: %s', value);
|
|
this.emit('changed', value);
|
|
};
|
|
Storage.prototype.setMaxFileSize = function (maxFileSize) {
|
|
this.maxFileSize = maxFileSize;
|
|
this.check();
|
|
debug('max file size set: %d', maxFileSize);
|
|
};
|
|
Storage.prototype.check = function (done) {
|
|
debug('check');
|
|
var self = this;
|
|
done = done || function () {
|
|
};
|
|
this.getState(function (result) {
|
|
self.setState(result);
|
|
if (!self.available()) {
|
|
onComplete('unhealthy');
|
|
return;
|
|
}
|
|
if (self.dcf) {
|
|
self.dcf.checkFileCounter();
|
|
}
|
|
self.isSpace(function (result) {
|
|
if (!result) {
|
|
self.setState('nospace');
|
|
}
|
|
onComplete('healthy');
|
|
});
|
|
});
|
|
function onComplete(state) {
|
|
self.emit('checked', state);
|
|
}
|
|
};
|
|
Storage.prototype.isSpace = function (done) {
|
|
var maxFileSize = this.maxFileSize;
|
|
this.picture.freeSpace().onsuccess = function (e) {
|
|
var freeSpace = e.target.result;
|
|
var result = freeSpace > maxFileSize;
|
|
debug('is space: %s', result, freeSpace, maxFileSize);
|
|
done(result);
|
|
};
|
|
};
|
|
Storage.prototype.getState = function (done) {
|
|
if (!this.picture) {
|
|
setTimeout(function () {
|
|
done('unavailable');
|
|
});
|
|
return;
|
|
}
|
|
this.picture.available().onsuccess = function (e) {
|
|
done(e.target.result);
|
|
};
|
|
};
|
|
Storage.prototype.available = function () {
|
|
return this.state === 'available';
|
|
};
|
|
Storage.prototype.deletePicture = function (filepath, done) {
|
|
var req = this.picture.delete(filepath);
|
|
req.onerror = function (e) {
|
|
var message = 'Failed to delete ' + filepath + ' from DeviceStorage:' + e.target.error;
|
|
console.warn(message);
|
|
done(message);
|
|
};
|
|
if (done) {
|
|
req.onsuccess = function () {
|
|
done(null);
|
|
};
|
|
}
|
|
};
|
|
Storage.prototype.deleteVideo = function (filepath) {
|
|
var poster = filepath.replace('.3gp', '.jpg');
|
|
this.video.delete(filepath).onerror = function (e) {
|
|
console.warn('Failed to delete', filepath, 'from DeviceStorage:', e.target.error);
|
|
};
|
|
this.picture.delete(poster).onerror = function (e) {
|
|
console.warn('Failed to delete poster image', poster, 'for video', filepath, 'from DeviceStorage:', e.target.error);
|
|
};
|
|
};
|
|
function getDir(filepath) {
|
|
var index = filepath.lastIndexOf('/') + 1;
|
|
return index ? filepath.substring(0, index) : '';
|
|
}
|
|
});
|
|
define('controllers/storage', [
|
|
'require',
|
|
'exports',
|
|
'module',
|
|
'debug',
|
|
'lib/bind-all',
|
|
'lib/storage'
|
|
], function (require, exports, module) {
|
|
'use strict';
|
|
var debug = require('debug')('controller:storage');
|
|
var bindAll = require('lib/bind-all');
|
|
var Storage = require('lib/storage');
|
|
module.exports = function (app) {
|
|
return new StorageController(app);
|
|
};
|
|
module.exports.StorageController = StorageController;
|
|
function StorageController(app) {
|
|
bindAll(this);
|
|
this.app = app;
|
|
this.camera = app.camera;
|
|
this.settings = app.settings;
|
|
this.storage = app.storage || new Storage();
|
|
this.bindEvents();
|
|
this.configure();
|
|
debug('initialized');
|
|
}
|
|
StorageController.prototype.configure = function () {
|
|
this.storage.configure();
|
|
this.camera.createVideoFilepath = this.storage.createVideoFilepath;
|
|
this.updateMaxFileSize();
|
|
};
|
|
StorageController.prototype.bindEvents = function () {
|
|
debug('bind events');
|
|
this.settings.pictureSizes.on('change:selected', this.updateMaxFileSize);
|
|
this.app.on('previewgallery:deletepicture', this.storage.deletePicture);
|
|
this.app.on('previewgallery:deletevideo', this.storage.deleteVideo);
|
|
this.app.on('settings:configured', this.updateMaxFileSize);
|
|
this.app.on('camera:newimage', this.storePicture);
|
|
this.app.on('camera:newvideo', this.storeVideo);
|
|
this.app.on('visible', this.storage.check);
|
|
this.storage.on('volumechanged', this.app.firer('storage:volumechanged'));
|
|
this.storage.on('itemdeleted', this.app.firer('storage:itemdeleted'));
|
|
this.storage.on('changed', this.onChanged);
|
|
this.storage.on('checked', this.onChecked);
|
|
debug('events bound');
|
|
};
|
|
StorageController.prototype.onChanged = function (state) {
|
|
debug('changed: %s', state);
|
|
this.app.emit('storage:changed', state);
|
|
};
|
|
StorageController.prototype.onChecked = function (value) {
|
|
debug('checked: %s', value);
|
|
this.app.emit('storage:checked', value);
|
|
this.app.emit('storage:checked:' + value);
|
|
};
|
|
StorageController.prototype.storePicture = function (picture) {
|
|
var memoryBlob = picture.blob;
|
|
var self = this;
|
|
this.storage.addPicture(memoryBlob, function (error, filepath, abspath, fileBlob) {
|
|
picture.blob = fileBlob;
|
|
picture.filepath = filepath;
|
|
debug('stored picture', picture);
|
|
self.app.emit('newmedia', picture);
|
|
});
|
|
};
|
|
StorageController.prototype.storeVideo = function (video) {
|
|
debug('new video', video);
|
|
var poster = video.poster;
|
|
var self = this;
|
|
poster.filepath = video.filepath.replace('.3gp', '.jpg');
|
|
video.isVideo = true;
|
|
this.storage.addPicture(poster.blob, { filepath: poster.filepath }, function (error, path, absolutePath, fileBlob) {
|
|
poster.blob = fileBlob;
|
|
debug('new video', video);
|
|
self.app.emit('newmedia', video);
|
|
});
|
|
};
|
|
StorageController.prototype.updateMaxFileSize = function () {
|
|
var pictureSize = this.settings.pictureSizes.selected('data');
|
|
if (pictureSize) {
|
|
var bytes = pictureSize.width * pictureSize.height / 2 + 25000;
|
|
this.storage.setMaxFileSize(bytes);
|
|
debug('maxFileSize updated %s', bytes);
|
|
}
|
|
};
|
|
});
|
|
window.jsStarted = Date.now();
|
|
define('main', [
|
|
'require',
|
|
'lib/settings',
|
|
'lib/geo-location',
|
|
'config/config',
|
|
'lib/camera/camera',
|
|
'app',
|
|
'controllers/overlay',
|
|
'controllers/battery',
|
|
'controllers/hud',
|
|
'controllers/controls',
|
|
'controllers/viewfinder',
|
|
'controllers/settings',
|
|
'controllers/activity',
|
|
'controllers/camera',
|
|
'controllers/storage'
|
|
], function (require) {
|
|
var perf = {
|
|
jsStarted: window.jsStarted,
|
|
firstModule: Date.now()
|
|
};
|
|
var Settings = require('lib/settings');
|
|
var GeoLocation = require('lib/geo-location');
|
|
var settingsData = require('config/config');
|
|
var settings = new Settings(settingsData);
|
|
var Camera = require('lib/camera/camera');
|
|
var App = require('app');
|
|
var key;
|
|
if (settingsData.globals) {
|
|
for (key in settingsData.globals) {
|
|
window[key] = settingsData.globals[key];
|
|
}
|
|
}
|
|
var app = window.app = new App({
|
|
settings: settings,
|
|
geolocation: new GeoLocation(),
|
|
el: document.body,
|
|
doc: document,
|
|
win: window,
|
|
perf: perf,
|
|
camera: new Camera({ focus: settingsData.focus }),
|
|
controllers: {
|
|
overlay: require('controllers/overlay'),
|
|
battery: require('controllers/battery'),
|
|
hud: require('controllers/hud'),
|
|
controls: require('controllers/controls'),
|
|
viewfinder: require('controllers/viewfinder'),
|
|
settings: require('controllers/settings'),
|
|
activity: require('controllers/activity'),
|
|
camera: require('controllers/camera'),
|
|
storage: require('controllers/storage'),
|
|
lazy: [
|
|
'controllers/zoom-bar',
|
|
'controllers/indicators',
|
|
'controllers/media',
|
|
'controllers/sounds'
|
|
]
|
|
}
|
|
});
|
|
app.camera.load();
|
|
app.settings.fetch();
|
|
app.boot();
|
|
for (key in settingsData) {
|
|
delete settingsData[key];
|
|
}
|
|
});
|
|
requirejs.config({
|
|
baseUrl: '/js',
|
|
paths: {
|
|
'asyncStorage': '../shared/js/async_storage',
|
|
'getVideoRotation': '../shared/js/media/get_video_rotation',
|
|
'jpegMetaDataParser': '../shared/js/media/jpeg_metadata_parser',
|
|
'downsample': '../shared/js/media/downsample',
|
|
'getImageSize': '../shared/js/media/image_size',
|
|
'cropResizeRotate': '../shared/js/media/crop_resize_rotate',
|
|
'format': '../shared/js/format',
|
|
'GestureDetector': '../shared/js/gesture_detector',
|
|
'VideoPlayer': '../shared/js/media/video_player',
|
|
'MediaFrame': '../shared/js/media/media_frame',
|
|
'BlobView': '../shared/js/blobview',
|
|
'CustomDialog': '../shared/js/custom_dialog',
|
|
'debug': '../bower_components/debug/index',
|
|
'attach': '../bower_components/attach/index',
|
|
'model': '../bower_components/model/index',
|
|
'view': '../bower_components/view/index',
|
|
'evt': '../bower_components/evt/index',
|
|
'drag': '../bower_components/drag/index',
|
|
'device-orientation': '../bower_components/device-orientation/device-orientation',
|
|
'stop-recording-event': '../shared/js/stop_recording_event'
|
|
},
|
|
packages: [
|
|
{
|
|
name: 'gaia-header',
|
|
location: '../bower_components/gaia-header',
|
|
main: 'gaia-header'
|
|
},
|
|
{
|
|
name: 'gaia-icons',
|
|
location: '../bower_components/gaia-icons',
|
|
main: 'gaia-icons'
|
|
},
|
|
{
|
|
name: 'gaia-component',
|
|
location: '../bower_components/gaia-component',
|
|
main: 'gaia-component'
|
|
},
|
|
{
|
|
name: 'font-fit',
|
|
location: '../bower_components/font-fit',
|
|
main: 'font-fit'
|
|
}
|
|
],
|
|
shim: {
|
|
'format': { exports: 'Format' },
|
|
'getVideoRotation': {
|
|
deps: ['BlobView'],
|
|
exports: 'getVideoRotation'
|
|
},
|
|
'MediaFrame': {
|
|
deps: [
|
|
'format',
|
|
'VideoPlayer',
|
|
'downsample'
|
|
],
|
|
exports: 'MediaFrame'
|
|
},
|
|
'BlobView': { exports: 'BlobView' },
|
|
'asyncStorage': { exports: 'asyncStorage' },
|
|
'jpegMetaDataParser': {
|
|
deps: ['BlobView'],
|
|
exports: 'parseJPEGMetadata'
|
|
},
|
|
'getImageSize': {
|
|
deps: [
|
|
'BlobView',
|
|
'jpegMetaDataParser'
|
|
],
|
|
exports: 'getImageSize'
|
|
},
|
|
'cropResizeRotate': {
|
|
deps: [
|
|
'BlobView',
|
|
'getImageSize',
|
|
'jpegMetaDataParser',
|
|
'downsample'
|
|
],
|
|
exports: 'cropResizeRotate'
|
|
},
|
|
'GestureDetector': { exports: 'GestureDetector' },
|
|
'CustomDialog': { exports: 'CustomDialog' },
|
|
'stop-recording-event': { exports: 'StopRecordingEvent' }
|
|
}
|
|
});
|
|
define('config/require', function () {
|
|
});
|
|
if (!window.VulpesCompat || navigator.mozCameras) require(['main']); |