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