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