Source: lib/util/state_history.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.StateHistory');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. /**
  10. * This class is used to track the time spent in arbitrary states. When told of
  11. * a state, it will assume that state was active until a new state is provided.
  12. * When provided with identical states back-to-back, the existing entry will be
  13. * updated.
  14. *
  15. * @final
  16. */
  17. shaka.util.StateHistory = class {
  18. /** */
  19. constructor() {
  20. /**
  21. * The state that we think is still the current change. It is "open" for
  22. * updating.
  23. *
  24. * @private {?shaka.extern.StateChange}
  25. */
  26. this.open_ = null;
  27. /**
  28. * The stats that are "closed" for updating. The "open" state becomes closed
  29. * once we move to a new state.
  30. *
  31. * @private {!Array.<shaka.extern.StateChange>}
  32. */
  33. this.closed_ = [];
  34. }
  35. /**
  36. * @param {string} state
  37. */
  38. update(state) {
  39. // |open_| will only be |null| when we first call |update|.
  40. if (this.open_ == null) {
  41. this.start_(state);
  42. } else {
  43. this.update_(state);
  44. }
  45. }
  46. /**
  47. * Go through all entries in the history and count how much time was spend in
  48. * the given state.
  49. *
  50. * @param {string} state
  51. * @return {number}
  52. */
  53. getTimeSpentIn(state) {
  54. let sum = 0;
  55. if (this.open_ && this.open_.state == state) {
  56. sum += this.open_.duration;
  57. }
  58. for (const entry of this.closed_) {
  59. sum += entry.state == state ? entry.duration : 0;
  60. }
  61. return sum;
  62. }
  63. /**
  64. * Get a copy of each state change entry in the history. A copy of each entry
  65. * is created to break the reference to the internal data.
  66. *
  67. * @return {!Array.<shaka.extern.StateChange>}
  68. */
  69. getCopy() {
  70. const clone = (entry) => {
  71. return {
  72. timestamp: entry.timestamp,
  73. state: entry.state,
  74. duration: entry.duration,
  75. };
  76. };
  77. const copy = [];
  78. for (const entry of this.closed_) {
  79. copy.push(clone(entry));
  80. }
  81. if (this.open_) {
  82. copy.push(clone(this.open_));
  83. }
  84. return copy;
  85. }
  86. /**
  87. * @param {string} state
  88. * @private
  89. */
  90. start_(state) {
  91. goog.asserts.assert(
  92. this.open_ == null,
  93. 'There must be no open entry in order when we start');
  94. shaka.log.v1('Changing Player state to', state);
  95. this.open_ = {
  96. timestamp: this.getNowInSeconds_(),
  97. state: state,
  98. duration: 0,
  99. };
  100. }
  101. /**
  102. * @param {string} state
  103. * @private
  104. */
  105. update_(state) {
  106. goog.asserts.assert(
  107. this.open_,
  108. 'There must be an open entry in order to update it');
  109. const currentTimeSeconds = this.getNowInSeconds_();
  110. // Always update the duration so that it can always be as accurate as
  111. // possible.
  112. this.open_.duration = currentTimeSeconds - this.open_.timestamp;
  113. // If the state has not changed, there is no need to add a new entry.
  114. if (this.open_.state == state) {
  115. return;
  116. }
  117. // We have changed states, so "close" the open state.
  118. shaka.log.v1('Changing Player state to', state);
  119. this.closed_.push(this.open_);
  120. this.open_ = {
  121. timestamp: currentTimeSeconds,
  122. state: state,
  123. duration: 0,
  124. };
  125. }
  126. /**
  127. * Get the system time in seconds.
  128. *
  129. * @return {number}
  130. * @private
  131. */
  132. getNowInSeconds_() {
  133. return Date.now() / 1000;
  134. }
  135. };