Source: lib/dash/segment_base.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.dash.SegmentBase');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.dash.MpdUtils');
  20. goog.require('shaka.log');
  21. goog.require('shaka.media.InitSegmentReference');
  22. goog.require('shaka.media.Mp4SegmentIndexParser');
  23. goog.require('shaka.media.SegmentIndex');
  24. goog.require('shaka.media.WebmSegmentIndexParser');
  25. goog.require('shaka.util.Error');
  26. goog.require('shaka.util.ManifestParserUtils');
  27. goog.require('shaka.util.XmlUtils');
  28. /**
  29. * @namespace shaka.dash.SegmentBase
  30. * @summary A set of functions for parsing SegmentBase elements.
  31. */
  32. /**
  33. * Creates an init segment reference from a Context object.
  34. *
  35. * @param {shaka.dash.DashParser.Context} context
  36. * @param {function(?shaka.dash.DashParser.InheritanceFrame):Element} callback
  37. * @return {shaka.media.InitSegmentReference}
  38. */
  39. shaka.dash.SegmentBase.createInitSegment = function(context, callback) {
  40. const MpdUtils = shaka.dash.MpdUtils;
  41. const XmlUtils = shaka.util.XmlUtils;
  42. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  43. let initialization =
  44. MpdUtils.inheritChild(context, callback, 'Initialization');
  45. if (!initialization) {
  46. return null;
  47. }
  48. let resolvedUris = context.representation.baseUris;
  49. let uri = initialization.getAttribute('sourceURL');
  50. if (uri) {
  51. resolvedUris =
  52. ManifestParserUtils.resolveUris(context.representation.baseUris, [uri]);
  53. }
  54. let startByte = 0;
  55. let endByte = null;
  56. let range = XmlUtils.parseAttr(initialization, 'range', XmlUtils.parseRange);
  57. if (range) {
  58. startByte = range.start;
  59. endByte = range.end;
  60. }
  61. let getUris = function() { return resolvedUris; };
  62. return new shaka.media.InitSegmentReference(getUris, startByte, endByte);
  63. };
  64. /**
  65. * Creates a new Stream object.
  66. *
  67. * @param {shaka.dash.DashParser.Context} context
  68. * @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
  69. * @throws shaka.util.Error When there is a parsing error.
  70. * @return {shaka.dash.DashParser.StreamInfo}
  71. */
  72. shaka.dash.SegmentBase.createStream = function(context, requestInitSegment) {
  73. goog.asserts.assert(context.representation.segmentBase,
  74. 'Should only be called with SegmentBase');
  75. // Since SegmentBase does not need updates, simply treat any call as
  76. // the initial parse.
  77. const MpdUtils = shaka.dash.MpdUtils;
  78. const SegmentBase = shaka.dash.SegmentBase;
  79. const XmlUtils = shaka.util.XmlUtils;
  80. let unscaledPresentationTimeOffset = Number(MpdUtils.inheritAttribute(
  81. context, SegmentBase.fromInheritance_, 'presentationTimeOffset')) || 0;
  82. let timescaleStr = MpdUtils.inheritAttribute(
  83. context, SegmentBase.fromInheritance_, 'timescale');
  84. let timescale = 1;
  85. if (timescaleStr) {
  86. timescale = XmlUtils.parsePositiveInt(timescaleStr) || 1;
  87. }
  88. let scaledPresentationTimeOffset =
  89. (unscaledPresentationTimeOffset / timescale) || 0;
  90. let init =
  91. SegmentBase.createInitSegment(context, SegmentBase.fromInheritance_);
  92. let index = SegmentBase.createSegmentIndex_(
  93. context, requestInitSegment, init, scaledPresentationTimeOffset);
  94. return {
  95. createSegmentIndex: index.createSegmentIndex,
  96. findSegmentPosition: index.findSegmentPosition,
  97. getSegmentReference: index.getSegmentReference,
  98. initSegmentReference: init,
  99. scaledPresentationTimeOffset: scaledPresentationTimeOffset
  100. };
  101. };
  102. /**
  103. * Creates segment index info for the given info.
  104. *
  105. * @param {shaka.dash.DashParser.Context} context
  106. * @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
  107. * @param {shaka.media.InitSegmentReference} init
  108. * @param {!Array.<string>} uris
  109. * @param {number} startByte
  110. * @param {?number} endByte
  111. * @param {string} containerType
  112. * @param {number} scaledPresentationTimeOffset
  113. * @return {shaka.dash.DashParser.SegmentIndexFunctions}
  114. */
  115. shaka.dash.SegmentBase.createSegmentIndexFromUris = function(
  116. context, requestInitSegment, init, uris,
  117. startByte, endByte, containerType, scaledPresentationTimeOffset) {
  118. let presentationTimeline = context.presentationTimeline;
  119. let fitLast = !context.dynamic || !context.periodInfo.isLastPeriod;
  120. let periodIndex = context.periodInfo.index;
  121. let periodDuration = context.periodInfo.duration;
  122. // Create a local variable to bind to so we can set to null to help the GC.
  123. let localRequest = requestInitSegment;
  124. let segmentIndex = null;
  125. let create = function() {
  126. let async = [
  127. localRequest(uris, startByte, endByte),
  128. containerType == 'webm' ?
  129. localRequest(init.getUris(), init.startByte, init.endByte) :
  130. null
  131. ];
  132. localRequest = null;
  133. return Promise.all(async).then(function(results) {
  134. let indexData = results[0];
  135. let initData = results[1] || null;
  136. let references = null;
  137. if (containerType == 'mp4') {
  138. references = shaka.media.Mp4SegmentIndexParser(
  139. indexData, startByte, uris, scaledPresentationTimeOffset);
  140. } else {
  141. goog.asserts.assert(initData, 'WebM requires init data');
  142. let parser = new shaka.media.WebmSegmentIndexParser();
  143. references = parser.parse(indexData, initData, uris,
  144. scaledPresentationTimeOffset);
  145. }
  146. presentationTimeline.notifySegments(references, periodIndex == 0);
  147. // Since containers are never updated, we don't need to store the
  148. // segmentIndex in the map.
  149. goog.asserts.assert(!segmentIndex,
  150. 'Should not call createSegmentIndex twice');
  151. segmentIndex = new shaka.media.SegmentIndex(references);
  152. if (fitLast) {
  153. segmentIndex.fit(periodDuration);
  154. }
  155. });
  156. };
  157. let get = function(i) {
  158. goog.asserts.assert(segmentIndex, 'Must call createSegmentIndex first');
  159. return segmentIndex.get(i);
  160. };
  161. let find = function(t) {
  162. goog.asserts.assert(segmentIndex, 'Must call createSegmentIndex first');
  163. return segmentIndex.find(t);
  164. };
  165. return {
  166. createSegmentIndex: create,
  167. findSegmentPosition: find,
  168. getSegmentReference: get
  169. };
  170. };
  171. /**
  172. * @param {?shaka.dash.DashParser.InheritanceFrame} frame
  173. * @return {Element}
  174. * @private
  175. */
  176. shaka.dash.SegmentBase.fromInheritance_ = function(frame) {
  177. return frame.segmentBase;
  178. };
  179. /**
  180. * Creates segment index info from a Context object.
  181. *
  182. * @param {shaka.dash.DashParser.Context} context
  183. * @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
  184. * @param {shaka.media.InitSegmentReference} init
  185. * @param {number} scaledPresentationTimeOffset
  186. * @return {shaka.dash.DashParser.SegmentIndexFunctions}
  187. * @throws shaka.util.Error When there is a parsing error.
  188. * @private
  189. */
  190. shaka.dash.SegmentBase.createSegmentIndex_ = function(
  191. context, requestInitSegment, init, scaledPresentationTimeOffset) {
  192. const MpdUtils = shaka.dash.MpdUtils;
  193. const SegmentBase = shaka.dash.SegmentBase;
  194. const XmlUtils = shaka.util.XmlUtils;
  195. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  196. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  197. let contentType = context.representation.contentType;
  198. let containerType = context.representation.mimeType.split('/')[1];
  199. if (contentType != ContentType.TEXT && containerType != 'mp4' &&
  200. containerType != 'webm') {
  201. shaka.log.error(
  202. 'SegmentBase specifies an unsupported container type.',
  203. context.representation);
  204. throw new shaka.util.Error(
  205. shaka.util.Error.Severity.CRITICAL,
  206. shaka.util.Error.Category.MANIFEST,
  207. shaka.util.Error.Code.DASH_UNSUPPORTED_CONTAINER);
  208. }
  209. if ((containerType == 'webm') && !init) {
  210. shaka.log.error(
  211. 'SegmentBase does not contain sufficient segment information:',
  212. 'the SegmentBase uses a WebM container,',
  213. 'but does not contain an Initialization element.',
  214. context.representation);
  215. throw new shaka.util.Error(
  216. shaka.util.Error.Severity.CRITICAL,
  217. shaka.util.Error.Category.MANIFEST,
  218. shaka.util.Error.Code.DASH_WEBM_MISSING_INIT);
  219. }
  220. let representationIndex = MpdUtils.inheritChild(
  221. context, SegmentBase.fromInheritance_, 'RepresentationIndex');
  222. let indexRangeElem = MpdUtils.inheritAttribute(
  223. context, SegmentBase.fromInheritance_, 'indexRange');
  224. let indexUris = context.representation.baseUris;
  225. let indexRange = XmlUtils.parseRange(indexRangeElem || '');
  226. if (representationIndex) {
  227. let representationUri = representationIndex.getAttribute('sourceURL');
  228. if (representationUri) {
  229. indexUris = ManifestParserUtils.resolveUris(
  230. context.representation.baseUris, [representationUri]);
  231. }
  232. indexRange = XmlUtils.parseAttr(
  233. representationIndex, 'range', XmlUtils.parseRange, indexRange);
  234. }
  235. if (!indexRange) {
  236. shaka.log.error(
  237. 'SegmentBase does not contain sufficient segment information:',
  238. 'the SegmentBase does not contain @indexRange',
  239. 'or a RepresentationIndex element.',
  240. context.representation);
  241. throw new shaka.util.Error(
  242. shaka.util.Error.Severity.CRITICAL,
  243. shaka.util.Error.Category.MANIFEST,
  244. shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  245. }
  246. return shaka.dash.SegmentBase.createSegmentIndexFromUris(
  247. context, requestInitSegment, init, indexUris, indexRange.start,
  248. indexRange.end, containerType, scaledPresentationTimeOffset);
  249. };