summaryrefslogtreecommitdiff
path: root/javascript/videojs/src/js/control-bar/progress-control/time-tooltip.js
blob: aecd8b361727865a49fb04129ee222ee48590fa0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/**
 * @file time-tooltip.js
 */
import Component from '../../component';
import * as Dom from '../../utils/dom.js';
import {formatTime} from '../../utils/time.js';
import * as Fn from '../../utils/fn.js';

/** @import Player from '../../player' */

/**
 * Time tooltips display a time above the progress bar.
 *
 * @extends Component
 */
class TimeTooltip extends Component {

  /**
   * Creates an instance of this class.
   *
   * @param {Player} player
   *        The {@link Player} that this class should be attached to.
   *
   * @param {Object} [options]
   *        The key/value store of player options.
   */
  constructor(player, options) {
    super(player, options);
    this.update = Fn.throttle(Fn.bind_(this, this.update), Fn.UPDATE_REFRESH_INTERVAL);
  }

  /**
   * Create the time tooltip DOM element
   *
   * @return {Element}
   *         The element that was created.
   */
  createEl() {
    return super.createEl('div', {
      className: 'vjs-time-tooltip'
    }, {
      'aria-hidden': 'true'
    });
  }

  /**
   * Updates the position of the time tooltip relative to the `SeekBar`.
   *
   * @param {Object} seekBarRect
   *        The `ClientRect` for the {@link SeekBar} element.
   *
   * @param {number} seekBarPoint
   *        A number from 0 to 1, representing a horizontal reference point
   *        from the left edge of the {@link SeekBar}
   */
  update(seekBarRect, seekBarPoint, content) {
    const tooltipRect = Dom.findPosition(this.el_);
    const playerRect = Dom.getBoundingClientRect(this.player_.el());
    const seekBarPointPx = seekBarRect.width * seekBarPoint;

    // do nothing if either rect isn't available
    // for example, if the player isn't in the DOM for testing
    if (!playerRect || !tooltipRect) {
      return;
    }

    // This is the space left of the `seekBarPoint` available within the bounds
    // of the player. We calculate any gap between the left edge of the player
    // and the left edge of the `SeekBar` and add the number of pixels in the
    // `SeekBar` before hitting the `seekBarPoint`
    let spaceLeftOfPoint = (seekBarRect.left - playerRect.left) + seekBarPointPx;

    // This is the space right of the `seekBarPoint` available within the bounds
    // of the player. We calculate the number of pixels from the `seekBarPoint`
    // to the right edge of the `SeekBar` and add to that any gap between the
    // right edge of the `SeekBar` and the player.
    let spaceRightOfPoint = (seekBarRect.width - seekBarPointPx) +
      (playerRect.right - seekBarRect.right);

    // spaceRightOfPoint is always NaN for mouse time display
    // because the seekbarRect does not have a right property. This causes
    // the mouse tool tip to be truncated when it's close to the right edge of the player.
    // In such cases, we ignore the `playerRect.right - seekBarRect.right` value when calculating.
    // For the sake of consistency, we ignore seekBarRect.left - playerRect.left for the left edge.
    if (!spaceRightOfPoint) {
      spaceRightOfPoint = seekBarRect.width - seekBarPointPx;
      spaceLeftOfPoint = seekBarPointPx;
    }
    // This is the number of pixels by which the tooltip will need to be pulled
    // further to the right to center it over the `seekBarPoint`.
    let pullTooltipBy = tooltipRect.width / 2;

    // Adjust the `pullTooltipBy` distance to the left or right depending on
    // the results of the space calculations above.
    if (spaceLeftOfPoint < pullTooltipBy) {
      pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
    } else if (spaceRightOfPoint < pullTooltipBy) {
      pullTooltipBy = spaceRightOfPoint;
    }

    // Due to the imprecision of decimal/ratio based calculations and varying
    // rounding behaviors, there are cases where the spacing adjustment is off
    // by a pixel or two. This adds insurance to these calculations.
    if (pullTooltipBy < 0) {
      pullTooltipBy = 0;
    } else if (pullTooltipBy > tooltipRect.width) {
      pullTooltipBy = tooltipRect.width;
    }

    // prevent small width fluctuations within 0.4px from
    // changing the value below.
    // This really helps for live to prevent the play
    // progress time tooltip from jittering
    pullTooltipBy = Math.round(pullTooltipBy);

    this.el_.style.right = `-${pullTooltipBy}px`;
    this.write(content);
  }

  /**
   * Write the time to the tooltip DOM element.
   *
   * @param {string} content
   *        The formatted time for the tooltip.
   */
  write(content) {
    Dom.textContent(this.el_, content);
  }

  /**
   * Updates the position of the time tooltip relative to the `SeekBar`.
   *
   * @param {Object} seekBarRect
   *        The `ClientRect` for the {@link SeekBar} element.
   *
   * @param {number} seekBarPoint
   *        A number from 0 to 1, representing a horizontal reference point
   *        from the left edge of the {@link SeekBar}
   *
   * @param {number} time
   *        The time to update the tooltip to, not used during live playback
   *
   * @param {Function} cb
   *        A function that will be called during the request animation frame
   *        for tooltips that need to do additional animations from the default
   */
  updateTime(seekBarRect, seekBarPoint, time, cb) {
    this.requestNamedAnimationFrame('TimeTooltip#updateTime', () => {
      let content;
      const duration = this.player_.duration();

      if (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
        const liveWindow = this.player_.liveTracker.liveWindow();
        const secondsBehind = liveWindow - (seekBarPoint * liveWindow);

        content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow);
      } else {
        content = formatTime(time, duration);
      }

      this.update(seekBarRect, seekBarPoint, content);
      if (cb) {
        cb();
      }
    });
  }
}

Component.registerComponent('TimeTooltip', TimeTooltip);
export default TimeTooltip;