Source: lib/media/segment_reference.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.InitSegmentReference');
  7. goog.provide('shaka.media.SegmentReference');
  8. goog.require('goog.asserts');
  9. goog.require('shaka.log');
  10. goog.require('shaka.util.ArrayUtils');
  11. goog.require('shaka.util.BufferUtils');
  12. /**
  13. * Creates an InitSegmentReference, which provides the location to an
  14. * initialization segment.
  15. *
  16. * @export
  17. */
  18. shaka.media.InitSegmentReference = class {
  19. /**
  20. * @param {function(): !Array<string>} uris A function that creates the URIs
  21. * of the resource containing the segment.
  22. * @param {number} startByte The offset from the start of the resource to the
  23. * start of the segment.
  24. * @param {?number} endByte The offset from the start of the resource
  25. * to the end of the segment, inclusive. A value of null indicates that the
  26. * segment extends to the end of the resource.
  27. * @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information about
  28. * the quality of the media associated with this init segment.
  29. * @param {(null|number)=} timescale
  30. * @param {(null|BufferSource)=} segmentData
  31. * @param {?shaka.extern.aesKey=} aesKey
  32. * The segment's AES-128-CBC full segment encryption key and iv.
  33. * @param {boolean=} encrypted
  34. */
  35. constructor(uris, startByte, endByte, mediaQuality = null, timescale = null,
  36. segmentData = null, aesKey = null, encrypted = false) {
  37. /** @type {function(): !Array<string>} */
  38. this.getUris = uris;
  39. /** @const {number} */
  40. this.startByte = startByte;
  41. /** @const {?number} */
  42. this.endByte = endByte;
  43. /** @type {shaka.extern.MediaQualityInfo|null} */
  44. this.mediaQuality = mediaQuality;
  45. /** @type {number|null} */
  46. this.timescale = timescale;
  47. /** @type {BufferSource|null} */
  48. this.segmentData = segmentData;
  49. /** @type {?shaka.extern.aesKey} */
  50. this.aesKey = aesKey;
  51. /** @type {?string} */
  52. this.codecs = null;
  53. /** @type {?string} */
  54. this.mimeType = null;
  55. /** @type {?number} */
  56. this.boundaryEnd = null;
  57. /** @const {boolean} */
  58. this.encrypted = encrypted;
  59. }
  60. /**
  61. * Returns the offset from the start of the resource to the
  62. * start of the segment.
  63. *
  64. * @return {number}
  65. * @export
  66. */
  67. getStartByte() {
  68. return this.startByte;
  69. }
  70. /**
  71. * Returns the offset from the start of the resource to the end of the
  72. * segment, inclusive. A value of null indicates that the segment extends
  73. * to the end of the resource.
  74. *
  75. * @return {?number}
  76. * @export
  77. */
  78. getEndByte() {
  79. return this.endByte;
  80. }
  81. /**
  82. * Returns the size of the init segment.
  83. * @return {?number}
  84. */
  85. getSize() {
  86. if (this.endByte) {
  87. return this.endByte - this.startByte;
  88. } else {
  89. return null;
  90. }
  91. }
  92. /**
  93. * Returns media quality information for the segments associated with
  94. * this init segment.
  95. *
  96. * @return {?shaka.extern.MediaQualityInfo}
  97. */
  98. getMediaQuality() {
  99. return this.mediaQuality;
  100. }
  101. /**
  102. * Set the segment data.
  103. *
  104. * @param {!BufferSource} segmentData
  105. */
  106. setSegmentData(segmentData) {
  107. this.segmentData = segmentData;
  108. }
  109. /**
  110. * Return the segment data.
  111. *
  112. * @return {?BufferSource}
  113. */
  114. getSegmentData() {
  115. return this.segmentData;
  116. }
  117. /**
  118. * Check if two initSegmentReference have all the same values.
  119. * @param {?shaka.media.InitSegmentReference} reference1
  120. * @param {?shaka.media.InitSegmentReference} reference2
  121. * @return {boolean}
  122. */
  123. static equal(reference1, reference2) {
  124. const ArrayUtils = shaka.util.ArrayUtils;
  125. const BufferUtils = shaka.util.BufferUtils;
  126. if (reference1 === reference2) {
  127. return true;
  128. } else if (!reference1 || !reference2) {
  129. return reference1 == reference2;
  130. } else {
  131. return reference1.getStartByte() == reference2.getStartByte() &&
  132. reference1.getEndByte() == reference2.getEndByte() &&
  133. ArrayUtils.equal(
  134. reference1.getUris().sort(), reference2.getUris().sort()) &&
  135. BufferUtils.equal(reference1.getSegmentData(),
  136. reference2.getSegmentData());
  137. }
  138. }
  139. };
  140. /**
  141. * SegmentReference provides the start time, end time, and location to a media
  142. * segment.
  143. *
  144. * @export
  145. */
  146. shaka.media.SegmentReference = class {
  147. /**
  148. * @param {number} startTime The segment's start time in seconds.
  149. * @param {number} endTime The segment's end time in seconds. The segment
  150. * ends the instant before this time, so |endTime| must be strictly greater
  151. * than |startTime|.
  152. * @param {function(): !Array<string>} uris
  153. * A function that creates the URIs of the resource containing the segment.
  154. * @param {number} startByte The offset from the start of the resource to the
  155. * start of the segment.
  156. * @param {?number} endByte The offset from the start of the resource to the
  157. * end of the segment, inclusive. A value of null indicates that the
  158. * segment extends to the end of the resource.
  159. * @param {shaka.media.InitSegmentReference} initSegmentReference
  160. * The segment's initialization segment metadata, or null if the segments
  161. * are self-initializing.
  162. * @param {number} timestampOffset
  163. * The amount of time, in seconds, that must be added to the segment's
  164. * internal timestamps to align it to the presentation timeline.
  165. * <br>
  166. * For DASH, this value should equal the Period start time minus the first
  167. * presentation timestamp of the first frame/sample in the Period. For
  168. * example, for MP4 based streams, this value should equal Period start
  169. * minus the first segment's tfdt box's 'baseMediaDecodeTime' field (after
  170. * it has been converted to seconds).
  171. * <br>
  172. * For HLS, this value should be the start time of the most recent
  173. * discontinuity, or 0 if there is no preceding discontinuity. Only used
  174. * in segments mode.
  175. * @param {number} appendWindowStart
  176. * The start of the append window for this reference, relative to the
  177. * presentation. Any content from before this time will be removed by
  178. * MediaSource.
  179. * @param {number} appendWindowEnd
  180. * The end of the append window for this reference, relative to the
  181. * presentation. Any content from after this time will be removed by
  182. * MediaSource.
  183. * @param {!Array<!shaka.media.SegmentReference>=} partialReferences
  184. * A list of SegmentReferences for the partial segments.
  185. * @param {?string=} tilesLayout
  186. * The value is a grid-item-dimension consisting of two positive decimal
  187. * integers in the format: column-x-row ('4x3'). It describes the
  188. * arrangement of Images in a Grid. The minimum valid LAYOUT is '1x1'.
  189. * @param {?number=} tileDuration
  190. * The explicit duration of an individual tile within the tiles grid.
  191. * If not provided, the duration should be automatically calculated based on
  192. * the duration of the reference.
  193. * @param {?number=} syncTime
  194. * A time value, expressed in seconds since 1970, which is used to
  195. * synchronize between streams. Both produced and consumed by the HLS
  196. * parser. Other components should not need this value.
  197. * @param {shaka.media.SegmentReference.Status=} status
  198. * The segment status is used to indicate that a segment does not exist or is
  199. * not available.
  200. * @param {?shaka.extern.aesKey=} aesKey
  201. * The segment's AES-128-CBC full segment encryption key and iv.
  202. * @param {boolean=} allPartialSegments
  203. * Indicate if the segment has all partial segments
  204. */
  205. constructor(
  206. startTime, endTime, uris, startByte, endByte, initSegmentReference,
  207. timestampOffset, appendWindowStart, appendWindowEnd,
  208. partialReferences = [], tilesLayout = '', tileDuration = null,
  209. syncTime = null, status = shaka.media.SegmentReference.Status.AVAILABLE,
  210. aesKey = null, allPartialSegments = false) {
  211. // A preload hinted Partial Segment has the same startTime and endTime.
  212. goog.asserts.assert(startTime <= endTime,
  213. 'startTime must be less than or equal to endTime');
  214. goog.asserts.assert((endByte == null) || (startByte < endByte),
  215. 'startByte must be < endByte');
  216. /** @type {number} */
  217. this.startTime = startTime;
  218. /** @type {number} */
  219. this.endTime = endTime;
  220. /**
  221. * The "true" end time of the segment, without considering the period end
  222. * time. This is necessary for thumbnail segments, where timing requires us
  223. * to know the original segment duration as described in the manifest.
  224. * @type {number}
  225. */
  226. this.trueEndTime = endTime;
  227. /** @type {function(): !Array<string>} */
  228. this.getUrisInner = uris;
  229. /** @const {number} */
  230. this.startByte = startByte;
  231. /** @const {?number} */
  232. this.endByte = endByte;
  233. /** @type {shaka.media.InitSegmentReference} */
  234. this.initSegmentReference = initSegmentReference;
  235. /** @type {number} */
  236. this.timestampOffset = timestampOffset;
  237. /** @type {number} */
  238. this.appendWindowStart = appendWindowStart;
  239. /** @type {number} */
  240. this.appendWindowEnd = appendWindowEnd;
  241. /** @type {!Array<!shaka.media.SegmentReference>} */
  242. this.partialReferences = partialReferences;
  243. /** @type {?string} */
  244. this.tilesLayout = tilesLayout;
  245. /** @type {?number} */
  246. this.tileDuration = tileDuration;
  247. /**
  248. * A time value, expressed in seconds since 1970, which is used to
  249. * synchronize between streams. Both produced and consumed by the HLS
  250. * parser. Other components should not need this value.
  251. *
  252. * @type {?number}
  253. */
  254. this.syncTime = syncTime;
  255. /** @type {shaka.media.SegmentReference.Status} */
  256. this.status = status;
  257. /** @type {boolean} */
  258. this.preload = false;
  259. /** @type {boolean} */
  260. this.independent = true;
  261. /** @type {boolean} */
  262. this.byterangeOptimization = false;
  263. /** @type {?shaka.extern.aesKey} */
  264. this.aesKey = aesKey;
  265. /** @type {?shaka.extern.ThumbnailSprite} */
  266. this.thumbnailSprite = null;
  267. /** @type {number} */
  268. this.discontinuitySequence = -1;
  269. /** @type {boolean} */
  270. this.allPartialSegments = allPartialSegments;
  271. /** @type {boolean} */
  272. this.partial = false;
  273. /** @type {boolean} */
  274. this.lastPartial = false;
  275. for (const partial of this.partialReferences) {
  276. partial.markAsPartial();
  277. }
  278. if (this.allPartialSegments && this.partialReferences.length) {
  279. const lastPartial =
  280. this.partialReferences[this.partialReferences.length - 1];
  281. lastPartial.markAsLastPartial();
  282. }
  283. /** @type {?string} */
  284. this.codecs = null;
  285. /** @type {?string} */
  286. this.mimeType = null;
  287. /** @type {?number} */
  288. this.bandwidth = null;
  289. /** @type {BufferSource|null} */
  290. this.segmentData = null;
  291. /** @type {boolean} */
  292. this.removeSegmentDataOnGet = false;
  293. }
  294. /**
  295. * Creates and returns the URIs of the resource containing the segment.
  296. *
  297. * @return {!Array<string>}
  298. * @export
  299. */
  300. getUris() {
  301. return this.getUrisInner();
  302. }
  303. /**
  304. * Returns the segment's start time in seconds.
  305. *
  306. * @return {number}
  307. * @export
  308. */
  309. getStartTime() {
  310. return this.startTime;
  311. }
  312. /**
  313. * Returns the segment's end time in seconds.
  314. *
  315. * @return {number}
  316. * @export
  317. */
  318. getEndTime() {
  319. return this.endTime;
  320. }
  321. /**
  322. * Returns the offset from the start of the resource to the
  323. * start of the segment.
  324. *
  325. * @return {number}
  326. * @export
  327. */
  328. getStartByte() {
  329. return this.startByte;
  330. }
  331. /**
  332. * Returns the offset from the start of the resource to the end of the
  333. * segment, inclusive. A value of null indicates that the segment extends to
  334. * the end of the resource.
  335. *
  336. * @return {?number}
  337. * @export
  338. */
  339. getEndByte() {
  340. return this.endByte;
  341. }
  342. /**
  343. * Returns the size of the segment.
  344. * @return {?number}
  345. */
  346. getSize() {
  347. if (this.endByte) {
  348. return this.endByte - this.startByte;
  349. } else {
  350. return null;
  351. }
  352. }
  353. /**
  354. * Returns true if it contains partial SegmentReferences.
  355. * @return {boolean}
  356. */
  357. hasPartialSegments() {
  358. return this.partialReferences.length > 0;
  359. }
  360. /**
  361. * Returns true if it contains all partial SegmentReferences.
  362. * @return {boolean}
  363. */
  364. hasAllPartialSegments() {
  365. return this.allPartialSegments;
  366. }
  367. /**
  368. * Returns the segment's tiles layout. Only defined in image segments.
  369. *
  370. * @return {?string}
  371. * @export
  372. */
  373. getTilesLayout() {
  374. return this.tilesLayout;
  375. }
  376. /**
  377. * Returns the segment's explicit tile duration.
  378. * Only defined in image segments.
  379. *
  380. * @return {?number}
  381. * @export
  382. */
  383. getTileDuration() {
  384. return this.tileDuration;
  385. }
  386. /**
  387. * Returns the segment's status.
  388. *
  389. * @return {shaka.media.SegmentReference.Status}
  390. * @export
  391. */
  392. getStatus() {
  393. return this.status;
  394. }
  395. /**
  396. * Mark the reference as unavailable.
  397. *
  398. * @export
  399. */
  400. markAsUnavailable() {
  401. this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
  402. }
  403. /**
  404. * Mark the reference as preload.
  405. *
  406. * @export
  407. */
  408. markAsPreload() {
  409. this.preload = true;
  410. }
  411. /**
  412. * Returns true if the segment is preloaded.
  413. *
  414. * @return {boolean}
  415. * @export
  416. */
  417. isPreload() {
  418. return this.preload;
  419. }
  420. /**
  421. * Mark the reference as non-independent.
  422. *
  423. * @export
  424. */
  425. markAsNonIndependent() {
  426. this.independent = false;
  427. }
  428. /**
  429. * Returns true if the segment is independent.
  430. *
  431. * @return {boolean}
  432. * @export
  433. */
  434. isIndependent() {
  435. return this.independent;
  436. }
  437. /**
  438. * Mark the reference as partial.
  439. *
  440. * @export
  441. */
  442. markAsPartial() {
  443. this.partial = true;
  444. }
  445. /**
  446. * Returns true if the segment is partial.
  447. *
  448. * @return {boolean}
  449. * @export
  450. */
  451. isPartial() {
  452. return this.partial;
  453. }
  454. /**
  455. * Mark the reference as being the last part of the full segment
  456. *
  457. * @export
  458. */
  459. markAsLastPartial() {
  460. this.lastPartial = true;
  461. }
  462. /**
  463. * Returns true if reference as being the last part of the full segment.
  464. *
  465. * @return {boolean}
  466. * @export
  467. */
  468. isLastPartial() {
  469. return this.lastPartial;
  470. }
  471. /**
  472. * Mark the reference as byterange optimization.
  473. *
  474. * The "byterange optimization" means that it is playable using MP4 low
  475. * latency streaming with chunked data.
  476. *
  477. * @export
  478. */
  479. markAsByterangeOptimization() {
  480. this.byterangeOptimization = true;
  481. }
  482. /**
  483. * Returns true if the segment has a byterange optimization.
  484. *
  485. * @return {boolean}
  486. * @export
  487. */
  488. hasByterangeOptimization() {
  489. return this.byterangeOptimization;
  490. }
  491. /**
  492. * Set the segment's thumbnail sprite.
  493. *
  494. * @param {shaka.extern.ThumbnailSprite} thumbnailSprite
  495. * @export
  496. */
  497. setThumbnailSprite(thumbnailSprite) {
  498. this.thumbnailSprite = thumbnailSprite;
  499. }
  500. /**
  501. * Returns the segment's thumbnail sprite.
  502. *
  503. * @return {?shaka.extern.ThumbnailSprite}
  504. * @export
  505. */
  506. getThumbnailSprite() {
  507. return this.thumbnailSprite;
  508. }
  509. /**
  510. * Offset the segment reference by a fixed amount.
  511. *
  512. * @param {number} offset The amount to add to the segment's start and end
  513. * times.
  514. * @export
  515. */
  516. offset(offset) {
  517. this.startTime += offset;
  518. this.endTime += offset;
  519. this.trueEndTime += offset;
  520. for (const partial of this.partialReferences) {
  521. partial.startTime += offset;
  522. partial.endTime += offset;
  523. partial.trueEndTime += offset;
  524. }
  525. }
  526. /**
  527. * Sync this segment against a particular sync time that will serve as "0" in
  528. * the presentation timeline.
  529. *
  530. * @param {number} lowestSyncTime
  531. * @export
  532. */
  533. syncAgainst(lowestSyncTime) {
  534. if (this.syncTime == null) {
  535. shaka.log.alwaysError('Sync attempted without sync time!');
  536. return;
  537. }
  538. const desiredStart = this.syncTime - lowestSyncTime;
  539. const offset = desiredStart - this.startTime;
  540. if (Math.abs(offset) >= 0.001) {
  541. this.offset(offset);
  542. }
  543. }
  544. /**
  545. * Set the segment data.
  546. *
  547. * @param {!BufferSource} segmentData
  548. * @param {boolean=} singleUse
  549. * @export
  550. */
  551. setSegmentData(segmentData, singleUse = false) {
  552. this.segmentData = segmentData;
  553. this.removeSegmentDataOnGet = singleUse;
  554. }
  555. /**
  556. * Return the segment data.
  557. *
  558. * @param {boolean=} allowDeleteOnSingleUse
  559. * @return {?BufferSource}
  560. * @export
  561. */
  562. getSegmentData(allowDeleteOnSingleUse = true) {
  563. const ret = this.segmentData;
  564. if (allowDeleteOnSingleUse && this.removeSegmentDataOnGet) {
  565. this.segmentData = null;
  566. }
  567. return ret;
  568. }
  569. /**
  570. * Updates the init segment reference and propagates the update to all partial
  571. * references.
  572. * @param {shaka.media.InitSegmentReference} initSegmentReference
  573. */
  574. updateInitSegmentReference(initSegmentReference) {
  575. this.initSegmentReference = initSegmentReference;
  576. for (const partialReference of this.partialReferences) {
  577. partialReference.updateInitSegmentReference(initSegmentReference);
  578. }
  579. }
  580. };
  581. /**
  582. * Rather than using booleans to communicate what the state of the reference,
  583. * we have this enum.
  584. *
  585. * @enum {number}
  586. * @export
  587. */
  588. shaka.media.SegmentReference.Status = {
  589. AVAILABLE: 0,
  590. UNAVAILABLE: 1,
  591. MISSING: 2,
  592. };
  593. /**
  594. * A convenient typedef for when either type of reference is acceptable.
  595. *
  596. * @typedef {shaka.media.InitSegmentReference|shaka.media.SegmentReference}
  597. */
  598. shaka.media.AnySegmentReference;
  599. /**
  600. * @typedef {{
  601. * height: number,
  602. * positionX: number,
  603. * positionY: number,
  604. * width: number
  605. * }}
  606. *
  607. * @property {number} height
  608. * The thumbnail height in px.
  609. * @property {number} positionX
  610. * The thumbnail left position in px.
  611. * @property {number} positionY
  612. * The thumbnail top position in px.
  613. * @property {number} width
  614. * The thumbnail width in px.
  615. * @export
  616. */
  617. shaka.media.SegmentReference.ThumbnailSprite;