aboutsummaryrefslogtreecommitdiffstats
path: root/src/js/ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/ui.js')
-rw-r--r--src/js/ui.js513
1 files changed, 254 insertions, 259 deletions
diff --git a/src/js/ui.js b/src/js/ui.js
index 32db6ae7..99faa9b8 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -13,267 +13,262 @@ import is from './utils/is';
import loadImage from './utils/load-image';
const ui = {
- addStyleHook() {
- toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
- toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
- },
+ addStyleHook() {
+ toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
+ toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
+ },
- // Toggle native HTML5 media controls
- toggleNativeControls(toggle = false) {
- if (toggle && this.isHTML5) {
- this.media.setAttribute('controls', '');
- } else {
- this.media.removeAttribute('controls');
- }
- },
-
- // Setup the UI
- build() {
- // Re-attach media element listeners
- // TODO: Use event bubbling?
- this.listeners.media();
-
- // Don't setup interface if no support
- if (!this.supported.ui) {
- this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
-
- // Restore native controls
- ui.toggleNativeControls.call(this, true);
-
- // Bail
- return;
- }
-
- // Inject custom controls if not present
- if (!is.element(this.elements.controls)) {
- // Inject custom controls
- controls.inject.call(this);
-
- // Re-attach control listeners
- this.listeners.controls();
- }
-
- // Remove native controls
- ui.toggleNativeControls.call(this);
-
- // Setup captions for HTML5
- if (this.isHTML5) {
- captions.setup.call(this);
- }
-
- // Reset volume
- this.volume = null;
-
- // Reset mute state
- this.muted = null;
-
- // Reset loop state
- this.loop = null;
-
- // Reset quality setting
- this.quality = null;
-
- // Reset speed
- this.speed = null;
-
- // Reset volume display
- controls.updateVolume.call(this);
-
- // Reset time display
- controls.timeUpdate.call(this);
-
- // Update the UI
- ui.checkPlaying.call(this);
-
- // Check for picture-in-picture support
- toggleClass(
- this.elements.container,
- this.config.classNames.pip.supported,
- support.pip && this.isHTML5 && this.isVideo,
- );
-
- // Check for airplay support
- toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
-
- // Add iOS class
- toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
-
- // Add touch class
- toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
-
- // Ready for API calls
- this.ready = true;
-
- // Ready event at end of execution stack
- setTimeout(() => {
- triggerEvent.call(this, this.media, 'ready');
- }, 0);
-
- // Set the title
- ui.setTitle.call(this);
-
- // Assure the poster image is set, if the property was added before the element was created
- if (this.poster) {
- ui.setPoster.call(this, this.poster, false).catch(() => {});
- }
-
- // Manually set the duration if user has overridden it.
- // The event listeners for it doesn't get called if preload is disabled (#701)
- if (this.config.duration) {
- controls.durationUpdate.call(this);
- }
- },
-
- // Setup aria attribute for play and iframe title
- setTitle() {
- // Find the current text
- let label = i18n.get('play', this.config);
-
- // If there's a media title set, use that for the label
- if (is.string(this.config.title) && !is.empty(this.config.title)) {
- label += `, ${this.config.title}`;
- }
-
- // If there's a play button, set label
- Array.from(this.elements.buttons.play || []).forEach(button => {
- button.setAttribute('aria-label', label);
- });
-
- // Set iframe title
- // https://github.com/sampotts/plyr/issues/124
- if (this.isEmbed) {
- const iframe = getElement.call(this, 'iframe');
-
- if (!is.element(iframe)) {
- return;
- }
-
- // Default to media type
- const title = !is.empty(this.config.title) ? this.config.title : 'video';
- const format = i18n.get('frameTitle', this.config);
-
- iframe.setAttribute('title', format.replace('{title}', title));
- }
- },
-
- // Toggle poster
- togglePoster(enable) {
- toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
- },
-
- // Set the poster image (async)
- // Used internally for the poster setter, with the passive option forced to false
- setPoster(poster, passive = true) {
- // Don't override if call is passive
- if (passive && this.poster) {
- return Promise.reject(new Error('Poster already set'));
- }
-
- // Set property synchronously to respect the call order
- this.media.setAttribute('poster', poster);
-
- // HTML5 uses native poster attribute
- if (this.isHTML5) {
- return Promise.resolve(poster);
- }
-
- // Wait until ui is ready
- return (
- ready
- .call(this)
- // Load image
- .then(() => loadImage(poster))
- .catch(err => {
- // Hide poster on error unless it's been set by another call
- if (poster === this.poster) {
- ui.togglePoster.call(this, false);
- }
- // Rethrow
- throw err;
- })
- .then(() => {
- // Prevent race conditions
- if (poster !== this.poster) {
- throw new Error('setPoster cancelled by later call to setPoster');
- }
- })
- .then(() => {
- Object.assign(this.elements.poster.style, {
- backgroundImage: `url('${poster}')`,
- // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
- backgroundSize: '',
- });
-
- ui.togglePoster.call(this, true);
-
- return poster;
- })
- );
- },
-
- // Check playing state
- checkPlaying(event) {
- // Class hooks
- toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
- toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
- toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
-
- // Set state
- Array.from(this.elements.buttons.play || []).forEach(target => {
- Object.assign(target, { pressed: this.playing });
- target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
- });
-
- // Only update controls on non timeupdate events
- if (is.event(event) && event.type === 'timeupdate') {
- return;
- }
-
- // Toggle controls
+ // Toggle native HTML5 media controls
+ toggleNativeControls(toggle = false) {
+ if (toggle && this.isHTML5) {
+ this.media.setAttribute('controls', '');
+ } else {
+ this.media.removeAttribute('controls');
+ }
+ },
+
+ // Setup the UI
+ build() {
+ // Re-attach media element listeners
+ // TODO: Use event bubbling?
+ this.listeners.media();
+
+ // Don't setup interface if no support
+ if (!this.supported.ui) {
+ this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
+
+ // Restore native controls
+ ui.toggleNativeControls.call(this, true);
+
+ // Bail
+ return;
+ }
+
+ // Inject custom controls if not present
+ if (!is.element(this.elements.controls)) {
+ // Inject custom controls
+ controls.inject.call(this);
+
+ // Re-attach control listeners
+ this.listeners.controls();
+ }
+
+ // Remove native controls
+ ui.toggleNativeControls.call(this);
+
+ // Setup captions for HTML5
+ if (this.isHTML5) {
+ captions.setup.call(this);
+ }
+
+ // Reset volume
+ this.volume = null;
+
+ // Reset mute state
+ this.muted = null;
+
+ // Reset loop state
+ this.loop = null;
+
+ // Reset quality setting
+ this.quality = null;
+
+ // Reset speed
+ this.speed = null;
+
+ // Reset volume display
+ controls.updateVolume.call(this);
+
+ // Reset time display
+ controls.timeUpdate.call(this);
+
+ // Update the UI
+ ui.checkPlaying.call(this);
+
+ // Check for picture-in-picture support
+ toggleClass(
+ this.elements.container,
+ this.config.classNames.pip.supported,
+ support.pip && this.isHTML5 && this.isVideo,
+ );
+
+ // Check for airplay support
+ toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
+
+ // Add iOS class
+ toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
+
+ // Add touch class
+ toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
+
+ // Ready for API calls
+ this.ready = true;
+
+ // Ready event at end of execution stack
+ setTimeout(() => {
+ triggerEvent.call(this, this.media, 'ready');
+ }, 0);
+
+ // Set the title
+ ui.setTitle.call(this);
+
+ // Assure the poster image is set, if the property was added before the element was created
+ if (this.poster) {
+ ui.setPoster.call(this, this.poster, false).catch(() => {});
+ }
+
+ // Manually set the duration if user has overridden it.
+ // The event listeners for it doesn't get called if preload is disabled (#701)
+ if (this.config.duration) {
+ controls.durationUpdate.call(this);
+ }
+ },
+
+ // Setup aria attribute for play and iframe title
+ setTitle() {
+ // Find the current text
+ let label = i18n.get('play', this.config);
+
+ // If there's a media title set, use that for the label
+ if (is.string(this.config.title) && !is.empty(this.config.title)) {
+ label += `, ${this.config.title}`;
+ }
+
+ // If there's a play button, set label
+ Array.from(this.elements.buttons.play || []).forEach(button => {
+ button.setAttribute('aria-label', label);
+ });
+
+ // Set iframe title
+ // https://github.com/sampotts/plyr/issues/124
+ if (this.isEmbed) {
+ const iframe = getElement.call(this, 'iframe');
+
+ if (!is.element(iframe)) {
+ return;
+ }
+
+ // Default to media type
+ const title = !is.empty(this.config.title) ? this.config.title : 'video';
+ const format = i18n.get('frameTitle', this.config);
+
+ iframe.setAttribute('title', format.replace('{title}', title));
+ }
+ },
+
+ // Toggle poster
+ togglePoster(enable) {
+ toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
+ },
+
+ // Set the poster image (async)
+ // Used internally for the poster setter, with the passive option forced to false
+ setPoster(poster, passive = true) {
+ // Don't override if call is passive
+ if (passive && this.poster) {
+ return Promise.reject(new Error('Poster already set'));
+ }
+
+ // Set property synchronously to respect the call order
+ this.media.setAttribute('poster', poster);
+
+ // HTML5 uses native poster attribute
+ if (this.isHTML5) {
+ return Promise.resolve(poster);
+ }
+
+ // Wait until ui is ready
+ return (
+ ready
+ .call(this)
+ // Load image
+ .then(() => loadImage(poster))
+ .catch(err => {
+ // Hide poster on error unless it's been set by another call
+ if (poster === this.poster) {
+ ui.togglePoster.call(this, false);
+ }
+ // Rethrow
+ throw err;
+ })
+ .then(() => {
+ // Prevent race conditions
+ if (poster !== this.poster) {
+ throw new Error('setPoster cancelled by later call to setPoster');
+ }
+ })
+ .then(() => {
+ Object.assign(this.elements.poster.style, {
+ backgroundImage: `url('${poster}')`,
+ // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
+ backgroundSize: '',
+ });
+
+ ui.togglePoster.call(this, true);
+
+ return poster;
+ })
+ );
+ },
+
+ // Check playing state
+ checkPlaying(event) {
+ // Class hooks
+ toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
+ toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
+ toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
+
+ // Set state
+ Array.from(this.elements.buttons.play || []).forEach(target => {
+ Object.assign(target, { pressed: this.playing });
+ target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
+ });
+
+ // Only update controls on non timeupdate events
+ if (is.event(event) && event.type === 'timeupdate') {
+ return;
+ }
+
+ // Toggle controls
+ ui.toggleControls.call(this);
+ },
+
+ // Check if media is loading
+ checkLoading(event) {
+ this.loading = ['stalled', 'waiting'].includes(event.type);
+
+ // Clear timer
+ clearTimeout(this.timers.loading);
+
+ // Timer to prevent flicker when seeking
+ this.timers.loading = setTimeout(
+ () => {
+ // Update progress bar loading class state
+ toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
+
+ // Update controls visibility
ui.toggleControls.call(this);
- },
-
- // Check if media is loading
- checkLoading(event) {
- this.loading = ['stalled', 'waiting'].includes(event.type);
-
- // Clear timer
- clearTimeout(this.timers.loading);
-
- // Timer to prevent flicker when seeking
- this.timers.loading = setTimeout(
- () => {
- // Update progress bar loading class state
- toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
-
- // Update controls visibility
- ui.toggleControls.call(this);
- },
- this.loading ? 250 : 0,
- );
- },
-
- // Toggle controls based on state and `force` argument
- toggleControls(force) {
- const { controls: controlsElement } = this.elements;
-
- if (controlsElement && this.config.hideControls) {
- // Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
- const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();
-
- // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
- this.toggleControls(
- Boolean(
- force ||
- this.loading ||
- this.paused ||
- controlsElement.pressed ||
- controlsElement.hover ||
- recentTouchSeek,
- ),
- );
- }
- },
+ },
+ this.loading ? 250 : 0,
+ );
+ },
+
+ // Toggle controls based on state and `force` argument
+ toggleControls(force) {
+ const { controls: controlsElement } = this.elements;
+
+ if (controlsElement && this.config.hideControls) {
+ // Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
+ const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();
+
+ // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
+ this.toggleControls(
+ Boolean(
+ force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek,
+ ),
+ );
+ }
+ },
};
export default ui;