diff --git a/ui/component/viewers/videoViewer/internal/lbry-playback-rate.js b/ui/component/viewers/videoViewer/internal/lbry-playback-rate.js new file mode 100644 index 000000000..fdb0efb43 --- /dev/null +++ b/ui/component/viewers/videoViewer/internal/lbry-playback-rate.js @@ -0,0 +1,95 @@ +import videojs from 'video.js'; + +const IS_DEV = process.env.NODE_ENV !== 'production'; +const CONTROL_BAR = 'ControlBar'; +const PLAYBACK_RATE_MENU_BUTTON = 'PlaybackRateMenuButton'; +const MENU = 'Menu'; + +const Menu = videojs.getComponent(MENU); +const PlaybackRateMenuButton = videojs.getComponent(PLAYBACK_RATE_MENU_BUTTON); + +class LbryPlaybackRateMenuButton extends PlaybackRateMenuButton { + static replaceExisting(player) { + try { + const controlBar = player.getChild(CONTROL_BAR); + const playbackRateMenuButton = controlBar.getChild(PLAYBACK_RATE_MENU_BUTTON); + controlBar.removeChild(playbackRateMenuButton); + controlBar.addChild(new LbryPlaybackRateMenuButton(player)); + } catch (error) { + if (IS_DEV) throw Error('\n\nvideojs.jsx: Playback rate hierarchy changed?\n\n' + error); + } + } + + constructor(player, options = {}) { + super(player, options); + } + + /** + * The default behavior cycles through the selection, while the popup is + * invoked by hover. We have removed the hover behavior (1637) since it is + * annoying when accidentally hovered, and it's also needed to address a + * z-order issue. + * + * With the hover behavior gone, we need to override the default click + * behavior to invoke the popup instead of cycling through the settings. + * + * Ref: https://github.com/videojs/video.js/issues/3394#issuecomment-230793773 + * @param event + */ + handleClick(event) { + if (this.buttonPressed_) { + this.unpressButton(); + } else { + this.pressButton(); + } + } + + /** + * The default menu behavior is to dismiss the popup when losing focus, unless + * the new focus is on the associated button. The associated button is + * specifically defined as the first child, but PlaybackRateMenuButton + * implements it as the second child. + * + * Fixed by reversing the check to use `relatedTarget.parentElement` instead. + * It shouldn't matter which sub-element was clicked, as long as it's part of + * the button. + * + * @param event + * @param menu + */ + handleMenuBlur(event, menu) { + const relatedTarget = event.relatedTarget || document.activeElement; + + if ( + !menu.children().some((element) => { + return element.el() === relatedTarget; + }) + ) { + const btn = menu.menuButton_; + if (btn && btn.buttonPressed_ && relatedTarget.parentElement !== btn.el()) { + btn.unpressButton(); + } + } + } + + createMenu() { + const menu = new Menu(this.player_, { menuButton: this }); + + // Override the original with ours. Must be done after the constructor + // is called, as that is where `boundHandleBlur_` is originally set. + menu.boundHandleBlur_ = (e) => this.handleMenuBlur(e, menu); + + this.hideThreshold_ = 0; // Must reset. See `MenuButton::createMenu` notes. + this.items = this.createItems(); + + if (this.items) { + for (let i = 0; i < this.items.length; ++i) { + menu.addItem(this.items[i]); + } + } + + return menu; + } +} + +export default LbryPlaybackRateMenuButton; diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index 3787be691..04bf9f469 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -16,6 +16,7 @@ import eventTracking from 'videojs-event-tracking'; import functions from './videojs-functions'; import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin'; import keyboardShorcuts from './videojs-shortcuts'; +import LbryPlaybackRateMenuButton from './lbry-playback-rate'; import LbryVolumeBarClass from './lbry-volume-bar'; import Chromecast from './chromecast'; import playerjs from 'player.js'; @@ -278,8 +279,8 @@ export default React.memo(function VideoJs(props: Props) { // runAds(internalFeatureEnabled, allowPreRoll, player, embedded); - // Replace volume bar with custom LBRY volume bar LbryVolumeBarClass.replaceExisting(player); + LbryPlaybackRateMenuButton.replaceExisting(player); // Add reloadSourceOnError plugin player.reloadSourceOnError({ errorInterval: 10 }); diff --git a/ui/scss/component/_videojs.scss b/ui/scss/component/_videojs.scss index 692b18bb3..02b286983 100644 --- a/ui/scss/component/_videojs.scss +++ b/ui/scss/component/_videojs.scss @@ -205,6 +205,16 @@ $control-bar-popup-font-size: 0.8rem; opacity: 1; } +.vjs-lock-showing { + @media (max-width: $breakpoint-small) { + width: 8em !important; + // overflow-y:scroll; + .vjs-menu-content { + max-height: 176px !important; + } + } +} + [dir] .vjs-menu-button-popup .vjs-menu .vjs-menu-content { //background-color: rgba(43, 51, 63); background-color: rgba(var(--color-header-button-base), 0.9); @@ -239,6 +249,18 @@ $control-bar-popup-font-size: 0.8rem; background-color: var(--color-primary); border-radius: var(--border-radius); } + + @media (max-width: $breakpoint-small) { + padding: 0.1em 0; + margin-left: var(--spacing-xxs); + margin-right: var(--spacing-xxs); + &:first-child { + margin-top: var(--spacing-xxs); + } + &:last-child { + margin-bottom: var(--spacing-xxs); + } + } } .vjs-selected { background-color: var(--color-primary);