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