Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/apps/system/camera/js/filmstrip.js
diff options
context:
space:
mode:
authorDaniel Narvaez <dwnarvaez@gmail.com>2013-02-06 14:30:36 (GMT)
committer Daniel Narvaez <dwnarvaez@gmail.com>2013-02-06 14:30:36 (GMT)
commitf5bac2f1e1a51b83d215a07f9d5d87db337873f3 (patch)
tree15c5f594b1e00c6272552cc5544a1bc757713e34 /apps/system/camera/js/filmstrip.js
parentc301ead6bb9c201801400964427b517dafb6a03c (diff)
Get the build to work
Diffstat (limited to 'apps/system/camera/js/filmstrip.js')
-rw-r--r--apps/system/camera/js/filmstrip.js568
1 files changed, 0 insertions, 568 deletions
diff --git a/apps/system/camera/js/filmstrip.js b/apps/system/camera/js/filmstrip.js
deleted file mode 100644
index d428801..0000000
--- a/apps/system/camera/js/filmstrip.js
+++ /dev/null
@@ -1,568 +0,0 @@
-/*
- * filmstrip.js: filmstrip, thumbnails and previews for the camera.
- */
-
-'use strict';
-
-var Filmstrip = (function() {
-
- // This array holds all the data we need for image and video previews
- var items = [];
- var currentItemIndex;
-
- // Maximum number of thumbnails in the filmstrip
- var MAX_THUMBNAILS = 5;
- var THUMBNAIL_WIDTH = 46; // size of each thumbnail
- var THUMBNAIL_HEIGHT = 46;
-
- // Timer for auto-hiding the filmstrip
- var hideTimer = null;
-
- // Document elements we care about
- var filmstrip = document.getElementById('filmstrip');
- var preview = document.getElementById('preview');
- var frameContainer = document.getElementById('frame-container');
- var mediaFrame = document.getElementById('media-frame');
- var cameraButton = document.getElementById('camera-button');
- var shareButton = document.getElementById('share-button');
- var deleteButton = document.getElementById('delete-button');
- var filmstripGalleryButton =
- document.getElementById('filmstrip-gallery-button');
-
- // Offscreen elements for generating thumbnails with
- var offscreenImage = new Image();
- var offscreenVideo = document.createElement('video');
-
- // Set up event handlers
- cameraButton.onclick = returnToCameraMode;
- deleteButton.onclick = deleteCurrentItem;
- shareButton.onclick = shareCurrentItem;
- filmstripGalleryButton.onclick = Camera.galleryBtnPressed;
- mediaFrame.addEventListener('dbltap', handleDoubleTap);
- mediaFrame.addEventListener('transform', handleTransform);
- mediaFrame.addEventListener('pan', handlePan);
- mediaFrame.addEventListener('swipe', handleSwipe);
-
- // Generate gesture events
- var gestureDetector = new GestureDetector(mediaFrame);
- gestureDetector.startDetecting();
-
- // Create the MediaFrame for previews
- var frame = new MediaFrame(mediaFrame);
-
- // Start off with it positioned correctly.
- setOrientation(Camera._phoneOrientation);
-
- // If we're running in secure mode, we never want the user to see the
- // gallery button or the share button.
- filmstrip.removeChild(filmstripGalleryButton);
- shareButton.parentNode.removeChild(shareButton);
-
- function isShown() {
- return !filmstrip.classList.contains('hidden');
- }
-
- function hide() {
- filmstrip.classList.add('hidden');
- if (hideTimer) {
- clearTimeout(hideTimer);
- hideTimer = null;
- }
- }
-
- /*
- * With a time, show the filmstrip and then hide it after the time is up.
- * Without time, show until hidden.
- * Tapping in the camera toggles it. And if toggled on, it will be on
- * without a timer.
- * It is always on when a preview is shown.
- * After recording a photo or video, it is shown for 5 seconds.
- * And it is also shown for 5 seconds after leaving preview mode.
- */
- function show(time) {
- filmstrip.classList.remove('hidden');
- if (hideTimer) {
- clearTimeout(hideTimer);
- hideTimer = null;
- }
- if (time)
- hideTimer = setTimeout(hide, time);
- }
-
- filmstrip.onclick = function(event) {
- var target = event.target;
- if (!target || !target.classList.contains('thumbnail'))
- return;
-
- var index = parseInt(target.dataset.index);
- previewItem(index);
- // If we're showing previews be sure we're showing the filmstrip
- // with no timeout and be sure that the viewfinder video is paused.
- show();
- Camera.viewfinder.pause();
- // If there is a preview shown, we want the gallery button in
- // the filmstrip
- filmstripGalleryButton.classList.remove('hidden');
- };
-
- function previewItem(index) {
- // Don't redisplay the item if it is already displayed
- if (currentItemIndex === index)
- return;
-
- var item = items[index];
-
- if (item.isImage) {
- frame.displayImage(item.blob, item.width, item.height, item.preview);
- }
- else if (item.isVideo) {
- frame.displayVideo(item.blob, item.width, item.height, item.rotation);
- }
-
- preview.classList.remove('offscreen');
- currentItemIndex = index;
-
- // Highlight the border of the thumbnail we're previewing
- // and clear the highlight on all others
- items.forEach(function(item, itemindex) {
- if (itemindex === index)
- item.element.classList.add('previewed');
- else
- item.element.classList.remove('previewed');
- });
- }
-
- function returnToCameraMode() {
- Camera.viewfinder.play(); // Restart the viewfinder
- show(Camera.FILMSTRIP_DURATION); // Fade the filmstrip after a delay
- // hide the gallery button in the filmstrip
- filmstripGalleryButton.classList.add('hidden');
- preview.classList.add('offscreen');
- frame.clear();
- if (items.length > 0)
- items[currentItemIndex].element.classList.remove('previewed');
- currentItemIndex = null;
- }
-
- function deleteCurrentItem() {
- var item = items[currentItemIndex];
- var msg, storage, filename;
-
- if (item.isImage) {
- msg = navigator.mozL10n.get('delete-photo?');
- storage = Camera._pictureStorage;
- filename = item.filename;
- }
- else {
- msg = navigator.mozL10n.get('delete-video?');
- storage = Camera._videoStorage;
- filename = item.filename;
- }
-
- // The system app is not allowed to use confirm, I think
- // so if we're running in secure mode, just delete the file without
- // confirmation
- if (Camera._secureMode || confirm(msg)) {
- // Remove the item from the array of items
- items.splice(currentItemIndex, 1);
-
- // Remove the thumbnail image from the filmstrip
- filmstrip.removeChild(item.element);
- URL.revokeObjectURL(item.element.src);
-
- // Renumber the item elements
- items.forEach(function(item, index) {
- item.element.dataset.index = index;
- });
-
- // If there are no more items, go back to the camera
- if (items.length === 0) {
- returnToCameraMode();
- }
- else {
- // Otherwise, switch the frame to display the next item. But if
- // we just deleted the last item, then we'll need to display the
- // previous item.
- var newindex = currentItemIndex;
- if (newindex >= items.length)
- newindex = items.length - 1;
- currentItemIndex = null;
- previewItem(newindex);
- }
-
- // Actually delete the file
- storage.delete(filename).onerror = function(e) {
- console.warn('Failed to delete', filename,
- 'from DeviceStorage:', e.target.error);
- }
- }
- }
-
- function shareCurrentItem() {
- if (Camera._secureMode)
- return;
- var item = items[currentItemIndex];
- var type = item.isImage ? 'image/*' : 'video/*';
- var nameonly = item.filename.substring(item.filename.lastIndexOf('/') + 1);
- var activity = new MozActivity({
- name: 'share',
- data: {
- type: type,
- number: 1,
- blobs: [item.blob],
- filenames: [nameonly],
- filepaths: [item.filename] /* temporary hack for bluetooth app */
- }
- });
- activity.onerror = function(e) {
- console.warn('Share activity error:', activity.error.name);
- }
- }
-
- function handleDoubleTap(e) {
- if (!items[currentItemIndex].isImage)
- return;
-
- var scale;
- if (frame.fit.scale > frame.fit.baseScale)
- scale = frame.fit.baseScale / frame.fit.scale;
- else
- scale = 2;
-
- // If the phone orientation is 0 (unrotated) then the gesture detector's
- // event coordinates match what's on the screen, and we use them to
- // specify a point to zoom in or out on. For other orientations we could
- // calculate the correct point, but instead just use the midpoint.
- var x, y;
- if (Camera._phoneOrientation === 0) {
- x = e.detail.clientX;
- y = e.detail.clientY;
- }
- else {
- x = mediaFrame.offsetWidth / 2;
- y = mediaFrame.offsetHeight / 2;
- }
-
- frame.zoom(scale, x, y, 200);
- }
-
- function handleTransform(e) {
- if (!items[currentItemIndex].isImage)
- return;
-
- // If the phone orientation is 0 (unrotated) then the gesture detector's
- // event coordinates match what's on the screen, and we use them to
- // specify a point to zoom in or out on. For other orientations we could
- // calculate the correct point, but instead just use the midpoint.
- var x, y;
- if (Camera._phoneOrientation === 0) {
- x = e.detail.midpoint.clientX;
- y = e.detail.midpoint.clientY;
- }
- else {
- x = mediaFrame.offsetWidth / 2;
- y = mediaFrame.offsetHeight / 2;
- }
-
- frame.zoom(e.detail.relative.scale, x, y);
- }
-
- function handlePan(e) {
- if (!items[currentItemIndex].isImage)
- return;
-
- // The gesture detector event does not take our CSS rotation into
- // account, so we have to pan by a dx and dy that depend on how
- // the MediaFrame is rotated
- var dx, dy;
- switch (Camera._phoneOrientation) {
- case 0:
- dx = e.detail.relative.dx;
- dy = e.detail.relative.dy;
- break;
- case 90:
- dx = -e.detail.relative.dy;
- dy = e.detail.relative.dx;
- break;
- case 180:
- dx = -e.detail.relative.dx;
- dy = -e.detail.relative.dy;
- break;
- case 270:
- dx = e.detail.relative.dy;
- dy = -e.detail.relative.dx;
- break;
- }
-
- frame.pan(dx, dy);
- }
-
- function handleSwipe(e) {
- // Because the stuff around the media frame does not change position
- // when the phone is rotated, we don't alter these directions based
- // on orientation. To dismiss the preview, the user always swipes toward
- // the filmstrip.
-
- switch (e.detail.direction) {
- case 'up': // close the preview if the swipe is fast enough
- if (e.detail.vy < -1)
- returnToCameraMode();
- break;
- case 'left': // go to next image if fast enough
- if (e.detail.vx < -1 && currentItemIndex < items.length - 1)
- previewItem(currentItemIndex + 1);
- break;
- case 'right': // go to previous image if fast enough
- if (e.detail.vx > 1 && currentItemIndex > 0)
- previewItem(currentItemIndex - 1);
- break;
- }
- }
-
- function addImage(filename, blob) {
- parseJPEGMetadata(blob, function getPreviewBlob(metadata) {
- if (metadata.preview) {
- var previewBlob = blob.slice(metadata.preview.start,
- metadata.preview.end,
- 'image/jpeg');
-
- offscreenImage.src = URL.createObjectURL(previewBlob);
- offscreenImage.onload = function() {
- createThumbnailFromElement(offscreenImage, false, 0,
- function(thumbnail) {
- addItem({
- isImage: true,
- filename: filename,
- thumbnail: thumbnail,
- blob: blob,
- width: metadata.width,
- height: metadata.height,
- preview: metadata.preview
- });
- });
- URL.revokeObjectURL(offscreenImage.src);
- offscreenImage.onload = null;
- offscreenImage.src = null;
- };
- }
- }, function logerr(msg) { console.warn(msg); });
- }
-
- function addVideo(filename) {
- var request = Camera._videoStorage.get(filename);
- request.onerror = function() {
- console.warn('addVideo:', filename, request.error.name);
- };
- request.onsuccess = function() {
- var blob = request.result;
- getVideoRotation(blob, function(rotation) {
- if (typeof rotation !== 'number') {
- console.warn('Unexpected rotation:', rotation);
- rotation = 0;
- }
-
- var url = URL.createObjectURL(blob);
-
- offscreenVideo.preload = 'metadata';
- offscreenVideo.style.width = THUMBNAIL_WIDTH + 'px';
- offscreenVideo.style.height = THUMBNAIL_HEIGHT + 'px';
- offscreenVideo.src = url;
-
- offscreenVideo.onerror = function() {
- URL.revokeObjectURL(url);
- offscreenVideo.onerror = null;
- offscreenVideo.onloadedmetadata = null;
- offscreenVideo.removeAttribute('src');
- offscreenVideo.load();
- console.warn('not a video file', filename);
- }
-
- offscreenVideo.onloadedmetadata = function() {
- createThumbnailFromElement(offscreenVideo, true, rotation,
- function(thumbnail) {
- addItem({
- isVideo: true,
- filename: filename,
- thumbnail: thumbnail,
- blob: blob,
- width: offscreenVideo.videoWidth,
- height: offscreenVideo.videoHeight,
- rotation: rotation
- });
- });
- URL.revokeObjectURL(url);
- offscreenVideo.onerror = null;
- offscreenVideo.onloadedmetadata = null;
- offscreenVideo.removeAttribute('src');
- offscreenVideo.load();
- };
- });
- };
- }
-
- // Add a thumbnail to the filmstrip.
- // The details object contains everything we need to know
- // to display the thumbnail and preview the image or video
- function addItem(item) {
- // Thumbnails go from most recent to least recent.
- items.unshift(item);
-
- // Create an image element for this new thumbnail and display it
- item.element = new Image();
- item.element.src = URL.createObjectURL(item.thumbnail);
- item.element.classList.add('thumbnail');
- filmstrip.insertBefore(item.element, filmstrip.firstElementChild);
-
- // If we have too many thumbnails now, remove the oldest one from
- // the array, and remove its element from the filmstrip and release
- // its blob url
- if (items.length > MAX_THUMBNAILS) {
- var oldest = items.pop();
- filmstrip.removeChild(oldest.element);
- URL.revokeObjectURL(oldest.element.src);
- }
-
- // Now update the index associated with each of the remaining elements
- // so that the click event handle knows which one it clicked on
- items.forEach(function(item, index) {
- item.element.dataset.index = index;
- });
- }
-
- // Remove all items from the filmstrip. Don't delete the files, but
- // forget all of our state. This also exits preview mode if we're in it.
- function clear() {
- if (!preview.classList.contains('offscreen'))
- returnToCameraMode();
- items.forEach(function(item) {
- filmstrip.removeChild(item.element);
- URL.revokeObjectURL(item.element.src);
- });
- items.length = 0;
- }
-
- // Create a thumbnail size canvas, copy the <img> or <video> into it
- // cropping the edges as needed to make it fit, and then extract the
- // thumbnail image as a blob and pass it to the callback.
- function createThumbnailFromElement(elt, video, rotation, callback) {
- // Create a thumbnail image
- var canvas = document.createElement('canvas');
- var context = canvas.getContext('2d');
- canvas.width = THUMBNAIL_WIDTH;
- canvas.height = THUMBNAIL_HEIGHT;
- var eltwidth = video ? elt.videoWidth : elt.width;
- var eltheight = video ? elt.videoHeight : elt.height;
- var scalex = canvas.width / eltwidth;
- var scaley = canvas.height / eltheight;
-
- // Take the larger of the two scales: we crop the image to the thumbnail
- var scale = Math.max(scalex, scaley);
-
- // Calculate the region of the image that will be copied to the
- // canvas to create the thumbnail
- var w = Math.round(THUMBNAIL_WIDTH / scale);
- var h = Math.round(THUMBNAIL_HEIGHT / scale);
- var x = Math.round((eltwidth - w) / 2);
- var y = Math.round((eltheight - h) / 2);
-
- // If a rotation is specified, rotate the canvas context
- if (rotation) {
- context.save();
- switch (rotation) {
- case 90:
- context.translate(THUMBNAIL_WIDTH, 0);
- context.rotate(Math.PI / 2);
- break;
- case 180:
- context.translate(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
- context.rotate(Math.PI);
- break;
- case 270:
- context.translate(0, THUMBNAIL_HEIGHT);
- context.rotate(-Math.PI / 2);
- break;
- }
- }
-
- // Draw that region of the image into the canvas, scaling it down
- context.drawImage(elt, x, y, w, h,
- 0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
-
- // Restore the default rotation so the play arrow comes out correctly
- if (rotation) {
- context.restore();
- }
-
- // If this is a video, superimpose a translucent play button over
- // the captured video frame to distinguish it from a still photo thumbnail
- if (video) {
- // First draw a transparent gray circle
- context.fillStyle = 'rgba(0, 0, 0, .3)';
- context.beginPath();
- context.arc(THUMBNAIL_WIDTH / 2, THUMBNAIL_HEIGHT / 2,
- THUMBNAIL_HEIGHT / 3, 0, 2 * Math.PI, false);
- context.fill();
-
- // Now outline the circle in white
- context.strokeStyle = 'rgba(255,255,255,.6)';
- context.lineWidth = 2;
- context.stroke();
-
- // And add a white play arrow.
- context.beginPath();
- context.fillStyle = 'rgba(255,255,255,.6)';
- // The height of an equilateral triangle is sqrt(3)/2 times the side
- var side = THUMBNAIL_HEIGHT / 3;
- var triangle_height = side * Math.sqrt(3) / 2;
- context.moveTo(THUMBNAIL_WIDTH / 2 + triangle_height * 2 / 3,
- THUMBNAIL_HEIGHT / 2);
- context.lineTo(THUMBNAIL_WIDTH / 2 - triangle_height / 3,
- THUMBNAIL_HEIGHT / 2 - side / 2);
- context.lineTo(THUMBNAIL_WIDTH / 2 - triangle_height / 3,
- THUMBNAIL_HEIGHT / 2 + side / 2);
- context.closePath();
- context.fill();
- }
-
- canvas.toBlob(callback, 'image/jpeg');
- }
-
- function setOrientation(orientation) {
- preview.dataset.orientation = orientation;
- filmstrip.dataset.orientation = orientation;
- mediaFrame.dataset.orientation = orientation;
-
- // When we rotate the media frame, we also have to change its size
- var containerWidth = frameContainer.offsetWidth;
- var containerHeight = frameContainer.offsetHeight;
- if (orientation === 0 || orientation === 180) {
- mediaFrame.style.width = containerWidth + 'px';
- mediaFrame.style.height = containerHeight + 'px';
- mediaFrame.style.top = 0 + 'px';
- mediaFrame.style.left = 0 + 'px';
- }
- else {
- mediaFrame.style.width = containerHeight + 'px';
- mediaFrame.style.height = containerWidth + 'px';
- mediaFrame.style.top = ((containerHeight - containerWidth) / 2) + 'px';
- mediaFrame.style.left = ((containerWidth - containerHeight) / 2) + 'px';
- }
-
- // And rotate so this new size fills the screen
- mediaFrame.style.transform = 'rotate(-' + orientation + 'deg)';
-
- // And we have to resize the frame (and its video player)
- frame.resize();
- frame.video.setPlayerSize();
- }
-
- return {
- isShown: isShown,
- hide: hide,
- show: show,
- addImage: addImage,
- addVideo: addVideo,
- clear: clear,
- setOrientation: setOrientation
- };
-}());