Home Reference Source

src/controller/fps-controller.ts

  1. import { Events } from '../events';
  2. import { logger } from '../utils/logger';
  3. import type { ComponentAPI } from '../types/component-api';
  4. import type Hls from '../hls';
  5. import type { MediaAttachingData } from '../types/events';
  6. import StreamController from './stream-controller';
  7.  
  8. class FPSController implements ComponentAPI {
  9. private hls: Hls;
  10. private isVideoPlaybackQualityAvailable: boolean = false;
  11. private timer?: number;
  12. private media: HTMLVideoElement | null = null;
  13. private lastTime: any;
  14. private lastDroppedFrames: number = 0;
  15. private lastDecodedFrames: number = 0;
  16. // stream controller must be provided as a dependency!
  17. private streamController!: StreamController;
  18.  
  19. constructor(hls: Hls) {
  20. this.hls = hls;
  21.  
  22. this.registerListeners();
  23. }
  24.  
  25. public setStreamController(streamController: StreamController) {
  26. this.streamController = streamController;
  27. }
  28.  
  29. protected registerListeners() {
  30. this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
  31. }
  32.  
  33. protected unregisterListeners() {
  34. this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching);
  35. }
  36.  
  37. destroy() {
  38. if (this.timer) {
  39. clearInterval(this.timer);
  40. }
  41.  
  42. this.unregisterListeners();
  43. this.isVideoPlaybackQualityAvailable = false;
  44. this.media = null;
  45. }
  46.  
  47. protected onMediaAttaching(
  48. event: Events.MEDIA_ATTACHING,
  49. data: MediaAttachingData
  50. ) {
  51. const config = this.hls.config;
  52. if (config.capLevelOnFPSDrop) {
  53. const media =
  54. data.media instanceof self.HTMLVideoElement ? data.media : null;
  55. this.media = media;
  56. if (media && typeof media.getVideoPlaybackQuality === 'function') {
  57. this.isVideoPlaybackQualityAvailable = true;
  58. }
  59.  
  60. self.clearInterval(this.timer);
  61. this.timer = self.setTimeout(
  62. this.checkFPSInterval.bind(this),
  63. config.fpsDroppedMonitoringPeriod
  64. );
  65. }
  66. }
  67.  
  68. checkFPS(
  69. video: HTMLVideoElement,
  70. decodedFrames: number,
  71. droppedFrames: number
  72. ) {
  73. const currentTime = performance.now();
  74. if (decodedFrames) {
  75. if (this.lastTime) {
  76. const currentPeriod = currentTime - this.lastTime;
  77. const currentDropped = droppedFrames - this.lastDroppedFrames;
  78. const currentDecoded = decodedFrames - this.lastDecodedFrames;
  79. const droppedFPS = (1000 * currentDropped) / currentPeriod;
  80. const hls = this.hls;
  81. hls.trigger(Events.FPS_DROP, {
  82. currentDropped: currentDropped,
  83. currentDecoded: currentDecoded,
  84. totalDroppedFrames: droppedFrames,
  85. });
  86. if (droppedFPS > 0) {
  87. // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
  88. if (
  89. currentDropped >
  90. hls.config.fpsDroppedMonitoringThreshold * currentDecoded
  91. ) {
  92. let currentLevel = hls.currentLevel;
  93. logger.warn(
  94. 'drop FPS ratio greater than max allowed value for currentLevel: ' +
  95. currentLevel
  96. );
  97. if (
  98. currentLevel > 0 &&
  99. (hls.autoLevelCapping === -1 ||
  100. hls.autoLevelCapping >= currentLevel)
  101. ) {
  102. currentLevel = currentLevel - 1;
  103. hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
  104. level: currentLevel,
  105. droppedLevel: hls.currentLevel,
  106. });
  107. hls.autoLevelCapping = currentLevel;
  108. this.streamController.nextLevelSwitch();
  109. }
  110. }
  111. }
  112. }
  113. this.lastTime = currentTime;
  114. this.lastDroppedFrames = droppedFrames;
  115. this.lastDecodedFrames = decodedFrames;
  116. }
  117. }
  118.  
  119. checkFPSInterval() {
  120. const video = this.media;
  121. if (video) {
  122. if (this.isVideoPlaybackQualityAvailable) {
  123. const videoPlaybackQuality = video.getVideoPlaybackQuality();
  124. this.checkFPS(
  125. video,
  126. videoPlaybackQuality.totalVideoFrames,
  127. videoPlaybackQuality.droppedVideoFrames
  128. );
  129. } else {
  130. // HTMLVideoElement doesn't include the webkit types
  131. this.checkFPS(
  132. video,
  133. (video as any).webkitDecodedFrameCount as number,
  134. (video as any).webkitDroppedFrameCount as number
  135. );
  136. }
  137. }
  138. }
  139. }
  140.  
  141. export default FPSController;