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); |
- } |
-}; |