ewma-bandwidth-estimator.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /*
  2. * EWMA Bandwidth Estimator
  3. * - heavily inspired from shaka-player
  4. * Tracks bandwidth samples and estimates available bandwidth.
  5. * Based on the minimum of two exponentially-weighted moving averages with
  6. * different half-lives.
  7. */
  8. import EWMA from '../utils/ewma';
  9. class EwmaBandWidthEstimator {
  10. private defaultEstimate_: number;
  11. private minWeight_: number;
  12. private minDelayMs_: number;
  13. private slow_: EWMA;
  14. private fast_: EWMA;
  15. private defaultTTFB_: number;
  16. private ttfb_: EWMA;
  17. constructor(
  18. slow: number,
  19. fast: number,
  20. defaultEstimate: number,
  21. defaultTTFB: number = 100,
  22. ) {
  23. this.defaultEstimate_ = defaultEstimate;
  24. this.minWeight_ = 0.001;
  25. this.minDelayMs_ = 50;
  26. this.slow_ = new EWMA(slow);
  27. this.fast_ = new EWMA(fast);
  28. this.defaultTTFB_ = defaultTTFB;
  29. this.ttfb_ = new EWMA(slow);
  30. }
  31. update(slow: number, fast: number) {
  32. const { slow_, fast_, ttfb_ } = this;
  33. if (slow_.halfLife !== slow) {
  34. this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight());
  35. }
  36. if (fast_.halfLife !== fast) {
  37. this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight());
  38. }
  39. if (ttfb_.halfLife !== slow) {
  40. this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight());
  41. }
  42. }
  43. sample(durationMs: number, numBytes: number) {
  44. durationMs = Math.max(durationMs, this.minDelayMs_);
  45. const numBits = 8 * numBytes;
  46. // weight is duration in seconds
  47. const durationS = durationMs / 1000;
  48. // value is bandwidth in bits/s
  49. const bandwidthInBps = numBits / durationS;
  50. this.fast_.sample(durationS, bandwidthInBps);
  51. this.slow_.sample(durationS, bandwidthInBps);
  52. }
  53. sampleTTFB(ttfb: number) {
  54. // weight is frequency curve applied to TTFB in seconds
  55. // (longer times have less weight with expected input under 1 second)
  56. const seconds = ttfb / 1000;
  57. const weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2);
  58. this.ttfb_.sample(weight, Math.max(ttfb, 5));
  59. }
  60. canEstimate(): boolean {
  61. return this.fast_.getTotalWeight() >= this.minWeight_;
  62. }
  63. getEstimate(): number {
  64. if (this.canEstimate()) {
  65. // console.log('slow estimate:'+ Math.round(this.slow_.getEstimate()));
  66. // console.log('fast estimate:'+ Math.round(this.fast_.getEstimate()));
  67. // Take the minimum of these two estimates. This should have the effect of
  68. // adapting down quickly, but up more slowly.
  69. return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate());
  70. } else {
  71. return this.defaultEstimate_;
  72. }
  73. }
  74. getEstimateTTFB(): number {
  75. if (this.ttfb_.getTotalWeight() >= this.minWeight_) {
  76. return this.ttfb_.getEstimate();
  77. } else {
  78. return this.defaultTTFB_;
  79. }
  80. }
  81. get defaultEstimate(): number {
  82. return this.defaultEstimate_;
  83. }
  84. destroy() {}
  85. }
  86. export default EwmaBandWidthEstimator;