diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/js/controls.js | 14 | ||||
| -rw-r--r-- | src/js/listeners.js | 432 | ||||
| -rw-r--r-- | src/js/plyr.js | 109 | ||||
| -rw-r--r-- | src/js/support.js | 20 | ||||
| -rw-r--r-- | src/js/ui.js | 82 | ||||
| -rw-r--r-- | src/js/utils/animation.js | 14 | ||||
| -rw-r--r-- | src/sass/components/poster.scss | 7 | ||||
| -rw-r--r-- | src/sass/components/progress.scss | 1 | ||||
| -rw-r--r-- | src/sass/lib/mixins.scss | 1 | 
9 files changed, 412 insertions, 268 deletions
| diff --git a/src/js/controls.js b/src/js/controls.js index da404441..27611b2f 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -378,7 +378,7 @@ const controls = {              // Show the respective menu              if (!isRadioButton && [32,39].includes(event.which)) { -                controls.showMenuPanel.call(this, type); +                controls.showMenuPanel.call(this, type, true);              } else {                  let target; @@ -477,7 +477,7 @@ const controls = {                          break;                  } -                controls.showMenuPanel.call(this, 'home'); +                controls.showMenuPanel.call(this, 'home', event.type === 'keydown');              },              type,              false, @@ -1118,7 +1118,7 @@ const controls = {      },      // Show a panel in the menu -    showMenuPanel(type = '') { +    showMenuPanel(type = '', tabFocus = false) {          const target = document.getElementById(`plyr-settings-${this.id}-${type}`);          // Nothing to show, bail @@ -1170,7 +1170,7 @@ const controls = {          // Focus the first item          const firstItem = target.querySelector('[role^="menuitem"]'); -        setFocus.call(this, firstItem, true); +        setFocus.call(this, firstItem, tabFocus);      },      // Build the default HTML @@ -1344,7 +1344,7 @@ const controls = {                  // Show menu on click                  on(menuItem, 'click', () => { -                    controls.showMenuPanel.call(this, type); +                    controls.showMenuPanel.call(this, type, false);                  });                  const flex = createElement('span', null, i18n.get(type, this.config)); @@ -1406,12 +1406,12 @@ const controls = {                      event.stopPropagation();                      // Show the respective menu -                    controls.showMenuPanel.call(this, 'home'); +                    controls.showMenuPanel.call(this, 'home', true);                  }, false);                  // Go back via button click                  on(backButton, 'click', () => { -                    controls.showMenuPanel.call(this, 'home'); +                    controls.showMenuPanel.call(this, 'home', false);                  });                  // Add to pane diff --git a/src/js/listeners.js b/src/js/listeners.js index 0b803454..e19894ba 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -4,10 +4,12 @@  import controls from './controls';  import ui from './ui'; +import { repaint } from './utils/animation';  import browser from './utils/browser';  import {      getElement,      getElements, +    hasClass,      matches,      toggleClass,      toggleHidden, @@ -30,6 +32,7 @@ class Listeners {      // Handle key presses      handleKey(event) { +        const { player } = this;          const code = event.keyCode ? event.keyCode : event.which;          const pressed = event.type === 'keydown';          const repeat = pressed && code === this.lastKey; @@ -48,7 +51,7 @@ class Listeners {          // Seek by the number keys          const seekByKey = () => {              // Divide the max duration into 10th's and times by the number value -            this.player.currentTime = this.player.duration / 10 * (code - 48); +            player.currentTime = player.duration / 10 * (code - 48);          };          // Handle the key on keydown @@ -59,8 +62,8 @@ class Listeners {              // and any that accept key input http://webaim.org/techniques/keyboard/              const focused = document.activeElement;              if (is.element(focused)) { -                const { editable } = this.player.config.selectors; -                const { seek } = this.player.elements.inputs; +                const { editable } = player.config.selectors; +                const { seek } = player.elements.inputs;                  if (focused !== seek && matches(focused, editable)) {                      return; @@ -126,52 +129,52 @@ class Listeners {                  case 75:                      // Space and K key                      if (!repeat) { -                        this.player.togglePlay(); +                        player.togglePlay();                      }                      break;                  case 38:                      // Arrow up -                    this.player.increaseVolume(0.1); +                    player.increaseVolume(0.1);                      break;                  case 40:                      // Arrow down -                    this.player.decreaseVolume(0.1); +                    player.decreaseVolume(0.1);                      break;                  case 77:                      // M key                      if (!repeat) { -                        this.player.muted = !this.player.muted; +                        player.muted = !player.muted;                      }                      break;                  case 39:                      // Arrow forward -                    this.player.forward(); +                    player.forward();                      break;                  case 37:                      // Arrow back -                    this.player.rewind(); +                    player.rewind();                      break;                  case 70:                      // F key -                    this.player.fullscreen.toggle(); +                    player.fullscreen.toggle();                      break;                  case 67:                      // C key                      if (!repeat) { -                        this.player.toggleCaptions(); +                        player.toggleCaptions();                      }                      break;                  case 76:                      // L key -                    this.player.loop = !this.player.loop; +                    player.loop = !player.loop;                      break;                  /* case 73: @@ -193,11 +196,11 @@ class Listeners {              // Escape is handle natively when in full screen              // So we only need to worry about non native              if ( -                !this.player.fullscreen.enabled && -                this.player.fullscreen.active && +                !player.fullscreen.enabled && +                player.fullscreen.active &&                  code === 27              ) { -                this.player.fullscreen.toggle(); +                player.fullscreen.toggle();              }              // Store last code for next cycle @@ -214,17 +217,21 @@ class Listeners {      // Device is touch enabled      firstTouch() { -        this.player.touch = true; +        const { player } = this; + +        player.touch = true;          // Add touch class          toggleClass( -            this.player.elements.container, -            this.player.config.classNames.isTouch, +            player.elements.container, +            player.config.classNames.isTouch,              true,          );      }      setTabFocus(event) { +        const { player } = this; +          clearTimeout(this.focusTimer);          // Ignore any key other than tab @@ -239,8 +246,8 @@ class Listeners {          // Remove current classes          const removeCurrent = () => { -            const className = this.player.config.classNames.tabFocus; -            const current = getElements.call(this.player, `.${className}`); +            const className = player.config.classNames.tabFocus; +            const current = getElements.call(player, `.${className}`);              toggleClass(current, className, false);          }; @@ -257,18 +264,17 @@ class Listeners {          // Delay the adding of classname until the focus has changed          // This event fires before the focusin event -          this.focusTimer = setTimeout(() => {              const focused = document.activeElement;              // Ignore if current focus element isn't inside the player -            if (!this.player.elements.container.contains(focused)) { +            if (!player.elements.container.contains(focused)) {                  return;              }              toggleClass(                  document.activeElement, -                this.player.config.classNames.tabFocus, +                player.config.classNames.tabFocus,                  true,              );          }, 10); @@ -276,10 +282,12 @@ class Listeners {      // Global window & document listeners      global(toggle = true) { +        const { player } = this; +          // Keyboard shortcuts -        if (this.player.config.keyboard.global) { +        if (player.config.keyboard.global) {              toggleListener.call( -                this.player, +                player,                  window,                  'keydown keyup',                  this.handleKey, @@ -290,7 +298,7 @@ class Listeners {          // Click anywhere closes menu          toggleListener.call( -            this.player, +            player,              document.body,              'click',              this.toggleMenu, @@ -298,11 +306,11 @@ class Listeners {          );          // Detect touch by events -        once.call(this.player, document.body, 'touchstart', this.firstTouch); +        once.call(player, document.body, 'touchstart', this.firstTouch);          // Tab focus detection          toggleListener.call( -            this.player, +            player,              document.body,              'keydown focus blur',              this.setTabFocus, @@ -314,14 +322,13 @@ class Listeners {      // Container listeners      container() { +        const { player } = this; +          // Keyboard shortcuts -        if ( -            !this.player.config.keyboard.global && -            this.player.config.keyboard.focused -        ) { +        if (!player.config.keyboard.global && player.config.keyboard.focused) {              on.call( -                this.player, -                this.player.elements.container, +                player, +                player.elements.container,                  'keydown keyup',                  this.handleKey,                  false, @@ -330,11 +337,11 @@ class Listeners {          // Toggle controls on mouse events and entering fullscreen          on.call( -            this.player, -            this.player.elements.container, +            player, +            player.elements.container,              'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',              event => { -                const { controls } = this.player.elements; +                const { controls } = player.elements;                  // Remove button states for fullscreen                  if (event.type === 'enterfullscreen') { @@ -350,17 +357,17 @@ class Listeners {                  let delay = 0;                  if (show) { -                    ui.toggleControls.call(this.player, true); +                    ui.toggleControls.call(player, true);                      // Use longer timeout for touch devices -                    delay = this.player.touch ? 3000 : 2000; +                    delay = player.touch ? 3000 : 2000;                  }                  // Clear timer -                clearTimeout(this.player.timers.controls); +                clearTimeout(player.timers.controls);                  // Set new timer to prevent flicker when seeking -                this.player.timers.controls = setTimeout( -                    () => ui.toggleControls.call(this.player, false), +                player.timers.controls = setTimeout( +                    () => ui.toggleControls.call(player, false),                      delay,                  );              }, @@ -369,100 +376,89 @@ class Listeners {      // Listen for media events      media() { +        const { player } = this; +          // Time change on media -        on.call( -            this.player, -            this.player.media, -            'timeupdate seeking seeked', -            event => controls.timeUpdate.call(this.player, event), +        on.call(player, player.media, 'timeupdate seeking seeked', event => +            controls.timeUpdate.call(player, event),          );          // Display duration          on.call( -            this.player, -            this.player.media, +            player, +            player.media,              'durationchange loadeddata loadedmetadata', -            event => controls.durationUpdate.call(this.player, event), +            event => controls.durationUpdate.call(player, event),          );          // Check for audio tracks on load          // We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point -        on.call(this.player, this.player.media, 'canplay', () => { -            toggleHidden(this.player.elements.volume, !this.player.hasAudio); -            toggleHidden( -                this.player.elements.buttons.mute, -                !this.player.hasAudio, -            ); +        on.call(player, player.media, 'canplay', () => { +            toggleHidden(player.elements.volume, !player.hasAudio); +            toggleHidden(player.elements.buttons.mute, !player.hasAudio);          });          // Handle the media finishing -        on.call(this.player, this.player.media, 'ended', () => { +        on.call(player, player.media, 'ended', () => {              // Show poster on end -            if ( -                this.player.isHTML5 && -                this.player.isVideo && -                this.player.config.resetOnEnd -            ) { +            if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {                  // Restart -                this.player.restart(); +                player.restart();              }          });          // Check for buffer progress          on.call( -            this.player, -            this.player.media, +            player, +            player.media,              'progress playing seeking seeked', -            event => controls.updateProgress.call(this.player, event), +            event => controls.updateProgress.call(player, event),          );          // Handle volume changes -        on.call(this.player, this.player.media, 'volumechange', event => -            controls.updateVolume.call(this.player, event), +        on.call(player, player.media, 'volumechange', event => +            controls.updateVolume.call(player, event),          );          // Handle play/pause          on.call( -            this.player, -            this.player.media, +            player, +            player.media,              'playing play pause ended emptied timeupdate', -            event => ui.checkPlaying.call(this.player, event), +            event => ui.checkPlaying.call(player, event),          );          // Loading state -        on.call( -            this.player, -            this.player.media, -            'waiting canplay seeked playing', -            event => ui.checkLoading.call(this.player, event), +        on.call(player, player.media, 'waiting canplay seeked playing', event => +            ui.checkLoading.call(player, event),          );          // If autoplay, then load advertisement if required          // TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows -        on.call(this.player, this.player.media, 'playing', () => { -            if (!this.player.ads) { +        on.call(player, player.media, 'playing', () => { +            if (!player.ads) {                  return;              }              // If ads are enabled, wait for them first -            if (this.player.ads.enabled && !this.player.ads.initialized) { +            if (player.ads.enabled && !player.ads.initialized) {                  // Wait for manager response -                this.player.ads.managerPromise -                    .then(() => this.player.ads.play()) -                    .catch(() => this.player.play()); +                player.ads.managerPromise +                    .then(() => player.ads.play()) +                    .catch(() => player.play());              }          });          // Click video          if ( -            this.player.supported.ui && -            this.player.config.clickToPlay && -            !this.player.isAudio +            player.supported.ui && +            player.config.clickToPlay && +            !player.isAudio          ) {              // Re-fetch the wrapper              const wrapper = getElement.call( -                this.player, -                `.${this.player.config.classNames.video}`, +                player, +                `.${player.config.classNames.video}`,              );              // Bail if there's no wrapper (this should never happen) @@ -471,32 +467,49 @@ class Listeners {              }              // On click play, pause ore restart -            on.call(this.player, wrapper, 'click', () => { -                // Touch devices will just show controls (if we're hiding controls) -                if ( -                    this.player.config.hideControls && -                    this.player.touch && -                    !this.player.paused -                ) { -                    return; -                } +            on.call( +                player, +                player.elements.container, +                'click touchstart', +                event => { +                    const targets = [player.elements.container, wrapper]; + +                    // Ignore if click if not container or in video wrapper +                    if ( +                        !targets.includes(event.target) && +                        !wrapper.contains(event.target) +                    ) { +                        return; +                    } -                if (this.player.paused) { -                    this.player.play(); -                } else if (this.player.ended) { -                    this.player.restart(); -                    this.player.play(); -                } else { -                    this.player.pause(); -                } -            }); +                    // First touch on touch devices will just show controls (if we're hiding controls) +                    // If controls are shown then it'll toggle like a pointer device +                    if ( +                        player.config.hideControls && +                        player.touch && +                        hasClass( +                            player.elements.container, +                            player.config.classNames.hideControls, +                        ) +                    ) { +                        return; +                    } + +                    if (player.ended) { +                        player.restart(); +                        player.play(); +                    } else { +                        player.togglePlay(); +                    } +                }, +            );          }          // Disable right click -        if (this.player.supported.ui && this.player.config.disableContextMenu) { +        if (player.supported.ui && player.config.disableContextMenu) {              on.call( -                this.player, -                this.player.elements.wrapper, +                player, +                player.elements.wrapper,                  'contextmenu',                  event => {                      event.preventDefault(); @@ -506,34 +519,34 @@ class Listeners {          }          // Volume change -        on.call(this.player, this.player.media, 'volumechange', () => { +        on.call(player, player.media, 'volumechange', () => {              // Save to storage -            this.player.storage.set({ -                volume: this.player.volume, -                muted: this.player.muted, +            player.storage.set({ +                volume: player.volume, +                muted: player.muted,              });          });          // Speed change -        on.call(this.player, this.player.media, 'ratechange', () => { +        on.call(player, player.media, 'ratechange', () => {              // Update UI -            controls.updateSetting.call(this.player, 'speed'); +            controls.updateSetting.call(player, 'speed');              // Save to storage -            this.player.storage.set({ speed: this.player.speed }); +            player.storage.set({ speed: player.speed });          });          // Quality request -        on.call(this.player, this.player.media, 'qualityrequested', event => { +        on.call(player, player.media, 'qualityrequested', event => {              // Save to storage -            this.player.storage.set({ quality: event.detail.quality }); +            player.storage.set({ quality: event.detail.quality });          });          // Quality change -        on.call(this.player, this.player.media, 'qualitychange', event => { +        on.call(player, player.media, 'qualitychange', event => {              // Update UI              controls.updateSetting.call( -                this.player, +                player,                  'quality',                  null,                  event.detail.quality, @@ -542,21 +555,21 @@ class Listeners {          // Proxy events to container          // Bubble up key events for Edge -        const proxyEvents = this.player.config.events +        const proxyEvents = player.config.events              .concat(['keyup', 'keydown'])              .join(' '); -        on.call(this.player, this.player.media, proxyEvents, event => { +        on.call(player, player.media, proxyEvents, event => {              let { detail = {} } = event;              // Get error details from media              if (event.type === 'error') { -                detail = this.player.media.error; +                detail = player.media.error;              }              triggerEvent.call( -                this.player, -                this.player.elements.container, +                player, +                player.elements.container,                  event.type,                  true,                  detail, @@ -566,28 +579,30 @@ class Listeners {      // Run default and custom handlers      proxy(event, defaultHandler, customHandlerKey) { -        const customHandler = this.player.config.listeners[customHandlerKey]; +        const { player } = this; +        const customHandler = player.config.listeners[customHandlerKey];          const hasCustomHandler = is.function(customHandler);          let returned = true;          // Execute custom handler          if (hasCustomHandler) { -            returned = customHandler.call(this.player, event); +            returned = customHandler.call(player, event);          }          // Only call default handler if not prevented in custom handler          if (returned && is.function(defaultHandler)) { -            defaultHandler.call(this.player, event); +            defaultHandler.call(player, event);          }      }      // Trigger custom and default handlers      bind(element, type, defaultHandler, customHandlerKey, passive = true) { -        const customHandler = this.player.config.listeners[customHandlerKey]; +        const { player } = this; +        const customHandler = player.config.listeners[customHandlerKey];          const hasCustomHandler = is.function(customHandler);          on.call( -            this.player, +            player,              element,              type,              event => this.proxy(event, defaultHandler, customHandlerKey), @@ -597,91 +612,93 @@ class Listeners {      // Listen for control events      controls() { +        const { player } = this; +          // IE doesn't support input event, so we fallback to change          const inputEvent = browser.isIE ? 'change' : 'input';          // Play/pause toggle -        if (this.player.elements.buttons.play) { -            Array.from(this.player.elements.buttons.play).forEach(button => { -                this.bind(button, 'click', this.player.togglePlay, 'play'); +        if (player.elements.buttons.play) { +            Array.from(player.elements.buttons.play).forEach(button => { +                this.bind(button, 'click', player.togglePlay, 'play');              });          }          // Pause          this.bind( -            this.player.elements.buttons.restart, +            player.elements.buttons.restart,              'click', -            this.player.restart, +            player.restart,              'restart',          );          // Rewind          this.bind( -            this.player.elements.buttons.rewind, +            player.elements.buttons.rewind,              'click', -            this.player.rewind, +            player.rewind,              'rewind',          );          // Rewind          this.bind( -            this.player.elements.buttons.fastForward, +            player.elements.buttons.fastForward,              'click', -            this.player.forward, +            player.forward,              'fastForward',          );          // Mute toggle          this.bind( -            this.player.elements.buttons.mute, +            player.elements.buttons.mute,              'click',              () => { -                this.player.muted = !this.player.muted; +                player.muted = !player.muted;              },              'mute',          );          // Captions toggle -        this.bind(this.player.elements.buttons.captions, 'click', () => -            this.player.toggleCaptions(), +        this.bind(player.elements.buttons.captions, 'click', () => +            player.toggleCaptions(),          );          // Fullscreen toggle          this.bind( -            this.player.elements.buttons.fullscreen, +            player.elements.buttons.fullscreen,              'click',              () => { -                this.player.fullscreen.toggle(); +                player.fullscreen.toggle();              },              'fullscreen',          );          // Picture-in-Picture          this.bind( -            this.player.elements.buttons.pip, +            player.elements.buttons.pip,              'click',              () => { -                this.player.pip = 'toggle'; +                player.pip = 'toggle';              },              'pip',          );          // Airplay          this.bind( -            this.player.elements.buttons.airplay, +            player.elements.buttons.airplay,              'click', -            this.player.airplay, +            player.airplay,              'airplay',          );          // Settings menu - click toggle -        this.bind(this.player.elements.buttons.settings, 'click', event => { -            controls.toggleMenu.call(this.player, event); +        this.bind(player.elements.buttons.settings, 'click', event => { +            controls.toggleMenu.call(player, event);          });          // Settings menu - keyboard toggle          this.bind( -            this.player.elements.buttons.settings, +            player.elements.buttons.settings,              'keydown',              event => {                  // We only care about space @@ -696,33 +713,28 @@ class Listeners {                  event.stopPropagation();                  // Toggle menu -                controls.toggleMenu.call(this.player, event); +                controls.toggleMenu.call(player, event);              },              null,              false,          );          // Set range input alternative "value", which matches the tooltip time (#954) -        this.bind( -            this.player.elements.inputs.seek, -            'mousedown mousemove', -            event => { -                const clientRect = this.player.elements.progress.getBoundingClientRect(); -                const percent = -                    100 / clientRect.width * (event.pageX - clientRect.left); -                event.currentTarget.setAttribute('seek-value', percent); -            }, -        ); +        this.bind(player.elements.inputs.seek, 'mousedown mousemove', event => { +            const rect = player.elements.progress.getBoundingClientRect(); +            const percent = 100 / rect.width * (event.pageX - rect.left); +            event.currentTarget.setAttribute('seek-value', percent); +        });          // Pause while seeking          this.bind( -            this.player.elements.inputs.seek, +            player.elements.inputs.seek,              'mousedown mouseup keydown keyup touchstart touchend',              event => {                  const seek = event.currentTarget; -                  const code = event.keyCode ? event.keyCode : event.which;                  const eventType = event.type; +                const attribute = 'play-on-seeked';                  if (                      (eventType === 'keydown' || eventType === 'keyup') && @@ -731,7 +743,7 @@ class Listeners {                      return;                  }                  // Was playing before? -                const play = seek.hasAttribute('play-on-seeked'); +                const play = seek.hasAttribute(attribute);                  // Done seeking                  const done = ['mouseup', 'touchend', 'keyup'].includes( @@ -740,18 +752,18 @@ class Listeners {                  // If we're done seeking and it was playing, resume playback                  if (play && done) { -                    seek.removeAttribute('play-on-seeked'); -                    this.player.play(); -                } else if (!done && this.player.playing) { -                    seek.setAttribute('play-on-seeked', ''); -                    this.player.pause(); +                    seek.removeAttribute(attribute); +                    player.play(); +                } else if (!done && player.playing) { +                    seek.setAttribute(attribute, ''); +                    player.pause();                  }              },          );          // Seek          this.bind( -            this.player.elements.inputs.seek, +            player.elements.inputs.seek,              inputEvent,              event => {                  const seek = event.currentTarget; @@ -765,8 +777,13 @@ class Listeners {                  seek.removeAttribute('seek-value'); -                this.player.currentTime = -                    seekTo / seek.max * this.player.duration; +                // Super weird iOS bug where after you interact with an <input type="range">, +                // it takes over further interactions on the page. This is a hack +                if (browser.isIos) { +                    repaint(seek); +                } + +                player.currentTime = seekTo / seek.max * player.duration;              },              'seek',          ); @@ -774,65 +791,61 @@ class Listeners {          // Current time invert          // Only if one time element is used for both currentTime and duration          if ( -            this.player.config.toggleInvert && -            !is.element(this.player.elements.display.duration) +            player.config.toggleInvert && +            !is.element(player.elements.display.duration)          ) { -            this.bind(this.player.elements.display.currentTime, 'click', () => { +            this.bind(player.elements.display.currentTime, 'click', () => {                  // Do nothing if we're at the start -                if (this.player.currentTime === 0) { +                if (player.currentTime === 0) {                      return;                  } -                this.player.config.invertTime = !this.player.config.invertTime; +                player.config.invertTime = !player.config.invertTime; -                controls.timeUpdate.call(this.player); +                controls.timeUpdate.call(player);              });          }          // Volume          this.bind( -            this.player.elements.inputs.volume, +            player.elements.inputs.volume,              inputEvent,              event => { -                this.player.volume = event.target.value; +                player.volume = event.target.value;              },              'volume',          );          // Polyfill for lower fill in <input type="range"> for webkit          if (browser.isWebkit) { -            Array.from( -                getElements.call(this.player, 'input[type="range"]'), -            ).forEach(element => { -                this.bind(element, 'input', event => -                    controls.updateRangeFill.call(this.player, event.target), -                ); -            }); +            Array.from(getElements.call(player, 'input[type="range"]')).forEach( +                element => { +                    this.bind(element, 'input', event => +                        controls.updateRangeFill.call(player, event.target), +                    ); +                }, +            );          }          // Seek tooltip          this.bind( -            this.player.elements.progress, +            player.elements.progress,              'mouseenter mouseleave mousemove', -            event => controls.updateSeekTooltip.call(this.player, event), +            event => controls.updateSeekTooltip.call(player, event),          );          // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) -        this.bind( -            this.player.elements.controls, -            'mouseenter mouseleave', -            event => { -                this.player.elements.controls.hover = -                    !this.player.touch && event.type === 'mouseenter'; -            }, -        ); +        this.bind(player.elements.controls, 'mouseenter mouseleave', event => { +            player.elements.controls.hover = +                !player.touch && event.type === 'mouseenter'; +        });          // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)          this.bind( -            this.player.elements.controls, +            player.elements.controls,              'mousedown mouseup touchstart touchend touchcancel',              event => { -                this.player.elements.controls.pressed = [ +                player.elements.controls.pressed = [                      'mousedown',                      'touchstart',                  ].includes(event.type); @@ -840,21 +853,22 @@ class Listeners {          );          // Focus in/out on controls -        this.bind(this.player.elements.controls, 'focusin focusout', event => { -            const { config, elements, timers } = this.player; +        this.bind(player.elements.controls, 'focusin focusout', event => { +            const { config, elements, timers } = player; +            const isFocusIn = event.type === 'focusin';              // Skip transition to prevent focus from scrolling the parent element              toggleClass(                  elements.controls,                  config.classNames.noTransition, -                event.type === 'focusin', +                isFocusIn,              );              // Toggle -            ui.toggleControls.call(this.player, event.type === 'focusin'); +            ui.toggleControls.call(player, isFocusIn);              // If focusin, hide again after delay -            if (event.type === 'focusin') { +            if (isFocusIn) {                  // Restore transition                  setTimeout(() => {                      toggleClass( @@ -872,7 +886,7 @@ class Listeners {                  // Hide                  timers.controls = setTimeout( -                    () => ui.toggleControls.call(this.player, false), +                    () => ui.toggleControls.call(player, false),                      delay,                  );              } @@ -880,7 +894,7 @@ class Listeners {          // Mouse wheel for volume          this.bind( -            this.player.elements.inputs.volume, +            player.elements.inputs.volume,              'wheel',              event => {                  // Detect "natural" scroll - suppored on OS X Safari only @@ -896,10 +910,10 @@ class Listeners {                  const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y);                  // Change the volume by 2% -                this.player.increaseVolume(direction / 50); +                player.increaseVolume(direction / 50);                  // Don't break page scrolling at max and min -                const { volume } = this.player.media; +                const { volume } = player.media;                  if (                      (direction === 1 && volume < 1) ||                      (direction === -1 && volume > 0) diff --git a/src/js/plyr.js b/src/js/plyr.js index ab1f7866..b4c54ca8 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -52,7 +52,11 @@ class Plyr {          }          // jQuery, NodeList or Array passed, use first element -        if ((window.jQuery && this.media instanceof jQuery) || is.nodeList(this.media) || is.array(this.media)) { +        if ( +            (window.jQuery && this.media instanceof jQuery) || +            is.nodeList(this.media) || +            is.array(this.media) +        ) {              // eslint-disable-next-line              this.media = this.media[0];          } @@ -65,7 +69,9 @@ class Plyr {              options || {},              (() => {                  try { -                    return JSON.parse(this.media.getAttribute('data-plyr-config')); +                    return JSON.parse( +                        this.media.getAttribute('data-plyr-config'), +                    );                  } catch (e) {                      return {};                  } @@ -185,21 +191,30 @@ class Plyr {                          // TODO: replace fullscreen.iosNative with this playsinline config option                          // YouTube requires the playsinline in the URL                          if (this.isYouTube) { -                            this.config.playsinline = truthy.includes(url.searchParams.get('playsinline')); +                            this.config.playsinline = truthy.includes( +                                url.searchParams.get('playsinline'), +                            );                          } else {                              this.config.playsinline = true;                          }                      }                  } else {                      // <div> with attributes -                    this.provider = this.media.getAttribute(this.config.attributes.embed.provider); +                    this.provider = this.media.getAttribute( +                        this.config.attributes.embed.provider, +                    );                      // Remove attribute -                    this.media.removeAttribute(this.config.attributes.embed.provider); +                    this.media.removeAttribute( +                        this.config.attributes.embed.provider, +                    );                  }                  // Unsupported or missing provider -                if (is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { +                if ( +                    is.empty(this.provider) || +                    !Object.keys(providers).includes(this.provider) +                ) {                      this.debug.error('Setup failed: Invalid provider');                      return;                  } @@ -221,7 +236,10 @@ class Plyr {                  if (this.media.hasAttribute('autoplay')) {                      this.config.autoplay = true;                  } -                if (this.media.hasAttribute('playsinline')) { +                if ( +                    this.media.hasAttribute('playsinline') || +                    this.media.hasAttribute('webkit-playsinline') +                ) {                      this.config.playsinline = true;                  }                  if (this.media.hasAttribute('muted')) { @@ -239,7 +257,11 @@ class Plyr {          }          // Check for support again but with type -        this.supported = support.check(this.type, this.provider, this.config.playsinline); +        this.supported = support.check( +            this.type, +            this.provider, +            this.config.playsinline, +        );          // If no support for even API, bail          if (!this.supported.api) { @@ -272,9 +294,14 @@ class Plyr {          // Listen for events if debugging          if (this.config.debug) { -            on.call(this, this.elements.container, this.config.events.join(' '), event => { -                this.debug.log(`event: ${event.type}`); -            }); +            on.call( +                this, +                this.elements.container, +                this.config.events.join(' '), +                event => { +                    this.debug.log(`event: ${event.type}`); +                }, +            );          }          // Setup interface @@ -293,7 +320,9 @@ class Plyr {          this.fullscreen = new Fullscreen(this);          // Setup ads if provided -        this.ads = new Ads(this); +        if (this.config.ads.enabled) { +            this.ads = new Ads(this); +        }          // Autoplay if required          if (this.config.autoplay) { @@ -422,7 +451,9 @@ class Plyr {       * @param {number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime       */      rewind(seekTime) { -        this.currentTime = this.currentTime - (is.number(seekTime) ? seekTime : this.config.seekTime); +        this.currentTime = +            this.currentTime - +            (is.number(seekTime) ? seekTime : this.config.seekTime);      }      /** @@ -430,7 +461,9 @@ class Plyr {       * @param {number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime       */      forward(seekTime) { -        this.currentTime = this.currentTime + (is.number(seekTime) ? seekTime : this.config.seekTime); +        this.currentTime = +            this.currentTime + +            (is.number(seekTime) ? seekTime : this.config.seekTime);      }      /** @@ -447,7 +480,9 @@ class Plyr {          const inputIsValid = is.number(input) && input > 0;          // Set -        this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0; +        this.media.currentTime = inputIsValid +            ? Math.min(input, this.duration) +            : 0;          // Logging          this.debug.log(`Seeking to ${this.currentTime} seconds`); @@ -497,7 +532,10 @@ class Plyr {          // Media duration can be NaN or Infinity before the media has loaded          const realDuration = (this.media || {}).duration; -        const duration = !is.number(realDuration) || realDuration === Infinity ? 0 : realDuration; +        const duration = +            !is.number(realDuration) || realDuration === Infinity +                ? 0 +                : realDuration;          // If config duration is funky, use regular duration          return fauxDuration || duration; @@ -691,12 +729,16 @@ class Plyr {          if (!options.includes(quality)) {              const value = closest(options, quality); -            this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`); +            this.debug.warn( +                `Unsupported quality option: ${quality}, using ${value} instead`, +            );              quality = value;          }          // Trigger request event -        triggerEvent.call(this, this.media, 'qualityrequested', false, { quality }); +        triggerEvent.call(this, this.media, 'qualityrequested', false, { +            quality, +        });          // Update config          config.selected = quality; @@ -888,7 +930,9 @@ class Plyr {          const toggle = is.boolean(input) ? input : this.pip === states.inline;          // Toggle based on current state -        this.media.webkitSetPresentationMode(toggle ? states.pip : states.inline); +        this.media.webkitSetPresentationMode( +            toggle ? states.pip : states.inline, +        );      }      /** @@ -921,25 +965,39 @@ class Plyr {          // Don't toggle if missing UI support or if it's audio          if (this.supported.ui && !this.isAudio) {              // Get state before change -            const isHidden = hasClass(this.elements.container, this.config.classNames.hideControls); +            const isHidden = hasClass( +                this.elements.container, +                this.config.classNames.hideControls, +            );              // Negate the argument if not undefined since adding the class to hides the controls              const force = typeof toggle === 'undefined' ? undefined : !toggle;              // Apply and get updated state -            const hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force); +            const hiding = toggleClass( +                this.elements.container, +                this.config.classNames.hideControls, +                force, +            );              // Close menu -            if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) { +            if ( +                hiding && +                this.config.controls.includes('settings') && +                !is.empty(this.config.settings) +            ) {                  controls.toggleMenu.call(this, false);              } +              // Trigger event on change              if (hiding !== isHidden) {                  const eventName = hiding ? 'controlshidden' : 'controlsshown';                  triggerEvent.call(this, this.media, eventName);              } +              return !hiding;          } +          return false;      } @@ -1017,7 +1075,12 @@ class Plyr {                  replaceElement(this.elements.original, this.elements.container);                  // Event -                triggerEvent.call(this, this.elements.original, 'destroyed', true); +                triggerEvent.call( +                    this, +                    this.elements.original, +                    'destroyed', +                    true, +                );                  // Callback                  if (is.function(callback)) { diff --git a/src/js/support.js b/src/js/support.js index 6395293f..4681f5c7 100644 --- a/src/js/support.js +++ b/src/js/support.js @@ -25,9 +25,13 @@ const support = {      // Check for support      // Basic functionality vs full UI      check(type, provider, playsinline) { -        const canPlayInline = browser.isIPhone && playsinline && support.playsinline; +        const canPlayInline = +            browser.isIPhone && playsinline && support.playsinline;          const api = support[type] || provider !== 'html5'; -        const ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline); +        const ui = +            api && +            support.rangeInput && +            (type !== 'video' || !browser.isIPhone || canPlayInline);          return {              api, @@ -37,7 +41,9 @@ const support = {      // Picture-in-picture support      // Safari only currently -    pip: (() => !browser.isIPhone && is.function(createElement('video').webkitSetPresentationMode))(), +    pip: (() => +        !browser.isIPhone && +        is.function(createElement('video').webkitSetPresentationMode))(),      // Airplay support      // Safari only currently @@ -69,7 +75,9 @@ const support = {          }          try { -            return Boolean(type && this.media.canPlayType(type).replace(/no/, '')); +            return Boolean( +                type && this.media.canPlayType(type).replace(/no/, ''), +            );          } catch (err) {              return false;          } @@ -94,7 +102,9 @@ const support = {      // Reduced motion iOS & MacOS setting      // https://webkit.org/blog/7551/responsive-design-for-motion/ -    reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches, +    reducedMotion: +        'matchMedia' in window && +        window.matchMedia('(prefers-reduced-motion)').matches,  };  export default support; diff --git a/src/js/ui.js b/src/js/ui.js index 34fe7e82..8c61d805 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -14,8 +14,16 @@ import loadImage from './utils/loadImage';  const ui = {      addStyleHook() { -        toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true); -        toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui); +        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 @@ -35,7 +43,9 @@ const ui = {          // Don't setup interface if no support          if (!this.supported.ui) { -            this.debug.warn(`Basic support only for ${this.provider} ${this.type}`); +            this.debug.warn( +                `Basic support only for ${this.provider} ${this.type}`, +            );              // Restore native controls              ui.toggleNativeControls.call(this, true); @@ -93,13 +103,25 @@ const ui = {          );          // Check for airplay support -        toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5); +        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); +        toggleClass( +            this.elements.container, +            this.config.classNames.isIos, +            browser.isIos, +        );          // Add touch class -        toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch); +        toggleClass( +            this.elements.container, +            this.config.classNames.isTouch, +            this.touch, +        );          // Ready for API calls          this.ready = true; @@ -149,7 +171,9 @@ const ui = {              }              // Default to media type -            const title = !is.empty(this.config.title) ? this.config.title : 'video'; +            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)); @@ -158,7 +182,11 @@ const ui = {      // Toggle poster      togglePoster(enable) { -        toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable); +        toggleClass( +            this.elements.container, +            this.config.classNames.posterEnabled, +            enable, +        );      },      // Set the poster image (async) @@ -189,7 +217,9 @@ const ui = {                  .then(() => {                      // Prevent race conditions                      if (poster !== this.poster) { -                        throw new Error('setPoster cancelled by later call to setPoster'); +                        throw new Error( +                            'setPoster cancelled by later call to setPoster', +                        );                      }                  })                  .then(() => { @@ -207,9 +237,21 @@ const ui = {      // 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); +        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 => { @@ -235,7 +277,11 @@ const ui = {          // 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); +            toggleClass( +                this.elements.container, +                this.config.classNames.loading, +                this.loading, +            );              // Update controls visibility              ui.toggleControls.call(this); @@ -248,7 +294,15 @@ const ui = {          if (controls && this.config.hideControls) {              // Show controls if force, loading, paused, or button interaction, otherwise hide -            this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover)); +            this.toggleControls( +                Boolean( +                    force || +                        this.loading || +                        this.paused || +                        controls.pressed || +                        controls.hover, +                ), +            );          }      },  }; diff --git a/src/js/utils/animation.js b/src/js/utils/animation.js index 95e39f03..49bc0b8c 100644 --- a/src/js/utils/animation.js +++ b/src/js/utils/animation.js @@ -15,7 +15,9 @@ export const transitionEndEvent = (() => {          transition: 'transitionend',      }; -    const type = Object.keys(events).find(event => element.style[event] !== undefined); +    const type = Object.keys(events).find( +        event => element.style[event] !== undefined, +    );      return is.string(type) ? events[type] : false;  })(); @@ -23,8 +25,12 @@ export const transitionEndEvent = (() => {  // Force repaint of element  export function repaint(element) {      setTimeout(() => { -        toggleHidden(element, true); -        element.offsetHeight; // eslint-disable-line -        toggleHidden(element, false); +        try { +            toggleHidden(element, true); +            element.offsetHeight; // eslint-disable-line +            toggleHidden(element, false); +        } catch (e) { +            // Do nothing +        }      }, 0);  } diff --git a/src/sass/components/poster.scss b/src/sass/components/poster.scss index 9bf7398d..15e87257 100644 --- a/src/sass/components/poster.scss +++ b/src/sass/components/poster.scss @@ -7,17 +7,16 @@      background-position: 50% 50%;      background-repeat: no-repeat;      background-size: contain; +    display: none;      height: 100%;      left: 0; -    opacity: 0; +    pointer-events: none;      position: absolute;      top: 0; -    transition: opacity 0.3s ease;      width: 100%;      z-index: 1;  }  .plyr--stopped.plyr__poster-enabled .plyr__poster { -    opacity: 1; -    pointer-events: none; +    display: block;  } diff --git a/src/sass/components/progress.scss b/src/sass/components/progress.scss index eddd32ab..16992808 100644 --- a/src/sass/components/progress.scss +++ b/src/sass/components/progress.scss @@ -3,7 +3,6 @@  // --------------------------------------------------------------  .plyr__progress { -    display: flex;      flex: 1;      left: $plyr-range-thumb-height / 2;      margin-right: $plyr-range-thumb-height; diff --git a/src/sass/lib/mixins.scss b/src/sass/lib/mixins.scss index 2aec702c..e6afe046 100644 --- a/src/sass/lib/mixins.scss +++ b/src/sass/lib/mixins.scss @@ -36,7 +36,6 @@      border: 0;      border-radius: 100%;      box-shadow: $plyr-range-thumb-shadow; -    box-sizing: border-box;      height: $plyr-range-thumb-height;      position: relative;      transition: all 0.2s ease; | 
