diff options
Diffstat (limited to 'javascript/videojs/src/js/control-bar/progress-control/seek-bar.js')
| -rw-r--r-- | javascript/videojs/src/js/control-bar/progress-control/seek-bar.js | 610 |
1 files changed, 0 insertions, 610 deletions
diff --git a/javascript/videojs/src/js/control-bar/progress-control/seek-bar.js b/javascript/videojs/src/js/control-bar/progress-control/seek-bar.js deleted file mode 100644 index bc7d83c..0000000 --- a/javascript/videojs/src/js/control-bar/progress-control/seek-bar.js +++ /dev/null @@ -1,610 +0,0 @@ -/** - * @file seek-bar.js - */ -import Slider from '../../slider/slider.js'; -import Component from '../../component.js'; -import {IS_IOS, IS_ANDROID} from '../../utils/browser.js'; -import * as Dom from '../../utils/dom.js'; -import * as Fn from '../../utils/fn.js'; -import {formatTime} from '../../utils/time.js'; -import {silencePromise} from '../../utils/promise'; -import {merge} from '../../utils/obj'; -import document from 'global/document'; - -/** @import Player from '../../player' */ - -import './load-progress-bar.js'; -import './play-progress-bar.js'; -import './mouse-time-display.js'; - -/** - * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} - * as its `bar`. - * - * @extends Slider - */ -class SeekBar extends Slider { - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - * @param {number} [options.stepSeconds=5] - * The number of seconds to increment on keyboard control - * @param {number} [options.pageMultiplier=12] - * The multiplier of stepSeconds that PgUp/PgDown move the timeline. - */ - constructor(player, options) { - options = merge(SeekBar.prototype.options_, options); - - // Avoid mutating the prototype's `children` array by creating a copy - options.children = [...options.children]; - - const shouldDisableSeekWhileScrubbing = - (player.options_.disableSeekWhileScrubbingOnMobile && (IS_IOS || IS_ANDROID)) || - (player.options_.disableSeekWhileScrubbingOnSTV); - - // Add the TimeTooltip as a child if we are on desktop, or on mobile with `disableSeekWhileScrubbingOnMobile: true` - if ((!IS_IOS && !IS_ANDROID) || shouldDisableSeekWhileScrubbing) { - options.children.splice(1, 0, 'mouseTimeDisplay'); - } - - super(player, options); - - this.shouldDisableSeekWhileScrubbing_ = shouldDisableSeekWhileScrubbing; - this.pendingSeekTime_ = null; - - this.setEventHandlers_(); - } - - /** - * Sets the event handlers - * - * @private - */ - setEventHandlers_() { - this.update_ = Fn.bind_(this, this.update); - this.update = Fn.throttle(this.update_, Fn.UPDATE_REFRESH_INTERVAL); - - this.on(this.player_, ['durationchange', 'timeupdate'], this.update); - this.on(this.player_, ['ended'], this.update_); - if (this.player_.liveTracker) { - this.on(this.player_.liveTracker, 'liveedgechange', this.update); - } - - // when playing, let's ensure we smoothly update the play progress bar - // via an interval - this.updateInterval = null; - - this.enableIntervalHandler_ = (e) => this.enableInterval_(e); - this.disableIntervalHandler_ = (e) => this.disableInterval_(e); - - this.on(this.player_, ['playing'], this.enableIntervalHandler_); - - this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); - - // we don't need to update the play progress if the document is hidden, - // also, this causes the CPU to spike and eventually crash the page on IE11. - if ('hidden' in document && 'visibilityState' in document) { - this.on(document, 'visibilitychange', this.toggleVisibility_); - } - } - - toggleVisibility_(e) { - if (document.visibilityState === 'hidden') { - this.cancelNamedAnimationFrame('SeekBar#update'); - this.cancelNamedAnimationFrame('Slider#update'); - this.disableInterval_(e); - } else { - if (!this.player_.ended() && !this.player_.paused()) { - this.enableInterval_(); - } - - // we just switched back to the page and someone may be looking, so, update ASAP - this.update(); - } - } - - enableInterval_() { - if (this.updateInterval) { - return; - - } - this.updateInterval = this.setInterval(this.update, Fn.UPDATE_REFRESH_INTERVAL); - } - - disableInterval_(e) { - if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') { - return; - } - - if (!this.updateInterval) { - return; - } - - this.clearInterval(this.updateInterval); - this.updateInterval = null; - } - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - createEl() { - return super.createEl('div', { - className: 'vjs-progress-holder' - }, { - 'aria-label': this.localize('Progress Bar') - }); - } - - /** - * This function updates the play progress bar and accessibility - * attributes to whatever is passed in. - * - * @param {Event} [event] - * The `timeupdate` or `ended` event that caused this to run. - * - * @listens Player#timeupdate - * - * @return {number} - * The current percent at a number from 0-1 - */ - update(event) { - // ignore updates while the tab is hidden - if (document.visibilityState === 'hidden') { - return; - } - - const percent = super.update(); - - this.requestNamedAnimationFrame('SeekBar#update', () => { - const currentTime = this.player_.ended() ? - this.player_.duration() : this.getCurrentTime_(); - const liveTracker = this.player_.liveTracker; - let duration = this.player_.duration(); - - if (liveTracker && liveTracker.isLive()) { - duration = this.player_.liveTracker.liveCurrentTime(); - } - - if (this.percent_ !== percent) { - // machine readable value of progress bar (percentage complete) - this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); - this.percent_ = percent; - } - - if (this.currentTime_ !== currentTime || this.duration_ !== duration) { - // human readable value of progress bar (time complete) - this.el_.setAttribute( - 'aria-valuetext', - this.localize( - 'progress bar timing: currentTime={1} duration={2}', - [formatTime(currentTime, duration), - formatTime(duration, duration)], - '{1} of {2}' - ) - ); - - this.currentTime_ = currentTime; - this.duration_ = duration; - } - - // update the progress bar time tooltip with the current time - if (this.bar) { - this.bar.update(Dom.getBoundingClientRect(this.el()), this.getProgress(), event); - } - }); - - return percent; - } - - /** - * Prevent liveThreshold from causing seeks to seem like they - * are not happening from a user perspective. - * - * @param {number} ct - * current time to seek to - */ - userSeek_(ct) { - if (this.player_.liveTracker && this.player_.liveTracker.isLive()) { - this.player_.liveTracker.nextSeekedFromUser(); - } - - this.player_.currentTime(ct); - } - - /** - * Get the value of current time but allows for smooth scrubbing, - * when player can't keep up. - * - * @return {number} - * The current time value to display - * - * @private - */ - getCurrentTime_() { - return (this.player_.scrubbing()) ? - this.player_.getCache().currentTime : - this.player_.currentTime(); - } - - /** - * Getter and setter for pendingSeekTime. - * Ensures the value is clamped between 0 and duration. - * - * @param {number|null} [time] - Optional. The new pending seek time, can be a number or null. - * @return {number|null} - The current pending seek time. - */ - pendingSeekTime(time) { - if (time !== undefined) { - if (time !== null) { - const duration = this.player_.duration(); - - this.pendingSeekTime_ = Math.max(0, Math.min(time, duration)); - } else { - this.pendingSeekTime_ = null; - } - } - return this.pendingSeekTime_; - } - - /** - * Get the percentage of media played so far. - * - * @return {number} - * The percentage of media played so far (0 to 1). - */ - getPercent() { - // If we have a pending seek time, we are scrubbing on mobile and should set the slider percent - // to reflect the current scrub location. - if (this.pendingSeekTime() !== null) { - return this.pendingSeekTime() / this.player_.duration(); - } - - const currentTime = this.getCurrentTime_(); - let percent; - const liveTracker = this.player_.liveTracker; - - if (liveTracker && liveTracker.isLive()) { - percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); - - // prevent the percent from changing at the live edge - if (liveTracker.atLiveEdge()) { - percent = 1; - } - } else { - percent = currentTime / this.player_.duration(); - } - - return percent; - } - - /** - * Handle mouse down on seek bar - * - * @param {MouseEvent} event - * The `mousedown` event that caused this to run. - * - * @listens mousedown - */ - handleMouseDown(event) { - if (!Dom.isSingleLeftClick(event)) { - return; - } - - // Stop event propagation to prevent double fire in progress-control.js - event.stopPropagation(); - - this.videoWasPlaying = !this.player_.paused(); - - // Don't pause if we are on mobile and `disableSeekWhileScrubbingOnMobile: true`. - // In that case, playback should continue while the player scrubs to a new location. - if (!this.shouldDisableSeekWhileScrubbing_) { - this.player_.pause(); - } - - super.handleMouseDown(event); - } - - /** - * Handle mouse move on seek bar - * - * @param {MouseEvent} event - * The `mousemove` event that caused this to run. - * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false - * - * @listens mousemove - */ - handleMouseMove(event, mouseDown = false) { - if (!Dom.isSingleLeftClick(event) || isNaN(this.player_.duration())) { - return; - } - - if (!mouseDown && !this.player_.scrubbing()) { - this.player_.scrubbing(true); - } - - let newTime; - const distance = this.calculateDistance(event); - const liveTracker = this.player_.liveTracker; - - if (!liveTracker || !liveTracker.isLive()) { - newTime = distance * this.player_.duration(); - - // Don't let video end while scrubbing. - if (newTime === this.player_.duration()) { - newTime = newTime - 0.1; - } - } else { - - if (distance >= 0.99) { - liveTracker.seekToLiveEdge(); - return; - } - const seekableStart = liveTracker.seekableStart(); - const seekableEnd = liveTracker.liveCurrentTime(); - - newTime = seekableStart + (distance * liveTracker.liveWindow()); - - // Don't let video end while scrubbing. - if (newTime >= seekableEnd) { - newTime = seekableEnd; - } - - // Compensate for precision differences so that currentTime is not less - // than seekable start - if (newTime <= seekableStart) { - newTime = seekableStart + 0.1; - } - - // On android seekableEnd can be Infinity sometimes, - // this will cause newTime to be Infinity, which is - // not a valid currentTime. - if (newTime === Infinity) { - return; - } - } - - // if on mobile and `disableSeekWhileScrubbingOnMobile: true`, keep track of the desired seek point but we won't initiate the seek until 'touchend' - if (this.shouldDisableSeekWhileScrubbing_) { - this.pendingSeekTime(newTime); - } else { - this.userSeek_(newTime); - } - - if (this.player_.options_.enableSmoothSeeking) { - this.update(); - } - } - - enable() { - super.enable(); - const mouseTimeDisplay = this.getChild('mouseTimeDisplay'); - - if (!mouseTimeDisplay) { - return; - } - - mouseTimeDisplay.show(); - } - - disable() { - super.disable(); - const mouseTimeDisplay = this.getChild('mouseTimeDisplay'); - - if (!mouseTimeDisplay) { - return; - } - - mouseTimeDisplay.hide(); - } - - /** - * Handle mouse up on seek bar - * - * @param {MouseEvent} event - * The `mouseup` event that caused this to run. - * - * @listens mouseup - */ - handleMouseUp(event) { - super.handleMouseUp(event); - - // Stop event propagation to prevent double fire in progress-control.js - if (event) { - event.stopPropagation(); - } - this.player_.scrubbing(false); - - // If we have a pending seek time, then we have finished scrubbing on mobile and should initiate a seek. - if (this.pendingSeekTime() !== null) { - this.userSeek_(this.pendingSeekTime()); - - this.pendingSeekTime(null); - } - - /** - * Trigger timeupdate because we're done seeking and the time has changed. - * This is particularly useful for if the player is paused to time the time displays. - * - * @event Tech#timeupdate - * @type {Event} - */ - this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); - if (this.videoWasPlaying) { - silencePromise(this.player_.play()); - } else { - // We're done seeking and the time has changed. - // If the player is paused, make sure we display the correct time on the seek bar. - this.update_(); - } - } - - /** - * Handles pending seek time when `disableSeekWhileScrubbingOnSTV` is enabled. - * - * @param {number} stepAmount - The number of seconds to step (positive for forward, negative for backward). - */ - handlePendingSeek_(stepAmount) { - if (!this.player_.paused()) { - this.player_.pause(); - } - - const currentPos = this.pendingSeekTime() !== null ? - this.pendingSeekTime() : - this.player_.currentTime(); - - this.pendingSeekTime(currentPos + stepAmount); - this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); - } - - /** - * Move more quickly fast forward for keyboard-only users - */ - stepForward() { - // if `disableSeekWhileScrubbingOnSTV: true`, keep track of the desired seek point but we won't initiate the seek - if (this.shouldDisableSeekWhileScrubbing_) { - this.handlePendingSeek_(this.options().stepSeconds); - } else { - this.userSeek_(this.player_.currentTime() + this.options().stepSeconds); - } - } - - /** - * Move more quickly rewind for keyboard-only users - */ - stepBack() { - // if `disableSeekWhileScrubbingOnSTV: true`, keep track of the desired seek point but we won't initiate the seek - if (this.shouldDisableSeekWhileScrubbing_) { - this.handlePendingSeek_(-this.options().stepSeconds); - } else { - this.userSeek_(this.player_.currentTime() - this.options().stepSeconds); - } - } - - /** - * Toggles the playback state of the player - * This gets called when enter or space is used on the seekbar - * - * @param {KeyboardEvent} event - * The `keydown` event that caused this function to be called - * - */ - handleAction(event) { - if (this.pendingSeekTime() !== null) { - this.userSeek_(this.pendingSeekTime()); - this.pendingSeekTime(null); - } - if (this.player_.paused()) { - this.player_.play(); - } else { - this.player_.pause(); - } - } - - /** - * Called when this SeekBar has focus and a key gets pressed down. - * Supports the following keys: - * - * Space or Enter key fire a click event - * Home key moves to start of the timeline - * End key moves to end of the timeline - * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline - * PageDown key moves back a larger step than ArrowDown - * PageUp key moves forward a large step - * - * @param {KeyboardEvent} event - * The `keydown` event that caused this function to be called. - * - * @listens keydown - */ - handleKeyDown(event) { - const liveTracker = this.player_.liveTracker; - - if (event.key === ' ' || event.key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - this.handleAction(event); - } else if (event.key === 'Home') { - event.preventDefault(); - event.stopPropagation(); - this.userSeek_(0); - } else if (event.key === 'End') { - event.preventDefault(); - event.stopPropagation(); - if (liveTracker && liveTracker.isLive()) { - this.userSeek_(liveTracker.liveCurrentTime()); - } else { - this.userSeek_(this.player_.duration()); - } - } else if (/^[0-9]$/.test(event.key)) { - event.preventDefault(); - event.stopPropagation(); - const gotoFraction = parseInt(event.key, 10) * 0.1; - - if (liveTracker && liveTracker.isLive()) { - this.userSeek_(liveTracker.seekableStart() + (liveTracker.liveWindow() * gotoFraction)); - } else { - this.userSeek_(this.player_.duration() * gotoFraction); - } - } else if (event.key === 'PageDown') { - event.preventDefault(); - event.stopPropagation(); - this.userSeek_(this.player_.currentTime() - (this.options().stepSeconds * this.options().pageMultiplier)); - } else if (event.key === 'PageUp') { - event.preventDefault(); - event.stopPropagation(); - this.userSeek_(this.player_.currentTime() + (this.options().stepSeconds * this.options().pageMultiplier)); - } else { - // Pass keydown handling up for unsupported keys - super.handleKeyDown(event); - } - } - - dispose() { - this.disableInterval_(); - - this.off(this.player_, ['durationchange', 'timeupdate'], this.update); - this.off(this.player_, ['ended'], this.update_); - if (this.player_.liveTracker) { - this.off(this.player_.liveTracker, 'liveedgechange', this.update); - } - - this.off(this.player_, ['playing'], this.enableIntervalHandler_); - this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); - - // we don't need to update the play progress if the document is hidden, - // also, this causes the CPU to spike and eventually crash the page on IE11. - if ('hidden' in document && 'visibilityState' in document) { - this.off(document, 'visibilitychange', this.toggleVisibility_); - } - - super.dispose(); - } -} - -/** - * Default options for the `SeekBar` - * - * @type {Object} - * @private - */ -SeekBar.prototype.options_ = { - children: [ - 'loadProgressBar', - 'playProgressBar' - ], - barName: 'playProgressBar', - stepSeconds: 5, - pageMultiplier: 12 -}; - -Component.registerComponent('SeekBar', SeekBar); -export default SeekBar; |
