From f375aacca845e852fdcbe083e6d714b37b3df696 Mon Sep 17 00:00:00 2001 From: alesmaye Date: Fri, 30 Aug 2024 18:14:26 +0200 Subject: [PATCH] loading animation --- src/scss/skin-super-modern/_skin.scss | 1 + .../components/_loadingicon.scss | 57 ++++++++++ src/ts/components/loadingicon.ts | 105 ++++++++++++++++++ src/ts/components/touchcontroloverlay.ts | 6 +- 4 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/scss/skin-super-modern/components/_loadingicon.scss create mode 100644 src/ts/components/loadingicon.ts diff --git a/src/scss/skin-super-modern/_skin.scss b/src/scss/skin-super-modern/_skin.scss index cd706b96f..f00982c68 100644 --- a/src/scss/skin-super-modern/_skin.scss +++ b/src/scss/skin-super-modern/_skin.scss @@ -49,6 +49,7 @@ @import 'components/subtitlesettingspaneltogglebutton'; @import 'components/touch-control-overlay'; @import 'components/smallcenteredplaybacktogglebutton'; + @import 'components/loadingicon'; @import 'skin-ads'; @import 'skin-cast-receiver'; @import 'skin-modern-smallscreen'; diff --git a/src/scss/skin-super-modern/components/_loadingicon.scss b/src/scss/skin-super-modern/components/_loadingicon.scss new file mode 100644 index 000000000..e7f386d02 --- /dev/null +++ b/src/scss/skin-super-modern/components/_loadingicon.scss @@ -0,0 +1,57 @@ +@import '../variables'; +@import '../mixins'; + +@keyframes #{$prefix}-rotating { + from { + -ms-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -o-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -ms-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + -o-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.#{$prefix}-ui-loading-icon { + @extend %ui-container; + + cursor: default; + height: 2.5em; + outline: none; + overflow: hidden; // hide overflow from scale animation + width: 2.5em; + display: block; + + + background-position: center; + background-repeat: no-repeat; + background-size: 4em; + background: url('../../assets/skin-super-modern/images/loader.svg'); + + &.#{prefix}-show { + display: block; + } + + .#{prefix}-container-wraper { + height: 100%; + } + + * > &.#{prefix}-ui-loading-icon-image { + background-position: center; + background-repeat: no-repeat; + background-size: 4em; + height: 100%; + width: 100%; + background: url('../../assets/skin-super-modern/images/loader.svg'); + + &.#{$prefix}-loading { + animation: #{$prefix}-rotating 2s linear infinite; + } + } +} diff --git a/src/ts/components/loadingicon.ts b/src/ts/components/loadingicon.ts new file mode 100644 index 000000000..cec239225 --- /dev/null +++ b/src/ts/components/loadingicon.ts @@ -0,0 +1,105 @@ +import { PlayerAPI } from 'bitmovin-player'; +import { UIInstanceManager } from '../uimanager'; +import {Component, ComponentConfig} from './component'; +import { Container, ContainerConfig } from './container'; +import { Timeout } from '../timeout'; +import { EventDispatcher, NoArgs } from '../eventdispatcher'; + +export interface LoadingIconConfig extends ContainerConfig { + /** + * Delay in milliseconds after which the buffering overlay will be displayed. Useful to bypass short stalls without + * displaying the overlay. Set to 0 to display the overlay instantly. + * Default: 1000ms (1 second) + */ + showDelayMs?: number; +} + +export class LoadingIcon extends Container { + private loader: Component; + private isLoading: boolean = false; + + private loadingEvents = { + loadingStartEvent: new EventDispatcher(), + loadingEndEvent: new EventDispatcher(), + } + + constructor(config: LoadingIconConfig = {}) { + super(config); + + this.loader = new Component({ tag: 'div', cssClass: 'ui-loading-icon-image', role: 'img' }); + + this.config = this.mergeConfig(config, { + cssClass: 'ui-loading-icon', + role: 'icon', + components: [this.loader], + showDelayMs: 1000, + }, this.config); + } + + configure(player: PlayerAPI, uimanager: UIInstanceManager): void { + super.configure(player, uimanager); + + let config = this.getConfig(); + + let overlayShowTimeout = new Timeout(config.showDelayMs, () => { + this.startLoader(); + }); + + let showOverlay = () => { + overlayShowTimeout.start(); + }; + + let hideOverlay = () => { + overlayShowTimeout.clear(); + this.stopLoader(); + }; + + player.on(player.exports.PlayerEvent.StallStarted, showOverlay); + player.on(player.exports.PlayerEvent.StallEnded, hideOverlay); + player.on(player.exports.PlayerEvent.Play, showOverlay); + player.on(player.exports.PlayerEvent.Playing, hideOverlay); + player.on(player.exports.PlayerEvent.Paused, hideOverlay); + player.on(player.exports.PlayerEvent.Seek, showOverlay); + player.on(player.exports.PlayerEvent.Seeked, hideOverlay); + player.on(player.exports.PlayerEvent.TimeShift, showOverlay); + player.on(player.exports.PlayerEvent.TimeShifted, hideOverlay); + player.on(player.exports.PlayerEvent.SourceUnloaded, hideOverlay); + + // Show overlay if player is already stalled at init + if (player.isStalled()) { + this.startLoader(); + } + } + + private startLoader(): void { + if (!this.isLoading) { + this.isLoading = true; + this.loader.getDomElement().addClass(this.prefixCss('loading')); + this.onLoadingStartEvent(); + } + } + + private stopLoader(): void { + if (this.isLoading) { + this.isLoading = false; + this.loader.getDomElement().removeClass(this.prefixCss('loading')); + this.onLoadingEndEvent(); + } + } + + public onLoadingStartEvent(): void { + this.loadingEvents.loadingStartEvent.dispatch(this); + } + + public onLoadingEndEvent(): void { + this.loadingEvents.loadingEndEvent.dispatch(this); + } + + get loadingStartEvent(): EventDispatcher { + return this.loadingEvents.loadingStartEvent; + } + + get loadingEndEvent(): EventDispatcher { + return this.loadingEvents.loadingEndEvent; + } +} \ No newline at end of file diff --git a/src/ts/components/touchcontroloverlay.ts b/src/ts/components/touchcontroloverlay.ts index 461627fa7..0f649da7f 100644 --- a/src/ts/components/touchcontroloverlay.ts +++ b/src/ts/components/touchcontroloverlay.ts @@ -7,6 +7,7 @@ import { Timeout } from '../timeout'; import { HTMLElementWithComponent } from '../dom'; import { Label, LabelConfig } from './label'; import { i18n } from '../localization/i18n'; +import { LoadingIcon } from './loadingicon'; export interface TouchControlOverlayConfig extends ContainerConfig { /** @@ -59,6 +60,7 @@ export class TouchControlOverlay extends Container { }; private playbackToggleButton: SmallCenteredPlaybackToggleButton; + private loadingIcon: LoadingIcon; private seekForwardLabel: Label; private seekBackwardLabel: Label; @@ -75,6 +77,8 @@ export class TouchControlOverlay extends Container { enterFullscreenOnInitialPlayback: Boolean(config.enterFullscreenOnInitialPlayback), }); + this.loadingIcon = new LoadingIcon(); + this.seekForwardLabel = new Label({text: '', for: this.getConfig().id, cssClass: 'seek-forward-label', hidden: true}); this.seekBackwardLabel = new Label({text: '', for: this.getConfig().id, cssClass: 'seek-backward-label', hidden: true}); @@ -83,7 +87,7 @@ export class TouchControlOverlay extends Container { acceptsTouchWithUiHidden: true, seekTime: 10, seekDoubleTapMargin: 15, - components: [this.playbackToggleButton, this.seekForwardLabel, this.seekBackwardLabel], + components: [this.loadingIcon, this.seekForwardLabel, this.seekBackwardLabel], }, this.config); }