Home Reference Source

src/controller/fps-controller.ts

  1. import { Events } from '../events';
  2. import { logger } from '../utils/logger';
  3. import { ComponentAPI } from '../types/component-api';
  4. import Hls from '../hls';
  5. import { 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 (event: Events.MEDIA_ATTACHING, data: MediaAttachingData) {
  48. const config = this.hls.config;
  49. if (config.capLevelOnFPSDrop) {
  50. const media = data.media instanceof self.HTMLVideoElement ? data.media : null;
  51. this.media = media;
  52. if (media && typeof media.getVideoPlaybackQuality === 'function') {
  53. this.isVideoPlaybackQualityAvailable = true;
  54. }
  55.  
  56. self.clearInterval(this.timer);
  57. this.timer = self.setTimeout(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod);
  58. }
  59. }
  60.  
  61. checkFPS (video: HTMLVideoElement, decodedFrames: number, droppedFrames: number) {
  62. const currentTime = performance.now();
  63. if (decodedFrames) {
  64. if (this.lastTime) {
  65. const currentPeriod = currentTime - this.lastTime;
  66. const currentDropped = droppedFrames - this.lastDroppedFrames;
  67. const currentDecoded = decodedFrames - this.lastDecodedFrames;
  68. const droppedFPS = 1000 * currentDropped / currentPeriod;
  69. const hls = this.hls;
  70. hls.trigger(Events.FPS_DROP, { currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames });
  71. if (droppedFPS > 0) {
  72. // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
  73. if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
  74. let currentLevel = hls.currentLevel;
  75. logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
  76. if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
  77. currentLevel = currentLevel - 1;
  78. hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, { level: currentLevel, droppedLevel: hls.currentLevel });
  79. hls.autoLevelCapping = currentLevel;
  80. this.streamController.nextLevelSwitch();
  81. }
  82. }
  83. }
  84. }
  85. this.lastTime = currentTime;
  86. this.lastDroppedFrames = droppedFrames;
  87. this.lastDecodedFrames = decodedFrames;
  88. }
  89. }
  90.  
  91. checkFPSInterval () {
  92. const video = this.media;
  93. if (video) {
  94. if (this.isVideoPlaybackQualityAvailable) {
  95. const videoPlaybackQuality = video.getVideoPlaybackQuality();
  96. this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);
  97. } else {
  98. // HTMLVideoElement doesn't include the webkit types
  99. this.checkFPS(video, (video as any).webkitDecodedFrameCount as number, (video as any).webkitDroppedFrameCount as number);
  100. }
  101. }
  102. }
  103. }
  104.  
  105. export default FPSController;