1. 1 : /**
  2. 2 : * @file time-tooltip.js
  3. 3 : */
  4. 4 : import Component from '../../component';
  5. 5 : import * as Dom from '../../utils/dom.js';
  6. 6 : import {formatTime} from '../../utils/time.js';
  7. 7 : import * as Fn from '../../utils/fn.js';
  8. 8 :
  9. 9 : /**
  10. 10 : * Time tooltips display a time above the progress bar.
  11. 11 : *
  12. 12 : * @extends Component
  13. 13 : */
  14. 14 : class TimeTooltip extends Component {
  15. 15 :
  16. 16 : /**
  17. 17 : * Creates an instance of this class.
  18. 18 : *
  19. 19 : * @param {Player} player
  20. 20 : * The {@link Player} that this class should be attached to.
  21. 21 : *
  22. 22 : * @param {Object} [options]
  23. 23 : * The key/value store of player options.
  24. 24 : */
  25. 25 : constructor(player, options) {
  26. 26 : super(player, options);
  27. 27 : this.update = Fn.throttle(Fn.bind_(this, this.update), Fn.UPDATE_REFRESH_INTERVAL);
  28. 28 : }
  29. 29 :
  30. 30 : /**
  31. 31 : * Create the time tooltip DOM element
  32. 32 : *
  33. 33 : * @return {Element}
  34. 34 : * The element that was created.
  35. 35 : */
  36. 36 : createEl() {
  37. 37 : return super.createEl('div', {
  38. 38 : className: 'vjs-time-tooltip'
  39. 39 : }, {
  40. 40 : 'aria-hidden': 'true'
  41. 41 : });
  42. 42 : }
  43. 43 :
  44. 44 : /**
  45. 45 : * Updates the position of the time tooltip relative to the `SeekBar`.
  46. 46 : *
  47. 47 : * @param {Object} seekBarRect
  48. 48 : * The `ClientRect` for the {@link SeekBar} element.
  49. 49 : *
  50. 50 : * @param {number} seekBarPoint
  51. 51 : * A number from 0 to 1, representing a horizontal reference point
  52. 52 : * from the left edge of the {@link SeekBar}
  53. 53 : */
  54. 54 : update(seekBarRect, seekBarPoint, content) {
  55. 55 : const tooltipRect = Dom.findPosition(this.el_);
  56. 56 : const playerRect = Dom.getBoundingClientRect(this.player_.el());
  57. 57 : const seekBarPointPx = seekBarRect.width * seekBarPoint;
  58. 58 :
  59. 59 : // do nothing if either rect isn't available
  60. 60 : // for example, if the player isn't in the DOM for testing
  61. 61 : if (!playerRect || !tooltipRect) {
  62. 62 : return;
  63. 63 : }
  64. 64 :
  65. 65 : // This is the space left of the `seekBarPoint` available within the bounds
  66. 66 : // of the player. We calculate any gap between the left edge of the player
  67. 67 : // and the left edge of the `SeekBar` and add the number of pixels in the
  68. 68 : // `SeekBar` before hitting the `seekBarPoint`
  69. 69 : const spaceLeftOfPoint = (seekBarRect.left - playerRect.left) + seekBarPointPx;
  70. 70 :
  71. 71 : // This is the space right of the `seekBarPoint` available within the bounds
  72. 72 : // of the player. We calculate the number of pixels from the `seekBarPoint`
  73. 73 : // to the right edge of the `SeekBar` and add to that any gap between the
  74. 74 : // right edge of the `SeekBar` and the player.
  75. 75 : const spaceRightOfPoint = (seekBarRect.width - seekBarPointPx) +
  76. 76 : (playerRect.right - seekBarRect.right);
  77. 77 :
  78. 78 : // This is the number of pixels by which the tooltip will need to be pulled
  79. 79 : // further to the right to center it over the `seekBarPoint`.
  80. 80 : let pullTooltipBy = tooltipRect.width / 2;
  81. 81 :
  82. 82 : // Adjust the `pullTooltipBy` distance to the left or right depending on
  83. 83 : // the results of the space calculations above.
  84. 84 : if (spaceLeftOfPoint < pullTooltipBy) {
  85. 85 : pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
  86. 86 : } else if (spaceRightOfPoint < pullTooltipBy) {
  87. 87 : pullTooltipBy = spaceRightOfPoint;
  88. 88 : }
  89. 89 :
  90. 90 : // Due to the imprecision of decimal/ratio based calculations and varying
  91. 91 : // rounding behaviors, there are cases where the spacing adjustment is off
  92. 92 : // by a pixel or two. This adds insurance to these calculations.
  93. 93 : if (pullTooltipBy < 0) {
  94. 94 : pullTooltipBy = 0;
  95. 95 : } else if (pullTooltipBy > tooltipRect.width) {
  96. 96 : pullTooltipBy = tooltipRect.width;
  97. 97 : }
  98. 98 :
  99. 99 : // prevent small width fluctuations within 0.4px from
  100. 100 : // changing the value below.
  101. 101 : // This really helps for live to prevent the play
  102. 102 : // progress time tooltip from jittering
  103. 103 : pullTooltipBy = Math.round(pullTooltipBy);
  104. 104 :
  105. 105 : this.el_.style.right = `-${pullTooltipBy}px`;
  106. 106 : this.write(content);
  107. 107 : }
  108. 108 :
  109. 109 : /**
  110. 110 : * Write the time to the tooltip DOM element.
  111. 111 : *
  112. 112 : * @param {string} content
  113. 113 : * The formatted time for the tooltip.
  114. 114 : */
  115. 115 : write(content) {
  116. 116 : Dom.textContent(this.el_, content);
  117. 117 : }
  118. 118 :
  119. 119 : /**
  120. 120 : * Updates the position of the time tooltip relative to the `SeekBar`.
  121. 121 : *
  122. 122 : * @param {Object} seekBarRect
  123. 123 : * The `ClientRect` for the {@link SeekBar} element.
  124. 124 : *
  125. 125 : * @param {number} seekBarPoint
  126. 126 : * A number from 0 to 1, representing a horizontal reference point
  127. 127 : * from the left edge of the {@link SeekBar}
  128. 128 : *
  129. 129 : * @param {number} time
  130. 130 : * The time to update the tooltip to, not used during live playback
  131. 131 : *
  132. 132 : * @param {Function} cb
  133. 133 : * A function that will be called during the request animation frame
  134. 134 : * for tooltips that need to do additional animations from the default
  135. 135 : */
  136. 136 : updateTime(seekBarRect, seekBarPoint, time, cb) {
  137. 137 : this.requestNamedAnimationFrame('TimeTooltip#updateTime', () => {
  138. 138 : let content;
  139. 139 : const duration = this.player_.duration();
  140. 140 :
  141. 141 : if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
  142. 142 : const liveWindow = this.player_.liveTracker.liveWindow();
  143. 143 : const secondsBehind = liveWindow - (seekBarPoint * liveWindow);
  144. 144 :
  145. 145 : content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
  146. 146 : } else {
  147. 147 : content = formatTime(time, duration);
  148. 148 : }
  149. 149 :
  150. 150 : this.update(seekBarRect, seekBarPoint, content);
  151. 151 : if (cb) {
  152. 152 : cb();
  153. 153 : }
  154. 154 : });
  155. 155 : }
  156. 156 : }
  157. 157 :
  158. 158 : Component.registerComponent('TimeTooltip', TimeTooltip);
  159. 159 : export default TimeTooltip;