Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
214
gaia/apps/camera/js/controllers/media.js
Executable file
214
gaia/apps/camera/js/controllers/media.js
Executable file
|
|
@ -0,0 +1,214 @@
|
|||
define(function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var debug = require('debug')('controller:media');
|
||||
var bindAll = require('lib/bind-all');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = function(app) { return new MediaController(app); };
|
||||
module.exports.MediaController = MediaController;
|
||||
|
||||
function MediaController(app) {
|
||||
bindAll(this);
|
||||
this.app = app;
|
||||
this.require = app.require;
|
||||
this.settings = app.settings;
|
||||
this.bindEvents();
|
||||
this.configure();
|
||||
debug('initialized');
|
||||
}
|
||||
|
||||
MediaController.prototype.bindEvents = function() {
|
||||
this.app.on('storage:itemdeleted', this.onItemDeleted);
|
||||
this.app.on('previewgallery:deletevideo', this.onItemDeleted);
|
||||
this.app.on('previewgallery:deletepicture', this.onItemDeleted);
|
||||
this.app.on('storage:changed', this.onStorageChanged);
|
||||
this.app.on('newmedia', this.onNewMedia);
|
||||
this.app.on('hidden', this.onHidden);
|
||||
this.app.on('lazyloaded', this.configured);
|
||||
|
||||
// Preload libraries we may need after capturing to maintain snappiness
|
||||
var self = this;
|
||||
this.app.once('capture', function() {
|
||||
var lib = ['lib/create-thumbnail-image', 'lib/prepare-preview-blob'];
|
||||
self.require(lib, function() {});
|
||||
});
|
||||
|
||||
debug('events bound');
|
||||
};
|
||||
|
||||
MediaController.prototype.configure = function() {
|
||||
this.items = []; // All the pictures and videos we know about
|
||||
this.currentItem = null;
|
||||
|
||||
var dpr = window.devicePixelRatio;
|
||||
this.thumbnailSize = {
|
||||
width: this.settings.previewGallery.get('thumbnailWidth') * dpr,
|
||||
height: this.settings.previewGallery.get('thumbnailHeight') * dpr
|
||||
};
|
||||
|
||||
this.configured();
|
||||
};
|
||||
|
||||
MediaController.prototype.configured = function() {
|
||||
this.app.emit('media:configured', this.items);
|
||||
};
|
||||
|
||||
MediaController.prototype.onNewMedia = function(item) {
|
||||
// If we're handling a pick activity we don't generate thumbnails
|
||||
if (this.app.activity.pick) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
if (item.isVideo) {
|
||||
// If the new media is video, use it as-is
|
||||
addNewMedia(item);
|
||||
} else {
|
||||
// If it is a photo, find its EXIF preview first
|
||||
this.require(['lib/prepare-preview-blob'], function(preparePreview) {
|
||||
preparePreview(item.blob, function(metadata) {
|
||||
metadata.blob = item.blob;
|
||||
metadata.filepath = item.filepath;
|
||||
addNewMedia(metadata);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addNewMedia(item) {
|
||||
self.items.unshift(item);
|
||||
self.updateThumbnail();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all items in the media controller
|
||||
* when storage becomes unavailable.
|
||||
*
|
||||
* @param {String} status
|
||||
*/
|
||||
MediaController.prototype.onStorageChanged = function(status) {
|
||||
if (status === 'unavailable') {
|
||||
this.configure();
|
||||
this.updateThumbnail();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* As a privacy feature, when the camera app is used from the lockscreen
|
||||
* and the lockscreen is actually locked with a passcode, we don't want
|
||||
* the camera to retain any state from one use to the next. So if the
|
||||
* camera is hidden (i.e. if the phone returns to the lockscreen) we
|
||||
* forget our state. In practice, it appears that the system app actually
|
||||
* kills the camera when this happens, so this code is redundant.
|
||||
*/
|
||||
MediaController.prototype.onHidden = function() {
|
||||
if (this.app.inSecureMode) {
|
||||
this.configure(); // Forget all stored images
|
||||
this.updateThumbnail(); // Get rid of any thumbnail
|
||||
}
|
||||
};
|
||||
|
||||
MediaController.prototype.updateThumbnail = function() {
|
||||
// This is the media item that we want to create a thumbnail for.
|
||||
var media = this.items[0] || null;
|
||||
if (media === this.currentItem) {
|
||||
return;
|
||||
}
|
||||
if (media === null) {
|
||||
this.app.emit('newthumbnail', null);
|
||||
return;
|
||||
}
|
||||
|
||||
// To create a thumbanil, we first need to figure out which image
|
||||
// blob to use, and what the size and rotation of that image is.
|
||||
var blob; // The image we'll create the thumbnail from.
|
||||
var metadata = { // The metadata for that image.
|
||||
rotation: media.rotation,
|
||||
mirrored: media.mirrored
|
||||
};
|
||||
|
||||
if (media.isVideo) {
|
||||
// If it is a video we can create a thumbnail from the poster image
|
||||
blob = media.poster.blob;
|
||||
metadata.width = media.poster.width;
|
||||
metadata.height = media.poster.height;
|
||||
} else {
|
||||
// If it is a photo we want to use the EXIF preview rather than
|
||||
// decoding the whole image if we can.
|
||||
if (media.preview) {
|
||||
|
||||
// The Tarako may produce EXIF previews that have the wrong
|
||||
// aspect ratio and are distorted. Check for that, and if the
|
||||
// aspect ratio is not right, then create a thumbnail from the
|
||||
// full size image
|
||||
var fullRatio = media.width / media.height;
|
||||
var previewRatio = media.preview.width / media.preview.height;
|
||||
|
||||
// If aspect ratios match, create thumbnail from EXIF preview
|
||||
if (Math.abs(fullRatio - previewRatio) < 0.01) {
|
||||
blob = media.blob.slice(media.preview.start, media.preview.end,
|
||||
'image/jpeg');
|
||||
metadata.width = media.preview.width;
|
||||
metadata.height = media.preview.height;
|
||||
}
|
||||
}
|
||||
|
||||
// If a thumbnail couldn't be obtained from the EXIF preview,
|
||||
// use the full image
|
||||
if (!blob) {
|
||||
blob = media.blob;
|
||||
metadata.width = media.width;
|
||||
metadata.height = media.height;
|
||||
}
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.require(['lib/create-thumbnail-image'], function(createThumbnailImage) {
|
||||
createThumbnailImage(blob, metadata, self.thumbnailSize, (thumb) => {
|
||||
self.currentItem = media;
|
||||
self.app.emit('newthumbnail', thumb);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete and update items in the preview gallery
|
||||
* when images/videos are deleted by others
|
||||
*
|
||||
* @param {Object} filepath
|
||||
*/
|
||||
MediaController.prototype.onItemDeleted = function(data) {
|
||||
var deleteIdx = -1;
|
||||
var deletedFilepath = typeof(data) === 'object' ? data.path : data;
|
||||
|
||||
// find the item in items
|
||||
for (var n = 0; n < this.items.length; n++) {
|
||||
if (this.items[n].filepath === deletedFilepath) {
|
||||
deleteIdx = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit when item not found
|
||||
if (n === this.items.length) { return; }
|
||||
|
||||
var item = this.items[deleteIdx];
|
||||
|
||||
// Check if this event is being stopped such as in the case
|
||||
// of resizing an image for a share activity.
|
||||
if (item.resizing) { return; }
|
||||
this.items.splice(deleteIdx, 1);
|
||||
this.updateThumbnail();
|
||||
this.app.emit('media:deleted');
|
||||
};
|
||||
|
||||
});
|
||||
Loading…
Reference in a new issue