1 // Copyright (c) 2012 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 * Display error message. 9 * @param {string} message Message id. 10 */ 11 function showErrorMessage(message) { 12 var errorBanner = document.querySelector('#error'); 13 errorBanner.textContent = 14 loadTimeData.getString(message); 15 errorBanner.setAttribute('visible', 'true'); 16 17 // The window is hidden if the video has not loaded yet. 18 chrome.app.window.current().show(); 19 } 20 21 /** 22 * Handles playback (decoder) errors. 23 */ 24 function onPlaybackError() { 25 showErrorMessage('GALLERY_VIDEO_DECODING_ERROR'); 26 decodeErrorOccured = true; 27 28 // Disable inactivity watcher, and disable the ui, by hiding tools manually. 29 controls.inactivityWatcher.disabled = true; 30 document.querySelector('#video-player').setAttribute('disabled', 'true'); 31 32 // Detach the video element, since it may be unreliable and reset stored 33 // current playback time. 34 controls.cleanup(); 35 controls.clearState(); 36 37 // Avoid reusing a video element. 38 video.parentNode.removeChild(video); 39 video = null; 40 } 41 42 /** 43 * @param {Element} playerContainer Main container. 44 * @param {Element} videoContainer Container for the video element. 45 * @param {Element} controlsContainer Container for video controls. 46 * @constructor 47 */ 48 function FullWindowVideoControls( 49 playerContainer, videoContainer, controlsContainer) { 50 VideoControls.call(this, 51 controlsContainer, 52 onPlaybackError, 53 loadTimeData.getString.bind(loadTimeData), 54 this.toggleFullScreen_.bind(this), 55 videoContainer); 56 57 this.playerContainer_ = playerContainer; 58 59 this.updateStyle(); 60 window.addEventListener('resize', this.updateStyle.bind(this)); 61 62 document.addEventListener('keydown', function(e) { 63 if (e.keyIdentifier == 'U+0020') { // Space 64 this.togglePlayStateWithFeedback(); 65 e.preventDefault(); 66 } 67 if (e.keyIdentifier == 'U+001B') { // Escape 68 util.toggleFullScreen( 69 chrome.app.window.current(), 70 false); // Leave the full screen mode. 71 e.preventDefault(); 72 } 73 }.bind(this)); 74 75 // TODO(mtomasz): Simplify. crbug.com/254318. 76 videoContainer.addEventListener('click', function(e) { 77 if (e.ctrlKey) { 78 this.toggleLoopedModeWithFeedback(true); 79 if (!this.isPlaying()) 80 this.togglePlayStateWithFeedback(); 81 } else { 82 this.togglePlayStateWithFeedback(); 83 } 84 }.bind(this)); 85 86 this.inactivityWatcher_ = new MouseInactivityWatcher(playerContainer); 87 this.__defineGetter__('inactivityWatcher', function() { 88 return this.inactivityWatcher_; 89 }); 90 91 this.inactivityWatcher_.check(); 92 } 93 94 FullWindowVideoControls.prototype = { __proto__: VideoControls.prototype }; 95 96 /** 97 * Save the current state so that it survives page/app reload. 98 */ 99 FullWindowVideoControls.prototype.onPlayStateChanged = function() { 100 this.encodeState(); 101 }; 102 103 /** 104 * Restore the state after the video is loaded. 105 */ 106 FullWindowVideoControls.prototype.restorePlayState = function() { 107 if (!this.decodeState()) { 108 VideoControls.prototype.restorePlayState.apply(this, arguments); 109 this.play(); 110 } 111 }; 112 113 /** 114 * Toggles the full screen mode. 115 * @private 116 */ 117 FullWindowVideoControls.prototype.toggleFullScreen_ = function() { 118 var appWindow = chrome.app.window.current(); 119 util.toggleFullScreen(appWindow, !util.isFullScreen(appWindow)); 120 }; 121 122 // TODO(mtomasz): Convert it to class members: crbug.com/171191. 123 var decodeErrorOccured; 124 var video; 125 var controls; 126 var metadataCache; 127 var volumeManager; 128 var selectedItemFilesystemPath; 129 130 /** 131 * Initialize the video player window. 132 */ 133 function loadVideoPlayer() { 134 document.ondragstart = function(e) { e.preventDefault() }; 135 136 chrome.fileBrowserPrivate.getStrings(function(strings) { 137 loadTimeData.data = strings; 138 139 controls = new FullWindowVideoControls( 140 document.querySelector('#video-player'), 141 document.querySelector('#video-container'), 142 document.querySelector('#controls')); 143 144 metadataCache = MetadataCache.createFull(); 145 volumeManager = VolumeManager.getInstance(); 146 147 // If the video player is starting before the first instance of the File 148 // Manager then it does not have access to filesystem URLs. Request it now. 149 chrome.fileBrowserPrivate.requestFileSystem(reload); 150 151 volumeManager.addEventListener('externally-unmounted', 152 onExternallyUnmounted); 153 154 var reloadVideo = function(e) { 155 if (decodeErrorOccured) { 156 reload(); 157 e.preventDefault(); 158 } 159 }; 160 161 document.addEventListener('keydown', reloadVideo, true); 162 document.addEventListener('click', reloadVideo, true); 163 164 }); 165 } 166 167 /** 168 * Closes video player when a volume containing the played item is unmounted. 169 * @param {Event} event The unmount event. 170 */ 171 function onExternallyUnmounted(event) { 172 if (!selectedItemFilesystemPath) 173 return; 174 if (selectedItemFilesystemPath.indexOf(event.mountPath) == 0) 175 window.close(); 176 } 177 178 /** 179 * Unload the player. 180 */ 181 function unload() { 182 if (!controls.getMedia()) 183 return; 184 185 controls.savePosition(true /* exiting */); 186 controls.cleanup(); 187 } 188 189 /** 190 * Reload the player. 191 */ 192 function reload() { 193 // Re-enable ui and hide error message if already displayed. 194 document.querySelector('#video-player').removeAttribute('disabled'); 195 document.querySelector('#error').removeAttribute('visible'); 196 controls.inactivityWatcher.disabled = false; 197 decodeErrorOccured = false; 198 199 var src; 200 if (window.appState) { 201 util.saveAppState(); 202 src = window.appState.url; 203 } else { 204 src = document.location.search.substr(1); 205 } 206 if (!src) { 207 showErrorMessage('GALLERY_VIDEO_ERROR'); 208 return; 209 } 210 211 document.title = decodeURIComponent(src.split('/').pop()); 212 213 metadataCache.get(src, 'streaming', function(streaming) { 214 if (streaming && !navigator.onLine) { 215 showErrorMessage('GALLERY_VIDEO_OFFLINE'); 216 return; 217 } 218 219 // Detach the previous video element, if exists. 220 if (video) { 221 video.parentNode.removeChild(video); 222 } 223 224 video = document.createElement('video'); 225 document.querySelector('#video-container').appendChild(video); 226 controls.attachMedia(video); 227 228 video.src = src; 229 video.load(); 230 video.addEventListener('loadedmetadata', function() { 231 // TODO: chrome.app.window soon will be able to resize the content area. 232 // Until then use approximate title bar height. 233 var TITLE_HEIGHT = 28; 234 235 var aspect = video.videoWidth / video.videoHeight; 236 var newWidth = video.videoWidth; 237 var newHeight = video.videoHeight + TITLE_HEIGHT; 238 239 var shrinkX = newWidth / window.screen.availWidth; 240 var shrinkY = newHeight / window.screen.availHeight; 241 if (shrinkX > 1 || shrinkY > 1) { 242 if (shrinkY > shrinkX) { 243 newHeight = newHeight / shrinkY; 244 newWidth = (newHeight - TITLE_HEIGHT) * aspect; 245 } else { 246 newWidth = newWidth / shrinkX; 247 newHeight = newWidth / aspect + TITLE_HEIGHT; 248 } 249 } 250 251 var oldLeft = window.screenX; 252 var oldTop = window.screenY; 253 var oldWidth = window.outerWidth; 254 var oldHeight = window.outerHeight; 255 256 if (!oldWidth && !oldHeight) { 257 oldLeft = window.screen.availWidth / 2; 258 oldTop = window.screen.availHeight / 2; 259 } 260 261 var appWindow = chrome.app.window.current(); 262 appWindow.resizeTo(newWidth, newHeight); 263 appWindow.moveTo(oldLeft - (newWidth - oldWidth) / 2, 264 oldTop - (newHeight - oldHeight) / 2); 265 appWindow.show(); 266 }); 267 268 // Resolve real filesystem path of the current video. 269 selectedItemFilesystemPath = null; 270 webkitResolveLocalFileSystemURL(src, 271 function(entry) { 272 var video = document.querySelector('video'); 273 if (video.src != src) return; 274 selectedItemFilesystemPath = entry.fullPath; 275 }); 276 }); 277 } 278 279 util.addPageLoadHandler(loadVideoPlayer); 280