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