Source: lib/dash/segment_template.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.SegmentTemplate');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.dash.MpdUtils');
  20. goog.require('shaka.dash.SegmentBase');
  21. goog.require('shaka.log');
  22. goog.require('shaka.media.InitSegmentReference');
  23. goog.require('shaka.media.SegmentIndex');
  24. goog.require('shaka.media.SegmentReference');
  25. goog.require('shaka.util.Error');
  26. goog.require('shaka.util.ManifestParserUtils');
  27. /**
  28. * @namespace shaka.dash.SegmentTemplate
  29. * @summary A set of functions for parsing SegmentTemplate elements.
  30. */
  31. /**
  32. * Creates a new Stream object or updates the Stream in the manifest.
  33. *
  34. * @param {shaka.dash.DashParser.Context} context
  35. * @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
  36. * @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
  37. * @param {boolean} isUpdate True if the manifest is being updated.
  38. * @throws shaka.util.Error When there is a parsing error.
  39. * @return {shaka.dash.DashParser.StreamInfo}
  40. */
  41. shaka.dash.SegmentTemplate.createStream = function(
  42. context, requestInitSegment, segmentIndexMap, isUpdate) {
  43. goog.asserts.assert(context.representation.segmentTemplate,
  44. 'Should only be called with SegmentTemplate');
  45. const SegmentTemplate = shaka.dash.SegmentTemplate;
  46. let init = SegmentTemplate.createInitSegment_(context);
  47. let info = SegmentTemplate.parseSegmentTemplateInfo_(context);
  48. SegmentTemplate.checkSegmentTemplateInfo_(context, info);
  49. /** @type {?shaka.dash.DashParser.SegmentIndexFunctions} */
  50. let segmentIndexFunctions = null;
  51. if (info.indexTemplate) {
  52. segmentIndexFunctions = SegmentTemplate.createFromIndexTemplate_(
  53. context, requestInitSegment, init, info);
  54. } else if (info.segmentDuration) {
  55. if (!isUpdate) {
  56. context.presentationTimeline.notifyMaxSegmentDuration(
  57. info.segmentDuration);
  58. }
  59. segmentIndexFunctions = SegmentTemplate.createFromDuration_(context, info);
  60. } else {
  61. /** @type {shaka.media.SegmentIndex} */
  62. let segmentIndex = null;
  63. let id = null;
  64. if (context.period.id && context.representation.id) {
  65. // Only check/store the index if period and representation IDs are set.
  66. id = context.period.id + ',' + context.representation.id;
  67. segmentIndex = segmentIndexMap[id];
  68. }
  69. let references = SegmentTemplate.createFromTimeline_(context, info);
  70. // Don't fit live content, since it might receive more segments.
  71. // Unless that live content is multi-period; it's safe to fit every period
  72. // but the last one, since only the last period might receive new segments.
  73. let shouldFit = !context.dynamic || !context.periodInfo.isLastPeriod;
  74. if (segmentIndex) {
  75. if (shouldFit) {
  76. // Fit the new references before merging them, so that the merge
  77. // algorithm has a more accurate view of their start and end times.
  78. let wrapper = new shaka.media.SegmentIndex(references);
  79. wrapper.fit(context.periodInfo.duration);
  80. }
  81. segmentIndex.merge(references);
  82. let start = context.presentationTimeline.getSegmentAvailabilityStart();
  83. segmentIndex.evict(start - context.periodInfo.start);
  84. } else {
  85. context.presentationTimeline.notifySegments(
  86. references, context.periodInfo.index == 0);
  87. segmentIndex = new shaka.media.SegmentIndex(references);
  88. if (id && context.dynamic) {
  89. segmentIndexMap[id] = segmentIndex;
  90. }
  91. }
  92. if (shouldFit) {
  93. segmentIndex.fit(context.periodInfo.duration);
  94. }
  95. segmentIndexFunctions = {
  96. createSegmentIndex: Promise.resolve.bind(Promise),
  97. findSegmentPosition: segmentIndex.find.bind(segmentIndex),
  98. getSegmentReference: segmentIndex.get.bind(segmentIndex)
  99. };
  100. }
  101. return {
  102. createSegmentIndex: segmentIndexFunctions.createSegmentIndex,
  103. findSegmentPosition: segmentIndexFunctions.findSegmentPosition,
  104. getSegmentReference: segmentIndexFunctions.getSegmentReference,
  105. initSegmentReference: init,
  106. scaledPresentationTimeOffset: info.scaledPresentationTimeOffset
  107. };
  108. };
  109. /**
  110. * @typedef {{
  111. * timescale: number,
  112. * segmentDuration: ?number,
  113. * startNumber: number,
  114. * scaledPresentationTimeOffset: number,
  115. * unscaledPresentationTimeOffset: number,
  116. * timeline: Array.<shaka.dash.MpdUtils.TimeRange>,
  117. * mediaTemplate: ?string,
  118. * indexTemplate: ?string
  119. * }}
  120. * @private
  121. *
  122. * @description
  123. * Contains information about a SegmentTemplate.
  124. *
  125. * @property {number} timescale
  126. * The time-scale of the representation.
  127. * @property {?number} segmentDuration
  128. * The duration of the segments in seconds, if given.
  129. * @property {number} startNumber
  130. * The start number of the segments; 1 or greater.
  131. * @property {number} scaledPresentationTimeOffset
  132. * The presentation time offset of the representation, in seconds.
  133. * @property {number} unscaledPresentationTimeOffset
  134. * The presentation time offset of the representation, in timescale units.
  135. * @property {Array.<shaka.dash.MpdUtils.TimeRange>} timeline
  136. * The timeline of the representation, if given. Times in seconds.
  137. * @property {?string} mediaTemplate
  138. * The media URI template, if given.
  139. * @property {?string} indexTemplate
  140. * The index URI template, if given.
  141. */
  142. shaka.dash.SegmentTemplate.SegmentTemplateInfo;
  143. /**
  144. * @param {?shaka.dash.DashParser.InheritanceFrame} frame
  145. * @return {Element}
  146. * @private
  147. */
  148. shaka.dash.SegmentTemplate.fromInheritance_ = function(frame) {
  149. return frame.segmentTemplate;
  150. };
  151. /**
  152. * Parses a SegmentTemplate element into an info object.
  153. *
  154. * @param {shaka.dash.DashParser.Context} context
  155. * @return {shaka.dash.SegmentTemplate.SegmentTemplateInfo}
  156. * @private
  157. */
  158. shaka.dash.SegmentTemplate.parseSegmentTemplateInfo_ = function(context) {
  159. const SegmentTemplate = shaka.dash.SegmentTemplate;
  160. const MpdUtils = shaka.dash.MpdUtils;
  161. let segmentInfo =
  162. MpdUtils.parseSegmentInfo(context, SegmentTemplate.fromInheritance_);
  163. let media = MpdUtils.inheritAttribute(
  164. context, SegmentTemplate.fromInheritance_, 'media');
  165. let index = MpdUtils.inheritAttribute(
  166. context, SegmentTemplate.fromInheritance_, 'index');
  167. return {
  168. segmentDuration: segmentInfo.segmentDuration,
  169. timescale: segmentInfo.timescale,
  170. startNumber: segmentInfo.startNumber,
  171. scaledPresentationTimeOffset: segmentInfo.scaledPresentationTimeOffset,
  172. unscaledPresentationTimeOffset: segmentInfo.unscaledPresentationTimeOffset,
  173. timeline: segmentInfo.timeline,
  174. mediaTemplate: media,
  175. indexTemplate: index
  176. };
  177. };
  178. /**
  179. * Verifies a SegmentTemplate info object.
  180. *
  181. * @param {shaka.dash.DashParser.Context} context
  182. * @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info
  183. * @throws shaka.util.Error When there is a parsing error.
  184. * @private
  185. */
  186. shaka.dash.SegmentTemplate.checkSegmentTemplateInfo_ = function(context, info) {
  187. let n = 0;
  188. n += info.indexTemplate ? 1 : 0;
  189. n += info.timeline ? 1 : 0;
  190. n += info.segmentDuration ? 1 : 0;
  191. if (n == 0) {
  192. shaka.log.error(
  193. 'SegmentTemplate does not contain any segment information:',
  194. 'the SegmentTemplate must contain either an index URL template',
  195. 'a SegmentTimeline, or a segment duration.',
  196. context.representation);
  197. throw new shaka.util.Error(
  198. shaka.util.Error.Severity.CRITICAL,
  199. shaka.util.Error.Category.MANIFEST,
  200. shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  201. } else if (n != 1) {
  202. shaka.log.warning(
  203. 'SegmentTemplate containes multiple segment information sources:',
  204. 'the SegmentTemplate should only contain an index URL template,',
  205. 'a SegmentTimeline or a segment duration.',
  206. context.representation);
  207. if (info.indexTemplate) {
  208. shaka.log.info('Using the index URL template by default.');
  209. info.timeline = null;
  210. info.segmentDuration = null;
  211. } else {
  212. goog.asserts.assert(info.timeline, 'There should be a timeline');
  213. shaka.log.info('Using the SegmentTimeline by default.');
  214. info.segmentDuration = null;
  215. }
  216. }
  217. if (!info.indexTemplate && !info.mediaTemplate) {
  218. shaka.log.error(
  219. 'SegmentTemplate does not contain sufficient segment information:',
  220. 'the SegmentTemplate\'s media URL template is missing.',
  221. context.representation);
  222. throw new shaka.util.Error(
  223. shaka.util.Error.Severity.CRITICAL,
  224. shaka.util.Error.Category.MANIFEST,
  225. shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  226. }
  227. };
  228. /**
  229. * Creates segment index functions from a index URL template.
  230. *
  231. * @param {shaka.dash.DashParser.Context} context
  232. * @param {shaka.dash.DashParser.RequestInitSegmentCallback} requestInitSegment
  233. * @param {shaka.media.InitSegmentReference} init
  234. * @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info
  235. * @throws shaka.util.Error When there is a parsing error.
  236. * @return {shaka.dash.DashParser.SegmentIndexFunctions}
  237. * @private
  238. */
  239. shaka.dash.SegmentTemplate.createFromIndexTemplate_ = function(
  240. context, requestInitSegment, init, info) {
  241. const MpdUtils = shaka.dash.MpdUtils;
  242. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  243. // Determine the container type.
  244. let containerType = context.representation.mimeType.split('/')[1];
  245. if ((containerType != 'mp4') && (containerType != 'webm')) {
  246. shaka.log.error(
  247. 'SegmentTemplate specifies an unsupported container type.',
  248. context.representation);
  249. throw new shaka.util.Error(
  250. shaka.util.Error.Severity.CRITICAL,
  251. shaka.util.Error.Category.MANIFEST,
  252. shaka.util.Error.Code.DASH_UNSUPPORTED_CONTAINER);
  253. }
  254. if ((containerType == 'webm') && !init) {
  255. shaka.log.error(
  256. 'SegmentTemplate does not contain sufficient segment information:',
  257. 'the SegmentTemplate uses a WebM container,',
  258. 'but does not contain an initialization URL template.',
  259. context.representation);
  260. throw new shaka.util.Error(
  261. shaka.util.Error.Severity.CRITICAL,
  262. shaka.util.Error.Category.MANIFEST,
  263. shaka.util.Error.Code.DASH_WEBM_MISSING_INIT);
  264. }
  265. goog.asserts.assert(info.indexTemplate, 'must be using index template');
  266. let filledTemplate = MpdUtils.fillUriTemplate(
  267. info.indexTemplate, context.representation.id,
  268. null, context.bandwidth || null, null);
  269. let resolvedUris = ManifestParserUtils.resolveUris(
  270. context.representation.baseUris, [filledTemplate]);
  271. return shaka.dash.SegmentBase.createSegmentIndexFromUris(
  272. context, requestInitSegment, init, resolvedUris, 0, null, containerType,
  273. info.scaledPresentationTimeOffset);
  274. };
  275. /**
  276. * Creates segment index functions from a segment duration.
  277. *
  278. * @param {shaka.dash.DashParser.Context} context
  279. * @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info
  280. * @return {shaka.dash.DashParser.SegmentIndexFunctions}
  281. * @private
  282. */
  283. shaka.dash.SegmentTemplate.createFromDuration_ = function(context, info) {
  284. goog.asserts.assert(info.mediaTemplate,
  285. 'There should be a media template with duration');
  286. const MpdUtils = shaka.dash.MpdUtils;
  287. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  288. let periodDuration = context.periodInfo.duration;
  289. let segmentDuration = info.segmentDuration;
  290. let startNumber = info.startNumber;
  291. let timescale = info.timescale;
  292. let template = info.mediaTemplate;
  293. let bandwidth = context.bandwidth || null;
  294. let id = context.representation.id;
  295. let baseUris = context.representation.baseUris;
  296. let find = function(periodTime) {
  297. if (periodTime < 0) {
  298. return null;
  299. } else if (periodDuration && periodTime >= periodDuration) {
  300. return null;
  301. }
  302. return Math.floor(periodTime / segmentDuration);
  303. };
  304. let get = function(position) {
  305. let segmentStart = position * segmentDuration;
  306. // Cap the segment end at the period end, to avoid period transition issues
  307. // in StreamingEngine.
  308. let segmentEnd = segmentStart + segmentDuration;
  309. if (periodDuration) segmentEnd = Math.min(segmentEnd, periodDuration);
  310. // Do not construct segments references that should not exist.
  311. if (segmentEnd < 0) {
  312. return null;
  313. } else if (periodDuration && segmentStart >= periodDuration) {
  314. return null;
  315. }
  316. let getUris = function() {
  317. let mediaUri = MpdUtils.fillUriTemplate(
  318. template, id, position + startNumber, bandwidth,
  319. segmentStart * timescale);
  320. return ManifestParserUtils.resolveUris(baseUris, [mediaUri]);
  321. };
  322. return new shaka.media.SegmentReference(
  323. position, segmentStart, segmentEnd, getUris, 0, null);
  324. };
  325. return {
  326. createSegmentIndex: Promise.resolve.bind(Promise),
  327. findSegmentPosition: find,
  328. getSegmentReference: get
  329. };
  330. };
  331. /**
  332. * Creates segment references from a timeline.
  333. *
  334. * @param {shaka.dash.DashParser.Context} context
  335. * @param {shaka.dash.SegmentTemplate.SegmentTemplateInfo} info
  336. * @return {!Array.<!shaka.media.SegmentReference>}
  337. * @private
  338. */
  339. shaka.dash.SegmentTemplate.createFromTimeline_ = function(context, info) {
  340. goog.asserts.assert(info.mediaTemplate,
  341. 'There should be a media template with a timeline');
  342. const MpdUtils = shaka.dash.MpdUtils;
  343. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  344. /** @type {!Array.<!shaka.media.SegmentReference>} */
  345. let references = [];
  346. for (let i = 0; i < info.timeline.length; i++) {
  347. let start = info.timeline[i].start;
  348. let unscaledStart = info.timeline[i].unscaledStart;
  349. let end = info.timeline[i].end;
  350. // Note: i = k - 1, where k indicates the k'th segment listed in the MPD.
  351. // (See section 5.3.9.5.3 of the DASH spec.)
  352. let segmentReplacement = i + info.startNumber;
  353. // Consider the presentation time offset in segment uri computation
  354. let timeReplacement = unscaledStart +
  355. info.unscaledPresentationTimeOffset;
  356. let createUris = (function(
  357. template, repId, bandwidth, baseUris, segmentId, time) {
  358. let mediaUri = MpdUtils.fillUriTemplate(
  359. template, repId, segmentId, bandwidth, time);
  360. return ManifestParserUtils.resolveUris(baseUris, [mediaUri])
  361. .map(function(g) { return g.toString(); });
  362. }.bind(null, info.mediaTemplate, context.representation.id,
  363. context.bandwidth || null, context.representation.baseUris,
  364. segmentReplacement, timeReplacement));
  365. references.push(new shaka.media.SegmentReference(
  366. segmentReplacement, start, end, createUris, 0, null));
  367. }
  368. return references;
  369. };
  370. /**
  371. * Creates an init segment reference from a context object.
  372. *
  373. * @param {shaka.dash.DashParser.Context} context
  374. * @return {shaka.media.InitSegmentReference}
  375. * @private
  376. */
  377. shaka.dash.SegmentTemplate.createInitSegment_ = function(context) {
  378. const MpdUtils = shaka.dash.MpdUtils;
  379. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  380. const SegmentTemplate = shaka.dash.SegmentTemplate;
  381. let initialization = MpdUtils.inheritAttribute(
  382. context, SegmentTemplate.fromInheritance_, 'initialization');
  383. if (!initialization) {
  384. return null;
  385. }
  386. let repId = context.representation.id;
  387. let bandwidth = context.bandwidth || null;
  388. let baseUris = context.representation.baseUris;
  389. let getUris = function() {
  390. goog.asserts.assert(initialization, 'Should have returned earler');
  391. let filledTemplate = MpdUtils.fillUriTemplate(
  392. initialization, repId, null, bandwidth, null);
  393. let resolvedUris = ManifestParserUtils.resolveUris(
  394. baseUris, [filledTemplate]);
  395. return resolvedUris;
  396. };
  397. return new shaka.media.InitSegmentReference(getUris, 0, null);
  398. };