Source: lib/dash/segment_list.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.dash.SegmentList');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.dash.MpdUtils');
  9. goog.require('shaka.dash.SegmentBase');
  10. goog.require('shaka.log');
  11. goog.require('shaka.media.InitSegmentReference');
  12. goog.require('shaka.media.SegmentIndex');
  13. goog.require('shaka.media.SegmentReference');
  14. goog.require('shaka.util.Error');
  15. goog.require('shaka.util.Functional');
  16. goog.require('shaka.util.ManifestParserUtils');
  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. /**
  22. * @summary A set of functions for parsing SegmentList elements.
  23. */
  24. shaka.dash.SegmentList = class {
  25. /**
  26. * Creates a new StreamInfo object.
  27. * Updates the existing SegmentIndex, if any.
  28. *
  29. * @param {shaka.dash.DashParser.Context} context
  30. * @param {!Object<string, !shaka.extern.Stream>} streamMap
  31. * @param {shaka.extern.aesKey|undefined} aesKey
  32. * @return {shaka.dash.DashParser.StreamInfo}
  33. */
  34. static createStreamInfo(context, streamMap, aesKey) {
  35. goog.asserts.assert(context.representation.segmentList,
  36. 'Should only be called with SegmentList');
  37. const SegmentList = shaka.dash.SegmentList;
  38. const initSegmentReference = shaka.dash.SegmentBase.createInitSegment(
  39. context, SegmentList.fromInheritance_, aesKey);
  40. const info = SegmentList.parseSegmentListInfo_(context);
  41. SegmentList.checkSegmentListInfo_(context, info);
  42. /** @type {shaka.media.SegmentIndex} */
  43. let segmentIndex = null;
  44. let stream = null;
  45. if (context.period.id && context.representation.id) {
  46. // Only check/store the index if period and representation IDs are set.
  47. const id = context.period.id + ',' + context.representation.id;
  48. stream = streamMap[id];
  49. if (stream) {
  50. segmentIndex = stream.segmentIndex;
  51. }
  52. }
  53. const references = SegmentList.createSegmentReferences_(
  54. context.periodInfo.start, context.periodInfo.duration,
  55. info.startNumber, context.representation.getBaseUris, info,
  56. initSegmentReference, aesKey, context.representation.mimeType,
  57. context.representation.codecs, context.bandwidth, context.urlParams);
  58. const isNew = !segmentIndex;
  59. if (segmentIndex) {
  60. const start = context.presentationTimeline.getSegmentAvailabilityStart();
  61. segmentIndex.mergeAndEvict(references, start);
  62. } else {
  63. segmentIndex = new shaka.media.SegmentIndex(references);
  64. }
  65. context.presentationTimeline.notifySegments(references);
  66. if (!context.dynamic || !context.periodInfo.isLastPeriod) {
  67. const periodStart = context.periodInfo.start;
  68. const periodEnd = context.periodInfo.duration ?
  69. context.periodInfo.start + context.periodInfo.duration : Infinity;
  70. segmentIndex.fit(periodStart, periodEnd, isNew);
  71. }
  72. if (stream) {
  73. stream.segmentIndex = segmentIndex;
  74. }
  75. return {
  76. generateSegmentIndex: () => {
  77. if (!segmentIndex || segmentIndex.isEmpty()) {
  78. segmentIndex.merge(references);
  79. }
  80. return Promise.resolve(segmentIndex);
  81. },
  82. };
  83. }
  84. /**
  85. * @param {?shaka.dash.DashParser.InheritanceFrame} frame
  86. * @return {?shaka.extern.xml.Node}
  87. * @private
  88. */
  89. static fromInheritance_(frame) {
  90. return frame.segmentList;
  91. }
  92. /**
  93. * Parses the SegmentList items to create an info object.
  94. *
  95. * @param {shaka.dash.DashParser.Context} context
  96. * @return {shaka.dash.SegmentList.SegmentListInfo}
  97. * @private
  98. */
  99. static parseSegmentListInfo_(context) {
  100. const SegmentList = shaka.dash.SegmentList;
  101. const MpdUtils = shaka.dash.MpdUtils;
  102. const mediaSegments = SegmentList.parseMediaSegments_(context);
  103. const segmentInfo =
  104. MpdUtils.parseSegmentInfo(context, SegmentList.fromInheritance_);
  105. let startNumber = segmentInfo.startNumber;
  106. if (startNumber == 0) {
  107. shaka.log.warning('SegmentList@startNumber must be > 0');
  108. startNumber = 1;
  109. }
  110. let startTime = 0;
  111. if (segmentInfo.segmentDuration) {
  112. // See DASH sec. 5.3.9.5.3
  113. // Don't use presentationTimeOffset for @duration.
  114. startTime = segmentInfo.segmentDuration * (startNumber - 1);
  115. } else if (segmentInfo.timeline && segmentInfo.timeline.length > 0) {
  116. // The presentationTimeOffset was considered in timeline creation.
  117. startTime = segmentInfo.timeline[0].start;
  118. }
  119. return {
  120. segmentDuration: segmentInfo.segmentDuration,
  121. startTime: startTime,
  122. startNumber: startNumber,
  123. scaledPresentationTimeOffset: segmentInfo.scaledPresentationTimeOffset,
  124. timeline: segmentInfo.timeline,
  125. mediaSegments: mediaSegments,
  126. };
  127. }
  128. /**
  129. * Checks whether a SegmentListInfo object is valid.
  130. *
  131. * @param {shaka.dash.DashParser.Context} context
  132. * @param {shaka.dash.SegmentList.SegmentListInfo} info
  133. * @private
  134. */
  135. static checkSegmentListInfo_(context, info) {
  136. if (!info.segmentDuration && !info.timeline &&
  137. info.mediaSegments.length > 1) {
  138. shaka.log.warning(
  139. 'SegmentList does not contain sufficient segment information:',
  140. 'the SegmentList specifies multiple segments,',
  141. 'but does not specify a segment duration or timeline.',
  142. context.representation);
  143. throw new shaka.util.Error(
  144. shaka.util.Error.Severity.CRITICAL,
  145. shaka.util.Error.Category.MANIFEST,
  146. shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  147. }
  148. if (!info.segmentDuration && !context.periodInfo.duration &&
  149. !info.timeline && info.mediaSegments.length == 1) {
  150. shaka.log.warning(
  151. 'SegmentList does not contain sufficient segment information:',
  152. 'the SegmentList specifies one segment,',
  153. 'but does not specify a segment duration, period duration,',
  154. 'or timeline.',
  155. context.representation);
  156. throw new shaka.util.Error(
  157. shaka.util.Error.Severity.CRITICAL,
  158. shaka.util.Error.Category.MANIFEST,
  159. shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  160. }
  161. if (info.timeline && info.timeline.length == 0) {
  162. shaka.log.warning(
  163. 'SegmentList does not contain sufficient segment information:',
  164. 'the SegmentList has an empty timeline.',
  165. context.representation);
  166. throw new shaka.util.Error(
  167. shaka.util.Error.Severity.CRITICAL,
  168. shaka.util.Error.Category.MANIFEST,
  169. shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  170. }
  171. }
  172. /**
  173. * Creates an array of segment references for the given data.
  174. *
  175. * @param {number} periodStart in seconds.
  176. * @param {?number} periodDuration in seconds.
  177. * @param {number} startNumber
  178. * @param {function(): !Array<string>} getBaseUris
  179. * @param {shaka.dash.SegmentList.SegmentListInfo} info
  180. * @param {shaka.media.InitSegmentReference} initSegmentReference
  181. * @param {shaka.extern.aesKey|undefined} aesKey
  182. * @param {string} mimeType
  183. * @param {string} codecs
  184. * @param {number} bandwidth
  185. * @param {function():string} urlParams
  186. * @return {!Array<!shaka.media.SegmentReference>}
  187. * @private
  188. */
  189. static createSegmentReferences_(
  190. periodStart, periodDuration, startNumber, getBaseUris, info,
  191. initSegmentReference, aesKey, mimeType, codecs, bandwidth, urlParams) {
  192. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  193. let max = info.mediaSegments.length;
  194. if (info.timeline && info.timeline.length != info.mediaSegments.length) {
  195. max = Math.min(info.timeline.length, info.mediaSegments.length);
  196. shaka.log.warning(
  197. 'The number of items in the segment timeline and the number of ',
  198. 'segment URLs do not match, truncating', info.mediaSegments.length,
  199. 'to', max);
  200. }
  201. const timestampOffset = periodStart - info.scaledPresentationTimeOffset;
  202. const appendWindowStart = periodStart;
  203. const appendWindowEnd = periodDuration ?
  204. periodStart + periodDuration : Infinity;
  205. /** @type {!Array<!shaka.media.SegmentReference>} */
  206. const references = [];
  207. let prevEndTime = info.startTime;
  208. for (let i = 0; i < max; i++) {
  209. const segment = info.mediaSegments[i];
  210. const startTime = prevEndTime;
  211. let endTime;
  212. if (info.segmentDuration != null) {
  213. endTime = startTime + info.segmentDuration;
  214. } else if (info.timeline) {
  215. // Ignore the timepoint start since they are continuous.
  216. endTime = info.timeline[i].end;
  217. } else {
  218. // If segmentDuration and timeline are null then there must
  219. // be exactly one segment.
  220. goog.asserts.assert(
  221. info.mediaSegments.length == 1 && periodDuration,
  222. 'There should be exactly one segment with a Period duration.');
  223. endTime = startTime + periodDuration;
  224. }
  225. let uris = null;
  226. const getUris = () => {
  227. if (uris == null) {
  228. uris = ManifestParserUtils.resolveUris(
  229. getBaseUris(), [segment.mediaUri], urlParams());
  230. }
  231. return uris;
  232. };
  233. const ref = new shaka.media.SegmentReference(
  234. periodStart + startTime,
  235. periodStart + endTime,
  236. getUris,
  237. segment.start,
  238. segment.end,
  239. initSegmentReference,
  240. timestampOffset,
  241. appendWindowStart, appendWindowEnd,
  242. /* partialReferences= */ [],
  243. /* tilesLayout= */ '',
  244. /* tileDuration= */ null,
  245. /* syncTime= */ null,
  246. shaka.media.SegmentReference.Status.AVAILABLE,
  247. aesKey);
  248. ref.codecs = codecs;
  249. ref.mimeType = mimeType;
  250. ref.bandwidth = bandwidth;
  251. references.push(ref);
  252. prevEndTime = endTime;
  253. }
  254. return references;
  255. }
  256. /**
  257. * Parses the media URIs from the context.
  258. *
  259. * @param {shaka.dash.DashParser.Context} context
  260. * @return {!Array<shaka.dash.SegmentList.MediaSegment>}
  261. * @private
  262. */
  263. static parseMediaSegments_(context) {
  264. const Functional = shaka.util.Functional;
  265. /** @type {!Array<!shaka.extern.xml.Node>} */
  266. const segmentLists = [
  267. context.representation.segmentList,
  268. context.adaptationSet.segmentList,
  269. context.period.segmentList,
  270. ].filter(Functional.isNotNull);
  271. const TXml = shaka.util.TXml;
  272. const StringUtils = shaka.util.StringUtils;
  273. // Search each SegmentList for one with at least one SegmentURL element,
  274. // select the first one, and convert each SegmentURL element to a tuple.
  275. return segmentLists
  276. .map((node) => { return TXml.findChildren(node, 'SegmentURL'); })
  277. .reduce((all, part) => { return all.length > 0 ? all : part; })
  278. .map((urlNode) => {
  279. if (urlNode.attributes['indexRange'] &&
  280. !context.indexRangeWarningGiven) {
  281. context.indexRangeWarningGiven = true;
  282. shaka.log.warning(
  283. 'We do not support the SegmentURL@indexRange attribute on ' +
  284. 'SegmentList. We only use the SegmentList@duration ' +
  285. 'attribute or SegmentTimeline, which must be accurate.');
  286. }
  287. const uri = StringUtils.htmlUnescape(urlNode.attributes['media']);
  288. const range = TXml.parseAttr(
  289. urlNode, 'mediaRange', TXml.parseRange,
  290. {start: 0, end: null});
  291. return {mediaUri: uri, start: range.start, end: range.end};
  292. });
  293. }
  294. };
  295. /**
  296. * @typedef {{
  297. * mediaUri: string,
  298. * start: number,
  299. * end: ?number
  300. * }}
  301. *
  302. * @property {string} mediaUri
  303. * The URI of the segment.
  304. * @property {number} start
  305. * The start byte of the segment.
  306. * @property {?number} end
  307. * The end byte of the segment, or null.
  308. */
  309. shaka.dash.SegmentList.MediaSegment;
  310. /**
  311. * @typedef {{
  312. * segmentDuration: ?number,
  313. * startTime: number,
  314. * startNumber: number,
  315. * scaledPresentationTimeOffset: number,
  316. * timeline: Array<shaka.media.PresentationTimeline.TimeRange>,
  317. * mediaSegments: !Array<shaka.dash.SegmentList.MediaSegment>
  318. * }}
  319. * @private
  320. *
  321. * @description
  322. * Contains information about a SegmentList.
  323. *
  324. * @property {?number} segmentDuration
  325. * The duration of the segments, if given.
  326. * @property {number} startTime
  327. * The start time of the first segment, in seconds.
  328. * @property {number} startNumber
  329. * The start number of the segments; 1 or greater.
  330. * @property {number} scaledPresentationTimeOffset
  331. * The scaledPresentationTimeOffset of the representation, in seconds.
  332. * @property {Array<shaka.media.PresentationTimeline.TimeRange>} timeline
  333. * The timeline of the representation, if given. Times in seconds.
  334. * @property {!Array<shaka.dash.SegmentList.MediaSegment>} mediaSegments
  335. * The URI and byte-ranges of the media segments.
  336. */
  337. shaka.dash.SegmentList.SegmentListInfo;