Source: lib/dash/segment_base.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.dash.SegmentBase');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.dash.MpdUtils');
  9. goog.require('shaka.log');
  10. goog.require('shaka.media.InitSegmentReference');
  11. goog.require('shaka.media.Mp4SegmentIndexParser');
  12. goog.require('shaka.media.SegmentIndex');
  13. goog.require('shaka.media.WebmSegmentIndexParser');
  14. goog.require('shaka.util.Error');
  15. goog.require('shaka.util.ManifestParserUtils');
  16. goog.require('shaka.util.ObjectUtils');
  17. goog.require('shaka.util.XmlUtils');
  18. goog.requireType('shaka.dash.DashParser');
  19. goog.requireType('shaka.media.PresentationTimeline');
  20. goog.requireType('shaka.media.SegmentReference');
  21. /**
  22. * @summary A set of functions for parsing SegmentBase elements.
  23. */
  24. shaka.dash.SegmentBase = class {
  25. /**
  26. * Creates an init segment reference from a Context object.
  27. *
  28. * @param {shaka.dash.DashParser.Context} context
  29. * @param {function(?shaka.dash.DashParser.InheritanceFrame):Element} callback
  30. * @return {shaka.media.InitSegmentReference}
  31. */
  32. static createInitSegment(context, callback) {
  33. const MpdUtils = shaka.dash.MpdUtils;
  34. const XmlUtils = shaka.util.XmlUtils;
  35. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  36. const initialization =
  37. MpdUtils.inheritChild(context, callback, 'Initialization');
  38. if (!initialization) {
  39. return null;
  40. }
  41. let resolvedUris = context.representation.baseUris;
  42. const uri = initialization.getAttribute('sourceURL');
  43. if (uri) {
  44. resolvedUris = ManifestParserUtils.resolveUris(
  45. context.representation.baseUris, [uri]);
  46. }
  47. let startByte = 0;
  48. let endByte = null;
  49. const range =
  50. XmlUtils.parseAttr(initialization, 'range', XmlUtils.parseRange);
  51. if (range) {
  52. startByte = range.start;
  53. endByte = range.end;
  54. }
  55. const getUris = () => resolvedUris;
  56. return new shaka.media.InitSegmentReference(getUris, startByte, endByte);
  57. }
  58. /**
  59. * Creates a new StreamInfo object.
  60. *
  61. * @param {shaka.dash.DashParser.Context} context
  62. * @param {shaka.dash.DashParser.RequestInitSegmentCallback}
  63. * requestInitSegment
  64. * @return {shaka.dash.DashParser.StreamInfo}
  65. */
  66. static createStreamInfo(context, requestInitSegment) {
  67. goog.asserts.assert(context.representation.segmentBase,
  68. 'Should only be called with SegmentBase');
  69. // Since SegmentBase does not need updates, simply treat any call as
  70. // the initial parse.
  71. const MpdUtils = shaka.dash.MpdUtils;
  72. const SegmentBase = shaka.dash.SegmentBase;
  73. const XmlUtils = shaka.util.XmlUtils;
  74. const unscaledPresentationTimeOffset = Number(MpdUtils.inheritAttribute(
  75. context, SegmentBase.fromInheritance_, 'presentationTimeOffset')) || 0;
  76. const timescaleStr = MpdUtils.inheritAttribute(
  77. context, SegmentBase.fromInheritance_, 'timescale');
  78. let timescale = 1;
  79. if (timescaleStr) {
  80. timescale = XmlUtils.parsePositiveInt(timescaleStr) || 1;
  81. }
  82. const scaledPresentationTimeOffset =
  83. (unscaledPresentationTimeOffset / timescale) || 0;
  84. const initSegmentReference =
  85. SegmentBase.createInitSegment(context, SegmentBase.fromInheritance_);
  86. // Throws an immediate error if the format is unsupported.
  87. SegmentBase.checkSegmentIndexRangeSupport_(context, initSegmentReference);
  88. // Direct fields of context will be reassigned by the parser before
  89. // generateSegmentIndex is called. So we must make a shallow copy first,
  90. // and use that in the generateSegmentIndex callbacks.
  91. const shallowCopyOfContext =
  92. shaka.util.ObjectUtils.shallowCloneObject(context);
  93. return {
  94. generateSegmentIndex: () => {
  95. return SegmentBase.generateSegmentIndex_(
  96. shallowCopyOfContext, requestInitSegment, initSegmentReference,
  97. scaledPresentationTimeOffset);
  98. },
  99. };
  100. }
  101. /**
  102. * Creates a SegmentIndex for the given URIs and context.
  103. *
  104. * @param {shaka.dash.DashParser.Context} context
  105. * @param {shaka.dash.DashParser.RequestInitSegmentCallback}
  106. * requestInitSegment
  107. * @param {shaka.media.InitSegmentReference} initSegmentReference
  108. * @param {!Array.<string>} uris
  109. * @param {number} startByte
  110. * @param {?number} endByte
  111. * @param {number} scaledPresentationTimeOffset
  112. * @return {!Promise.<shaka.media.SegmentIndex>}
  113. */
  114. static async generateSegmentIndexFromUris(
  115. context, requestInitSegment, initSegmentReference, uris, startByte,
  116. endByte, scaledPresentationTimeOffset) {
  117. // Unpack context right away, before we start an async process.
  118. // This immunizes us against changes to the context object later.
  119. /** @type {shaka.media.PresentationTimeline} */
  120. const presentationTimeline = context.presentationTimeline;
  121. const fitLast = !context.dynamic || !context.periodInfo.isLastPeriod;
  122. const periodStart = context.periodInfo.start;
  123. const periodDuration = context.periodInfo.duration;
  124. const containerType = context.representation.mimeType.split('/')[1];
  125. // Create a local variable to bind to so we can set to null to help the GC.
  126. let localRequest = requestInitSegment;
  127. let segmentIndex = null;
  128. const responses = [
  129. localRequest(uris, startByte, endByte),
  130. containerType == 'webm' ?
  131. localRequest(
  132. initSegmentReference.getUris(),
  133. initSegmentReference.startByte,
  134. initSegmentReference.endByte) :
  135. null,
  136. ];
  137. localRequest = null;
  138. const results = await Promise.all(responses);
  139. const indexData = results[0];
  140. const initData = results[1] || null;
  141. /** @type {Array.<!shaka.media.SegmentReference>} */
  142. let references = null;
  143. const timestampOffset = periodStart - scaledPresentationTimeOffset;
  144. const appendWindowStart = periodStart;
  145. const appendWindowEnd = periodDuration ?
  146. periodStart + periodDuration : Infinity;
  147. if (containerType == 'mp4') {
  148. references = shaka.media.Mp4SegmentIndexParser.parse(
  149. indexData, startByte, uris, initSegmentReference, timestampOffset,
  150. appendWindowStart, appendWindowEnd);
  151. } else {
  152. goog.asserts.assert(initData, 'WebM requires init data');
  153. references = shaka.media.WebmSegmentIndexParser.parse(
  154. indexData, initData, uris, initSegmentReference, timestampOffset,
  155. appendWindowStart, appendWindowEnd);
  156. }
  157. presentationTimeline.notifySegments(references);
  158. // Since containers are never updated, we don't need to store the
  159. // segmentIndex in the map.
  160. goog.asserts.assert(!segmentIndex,
  161. 'Should not call generateSegmentIndex twice');
  162. segmentIndex = new shaka.media.SegmentIndex(references);
  163. if (fitLast) {
  164. segmentIndex.fit(appendWindowStart, appendWindowEnd, /* isNew= */ true);
  165. }
  166. return segmentIndex;
  167. }
  168. /**
  169. * @param {?shaka.dash.DashParser.InheritanceFrame} frame
  170. * @return {Element}
  171. * @private
  172. */
  173. static fromInheritance_(frame) {
  174. return frame.segmentBase;
  175. }
  176. /**
  177. * Compute the byte range of the segment index from the container.
  178. *
  179. * @param {shaka.dash.DashParser.Context} context
  180. * @return {?{start: number, end: number}}
  181. * @private
  182. */
  183. static computeIndexRange_(context) {
  184. const MpdUtils = shaka.dash.MpdUtils;
  185. const SegmentBase = shaka.dash.SegmentBase;
  186. const XmlUtils = shaka.util.XmlUtils;
  187. const representationIndex = MpdUtils.inheritChild(
  188. context, SegmentBase.fromInheritance_, 'RepresentationIndex');
  189. const indexRangeElem = MpdUtils.inheritAttribute(
  190. context, SegmentBase.fromInheritance_, 'indexRange');
  191. let indexRange = XmlUtils.parseRange(indexRangeElem || '');
  192. if (representationIndex) {
  193. indexRange = XmlUtils.parseAttr(
  194. representationIndex, 'range', XmlUtils.parseRange, indexRange);
  195. }
  196. return indexRange;
  197. }
  198. /**
  199. * Compute the URIs of the segment index from the container.
  200. *
  201. * @param {shaka.dash.DashParser.Context} context
  202. * @return {!Array.<string>}
  203. * @private
  204. */
  205. static computeIndexUris_(context) {
  206. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  207. const MpdUtils = shaka.dash.MpdUtils;
  208. const SegmentBase = shaka.dash.SegmentBase;
  209. const representationIndex = MpdUtils.inheritChild(
  210. context, SegmentBase.fromInheritance_, 'RepresentationIndex');
  211. let indexUris = context.representation.baseUris;
  212. if (representationIndex) {
  213. const representationUri = representationIndex.getAttribute('sourceURL');
  214. if (representationUri) {
  215. indexUris = ManifestParserUtils.resolveUris(
  216. context.representation.baseUris, [representationUri]);
  217. }
  218. }
  219. return indexUris;
  220. }
  221. /**
  222. * Check if this type of segment index is supported. This allows for
  223. * immediate errors during parsing, as opposed to an async error from
  224. * createSegmentIndex().
  225. *
  226. * Also checks for a valid byte range, which is not required for callers from
  227. * SegmentTemplate.
  228. *
  229. * @param {shaka.dash.DashParser.Context} context
  230. * @param {shaka.media.InitSegmentReference} initSegmentReference
  231. * @private
  232. */
  233. static checkSegmentIndexRangeSupport_(context, initSegmentReference) {
  234. const SegmentBase = shaka.dash.SegmentBase;
  235. SegmentBase.checkSegmentIndexSupport(context, initSegmentReference);
  236. const indexRange = SegmentBase.computeIndexRange_(context);
  237. if (!indexRange) {
  238. shaka.log.error(
  239. 'SegmentBase does not contain sufficient segment information:',
  240. 'the SegmentBase does not contain @indexRange',
  241. 'or a RepresentationIndex element.',
  242. context.representation);
  243. throw new shaka.util.Error(
  244. shaka.util.Error.Severity.CRITICAL,
  245. shaka.util.Error.Category.MANIFEST,
  246. shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  247. }
  248. }
  249. /**
  250. * Check if this type of segment index is supported. This allows for
  251. * immediate errors during parsing, as opposed to an async error from
  252. * createSegmentIndex().
  253. *
  254. * @param {shaka.dash.DashParser.Context} context
  255. * @param {shaka.media.InitSegmentReference} initSegmentReference
  256. */
  257. static checkSegmentIndexSupport(context, initSegmentReference) {
  258. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  259. const contentType = context.representation.contentType;
  260. const containerType = context.representation.mimeType.split('/')[1];
  261. if (contentType != ContentType.TEXT && containerType != 'mp4' &&
  262. containerType != 'webm') {
  263. shaka.log.error(
  264. 'SegmentBase specifies an unsupported container type.',
  265. context.representation);
  266. throw new shaka.util.Error(
  267. shaka.util.Error.Severity.CRITICAL,
  268. shaka.util.Error.Category.MANIFEST,
  269. shaka.util.Error.Code.DASH_UNSUPPORTED_CONTAINER);
  270. }
  271. if ((containerType == 'webm') && !initSegmentReference) {
  272. shaka.log.error(
  273. 'SegmentBase does not contain sufficient segment information:',
  274. 'the SegmentBase uses a WebM container,',
  275. 'but does not contain an Initialization element.',
  276. context.representation);
  277. throw new shaka.util.Error(
  278. shaka.util.Error.Severity.CRITICAL,
  279. shaka.util.Error.Category.MANIFEST,
  280. shaka.util.Error.Code.DASH_WEBM_MISSING_INIT);
  281. }
  282. }
  283. /**
  284. * Generate a SegmentIndex from a Context object.
  285. *
  286. * @param {shaka.dash.DashParser.Context} context
  287. * @param {shaka.dash.DashParser.RequestInitSegmentCallback}
  288. * requestInitSegment
  289. * @param {shaka.media.InitSegmentReference} initSegmentReference
  290. * @param {number} scaledPresentationTimeOffset
  291. * @return {!Promise.<shaka.media.SegmentIndex>}
  292. * @private
  293. */
  294. static generateSegmentIndex_(
  295. context, requestInitSegment, initSegmentReference,
  296. scaledPresentationTimeOffset) {
  297. const SegmentBase = shaka.dash.SegmentBase;
  298. const indexUris = SegmentBase.computeIndexUris_(context);
  299. const indexRange = SegmentBase.computeIndexRange_(context);
  300. goog.asserts.assert(indexRange, 'Index range should not be null!');
  301. return shaka.dash.SegmentBase.generateSegmentIndexFromUris(
  302. context, requestInitSegment, initSegmentReference, indexUris,
  303. indexRange.start, indexRange.end,
  304. scaledPresentationTimeOffset);
  305. }
  306. };