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