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