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