Source: lib/media/segment_index.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.MetaSegmentIndex');
  7. goog.provide('shaka.media.SegmentIndex');
  8. goog.provide('shaka.media.SegmentIterator');
  9. goog.require('goog.asserts');
  10. goog.require('shaka.log');
  11. goog.require('shaka.media.SegmentReference');
  12. goog.require('shaka.util.IReleasable');
  13. goog.require('shaka.util.Timer');
  14. /**
  15. * SegmentIndex.
  16. *
  17. * @implements {shaka.extern.SegmentIndex}
  18. * @implements {shaka.util.IReleasable}
  19. * @implements {Iterable<?shaka.media.SegmentReference>}
  20. * @export
  21. */
  22. shaka.media.SegmentIndex = class {
  23. /**
  24. * @param {!Array<!shaka.media.SegmentReference>} references The list of
  25. * SegmentReferences, which must be sorted first by their start times
  26. * (ascending) and second by their end times (ascending).
  27. */
  28. constructor(references) {
  29. if (goog.DEBUG) {
  30. shaka.media.SegmentIndex.assertCorrectReferences_(references);
  31. }
  32. /** @protected {!Array<!shaka.media.SegmentReference>} */
  33. this.references = references;
  34. /** @private {shaka.util.Timer} */
  35. this.timer_ = null;
  36. /**
  37. * The number of references that have been removed from the front of the
  38. * array. Used to create stable positions in the find/get APIs.
  39. *
  40. * @protected {number}
  41. */
  42. this.numEvicted_ = 0;
  43. /** @private {boolean} */
  44. this.immutable_ = false;
  45. }
  46. /**
  47. * Get immutability
  48. *
  49. * @return {boolean}
  50. */
  51. getIsImmutable() {
  52. return this.immutable_;
  53. }
  54. /**
  55. * @override
  56. * @export
  57. */
  58. getNumReferences() {
  59. return this.references.length;
  60. }
  61. /**
  62. * @override
  63. * @export
  64. */
  65. getNumEvicted() {
  66. return this.numEvicted_;
  67. }
  68. /**
  69. * @override
  70. * @export
  71. */
  72. release() {
  73. if (this.immutable_) {
  74. return;
  75. }
  76. this.references = [];
  77. if (this.timer_) {
  78. this.timer_.stop();
  79. }
  80. this.timer_ = null;
  81. }
  82. /**
  83. * Marks the index as immutable. Segments cannot be added or removed after
  84. * this point. This doesn't affect the references themselves. This also
  85. * makes the destroy/release methods do nothing.
  86. *
  87. * This is mainly for testing.
  88. *
  89. * @export
  90. */
  91. markImmutable() {
  92. this.immutable_ = true;
  93. }
  94. /**
  95. * Iterates over all top-level segment references in this segment index.
  96. * @param {function(!shaka.media.SegmentReference)} fn
  97. */
  98. forEachTopLevelReference(fn) {
  99. for (const reference of this.references) {
  100. fn(reference);
  101. }
  102. }
  103. /**
  104. * Return the earliest reference, or null if empty.
  105. * @return {shaka.media.SegmentReference}
  106. */
  107. earliestReference() {
  108. return this.references[0] || null;
  109. }
  110. /**
  111. * Drop the first N references.
  112. * Used in early HLS synchronization, and does not count as eviction.
  113. * @param {number} n
  114. */
  115. dropFirstReferences(n) {
  116. this.references.splice(0, n);
  117. }
  118. /**
  119. * @override
  120. * @export
  121. */
  122. find(time) {
  123. // For live streams, searching from the end is faster. For VOD, it balances
  124. // out either way. In both cases, references.length is small enough that
  125. // the difference isn't huge.
  126. const lastReferenceIndex = this.references.length - 1;
  127. for (let i = lastReferenceIndex; i >= 0; --i) {
  128. const r = this.references[i];
  129. const start = r.startTime;
  130. // A rounding error can cause /time/ to equal e.endTime or fall in between
  131. // the references by a fraction of a second. To account for this, we use
  132. // the start of the next segment as /end/, unless this is the last
  133. // reference, in which case we use its end time as /end/.
  134. const end = i < lastReferenceIndex ?
  135. this.references[i + 1].startTime : r.endTime;
  136. // Note that a segment ends immediately before the end time.
  137. if ((time >= start) && (time < end)) {
  138. return i + this.numEvicted_;
  139. }
  140. }
  141. if (this.references.length && time < this.references[0].startTime) {
  142. return this.numEvicted_;
  143. }
  144. return null;
  145. }
  146. /**
  147. * @override
  148. * @export
  149. */
  150. get(position) {
  151. if (this.references.length == 0) {
  152. return null;
  153. }
  154. const index = position - this.numEvicted_;
  155. if (index < 0 || index >= this.references.length) {
  156. return null;
  157. }
  158. return this.references[index];
  159. }
  160. /**
  161. * Offset all segment references by a fixed amount.
  162. *
  163. * @param {number} offset The amount to add to each segment's start and end
  164. * times.
  165. * @export
  166. */
  167. offset(offset) {
  168. if (!this.immutable_) {
  169. for (const ref of this.references) {
  170. ref.offset(offset);
  171. }
  172. }
  173. }
  174. /**
  175. * Merges the given SegmentReferences. Supports extending the original
  176. * references only. Will replace old references with equivalent new ones, and
  177. * keep any unique old ones.
  178. *
  179. * Used, for example, by the DASH and HLS parser, where manifests may not list
  180. * all available references, so we must keep available references in memory to
  181. * fill the availability window.
  182. *
  183. * @param {!Array<!shaka.media.SegmentReference>} references The list of
  184. * SegmentReferences, which must be sorted first by their start times
  185. * (ascending) and second by their end times (ascending).
  186. */
  187. merge(references) {
  188. if (goog.DEBUG) {
  189. shaka.media.SegmentIndex.assertCorrectReferences_(references);
  190. }
  191. if (this.immutable_) {
  192. return;
  193. }
  194. if (!references.length) {
  195. return;
  196. }
  197. // Partial segments are used for live edge, and should be removed when they
  198. // get older. Remove the old SegmentReferences after the first new
  199. // reference's start time.
  200. // Use times rounded to the millisecond for filtering purposes, so that
  201. // tiny rounding errors will not result in duplicate segments in the index.
  202. const firstStartTime = Math.round(references[0].startTime * 1000) / 1000;
  203. this.references = this.references.filter((r) => {
  204. return (Math.round(r.startTime * 1000) / 1000) < firstStartTime;
  205. });
  206. this.references.push(...references);
  207. if (goog.DEBUG) {
  208. shaka.media.SegmentIndex.assertCorrectReferences_(this.references);
  209. }
  210. }
  211. /**
  212. * Merges the given SegmentReferences and evicts the ones that end before the
  213. * given time. Supports extending the original references only.
  214. * Will not replace old references or interleave new ones.
  215. * Used, for example, by the DASH and HLS parser, where manifests may not list
  216. * all available references, so we must keep available references in memory to
  217. * fill the availability window.
  218. *
  219. * @param {!Array<!shaka.media.SegmentReference>} references The list of
  220. * SegmentReferences, which must be sorted first by their start times
  221. * (ascending) and second by their end times (ascending).
  222. * @param {number} windowStart The start of the availability window to filter
  223. * out the references that are no longer available.
  224. * @export
  225. */
  226. mergeAndEvict(references, windowStart) {
  227. // Filter out the references that are no longer available to avoid
  228. // repeatedly evicting them and messing up eviction count.
  229. references = references.filter((r) => {
  230. return r.endTime > windowStart &&
  231. (this.references.length == 0 ||
  232. r.endTime > this.references[0].startTime);
  233. });
  234. const oldFirstRef = this.references[0];
  235. this.merge(references);
  236. const newFirstRef = this.references[0];
  237. if (oldFirstRef) {
  238. // We don't compare the actual ref, since the object could legitimately be
  239. // replaced with an equivalent. Even the URIs could change due to
  240. // load-balancing actions taken by the server. However, if the time
  241. // changes, its not an equivalent reference.
  242. goog.asserts.assert(oldFirstRef.startTime == newFirstRef.startTime,
  243. 'SegmentIndex.merge should not change the first reference time!');
  244. }
  245. this.evict(windowStart);
  246. }
  247. /**
  248. * Removes all SegmentReferences that end before the given time.
  249. *
  250. * @param {number} time The time in seconds.
  251. * @export
  252. */
  253. evict(time) {
  254. if (this.immutable_) {
  255. return;
  256. }
  257. const oldSize = this.references.length;
  258. this.references = this.references.filter((ref) => ref.endTime > time);
  259. const newSize = this.references.length;
  260. const diff = oldSize - newSize;
  261. // Tracking the number of evicted refs will keep their "positions" stable
  262. // for the caller.
  263. this.numEvicted_ += diff;
  264. }
  265. /**
  266. * Drops references that start after windowEnd, or end before windowStart,
  267. * and contracts the last reference so that it ends at windowEnd.
  268. *
  269. * Do not call on the last period of a live presentation (unknown duration).
  270. * It is okay to call on the other periods of a live presentation, where the
  271. * duration is known and another period has been added.
  272. *
  273. * @param {number} windowStart
  274. * @param {?number} windowEnd
  275. * @param {boolean=} isNew Whether this is a new SegmentIndex and we shouldn't
  276. * update the number of evicted elements.
  277. * @export
  278. */
  279. fit(windowStart, windowEnd, isNew = false) {
  280. goog.asserts.assert(windowEnd != null,
  281. 'Content duration must be known for static content!');
  282. goog.asserts.assert(windowEnd != Infinity,
  283. 'Content duration must be finite for static content!');
  284. if (this.immutable_) {
  285. return;
  286. }
  287. // Trim out references we will never use.
  288. while (this.references.length) {
  289. const lastReference = this.references[this.references.length - 1];
  290. if (lastReference.startTime >= windowEnd) {
  291. this.references.pop();
  292. } else {
  293. break;
  294. }
  295. }
  296. while (this.references.length) {
  297. const firstReference = this.references[0];
  298. if (firstReference.endTime <= windowStart) {
  299. this.references.shift();
  300. if (!isNew) {
  301. this.numEvicted_++;
  302. }
  303. } else {
  304. break;
  305. }
  306. }
  307. if (this.references.length == 0) {
  308. return;
  309. }
  310. // Adjust the last SegmentReference.
  311. const lastReference = this.references[this.references.length - 1];
  312. const newReference = new shaka.media.SegmentReference(
  313. lastReference.startTime,
  314. /* endTime= */ windowEnd,
  315. lastReference.getUrisInner,
  316. lastReference.startByte,
  317. lastReference.endByte,
  318. lastReference.initSegmentReference,
  319. lastReference.timestampOffset,
  320. lastReference.appendWindowStart,
  321. lastReference.appendWindowEnd,
  322. lastReference.partialReferences,
  323. lastReference.tilesLayout,
  324. lastReference.tileDuration,
  325. lastReference.syncTime,
  326. lastReference.status,
  327. lastReference.aesKey,
  328. );
  329. newReference.mimeType = lastReference.mimeType;
  330. newReference.codecs = lastReference.codecs;
  331. newReference.discontinuitySequence = lastReference.discontinuitySequence;
  332. this.references[this.references.length - 1] = newReference;
  333. }
  334. /**
  335. * Updates the references every so often. Stops when the references list
  336. * returned by the callback is null.
  337. *
  338. * @param {number} interval The interval in seconds.
  339. * @param {function(): Array<shaka.media.SegmentReference>} updateCallback
  340. * @export
  341. */
  342. updateEvery(interval, updateCallback) {
  343. goog.asserts.assert(!this.timer_, 'SegmentIndex timer already started!');
  344. if (this.immutable_) {
  345. return;
  346. }
  347. if (this.timer_) {
  348. this.timer_.stop();
  349. }
  350. this.timer_ = new shaka.util.Timer(() => {
  351. const references = updateCallback();
  352. if (references) {
  353. this.references.push(...references);
  354. } else {
  355. this.timer_.stop();
  356. this.timer_ = null;
  357. }
  358. });
  359. this.timer_.tickEvery(interval);
  360. }
  361. /**
  362. * @return {!shaka.media.SegmentIterator}
  363. * @override
  364. */
  365. [Symbol.iterator]() {
  366. const iter = this.getIteratorForTime(0);
  367. goog.asserts.assert(iter != null, 'Iterator for 0 should never be null!');
  368. return iter;
  369. }
  370. /**
  371. * Returns a new iterator that initially points to the segment that contains
  372. * the given time, or the nearest independent segment before it.
  373. *
  374. * Like the normal iterator, next() must be called first to get to the first
  375. * element. Returns null if we do not find a segment at the
  376. * requested time.
  377. *
  378. * The first segment returned by the iterator _MUST_ be an independent
  379. * segment. Assumes that only partial references can be dependent, based on
  380. * RFC 8216 rev 13, section 8.1: "Each (non-Partial) Media Segment in a Media
  381. * Playlist will contain at least one independent frame."
  382. *
  383. * @param {number} time
  384. * @param {boolean=} allowNonIndependent
  385. * @param {boolean=} reverse
  386. * @return {?shaka.media.SegmentIterator}
  387. * @export
  388. */
  389. getIteratorForTime(time, allowNonIndependent = false, reverse = false) {
  390. let index = this.find(time);
  391. if (index == null) {
  392. return null;
  393. }
  394. const ref = this.get(index);
  395. // Adjust index to point to previous or next index (if reversed), so first
  396. // next() call will traverse in proper direction.
  397. if (!reverse) {
  398. index--;
  399. } else {
  400. index++;
  401. }
  402. let partialSegmentIndex = -1;
  403. if (ref && ref.hasPartialSegments()) {
  404. // Look for a partial SegmentReference.
  405. for (let i = ref.partialReferences.length - 1; i >= 0; --i) {
  406. let r = ref.partialReferences[i];
  407. // Note that a segment ends immediately before the end time.
  408. if ((time >= r.startTime) && (time < r.endTime)) {
  409. if (!allowNonIndependent) {
  410. // Find an independent partial segment by moving backwards.
  411. while (i && (!r.isIndependent())) {
  412. i--;
  413. r = ref.partialReferences[i];
  414. }
  415. if (!r.isIndependent()) {
  416. shaka.log.alwaysError('No independent partial segment found!');
  417. return null;
  418. }
  419. }
  420. // Call to next() should move the partial segment, not the full
  421. // segment.
  422. if (reverse) {
  423. index--;
  424. } else {
  425. index++;
  426. }
  427. partialSegmentIndex = i - 1;
  428. break;
  429. }
  430. }
  431. }
  432. return new shaka.media.SegmentIterator(
  433. this, index, partialSegmentIndex, reverse);
  434. }
  435. /**
  436. * @return {boolean}
  437. */
  438. isEmpty() {
  439. return this.getNumReferences() == 0;
  440. }
  441. /**
  442. * Return -1 unless this is a TimelineSegmentIndex.
  443. *
  444. * @return {number}
  445. */
  446. continuityTimeline() {
  447. return -1;
  448. }
  449. /**
  450. * Create a SegmentIndex for a single segment of the given start time and
  451. * duration at the given URIs.
  452. *
  453. * @param {number} startTime
  454. * @param {number} duration
  455. * @param {!Array<string>} uris
  456. * @return {!shaka.media.SegmentIndex}
  457. * @export
  458. */
  459. static forSingleSegment(startTime, duration, uris) {
  460. const reference = new shaka.media.SegmentReference(
  461. /* startTime= */ startTime,
  462. /* endTime= */ startTime + duration,
  463. /* getUris= */ () => uris,
  464. /* startByte= */ 0,
  465. /* endByte= */ null,
  466. /* initSegmentReference= */ null,
  467. /* presentationTimeOffset= */ startTime,
  468. /* appendWindowStart= */ startTime,
  469. /* appendWindowEnd= */ startTime + duration);
  470. return new shaka.media.SegmentIndex([reference]);
  471. }
  472. };
  473. if (goog.DEBUG) {
  474. /**
  475. * Asserts that the given SegmentReferences are sorted.
  476. *
  477. * @param {!Array<shaka.media.SegmentReference>} references
  478. * @private
  479. */
  480. shaka.media.SegmentIndex.assertCorrectReferences_ = (references) => {
  481. goog.asserts.assert(references.every((r2, i) => {
  482. if (i == 0) {
  483. return true;
  484. }
  485. const r1 = references[i - 1];
  486. if (r1.startTime < r2.startTime) {
  487. return true;
  488. } else if (r1.startTime > r2.startTime) {
  489. return false;
  490. } else {
  491. if (r1.endTime <= r2.endTime) {
  492. return true;
  493. } else {
  494. return false;
  495. }
  496. }
  497. }), 'SegmentReferences are incorrect');
  498. };
  499. }
  500. /**
  501. * An iterator over a SegmentIndex's references.
  502. *
  503. * @implements {Iterator<?shaka.media.SegmentReference>}
  504. * @export
  505. */
  506. shaka.media.SegmentIterator = class {
  507. /**
  508. * @param {!shaka.media.SegmentIndex} segmentIndex
  509. * @param {number} index
  510. * @param {number} partialSegmentIndex
  511. * @param {boolean} reverse
  512. */
  513. constructor(segmentIndex, index, partialSegmentIndex, reverse) {
  514. /** @private {!shaka.media.SegmentIndex} */
  515. this.segmentIndex_ = segmentIndex;
  516. /** @private {number} */
  517. this.currentPosition_ = index;
  518. /** @private {number} */
  519. this.currentPartialPosition_ = partialSegmentIndex;
  520. /** @private {boolean} */
  521. this.reverse = reverse;
  522. }
  523. /**
  524. * @param {boolean} reverse
  525. * @export
  526. */
  527. setReverse(reverse) {
  528. this.reverse = reverse;
  529. }
  530. /**
  531. * @return {number}
  532. * @export
  533. */
  534. currentPosition() {
  535. return this.currentPosition_;
  536. }
  537. /**
  538. * @return {?shaka.media.SegmentReference}
  539. * @export
  540. */
  541. current() {
  542. let ref = this.segmentIndex_.get(this.currentPosition_);
  543. // When we advance past the end of partial references in next(), then add
  544. // new references in merge(), the pointers may not make sense any more.
  545. // This adjusts the invalid pointer values to point to the next newly added
  546. // segment or partial segment.
  547. if (ref && ref.hasPartialSegments() && ref.hasAllPartialSegments() &&
  548. this.currentPartialPosition_ >= ref.partialReferences.length) {
  549. this.currentPosition_++;
  550. this.currentPartialPosition_ = 0;
  551. ref = this.segmentIndex_.get(this.currentPosition_);
  552. }
  553. // If the regular segment contains partial segments, get the current
  554. // partial SegmentReference.
  555. if (ref && ref.hasPartialSegments()) {
  556. const partial = ref.partialReferences[this.currentPartialPosition_];
  557. return partial;
  558. }
  559. return ref;
  560. }
  561. /**
  562. * @override
  563. * @export
  564. * @return {!IIterableResult<?shaka.media.SegmentReference>}
  565. */
  566. next() {
  567. const ref = this.segmentIndex_.get(this.currentPosition_);
  568. if (!this.reverse) {
  569. if (ref && ref.hasPartialSegments()) {
  570. // If the regular segment contains partial segments, move to the next
  571. // partial SegmentReference.
  572. this.currentPartialPosition_++;
  573. // If the current regular segment has been published completely, and
  574. // we've reached the end of its partial segments list, move to the next
  575. // regular segment.
  576. // If the Partial Segments list is still on the fly, do not move to
  577. // the next regular segment.
  578. if (ref.hasAllPartialSegments() &&
  579. this.currentPartialPosition_ == ref.partialReferences.length) {
  580. this.currentPosition_++;
  581. this.currentPartialPosition_ = 0;
  582. }
  583. } else {
  584. // If the regular segment doesn't contain partial segments, move to the
  585. // next regular segment.
  586. this.currentPosition_++;
  587. this.currentPartialPosition_ = 0;
  588. }
  589. } else {
  590. if (ref && ref.hasPartialSegments()) {
  591. // If the regular segment contains partial segments, move to the
  592. // previous partial SegmentReference.
  593. this.currentPartialPosition_--;
  594. if (this.currentPartialPosition_ < 0) {
  595. this.currentPosition_--;
  596. const prevRef = this.segmentIndex_.get(this.currentPosition_);
  597. if (prevRef && prevRef.hasPartialSegments()) {
  598. this.currentPartialPosition_ = prevRef.partialReferences.length - 1;
  599. } else {
  600. this.currentPartialPosition_ = 0;
  601. }
  602. }
  603. } else {
  604. // If the regular segment doesn't contain partial segments, move to the
  605. // previous regular segment.
  606. this.currentPosition_--;
  607. this.currentPartialPosition_ = 0;
  608. }
  609. }
  610. const res = this.current();
  611. return {
  612. 'value': res,
  613. 'done': !res,
  614. };
  615. }
  616. /**
  617. * @export
  618. */
  619. resetToLastIndependent() {
  620. const current = this.current();
  621. if (current.isPartial() && !current.isIndependent()) {
  622. const ref = this.segmentIndex_.get(this.currentPosition_);
  623. if (ref && ref.hasPartialSegments()) {
  624. let partial = ref.partialReferences[this.currentPartialPosition_];
  625. while (partial.isIndependent()) {
  626. if (this.currentPartialPosition_ <= 0) {
  627. break;
  628. }
  629. this.currentPartialPosition_--;
  630. partial = ref.partialReferences[this.currentPartialPosition_];
  631. }
  632. }
  633. }
  634. }
  635. };
  636. /**
  637. * A meta-SegmentIndex composed of multiple other SegmentIndexes.
  638. * Used in constructing multi-Period Streams for DASH.
  639. *
  640. * @extends {shaka.media.SegmentIndex}
  641. * @implements {shaka.util.IReleasable}
  642. * @implements {Iterable<?shaka.media.SegmentReference>}
  643. * @export
  644. */
  645. shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
  646. constructor() {
  647. super([]);
  648. /** @private {!Array<!shaka.media.SegmentIndex>} */
  649. this.indexes_ = [];
  650. }
  651. /** Evicts all old SegmentIndexes in this MetaSegmentIndex that are empty. */
  652. evictEmpty() {
  653. while (this.indexes_.length > 1 && this.indexes_[0].isEmpty()) {
  654. const index = this.indexes_.shift();
  655. // In the case of this class, this.numEvicted_ represents the evicted
  656. // segments that were in indexes that were entirely evicted.
  657. this.numEvicted_ += index.getNumEvicted();
  658. index.release();
  659. }
  660. }
  661. /**
  662. * Append a SegmentIndex to this MetaSegmentIndex. This effectively stitches
  663. * the underlying Stream onto the end of the multi-Period Stream represented
  664. * by this MetaSegmentIndex.
  665. *
  666. * @param {!shaka.media.SegmentIndex} segmentIndex
  667. */
  668. appendSegmentIndex(segmentIndex) {
  669. goog.asserts.assert(
  670. this.indexes_.length == 0 || segmentIndex.getNumEvicted() == 0,
  671. 'Should not append a new segment index with already-evicted segments');
  672. this.indexes_.push(segmentIndex);
  673. }
  674. /**
  675. * Create a clone of this MetaSegmentIndex containing all the same indexes.
  676. *
  677. * @return {!shaka.media.MetaSegmentIndex}
  678. */
  679. clone() {
  680. const clone = new shaka.media.MetaSegmentIndex();
  681. // Be careful to clone the Array. We don't want to share the reference with
  682. // our clone and affect each other accidentally.
  683. clone.indexes_ = this.indexes_.slice();
  684. clone.numEvicted_ = this.numEvicted_;
  685. return clone;
  686. }
  687. /**
  688. * @override
  689. * @export
  690. */
  691. release() {
  692. for (const index of this.indexes_) {
  693. index.release();
  694. }
  695. this.indexes_ = [];
  696. }
  697. /**
  698. * @override
  699. * @export
  700. */
  701. forEachTopLevelReference(fn) {
  702. for (const index of this.indexes_) {
  703. index.forEachTopLevelReference(fn);
  704. }
  705. }
  706. /**
  707. * Iterates over all segment indexes in this meta segment index.
  708. * @param {function(!shaka.media.SegmentIndex)} fn
  709. */
  710. forEachIndex(fn) {
  711. for (const index of this.indexes_) {
  712. fn(index);
  713. }
  714. }
  715. /**
  716. * @override
  717. * @export
  718. */
  719. find(time) {
  720. let numPassedInEarlierIndexes = this.numEvicted_;
  721. for (const index of this.indexes_) {
  722. const position = index.find(time);
  723. if (position != null) {
  724. return position + numPassedInEarlierIndexes;
  725. }
  726. numPassedInEarlierIndexes += index.getNumEvicted() +
  727. index.getNumReferences();
  728. }
  729. return null;
  730. }
  731. /**
  732. * Return a list of the continuity timelines for each of the segment indexes
  733. * of this meta segment index, assuming the child segment index is a timeline
  734. * segment index.
  735. * @return {number}
  736. */
  737. getTimelineForTime(time) {
  738. for (const index of this.indexes_) {
  739. const position = index.find(time);
  740. if (position != null) {
  741. return index.continuityTimeline();
  742. }
  743. }
  744. return -1;
  745. }
  746. /**
  747. * @override
  748. * @export
  749. */
  750. get(position) {
  751. let numPassedInEarlierIndexes = this.numEvicted_;
  752. let sawSegments = false;
  753. for (const index of this.indexes_) {
  754. goog.asserts.assert(
  755. !sawSegments || index.getNumEvicted() == 0,
  756. 'Should not see evicted segments after available segments');
  757. const reference = index.get(position - numPassedInEarlierIndexes);
  758. if (reference) {
  759. return reference;
  760. }
  761. const num = index.getNumReferences();
  762. numPassedInEarlierIndexes += index.getNumEvicted() + num;
  763. sawSegments = sawSegments || num != 0;
  764. }
  765. return null;
  766. }
  767. /**
  768. * @override
  769. * @export
  770. */
  771. offset(offset) {
  772. // offset() is only used by HLS, and MetaSegmentIndex is only used for DASH.
  773. goog.asserts.assert(
  774. false, 'offset() should not be used in MetaSegmentIndex!');
  775. }
  776. /**
  777. * @override
  778. * @export
  779. */
  780. merge(references) {
  781. // merge() is only used internally by the DASH and HLS parser on
  782. // SegmentIndexes, but never on MetaSegmentIndex.
  783. goog.asserts.assert(
  784. false, 'merge() should not be used in MetaSegmentIndex!');
  785. }
  786. /**
  787. * @override
  788. * @export
  789. */
  790. evict(time) {
  791. for (const index of this.indexes_) {
  792. index.evict(time);
  793. }
  794. this.evictEmpty();
  795. }
  796. /**
  797. * @override
  798. * @export
  799. */
  800. mergeAndEvict(references, windowStart) {
  801. // mergeAndEvict() is only used internally by the DASH and HLS parser on
  802. // SegmentIndexes, but never on MetaSegmentIndex.
  803. goog.asserts.assert(
  804. false, 'mergeAndEvict() should not be used in MetaSegmentIndex!');
  805. }
  806. /**
  807. * @override
  808. * @export
  809. */
  810. fit(windowStart, windowEnd) {
  811. // fit() is only used internally by manifest parsers on SegmentIndexes, but
  812. // never on MetaSegmentIndex.
  813. goog.asserts.assert(false, 'fit() should not be used in MetaSegmentIndex!');
  814. }
  815. /**
  816. * @override
  817. * @export
  818. */
  819. updateEvery(interval, updateCallback) {
  820. // updateEvery() is only used internally by the DASH parser on
  821. // SegmentIndexes, but never on MetaSegmentIndex.
  822. goog.asserts.assert(
  823. false, 'updateEvery() should not be used in MetaSegmentIndex!');
  824. }
  825. };