|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | /** | 
|  | * @param {!HTMLElement} playerContainer Main container. | 
|  | * @param {!HTMLElement} videoContainer Container for the video element. | 
|  | * @param {!HTMLElement} controlsContainer Container for video controls. | 
|  | * @constructor | 
|  | * @struct | 
|  | * @extends {VideoControls} | 
|  | */ | 
|  | function FullWindowVideoControls( | 
|  | playerContainer, videoContainer, controlsContainer) { | 
|  | VideoControls.call(this, | 
|  | controlsContainer, | 
|  | this.onPlaybackError_.wrap(this), | 
|  | this.toggleFullScreen_.wrap(this), | 
|  | videoContainer); | 
|  |  | 
|  | this.playerContainer_ = playerContainer; | 
|  | this.decodeErrorOccured = false; | 
|  |  | 
|  | this.casting = false; | 
|  |  | 
|  | var currentWindow = chrome.app.window.current(); | 
|  | currentWindow.onFullscreened.addListener(this.onFullScreenChanged.bind(this)); | 
|  | currentWindow.onRestored.addListener(this.onFullScreenChanged.bind(this)); | 
|  | document.addEventListener('keydown', function(e) { | 
|  | this.inactivityWatcher_.kick(); | 
|  | switch (util.getKeyModifiers(e) + e.key) { | 
|  | // Handle debug shortcut keys. | 
|  | case 'Ctrl-Shift-I': // Ctrl+Shift+I | 
|  | chrome.fileManagerPrivate.openInspector('normal'); | 
|  | break; | 
|  | case 'Ctrl-Shift-J': // Ctrl+Shift+J | 
|  | chrome.fileManagerPrivate.openInspector('console'); | 
|  | break; | 
|  | case 'Ctrl-Shift-C': // Ctrl+Shift+C | 
|  | chrome.fileManagerPrivate.openInspector('element'); | 
|  | break; | 
|  | case 'Ctrl-Shift-B': // Ctrl+Shift+B | 
|  | chrome.fileManagerPrivate.openInspector('background'); | 
|  | break; | 
|  |  | 
|  | case ' ': // Space | 
|  | case 'k': | 
|  | case 'MediaPlayPause': | 
|  | if (!e.target.classList.contains('menu-button')) | 
|  | this.togglePlayStateWithFeedback(); | 
|  | break; | 
|  | case 'Escape': | 
|  | util.toggleFullScreen( | 
|  | chrome.app.window.current(), | 
|  | false);  // Leave the full screen mode. | 
|  | break; | 
|  | case 'MediaTrackNext': | 
|  | player.advance_(1); | 
|  | break; | 
|  | case 'MediaTrackPrevious': | 
|  | player.advance_(0); | 
|  | break; | 
|  | case 'ArrowRight': | 
|  | if (!e.target.classList.contains('volume')) | 
|  | this.smallSkip(true); | 
|  | break; | 
|  | case 'ArrowLeft': | 
|  | if (!e.target.classList.contains('volume')) | 
|  | this.smallSkip(false); | 
|  | break; | 
|  | case 'l': | 
|  | this.bigSkip(true); | 
|  | break; | 
|  | case 'j': | 
|  | this.bigSkip(false); | 
|  | break; | 
|  | case 'BrowserBack': | 
|  | chrome.app.window.current().close(); | 
|  | break; | 
|  | case 'MediaStop': | 
|  | // TODO: Define "Stop" behavior. | 
|  | break; | 
|  | } | 
|  | }.wrap(this)); | 
|  | document.addEventListener('keypress', function(e) { | 
|  | this.inactivityWatcher_.kick(); | 
|  | }.wrap(this)); | 
|  |  | 
|  | // TODO(mtomasz): Simplify. crbug.com/254318. | 
|  | var clickInProgress = false; | 
|  | videoContainer.addEventListener('click', function(e) { | 
|  | if (clickInProgress) | 
|  | return; | 
|  |  | 
|  | clickInProgress = true; | 
|  | var togglePlayState = function() { | 
|  | clickInProgress = false; | 
|  |  | 
|  | if (e.ctrlKey) { | 
|  | this.toggleLoopedModeWithFeedback(true); | 
|  | if (!this.isPlaying()) | 
|  | this.togglePlayStateWithFeedback(); | 
|  | } else { | 
|  | this.togglePlayStateWithFeedback(); | 
|  | } | 
|  | }.wrap(this); | 
|  |  | 
|  | if (!this.media_) | 
|  | player.reloadCurrentVideo(togglePlayState); | 
|  | else | 
|  | setTimeout(togglePlayState, 0); | 
|  | }.wrap(this)); | 
|  |  | 
|  | /** | 
|  | * @type {MouseInactivityWatcher} | 
|  | * @private | 
|  | */ | 
|  | this.inactivityWatcher_ = new MouseInactivityWatcher(playerContainer); | 
|  | this.inactivityWatcher_.check(); | 
|  | } | 
|  |  | 
|  | FullWindowVideoControls.prototype = { __proto__: VideoControls.prototype }; | 
|  |  | 
|  | /** | 
|  | * Gets inactivity watcher. | 
|  | * @return {MouseInactivityWatcher} An inactivity watcher. | 
|  | */ | 
|  | FullWindowVideoControls.prototype.getInactivityWatcher = function() { | 
|  | return this.inactivityWatcher_; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Displays error message. | 
|  | * | 
|  | * @param {string} message Message id. | 
|  | */ | 
|  | FullWindowVideoControls.prototype.showErrorMessage = function(message) { | 
|  | var errorBanner = getRequiredElement('error'); | 
|  | errorBanner.textContent = str(message); | 
|  | errorBanner.setAttribute('visible', 'true'); | 
|  |  | 
|  | // The window is hidden if the video has not loaded yet. | 
|  | chrome.app.window.current().show(); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Handles playback (decoder) errors. | 
|  | * @param {MediaError} error Error object. | 
|  | * @private | 
|  | */ | 
|  | FullWindowVideoControls.prototype.onPlaybackError_ = function(error) { | 
|  | if (error.target && error.target.error && | 
|  | error.target.error.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) { | 
|  | if (this.casting) | 
|  | this.showErrorMessage('VIDEO_PLAYER_VIDEO_FILE_UNSUPPORTED_FOR_CAST'); | 
|  | else | 
|  | this.showErrorMessage('VIDEO_PLAYER_VIDEO_FILE_UNSUPPORTED'); | 
|  | this.decodeErrorOccured = false; | 
|  | } else { | 
|  | this.showErrorMessage('VIDEO_PLAYER_PLAYBACK_ERROR'); | 
|  | this.decodeErrorOccured = true; | 
|  | } | 
|  |  | 
|  | // Disable controls on the ui. | 
|  | getRequiredElement('video-player').setAttribute('disabled', 'true'); | 
|  |  | 
|  | // Detach the video element, since it may be unreliable and reset stored | 
|  | // current playback time. | 
|  | this.cleanup(); | 
|  | this.clearState(); | 
|  |  | 
|  | // Avoid reusing a video element. | 
|  | player.unloadVideo(); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Toggles the full screen mode. | 
|  | * @private | 
|  | */ | 
|  | FullWindowVideoControls.prototype.toggleFullScreen_ = function() { | 
|  | var appWindow = chrome.app.window.current(); | 
|  | util.toggleFullScreen(appWindow, !util.isFullScreen(appWindow)); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Media completion handler. | 
|  | */ | 
|  | FullWindowVideoControls.prototype.onMediaComplete = function() { | 
|  | VideoControls.prototype.onMediaComplete.apply(this, arguments); | 
|  | if (!this.getMedia().loop) | 
|  | player.advance_(1); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Video Player | 
|  | * | 
|  | * @constructor | 
|  | * @struct | 
|  | */ | 
|  | function VideoPlayer() { | 
|  | this.controls_ = null; | 
|  | this.videoElement_ = null; | 
|  |  | 
|  | /** | 
|  | * @type {Array<!FileEntry>} | 
|  | * @private | 
|  | */ | 
|  | this.videos_ = null; | 
|  |  | 
|  | this.currentPos_ = 0; | 
|  |  | 
|  | this.currentSession_ = null; | 
|  | this.currentCast_ = null; | 
|  |  | 
|  | this.loadQueue_ = new AsyncUtil.Queue(); | 
|  |  | 
|  | this.onCastSessionUpdateBound_ = this.onCastSessionUpdate_.wrap(this); | 
|  | } | 
|  |  | 
|  | VideoPlayer.prototype = /** @struct */ { | 
|  | /** | 
|  | * @return {FullWindowVideoControls} | 
|  | */ | 
|  | get controls() { | 
|  | return this.controls_; | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Initializes the video player window. This method must be called after DOM | 
|  | * initialization. | 
|  | * @param {!Array<!FileEntry>} videos List of videos. | 
|  | */ | 
|  | VideoPlayer.prototype.prepare = function(videos) { | 
|  | this.videos_ = videos; | 
|  |  | 
|  | var preventDefault = function(event) { event.preventDefault(); }.wrap(null); | 
|  |  | 
|  | document.ondragstart = preventDefault; | 
|  |  | 
|  | cr.ui.decorate(getRequiredElement('cast-menu'), cr.ui.Menu); | 
|  |  | 
|  | this.controls_ = new FullWindowVideoControls( | 
|  | getRequiredElement('video-player'), | 
|  | getRequiredElement('video-container'), | 
|  | getRequiredElement('controls')); | 
|  |  | 
|  | var observer = new MutationObserver(function(mutations) { | 
|  | var isLoadingOrDisabledChanged = mutations.some(function(mutation) { | 
|  | return mutation.attributeName === 'loading' || | 
|  | mutation.attributeName === 'disabled'; | 
|  | }); | 
|  | if (isLoadingOrDisabledChanged) | 
|  | this.updateInactivityWatcherState_(); | 
|  | }.bind(this)); | 
|  | observer.observe(getRequiredElement('video-player'), | 
|  | { attributes: true, childList: false }); | 
|  |  | 
|  | var reloadVideo = function(e) { | 
|  | if (this.controls_.decodeErrorOccured && | 
|  | // Ignore shortcut keys | 
|  | !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { | 
|  | this.reloadCurrentVideo(function() { | 
|  | this.videoElement_.play(); | 
|  | }.wrap(this)); | 
|  | e.preventDefault(); | 
|  | } | 
|  | }.wrap(this); | 
|  |  | 
|  | var arrowRight = queryRequiredElement('.arrow-box .arrow.right'); | 
|  | arrowRight.addEventListener('click', this.advance_.wrap(this, 1)); | 
|  | var arrowLeft = queryRequiredElement('.arrow-box .arrow.left'); | 
|  | arrowLeft.addEventListener('click', this.advance_.wrap(this, 0)); | 
|  |  | 
|  | var videoPlayerElement = getRequiredElement('video-player'); | 
|  | if (videos.length > 1) | 
|  | videoPlayerElement.setAttribute('multiple', true); | 
|  | else | 
|  | videoPlayerElement.removeAttribute('multiple'); | 
|  |  | 
|  | var castButton = queryRequiredElement('.cast-button'); | 
|  | castButton.addEventListener('click', | 
|  | this.onCastButtonClicked_.wrap(this)); | 
|  |  | 
|  | document.addEventListener('keydown', reloadVideo); | 
|  | document.addEventListener('click', reloadVideo); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Unloads the player. | 
|  | */ | 
|  | function unload() { | 
|  | // Releases keep awake just in case (should be released on unloading video). | 
|  | chrome.power.releaseKeepAwake(); | 
|  |  | 
|  | if (!player.controls || !player.controls.getMedia()) | 
|  | return; | 
|  |  | 
|  | player.controls.savePosition(true /* exiting */); | 
|  | player.controls.cleanup(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Loads the video file. | 
|  | * @param {!FileEntry} video Entry of the video to be played. | 
|  | * @param {function()=} opt_callback Completion callback. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.loadVideo_ = function(video, opt_callback) { | 
|  | this.unloadVideo(true); | 
|  |  | 
|  | this.loadQueue_.run(function(callback) { | 
|  | document.title = video.name; | 
|  |  | 
|  | var videoPlayerElement = getRequiredElement('video-player'); | 
|  | if (this.currentPos_ === (this.videos_.length - 1)) | 
|  | videoPlayerElement.setAttribute('last-video', true); | 
|  | else | 
|  | videoPlayerElement.removeAttribute('last-video'); | 
|  |  | 
|  | if (this.currentPos_ === 0) | 
|  | videoPlayerElement.setAttribute('first-video', true); | 
|  | else | 
|  | videoPlayerElement.removeAttribute('first-video'); | 
|  |  | 
|  | // Re-enables ui and hides error message if already displayed. | 
|  | getRequiredElement('video-player').removeAttribute('disabled'); | 
|  | getRequiredElement('error').removeAttribute('visible'); | 
|  | this.controls.detachMedia(); | 
|  | this.controls.decodeErrorOccured = false; | 
|  | this.controls.casting = !!this.currentCast_; | 
|  |  | 
|  | videoPlayerElement.setAttribute('loading', true); | 
|  |  | 
|  | var media = new MediaManager(video); | 
|  |  | 
|  | // Show video's thumbnail if available while loading the video. | 
|  | media.getThumbnail() | 
|  | .then(function(thumbnailUrl) { | 
|  | if (!thumbnailUrl) | 
|  | return Promise.reject(); | 
|  |  | 
|  | return new Promise(function(resolve, reject) { | 
|  | ImageLoaderClient.getInstance().load( | 
|  | thumbnailUrl, | 
|  | function(result) { | 
|  | if (result.data) | 
|  | resolve(result.data); | 
|  | else | 
|  | reject(); | 
|  | }); | 
|  | }); | 
|  | }) | 
|  | .then(function(dataUrl) { | 
|  | getRequiredElement('thumbnail').style.backgroundImage = | 
|  | 'url(' + dataUrl + ')'; | 
|  | }) | 
|  | .catch(function() { | 
|  | // Shows no image on error. | 
|  | getRequiredElement('thumbnail').style.backgroundImage = ''; | 
|  | }); | 
|  |  | 
|  | var videoElementInitializePromise; | 
|  | if (this.currentCast_) { | 
|  | metrics.recordPlayType(metrics.PLAY_TYPE.CAST); | 
|  |  | 
|  | getRequiredElement('cast-name').textContent = | 
|  | this.currentCast_.friendlyName; | 
|  |  | 
|  | videoPlayerElement.setAttribute('castable', true); | 
|  |  | 
|  | videoElementInitializePromise = media.isAvailableForCast() | 
|  | .then(function(result) { | 
|  | if (!result) | 
|  | return Promise.reject('No casts are available.'); | 
|  |  | 
|  | return new Promise(function(fulfill, reject) { | 
|  | if (this.currentSession_) { | 
|  | fulfill(this.currentSession_); | 
|  | } else { | 
|  | chrome.cast.requestSession( | 
|  | fulfill, reject, undefined, this.currentCast_.label); | 
|  | } | 
|  | }.bind(this)).then(function(session) { | 
|  | videoPlayerElement.setAttribute('casting', true); | 
|  | session.addUpdateListener(this.onCastSessionUpdateBound_); | 
|  |  | 
|  | this.currentSession_ = session; | 
|  | this.videoElement_ = new CastVideoElement(media, session); | 
|  | }.bind(this)); | 
|  | }.bind(this)); | 
|  | } else { | 
|  | metrics.recordPlayType(metrics.PLAY_TYPE.LOCAL); | 
|  | videoPlayerElement.removeAttribute('casting'); | 
|  |  | 
|  | this.videoElement_ = document.createElement('video'); | 
|  | getRequiredElement('video-container').appendChild(this.videoElement_); | 
|  |  | 
|  | var videoUrl = video.toURL(); | 
|  | var source = document.createElement('source'); | 
|  | source.src = videoUrl; | 
|  | this.videoElement_.appendChild(source); | 
|  |  | 
|  | media.isAvailableForCast().then(function(result) { | 
|  | if (result) | 
|  | videoPlayerElement.setAttribute('castable', true); | 
|  | else | 
|  | videoPlayerElement.removeAttribute('castable'); | 
|  | }).catch(function() { | 
|  | videoPlayerElement.setAttribute('castable', true); | 
|  | }); | 
|  |  | 
|  | videoElementInitializePromise = this.searchSubtitle_(videoUrl) | 
|  | .then(function(subltitleUrl) { | 
|  | if (subltitleUrl) { | 
|  | var track = document.createElement('track'); | 
|  | track.src = subltitleUrl; | 
|  | track.kind = 'subtitles'; | 
|  | track.default = true; | 
|  | this.videoElement_.appendChild(track); | 
|  | } | 
|  | }.bind(this)); | 
|  | } | 
|  | videoElementInitializePromise | 
|  | .then(function() { | 
|  | var handler = function(currentPos) { | 
|  | if (currentPos === this.currentPos_) { | 
|  | if (opt_callback) | 
|  | opt_callback(); | 
|  | videoPlayerElement.removeAttribute('loading'); | 
|  | } | 
|  |  | 
|  | this.videoElement_.removeEventListener('loadedmetadata', handler); | 
|  | }.wrap(this, this.currentPos_); | 
|  |  | 
|  | this.videoElement_.addEventListener('loadedmetadata', handler); | 
|  |  | 
|  | this.videoElement_.addEventListener('play', function() { | 
|  | chrome.power.requestKeepAwake('display'); | 
|  | this.updateInactivityWatcherState_(); | 
|  | }.wrap(this)); | 
|  | this.videoElement_.addEventListener('pause', function() { | 
|  | chrome.power.releaseKeepAwake(); | 
|  | this.updateInactivityWatcherState_(); | 
|  | }.wrap(this)); | 
|  | this.controls.attachMedia(this.videoElement_); | 
|  | this.videoElement_.load(); | 
|  | callback(); | 
|  | }.bind(this)) | 
|  | // In case of error. | 
|  | .catch(function(error) { | 
|  | if (this.currentCast_) | 
|  | metrics.recordCastVideoErrorAction(); | 
|  |  | 
|  | videoPlayerElement.removeAttribute('loading'); | 
|  | console.error('Failed to initialize the video element.', | 
|  | error.stack || error); | 
|  | this.controls_.showErrorMessage( | 
|  | 'VIDEO_PLAYER_VIDEO_FILE_UNSUPPORTED'); | 
|  | callback(); | 
|  | }.bind(this)); | 
|  | }.wrap(this)); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Search subtile file corresponding to a video. | 
|  | * @param {string} url a url of a video. | 
|  | * @return {string} a url of subtitle file, or an empty string. | 
|  | */ | 
|  | VideoPlayer.prototype.searchSubtitle_ = function(url) { | 
|  | var baseUrl = util.splitExtension(url)[0]; | 
|  | var resolveLocalFileSystemWithExtension = function(extension) { | 
|  | return new Promise( | 
|  | window.webkitResolveLocalFileSystemURL.bind(null, baseUrl + extension)); | 
|  | }; | 
|  | return resolveLocalFileSystemWithExtension('.vtt').then(function(subtitle) { | 
|  | return subtitle.toURL(); | 
|  | }).catch(function() { | 
|  | return ''; | 
|  | }); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Plays the first video. | 
|  | */ | 
|  | VideoPlayer.prototype.playFirstVideo = function() { | 
|  | this.currentPos_ = 0; | 
|  | this.reloadCurrentVideo(this.onFirstVideoReady_.wrap(this)); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Unloads the current video. | 
|  | * @param {boolean=} opt_keepSession If true, keep using the current session. | 
|  | *     Otherwise, discards the session. | 
|  | */ | 
|  | VideoPlayer.prototype.unloadVideo = function(opt_keepSession) { | 
|  | this.loadQueue_.run(function(callback) { | 
|  | chrome.power.releaseKeepAwake(); | 
|  |  | 
|  | // Detaches the media from the control. | 
|  | this.controls.detachMedia(); | 
|  |  | 
|  | if (this.videoElement_) { | 
|  | // If the element has dispose method, call it (CastVideoElement has it). | 
|  | if (this.videoElement_.dispose) | 
|  | this.videoElement_.dispose(); | 
|  | // Detach the previous video element, if exists. | 
|  | if (this.videoElement_.parentNode) | 
|  | this.videoElement_.parentNode.removeChild(this.videoElement_); | 
|  | } | 
|  | this.videoElement_ = null; | 
|  |  | 
|  | if (!opt_keepSession && this.currentSession_) { | 
|  | // We should not request stop() if the current session is not connected to | 
|  | // the receiver. | 
|  | if (this.currentSession_.status === chrome.cast.SessionStatus.CONNECTED) | 
|  | this.currentSession_.stop(callback, callback); | 
|  | else | 
|  | setTimeout(callback); | 
|  | this.currentSession_.removeUpdateListener(this.onCastSessionUpdateBound_); | 
|  | this.currentSession_ = null; | 
|  | } else { | 
|  | callback(); | 
|  | } | 
|  | }.wrap(this)); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Called when the first video is ready after starting to load. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.onFirstVideoReady_ = function() { | 
|  | var videoWidth = this.videoElement_.videoWidth; | 
|  | var videoHeight = this.videoElement_.videoHeight; | 
|  |  | 
|  | var aspect = videoWidth / videoHeight; | 
|  | var newWidth = videoWidth; | 
|  | var newHeight = videoHeight; | 
|  |  | 
|  | var shrinkX = newWidth / window.screen.availWidth; | 
|  | var shrinkY = newHeight / window.screen.availHeight; | 
|  | if (shrinkX > 1 || shrinkY > 1) { | 
|  | if (shrinkY > shrinkX) { | 
|  | newHeight = newHeight / shrinkY; | 
|  | newWidth = newHeight * aspect; | 
|  | } else { | 
|  | newWidth = newWidth / shrinkX; | 
|  | newHeight = newWidth / aspect; | 
|  | } | 
|  | } | 
|  |  | 
|  | var oldLeft = window.screenX; | 
|  | var oldTop = window.screenY; | 
|  | var oldWidth = window.innerWidth; | 
|  | var oldHeight = window.innerHeight; | 
|  |  | 
|  | if (!oldWidth && !oldHeight) { | 
|  | oldLeft = window.screen.availWidth / 2; | 
|  | oldTop = window.screen.availHeight / 2; | 
|  | } | 
|  |  | 
|  | var appWindow = chrome.app.window.current(); | 
|  | appWindow.innerBounds.width = Math.round(newWidth); | 
|  | appWindow.innerBounds.height = Math.round(newHeight); | 
|  | appWindow.outerBounds.left = Math.max( | 
|  | 0, Math.round(oldLeft - (newWidth - oldWidth) / 2)); | 
|  | appWindow.outerBounds.top = Math.max( | 
|  | 0, Math.round(oldTop - (newHeight - oldHeight) / 2)); | 
|  | appWindow.show(); | 
|  |  | 
|  | this.videoElement_.play(); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Advances to the next (or previous) track. | 
|  | * | 
|  | * @param {boolean} direction True to the next, false to the previous. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.advance_ = function(direction) { | 
|  | var newPos = this.currentPos_ + (direction ? 1 : -1); | 
|  | if (0 <= newPos && newPos < this.videos_.length) { | 
|  | this.currentPos_ = newPos; | 
|  | this.reloadCurrentVideo(function() { | 
|  | this.videoElement_.play(); | 
|  | }.wrap(this)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Reloads the current video. | 
|  | * | 
|  | * @param {function()=} opt_callback Completion callback. | 
|  | */ | 
|  | VideoPlayer.prototype.reloadCurrentVideo = function(opt_callback) { | 
|  | var currentVideo = this.videos_[this.currentPos_]; | 
|  | this.loadVideo_(currentVideo, opt_callback); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Invokes when a menuitem in the cast menu is selected. | 
|  | * @param {Object} cast Selected element in the list of casts. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.onCastSelected_ = function(cast) { | 
|  | // If the selected item is same as the current item, do nothing. | 
|  | if ((this.currentCast_ && this.currentCast_.label) === (cast && cast.label)) | 
|  | return; | 
|  |  | 
|  | this.unloadVideo(false); | 
|  |  | 
|  | // Waits for unloading video. | 
|  | this.loadQueue_.run(function(callback) { | 
|  | this.currentCast_ = cast || null; | 
|  | this.updateCheckOnCastMenu_(); | 
|  | this.reloadCurrentVideo(); | 
|  | callback(); | 
|  | }.wrap(this)); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Set the list of casts. | 
|  | * @param {Array<Object>} casts List of casts. | 
|  | */ | 
|  | VideoPlayer.prototype.setCastList = function(casts) { | 
|  | var videoPlayerElement = getRequiredElement('video-player'); | 
|  | var menu = getRequiredElement('cast-menu'); | 
|  | menu.innerHTML = ''; | 
|  |  | 
|  | // TODO(yoshiki): Handle the case that the current cast disappears. | 
|  |  | 
|  | if (casts.length === 0) { | 
|  | videoPlayerElement.removeAttribute('cast-available'); | 
|  | if (this.currentCast_) | 
|  | this.onCurrentCastDisappear_(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (this.currentCast_) { | 
|  | var currentCastAvailable = casts.some(function(cast) { | 
|  | return this.currentCast_.label === cast.label; | 
|  | }.wrap(this)); | 
|  |  | 
|  | if (!currentCastAvailable) | 
|  | this.onCurrentCastDisappear_(); | 
|  | } | 
|  |  | 
|  | var item = new cr.ui.MenuItem(); | 
|  | item.label = str('VIDEO_PLAYER_PLAY_THIS_COMPUTER'); | 
|  | item.setAttribute('aria-label', item.label); | 
|  | item.castLabel = ''; | 
|  | item.addEventListener('activate', this.onCastSelected_.wrap(this, null)); | 
|  | menu.appendChild(item); | 
|  |  | 
|  | for (var i = 0; i < casts.length; i++) { | 
|  | var item = new cr.ui.MenuItem(); | 
|  | item.label = casts[i].friendlyName; | 
|  | item.setAttribute('aria-label', item.label); | 
|  | item.castLabel = casts[i].label; | 
|  | item.addEventListener('activate', | 
|  | this.onCastSelected_.wrap(this, casts[i])); | 
|  | menu.appendChild(item); | 
|  | } | 
|  | this.updateCheckOnCastMenu_(); | 
|  | videoPlayerElement.setAttribute('cast-available', true); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Tells the availability of cast receivers to VideoPlayeru topdate the | 
|  | * visibility of the cast button.. | 
|  | * @param {boolean} available Whether at least one cast receiver is available. | 
|  | */ | 
|  | VideoPlayer.prototype.setCastAvailability = function(available) { | 
|  | var videoPlayerElement = getRequiredElement('video-player'); | 
|  | if (available) { | 
|  | videoPlayerElement.setAttribute('mr-cast-available', true); | 
|  | } else { | 
|  | videoPlayerElement.removeAttribute('mr-cast-available'); | 
|  | if (this.currentCast_) | 
|  | this.onCurrentCastDisappear_(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Handles click event on cast button to request a session. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.onCastButtonClicked_ = function() { | 
|  | // This method is called only when Media Router is enabled. | 
|  | // In this case, requestSession() will open a built-in dialog (not a dropdown | 
|  | // menu) to choose the receiver, and callback is called with the session | 
|  | // object after user's operation.. | 
|  | chrome.cast.requestSession( | 
|  | function(session) { | 
|  | this.unloadVideo(true); | 
|  | this.loadQueue_.run(function(callback) { | 
|  | this.currentCast_ = { | 
|  | label: session.receiver.label, | 
|  | friendlyName: session.receiver.friendlyName | 
|  | }; | 
|  | this.currentSession_ = session; | 
|  | this.reloadCurrentVideo(); | 
|  | callback(); | 
|  | }.bind(this)); | 
|  | }.bind(this), | 
|  | function(error) { | 
|  | if (error.code !== chrome.cast.ErrorCode.CANCEL) | 
|  | console.error('requestSession from cast button failed', error); | 
|  | }); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Updates the check status of the cast menu items. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.updateCheckOnCastMenu_ = function() { | 
|  | var menuItems = | 
|  | /** @type {cr.ui.Menu} */ (getRequiredElement('cast-menu')).menuItems; | 
|  | for (var i = 0; i < menuItems.length; i++) { | 
|  | var item = menuItems[i]; | 
|  | if (this.currentCast_ === null) { | 
|  | // Playing on this computer. | 
|  | if (item.castLabel === '') | 
|  | item.checked = true; | 
|  | else | 
|  | item.checked = false; | 
|  | } else { | 
|  | // Playing on cast device. | 
|  | if (item.castLabel === this.currentCast_.label) | 
|  | item.checked = true; | 
|  | else | 
|  | item.checked = false; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Called when the current cast is disappear from the cast list. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.onCurrentCastDisappear_ = function() { | 
|  | this.currentCast_ = null; | 
|  | if (this.currentSession_) { | 
|  | this.currentSession_.removeUpdateListener(this.onCastSessionUpdateBound_); | 
|  | this.currentSession_ = null; | 
|  | } | 
|  | this.controls.showErrorMessage('VIDEO_PLAYER_PLAYBACK_ERROR'); | 
|  | this.unloadVideo(); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * This method should be called when the session is updated. | 
|  | * @param {boolean} alive Whether the session is alive or not. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.onCastSessionUpdate_ = function(alive) { | 
|  | if (!alive) { | 
|  | var videoPlayerElement = getRequiredElement('video-player'); | 
|  | videoPlayerElement.removeAttribute('casting'); | 
|  |  | 
|  | // Loads the current video in local player. | 
|  | this.unloadVideo(); | 
|  | this.loadQueue_.run(function(callback) { | 
|  | this.currentCast_ = null; | 
|  | if (!chrome.cast.usingPresentationApi) | 
|  | this.updateCheckOnCastMenu_(); | 
|  | this.reloadCurrentVideo(); | 
|  | callback(); | 
|  | }.wrap(this)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Updates the MouseInactivityWatcher's disable property to prevent control | 
|  | * panel from being hidden in some situations. | 
|  | * @private | 
|  | */ | 
|  | VideoPlayer.prototype.updateInactivityWatcherState_ = function() { | 
|  | var videoPlayerElement = getRequiredElement('video-player'); | 
|  | // If any of following condition is met, we don't hide the tool bar. | 
|  | // - Loaded video is paused. | 
|  | // - Loading a video is in progress. | 
|  | // - Opening video has an error. | 
|  | this.controls.getInactivityWatcher().disabled = | 
|  | (this.videoElement_ && this.videoElement_.paused) || | 
|  | videoPlayerElement.hasAttribute('loading') || | 
|  | videoPlayerElement.hasAttribute('disabled'); | 
|  | }; | 
|  |  | 
|  | var player = new VideoPlayer(); | 
|  |  | 
|  | /** | 
|  | * Initializes the load time data. | 
|  | * @param {function()} callback Called when the load time data is ready. | 
|  | */ | 
|  | function initStrings(callback) { | 
|  | chrome.fileManagerPrivate.getStrings(function(strings) { | 
|  | loadTimeData.data = strings; | 
|  | callback(); | 
|  | }.wrap(null)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initializes the volume manager. | 
|  | * @param {function()} callback Called when the volume manager is ready. | 
|  | */ | 
|  | function initVolumeManager(callback) { | 
|  | var volumeManager = new FilteredVolumeManager(AllowedPaths.ANY_PATH, false); | 
|  | volumeManager.ensureInitialized(callback); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Promise to initialize both the volume manager and the load time data. | 
|  | * @type {!Promise} | 
|  | */ | 
|  | const initPromise = Promise.all([ | 
|  | new Promise(initStrings.wrap(null)), | 
|  | new Promise(initVolumeManager.wrap(null)), | 
|  | ]); | 
|  |  | 
|  | /** | 
|  | * Initialize the video player. | 
|  | */ | 
|  | initPromise.then(function() { | 
|  | if (document.readyState !== 'loading') | 
|  | return; | 
|  | return new Promise(function(fulfill, reject) { | 
|  | document.addEventListener('DOMContentLoaded', fulfill); | 
|  | }.wrap()); | 
|  | }.wrap()).then(function() { | 
|  | const isReady = document.readyState !== 'loading'; | 
|  | assert(isReady, 'VideoPlayer DOM document is still loading'); | 
|  | i18nTemplate.process(document, loadTimeData); | 
|  | return new Promise(function(fulfill, reject) { | 
|  | util.URLsToEntries(window.appState.items, function(entries) { | 
|  | metrics.recordOpenVideoPlayerAction(); | 
|  | metrics.recordNumberOfOpenedFiles(entries.length); | 
|  | player.prepare(entries); | 
|  | player.playFirstVideo(player, fulfill); | 
|  | }.wrap()); | 
|  | }.wrap()); | 
|  | }.wrap()); |