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. /**
  23. * Creates a SegmentIndex.
  24. *
  25. * @param {!Array.<!shaka.media.SegmentReference>} references The list of
  26. * SegmentReferences, which must be sorted first by their start times
  27. * (ascending) and second by their end times (ascending). They must have
  28. * continuous, increasing positions.
  29. *
  30. * @constructor
  31. * @struct
  32. * @implements {shaka.util.IDestroyable}
  33. * @export
  34. */
  35. shaka.media.SegmentIndex = function(references) {
  36. if (goog.DEBUG) {
  37. shaka.media.SegmentIndex.assertCorrectReferences_(references);
  38. }
  39. /** @private {Array.<!shaka.media.SegmentReference>} */
  40. this.references_ = references;
  41. };
  42. /**
  43. * @override
  44. * @export
  45. */
  46. shaka.media.SegmentIndex.prototype.destroy = function() {
  47. this.references_ = null;
  48. return Promise.resolve();
  49. };
  50. /**
  51. * Finds the position of the segment for the given time, in seconds, relative
  52. * to the start of a particular Period. Returns the position of the segment
  53. * with the largest end time if more than one segment is known for the given
  54. * time.
  55. *
  56. * @param {number} time
  57. * @return {?number} The position of the segment, or null
  58. * if the position of the segment could not be determined.
  59. * @export
  60. */
  61. shaka.media.SegmentIndex.prototype.find = function(time) {
  62. // For live streams, searching from the end is faster. For VOD, it balances
  63. // out either way. In both cases, references_.length is small enough that the
  64. // difference isn't huge.
  65. for (let i = this.references_.length - 1; i >= 0; --i) {
  66. let r = this.references_[i];
  67. // Note that a segment ends immediately before the end time.
  68. if ((time >= r.startTime) && (time < r.endTime)) {
  69. return r.position;
  70. }
  71. }
  72. if (this.references_.length && time < this.references_[0].startTime) {
  73. return this.references_[0].position;
  74. }
  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. }
  89. let index = position - this.references_[0].position;
  90. if (index < 0 || index >= this.references_.length) {
  91. return null;
  92. }
  93. return this.references_[index];
  94. };
  95. /**
  96. * Offset all segment references by a fixed amount.
  97. *
  98. * @param {number} offset The amount to add to each segment's start and end
  99. * times.
  100. * @export
  101. */
  102. shaka.media.SegmentIndex.prototype.offset = function(offset) {
  103. for (let i = 0; i < this.references_.length; ++i) {
  104. this.references_[i].startTime += offset;
  105. this.references_[i].endTime += offset;
  106. }
  107. };
  108. /**
  109. * Merges the given SegmentReferences. Supports extending the original
  110. * references only. Will not replace old references or interleave new ones.
  111. *
  112. * @param {!Array.<!shaka.media.SegmentReference>} references The list of
  113. * SegmentReferences, which must be sorted first by their start times
  114. * (ascending) and second by their end times (ascending). They must have
  115. * continuous, increasing positions.
  116. * @export
  117. */
  118. shaka.media.SegmentIndex.prototype.merge = function(references) {
  119. if (goog.DEBUG) {
  120. shaka.media.SegmentIndex.assertCorrectReferences_(references);
  121. }
  122. let newReferences = [];
  123. let i = 0;
  124. let j = 0;
  125. while ((i < this.references_.length) && (j < references.length)) {
  126. let r1 = this.references_[i];
  127. let r2 = references[j];
  128. if (r1.startTime < r2.startTime) {
  129. newReferences.push(r1);
  130. i++;
  131. } else if (r1.startTime > r2.startTime) {
  132. if (i == 0) {
  133. // If the reference appears before any existing reference, it may have
  134. // been evicted before; in this case, simply add it back and it will be
  135. // evicted again later.
  136. newReferences.push(r2);
  137. } else {
  138. // Drop the new reference if it would have to be interleaved with the
  139. // old one. Issue a warning, since this is not a supported update.
  140. shaka.log.warning('Refusing to rewrite original references on update!');
  141. }
  142. j++;
  143. } else {
  144. // When period is changed, fit() will expand the last segment to the start
  145. // of the next period. So, it is valid to have end time updated to the
  146. // last segment reference in a period.
  147. if (Math.abs(r1.endTime - r2.endTime) > 0.1) {
  148. goog.asserts.assert(r2.endTime > r1.endTime &&
  149. i == this.references_.length - 1 &&
  150. j == references.length - 1,
  151. 'This should be an update of the last segment in a period');
  152. let r = new shaka.media.SegmentReference(r1.position,
  153. r2.startTime, r2.endTime, r2.getUris, r2.startByte, r2.endByte);
  154. newReferences.push(r);
  155. } else {
  156. // Drop the new reference if there's an old reference with the
  157. // same time.
  158. newReferences.push(r1);
  159. }
  160. i++;
  161. j++;
  162. }
  163. }
  164. while (i < this.references_.length) {
  165. newReferences.push(this.references_[i++]);
  166. }
  167. if (newReferences.length) {
  168. // The rest of these references may need to be renumbered.
  169. let nextPosition = newReferences[newReferences.length - 1].position + 1;
  170. while (j < references.length) {
  171. let r = references[j++];
  172. let r2 = new shaka.media.SegmentReference(nextPosition++,
  173. r.startTime, r.endTime, r.getUris, r.startByte, r.endByte);
  174. newReferences.push(r2);
  175. }
  176. } else {
  177. newReferences = references;
  178. }
  179. if (goog.DEBUG) {
  180. shaka.media.SegmentIndex.assertCorrectReferences_(newReferences);
  181. }
  182. this.references_ = newReferences;
  183. };
  184. /**
  185. * Replace existing references with new ones, without merging.
  186. *
  187. * @param {!Array.<!shaka.media.SegmentReference>} newReferences
  188. */
  189. shaka.media.SegmentIndex.prototype.replace = function(newReferences) {
  190. if (goog.DEBUG) {
  191. shaka.media.SegmentIndex.assertCorrectReferences_(newReferences);
  192. }
  193. this.references_ = newReferences;
  194. };
  195. /**
  196. * Removes all SegmentReferences that end before the given time.
  197. *
  198. * @param {number} time The time in seconds.
  199. * @export
  200. */
  201. shaka.media.SegmentIndex.prototype.evict = function(time) {
  202. for (let i = 0; i < this.references_.length; ++i) {
  203. if (this.references_[i].endTime > time) {
  204. this.references_.splice(0, i);
  205. return;
  206. }
  207. }
  208. this.references_ = [];
  209. };
  210. /**
  211. * Expands the first SegmentReference so it begins at the start of its Period
  212. * if it already begins close to the start of its Period.
  213. *
  214. * Also expands or contracts the last SegmentReference so it ends at the end of
  215. * its Period.
  216. *
  217. * Do not call on the last period of a live presentation (unknown duration).
  218. * It is okay to call on the other periods of a live presentation, where the
  219. * duration is known and another period has been added.
  220. *
  221. * @param {?number} periodDuration
  222. */
  223. shaka.media.SegmentIndex.prototype.fit = function(periodDuration) {
  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. let 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. let 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. }
  248. // Adjust the last SegmentReference.
  249. let lastReference = this.references_[this.references_.length - 1];
  250. this.references_[this.references_.length - 1] =
  251. new shaka.media.SegmentReference(
  252. lastReference.position,
  253. lastReference.startTime,
  254. /* endTime */ periodDuration,
  255. lastReference.getUris,
  256. lastReference.startByte,
  257. lastReference.endByte);
  258. };
  259. if (goog.DEBUG) {
  260. /**
  261. * Asserts that the given SegmentReferences are sorted and have continuous,
  262. * increasing positions.
  263. *
  264. * @param {!Array.<shaka.media.SegmentReference>} references
  265. * @private
  266. */
  267. shaka.media.SegmentIndex.assertCorrectReferences_ = function(references) {
  268. goog.asserts.assert(references.every(function(r2, i) {
  269. if (i == 0) return true;
  270. let r1 = references[i - 1];
  271. if (r2.position != r1.position + 1) return false;
  272. if (r1.startTime < r2.startTime) {
  273. return true;
  274. } else if (r1.startTime > r2.startTime) {
  275. return false;
  276. } else {
  277. if (r1.endTime <= r2.endTime) {
  278. return true;
  279. } else {
  280. return false;
  281. }
  282. }
  283. }), 'SegmentReferences are incorrect');
  284. };
  285. }