src/controller/fps-controller.ts
- import { Events } from '../events';
- import { logger } from '../utils/logger';
- import type { ComponentAPI } from '../types/component-api';
- import type Hls from '../hls';
- import type { MediaAttachingData } from '../types/events';
- import StreamController from './stream-controller';
-
- class FPSController implements ComponentAPI {
- private hls: Hls;
- private isVideoPlaybackQualityAvailable: boolean = false;
- private timer?: number;
- private media: HTMLVideoElement | null = null;
- private lastTime: any;
- private lastDroppedFrames: number = 0;
- private lastDecodedFrames: number = 0;
- // stream controller must be provided as a dependency!
- private streamController!: StreamController;
-
- constructor(hls: Hls) {
- this.hls = hls;
-
- this.registerListeners();
- }
-
- public setStreamController(streamController: StreamController) {
- this.streamController = streamController;
- }
-
- protected registerListeners() {
- this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
- }
-
- protected unregisterListeners() {
- this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching);
- }
-
- destroy() {
- if (this.timer) {
- clearInterval(this.timer);
- }
-
- this.unregisterListeners();
- this.isVideoPlaybackQualityAvailable = false;
- this.media = null;
- }
-
- protected onMediaAttaching(
- event: Events.MEDIA_ATTACHING,
- data: MediaAttachingData
- ) {
- const config = this.hls.config;
- if (config.capLevelOnFPSDrop) {
- const media =
- data.media instanceof self.HTMLVideoElement ? data.media : null;
- this.media = media;
- if (media && typeof media.getVideoPlaybackQuality === 'function') {
- this.isVideoPlaybackQualityAvailable = true;
- }
-
- self.clearInterval(this.timer);
- this.timer = self.setTimeout(
- this.checkFPSInterval.bind(this),
- config.fpsDroppedMonitoringPeriod
- );
- }
- }
-
- checkFPS(
- video: HTMLVideoElement,
- decodedFrames: number,
- droppedFrames: number
- ) {
- const currentTime = performance.now();
- if (decodedFrames) {
- if (this.lastTime) {
- const currentPeriod = currentTime - this.lastTime;
- const currentDropped = droppedFrames - this.lastDroppedFrames;
- const currentDecoded = decodedFrames - this.lastDecodedFrames;
- const droppedFPS = (1000 * currentDropped) / currentPeriod;
- const hls = this.hls;
- hls.trigger(Events.FPS_DROP, {
- currentDropped: currentDropped,
- currentDecoded: currentDecoded,
- totalDroppedFrames: droppedFrames,
- });
- if (droppedFPS > 0) {
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
- if (
- currentDropped >
- hls.config.fpsDroppedMonitoringThreshold * currentDecoded
- ) {
- let currentLevel = hls.currentLevel;
- logger.warn(
- 'drop FPS ratio greater than max allowed value for currentLevel: ' +
- currentLevel
- );
- if (
- currentLevel > 0 &&
- (hls.autoLevelCapping === -1 ||
- hls.autoLevelCapping >= currentLevel)
- ) {
- currentLevel = currentLevel - 1;
- hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
- level: currentLevel,
- droppedLevel: hls.currentLevel,
- });
- hls.autoLevelCapping = currentLevel;
- this.streamController.nextLevelSwitch();
- }
- }
- }
- }
- this.lastTime = currentTime;
- this.lastDroppedFrames = droppedFrames;
- this.lastDecodedFrames = decodedFrames;
- }
- }
-
- checkFPSInterval() {
- const video = this.media;
- if (video) {
- if (this.isVideoPlaybackQualityAvailable) {
- const videoPlaybackQuality = video.getVideoPlaybackQuality();
- this.checkFPS(
- video,
- videoPlaybackQuality.totalVideoFrames,
- videoPlaybackQuality.droppedVideoFrames
- );
- } else {
- // HTMLVideoElement doesn't include the webkit types
- this.checkFPS(
- video,
- (video as any).webkitDecodedFrameCount as number,
- (video as any).webkitDroppedFrameCount as number
- );
- }
- }
- }
- }
-
- export default FPSController;