Files
shaka-player/ui/range_element.js
T
Joey Parrish 5001086901 fix(ui): Fix renaming of UI base class protected members
This fixes the renaming of the protected members of SettingsMenu and
RangeElement, both of which are useful to apps building custom UI
plugins.

In order for those app-level plugins to access the protected members
of the base class, they needed to be part of an external interface.
Otherwise the compiler would rename them.

Closes #2923

Change-Id: I2edb9f1428789fac1e6c60ec6a68b20cdd249ae5
2020-10-20 15:51:54 +00:00

198 lines
4.7 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.ui.RangeElement');
goog.require('shaka.ui.Element');
goog.require('shaka.util.Dom');
goog.requireType('shaka.ui.Controls');
/**
* A range element, built to work across browsers.
*
* In particular, getting styles to work right on IE requires a specific
* structure.
*
* This also handles the case where the range element is being manipulated and
* updated at the same time. This can happen when seeking during playback or
* when casting.
*
* @implements {shaka.extern.IUIRangeElement}
* @export
*/
shaka.ui.RangeElement = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
* @param {!Array.<string>} containerClassNames
* @param {!Array.<string>} barClassNames
*/
constructor(parent, controls, containerClassNames, barClassNames) {
super(parent, controls);
/**
* This container is to support IE 11. See detailed notes in
* less/range_elements.less for a complete explanation.
* @protected {!HTMLElement}
*/
this.container = shaka.util.Dom.createHTMLElement('div');
this.container.classList.add('shaka-range-container');
this.container.classList.add(...containerClassNames);
/** @private {boolean} */
this.isChanging_ = false;
/** @protected {!HTMLInputElement} */
this.bar =
/** @type {!HTMLInputElement} */ (document.createElement('input'));
this.bar.classList.add('shaka-range-element');
this.bar.classList.add(...barClassNames);
this.bar.type = 'range';
// TODO(#2027): step=any causes keyboard nav problems on IE 11.
this.bar.step = 'any';
this.bar.min = '0';
this.bar.max = '1';
this.bar.value = '0';
this.container.appendChild(this.bar);
this.parent.appendChild(this.container);
this.eventManager.listen(this.bar, 'mousedown', () => {
if (this.controls.isOpaque()) {
this.isChanging_ = true;
this.onChangeStart();
}
});
this.eventManager.listen(this.bar, 'touchstart', (e) => {
if (this.controls.isOpaque()) {
this.isChanging_ = true;
this.setBarValueForTouch_(e);
this.onChangeStart();
}
});
this.eventManager.listen(this.bar, 'input', () => {
this.onChange();
});
this.eventManager.listen(this.bar, 'touchmove', (e) => {
if (this.isChanging_) {
this.setBarValueForTouch_(e);
this.onChange();
}
});
this.eventManager.listen(this.bar, 'touchend', (e) => {
if (this.isChanging_) {
this.isChanging_ = false;
this.setBarValueForTouch_(e);
this.onChangeEnd();
}
});
this.eventManager.listen(this.bar, 'mouseup', () => {
if (this.isChanging_) {
this.isChanging_ = false;
this.onChangeEnd();
}
});
}
/**
* @override
* @export
*/
setRange(min, max) {
this.bar.min = min;
this.bar.max = max;
}
/**
* Called when user interaction begins.
* To be overridden by subclasses.
* @override
* @export
*/
onChangeStart() {}
/**
* Called when a new value is set by user interaction.
* To be overridden by subclasses.
* @override
* @export
*/
onChange() {}
/**
* Called when user interaction ends.
* To be overridden by subclasses.
* @override
* @export
*/
onChangeEnd() {}
/**
* @override
* @export
*/
getValue() {
return parseFloat(this.bar.value);
}
/**
* @override
* @export
*/
setValue(value) {
// The user interaction overrides any external values being pushed in.
if (this.isChanging_) {
return;
}
this.bar.value = value;
}
/**
* Synchronize the touch position with the range value.
* Comes in handy on iOS, where users have to grab the handle in order
* to start seeking.
* @param {Event} event
* @private
*/
setBarValueForTouch_(event) {
event.preventDefault();
const changedTouch = /** @type {TouchEvent} */ (event).changedTouches[0];
const rect = this.bar.getBoundingClientRect();
const min = parseFloat(this.bar.min);
const max = parseFloat(this.bar.max);
// Calculate the range value based on the touch position.
// Pixels from the left of the range element
const touchPosition = changedTouch.clientX - rect.left;
// Pixels per unit value of the range element.
const scale = (max - min) / rect.width;
// Touch position in units, which may be outside the allowed range.
let value = min + scale * touchPosition;
// Keep value within bounds.
if (value < min) {
value = min;
} else if (value > max) {
value = max;
}
this.bar.value = value;
}
};