bitmovin-player-ui
Version:
Bitmovin Player UI Framework
215 lines (189 loc) • 7.42 kB
text/typescript
import { LabelConfig, Label } from './Label';
import { UIInstanceManager } from '../../UIManager';
import LiveStreamDetectorEventArgs = PlayerUtils.LiveStreamDetectorEventArgs;
import { PlayerUtils } from '../../utils/PlayerUtils';
import { StringUtils } from '../../utils/StringUtils';
import { PlayerAPI } from 'bitmovin-player';
import { i18n } from '../../localization/i18n';
export enum PlaybackTimeLabelMode {
/**
* Displays the current time
*/
CurrentTime,
/**
* Displays the duration of the content
*/
TotalTime,
/**
* Displays the current time and the duration of the content
* Format: ${currentTime} / ${totalTime}
*/
CurrentAndTotalTime,
/**
* Displays the remaining time of the content
*/
RemainingTime,
}
/**
* @category Configs
*/
export interface PlaybackTimeLabelConfig extends LabelConfig {
/**
* The type of which time should be displayed in the label.
* Default: PlaybackTimeLabelMode.CurrentAndTotalTime
*/
timeLabelMode?: PlaybackTimeLabelMode;
/**
* Boolean if the label should be hidden in live playback
*/
hideInLivePlayback?: boolean;
}
/**
* A label that display the current playback time and the total time through {@link PlaybackTimeLabel#setTime setTime}
* or any string through {@link PlaybackTimeLabel#setText setText}.
*
* @category Labels
*/
export class PlaybackTimeLabel extends Label<PlaybackTimeLabelConfig> {
private timeFormat: string;
constructor(config: PlaybackTimeLabelConfig = {}) {
super(config);
this.config = this.mergeConfig(
config,
<PlaybackTimeLabelConfig>{
cssClass: 'ui-playbacktimelabel',
timeLabelMode: PlaybackTimeLabelMode.CurrentAndTotalTime,
hideInLivePlayback: false,
},
this.config,
);
}
configure(player: PlayerAPI, uimanager: UIInstanceManager): void {
super.configure(player, uimanager);
const config = this.getConfig();
let live = false;
const liveCssClass = this.prefixCss('ui-playbacktimelabel-live');
const liveEdgeCssClass = this.prefixCss('ui-playbacktimelabel-live-edge');
let minWidth = 0;
const liveClickHandler = () => {
player.timeShift(0);
};
const updateLiveState = () => {
// Player is playing a live stream when the duration is infinite
live = player.isLive();
// Attach/detach live marker class
if (live) {
this.getDomElement().addClass(liveCssClass);
this.setText(i18n.getLocalizer('live'));
if (config.hideInLivePlayback) {
this.hide();
}
this.onClick.subscribe(liveClickHandler);
updateLiveTimeshiftState();
} else {
this.getDomElement().removeClass(liveCssClass);
this.getDomElement().removeClass(liveEdgeCssClass);
this.show();
this.onClick.unsubscribe(liveClickHandler);
}
};
const updateLiveTimeshiftState = () => {
if (!live) {
return;
}
// The player is only at the live edge iff the stream is not shifted and it is actually playing or playback has
// never been started (meaning it isn't paused). A player that is paused is always behind the live edge.
// An exception is made for live streams without a timeshift window, because here we "stop" playback instead
// of pausing it (from a UI perspective), so we keep the live edge indicator on because a play would always
// resume at the live edge.
const isTimeshifted = player.getTimeShift() < 0;
const isTimeshiftAvailable = player.getMaxTimeShift() < 0;
if (!isTimeshifted && (!player.isPaused() || !isTimeshiftAvailable)) {
this.getDomElement().addClass(liveEdgeCssClass);
} else {
this.getDomElement().removeClass(liveEdgeCssClass);
}
};
const playbackTimeHandler = () => {
if (!live && player.getDuration() !== Infinity) {
this.setTime(PlayerUtils.getCurrentTimeRelativeToSeekableRange(player), player.getDuration());
}
// To avoid 'jumping' in the UI by varying label sizes due to non-monospaced fonts,
// we gradually increase the min-width with the content to reach a stable size.
const width = this.getDomElement().width();
if (width > minWidth) {
minWidth = width;
this.getDomElement().css({
'min-width': minWidth + 'px',
});
}
};
const updateTimeFormatBasedOnDuration = () => {
// Set time format depending on source duration
this.timeFormat =
Math.abs(player.isLive() ? player.getMaxTimeShift() : player.getDuration()) >= 3600
? StringUtils.FORMAT_HHMMSS
: StringUtils.FORMAT_MMSS;
playbackTimeHandler();
};
const liveStreamDetector = new PlayerUtils.LiveStreamDetector(player, uimanager);
liveStreamDetector.onLiveChanged.subscribe((sender, args: LiveStreamDetectorEventArgs) => {
live = args.live;
playbackTimeHandler();
updateTimeFormatBasedOnDuration();
updateLiveState();
});
liveStreamDetector.detect(); // Initial detection
const init = () => {
// Reset min-width when a new source is ready (especially for switching VOD/Live modes where the label content
// changes)
minWidth = 0;
this.getDomElement().css({
'min-width': null,
});
updateTimeFormatBasedOnDuration();
};
player.on(player.exports.PlayerEvent.TimeChanged, playbackTimeHandler);
player.on(player.exports.PlayerEvent.Ready, updateTimeFormatBasedOnDuration);
player.on(player.exports.PlayerEvent.Seeked, playbackTimeHandler);
player.on(player.exports.PlayerEvent.TimeShift, updateLiveTimeshiftState);
player.on(player.exports.PlayerEvent.TimeShifted, updateLiveTimeshiftState);
player.on(player.exports.PlayerEvent.Playing, updateLiveTimeshiftState);
player.on(player.exports.PlayerEvent.Paused, updateLiveTimeshiftState);
player.on(player.exports.PlayerEvent.StallStarted, updateLiveTimeshiftState);
player.on(player.exports.PlayerEvent.StallEnded, updateLiveTimeshiftState);
player.on(player.exports.PlayerEvent.DurationChanged, init);
uimanager.getConfig().events.onUpdated.subscribe(init);
init();
}
/**
* Sets the current playback time and total duration.
* @param playbackSeconds the current playback time in seconds
* @param durationSeconds the total duration in seconds
*/
setTime(playbackSeconds: number, durationSeconds: number) {
const currentTime = StringUtils.secondsToTime(playbackSeconds, this.timeFormat);
const totalTime = StringUtils.secondsToTime(durationSeconds, this.timeFormat);
switch ((<PlaybackTimeLabelConfig>this.config).timeLabelMode) {
case PlaybackTimeLabelMode.CurrentTime:
this.setText(`${currentTime}`);
break;
case PlaybackTimeLabelMode.TotalTime:
this.setText(`${totalTime}`);
break;
case PlaybackTimeLabelMode.CurrentAndTotalTime:
this.setText(`${currentTime} / ${totalTime}`);
break;
case PlaybackTimeLabelMode.RemainingTime:
this.setText(`${StringUtils.secondsToTime(durationSeconds - playbackSeconds, this.timeFormat)}`);
break;
}
}
/**
* Sets the current time format
* @param timeFormat the time format
*/
protected setTimeFormat(timeFormat: string): void {
this.timeFormat = timeFormat;
}
}