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