Home | History | Annotate | Download | only in js
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 /**
      8  * @param {Element} playerContainer Main container.
      9  * @param {Element} videoContainer Container for the video element.
     10  * @param {Element} controlsContainer Container for video controls.
     11  * @constructor
     12  */
     13 function FullWindowVideoControls(
     14     playerContainer, videoContainer, controlsContainer) {
     15   VideoControls.call(this,
     16       controlsContainer,
     17       this.onPlaybackError_.wrap(this),
     18       loadTimeData.getString.wrap(loadTimeData),
     19       this.toggleFullScreen_.wrap(this),
     20       videoContainer);
     21 
     22   this.playerContainer_ = playerContainer;
     23   this.decodeErrorOccured = false;
     24 
     25   this.updateStyle();
     26   window.addEventListener('resize', this.updateStyle.wrap(this));
     27   document.addEventListener('keydown', function(e) {
     28     if (e.keyIdentifier == 'U+0020') {  // Space
     29       this.togglePlayStateWithFeedback();
     30       e.preventDefault();
     31     }
     32     if (e.keyIdentifier == 'U+001B') {  // Escape
     33       util.toggleFullScreen(
     34           chrome.app.window.current(),
     35           false);  // Leave the full screen mode.
     36       e.preventDefault();
     37     }
     38   }.wrap(this));
     39 
     40   // TODO(mtomasz): Simplify. crbug.com/254318.
     41   videoContainer.addEventListener('click', function(e) {
     42     if (e.ctrlKey) {
     43       this.toggleLoopedModeWithFeedback(true);
     44       if (!this.isPlaying())
     45         this.togglePlayStateWithFeedback();
     46     } else {
     47       this.togglePlayStateWithFeedback();
     48     }
     49   }.wrap(this));
     50 
     51   this.inactivityWatcher_ = new MouseInactivityWatcher(playerContainer);
     52   this.__defineGetter__('inactivityWatcher', function() {
     53     return this.inactivityWatcher_;
     54   }.wrap(this));
     55 
     56   this.inactivityWatcher_.check();
     57 }
     58 
     59 FullWindowVideoControls.prototype = { __proto__: VideoControls.prototype };
     60 
     61 /**
     62  * Displays error message.
     63  *
     64  * @param {string} message Message id.
     65  * @private
     66  */
     67 FullWindowVideoControls.prototype.showErrorMessage_ = function(message) {
     68   var errorBanner = document.querySelector('#error');
     69   errorBanner.textContent =
     70       loadTimeData.getString(message);
     71   errorBanner.setAttribute('visible', 'true');
     72 
     73   // The window is hidden if the video has not loaded yet.
     74   chrome.app.window.current().show();
     75 };
     76 
     77 /**
     78  * Handles playback (decoder) errors.
     79  * @private
     80  */
     81 FullWindowVideoControls.prototype.onPlaybackError_ = function() {
     82   this.showErrorMessage_('GALLERY_VIDEO_DECODING_ERROR');
     83   this.decodeErrorOccured = true;
     84 
     85   // Disable inactivity watcher, and disable the ui, by hiding tools manually.
     86   this.inactivityWatcher.disabled = true;
     87   document.querySelector('#video-player').setAttribute('disabled', 'true');
     88 
     89   // Detach the video element, since it may be unreliable and reset stored
     90   // current playback time.
     91   this.cleanup();
     92   this.clearState();
     93 
     94   // Avoid reusing a video element.
     95   player.unloadVideo();
     96 };
     97 
     98 /**
     99  * Toggles the full screen mode.
    100  * @private
    101  */
    102 FullWindowVideoControls.prototype.toggleFullScreen_ = function() {
    103   var appWindow = chrome.app.window.current();
    104   util.toggleFullScreen(appWindow, !util.isFullScreen(appWindow));
    105 };
    106 
    107 /**
    108  * @constructor
    109  */
    110 function VideoPlayer() {
    111   this.controls_ = null;
    112   this.videoElement_ = null;
    113   this.videos_ = null;
    114   this.currentPos_ = 0;
    115 
    116   Object.seal(this);
    117 }
    118 
    119 VideoPlayer.prototype = {
    120   get controls() {
    121     return this.controls_;
    122   }
    123 };
    124 
    125 /**
    126  * Initializes the video player window. This method must be called after DOM
    127  * initialization.
    128  * @param {Array.<Object.<string, Object>>} videos List of videos.
    129  */
    130 VideoPlayer.prototype.prepare = function(videos) {
    131   this.videos_ = videos;
    132 
    133   var preventDefault = function(event) { event.preventDefault(); }.wrap(null);
    134 
    135   document.ondragstart = preventDefault;
    136 
    137   var maximizeButton = document.querySelector('.maximize-button');
    138   maximizeButton.addEventListener(
    139     'click',
    140     function() {
    141       var appWindow = chrome.app.window.current();
    142       if (appWindow.isMaximized())
    143         appWindow.restore();
    144       else
    145         appWindow.maximize();
    146     }.wrap(null));
    147   maximizeButton.addEventListener('mousedown', preventDefault);
    148 
    149   var minimizeButton = document.querySelector('.minimize-button');
    150   minimizeButton.addEventListener(
    151     'click',
    152     function() {
    153       chrome.app.window.current().minimize()
    154     }.wrap(null));
    155   minimizeButton.addEventListener('mousedown', preventDefault);
    156 
    157   var closeButton = document.querySelector('.close-button');
    158   closeButton.addEventListener(
    159     'click',
    160     function() { close(); }.wrap(null));
    161   closeButton.addEventListener('mousedown', preventDefault);
    162 
    163   this.controls_ = new FullWindowVideoControls(
    164       document.querySelector('#video-player'),
    165       document.querySelector('#video-container'),
    166       document.querySelector('#controls'));
    167 
    168   var reloadVideo = function(e) {
    169     if (this.controls_.decodeErrorOccured &&
    170         // Ignore shortcut keys
    171         !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
    172       this.reloadCurrentVideo_(function() {
    173         this.videoElement_.play();
    174       }.wrap(this));
    175       e.preventDefault();
    176     }
    177   }.wrap(this);
    178 
    179   var arrowRight = document.querySelector('.arrow-box .arrow.right');
    180   arrowRight.addEventListener('click', this.advance_.wrap(this, 1));
    181   var arrowLeft = document.querySelector('.arrow-box .arrow.left');
    182   arrowLeft.addEventListener('click', this.advance_.wrap(this, 0));
    183 
    184   var videoPlayerElement = document.querySelector('#video-player');
    185   if (videos.length > 1)
    186     videoPlayerElement.setAttribute('multiple', true);
    187   else
    188     videoPlayerElement.removeAttribute('multiple');
    189 
    190   document.addEventListener('keydown', reloadVideo, true);
    191   document.addEventListener('click', reloadVideo, true);
    192 };
    193 
    194 /**
    195  * Unloads the player.
    196  */
    197 function unload() {
    198   if (!player.controls || !player.controls.getMedia())
    199     return;
    200 
    201   player.controls.savePosition(true /* exiting */);
    202   player.controls.cleanup();
    203 }
    204 
    205 /**
    206  * Loads the video file.
    207  * @param {string} url URL of the video file.
    208  * @param {string} title Title of the video file.
    209  * @param {function()=} opt_callback Completion callback.
    210  * @private
    211  */
    212 VideoPlayer.prototype.loadVideo_ = function(url, title, opt_callback) {
    213   this.unloadVideo();
    214 
    215   document.title = title;
    216 
    217   document.querySelector('#title').innerText = title;
    218 
    219   var videoPlayerElement = document.querySelector('#video-player');
    220   if (this.currentPos_ === (this.videos_.length - 1))
    221     videoPlayerElement.setAttribute('last-video', true);
    222   else
    223     videoPlayerElement.removeAttribute('last-video');
    224 
    225   if (this.currentPos_ === 0)
    226     videoPlayerElement.setAttribute('first-video', true);
    227   else
    228     videoPlayerElement.removeAttribute('first-video');
    229 
    230   // Re-enables ui and hides error message if already displayed.
    231   document.querySelector('#video-player').removeAttribute('disabled');
    232   document.querySelector('#error').removeAttribute('visible');
    233   this.controls.inactivityWatcher.disabled = false;
    234   this.controls.decodeErrorOccured = false;
    235 
    236   this.videoElement_ = document.createElement('video');
    237   document.querySelector('#video-container').appendChild(this.videoElement_);
    238   this.controls.attachMedia(this.videoElement_);
    239 
    240   this.videoElement_.src = url;
    241   this.videoElement_.load();
    242 
    243   if (opt_callback) {
    244     var handler = function(currentPos, event) {
    245       console.log('loaded: ', currentPos, this.currentPos_);
    246       if (currentPos === this.currentPos_)
    247         opt_callback();
    248       this.videoElement_.removeEventListener('loadedmetadata', handler);
    249     }.wrap(this, this.currentPos_);
    250 
    251     this.videoElement_.addEventListener('loadedmetadata', handler);
    252   }
    253 };
    254 
    255 /**
    256  * Plays the first video.
    257  */
    258 VideoPlayer.prototype.playFirstVideo = function() {
    259   this.currentPos_ = 0;
    260   this.reloadCurrentVideo_(this.onFirstVideoReady_.wrap(this));
    261 };
    262 
    263 /**
    264  * Unloads the current video.
    265  */
    266 VideoPlayer.prototype.unloadVideo = function() {
    267   // Detach the previous video element, if exists.
    268   if (this.videoElement_)
    269     this.videoElement_.parentNode.removeChild(this.videoElement_);
    270   this.videoElement_ = null;
    271 };
    272 
    273 /**
    274  * Called when the first video is ready after starting to load.
    275  * @private
    276  */
    277 VideoPlayer.prototype.onFirstVideoReady_ = function() {
    278   // TODO: chrome.app.window soon will be able to resize the content area.
    279   // Until then use approximate title bar height.
    280   var TITLE_HEIGHT = 33;
    281 
    282   var videoWidth = this.videoElement_.videoWidth;
    283   var videoHeight = this.videoElement_.videoHeight;
    284 
    285   var aspect = videoWidth / videoHeight;
    286   var newWidth = videoWidth;
    287   var newHeight = videoHeight + TITLE_HEIGHT;
    288 
    289   var shrinkX = newWidth / window.screen.availWidth;
    290   var shrinkY = newHeight / window.screen.availHeight;
    291   if (shrinkX > 1 || shrinkY > 1) {
    292     if (shrinkY > shrinkX) {
    293       newHeight = newHeight / shrinkY;
    294       newWidth = (newHeight - TITLE_HEIGHT) * aspect;
    295     } else {
    296       newWidth = newWidth / shrinkX;
    297       newHeight = newWidth / aspect + TITLE_HEIGHT;
    298     }
    299   }
    300 
    301   var oldLeft = window.screenX;
    302   var oldTop = window.screenY;
    303   var oldWidth = window.outerWidth;
    304   var oldHeight = window.outerHeight;
    305 
    306   if (!oldWidth && !oldHeight) {
    307     oldLeft = window.screen.availWidth / 2;
    308     oldTop = window.screen.availHeight / 2;
    309   }
    310 
    311   var appWindow = chrome.app.window.current();
    312   appWindow.resizeTo(newWidth, newHeight);
    313   appWindow.moveTo(oldLeft - (newWidth - oldWidth) / 2,
    314                    oldTop - (newHeight - oldHeight) / 2);
    315   appWindow.show();
    316 
    317   this.videoElement_.play();
    318 };
    319 
    320 /**
    321  * Advances to the next (or previous) track.
    322  *
    323  * @param {boolean} direction True to the next, false to the previous.
    324  * @private
    325  */
    326 VideoPlayer.prototype.advance_ = function(direction) {
    327   var newPos = this.currentPos_ + (direction ? 1 : -1);
    328   if (0 <= newPos && newPos < this.videos_.length) {
    329     this.currentPos_ = newPos;
    330     this.reloadCurrentVideo_(function() {
    331       this.videoElement_.play();
    332     }.wrap(this));
    333   }
    334 };
    335 
    336 /**
    337  * Reloads the current video.
    338  *
    339  * @param {function()=} opt_callback Completion callback.
    340  * @private
    341  */
    342 VideoPlayer.prototype.reloadCurrentVideo_ = function(opt_callback) {
    343   var currentVideo = this.videos_[this.currentPos_];
    344   this.loadVideo_(currentVideo.fileUrl, currentVideo.entry.name, opt_callback);
    345 };
    346 
    347 /**
    348  * Initialize the list of videos.
    349  * @param {function(Array.<Object>)} callback Called with the video list when
    350  *     it is ready.
    351  */
    352 function initVideos(callback) {
    353   if (window.videos) {
    354     var videos = window.videos;
    355     window.videos = null;
    356     callback(videos);
    357     return;
    358   }
    359 
    360   chrome.runtime.onMessage.addListener(
    361       function(request, sender, sendResponse) {
    362         var videos = window.videos;
    363         window.videos = null;
    364         callback(videos);
    365       }.wrap(null));
    366 }
    367 
    368 var player = new VideoPlayer();
    369 
    370 /**
    371  * Initializes the strings.
    372  * @param {function()} callback Called when the sting data is ready.
    373  */
    374 function initStrings(callback) {
    375   chrome.fileBrowserPrivate.getStrings(function(strings) {
    376     loadTimeData.data = strings;
    377     callback();
    378   }.wrap(null));
    379 }
    380 
    381 var initPromise = Promise.all(
    382     [new Promise(initVideos.wrap(null)),
    383      new Promise(initStrings.wrap(null)),
    384      new Promise(util.addPageLoadHandler.wrap(null))]);
    385 
    386 initPromise.then(function(results) {
    387   var videos = results[0];
    388   player.prepare(videos);
    389   return new Promise(player.playFirstVideo.wrap(player));
    390 }.wrap(null));
    391