Home Reference Source

src/controller/cap-level-controller.js

  1. /*
  2. * cap stream level to media size dimension controller
  3. */
  4.  
  5. import Event from '../events';
  6. import EventHandler from '../event-handler';
  7.  
  8. class CapLevelController extends EventHandler {
  9. constructor (hls) {
  10. super(hls,
  11. Event.FPS_DROP_LEVEL_CAPPING,
  12. Event.MEDIA_ATTACHING,
  13. Event.MANIFEST_PARSED,
  14. Event.BUFFER_CODECS,
  15. Event.MEDIA_DETACHING);
  16.  
  17. this.autoLevelCapping = Number.POSITIVE_INFINITY;
  18. this.firstLevel = null;
  19. this.levels = [];
  20. this.media = null;
  21. this.restrictedLevels = [];
  22. this.timer = null;
  23. }
  24.  
  25. destroy () {
  26. if (this.hls.config.capLevelToPlayerSize) {
  27. this.media = null;
  28. this.stopCapping();
  29. }
  30. }
  31.  
  32. onFpsDropLevelCapping (data) {
  33. // Don't add a restricted level more than once
  34. if (CapLevelController.isLevelAllowed(data.droppedLevel, this.restrictedLevels)) {
  35. this.restrictedLevels.push(data.droppedLevel);
  36. }
  37. }
  38.  
  39. onMediaAttaching (data) {
  40. this.media = data.media instanceof window.HTMLVideoElement ? data.media : null;
  41. }
  42.  
  43. onManifestParsed (data) {
  44. const hls = this.hls;
  45. this.restrictedLevels = [];
  46. this.levels = data.levels;
  47. this.firstLevel = data.firstLevel;
  48. if (hls.config.capLevelToPlayerSize && data.video) {
  49. // Start capping immediately if the manifest has signaled video codecs
  50. this.startCapping();
  51. }
  52. }
  53.  
  54. // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted
  55. // to the first level
  56. onBufferCodecs (data) {
  57. const hls = this.hls;
  58. if (hls.config.capLevelToPlayerSize && data.video) {
  59. // If the manifest did not signal a video codec capping has been deferred until we're certain video is present
  60. this.startCapping();
  61. }
  62. }
  63.  
  64. onLevelsUpdated (data) {
  65. this.levels = data.levels;
  66. }
  67.  
  68. onMediaDetaching () {
  69. this.stopCapping();
  70. }
  71.  
  72. detectPlayerSize () {
  73. if (this.media) {
  74. let levelsLength = this.levels ? this.levels.length : 0;
  75. if (levelsLength) {
  76. const hls = this.hls;
  77. hls.autoLevelCapping = this.getMaxLevel(levelsLength - 1);
  78. if (hls.autoLevelCapping > this.autoLevelCapping) {
  79. // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
  80. // usually happen when the user go to the fullscreen mode.
  81. hls.streamController.nextLevelSwitch();
  82. }
  83. this.autoLevelCapping = hls.autoLevelCapping;
  84. }
  85. }
  86. }
  87.  
  88. /*
  89. * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
  90. */
  91. getMaxLevel (capLevelIndex) {
  92. if (!this.levels) {
  93. return -1;
  94. }
  95.  
  96. const validLevels = this.levels.filter((level, index) =>
  97. CapLevelController.isLevelAllowed(index, this.restrictedLevels) && index <= capLevelIndex
  98. );
  99.  
  100. return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight);
  101. }
  102.  
  103. startCapping () {
  104. if (this.timer) {
  105. // Don't reset capping if started twice; this can happen if the manifest signals a video codec
  106. return;
  107. }
  108. this.autoLevelCapping = Number.POSITIVE_INFINITY;
  109. this.hls.firstLevel = this.getMaxLevel(this.firstLevel);
  110. clearInterval(this.timer);
  111. this.timer = setInterval(this.detectPlayerSize.bind(this), 1000);
  112. this.detectPlayerSize();
  113. }
  114.  
  115. stopCapping () {
  116. this.restrictedLevels = [];
  117. this.firstLevel = null;
  118. this.autoLevelCapping = Number.POSITIVE_INFINITY;
  119. if (this.timer) {
  120. this.timer = clearInterval(this.timer);
  121. this.timer = null;
  122. }
  123. }
  124.  
  125. get mediaWidth () {
  126. let width;
  127. const media = this.media;
  128. if (media) {
  129. width = media.width || media.clientWidth || media.offsetWidth;
  130. width *= CapLevelController.contentScaleFactor;
  131. }
  132. return width;
  133. }
  134.  
  135. get mediaHeight () {
  136. let height;
  137. const media = this.media;
  138. if (media) {
  139. height = media.height || media.clientHeight || media.offsetHeight;
  140. height *= CapLevelController.contentScaleFactor;
  141. }
  142. return height;
  143. }
  144.  
  145. static get contentScaleFactor () {
  146. let pixelRatio = 1;
  147. try {
  148. pixelRatio = window.devicePixelRatio;
  149. } catch (e) {}
  150. return pixelRatio;
  151. }
  152.  
  153. static isLevelAllowed (level, restrictedLevels = []) {
  154. return restrictedLevels.indexOf(level) === -1;
  155. }
  156.  
  157. static getMaxLevelByMediaSize (levels, width, height) {
  158. if (!levels || (levels && !levels.length)) {
  159. return -1;
  160. }
  161.  
  162. // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next
  163. // to determine whether we've chosen the greatest bandwidth for the media's dimensions
  164. const atGreatestBandiwdth = (curLevel, nextLevel) => {
  165. if (!nextLevel) {
  166. return true;
  167. }
  168.  
  169. return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height;
  170. };
  171.  
  172. // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to
  173. // the max level
  174. let maxLevelIndex = levels.length - 1;
  175.  
  176. for (let i = 0; i < levels.length; i += 1) {
  177. const level = levels[i];
  178. if ((level.width >= width || level.height >= height) && atGreatestBandiwdth(level, levels[i + 1])) {
  179. maxLevelIndex = i;
  180. break;
  181. }
  182. }
  183.  
  184. return maxLevelIndex;
  185. }
  186. }
  187.  
  188. export default CapLevelController;