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.aes128Key=} aes128Key
  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, aes128Key = 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. /** @const {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.aes128Key} */
  49. this.aes128Key = aes128Key;
  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.aes128Key=} aes128Key
  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. aes128Key = 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.aes128Key} */
  251. this.aes128Key = aes128Key;
  252. /** @type {?shaka.media.SegmentReference.ThumbnailSprite} */
  253. this.thumbnailSprite = null;
  254. /** @type {number} */
  255. this.discontinuitySequence = 0;
  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. }
  275. /**
  276. * Creates and returns the URIs of the resource containing the segment.
  277. *
  278. * @return {!Array.<string>}
  279. * @export
  280. */
  281. getUris() {
  282. return this.getUrisInner();
  283. }
  284. /**
  285. * Returns the segment's start time in seconds.
  286. *
  287. * @return {number}
  288. * @export
  289. */
  290. getStartTime() {
  291. return this.startTime;
  292. }
  293. /**
  294. * Returns the segment's end time in seconds.
  295. *
  296. * @return {number}
  297. * @export
  298. */
  299. getEndTime() {
  300. return this.endTime;
  301. }
  302. /**
  303. * Returns the offset from the start of the resource to the
  304. * start of the segment.
  305. *
  306. * @return {number}
  307. * @export
  308. */
  309. getStartByte() {
  310. return this.startByte;
  311. }
  312. /**
  313. * Returns the offset from the start of the resource to the end of the
  314. * segment, inclusive. A value of null indicates that the segment extends to
  315. * the end of the resource.
  316. *
  317. * @return {?number}
  318. * @export
  319. */
  320. getEndByte() {
  321. return this.endByte;
  322. }
  323. /**
  324. * Returns the size of the segment.
  325. * @return {?number}
  326. */
  327. getSize() {
  328. if (this.endByte) {
  329. return this.endByte - this.startByte;
  330. } else {
  331. return null;
  332. }
  333. }
  334. /**
  335. * Returns true if it contains partial SegmentReferences.
  336. * @return {boolean}
  337. */
  338. hasPartialSegments() {
  339. return this.partialReferences.length > 0;
  340. }
  341. /**
  342. * Returns true if it contains all partial SegmentReferences.
  343. * @return {boolean}
  344. */
  345. hasAllPartialSegments() {
  346. return this.allPartialSegments;
  347. }
  348. /**
  349. * Returns the segment's tiles layout. Only defined in image segments.
  350. *
  351. * @return {?string}
  352. * @export
  353. */
  354. getTilesLayout() {
  355. return this.tilesLayout;
  356. }
  357. /**
  358. * Returns the segment's explicit tile duration.
  359. * Only defined in image segments.
  360. *
  361. * @return {?number}
  362. * @export
  363. */
  364. getTileDuration() {
  365. return this.tileDuration;
  366. }
  367. /**
  368. * Returns the segment's status.
  369. *
  370. * @return {shaka.media.SegmentReference.Status}
  371. * @export
  372. */
  373. getStatus() {
  374. return this.status;
  375. }
  376. /**
  377. * Mark the reference as unavailable.
  378. *
  379. * @export
  380. */
  381. markAsUnavailable() {
  382. this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
  383. }
  384. /**
  385. * Mark the reference as preload.
  386. *
  387. * @export
  388. */
  389. markAsPreload() {
  390. this.preload = true;
  391. }
  392. /**
  393. * Returns true if the segment is preloaded.
  394. *
  395. * @return {boolean}
  396. * @export
  397. */
  398. isPreload() {
  399. return this.preload;
  400. }
  401. /**
  402. * Mark the reference as non-independent.
  403. *
  404. * @export
  405. */
  406. markAsNonIndependent() {
  407. this.independent = false;
  408. }
  409. /**
  410. * Returns true if the segment is independent.
  411. *
  412. * @return {boolean}
  413. * @export
  414. */
  415. isIndependent() {
  416. return this.independent;
  417. }
  418. /**
  419. * Mark the reference as partial.
  420. *
  421. * @export
  422. */
  423. markAsPartial() {
  424. this.partial = true;
  425. }
  426. /**
  427. * Returns true if the segment is partial.
  428. *
  429. * @return {boolean}
  430. * @export
  431. */
  432. isPartial() {
  433. return this.partial;
  434. }
  435. /**
  436. * Mark the reference as being the last part of the full segment
  437. *
  438. * @export
  439. */
  440. markAsLastPartial() {
  441. this.lastPartial = true;
  442. }
  443. /**
  444. * Returns true if reference as being the last part of the full segment.
  445. *
  446. * @return {boolean}
  447. * @export
  448. */
  449. isLastPartial() {
  450. return this.lastPartial;
  451. }
  452. /**
  453. * Mark the reference as byterange optimization.
  454. *
  455. * The "byterange optimization" means that it is playable using MP4 low
  456. * latency streaming with chunked data.
  457. *
  458. * @export
  459. */
  460. markAsByterangeOptimization() {
  461. this.byterangeOptimization = true;
  462. }
  463. /**
  464. * Returns true if the segment has a byterange optimization.
  465. *
  466. * @return {boolean}
  467. * @export
  468. */
  469. hasByterangeOptimization() {
  470. return this.byterangeOptimization;
  471. }
  472. /**
  473. * Set the segment's thumbnail sprite.
  474. *
  475. * @param {shaka.media.SegmentReference.ThumbnailSprite} thumbnailSprite
  476. * @export
  477. */
  478. setThumbnailSprite(thumbnailSprite) {
  479. this.thumbnailSprite = thumbnailSprite;
  480. }
  481. /**
  482. * Returns the segment's thumbnail sprite.
  483. *
  484. * @return {?shaka.media.SegmentReference.ThumbnailSprite}
  485. * @export
  486. */
  487. getThumbnailSprite() {
  488. return this.thumbnailSprite;
  489. }
  490. /**
  491. * Offset the segment reference by a fixed amount.
  492. *
  493. * @param {number} offset The amount to add to the segment's start and end
  494. * times.
  495. * @export
  496. */
  497. offset(offset) {
  498. this.startTime += offset;
  499. this.endTime += offset;
  500. this.trueEndTime += offset;
  501. for (const partial of this.partialReferences) {
  502. partial.startTime += offset;
  503. partial.endTime += offset;
  504. partial.trueEndTime += offset;
  505. }
  506. }
  507. /**
  508. * Sync this segment against a particular sync time that will serve as "0" in
  509. * the presentation timeline.
  510. *
  511. * @param {number} lowestSyncTime
  512. * @export
  513. */
  514. syncAgainst(lowestSyncTime) {
  515. if (this.syncTime == null) {
  516. shaka.log.alwaysError('Sync attempted without sync time!');
  517. return;
  518. }
  519. const desiredStart = this.syncTime - lowestSyncTime;
  520. const offset = desiredStart - this.startTime;
  521. if (Math.abs(offset) >= 0.001) {
  522. this.offset(offset);
  523. }
  524. }
  525. /**
  526. * Updates the init segment reference and propagates the update to all partial
  527. * references.
  528. * @param {shaka.media.InitSegmentReference} initSegmentReference
  529. */
  530. updateInitSegmentReference(initSegmentReference) {
  531. this.initSegmentReference = initSegmentReference;
  532. for (const partialReference of this.partialReferences) {
  533. partialReference.updateInitSegmentReference(initSegmentReference);
  534. }
  535. }
  536. };
  537. /**
  538. * Rather than using booleans to communicate what the state of the reference,
  539. * we have this enum.
  540. *
  541. * @enum {number}
  542. * @export
  543. */
  544. shaka.media.SegmentReference.Status = {
  545. AVAILABLE: 0,
  546. UNAVAILABLE: 1,
  547. MISSING: 2,
  548. };
  549. /**
  550. * A convenient typedef for when either type of reference is acceptable.
  551. *
  552. * @typedef {shaka.media.InitSegmentReference|shaka.media.SegmentReference}
  553. */
  554. shaka.media.AnySegmentReference;
  555. /**
  556. * @typedef {{
  557. * height: number,
  558. * positionX: number,
  559. * positionY: number,
  560. * width: number
  561. * }}
  562. *
  563. * @property {number} height
  564. * The thumbnail height in px.
  565. * @property {number} positionX
  566. * The thumbnail left position in px.
  567. * @property {number} positionY
  568. * The thumbnail top position in px.
  569. * @property {number} width
  570. * The thumbnail width in px.
  571. * @export
  572. */
  573. shaka.media.SegmentReference.ThumbnailSprite;