diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/js/controls.js | 534 | 
1 files changed, 284 insertions, 250 deletions
| diff --git a/src/js/controls.js b/src/js/controls.js index 78d3144f..d836184c 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -172,7 +172,7 @@ const controls = {      // Create a <button>      createButton(buttonType, attr) { -        const attributes = Object.assign({}, attr); +        const attributes = extend({}, attr);          let type = toCamelCase(buttonType);          const props = { @@ -198,8 +198,10 @@ const controls = {          // Set class name          if (Object.keys(attributes).includes('class')) { -            if (!attributes.class.includes(this.config.classNames.control)) { -                attributes.class += ` ${this.config.classNames.control}`; +            if (!attributes.class.split(' ').some(c => c === this.config.classNames.control)) { +                extend(attributes, { +                    class: `${attributes.class} ${this.config.classNames.control}`, +                });              }          } else {              attributes.class = this.config.classNames.control; @@ -377,13 +379,13 @@ const controls = {      },      // Create time display -    createTime(type) { -        const attributes = getAttributesFromSelector(this.config.selectors.display[type]); +    createTime(type, attrs) { +        const attributes = getAttributesFromSelector(this.config.selectors.display[type], attrs);          const container = createElement(              'div',              extend(attributes, { -                class: `${this.config.classNames.display.time} ${attributes.class ? attributes.class : ''}`.trim(), +                class: `${attributes.class ? attributes.class : ''} ${this.config.classNames.display.time} `.trim(),                  'aria-label': i18n.get(type, this.config),              }),              '00:00', @@ -1258,319 +1260,351 @@ const controls = {      },      // Build the default HTML -    // TODO: Set order based on order in the config.controls array?      create(data) { -        // Create the container -        const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper)); +        const { +            bindMenuItemShortcuts, +            createButton, +            createProgress, +            createRange, +            createTime, +            setQualityMenu, +            setSpeedMenu, +            showMenuPanel, +        } = controls; +        this.elements.controls = null; -        // Restart button -        if (this.config.controls.includes('restart')) { -            container.appendChild(controls.createButton.call(this, 'restart')); +        // Larger overlaid play button +        if (this.config.controls.includes('play-large')) { +            this.elements.container.appendChild(createButton.call(this, 'play-large'));          } -        // Rewind button -        if (this.config.controls.includes('rewind')) { -            container.appendChild(controls.createButton.call(this, 'rewind')); -        } +        // Create the container +        const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper)); +        this.elements.controls = container; -        // Play/Pause button -        if (this.config.controls.includes('play')) { -            container.appendChild(controls.createButton.call(this, 'play')); -        } +        // Default item attributes +        const defaultAttributes = { class: 'plyr__controls__item' }; -        // Fast forward button -        if (this.config.controls.includes('fast-forward')) { -            container.appendChild(controls.createButton.call(this, 'fast-forward')); -        } +        // Loop through controls in order +        dedupe(this.config.controls).forEach(control => { +            // Restart button +            if (control === 'restart') { +                container.appendChild(createButton.call(this, 'restart', defaultAttributes)); +            } -        // Progress -        if (this.config.controls.includes('progress')) { -            const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress)); +            // Rewind button +            if (control === 'rewind') { +                container.appendChild(createButton.call(this, 'rewind', defaultAttributes)); +            } -            // Seek range slider -            progress.appendChild( -                controls.createRange.call(this, 'seek', { -                    id: `plyr-seek-${data.id}`, -                }), -            ); +            // Play/Pause button +            if (control === 'play') { +                container.appendChild(createButton.call(this, 'play', defaultAttributes)); +            } -            // Buffer progress -            progress.appendChild(controls.createProgress.call(this, 'buffer')); +            // Fast forward button +            if (control === 'fast-forward') { +                container.appendChild(createButton.call(this, 'fast-forward', defaultAttributes)); +            } -            // TODO: Add loop display indicator +            // Progress +            if (control === 'progress') { +                const progressContainer = createElement('div', { +                    class: `${defaultAttributes.class} plyr__progress__container`, +                }); -            // Seek tooltip -            if (this.config.tooltips.seek) { -                const tooltip = createElement( -                    'span', -                    { -                        class: this.config.classNames.tooltip, -                    }, -                    '00:00', -                ); +                const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress)); -                progress.appendChild(tooltip); -                this.elements.display.seekTooltip = tooltip; -            } +                // Seek range slider +                progress.appendChild( +                    createRange.call(this, 'seek', { +                        id: `plyr-seek-${data.id}`, +                    }), +                ); -            this.elements.progress = progress; -            container.appendChild(this.elements.progress); -        } +                // Buffer progress +                progress.appendChild(createProgress.call(this, 'buffer')); -        // Media current time display -        if (this.config.controls.includes('current-time')) { -            container.appendChild(controls.createTime.call(this, 'currentTime')); -        } +                // TODO: Add loop display indicator -        // Media duration display -        if (this.config.controls.includes('duration')) { -            container.appendChild(controls.createTime.call(this, 'duration')); -        } +                // Seek tooltip +                if (this.config.tooltips.seek) { +                    const tooltip = createElement( +                        'span', +                        { +                            class: this.config.classNames.tooltip, +                        }, +                        '00:00', +                    ); -        // Volume controls -        if (this.config.controls.includes('mute') || this.config.controls.includes('volume')) { -            const volume = createElement('div', { -                class: 'plyr__volume', -            }); +                    progress.appendChild(tooltip); +                    this.elements.display.seekTooltip = tooltip; +                } -            // Toggle mute button -            if (this.config.controls.includes('mute')) { -                volume.appendChild(controls.createButton.call(this, 'mute')); +                this.elements.progress = progress; +                progressContainer.appendChild(this.elements.progress); +                container.appendChild(progressContainer);              } -            // Volume range control -            if (this.config.controls.includes('volume')) { -                // Set the attributes -                const attributes = { -                    max: 1, -                    step: 0.05, -                    value: this.config.volume, -                }; - -                // Create the volume range slider -                volume.appendChild( -                    controls.createRange.call( -                        this, -                        'volume', -                        extend(attributes, { -                            id: `plyr-volume-${data.id}`, -                        }), -                    ), -                ); - -                this.elements.volume = volume; +            // Media current time display +            if (control === 'current-time') { +                container.appendChild(createTime.call(this, 'currentTime', defaultAttributes));              } -            container.appendChild(volume); -        } - -        // Toggle captions button -        if (this.config.controls.includes('captions')) { -            container.appendChild(controls.createButton.call(this, 'captions')); -        } +            // Media duration display +            if (control === 'duration') { +                container.appendChild(createTime.call(this, 'duration', defaultAttributes)); +            } -        // Settings button / menu -        if (this.config.controls.includes('settings') && !is.empty(this.config.settings)) { -            const control = createElement('div', { -                class: 'plyr__menu', -                hidden: '', -            }); +            // Volume controls +            if (control === 'mute' || control === 'volume') { +                let { volume } = this.elements; -            control.appendChild( -                controls.createButton.call(this, 'settings', { -                    'aria-haspopup': true, -                    'aria-controls': `plyr-settings-${data.id}`, -                    'aria-expanded': false, -                }), -            ); +                // Create the volume container if needed +                if (!is.element(volume) || !container.contains(volume)) { +                    volume = createElement( +                        'div', +                        extend({}, defaultAttributes, { +                            class: `${defaultAttributes.class} plyr__volume`.trim(), +                        }), +                    ); -            const popup = createElement('div', { -                class: 'plyr__menu__container', -                id: `plyr-settings-${data.id}`, -                hidden: '', -            }); +                    this.elements.volume = volume; -            const inner = createElement('div'); +                    container.appendChild(volume); +                } -            const home = createElement('div', { -                id: `plyr-settings-${data.id}-home`, -            }); +                // Toggle mute button +                if (control === 'mute') { +                    volume.appendChild(createButton.call(this, 'mute')); +                } -            // Create the menu -            const menu = createElement('div', { -                role: 'menu', -            }); +                // Volume range control +                if (control === 'volume') { +                    // Set the attributes +                    const attributes = { +                        max: 1, +                        step: 0.05, +                        value: this.config.volume, +                    }; + +                    // Create the volume range slider +                    volume.appendChild( +                        createRange.call( +                            this, +                            'volume', +                            extend(attributes, { +                                id: `plyr-volume-${data.id}`, +                            }), +                        ), +                    ); +                } +            } -            home.appendChild(menu); -            inner.appendChild(home); -            this.elements.settings.panels.home = home; +            // Toggle captions button +            if (control === 'captions') { +                container.appendChild(createButton.call(this, 'captions', defaultAttributes)); +            } -            // Build the menu items -            this.config.settings.forEach(type => { -                // TODO: bundle this with the createMenuItem helper and bindings -                const menuItem = createElement( -                    'button', -                    extend(getAttributesFromSelector(this.config.selectors.buttons.settings), { -                        type: 'button', -                        class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`, -                        role: 'menuitem', -                        'aria-haspopup': true, +            // Settings button / menu +            if (control === 'settings' && !is.empty(this.config.settings)) { +                const control = createElement( +                    'div', +                    extend({}, defaultAttributes, { +                        class: `${defaultAttributes.class} plyr__menu`.trim(),                          hidden: '',                      }),                  ); -                // Bind menu shortcuts for keyboard users -                controls.bindMenuItemShortcuts.call(this, menuItem, type); +                control.appendChild( +                    createButton.call(this, 'settings', { +                        'aria-haspopup': true, +                        'aria-controls': `plyr-settings-${data.id}`, +                        'aria-expanded': false, +                    }), +                ); -                // Show menu on click -                on(menuItem, 'click', () => { -                    controls.showMenuPanel.call(this, type, false); +                const popup = createElement('div', { +                    class: 'plyr__menu__container', +                    id: `plyr-settings-${data.id}`, +                    hidden: '',                  }); -                const flex = createElement('span', null, i18n.get(type, this.config)); +                const inner = createElement('div'); -                const value = createElement('span', { -                    class: this.config.classNames.menu.value, +                const home = createElement('div', { +                    id: `plyr-settings-${data.id}-home`,                  }); -                // Speed contains HTML entities -                value.innerHTML = data[type]; +                // Create the menu +                const menu = createElement('div', { +                    role: 'menu', +                }); -                flex.appendChild(value); -                menuItem.appendChild(flex); -                menu.appendChild(menuItem); +                home.appendChild(menu); +                inner.appendChild(home); +                this.elements.settings.panels.home = home; + +                // Build the menu items +                this.config.settings.forEach(type => { +                    // TODO: bundle this with the createMenuItem helper and bindings +                    const menuItem = createElement( +                        'button', +                        extend(getAttributesFromSelector(this.config.selectors.buttons.settings), { +                            type: 'button', +                            class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`, +                            role: 'menuitem', +                            'aria-haspopup': true, +                            hidden: '', +                        }), +                    ); -                // Build the panes -                const pane = createElement('div', { -                    id: `plyr-settings-${data.id}-${type}`, -                    hidden: '', -                }); +                    // Bind menu shortcuts for keyboard users +                    bindMenuItemShortcuts.call(this, menuItem, type); -                // Back button -                const backButton = createElement('button', { -                    type: 'button', -                    class: `${this.config.classNames.control} ${this.config.classNames.control}--back`, -                }); +                    // Show menu on click +                    on(menuItem, 'click', () => { +                        showMenuPanel.call(this, type, false); +                    }); -                // Visible label -                backButton.appendChild( -                    createElement( -                        'span', -                        { -                            'aria-hidden': true, -                        }, -                        i18n.get(type, this.config), -                    ), -                ); +                    const flex = createElement('span', null, i18n.get(type, this.config)); -                // Screen reader label -                backButton.appendChild( -                    createElement( -                        'span', -                        { -                            class: this.config.classNames.hidden, -                        }, -                        i18n.get('menuBack', this.config), -                    ), -                ); +                    const value = createElement('span', { +                        class: this.config.classNames.menu.value, +                    }); -                // Go back via keyboard -                on( -                    pane, -                    'keydown', -                    event => { -                        // We only care about <- -                        if (event.which !== 37) { -                            return; -                        } +                    // Speed contains HTML entities +                    value.innerHTML = data[type]; -                        // Prevent seek -                        event.preventDefault(); -                        event.stopPropagation(); +                    flex.appendChild(value); +                    menuItem.appendChild(flex); +                    menu.appendChild(menuItem); -                        // Show the respective menu -                        controls.showMenuPanel.call(this, 'home', true); -                    }, -                    false, -                ); +                    // Build the panes +                    const pane = createElement('div', { +                        id: `plyr-settings-${data.id}-${type}`, +                        hidden: '', +                    }); -                // Go back via button click -                on(backButton, 'click', () => { -                    controls.showMenuPanel.call(this, 'home', false); -                }); +                    // Back button +                    const backButton = createElement('button', { +                        type: 'button', +                        class: `${this.config.classNames.control} ${this.config.classNames.control}--back`, +                    }); + +                    // Visible label +                    backButton.appendChild( +                        createElement( +                            'span', +                            { +                                'aria-hidden': true, +                            }, +                            i18n.get(type, this.config), +                        ), +                    ); + +                    // Screen reader label +                    backButton.appendChild( +                        createElement( +                            'span', +                            { +                                class: this.config.classNames.hidden, +                            }, +                            i18n.get('menuBack', this.config), +                        ), +                    ); + +                    // Go back via keyboard +                    on( +                        pane, +                        'keydown', +                        event => { +                            // We only care about <- +                            if (event.which !== 37) { +                                return; +                            } -                // Add to pane -                pane.appendChild(backButton); +                            // Prevent seek +                            event.preventDefault(); +                            event.stopPropagation(); -                // Menu -                pane.appendChild( -                    createElement('div', { -                        role: 'menu', -                    }), -                ); +                            // Show the respective menu +                            showMenuPanel.call(this, 'home', true); +                        }, +                        false, +                    ); -                inner.appendChild(pane); +                    // Go back via button click +                    on(backButton, 'click', () => { +                        showMenuPanel.call(this, 'home', false); +                    }); -                this.elements.settings.buttons[type] = menuItem; -                this.elements.settings.panels[type] = pane; -            }); +                    // Add to pane +                    pane.appendChild(backButton); -            popup.appendChild(inner); -            control.appendChild(popup); -            container.appendChild(control); +                    // Menu +                    pane.appendChild( +                        createElement('div', { +                            role: 'menu', +                        }), +                    ); -            this.elements.settings.popup = popup; -            this.elements.settings.menu = control; -        } +                    inner.appendChild(pane); -        // Picture in picture button -        if (this.config.controls.includes('pip') && support.pip) { -            container.appendChild(controls.createButton.call(this, 'pip')); -        } +                    this.elements.settings.buttons[type] = menuItem; +                    this.elements.settings.panels[type] = pane; +                }); -        // Airplay button -        if (this.config.controls.includes('airplay') && support.airplay) { -            container.appendChild(controls.createButton.call(this, 'airplay')); -        } +                popup.appendChild(inner); +                control.appendChild(popup); +                container.appendChild(control); -        // Download button -        if (this.config.controls.includes('download')) { -            const attributes = { -                element: 'a', -                href: this.download, -                target: '_blank', -            }; +                this.elements.settings.popup = popup; +                this.elements.settings.menu = control; +            } -            const { download } = this.config.urls; +            // Picture in picture button +            if (control === 'pip' && support.pip) { +                container.appendChild(createButton.call(this, 'pip', defaultAttributes)); +            } -            if (!is.url(download) && this.isEmbed) { -                extend(attributes, { -                    icon: `logo-${this.provider}`, -                    label: this.provider, -                }); +            // Airplay button +            if (control === 'airplay' && support.airplay) { +                container.appendChild(createButton.call(this, 'airplay', defaultAttributes));              } -            container.appendChild(controls.createButton.call(this, 'download', attributes)); -        } +            // Download button +            if (control === 'download') { +                const attributes = extend({}, defaultAttributes, { +                    element: 'a', +                    href: this.download, +                    target: '_blank', +                }); -        // Toggle fullscreen button -        if (this.config.controls.includes('fullscreen')) { -            container.appendChild(controls.createButton.call(this, 'fullscreen')); -        } +                const { download } = this.config.urls; -        // Larger overlaid play button -        if (this.config.controls.includes('play-large')) { -            this.elements.container.appendChild(controls.createButton.call(this, 'play-large')); -        } +                if (!is.url(download) && this.isEmbed) { +                    extend(attributes, { +                        icon: `logo-${this.provider}`, +                        label: this.provider, +                    }); +                } -        this.elements.controls = container; +                container.appendChild(createButton.call(this, 'download', attributes)); +            } + +            // Toggle fullscreen button +            if (control === 'fullscreen') { +                container.appendChild(createButton.call(this, 'fullscreen', defaultAttributes)); +            } +        });          // Set available quality levels          if (this.isHTML5) { -            controls.setQualityMenu.call(this, html5.getQualityOptions.call(this)); +            setQualityMenu.call(this, html5.getQualityOptions.call(this));          } -        controls.setSpeedMenu.call(this); +        setSpeedMenu.call(this);          return container;      }, | 
