Skip to content

Commit

Permalink
Merge pull request #649 from bitmovin/feature/loading-animation
Browse files Browse the repository at this point in the history
Loading/Buffer animation
  • Loading branch information
bitmovin-kenny authored Sep 13, 2024
2 parents 8b65326 + 5b6f6d8 commit 97b5cd6
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/scss/skin-super-modern/_skin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
28 changes: 28 additions & 0 deletions src/scss/skin-super-modern/components/_loadingicon.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@import '../variables';
@import '../mixins';

@keyframes #{$prefix}-rotating {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}

.#{$prefix}-ui-loading-icon {
@extend %ui-container;

background: url('../../assets/skin-super-modern/images/loader.svg') center no-repeat;
cursor: default;
display: none;
height: 2.5em;
outline: none;
width: 2.5em;

&.#{$prefix}-loading {
animation: #{$prefix}-rotating 3s linear infinite;
display: block;
}
}
104 changes: 104 additions & 0 deletions src/ts/components/loadingicon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { PlayerAPI } from 'bitmovin-player';
import { UIInstanceManager } from '../uimanager';
import { Container, ContainerConfig } from './container';
import { Timeout } from '../timeout';
import { EventDispatcher, NoArgs, Event } 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<LoadingIconConfig> {
private isLoading: boolean = false;

private loadingEvents = {
loadingStartEvent: new EventDispatcher<LoadingIcon, NoArgs>(),
loadingEndEvent: new EventDispatcher<LoadingIcon, NoArgs>(),
};

constructor(config: LoadingIconConfig = {}) {
super(config);

this.config = this.mergeConfig(config, {
cssClass: 'ui-loading-icon',
role: 'icon',
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.onLoadingStartEvent();
this.getDomElement().addClass(this.prefixCss('loading'));
}
}

private stopLoader(): void {
if (this.isLoading) {
this.isLoading = false;
this.onLoadingEndEvent();
this.getDomElement().removeClass(this.prefixCss('loading'));
}
}

public onLoadingStartEvent(): void {
this.loadingEvents.loadingStartEvent.dispatch(this);
}

public onLoadingEndEvent(): void {
this.loadingEvents.loadingEndEvent.dispatch(this);
}

public isSpinning(): boolean {
return this.isLoading;
}

get loadingStartEvent(): Event<LoadingIcon, NoArgs> {
return this.loadingEvents.loadingStartEvent.getEvent();
}

get loadingEndEvent(): Event<LoadingIcon, NoArgs> {
return this.loadingEvents.loadingEndEvent.getEvent();
}
}
25 changes: 25 additions & 0 deletions src/ts/components/touchcontroloverlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -59,6 +60,7 @@ export class TouchControlOverlay extends Container<TouchControlOverlayConfig> {
};

private playbackToggleButton: SmallCenteredPlaybackToggleButton;
private loadingIcon: LoadingIcon;
private seekForwardLabel: Label<LabelConfig>;
private seekBackwardLabel: Label<LabelConfig>;

Expand All @@ -75,6 +77,8 @@ export class TouchControlOverlay extends Container<TouchControlOverlayConfig> {
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});

Expand All @@ -90,6 +94,27 @@ export class TouchControlOverlay extends Container<TouchControlOverlayConfig> {
configure(player: PlayerAPI, uimanager: UIInstanceManager): void {
super.configure(player, uimanager);

this.loadingIcon.configure(player, uimanager);

let showLoadingIcon = () => {
this.removeComponent(this.playbackToggleButton);
this.addComponent(this.loadingIcon);
this.updateComponents();
};

let hideLoadingIcon = () => {
this.removeComponent(this.loadingIcon);
this.addComponent(this.playbackToggleButton);
this.updateComponents();
};

if (this.loadingIcon.isSpinning()) {
showLoadingIcon();
}

this.loadingIcon.loadingStartEvent.subscribe(showLoadingIcon);
this.loadingIcon.loadingEndEvent.subscribe(hideLoadingIcon);

let playerSeekTime = 0;
let startSeekTime = 0;

Expand Down

0 comments on commit 97b5cd6

Please sign in to comment.