diff options
| -rw-r--r-- | readme.md | 2 | ||||
| -rw-r--r-- | src/js/defaults.js | 1 | ||||
| -rw-r--r-- | src/js/listeners.js | 95 | ||||
| -rw-r--r-- | src/js/plyr.js | 123 | ||||
| -rw-r--r-- | src/js/ui.js | 33 | ||||
| -rw-r--r-- | src/js/utils.js | 14 | ||||
| -rw-r--r-- | src/sass/plyr.scss | 1 | ||||
| -rw-r--r-- | src/sass/states/error.scss | 25 | 
8 files changed, 104 insertions, 190 deletions
| @@ -361,7 +361,7 @@ player.fullscreen.enter(); // Enter fullscreen  | `fullscreen.exit()`      | -                | Exit fullscreen.                                                                                           |  | `fullscreen.toggle()`    | -                | Toggle fullscreen.                                                                                         |  | `airplay()`              | -                | Trigger the airplay dialog on supported devices.                                                           | -| `toggleControls(toggle)` | Boolean          | Toggle the controls based on the specified boolean.                                                        | +| `toggleControls(toggle)` | Boolean          | Toggle the controls (video only). Takes optional truthy value to force it on/off.                                                        |  | `on(event, function)`    | String, Function | Add an event listener for the specified event.                                                             |  | `off(event, function)`   | String, Function | Remove an event listener for the specified event.                                                          |  | `supports(type)`         | String           | Check support for a mime type.                                                                             | diff --git a/src/js/defaults.js b/src/js/defaults.js index f160b1aa..da089efc 100644 --- a/src/js/defaults.js +++ b/src/js/defaults.js @@ -338,7 +338,6 @@ const defaults = {          paused: 'plyr--paused',          stopped: 'plyr--stopped',          loading: 'plyr--loading', -        error: 'plyr--has-error',          hover: 'plyr--hover',          tooltip: 'plyr__tooltip',          cues: 'plyr__cues', diff --git a/src/js/listeners.js b/src/js/listeners.js index 167bee3c..5dd9d93f 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -238,13 +238,36 @@ class Listeners {              }, 0);          }); -        // Toggle controls visibility based on mouse movement -        if (this.player.config.hideControls) { -            // Toggle controls on mouse events and entering fullscreen -            utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => { -                this.player.toggleControls(event); -            }); -        } +        // Toggle controls on mouse events and entering fullscreen +        utils.on(this.player.elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', event => { +            const { controls } = this.player.elements; + +            // Remove button states for fullscreen +            if (event.type === 'enterfullscreen') { +                controls.pressed = false; +                controls.hover = false; +            } + +            // Show, then hide after a timeout unless another control event occurs +            const show = [ +                'touchstart', +                'touchmove', +                'mousemove', +            ].includes(event.type); + +            let delay = 0; + +            if (show) { +                ui.toggleControls.call(this.player, true); +                // Use longer timeout for touch devices +                delay = this.player.touch ? 3000 : 2000; +            } + +            // Clear timer +            clearTimeout(this.player.timers.controls); +            // Timer to prevent flicker when seeking +            this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay); +        });      }      // Listen for media events @@ -283,9 +306,6 @@ class Listeners {          // Loading state          utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event)); -        // Check if media failed to load -        // utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.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          utils.on(this.player.media, 'playing', () => { @@ -574,26 +594,45 @@ class Listeners {          // Seek tooltip          on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event)); -        // Toggle controls visibility based on mouse movement -        if (this.player.config.hideControls) { -            // Watch for cursor over controls so they don't hide when trying to interact -            on(this.player.elements.controls, 'mouseenter mouseleave', event => { -                this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter'; -            }); +        // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting) +        on(this.player.elements.controls, 'mouseenter mouseleave', event => { +            this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter'; +        }); -            // Watch for cursor over controls so they don't hide when trying to interact -            on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => { -                this.player.elements.controls.pressed = [ -                    'mousedown', -                    'touchstart', -                ].includes(event.type); -            }); +        // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting) +        on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => { +            this.player.elements.controls.pressed = [ +                'mousedown', +                'touchstart', +            ].includes(event.type); +        }); -            // Focus in/out on controls -            on(this.player.elements.controls, 'focusin focusout', event => { -                this.player.toggleControls(event); -            }); -        } +        // Focus in/out on controls +        on(this.player.elements.controls, 'focusin focusout', event => { +            const { config, elements, timers } = this.player; + +            // Skip transition to prevent focus from scrolling the parent element +            utils.toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin'); + +            // Toggle +            ui.toggleControls.call(this.player, event.type === 'focusin'); + +            // If focusin, hide again after delay +            if (event.type === 'focusin') { +                // Restore transition +                setTimeout(() => { +                    utils.toggleClass(elements.controls, config.classNames.noTransition, false); +                }, 0); + +                // Delay a little more for keyboard users +                const delay = this.touch ? 3000 : 4000; + +                // Clear timer +                clearTimeout(timers.controls); +                // Hide +                timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay); +            } +        });          // Mouse wheel for volume          on( diff --git a/src/js/plyr.js b/src/js/plyr.js index bed09827..ffd2a1e3 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -971,119 +971,32 @@ class Plyr {      /**       * Toggle the player controls -     * @param {boolean} toggle - Whether to show the controls +     * @param {boolean} [toggle] - Whether to show the controls       */      toggleControls(toggle) { -        // We need controls of course... -        if (!utils.is.element(this.elements.controls)) { -            return; -        } - -        // Don't hide if no UI support or it's audio -        if (!this.supported.ui || this.isAudio) { -            return; -        } - -        let delay = 0; -        let show = toggle; -        let isEnterFullscreen = false; - -        // Get toggle state if not set -        if (!utils.is.boolean(toggle)) { -            if (utils.is.event(toggle)) { -                // Is the enter fullscreen event -                isEnterFullscreen = toggle.type === 'enterfullscreen'; - -                // Events that show the controls -                const showEvents = [ -                    'touchstart', -                    'touchmove', -                    'mouseenter', -                    'mousemove', -                    'focusin', -                ]; - -                // Events that delay hiding -                const delayEvents = [ -                    'touchmove', -                    'touchend', -                    'mousemove', -                ]; - -                // Whether to show controls -                show = showEvents.includes(toggle.type); - -                // Delay hiding on move events -                if (delayEvents.includes(toggle.type)) { -                    delay = 2000; -                } - -                // Delay a little more for keyboard users -                if (!this.touch && toggle.type === 'focusin') { -                    delay = 3000; -                    utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true); -                } -            } else { -                show = utils.hasClass(this.elements.container, this.config.classNames.hideControls); -            } -        } +        // Don't toggle if missing UI support or if it's audio +        if (this.supported.ui && !this.isAudio) { +            // Get state before change +            const isHidden = utils.hasClass(this.elements.container, this.config.classNames.hideControls); -        // Clear timer on every call -        clearTimeout(this.timers.controls); +            // Negate the argument if not undefined since adding the class to hides the controls +            const force = typeof toggle === 'undefined' ? undefined : !toggle; -        // If the mouse is not over the controls, set a timeout to hide them -        if (show || this.paused || this.loading) { -            // Check if controls toggled -            const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false); - -            // Trigger event -            if (toggled) { -                utils.dispatchEvent.call(this, this.media, 'controlsshown'); -            } +            // Apply and get updated state +            const hiding = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, force); -            // Always show controls when paused or if touch -            if (this.paused || this.loading) { -                return; +            // Close menu +            if (hiding && this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { +                controls.toggleMenu.call(this, false);              } - -            // Delay for hiding on touch -            if (this.touch) { -                delay = 3000; +            // Trigger event on change +            if (hiding !== isHidden) { +                const eventName = hiding ? 'controlshidden' : 'controlsshown'; +                utils.dispatchEvent.call(this, this.media, eventName);              } +            return !hiding;          } - -        // If toggle is false or if we're playing (regardless of toggle), -        // then set the timer to hide the controls -        if (!show || this.playing) { -            this.timers.controls = setTimeout(() => { -                // We need controls of course... -                if (!utils.is.element(this.elements.controls)) { -                    return; -                } - -                // If the mouse is over the controls (and not entering fullscreen), bail -                if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) { -                    return; -                } - -                // Restore transition behaviour -                if (!utils.hasClass(this.elements.container, this.config.classNames.hideControls)) { -                    utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, false); -                } - -                // Set hideControls class -                const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, this.config.hideControls); - -                // Trigger event and close menu -                if (toggled) { -                    utils.dispatchEvent.call(this, this.media, 'controlshidden'); - -                    if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { -                        controls.toggleMenu.call(this, false); -                    } -                } -            }, delay); -        } +        return false;      }      /** diff --git a/src/js/ui.js b/src/js/ui.js index 8f3f6a77..039144a5 100644 --- a/src/js/ui.js +++ b/src/js/ui.js @@ -173,7 +173,7 @@ const ui = {          }          // Toggle controls -        this.toggleControls(!this.playing); +        ui.toggleControls.call(this);      },      // Check if media is loading @@ -188,35 +188,22 @@ const ui = {          // Timer to prevent flicker when seeking          this.timers.loading = setTimeout(() => { -            // Toggle container class hook +            // Update progress bar loading class state              utils.toggleClass(this.elements.container, this.config.classNames.loading, this.loading); -            // Show controls if loading, hide if done -            this.toggleControls(this.loading); +            // Update controls visibility +            ui.toggleControls.call(this);          }, this.loading ? 250 : 0);      }, -    // Check if media failed to load -    checkFailed() { -        // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/networkState -        this.failed = this.media.networkState === 3; +    // Toggle controls based on state and `force` argument +    toggleControls(force) { +        const { controls } = this.elements; -        if (this.failed) { -            utils.toggleClass(this.elements.container, this.config.classNames.loading, false); -            utils.toggleClass(this.elements.container, this.config.classNames.error, true); +        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));          } - -        // Clear timer -        clearTimeout(this.timers.failed); - -        // Timer to prevent flicker when seeking -        this.timers.loading = setTimeout(() => { -            // Toggle container class hook -            utils.toggleClass(this.elements.container, this.config.classNames.loading, this.loading); - -            // Show controls if loading, hide if done -            this.toggleControls(this.loading); -        }, this.loading ? 250 : 0);      },  }; diff --git a/src/js/utils.js b/src/js/utils.js index a58d8555..13e26655 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -393,14 +393,16 @@ const utils = {          }      }, -    // Toggle class on an element -    toggleClass(element, className, toggle) { +    // Mirror Element.classList.toggle, with IE compatibility for "force" argument +    toggleClass(element, className, force) {          if (utils.is.element(element)) { -            const contains = element.classList.contains(className); - -            element.classList[toggle ? 'add' : 'remove'](className); +            let method = 'toggle'; +            if (typeof force !== 'undefined') { +                method = force ? 'add' : 'remove'; +            } -            return (toggle && !contains) || (!toggle && contains); +            element.classList[method](className); +            return element.classList.contains(className);          }          return null; diff --git a/src/sass/plyr.scss b/src/sass/plyr.scss index 65134331..e934cf92 100644 --- a/src/sass/plyr.scss +++ b/src/sass/plyr.scss @@ -39,7 +39,6 @@  @import 'components/video';  @import 'components/volume'; -@import 'states/error';  @import 'states/fullscreen';  @import 'plugins/ads'; diff --git a/src/sass/states/error.scss b/src/sass/states/error.scss deleted file mode 100644 index 64d05c7b..00000000 --- a/src/sass/states/error.scss +++ /dev/null @@ -1,25 +0,0 @@ -// -------------------------------------------------------------- -// Error state -// -------------------------------------------------------------- - -.plyr--has-error { -    pointer-events: none; - -    &::after { -        align-items: center; -        background: rgba(#000, 90%); -        color: #fff; -        content: attr(data-plyr-error); -        display: flex; -        font-size: $plyr-font-size-base; -        height: 100%; -        justify-content: center; -        left: 0; -        position: absolute; -        text-align: center; -        text-shadow: 0 1px 1px rgba(#000, 10%); -        top: 0; -        width: 100%; -        z-index: 10; -    } -} | 
