diff --git a/assets/skin-super-modern/images/ad-skip.svg b/assets/skin-super-modern/images/ad-skip.svg new file mode 100644 index 000000000..26ee93211 --- /dev/null +++ b/assets/skin-super-modern/images/ad-skip.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/scss/skin-super-modern/_skin-ads.scss b/src/scss/skin-super-modern/_skin-ads.scss index 4fe18dea7..332dcb48b 100644 --- a/src/scss/skin-super-modern/_skin-ads.scss +++ b/src/scss/skin-super-modern/_skin-ads.scss @@ -1,40 +1,26 @@ @import 'variables'; // sass-lint:disable nesting-depth -.#{$prefix}-ui-skin-ads { - - .#{$prefix}-ui-ads-status { - background-color: $color-background-bars; - left: 1.5em; - padding: .5em 1.5em; - position: absolute; - top: 1em; - - .#{$prefix}-ui-label-ad-message { - @extend %ui-label; - - color: $color-secondary; - white-space: normal; +&.#{$prefix}-ui-skin-ads { + @import 'components/adskipbutton'; + @import 'components/adstatusoverlay'; + + .#{$prefix}-ui-seekbar { + .#{$prefix}-seekbar, + .#{$prefix}-seekbar-bars, + .#{$prefix}-seekbar-bars > * { + pointer-events: none; } - .#{$prefix}-ui-button-ad-skip { - @extend %ui-button; - - .#{$prefix}-label { - display: inherit; - - &:hover { - text-decoration: underline; - } - } + .#{$prefix}-seekbar-playbackposition-marker, + .#{$prefix}-seekbar-bufferlevel, + .#{$prefix}-seekbar-seekposition, + .#{$prefix}-seekbar-markers { + display: none; + } - // Add the dot between ad message and skip button - &::before { - color: $color-highlight; - content: '●'; - padding-left: .5em; - padding-right: .5em; - } + .#{$prefix}-seekbar-playbackposition { + background-color: $color-ads; } } diff --git a/src/scss/skin-super-modern/_variables.scss b/src/scss/skin-super-modern/_variables.scss index 2430b3625..28cae051f 100644 --- a/src/scss/skin-super-modern/_variables.scss +++ b/src/scss/skin-super-modern/_variables.scss @@ -17,6 +17,7 @@ $color-item-hover: #54565a !default; $color-background-menu: #212226 !default; $color-background-seek-circle: rgba(124, 124, 124, .35) !default; $color-shadow-seek-label: 0 0 30px 0 rgba(0, 0, 0, .75) !default; +$color-ads: #ffc737 !default; $font-family: sans-serif !default; $font-size: 1em !default; diff --git a/src/scss/skin-super-modern/components/_adskipbutton.scss b/src/scss/skin-super-modern/components/_adskipbutton.scss new file mode 100644 index 000000000..c1d9f5a66 --- /dev/null +++ b/src/scss/skin-super-modern/components/_adskipbutton.scss @@ -0,0 +1,39 @@ +@import '../variables'; + +%ui-button-ad-skip { + @extend %ui-button; + + background-color: transparentize(#000, 0.75); + border-radius: 20px; + padding: 0em 1em; + min-width: fit-content; + + .#{$prefix}-label { + display: inline-block; + color: $color-ads; + + &::after { + background-image: url('../../assets/skin-super-modern/images/ad-skip.svg'); + background-repeat: no-repeat; + background-size: 1em auto; + content: ' '; + display: inline-block; + height: 1em; + vertical-align: bottom; + margin-left: .5em; + width: 1em; + } + } + + &.#{$prefix}-disabled { + .#{$prefix}-label { + &::after { + display: none; + } + } + } +} + +.#{$prefix}-ui-button-ad-skip { + @extend %ui-button-ad-skip; +} diff --git a/src/scss/skin-super-modern/components/_adstatusoverlay.scss b/src/scss/skin-super-modern/components/_adstatusoverlay.scss new file mode 100644 index 000000000..d97b6b7e1 --- /dev/null +++ b/src/scss/skin-super-modern/components/_adstatusoverlay.scss @@ -0,0 +1,29 @@ +@import '../variables'; +@import '../mixins'; + +%ad-status-overlay { + @extend %ui-container; + @include layout-align-bottom; + + box-sizing: border-box; + line-height: 1em; + padding: 1em 1em .5em; + + .#{$prefix}-bar { + > .#{$prefix}-container-wrapper { + pointer-events: none; + display: flex; + margin: .5em 0; + } + } + + // Move the overlay up above the controlbar when it appears to avoid them overlapping + &.#{$prefix}-controlbar-visible { + bottom: 5em; + transition: bottom $animation-duration-short ease-in; + } +} + +.#{$prefix}-ui-ad-status-overlay { + @extend %ad-status-overlay; +} diff --git a/src/ts/components/adstatusoverlay.ts b/src/ts/components/adstatusoverlay.ts new file mode 100644 index 000000000..85b1aa8e2 --- /dev/null +++ b/src/ts/components/adstatusoverlay.ts @@ -0,0 +1,47 @@ +import { Container, ContainerConfig } from './container'; +import { AdSkipButton } from './adskipbutton'; +import { Spacer } from './spacer'; +import { PlayerAPI } from 'bitmovin-player'; +import { UIInstanceManager } from '../uimanager'; +import { Component, ComponentConfig } from './component'; +import { ControlBar } from './controlbar'; + +export class AdStatusOverlay extends Container { + private static readonly CLASS_CONTROLBAR_VISIBLE = 'controlbar-visible'; + + constructor(config: ContainerConfig = {}) { + super(config); + + this.config = this.mergeConfig( + config, + { + components: [ + new Container({ + components: [ + new Spacer(), + new AdSkipButton(), + ], + cssClasses: ['bar'], + }), + ], + cssClass: 'ui-ad-status-overlay', + }, + this.config, + ); + } + + configure(player: PlayerAPI, uimanager: UIInstanceManager) { + super.configure(player, uimanager); + + uimanager.onComponentShow.subscribe((component: Component) => { + if (component instanceof ControlBar) { + this.getDomElement().addClass(this.prefixCss(AdStatusOverlay.CLASS_CONTROLBAR_VISIBLE)); + } + }); + uimanager.onComponentHide.subscribe((component: Component) => { + if (component instanceof ControlBar) { + this.getDomElement().removeClass(this.prefixCss(AdStatusOverlay.CLASS_CONTROLBAR_VISIBLE)); + } + }); + } +} diff --git a/src/ts/components/seekbar.ts b/src/ts/components/seekbar.ts index 22e086ee2..041194d62 100644 --- a/src/ts/components/seekbar.ts +++ b/src/ts/components/seekbar.ts @@ -569,6 +569,13 @@ export class SeekBar extends Component { return; } + // Reset the currentTimeSeekBar and set the position to 0 if the player has no duration + if (this.player.getDuration() === 0) { + this.setPlaybackPosition(0); + currentTimeSeekBar = 0; + return; + } + currentTimeSeekBar += currentTimeUpdateDeltaSecs; try { diff --git a/src/ts/main.ts b/src/ts/main.ts index 82a7e80cb..e381b9536 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -90,6 +90,7 @@ export { SettingsPanelItem } from './components/settingspanelitem'; export { ReplayButton } from './components/replaybutton'; export { QuickSeekButton, QuickSeekButtonConfig } from './components/quickseekbutton'; export { ListSelector, ListSelectorConfig, ListItem, ListItemFilter, ListItemLabelTranslator } from './components/listselector'; +export { AdStatusOverlay } from './components/adstatusoverlay'; // Object.assign polyfill for ES5/IE9 // https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/assign diff --git a/src/ts/uifactory.ts b/src/ts/uifactory.ts index f2d3f638d..e888571b5 100644 --- a/src/ts/uifactory.ts +++ b/src/ts/uifactory.ts @@ -56,6 +56,7 @@ import { ModernSettingsPanelItem } from './components/modernsettingspanelitem'; import { ModernSettingsPanelPage } from './components/modernsettingspanelpage'; import { ModernSettingsPanel } from './components/modernsettingspanel'; import { TouchControlOverlay } from './components/touchcontroloverlay'; +import { AdStatusOverlay } from './components/adstatusoverlay'; export namespace UIFactory { export function buildDefaultSuperModernUI(player: PlayerAPI, config: UIConfig = {}): UIManager { @@ -447,11 +448,93 @@ export namespace UIFactory { } export function superModernMobileAdsUI() { - return new UIContainer({}); + let controlBar = new ControlBar({ + components: [ + new Container({ + components: [ + new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.CurrentTime }), + new SeekBar({ label: new SeekBarLabel() }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.TotalTime, + cssClasses: ['text-right'], + }), + ], + cssClasses: ['controlbar-top'], + }), + new Container({ + components: [ + new PlaybackToggleButton(), + new VolumeToggleButton(), + new Spacer(), + new FullscreenToggleButton(), + ], + cssClasses: ['controlbar-bottom'], + }), + ], + }); + + return new UIContainer({ + components: [ + new BufferingOverlay(), + new AdClickOverlay(), + new PlaybackToggleOverlay(), + controlBar, + new AdStatusOverlay(), + new ErrorMessageOverlay(), + ], + hideDelay: 2000, + hidePlayerStateExceptions: [ + PlayerUtils.PlayerState.Prepared, + PlayerUtils.PlayerState.Paused, + PlayerUtils.PlayerState.Finished, + ], + cssClasses: ['ui-skin-super-modern', 'ui-skin-smallscreen', 'ui-skin-ads'], + }); } export function superModernAdsUI() { - return new UIContainer({}); + let controlBar = new ControlBar({ + components: [ + new Container({ + components: [ + new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.CurrentTime }), + new SeekBar({ label: new SeekBarLabel() }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.TotalTime, + cssClasses: ['text-right'], + }), + ], + cssClasses: ['controlbar-top'], + }), + new Container({ + components: [ + new PlaybackToggleButton(), + new VolumeToggleButton(), + new Spacer(), + new FullscreenToggleButton(), + ], + cssClasses: ['controlbar-bottom'], + }), + ], + }); + + return new UIContainer({ + components: [ + new BufferingOverlay(), + new AdClickOverlay(), + new PlaybackToggleOverlay(), + new AdStatusOverlay(), + controlBar, + new ErrorMessageOverlay(), + ], + hideDelay: 2000, + hidePlayerStateExceptions: [ + PlayerUtils.PlayerState.Prepared, + PlayerUtils.PlayerState.Paused, + PlayerUtils.PlayerState.Finished, + ], + cssClasses: ['ui-skin-super-modern', 'ui-skin-ads'], + }); } export function superModernMobileUI() {