Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Bug 815791 - separate video view activity from video player app

  • Loading branch information...
commit de096976dc02c4c116c11557b4ec9f23e9e69f75 1 parent b4f1deb
@davidflanagan davidflanagan authored
View
84 apps/video/js/video.js
@@ -6,7 +6,8 @@ var ids = ['player', 'thumbnails', 'overlay', 'overlay-title',
'overlay-text', 'videoControls', 'videoFrame', 'videoBar',
'close', 'play', 'playHead', 'timeSlider', 'elapsedTime',
'video-title', 'duration-text', 'elapsed-text', 'bufferedTime',
- 'slider-wrapper', 'throbber', 'delete-video-button', 'delete-confirmation-button'];
+ 'slider-wrapper', 'throbber', 'delete-video-button',
+ 'delete-confirmation-button'];
ids.forEach(function createElementRef(name) {
dom[toCamelCase(name)] = document.getElementById(name);
@@ -41,10 +42,6 @@ var THUMBNAIL_HEIGHT = 160;
// Enumerating the readyState for html5 video api
var HAVE_NOTHING = 0;
-var activityData; // From an activity call
-var pendingActivity;
-var appStarted = false;
-
var storageState;
var currentOverlay;
@@ -65,9 +62,6 @@ function init() {
storageState = false;
updateDialog();
createThumbnailList();
- if (activityData) {
- startStream();
- }
};
videodb.onscanstart = function() {
@@ -88,9 +82,8 @@ function init() {
event.detail.forEach(videoDeleted);
};
- dom.deleteConfirmationButton.addEventListener('click', deleteSelectedVideoFile, false);
-
- appStarted = true;
+ dom.deleteConfirmationButton.addEventListener('click',
+ deleteSelectedVideoFile, false);
}
function videoAdded(videodata) {
@@ -153,11 +146,11 @@ function videoAdded(videodata) {
dom.thumbnails.addEventListener('contextmenu', function(evt) {
var node = evt.target;
var found = false;
- while (!found && node) {
- if (node.dataset.name) {
+ while (!found && node) {
+ if (node.dataset.name) {
found = true;
selectedVideo = node.dataset.name;
- } else {
+ } else {
node = node.parentNode;
}
}
@@ -208,11 +201,6 @@ function updateDialog() {
}
}
-function startStream() {
- showPlayer(activityData, true);
- activityData = null;
-}
-
function metaDataParser(videofile, callback, metadataError) {
var previewPlayer = document.createElement('video');
@@ -372,12 +360,6 @@ function setVideoPlaying(playing) {
}
}
-function completeActivity(deleteVideo) {
- pendingActivity.postResult({delete: deleteVideo});
- pendingActivity = null;
- dom.thumbnails.classList.remove('hidden');
-}
-
function playerMousedown(event) {
// If we interact with the controls before they fade away,
// cancel the fade
@@ -602,14 +584,9 @@ function playerEnded() {
clearTimeout(endedTimer);
endedTimer = null;
}
- if (pendingActivity) {
- pause();
- dom.player.currentTime = 0;
- setControlsVisibility(true);
- } else {
- dom.player.currentTime = 0;
- document.mozCancelFullScreen();
- }
+
+ dom.player.currentTime = 0;
+ document.mozCancelFullScreen();
}
function play() {
@@ -762,33 +739,6 @@ function formatDuration(duration) {
return '';
}
-function actHandle(activity) {
- var data = activity.source.data;
- var title = 'extras' in data ? (data.extras.title || '') : '';
- switch (activity.source.name) {
- case 'open':
- // Activities are required to specify whether they are inline in the manifest
- // so we know we are inline, dont bother showing thumbnails
- dom.thumbnails.classList.add('hidden');
- pendingActivity = activity;
- var filename = data.src.replace(/^ds\:videos:\/\//, '');
- activityData = {
- fromMediaDB: false,
- name: filename,
- title: title
- };
- break;
- case 'view':
- activityData = {
- url: data.url,
- title: title
- };
- break;
- }
- if (appStarted) {
- startStream();
- }
-}
// The mozRequestFullScreen can fail silently, so we keep asking
// for full screen until we detect that it happens, We limit the
@@ -802,20 +752,12 @@ function requestFullScreen(callback) {
if (++requests > MAX_FULLSCREEN_REQUESTS) {
window.clearInterval(fullscreenTimer);
fullscreenTimer = null;
- if (pendingActivity) {
- pendingActivity.postError('Could not play video');
- pendingActivity = null;
- }
return;
}
dom.videoFrame.mozRequestFullScreen();
}, 500);
}
-if (window.navigator.mozSetMessageHandler) {
- window.navigator.mozSetMessageHandler('activity', actHandle);
-}
-
// When we exit fullscreen mode, stop playing the video.
// This happens automatically when the user uses the back button (because
// back is Escape, which is also the "leave fullscreen mode" command).
@@ -824,11 +766,7 @@ if (window.navigator.mozSetMessageHandler) {
document.addEventListener('mozfullscreenchange', function() {
// We have exited fullscreen
if (document.mozFullScreenElement === null) {
- if (pendingActivity) {
- completeActivity(false);
- } else {
- hidePlayer();
- }
+ hidePlayer();
return;
}
View
318 apps/video/js/view.js
@@ -0,0 +1,318 @@
+'use strict';
+
+/*
+ * This is a stripped down version of video.js that handles the view activity
+ * and displays streaming videos specified by url.
+ *
+ * Unfortunately, there is a fair bit of code duplication between this
+ * file and video.js, but we are too close to the v1 deadline to refactor
+ * the shared code into a single shared file. If the video player UI changes
+ * those changes will have to be made in both video.js and view.js
+ */
+navigator.mozSetMessageHandler('activity', function viewVideo(activity) {
+ var dom = {}; // document elements
+ var screenLock; // keep the screen on when playing
+ var playing = false;
+ var endedTimer;
+ var controlShowing = false;
+ var controlFadeTimeout = null;
+ var dragging = false;
+ var data = activity.source.data;
+ var extras = data.extras || {};
+ var url = data.url;
+ var title = extras.title || '';
+
+ initUI();
+ showPlayer(url, title);
+
+ function initUI() {
+ // Fullscreen mode and inline activities don't seem to play well together
+ // so we'll play the video without going into fullscreen mode.
+
+ // Get all the elements we use by their id
+ var ids = ['player', 'videoFrame', 'videoControls',
+ 'close', 'play', 'playHead',
+ 'elapsedTime', 'video-title', 'duration-text', 'elapsed-text',
+ 'slider-wrapper'];
+
+ ids.forEach(function createElementRef(name) {
+ dom[toCamelCase(name)] = document.getElementById(name);
+ });
+
+ function toCamelCase(str) {
+ return str.replace(/\-(.)/g, function replacer(str, p1) {
+ return p1.toUpperCase();
+ });
+ }
+
+ dom.player.mozAudioChannelType = 'content';
+
+ // show|hide controls over the player
+ dom.videoControls.addEventListener('mousedown', playerMousedown);
+
+ // Rescale when window size changes. This should get called when
+ // orientation changes.
+ window.addEventListener('resize', function() {
+ if (dom.player.readyState !== dom.player.HAVE_NOTHING) {
+ setPlayerSize();
+ }
+ });
+
+ dom.player.addEventListener('timeupdate', timeUpdated);
+ dom.player.addEventListener('ended', playerEnded);
+
+ // Set the 'lang' and 'dir' attributes to <html> when the page is translated
+ window.addEventListener('localized', function showBody() {
+ document.documentElement.lang = navigator.mozL10n.language.code;
+ document.documentElement.dir = navigator.mozL10n.language.direction;
+ });
+ }
+
+ function setControlsVisibility(visible) {
+ dom.videoControls.classList[visible ? 'remove' : 'add']('hidden');
+ controlShowing = visible;
+ }
+
+ function playerMousedown(event) {
+ // If we interact with the controls before they fade away,
+ // cancel the fade
+ if (controlFadeTimeout) {
+ clearTimeout(controlFadeTimeout);
+ controlFadeTimeout = null;
+ }
+ if (!controlShowing) {
+ setControlsVisibility(true);
+ return;
+ }
+ if (event.target == dom.play) {
+ if (dom.player.paused)
+ play();
+ else
+ pause();
+ } else if (event.target == dom.close) {
+ done();
+ } else if (event.target == dom.sliderWrapper) {
+ dragSlider(event);
+ } else {
+ setControlsVisibility(false);
+ }
+ }
+
+ function done() {
+ // release our screen lock
+ pause();
+
+ // Release any video resources
+ dom.player.removeAttribute('src');
+ dom.player.load();
+
+ // End the activity
+ activity.postResult({});
+ }
+
+ // Make the video fit the container
+ function setPlayerSize() {
+ var containerWidth = window.innerWidth;
+ var containerHeight = window.innerHeight;
+
+ // Don't do anything if we don't know our size.
+ // This could happen if we get a resize event before our metadata loads
+ if (!dom.player.videoWidth || !dom.player.videoHeight)
+ return;
+
+ var width = dom.player.videoWidth;
+ var height = dom.player.videoHeight;
+ var xscale = containerWidth / width;
+ var yscale = containerHeight / height;
+ var scale = Math.min(xscale, yscale);
+
+ // scale large videos down, but don't scale small videos up
+ if (scale < 1) {
+ width *= scale;
+ height *= scale;
+ }
+
+ var left = ((containerWidth - width) / 2);
+ var top = ((containerHeight - height) / 2);
+
+ var transform = 'translate(' + left + 'px,' + top + 'px)';
+
+ if (scale < 1) {
+ transform += ' scale(' + scale + ')';
+ }
+
+ dom.player.style.transform = transform;
+ }
+
+ // show video player
+ function showPlayer(url, title) {
+ dom.videoTitle.textContent = title || '';
+ dom.player.src = url;
+ dom.player.onloadedmetadata = function() {
+ dom.durationText.textContent = formatDuration(dom.player.duration);
+ timeUpdated();
+
+ dom.play.classList.remove('paused');
+ setPlayerSize();
+
+ dom.player.currentTime = 0;
+
+ // Show the controls briefly then fade out
+ setControlsVisibility(true);
+ controlFadeTimeout = setTimeout(function() {
+ setControlsVisibility(false);
+ }, 2000);
+
+ play();
+ };
+ }
+
+ function playerEnded() {
+ if (dragging) {
+ return;
+ }
+ if (endedTimer) {
+ clearTimeout(endedTimer);
+ endedTimer = null;
+ }
+ // When the video is done, go right back to the calling app
+ done();
+ }
+
+ function play() {
+ // Switch the button icon
+ dom.play.classList.remove('paused');
+
+ // Start playing
+ dom.player.play();
+ playing = true;
+
+ // Don't let the screen go to sleep
+ if (!screenLock)
+ screenLock = navigator.requestWakeLock('screen');
+ }
+
+ function pause() {
+ // Switch the button icon
+ dom.play.classList.add('paused');
+
+ // Stop playing the video
+ dom.player.pause();
+ playing = false;
+
+ // Let the screen go to sleep
+ if (screenLock) {
+ screenLock.unlock();
+ screenLock = null;
+ }
+ }
+
+ // Update the progress bar and play head as the video plays
+ function timeUpdated() {
+ if (controlShowing) {
+ // We can't update a progress bar if we don't know how long
+ // the video is. It is kind of a bug that the <video> element
+ // can't figure this out for ogv videos.
+ if (dom.player.duration === Infinity || dom.player.duration === 0) {
+ return;
+ }
+
+ var percent = (dom.player.currentTime / dom.player.duration) * 100;
+ if (isNaN(percent)) // this happens when we end the activity
+ return;
+ percent += '%';
+
+ dom.elapsedText.textContent = formatDuration(dom.player.currentTime);
+ dom.elapsedTime.style.width = percent;
+ // Don't move the play head if the user is dragging it.
+ if (!dragging)
+ dom.playHead.style.left = percent;
+ }
+
+ // Since we don't always get reliable 'ended' events, see if
+ // we've reached the end this way.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=783512
+ // If we're within 1 second of the end of the video, register
+ // a timeout a half a second after we'd expect an ended event.
+ if (!endedTimer) {
+ if (!dragging && dom.player.currentTime >= dom.player.duration - 1) {
+ var timeUntilEnd = (dom.player.duration - dom.player.currentTime + .5);
+ endedTimer = setTimeout(playerEnded, timeUntilEnd * 1000);
+ }
+ } else if (dragging && dom.player.currentTime < dom.player.duration - 1) {
+ // If there is a timer set and we drag away from the end, cancel the timer
+ clearTimeout(endedTimer);
+ endedTimer = null;
+ }
+ }
+
+ // handle drags on the time slider
+ function dragSlider(e) {
+ var isPaused = dom.player.paused;
+ dragging = true;
+
+ // We can't do anything if we don't know our duration
+ if (dom.player.duration === Infinity)
+ return;
+
+ if (!isPaused) {
+ dom.player.pause();
+ }
+
+ // Capture all mouse moves and the mouse up
+ document.addEventListener('mousemove', mousemoveHandler, true);
+ document.addEventListener('mouseup', mouseupHandler, true);
+
+ function position(event) {
+ var rect = dom.sliderWrapper.getBoundingClientRect();
+ var position = (event.clientX - rect.left) / rect.width;
+ position = Math.max(position, 0);
+ position = Math.min(position, 1);
+ return position;
+ }
+
+ function mouseupHandler(event) {
+ document.removeEventListener('mousemove', mousemoveHandler, true);
+ document.removeEventListener('mouseup', mouseupHandler, true);
+
+ dragging = false;
+
+ dom.playHead.classList.remove('active');
+
+ if (dom.player.currentTime === dom.player.duration) {
+ pause();
+ } else if (!isPaused) {
+ dom.player.play();
+ }
+ }
+
+ function mousemoveHandler(event) {
+ var pos = position(event);
+ var percent = pos * 100 + '%';
+ dom.playHead.classList.add('active');
+ dom.playHead.style.left = percent;
+ dom.elapsedTime.style.width = percent;
+ dom.player.currentTime = dom.player.duration * pos;
+ dom.elapsedText.textContent = formatDuration(dom.player.currentTime);
+ }
+
+ mousemoveHandler(e);
+ }
+
+ function formatDuration(duration) {
+ function padLeft(num, length) {
+ var r = String(num);
+ while (r.length < length) {
+ r = '0' + r;
+ }
+ return r;
+ }
+
+ var minutes = Math.floor(duration / 60);
+ var seconds = Math.round(duration % 60);
+ if (minutes < 60) {
+ return padLeft(minutes, 2) + ':' + padLeft(seconds, 2);
+ }
+ return '';
+ }
+});
View
12 apps/video/manifest.webapp
@@ -37,17 +37,13 @@
"60": "/style/icons/60/Video.png"
},
"activities": {
- "open" : {
- "filters": {
- "type": ["video/webm", "video/mp4", "video/3gpp"]
- },
- "returnValue": true,
- "disposition": "inline"
- },
"view" : {
"filters": {
"type": ["video/webm", "video/mp4", "video/3gpp"]
- }
+ },
+ "href": "view.html",
+ "disposition": "inline",
+ "returnValue": true
}
}
}
View
3  apps/video/style/video.css
@@ -12,8 +12,7 @@ ul {
margin: 0;
height: 100%;
overflow-y: scroll;
- background: -moz-linear-gradient(top, #222 0%,#000 100%);
- background: linear-gradient(top, #222 0%,#000 100%);
+ background: linear-gradient(to bottom, #222 0%,#000 100%);
}
li {
margin: 0;
View
42 apps/video/view.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <title>Videos</title>
+ <link rel="resource" type="application/l10n" href="locales/locales.ini" />
+ <link rel="stylesheet" type="text/css" href="shared/style/headers.css">
+ <link rel="stylesheet" type="text/css" href="style/video.css" />
+ </head>
+ <body role="application" class="skin-dark">
+ <div id="videoFrame">
+ <video id="player" preload="metadata"></video>
+ <!-- video controls -->
+ <div id="videoControls" class="hidden">
+ <section role="region">
+ <header>
+ <a href="#" id="close"><span class="icon icon-back">back</span></a>
+ <h1 id="video-title"></h1>
+ </header>
+
+ <footer id="videoBar">
+ <button id="play"></button>
+ <div id="timeSlider">
+ <span id="elapsed-text"></span>
+ <div id="slider-wrapper">
+ <div id="elapsedTime" class="progress"></div>
+ <div id="bufferedTime" class="progress"></div>
+ <div id="timeBackground" class="progress"></div>
+ <button id="playHead"></button>
+ </div>
+ <span id="duration-text"></span>
+ </div>
+ </footer>
+ </section>
+ </div>
+ </div>
+
+ <script type="text/javascript" src="shared/js/l10n.js"></script>
+ <script type="text/javascript" src="js/view.js"></script>
+ </body>
+</html>
Please sign in to comment.
Something went wrong with that request. Please try again.