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