| Index: chrome/browser/resources/file_manager/js/media/media_controls.js
|
| diff --git a/chrome/browser/resources/file_manager/js/media/media_controls.js b/chrome/browser/resources/file_manager/js/media/media_controls.js
|
| deleted file mode 100644
|
| index a335bdc4c525294338951c4eee057c22e2073033..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/resources/file_manager/js/media/media_controls.js
|
| +++ /dev/null
|
| @@ -1,1245 +0,0 @@
|
| -// Copyright (c) 2012 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.
|
| -
|
| -'use strict';
|
| -
|
| -/**
|
| - * @fileoverview MediaControls class implements media playback controls
|
| - * that exist outside of the audio/video HTML element.
|
| - */
|
| -
|
| -/**
|
| - * @param {HTMLElement} containerElement The container for the controls.
|
| - * @param {function} onMediaError Function to display an error message.
|
| - * @constructor
|
| - */
|
| -function MediaControls(containerElement, onMediaError) {
|
| - this.container_ = containerElement;
|
| - this.document_ = this.container_.ownerDocument;
|
| - this.media_ = null;
|
| -
|
| - this.onMediaPlayBound_ = this.onMediaPlay_.bind(this, true);
|
| - this.onMediaPauseBound_ = this.onMediaPlay_.bind(this, false);
|
| - this.onMediaDurationBound_ = this.onMediaDuration_.bind(this);
|
| - this.onMediaProgressBound_ = this.onMediaProgress_.bind(this);
|
| - this.onMediaError_ = onMediaError || function() {};
|
| -}
|
| -
|
| -/**
|
| - * Button's state types. Values are used as CSS class names.
|
| - * @enum {string}
|
| - */
|
| -MediaControls.ButtonStateType = {
|
| - DEFAULT: 'default',
|
| - PLAYING: 'playing',
|
| - ENDED: 'ended'
|
| -};
|
| -
|
| -/**
|
| - * @return {HTMLAudioElement|HTMLVideoElement} The media element.
|
| - */
|
| -MediaControls.prototype.getMedia = function() { return this.media_ };
|
| -
|
| -/**
|
| - * Format the time in hh:mm:ss format (omitting redundant leading zeros).
|
| - *
|
| - * @param {number} timeInSec Time in seconds.
|
| - * @return {string} Formatted time string.
|
| - * @private
|
| - */
|
| -MediaControls.formatTime_ = function(timeInSec) {
|
| - var seconds = Math.floor(timeInSec % 60);
|
| - var minutes = Math.floor((timeInSec / 60) % 60);
|
| - var hours = Math.floor(timeInSec / 60 / 60);
|
| - var result = '';
|
| - if (hours) result += hours + ':';
|
| - if (hours && (minutes < 10)) result += '0';
|
| - result += minutes + ':';
|
| - if (seconds < 10) result += '0';
|
| - result += seconds;
|
| - return result;
|
| -};
|
| -
|
| -/**
|
| - * Create a custom control.
|
| - *
|
| - * @param {string} className Class name.
|
| - * @param {HTMLElement=} opt_parent Parent element or container if undefined.
|
| - * @return {HTMLElement} The new control element.
|
| - */
|
| -MediaControls.prototype.createControl = function(className, opt_parent) {
|
| - var parent = opt_parent || this.container_;
|
| - var control = this.document_.createElement('div');
|
| - control.className = className;
|
| - parent.appendChild(control);
|
| - return control;
|
| -};
|
| -
|
| -/**
|
| - * Create a custom button.
|
| - *
|
| - * @param {string} className Class name.
|
| - * @param {function(Event)} handler Click handler.
|
| - * @param {HTMLElement=} opt_parent Parent element or container if undefined.
|
| - * @param {number=} opt_numStates Number of states, default: 1.
|
| - * @return {HTMLElement} The new button element.
|
| - */
|
| -MediaControls.prototype.createButton = function(
|
| - className, handler, opt_parent, opt_numStates) {
|
| - opt_numStates = opt_numStates || 1;
|
| -
|
| - var button = this.createControl(className, opt_parent);
|
| - button.classList.add('media-button');
|
| - button.addEventListener('click', handler);
|
| -
|
| - var stateTypes = Object.keys(MediaControls.ButtonStateType);
|
| - for (var state = 0; state != opt_numStates; state++) {
|
| - var stateClass = MediaControls.ButtonStateType[stateTypes[state]];
|
| - this.createControl('normal ' + stateClass, button);
|
| - this.createControl('hover ' + stateClass, button);
|
| - this.createControl('active ' + stateClass, button);
|
| - }
|
| - this.createControl('disabled', button);
|
| -
|
| - button.setAttribute('state', MediaControls.ButtonStateType.DEFAULT);
|
| - button.addEventListener('click', handler);
|
| - return button;
|
| -};
|
| -
|
| -/**
|
| - * Enable/disable controls matching a given selector.
|
| - *
|
| - * @param {string} selector CSS selector.
|
| - * @param {boolean} on True if enable, false if disable.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.enableControls_ = function(selector, on) {
|
| - var controls = this.container_.querySelectorAll(selector);
|
| - for (var i = 0; i != controls.length; i++) {
|
| - var classList = controls[i].classList;
|
| - if (on)
|
| - classList.remove('disabled');
|
| - else
|
| - classList.add('disabled');
|
| - }
|
| -};
|
| -
|
| -/*
|
| - * Playback control.
|
| - */
|
| -
|
| -/**
|
| - * Play the media.
|
| - */
|
| -MediaControls.prototype.play = function() {
|
| - this.media_.play();
|
| -};
|
| -
|
| -/**
|
| - * Pause the media.
|
| - */
|
| -MediaControls.prototype.pause = function() {
|
| - this.media_.pause();
|
| -};
|
| -
|
| -/**
|
| - * @return {boolean} True if the media is currently playing.
|
| - */
|
| -MediaControls.prototype.isPlaying = function() {
|
| - return !this.media_.paused && !this.media_.ended;
|
| -};
|
| -
|
| -/**
|
| - * Toggle play/pause.
|
| - */
|
| -MediaControls.prototype.togglePlayState = function() {
|
| - if (this.isPlaying())
|
| - this.pause();
|
| - else
|
| - this.play();
|
| -};
|
| -
|
| -/**
|
| - * Toggle play/pause state on a mouse click on the play/pause button. Can be
|
| - * called externally. TODO(mtomasz): Remove it. http://www.crbug.com/254318.
|
| - *
|
| - * @param {Event=} opt_event Mouse click event.
|
| - */
|
| -MediaControls.prototype.onPlayButtonClicked = function(opt_event) {
|
| - this.togglePlayState();
|
| -};
|
| -
|
| -/**
|
| - * @param {HTMLElement=} opt_parent Parent container.
|
| - */
|
| -MediaControls.prototype.initPlayButton = function(opt_parent) {
|
| - this.playButton_ = this.createButton('play media-control',
|
| - this.onPlayButtonClicked.bind(this), opt_parent, 3 /* States. */);
|
| -};
|
| -
|
| -/*
|
| - * Time controls
|
| - */
|
| -
|
| -/**
|
| - * The default range of 100 is too coarse for the media progress slider.
|
| - */
|
| -MediaControls.PROGRESS_RANGE = 5000;
|
| -
|
| -/**
|
| - * @param {boolean=} opt_seekMark True if the progress slider should have
|
| - * a seek mark.
|
| - * @param {HTMLElement=} opt_parent Parent container.
|
| - */
|
| -MediaControls.prototype.initTimeControls = function(opt_seekMark, opt_parent) {
|
| - var timeControls = this.createControl('time-controls', opt_parent);
|
| -
|
| - var sliderConstructor =
|
| - opt_seekMark ? MediaControls.PreciseSlider : MediaControls.Slider;
|
| -
|
| - this.progressSlider_ = new sliderConstructor(
|
| - this.createControl('progress media-control', timeControls),
|
| - 0, /* value */
|
| - MediaControls.PROGRESS_RANGE,
|
| - this.onProgressChange_.bind(this),
|
| - this.onProgressDrag_.bind(this));
|
| -
|
| - var timeBox = this.createControl('time media-control', timeControls);
|
| -
|
| - this.duration_ = this.createControl('duration', timeBox);
|
| - // Set the initial width to the minimum to reduce the flicker.
|
| - this.duration_.textContent = MediaControls.formatTime_(0);
|
| -
|
| - this.currentTime_ = this.createControl('current', timeBox);
|
| -};
|
| -
|
| -/**
|
| - * @param {number} current Current time is seconds.
|
| - * @param {number} duration Duration in seconds.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.displayProgress_ = function(current, duration) {
|
| - var ratio = current / duration;
|
| - this.progressSlider_.setValue(ratio);
|
| - this.currentTime_.textContent = MediaControls.formatTime_(current);
|
| -};
|
| -
|
| -/**
|
| - * @param {number} value Progress [0..1].
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onProgressChange_ = function(value) {
|
| - if (!this.media_.seekable || !this.media_.duration) {
|
| - console.error('Inconsistent media state');
|
| - return;
|
| - }
|
| -
|
| - var current = this.media_.duration * value;
|
| - this.media_.currentTime = current;
|
| - this.currentTime_.textContent = MediaControls.formatTime_(current);
|
| -};
|
| -
|
| -/**
|
| - * @param {boolean} on True if dragging.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onProgressDrag_ = function(on) {
|
| - if (on) {
|
| - this.resumeAfterDrag_ = this.isPlaying();
|
| - this.media_.pause();
|
| - } else {
|
| - if (this.resumeAfterDrag_) {
|
| - if (this.media_.ended)
|
| - this.onMediaPlay_(false);
|
| - else
|
| - this.media_.play();
|
| - }
|
| - this.updatePlayButtonState_(this.isPlaying());
|
| - }
|
| -};
|
| -
|
| -/*
|
| - * Volume controls
|
| - */
|
| -
|
| -/**
|
| - * @param {HTMLElement=} opt_parent Parent element for the controls.
|
| - */
|
| -MediaControls.prototype.initVolumeControls = function(opt_parent) {
|
| - var volumeControls = this.createControl('volume-controls', opt_parent);
|
| -
|
| - this.soundButton_ = this.createButton('sound media-control',
|
| - this.onSoundButtonClick_.bind(this), volumeControls);
|
| - this.soundButton_.setAttribute('level', 3); // max level.
|
| -
|
| - this.volume_ = new MediaControls.AnimatedSlider(
|
| - this.createControl('volume media-control', volumeControls),
|
| - 1, /* value */
|
| - 100 /* range */,
|
| - this.onVolumeChange_.bind(this),
|
| - this.onVolumeDrag_.bind(this));
|
| -};
|
| -
|
| -/**
|
| - * Click handler for the sound level button.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onSoundButtonClick_ = function() {
|
| - if (this.media_.volume == 0) {
|
| - this.volume_.setValue(this.savedVolume_ || 1);
|
| - } else {
|
| - this.savedVolume_ = this.media_.volume;
|
| - this.volume_.setValue(0);
|
| - }
|
| - this.onVolumeChange_(this.volume_.getValue());
|
| -};
|
| -
|
| -/**
|
| - * @param {number} value Volume [0..1].
|
| - * @return {number} The rough level [0..3] used to pick an icon.
|
| - * @private
|
| - */
|
| -MediaControls.getVolumeLevel_ = function(value) {
|
| - if (value == 0) return 0;
|
| - if (value <= 1 / 3) return 1;
|
| - if (value <= 2 / 3) return 2;
|
| - return 3;
|
| -};
|
| -
|
| -/**
|
| - * @param {number} value Volume [0..1].
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onVolumeChange_ = function(value) {
|
| - this.media_.volume = value;
|
| - this.soundButton_.setAttribute('level', MediaControls.getVolumeLevel_(value));
|
| -};
|
| -
|
| -/**
|
| - * @param {boolean} on True if dragging is in progress.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onVolumeDrag_ = function(on) {
|
| - if (on && (this.media_.volume != 0)) {
|
| - this.savedVolume_ = this.media_.volume;
|
| - }
|
| -};
|
| -
|
| -/*
|
| - * Media event handlers.
|
| - */
|
| -
|
| -/**
|
| - * Attach a media element.
|
| - *
|
| - * @param {HTMLMediaElement} mediaElement The media element to control.
|
| - */
|
| -MediaControls.prototype.attachMedia = function(mediaElement) {
|
| - this.media_ = mediaElement;
|
| -
|
| - this.media_.addEventListener('play', this.onMediaPlayBound_);
|
| - this.media_.addEventListener('pause', this.onMediaPauseBound_);
|
| - this.media_.addEventListener('durationchange', this.onMediaDurationBound_);
|
| - this.media_.addEventListener('timeupdate', this.onMediaProgressBound_);
|
| - this.media_.addEventListener('error', this.onMediaError_);
|
| -
|
| - // Reflect the media state in the UI.
|
| - this.onMediaDuration_();
|
| - this.onMediaPlay_(this.isPlaying());
|
| - this.onMediaProgress_();
|
| - if (this.volume_) {
|
| - /* Copy the user selected volume to the new media element. */
|
| - this.media_.volume = this.volume_.getValue();
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Detach media event handlers.
|
| - */
|
| -MediaControls.prototype.detachMedia = function() {
|
| - if (!this.media_)
|
| - return;
|
| -
|
| - this.media_.removeEventListener('play', this.onMediaPlayBound_);
|
| - this.media_.removeEventListener('pause', this.onMediaPauseBound_);
|
| - this.media_.removeEventListener('durationchange', this.onMediaDurationBound_);
|
| - this.media_.removeEventListener('timeupdate', this.onMediaProgressBound_);
|
| - this.media_.removeEventListener('error', this.onMediaError_);
|
| -
|
| - this.media_ = null;
|
| -};
|
| -
|
| -/**
|
| - * Force-empty the media pipeline. This is a workaround for crbug.com/149957.
|
| - * The document is not going to be GC-ed until the last Files app window closes,
|
| - * but we want the media pipeline to deinitialize ASAP to minimize leakage.
|
| - */
|
| -MediaControls.prototype.cleanup = function() {
|
| - this.media_.src = '';
|
| - this.media_.load();
|
| - this.detachMedia();
|
| -};
|
| -
|
| -/**
|
| - * 'play' and 'pause' event handler.
|
| - * @param {boolean} playing True if playing.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onMediaPlay_ = function(playing) {
|
| - if (this.progressSlider_.isDragging())
|
| - return;
|
| -
|
| - this.updatePlayButtonState_(playing);
|
| - this.onPlayStateChanged();
|
| -};
|
| -
|
| -/**
|
| - * 'durationchange' event handler.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onMediaDuration_ = function() {
|
| - if (!this.media_.duration) {
|
| - this.enableControls_('.media-control', false);
|
| - return;
|
| - }
|
| -
|
| - this.enableControls_('.media-control', true);
|
| -
|
| - var sliderContainer = this.progressSlider_.getContainer();
|
| - if (this.media_.seekable)
|
| - sliderContainer.classList.remove('readonly');
|
| - else
|
| - sliderContainer.classList.add('readonly');
|
| -
|
| - var valueToString = function(value) {
|
| - return MediaControls.formatTime_(this.media_.duration * value);
|
| - }.bind(this);
|
| -
|
| - this.duration_.textContent = valueToString(1);
|
| -
|
| - if (this.progressSlider_.setValueToStringFunction)
|
| - this.progressSlider_.setValueToStringFunction(valueToString);
|
| -
|
| - if (this.media_.seekable)
|
| - this.restorePlayState();
|
| -};
|
| -
|
| -/**
|
| - * 'timeupdate' event handler.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.onMediaProgress_ = function() {
|
| - if (!this.media_.duration) {
|
| - this.displayProgress_(0, 1);
|
| - return;
|
| - }
|
| -
|
| - var current = this.media_.currentTime;
|
| - var duration = this.media_.duration;
|
| -
|
| - if (this.progressSlider_.isDragging())
|
| - return;
|
| -
|
| - this.displayProgress_(current, duration);
|
| -
|
| - if (current == duration) {
|
| - this.onMediaComplete();
|
| - }
|
| - this.onPlayStateChanged();
|
| -};
|
| -
|
| -/**
|
| - * Called when the media playback is complete.
|
| - */
|
| -MediaControls.prototype.onMediaComplete = function() {};
|
| -
|
| -/**
|
| - * Called when play/pause state is changed or on playback progress.
|
| - * This is the right moment to save the play state.
|
| - */
|
| -MediaControls.prototype.onPlayStateChanged = function() {};
|
| -
|
| -/**
|
| - * Updates the play button state.
|
| - * @param {boolean} playing If the video is playing.
|
| - * @private
|
| - */
|
| -MediaControls.prototype.updatePlayButtonState_ = function(playing) {
|
| - if (playing) {
|
| - this.playButton_.setAttribute('state',
|
| - MediaControls.ButtonStateType.PLAYING);
|
| - } else if (!this.media_.ended) {
|
| - this.playButton_.setAttribute('state',
|
| - MediaControls.ButtonStateType.DEFAULT);
|
| - } else {
|
| - this.playButton_.setAttribute('state',
|
| - MediaControls.ButtonStateType.ENDED);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Restore play state. Base implementation is empty.
|
| - */
|
| -MediaControls.prototype.restorePlayState = function() {};
|
| -
|
| -/**
|
| - * Encode current state into the page URL or the app state.
|
| - */
|
| -MediaControls.prototype.encodeState = function() {
|
| - if (!this.media_.duration)
|
| - return;
|
| -
|
| - if (window.appState) {
|
| - window.appState.time = this.media_.currentTime;
|
| - util.saveAppState();
|
| - return;
|
| - }
|
| -
|
| - var playState = JSON.stringify({
|
| - play: this.isPlaying(),
|
| - time: this.media_.currentTime
|
| - });
|
| -
|
| - var newLocation = document.location.origin + document.location.pathname +
|
| - document.location.search + '#' + playState;
|
| -
|
| - document.location.href = newLocation;
|
| -};
|
| -
|
| -/**
|
| - * Decode current state from the page URL or the app state.
|
| - * @return {boolean} True if decode succeeded.
|
| - */
|
| -MediaControls.prototype.decodeState = function() {
|
| - if (window.appState) {
|
| - if (!('time' in window.appState))
|
| - return false;
|
| - // There is no page reload for apps v2, only app restart.
|
| - // Always restart in paused state.
|
| - this.media_.currentTime = appState.time;
|
| - this.pause();
|
| - return true;
|
| - }
|
| -
|
| - var hash = document.location.hash.substring(1);
|
| - if (hash) {
|
| - try {
|
| - var playState = JSON.parse(hash);
|
| - if (!('time' in playState))
|
| - return false;
|
| -
|
| - this.media_.currentTime = playState.time;
|
| -
|
| - if (playState.play)
|
| - this.play();
|
| - else
|
| - this.pause();
|
| -
|
| - return true;
|
| - } catch (e) {
|
| - console.warn('Cannot decode play state');
|
| - }
|
| - }
|
| - return false;
|
| -};
|
| -
|
| -/**
|
| - * Remove current state from the page URL or the app state.
|
| - */
|
| -MediaControls.prototype.clearState = function() {
|
| - if (window.appState) {
|
| - if ('time' in window.appState)
|
| - delete window.appState.time;
|
| - util.saveAppState();
|
| - return;
|
| - }
|
| -
|
| - var newLocation = document.location.origin + document.location.pathname +
|
| - document.location.search + '#';
|
| -
|
| - document.location.href = newLocation;
|
| -};
|
| -
|
| -/**
|
| - * Create a customized slider control.
|
| - *
|
| - * @param {HTMLElement} container The containing div element.
|
| - * @param {number} value Initial value [0..1].
|
| - * @param {number} range Number of distinct slider positions to be supported.
|
| - * @param {function(number)} onChange Value change handler.
|
| - * @param {function(boolean)} onDrag Drag begin/end handler.
|
| - * @constructor
|
| - */
|
| -
|
| -MediaControls.Slider = function(container, value, range, onChange, onDrag) {
|
| - this.container_ = container;
|
| - this.onChange_ = onChange;
|
| - this.onDrag_ = onDrag;
|
| -
|
| - var document = this.container_.ownerDocument;
|
| -
|
| - this.container_.classList.add('custom-slider');
|
| -
|
| - this.input_ = document.createElement('input');
|
| - this.input_.type = 'range';
|
| - this.input_.min = 0;
|
| - this.input_.max = range;
|
| - this.input_.value = value * range;
|
| - this.container_.appendChild(this.input_);
|
| -
|
| - this.input_.addEventListener(
|
| - 'change', this.onInputChange_.bind(this));
|
| - this.input_.addEventListener(
|
| - 'mousedown', this.onInputDrag_.bind(this, true));
|
| - this.input_.addEventListener(
|
| - 'mouseup', this.onInputDrag_.bind(this, false));
|
| -
|
| - this.bar_ = document.createElement('div');
|
| - this.bar_.className = 'bar';
|
| - this.container_.appendChild(this.bar_);
|
| -
|
| - this.filled_ = document.createElement('div');
|
| - this.filled_.className = 'filled';
|
| - this.bar_.appendChild(this.filled_);
|
| -
|
| - var leftCap = document.createElement('div');
|
| - leftCap.className = 'cap left';
|
| - this.bar_.appendChild(leftCap);
|
| -
|
| - var rightCap = document.createElement('div');
|
| - rightCap.className = 'cap right';
|
| - this.bar_.appendChild(rightCap);
|
| -
|
| - this.value_ = value;
|
| - this.setFilled_(value);
|
| -};
|
| -
|
| -/**
|
| - * @return {HTMLElement} The container element.
|
| - */
|
| -MediaControls.Slider.prototype.getContainer = function() {
|
| - return this.container_;
|
| -};
|
| -
|
| -/**
|
| - * @return {HTMLElement} The standard input element.
|
| - * @private
|
| - */
|
| -MediaControls.Slider.prototype.getInput_ = function() {
|
| - return this.input_;
|
| -};
|
| -
|
| -/**
|
| - * @return {HTMLElement} The slider bar element.
|
| - */
|
| -MediaControls.Slider.prototype.getBar = function() {
|
| - return this.bar_;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} [0..1] The current value.
|
| - */
|
| -MediaControls.Slider.prototype.getValue = function() {
|
| - return this.value_;
|
| -};
|
| -
|
| -/**
|
| - * @param {number} value [0..1].
|
| - */
|
| -MediaControls.Slider.prototype.setValue = function(value) {
|
| - this.value_ = value;
|
| - this.setValueToUI_(value);
|
| -};
|
| -
|
| -/**
|
| - * Fill the given proportion the slider bar (from the left).
|
| - *
|
| - * @param {number} proportion [0..1].
|
| - * @private
|
| - */
|
| -MediaControls.Slider.prototype.setFilled_ = function(proportion) {
|
| - this.filled_.style.width = proportion * 100 + '%';
|
| -};
|
| -
|
| -/**
|
| - * Get the value from the input element.
|
| - *
|
| - * @return {number} Value [0..1].
|
| - * @private
|
| - */
|
| -MediaControls.Slider.prototype.getValueFromUI_ = function() {
|
| - return this.input_.value / this.input_.max;
|
| -};
|
| -
|
| -/**
|
| - * Update the UI with the current value.
|
| - *
|
| - * @param {number} value [0..1].
|
| - * @private
|
| - */
|
| -MediaControls.Slider.prototype.setValueToUI_ = function(value) {
|
| - this.input_.value = value * this.input_.max;
|
| - this.setFilled_(value);
|
| -};
|
| -
|
| -/**
|
| - * Compute the proportion in which the given position divides the slider bar.
|
| - *
|
| - * @param {number} position in pixels.
|
| - * @return {number} [0..1] proportion.
|
| - */
|
| -MediaControls.Slider.prototype.getProportion = function(position) {
|
| - var rect = this.bar_.getBoundingClientRect();
|
| - return Math.max(0, Math.min(1, (position - rect.left) / rect.width));
|
| -};
|
| -
|
| -/**
|
| - * 'change' event handler.
|
| - * @private
|
| - */
|
| -MediaControls.Slider.prototype.onInputChange_ = function() {
|
| - this.value_ = this.getValueFromUI_();
|
| - this.setFilled_(this.value_);
|
| - this.onChange_(this.value_);
|
| -};
|
| -
|
| -/**
|
| - * @return {boolean} True if dragging is in progress.
|
| - */
|
| -MediaControls.Slider.prototype.isDragging = function() {
|
| - return this.isDragging_;
|
| -};
|
| -
|
| -/**
|
| - * Mousedown/mouseup handler.
|
| - * @param {boolean} on True if the mouse is down.
|
| - * @private
|
| - */
|
| -MediaControls.Slider.prototype.onInputDrag_ = function(on) {
|
| - this.isDragging_ = on;
|
| - this.onDrag_(on);
|
| -};
|
| -
|
| -/**
|
| - * Create a customized slider with animated thumb movement.
|
| - *
|
| - * @param {HTMLElement} container The containing div element.
|
| - * @param {number} value Initial value [0..1].
|
| - * @param {number} range Number of distinct slider positions to be supported.
|
| - * @param {function(number)} onChange Value change handler.
|
| - * @param {function(boolean)} onDrag Drag begin/end handler.
|
| - * @param {function(number):string} formatFunction Value formatting function.
|
| - * @constructor
|
| - */
|
| -MediaControls.AnimatedSlider = function(
|
| - container, value, range, onChange, onDrag, formatFunction) {
|
| - MediaControls.Slider.apply(this, arguments);
|
| -};
|
| -
|
| -MediaControls.AnimatedSlider.prototype = {
|
| - __proto__: MediaControls.Slider.prototype
|
| -};
|
| -
|
| -/**
|
| - * Number of animation steps.
|
| - */
|
| -MediaControls.AnimatedSlider.STEPS = 10;
|
| -
|
| -/**
|
| - * Animation duration.
|
| - */
|
| -MediaControls.AnimatedSlider.DURATION = 100;
|
| -
|
| -/**
|
| - * @param {number} value [0..1].
|
| - * @private
|
| - */
|
| -MediaControls.AnimatedSlider.prototype.setValueToUI_ = function(value) {
|
| - if (this.animationInterval_) {
|
| - clearInterval(this.animationInterval_);
|
| - }
|
| - var oldValue = this.getValueFromUI_();
|
| - var step = 0;
|
| - this.animationInterval_ = setInterval(function() {
|
| - step++;
|
| - var currentValue = oldValue +
|
| - (value - oldValue) * (step / MediaControls.AnimatedSlider.STEPS);
|
| - MediaControls.Slider.prototype.setValueToUI_.call(this, currentValue);
|
| - if (step == MediaControls.AnimatedSlider.STEPS) {
|
| - clearInterval(this.animationInterval_);
|
| - }
|
| - }.bind(this),
|
| - MediaControls.AnimatedSlider.DURATION / MediaControls.AnimatedSlider.STEPS);
|
| -};
|
| -
|
| -/**
|
| - * Create a customized slider with a precise time feedback.
|
| - *
|
| - * The time value is shown above the slider bar at the mouse position.
|
| - *
|
| - * @param {HTMLElement} container The containing div element.
|
| - * @param {number} value Initial value [0..1].
|
| - * @param {number} range Number of distinct slider positions to be supported.
|
| - * @param {function(number)} onChange Value change handler.
|
| - * @param {function(boolean)} onDrag Drag begin/end handler.
|
| - * @param {function(number):string} formatFunction Value formatting function.
|
| - * @constructor
|
| - */
|
| -MediaControls.PreciseSlider = function(
|
| - container, value, range, onChange, onDrag, formatFunction) {
|
| - MediaControls.Slider.apply(this, arguments);
|
| -
|
| - var doc = this.container_.ownerDocument;
|
| -
|
| - /**
|
| - * @type {function(number):string}
|
| - * @private
|
| - */
|
| - this.valueToString_ = null;
|
| -
|
| - this.seekMark_ = doc.createElement('div');
|
| - this.seekMark_.className = 'seek-mark';
|
| - this.getBar().appendChild(this.seekMark_);
|
| -
|
| - this.seekLabel_ = doc.createElement('div');
|
| - this.seekLabel_.className = 'seek-label';
|
| - this.seekMark_.appendChild(this.seekLabel_);
|
| -
|
| - this.getContainer().addEventListener(
|
| - 'mousemove', this.onMouseMove_.bind(this));
|
| - this.getContainer().addEventListener(
|
| - 'mouseout', this.onMouseOut_.bind(this));
|
| -};
|
| -
|
| -MediaControls.PreciseSlider.prototype = {
|
| - __proto__: MediaControls.Slider.prototype
|
| -};
|
| -
|
| -/**
|
| - * Show the seek mark after a delay.
|
| - */
|
| -MediaControls.PreciseSlider.SHOW_DELAY = 200;
|
| -
|
| -/**
|
| - * Hide the seek mark for this long after changing the position with a click.
|
| - */
|
| -MediaControls.PreciseSlider.HIDE_AFTER_MOVE_DELAY = 2500;
|
| -
|
| -/**
|
| - * Hide the seek mark for this long after changing the position with a drag.
|
| - */
|
| -MediaControls.PreciseSlider.HIDE_AFTER_DRAG_DELAY = 750;
|
| -
|
| -/**
|
| - * Default hide timeout (no hiding).
|
| - */
|
| -MediaControls.PreciseSlider.NO_AUTO_HIDE = 0;
|
| -
|
| -/**
|
| - * @param {function(number):string} func Value formatting function.
|
| - */
|
| -MediaControls.PreciseSlider.prototype.setValueToStringFunction =
|
| - function(func) {
|
| - this.valueToString_ = func;
|
| -
|
| - /* It is not completely accurate to assume that the max value corresponds
|
| - to the longest string, but generous CSS padding will compensate for that. */
|
| - var labelWidth = this.valueToString_(1).length / 2 + 1;
|
| - this.seekLabel_.style.width = labelWidth + 'em';
|
| - this.seekLabel_.style.marginLeft = -labelWidth / 2 + 'em';
|
| -};
|
| -
|
| -/**
|
| - * Show the time above the slider.
|
| - *
|
| - * @param {number} ratio [0..1] The proportion of the duration.
|
| - * @param {number} timeout Timeout in ms after which the label should be hidden.
|
| - * MediaControls.PreciseSlider.NO_AUTO_HIDE means show until the next call.
|
| - * @private
|
| - */
|
| -MediaControls.PreciseSlider.prototype.showSeekMark_ =
|
| - function(ratio, timeout) {
|
| - // Do not update the seek mark for the first 500ms after the drag is finished.
|
| - if (this.latestMouseUpTime_ && (this.latestMouseUpTime_ + 500 > Date.now()))
|
| - return;
|
| -
|
| - this.seekMark_.style.left = ratio * 100 + '%';
|
| -
|
| - if (ratio < this.getValue()) {
|
| - this.seekMark_.classList.remove('inverted');
|
| - } else {
|
| - this.seekMark_.classList.add('inverted');
|
| - }
|
| - this.seekLabel_.textContent = this.valueToString_(ratio);
|
| -
|
| - this.seekMark_.classList.add('visible');
|
| -
|
| - if (this.seekMarkTimer_) {
|
| - clearTimeout(this.seekMarkTimer_);
|
| - this.seekMarkTimer_ = null;
|
| - }
|
| - if (timeout != MediaControls.PreciseSlider.NO_AUTO_HIDE) {
|
| - this.seekMarkTimer_ = setTimeout(this.hideSeekMark_.bind(this), timeout);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * @private
|
| - */
|
| -MediaControls.PreciseSlider.prototype.hideSeekMark_ = function() {
|
| - this.seekMarkTimer_ = null;
|
| - this.seekMark_.classList.remove('visible');
|
| -};
|
| -
|
| -/**
|
| - * 'mouseout' event handler.
|
| - * @param {Event} e Event.
|
| - * @private
|
| - */
|
| -MediaControls.PreciseSlider.prototype.onMouseMove_ = function(e) {
|
| - this.latestSeekRatio_ = this.getProportion(e.clientX);
|
| -
|
| - var self = this;
|
| - function showMark() {
|
| - if (!self.isDragging()) {
|
| - self.showSeekMark_(self.latestSeekRatio_,
|
| - MediaControls.PreciseSlider.HIDE_AFTER_MOVE_DELAY);
|
| - }
|
| - }
|
| -
|
| - if (this.seekMark_.classList.contains('visible')) {
|
| - showMark();
|
| - } else if (!this.seekMarkTimer_) {
|
| - this.seekMarkTimer_ =
|
| - setTimeout(showMark, MediaControls.PreciseSlider.SHOW_DELAY);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * 'mouseout' event handler.
|
| - * @param {Event} e Event.
|
| - * @private
|
| - */
|
| -MediaControls.PreciseSlider.prototype.onMouseOut_ = function(e) {
|
| - for (var element = e.relatedTarget; element; element = element.parentNode) {
|
| - if (element == this.getContainer())
|
| - return;
|
| - }
|
| - if (this.seekMarkTimer_) {
|
| - clearTimeout(this.seekMarkTimer_);
|
| - this.seekMarkTimer_ = null;
|
| - }
|
| - this.hideSeekMark_();
|
| -};
|
| -
|
| -/**
|
| - * 'change' event handler.
|
| - * @private
|
| - */
|
| -MediaControls.PreciseSlider.prototype.onInputChange_ = function() {
|
| - MediaControls.Slider.prototype.onInputChange_.apply(this, arguments);
|
| - if (this.isDragging()) {
|
| - this.showSeekMark_(
|
| - this.getValue(), MediaControls.PreciseSlider.NO_AUTO_HIDE);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Mousedown/mouseup handler.
|
| - * @param {boolean} on True if the mouse is down.
|
| - * @private
|
| - */
|
| -MediaControls.PreciseSlider.prototype.onInputDrag_ = function(on) {
|
| - MediaControls.Slider.prototype.onInputDrag_.apply(this, arguments);
|
| -
|
| - if (on) {
|
| - // Dragging started, align the seek mark with the thumb position.
|
| - this.showSeekMark_(
|
| - this.getValue(), MediaControls.PreciseSlider.NO_AUTO_HIDE);
|
| - } else {
|
| - // Just finished dragging.
|
| - // Show the label for the last time with a shorter timeout.
|
| - this.showSeekMark_(
|
| - this.getValue(), MediaControls.PreciseSlider.HIDE_AFTER_DRAG_DELAY);
|
| - this.latestMouseUpTime_ = Date.now();
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Create video controls.
|
| - *
|
| - * @param {HTMLElement} containerElement The container for the controls.
|
| - * @param {function} onMediaError Function to display an error message.
|
| - * @param {function(string):string} stringFunction Function providing localized
|
| - * strings.
|
| - * @param {function=} opt_fullScreenToggle Function to toggle fullscreen mode.
|
| - * @param {HTMLElement=} opt_stateIconParent The parent for the icon that
|
| - * gives visual feedback when the playback state changes.
|
| - * @constructor
|
| - */
|
| -function VideoControls(containerElement, onMediaError, stringFunction,
|
| - opt_fullScreenToggle, opt_stateIconParent) {
|
| - MediaControls.call(this, containerElement, onMediaError);
|
| - this.stringFunction_ = stringFunction;
|
| -
|
| - this.container_.classList.add('video-controls');
|
| - this.initPlayButton();
|
| - this.initTimeControls(true /* show seek mark */);
|
| - this.initVolumeControls();
|
| -
|
| - if (opt_fullScreenToggle) {
|
| - this.fullscreenButton_ =
|
| - this.createButton('fullscreen', opt_fullScreenToggle);
|
| - }
|
| -
|
| - if (opt_stateIconParent) {
|
| - this.stateIcon_ = this.createControl(
|
| - 'playback-state-icon', opt_stateIconParent);
|
| - this.textBanner_ = this.createControl('text-banner', opt_stateIconParent);
|
| - }
|
| -
|
| - var videoControls = this;
|
| - chrome.mediaPlayerPrivate.onTogglePlayState.addListener(
|
| - function() { videoControls.togglePlayStateWithFeedback(); });
|
| -}
|
| -
|
| -/**
|
| - * No resume if we are within this margin from the start or the end.
|
| - */
|
| -VideoControls.RESUME_MARGIN = 0.03;
|
| -
|
| -/**
|
| - * No resume for videos shorter than this.
|
| - */
|
| -VideoControls.RESUME_THRESHOLD = 5 * 60; // 5 min.
|
| -
|
| -/**
|
| - * When resuming rewind back this much.
|
| - */
|
| -VideoControls.RESUME_REWIND = 5; // seconds.
|
| -
|
| -VideoControls.prototype = { __proto__: MediaControls.prototype };
|
| -
|
| -/**
|
| - * Shows icon feedback for the current state of the video player.
|
| - * @private
|
| - */
|
| -VideoControls.prototype.showIconFeedback_ = function() {
|
| - this.stateIcon_.removeAttribute('state');
|
| - setTimeout(function() {
|
| - this.stateIcon_.setAttribute('state', this.isPlaying() ? 'play' : 'pause');
|
| - }.bind(this), 0);
|
| -};
|
| -
|
| -/**
|
| - * Shows a text banner.
|
| - *
|
| - * @param {string} identifier String identifier.
|
| - * @private
|
| - */
|
| -VideoControls.prototype.showTextBanner_ = function(identifier) {
|
| - this.textBanner_.removeAttribute('visible');
|
| - this.textBanner_.textContent = this.stringFunction_(identifier);
|
| - setTimeout(function() {
|
| - this.textBanner_.setAttribute('visible', 'true');
|
| - }.bind(this), 0);
|
| -};
|
| -
|
| -/**
|
| - * Toggle play/pause state on a mouse click on the play/pause button. Can be
|
| - * called externally.
|
| - *
|
| - * @param {Event} event Mouse click event.
|
| - */
|
| -VideoControls.prototype.onPlayButtonClicked = function(event) {
|
| - if (event.ctrlKey) {
|
| - this.toggleLoopedModeWithFeedback(true);
|
| - if (!this.isPlaying())
|
| - this.togglePlayState();
|
| - } else {
|
| - this.togglePlayState();
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Media completion handler.
|
| - */
|
| -VideoControls.prototype.onMediaComplete = function() {
|
| - this.onMediaPlay_(false); // Just update the UI.
|
| - this.savePosition(); // This will effectively forget the position.
|
| -};
|
| -
|
| -/**
|
| - * Toggles the looped mode with feedback.
|
| - * @param {boolean} on Whether enabled or not.
|
| - */
|
| -VideoControls.prototype.toggleLoopedModeWithFeedback = function(on) {
|
| - if (!this.getMedia().duration)
|
| - return;
|
| - this.toggleLoopedMode(on);
|
| - if (on) {
|
| - // TODO(mtomasz): Simplify, crbug.com/254318.
|
| - this.showTextBanner_('GALLERY_VIDEO_LOOPED_MODE');
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Toggles the looped mode.
|
| - * @param {boolean} on Whether enabled or not.
|
| - */
|
| -VideoControls.prototype.toggleLoopedMode = function(on) {
|
| - this.getMedia().loop = on;
|
| -};
|
| -
|
| -/**
|
| - * Toggles play/pause state and flash an icon over the video.
|
| - */
|
| -VideoControls.prototype.togglePlayStateWithFeedback = function() {
|
| - if (!this.getMedia().duration)
|
| - return;
|
| -
|
| - this.togglePlayState();
|
| - this.showIconFeedback_();
|
| -};
|
| -
|
| -/**
|
| - * Toggles play/pause state.
|
| - */
|
| -VideoControls.prototype.togglePlayState = function() {
|
| - if (this.isPlaying()) {
|
| - // User gave the Pause command. Save the state and reset the loop mode.
|
| - this.toggleLoopedMode(false);
|
| - this.savePosition();
|
| - }
|
| - MediaControls.prototype.togglePlayState.apply(this, arguments);
|
| -};
|
| -
|
| -/**
|
| - * Saves the playback position to the persistent storage.
|
| - * @param {boolean=} opt_sync True if the position must be saved synchronously
|
| - * (required when closing app windows).
|
| - */
|
| -VideoControls.prototype.savePosition = function(opt_sync) {
|
| - if (!this.media_.duration ||
|
| - this.media_.duration < VideoControls.RESUME_THRESHOLD) {
|
| - return;
|
| - }
|
| -
|
| - var ratio = this.media_.currentTime / this.media_.duration;
|
| - var position;
|
| - if (ratio < VideoControls.RESUME_MARGIN ||
|
| - ratio > (1 - VideoControls.RESUME_MARGIN)) {
|
| - // We are too close to the beginning or the end.
|
| - // Remove the resume position so that next time we start from the beginning.
|
| - position = null;
|
| - } else {
|
| - position = Math.floor(
|
| - Math.max(0, this.media_.currentTime - VideoControls.RESUME_REWIND));
|
| - }
|
| -
|
| - if (opt_sync) {
|
| - // Packaged apps cannot save synchronously.
|
| - // Pass the data to the background page.
|
| - if (!window.saveOnExit)
|
| - window.saveOnExit = [];
|
| - window.saveOnExit.push({ key: this.media_.src, value: position });
|
| - } else {
|
| - util.AppCache.update(this.media_.src, position);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Resumes the playback position saved in the persistent storage.
|
| - */
|
| -VideoControls.prototype.restorePlayState = function() {
|
| - if (this.media_.duration >= VideoControls.RESUME_THRESHOLD) {
|
| - util.AppCache.getValue(this.media_.src, function(position) {
|
| - if (position)
|
| - this.media_.currentTime = position;
|
| - }.bind(this));
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Updates style to best fit the size of the container.
|
| - */
|
| -VideoControls.prototype.updateStyle = function() {
|
| - // We assume that the video controls element fills the parent container.
|
| - // This is easier than adding margins to this.container_.clientWidth.
|
| - var width = this.container_.parentNode.clientWidth;
|
| -
|
| - // Set the margin to 5px for width >= 400, 0px for width < 160,
|
| - // interpolate linearly in between.
|
| - this.container_.style.margin =
|
| - Math.ceil((Math.max(160, Math.min(width, 400)) - 160) / 48) + 'px';
|
| -
|
| - var hideBelow = function(selector, limit) {
|
| - this.container_.querySelector(selector).style.display =
|
| - width < limit ? 'none' : '-webkit-box';
|
| - }.bind(this);
|
| -
|
| - hideBelow('.time', 350);
|
| - hideBelow('.volume', 275);
|
| - hideBelow('.volume-controls', 210);
|
| - hideBelow('.fullscreen', 150);
|
| -};
|
| -
|
| -/**
|
| - * Creates audio controls.
|
| - *
|
| - * @param {HTMLElement} container Parent container.
|
| - * @param {function(boolean)} advanceTrack Parameter: true=forward.
|
| - * @param {function} onError Error handler.
|
| - * @constructor
|
| - */
|
| -function AudioControls(container, advanceTrack, onError) {
|
| - MediaControls.call(this, container, onError);
|
| -
|
| - this.container_.classList.add('audio-controls');
|
| -
|
| - this.advanceTrack_ = advanceTrack;
|
| -
|
| - this.initPlayButton();
|
| - this.initTimeControls(false /* no seek mark */);
|
| - /* No volume controls */
|
| - this.createButton('previous', this.onAdvanceClick_.bind(this, false));
|
| - this.createButton('next', this.onAdvanceClick_.bind(this, true));
|
| -
|
| - var audioControls = this;
|
| - chrome.mediaPlayerPrivate.onNextTrack.addListener(
|
| - function() { audioControls.onAdvanceClick_(true); });
|
| - chrome.mediaPlayerPrivate.onPrevTrack.addListener(
|
| - function() { audioControls.onAdvanceClick_(false); });
|
| - chrome.mediaPlayerPrivate.onTogglePlayState.addListener(
|
| - function() { audioControls.togglePlayState(); });
|
| -}
|
| -
|
| -AudioControls.prototype = { __proto__: MediaControls.prototype };
|
| -
|
| -/**
|
| - * Media completion handler. Advances to the next track.
|
| - */
|
| -AudioControls.prototype.onMediaComplete = function() {
|
| - this.advanceTrack_(true);
|
| -};
|
| -
|
| -/**
|
| - * The track position after which "previous" button acts as "restart".
|
| - */
|
| -AudioControls.TRACK_RESTART_THRESHOLD = 5; // seconds.
|
| -
|
| -/**
|
| - * @param {boolean} forward True if advancing forward.
|
| - * @private
|
| - */
|
| -AudioControls.prototype.onAdvanceClick_ = function(forward) {
|
| - if (!forward &&
|
| - (this.getMedia().currentTime > AudioControls.TRACK_RESTART_THRESHOLD)) {
|
| - // We are far enough from the beginning of the current track.
|
| - // Restart it instead of than skipping to the previous one.
|
| - this.getMedia().currentTime = 0;
|
| - } else {
|
| - this.advanceTrack_(forward);
|
| - }
|
| -};
|
|
|