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