Source: lib/media/segment_index.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.media.SegmentIndex');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.log');
  20. goog.require('shaka.media.SegmentReference');
  21. goog.require('shaka.util.IDestroyable');
  22. goog.require('shaka.util.ManifestParserUtils');
  23. /**
  24. * Creates a SegmentIndex.
  25. *
  26. * @param {!Array.<!shaka.media.SegmentReference>} references The list of
  27. * SegmentReferences, which must be sorted first by their start times
  28. * (ascending) and second by their end times (ascending), and have
  29. * continuous, increasing positions.
  30. *
  31. * @constructor
  32. * @struct
  33. * @implements {shaka.util.IDestroyable}
  34. * @export
  35. */
  36. shaka.media.SegmentIndex = function(references) {
  37. if (goog.DEBUG) {
  38. shaka.media.SegmentIndex.assertCorrectReferences_(references);
  39. }
  40. /** @private {Array.<!shaka.media.SegmentReference>} */
  41. this.references_ = references;
  42. };
  43. /**
  44. * @override
  45. * @export
  46. */
  47. shaka.media.SegmentIndex.prototype.destroy = function() {
  48. this.references_ = null;
  49. return Promise.resolve();
  50. };
  51. /**
  52. * Finds the position of the segment for the given time, in seconds, relative
  53. * to the start of a particular Period. Returns the position of the segment
  54. * with the largest end time if more than one segment is known for the given
  55. * time.
  56. *
  57. * @param {number} time
  58. * @return {?number} The position of the segment, or null
  59. * if the position of the segment could not be determined.
  60. * @export
  61. */
  62. shaka.media.SegmentIndex.prototype.find = function(time) {
  63. // For live streams, searching from the end is faster. For VOD, it balances
  64. // out either way. In both cases, references_.length is small enough that the
  65. // difference isn't huge.
  66. for (var i = this.references_.length - 1; i >= 0; --i) {
  67. var r = this.references_[i];
  68. // Note that a segment ends immediately before the end time.
  69. if ((time >= r.startTime) && (time < r.endTime)) {
  70. return r.position;
  71. }
  72. }
  73. if (this.references_.length && time < this.references_[0].startTime)
  74. return this.references_[0].position;
  75. return null;
  76. };
  77. /**
  78. * Gets the SegmentReference for the segment at the given position.
  79. *
  80. * @param {number} position The position of the segment.
  81. * @return {shaka.media.SegmentReference} The SegmentReference, or null if
  82. * no such SegmentReference exists.
  83. * @export
  84. */
  85. shaka.media.SegmentIndex.prototype.get = function(position) {
  86. if (this.references_.length == 0)
  87. return null;
  88. var index = position - this.references_[0].position;
  89. if (index < 0 || index >= this.references_.length)
  90. return null;
  91. return this.references_[index];
  92. };
  93. /**
  94. * Offset all segment references by a fixed amount.
  95. *
  96. * @param {number} offset The amount to add to each segment's start and end
  97. * times.
  98. * @export
  99. */
  100. shaka.media.SegmentIndex.prototype.offset = function(offset) {
  101. for (var i = 0; i < this.references_.length; ++i) {
  102. this.references_[i].startTime += offset;
  103. this.references_[i].endTime += offset;
  104. }
  105. };
  106. /**
  107. * Merges the given SegmentReferences. Supports extending the original
  108. * references only. Will not replace old references or interleave new ones.
  109. *
  110. * @param {!Array.<!shaka.media.SegmentReference>} references The list of
  111. * SegmentReferences, which must be sorted first by their start times
  112. * (ascending) and second by their end times (ascending), and have
  113. * continuous, increasing positions.
  114. * @export
  115. */
  116. shaka.media.SegmentIndex.prototype.merge = function(references) {
  117. if (goog.DEBUG) {
  118. shaka.media.SegmentIndex.assertCorrectReferences_(references);
  119. }
  120. var newReferences = [];
  121. var i = 0;
  122. var j = 0;
  123. while ((i < this.references_.length) && (j < references.length)) {
  124. var r1 = this.references_[i];
  125. var r2 = references[j];
  126. if (r1.startTime < r2.startTime) {
  127. newReferences.push(r1);
  128. i++;
  129. } else if (r1.startTime > r2.startTime) {
  130. if (i == 0) {
  131. // If the reference appears before any existing reference, it may have
  132. // been evicted before; in this case, simply add it back and it will be
  133. // evicted again later.
  134. newReferences.push(r2);
  135. } else {
  136. // Drop the new reference if it would have to be interleaved with the
  137. // old one. Issue a warning, since this is not a supported update.
  138. shaka.log.warning('Refusing to rewrite original references on update!');
  139. }
  140. j++;
  141. } else {
  142. // When period is changed, fit() will expand the last segment to the start
  143. // of the next period. So, it is valid to have end time updated to the
  144. // last segment reference in a period.
  145. if (Math.abs(r1.endTime - r2.endTime) > 0.1) {
  146. goog.asserts.assert(r2.endTime > r1.endTime &&
  147. i == this.references_.length - 1 &&
  148. j == references.length - 1,
  149. 'This should be an update of the last segment in a period');
  150. var r = new shaka.media.SegmentReference(r1.position,
  151. r2.startTime, r2.endTime, r2.getUris, r2.startByte, r2.endByte);
  152. newReferences.push(r);
  153. } else {
  154. // Drop the new reference if there's an old reference with the
  155. // same time.
  156. newReferences.push(r1);
  157. }
  158. i++;
  159. j++;
  160. }
  161. }
  162. while (i < this.references_.length) {
  163. newReferences.push(this.references_[i++]);
  164. }
  165. if (newReferences.length) {
  166. // The rest of these refs may need to be renumbered.
  167. var nextPosition = newReferences[newReferences.length - 1].position + 1;
  168. while (j < references.length) {
  169. var r = references[j++];
  170. var r2 = new shaka.media.SegmentReference(nextPosition++,
  171. r.startTime, r.endTime, r.getUris, r.startByte, r.endByte);
  172. newReferences.push(r2);
  173. }
  174. } else {
  175. newReferences = references;
  176. }
  177. if (goog.DEBUG) {
  178. shaka.media.SegmentIndex.assertCorrectReferences_(newReferences);
  179. }
  180. this.references_ = newReferences;
  181. };
  182. /**
  183. * Replace existing references with new ones, without merging.
  184. *
  185. * @param {!Array.<!shaka.media.SegmentReference>} newReferences
  186. */
  187. shaka.media.SegmentIndex.prototype.replace = function(newReferences) {
  188. if (goog.DEBUG) {
  189. shaka.media.SegmentIndex.assertCorrectReferences_(newReferences);
  190. }
  191. this.references_ = newReferences;
  192. };
  193. /**
  194. * Removes all SegmentReferences that end before the given time.
  195. *
  196. * @param {number} time The time in seconds.
  197. * @export
  198. */
  199. shaka.media.SegmentIndex.prototype.evict = function(time) {
  200. for (var i = 0; i < this.references_.length; ++i) {
  201. if (this.references_[i].endTime > time) {
  202. this.references_.splice(0, i);
  203. return;
  204. }
  205. }
  206. this.references_ = [];
  207. };
  208. /**
  209. * Expands the first SegmentReference so it begins at the start of its Period
  210. * if it already begins close to the start of its Period.
  211. *
  212. * Also expands or contracts the last SegmentReference so it ends at the end of
  213. * its Period.
  214. *
  215. * Do not call on the last period of a live presentation (unknown duration).
  216. * It is okay to call on the other periods of a live presentation, where the
  217. * duration is known and another period has been added.
  218. *
  219. * @param {?number} periodDuration
  220. */
  221. shaka.media.SegmentIndex.prototype.fit = function(periodDuration) {
  222. /** @const {number} */
  223. var tolerance = shaka.util.ManifestParserUtils.GAP_OVERLAP_TOLERANCE_SECONDS;
  224. goog.asserts.assert(periodDuration != null,
  225. 'Period duration must be known for static content!');
  226. goog.asserts.assert(periodDuration != Infinity,
  227. 'Period duration must be finite for static content!');
  228. // Trim out references we will never use.
  229. while (this.references_.length) {
  230. var lastReference = this.references_[this.references_.length - 1];
  231. if (lastReference.startTime >= periodDuration) {
  232. this.references_.pop();
  233. } else {
  234. break;
  235. }
  236. }
  237. while (this.references_.length) {
  238. var firstReference = this.references_[0];
  239. if (firstReference.endTime <= 0) {
  240. this.references_.shift();
  241. } else {
  242. break;
  243. }
  244. }
  245. if (this.references_.length == 0)
  246. return;
  247. // Adjust the first SegmentReference if the start time is smaller than the gap
  248. // tolerance (including negative).
  249. var firstReference = this.references_[0];
  250. if (firstReference.startTime < tolerance) {
  251. this.references_[0] =
  252. new shaka.media.SegmentReference(
  253. firstReference.position,
  254. /* startTime */ 0,
  255. firstReference.endTime,
  256. firstReference.getUris,
  257. firstReference.startByte,
  258. firstReference.endByte);
  259. }
  260. // Adjust the last SegmentReference.
  261. var lastReference = this.references_[this.references_.length - 1];
  262. this.references_[this.references_.length - 1] =
  263. new shaka.media.SegmentReference(
  264. lastReference.position,
  265. lastReference.startTime,
  266. /* endTime */ periodDuration,
  267. lastReference.getUris,
  268. lastReference.startByte,
  269. lastReference.endByte);
  270. };
  271. if (goog.DEBUG) {
  272. /**
  273. * Asserts that the given SegmentReferences are sorted and have continuous,
  274. * increasing positions.
  275. *
  276. * @param {!Array.<shaka.media.SegmentReference>} references
  277. * @private
  278. */
  279. shaka.media.SegmentIndex.assertCorrectReferences_ = function(references) {
  280. goog.asserts.assert(references.every(function(r2, i) {
  281. if (i == 0) return true;
  282. var r1 = references[i - 1];
  283. if (r2.position != r1.position + 1) return false;
  284. if (r1.startTime < r2.startTime) {
  285. return true;
  286. } else if (r1.startTime > r2.startTime) {
  287. return false;
  288. } else {
  289. if (r1.endTime <= r2.endTime) {
  290. return true;
  291. } else {
  292. return false;
  293. }
  294. }
  295. }), 'SegmentReferences are incorrect');
  296. };
  297. }