Source: lib/dash/dash_parser.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.DashParser');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.dash.ContentProtection');
  20. goog.require('shaka.dash.SegmentBase');
  21. goog.require('shaka.dash.SegmentList');
  22. goog.require('shaka.dash.SegmentTemplate');
  23. goog.require('shaka.log');
  24. goog.require('shaka.media.DrmEngine');
  25. goog.require('shaka.media.ManifestParser');
  26. goog.require('shaka.media.PresentationTimeline');
  27. goog.require('shaka.media.SegmentReference');
  28. goog.require('shaka.media.TextEngine');
  29. goog.require('shaka.net.NetworkingEngine');
  30. goog.require('shaka.util.Error');
  31. goog.require('shaka.util.Functional');
  32. goog.require('shaka.util.LanguageUtils');
  33. goog.require('shaka.util.ManifestParserUtils');
  34. goog.require('shaka.util.StreamUtils');
  35. goog.require('shaka.util.StringUtils');
  36. goog.require('shaka.util.XmlUtils');
  37. /**
  38. * Creates a new DASH parser.
  39. *
  40. * @struct
  41. * @constructor
  42. * @implements {shakaExtern.ManifestParser}
  43. * @export
  44. */
  45. shaka.dash.DashParser = function() {
  46. /** @private {?shakaExtern.ManifestConfiguration} */
  47. this.config_ = null;
  48. /** @private {?shakaExtern.ManifestParser.PlayerInterface} */
  49. this.playerInterface_ = null;
  50. /** @private {!Array.<string>} */
  51. this.manifestUris_ = [];
  52. /** @private {?shakaExtern.Manifest} */
  53. this.manifest_ = null;
  54. /** @private {!Array.<string>} */
  55. this.periodIds_ = [];
  56. /** @private {number} */
  57. this.globalId_ = 1;
  58. /**
  59. * A map of IDs to SegmentIndex objects.
  60. * ID: Period@id,AdaptationSet@id,@Representation@id
  61. * e.g.: '1,5,23'
  62. * @private {!Object.<string, !shaka.media.SegmentIndex>}
  63. */
  64. this.segmentIndexMap_ = {};
  65. /**
  66. * The update period in seconds; or 0 for no updates.
  67. * @private {number}
  68. */
  69. this.updatePeriod_ = 0;
  70. /** @private {?number} */
  71. this.updateTimer_ = null;
  72. };
  73. /**
  74. * Contains the minimum amount of time, in seconds, between manifest update
  75. * requests.
  76. *
  77. * @private
  78. * @const {number}
  79. */
  80. shaka.dash.DashParser.MIN_UPDATE_PERIOD_ = 3;
  81. /**
  82. * The default MPD@suggestedPresentationDelay in seconds.
  83. *
  84. * @private
  85. * @const {number}
  86. */
  87. shaka.dash.DashParser.DEFAULT_SUGGESTED_PRESENTATION_DELAY_ = 10;
  88. /**
  89. * @typedef {
  90. * !function(!Array.<string>, ?number, ?number):!Promise.<!ArrayBuffer>
  91. * }
  92. */
  93. shaka.dash.DashParser.RequestInitSegmentCallback;
  94. /**
  95. * @typedef {{
  96. * segmentBase: Element,
  97. * segmentList: Element,
  98. * segmentTemplate: Element,
  99. * baseUris: !Array.<string>,
  100. * width: (number|undefined),
  101. * height: (number|undefined),
  102. * contentType: string,
  103. * mimeType: string,
  104. * codecs: string,
  105. * frameRate: (number|undefined),
  106. * containsEmsgBoxes: boolean,
  107. * id: string
  108. * }}
  109. *
  110. * @description
  111. * A collection of elements and properties which are inherited across levels
  112. * of a DASH manifest.
  113. *
  114. * @property {Element} segmentBase
  115. * The XML node for SegmentBase.
  116. * @property {Element} segmentList
  117. * The XML node for SegmentList.
  118. * @property {Element} segmentTemplate
  119. * The XML node for SegmentTemplate.
  120. * @property {!Array.<string>} baseUris
  121. * An array of absolute base URIs for the frame.
  122. * @property {(number|undefined)} width
  123. * The inherited width value.
  124. * @property {(number|undefined)} height
  125. * The inherited height value.
  126. * @property {string} contentType
  127. * The inherited media type.
  128. * @property {string} mimeType
  129. * The inherited MIME type value.
  130. * @property {string} codecs
  131. * The inherited codecs value.
  132. * @property {(number|undefined)} frameRate
  133. * The inherited framerate value.
  134. * @property {boolean} containsEmsgBoxes
  135. * Whether there are 'emsg' boxes.
  136. * @property {string} id
  137. * The ID of the element.
  138. */
  139. shaka.dash.DashParser.InheritanceFrame;
  140. /**
  141. * @typedef {{
  142. * dynamic: boolean,
  143. * presentationTimeline: !shaka.media.PresentationTimeline,
  144. * period: ?shaka.dash.DashParser.InheritanceFrame,
  145. * periodInfo: ?shaka.dash.DashParser.PeriodInfo,
  146. * adaptationSet: ?shaka.dash.DashParser.InheritanceFrame,
  147. * representation: ?shaka.dash.DashParser.InheritanceFrame,
  148. * bandwidth: (number|undefined),
  149. * indexRangeWarningGiven: boolean
  150. * }}
  151. *
  152. * @description
  153. * Contains context data for the streams.
  154. *
  155. * @property {boolean} dynamic
  156. * True if the MPD is dynamic (not all segments available at once)
  157. * @property {!shaka.media.PresentationTimeline} presentationTimeline
  158. * The PresentationTimeline.
  159. * @property {?shaka.dash.DashParser.InheritanceFrame} period
  160. * The inheritance from the Period element.
  161. * @property {?shaka.dash.DashParser.PeriodInfo} periodInfo
  162. * The Period info for the current Period.
  163. * @property {?shaka.dash.DashParser.InheritanceFrame} adaptationSet
  164. * The inheritance from the AdaptationSet element.
  165. * @property {?shaka.dash.DashParser.InheritanceFrame} representation
  166. * The inheritance from the Representation element.
  167. * @property {(number|undefined)} bandwidth
  168. * The bandwidth of the Representation.
  169. * @property {boolean} indexRangeWarningGiven
  170. * True if the warning about SegmentURL@indexRange has been printed.
  171. */
  172. shaka.dash.DashParser.Context;
  173. /**
  174. * @typedef {{
  175. * start: number,
  176. * duration: ?number,
  177. * node: !Element,
  178. * isLastPeriod: boolean
  179. * }}
  180. *
  181. * @description
  182. * Contains information about a Period element.
  183. *
  184. * @property {number} start
  185. * The start time of the period.
  186. * @property {?number} duration
  187. * The duration of the period; or null if the duration is not given. This
  188. * will be non-null for all periods except the last.
  189. * @property {!Element} node
  190. * The XML Node for the Period.
  191. * @property {boolean} isLastPeriod
  192. * Whether this Period is the last one in the manifest.
  193. */
  194. shaka.dash.DashParser.PeriodInfo;
  195. /**
  196. * @typedef {{
  197. * id: string,
  198. * contentType: ?string,
  199. * language: string,
  200. * main: boolean,
  201. * streams: !Array.<shakaExtern.Stream>,
  202. * drmInfos: !Array.<shakaExtern.DrmInfo>,
  203. * trickModeFor: ?string,
  204. * representationIds: !Array.<string>
  205. * }}
  206. *
  207. * @description
  208. * Contains information about an AdaptationSet element.
  209. *
  210. * @property {string} id
  211. * The unique ID of the adaptation set.
  212. * @property {?string} contentType
  213. * The content type of the AdaptationSet.
  214. * @property {string} language
  215. * The language of the AdaptationSet.
  216. * @property {boolean} main
  217. * Whether the AdaptationSet has the 'main' type.
  218. * @property {!Array.<shakaExtern.Stream>} streams
  219. * The streams this AdaptationSet contains.
  220. * @property {!Array.<shakaExtern.DrmInfo>} drmInfos
  221. * The DRM info for the AdaptationSet.
  222. * @property {?string} trickModeFor
  223. * If non-null, this AdaptationInfo represents trick mode tracks. This
  224. * property is the ID of the normal AdaptationSet these tracks should be
  225. * associated with.
  226. * @property {!Array.<string>} representationIds
  227. * An array of the IDs of the Representations this AdaptationSet contains.
  228. */
  229. shaka.dash.DashParser.AdaptationInfo;
  230. /**
  231. * @typedef {{
  232. * createSegmentIndex: shakaExtern.CreateSegmentIndexFunction,
  233. * findSegmentPosition: shakaExtern.FindSegmentPositionFunction,
  234. * getSegmentReference: shakaExtern.GetSegmentReferenceFunction
  235. * }}
  236. *
  237. * @description
  238. * Contains functions used to create and find segment references.
  239. *
  240. * @property {shakaExtern.CreateSegmentIndexFunction} createSegmentIndex
  241. * The createSegmentIndex function.
  242. * @property {shakaExtern.FindSegmentPositionFunction} findSegmentPosition
  243. * The findSegmentPosition function.
  244. * @property {shakaExtern.GetSegmentReferenceFunction} getSegmentReference
  245. * The getSegmentReference function.
  246. */
  247. shaka.dash.DashParser.SegmentIndexFunctions;
  248. /**
  249. * @typedef {{
  250. * createSegmentIndex: shakaExtern.CreateSegmentIndexFunction,
  251. * findSegmentPosition: shakaExtern.FindSegmentPositionFunction,
  252. * getSegmentReference: shakaExtern.GetSegmentReferenceFunction,
  253. * initSegmentReference: shaka.media.InitSegmentReference,
  254. * presentationTimeOffset: (number|undefined)
  255. * }}
  256. *
  257. * @description
  258. * Contains information about a Stream. This is passed from the createStream
  259. * methods.
  260. *
  261. * @property {shakaExtern.CreateSegmentIndexFunction} createSegmentIndex
  262. * The createSegmentIndex function for the stream.
  263. * @property {shakaExtern.FindSegmentPositionFunction} findSegmentPosition
  264. * The findSegmentPosition function for the stream.
  265. * @property {shakaExtern.GetSegmentReferenceFunction} getSegmentReference
  266. * The getSegmentReference function for the stream.
  267. * @property {shaka.media.InitSegmentReference} initSegmentReference
  268. * The init segment for the stream.
  269. * @property {(number|undefined)} presentationTimeOffset
  270. * The presentationTimeOffset for the stream.
  271. */
  272. shaka.dash.DashParser.StreamInfo;
  273. /**
  274. * @override
  275. * @exportInterface
  276. */
  277. shaka.dash.DashParser.prototype.configure = function(config) {
  278. goog.asserts.assert(config.dash != null,
  279. 'DashManifestConfiguration should not be null!');
  280. this.config_ = config;
  281. };
  282. /**
  283. * @override
  284. * @exportInterface
  285. */
  286. shaka.dash.DashParser.prototype.start = function(uri, playerInterface) {
  287. goog.asserts.assert(this.config_, 'Must call configure() before start()!');
  288. this.manifestUris_ = [uri];
  289. this.playerInterface_ = playerInterface;
  290. return this.requestManifest_().then(function() {
  291. if (this.playerInterface_)
  292. this.setUpdateTimer_(0);
  293. return this.manifest_;
  294. }.bind(this));
  295. };
  296. /**
  297. * @override
  298. * @exportInterface
  299. */
  300. shaka.dash.DashParser.prototype.stop = function() {
  301. this.playerInterface_ = null;
  302. this.config_ = null;
  303. this.manifestUris_ = [];
  304. this.manifest_ = null;
  305. this.periodIds_ = [];
  306. this.segmentIndexMap_ = {};
  307. if (this.updateTimer_ != null) {
  308. window.clearTimeout(this.updateTimer_);
  309. this.updateTimer_ = null;
  310. }
  311. return Promise.resolve();
  312. };
  313. /**
  314. * @override
  315. * @exportInterface
  316. */
  317. shaka.dash.DashParser.prototype.update = function() {
  318. this.requestManifest_().catch(function(error) {
  319. if (!this.playerInterface_) return;
  320. this.playerInterface_.onError(error);
  321. }.bind(this));
  322. };
  323. /**
  324. * @override
  325. * @exportInterface
  326. */
  327. shaka.dash.DashParser.prototype.onExpirationUpdated = function(
  328. sessionId, expiration) {
  329. // No-op
  330. };
  331. /**
  332. * Makes a network request for the manifest and parses the resulting data.
  333. *
  334. * @return {!Promise}
  335. * @private
  336. */
  337. shaka.dash.DashParser.prototype.requestManifest_ = function() {
  338. var requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
  339. var request = shaka.net.NetworkingEngine.makeRequest(
  340. this.manifestUris_, this.config_.retryParameters);
  341. return this.playerInterface_.networkingEngine.request(requestType, request)
  342. .then(function(response) {
  343. // Detect calls to stop().
  344. if (!this.playerInterface_)
  345. return;
  346. // This may throw; but it will result in a failed promise.
  347. return this.parseManifest_(response.data, response.uri);
  348. }.bind(this));
  349. };
  350. /**
  351. * Parses the manifest XML. This also handles updates and will update the
  352. * stored manifest.
  353. *
  354. * @param {!ArrayBuffer} data
  355. * @param {string} finalManifestUri The final manifest URI, which may
  356. * differ from this.manifestUri_ if there has been a redirect.
  357. * @return {!Promise}
  358. * @throws shaka.util.Error When there is a parsing error.
  359. * @private
  360. */
  361. shaka.dash.DashParser.prototype.parseManifest_ =
  362. function(data, finalManifestUri) {
  363. var Error = shaka.util.Error;
  364. var Functional = shaka.util.Functional;
  365. var XmlUtils = shaka.util.XmlUtils;
  366. var ManifestParserUtils = shaka.util.ManifestParserUtils;
  367. var string = shaka.util.StringUtils.fromUTF8(data);
  368. var parser = new DOMParser();
  369. var xml = null;
  370. var mpd = null;
  371. try {
  372. xml = parser.parseFromString(string, 'text/xml');
  373. } catch (exception) {}
  374. if (xml) {
  375. // parseFromString returns a Document object. A Document is a Node but not
  376. // an Element, so it cannot be used in XmlUtils (technically it can but the
  377. // types don't match). The |documentElement| member defines the top-level
  378. // element in the document.
  379. if (xml.documentElement.tagName == 'MPD')
  380. mpd = xml.documentElement;
  381. }
  382. if (mpd && mpd.getElementsByTagName('parsererror').length > 0)
  383. mpd = null; // It had a parser error in it.
  384. if (!mpd) {
  385. throw new Error(
  386. Error.Severity.CRITICAL, Error.Category.MANIFEST,
  387. Error.Code.DASH_INVALID_XML);
  388. }
  389. // Get any Location elements. This will update the manifest location and
  390. // the base URI.
  391. /** @type {!Array.<string>} */
  392. var manifestBaseUris = [finalManifestUri];
  393. /** @type {!Array.<string>} */
  394. var locations = XmlUtils.findChildren(mpd, 'Location')
  395. .map(XmlUtils.getContents)
  396. .filter(Functional.isNotNull);
  397. if (locations.length > 0) {
  398. this.manifestUris_ = locations;
  399. manifestBaseUris = locations;
  400. }
  401. var uris = XmlUtils.findChildren(mpd, 'BaseURL').map(XmlUtils.getContents);
  402. var baseUris = ManifestParserUtils.resolveUris(manifestBaseUris, uris);
  403. var minBufferTime =
  404. XmlUtils.parseAttr(mpd, 'minBufferTime', XmlUtils.parseDuration);
  405. this.updatePeriod_ = /** @type {number} */ (XmlUtils.parseAttr(
  406. mpd, 'minimumUpdatePeriod', XmlUtils.parseDuration, -1));
  407. var presentationStartTime = XmlUtils.parseAttr(
  408. mpd, 'availabilityStartTime', XmlUtils.parseDate);
  409. var segmentAvailabilityDuration = XmlUtils.parseAttr(
  410. mpd, 'timeShiftBufferDepth', XmlUtils.parseDuration);
  411. var suggestedPresentationDelay = XmlUtils.parseAttr(
  412. mpd, 'suggestedPresentationDelay', XmlUtils.parseDuration);
  413. var maxSegmentDuration = XmlUtils.parseAttr(
  414. mpd, 'maxSegmentDuration', XmlUtils.parseDuration);
  415. var mpdType = mpd.getAttribute('type') || 'static';
  416. var presentationTimeline;
  417. if (this.manifest_) {
  418. presentationTimeline = this.manifest_.presentationTimeline;
  419. } else {
  420. // DASH IOP v3.0 suggests using a default delay between minBufferTime and
  421. // timeShiftBufferDepth. This is literally the range of all feasible
  422. // choices for the value. Nothing older than timeShiftBufferDepth is still
  423. // available, and anything less than minBufferTime will cause buffering
  424. // issues.
  425. //
  426. // We have decided that our default will be 1.5 * minBufferTime, or 10s,
  427. // whichever is larger. This is fairly conservative. Content providers
  428. // should provide a suggestedPresentationDelay whenever possible to optimize
  429. // the live streaming experience.
  430. var defaultPresentationDelay = Math.max(
  431. shaka.dash.DashParser.DEFAULT_SUGGESTED_PRESENTATION_DELAY_,
  432. minBufferTime * 1.5);
  433. var presentationDelay = suggestedPresentationDelay != null ?
  434. suggestedPresentationDelay : defaultPresentationDelay;
  435. presentationTimeline = new shaka.media.PresentationTimeline(
  436. presentationStartTime, presentationDelay);
  437. }
  438. /** @type {shaka.dash.DashParser.Context} */
  439. var context = {
  440. // Don't base on updatePeriod_ since emsg boxes can cause manifest updates.
  441. dynamic: mpdType != 'static',
  442. presentationTimeline: presentationTimeline,
  443. period: null,
  444. periodInfo: null,
  445. adaptationSet: null,
  446. representation: null,
  447. bandwidth: undefined,
  448. indexRangeWarningGiven: false
  449. };
  450. var periodsAndDuration = this.parsePeriods_(context, baseUris, mpd);
  451. var duration = periodsAndDuration.duration;
  452. var periods = periodsAndDuration.periods;
  453. presentationTimeline.setStatic(mpdType == 'static');
  454. presentationTimeline.setDuration(duration || Infinity);
  455. presentationTimeline.setSegmentAvailabilityDuration(
  456. segmentAvailabilityDuration != null ?
  457. segmentAvailabilityDuration :
  458. Infinity);
  459. // Use @maxSegmentDuration to override smaller, derived values.
  460. presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
  461. if (!COMPILED) presentationTimeline.assertIsValid();
  462. if (this.manifest_) {
  463. // This is a manifest update, so we're done.
  464. return Promise.resolve();
  465. }
  466. // This is the first manifest parse, so we cannot return until we calculate
  467. // the clock offset.
  468. var timingElements = XmlUtils.findChildren(mpd, 'UTCTiming');
  469. var isLive = presentationTimeline.isLive();
  470. return this.parseUtcTiming_(
  471. baseUris, timingElements, isLive).then(function(offset) {
  472. // Detect calls to stop().
  473. if (!this.playerInterface_)
  474. return;
  475. presentationTimeline.setClockOffset(offset);
  476. this.manifest_ = {
  477. presentationTimeline: presentationTimeline,
  478. periods: periods,
  479. offlineSessionIds: [],
  480. minBufferTime: minBufferTime || 0
  481. };
  482. }.bind(this));
  483. };
  484. /**
  485. * Reads and parses the periods from the manifest. This first does some
  486. * partial parsing so the start and duration is available when parsing children.
  487. *
  488. * @param {shaka.dash.DashParser.Context} context
  489. * @param {!Array.<string>} baseUris
  490. * @param {!Element} mpd
  491. * @return {{periods: !Array.<shakaExtern.Period>, duration: ?number}}
  492. * @private
  493. */
  494. shaka.dash.DashParser.prototype.parsePeriods_ = function(
  495. context, baseUris, mpd) {
  496. var Functional = shaka.util.Functional;
  497. var XmlUtils = shaka.util.XmlUtils;
  498. var presentationDuration = XmlUtils.parseAttr(
  499. mpd, 'mediaPresentationDuration', XmlUtils.parseDuration);
  500. var periods = [];
  501. var prevEnd = 0;
  502. var periodNodes = XmlUtils.findChildren(mpd, 'Period');
  503. for (var i = 0; i < periodNodes.length; i++) {
  504. var elem = periodNodes[i];
  505. var start = /** @type {number} */ (
  506. XmlUtils.parseAttr(elem, 'start', XmlUtils.parseDuration, prevEnd));
  507. var givenDuration =
  508. XmlUtils.parseAttr(elem, 'duration', XmlUtils.parseDuration);
  509. var periodDuration = null;
  510. if (i != periodNodes.length - 1) {
  511. // "The difference between the start time of a Period and the start time
  512. // of the following Period is the duration of the media content
  513. // represented by this Period."
  514. var nextPeriod = periodNodes[i + 1];
  515. var nextStart =
  516. XmlUtils.parseAttr(nextPeriod, 'start', XmlUtils.parseDuration);
  517. if (nextStart != null)
  518. periodDuration = nextStart - start;
  519. } else if (presentationDuration != null) {
  520. // "The Period extends until the Period.start of the next Period, or
  521. // until the end of the Media Presentation in the case of the last
  522. // Period."
  523. periodDuration = presentationDuration - start;
  524. }
  525. if (periodDuration && givenDuration && periodDuration != givenDuration) {
  526. shaka.log.warning('There is a gap/overlap between Periods', elem);
  527. }
  528. // Only use the @duration in the MPD if we can't calculate it. We should
  529. // favor the @start of the following Period. This ensures that there aren't
  530. // gaps between Periods.
  531. if (periodDuration == null)
  532. periodDuration = givenDuration;
  533. // Parse child nodes.
  534. var info = {
  535. start: start,
  536. duration: periodDuration,
  537. node: elem,
  538. isLastPeriod: periodDuration == null || i == periodNodes.length - 1
  539. };
  540. var period = this.parsePeriod_(context, baseUris, info);
  541. periods.push(period);
  542. // If there are any new periods, call the callback and add them to the
  543. // manifest. If this is the first parse, it will see all of them as new.
  544. var periodId = context.period.id;
  545. if (this.periodIds_.every(Functional.isNotEqualFunc(periodId))) {
  546. this.playerInterface_.filterPeriod(period);
  547. this.periodIds_.push(periodId);
  548. if (this.manifest_)
  549. this.manifest_.periods.push(period);
  550. }
  551. if (periodDuration == null) {
  552. if (i != periodNodes.length - 1) {
  553. // If the duration is still null and we aren't at the end, then we will
  554. // skip any remaining periods.
  555. shaka.log.warning(
  556. 'Skipping Period', i + 1, 'and any subsequent Periods:', 'Period',
  557. i + 1, 'does not have a valid start time.', periods[i + 1]);
  558. }
  559. // The duration is unknown, so the end is unknown.
  560. prevEnd = null;
  561. break;
  562. }
  563. prevEnd = start + periodDuration;
  564. }
  565. if (presentationDuration != null) {
  566. if (prevEnd != presentationDuration) {
  567. shaka.log.warning(
  568. '@mediaPresentationDuration does not match the total duration of all',
  569. 'Periods.');
  570. // Assume @mediaPresentationDuration is correct.
  571. }
  572. return {
  573. periods: periods,
  574. duration: presentationDuration
  575. };
  576. } else {
  577. return {
  578. periods: periods,
  579. duration: prevEnd
  580. };
  581. }
  582. };
  583. /**
  584. * Parses a Period XML element. Unlike the other parse methods, this is not
  585. * given the Node; it is given a PeriodInfo structure. Also, partial parsing
  586. * was done before this was called so start and duration are valid.
  587. *
  588. * @param {shaka.dash.DashParser.Context} context
  589. * @param {!Array.<string>} baseUris
  590. * @param {shaka.dash.DashParser.PeriodInfo} periodInfo
  591. * @return {shakaExtern.Period}
  592. * @throws shaka.util.Error When there is a parsing error.
  593. * @private
  594. */
  595. shaka.dash.DashParser.prototype.parsePeriod_ = function(
  596. context, baseUris, periodInfo) {
  597. var Functional = shaka.util.Functional;
  598. var XmlUtils = shaka.util.XmlUtils;
  599. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  600. context.period = this.createFrame_(periodInfo.node, null, baseUris);
  601. context.periodInfo = periodInfo;
  602. // If the period doesn't have an ID, give it one based on its start time.
  603. if (!context.period.id) {
  604. shaka.log.info(
  605. 'No Period ID given for Period with start time ' + periodInfo.start +
  606. ', Assigning a default');
  607. context.period.id = '__shaka_period_' + periodInfo.start;
  608. }
  609. var eventStreamNodes = XmlUtils.findChildren(periodInfo.node, 'EventStream');
  610. eventStreamNodes.forEach(
  611. this.parseEventStream_.bind(this, periodInfo.start, periodInfo.duration));
  612. var adaptationSetNodes =
  613. XmlUtils.findChildren(periodInfo.node, 'AdaptationSet');
  614. var adaptationSets = adaptationSetNodes
  615. .map(this.parseAdaptationSet_.bind(this, context))
  616. .filter(Functional.isNotNull);
  617. var representationIds = adaptationSets
  618. .map(function(as) { return as.representationIds; })
  619. .reduce(Functional.collapseArrays, []);
  620. var uniqueRepIds = representationIds.filter(Functional.isNotDuplicate);
  621. if (context.dynamic && representationIds.length != uniqueRepIds.length) {
  622. throw new shaka.util.Error(
  623. shaka.util.Error.Severity.CRITICAL,
  624. shaka.util.Error.Category.MANIFEST,
  625. shaka.util.Error.Code.DASH_DUPLICATE_REPRESENTATION_ID);
  626. }
  627. var normalAdaptationSets = adaptationSets
  628. .filter(function(as) { return !as.trickModeFor; });
  629. var trickModeAdaptationSets = adaptationSets
  630. .filter(function(as) { return as.trickModeFor; });
  631. // Attach trick mode tracks to normal tracks.
  632. trickModeAdaptationSets.forEach(function(trickModeSet) {
  633. // There may be multiple trick mode streams, but we do not currently
  634. // support that. Just choose one.
  635. var trickModeVideo = trickModeSet.streams[0];
  636. var targetId = trickModeSet.trickModeFor;
  637. normalAdaptationSets.forEach(function(normalSet) {
  638. if (normalSet.id == targetId) {
  639. normalSet.streams.forEach(function(stream) {
  640. stream.trickModeVideo = trickModeVideo;
  641. });
  642. }
  643. });
  644. });
  645. var videoSets = this.getSetsOfType_(normalAdaptationSets, ContentType.VIDEO);
  646. var audioSets = this.getSetsOfType_(normalAdaptationSets, ContentType.AUDIO);
  647. if (!videoSets.length && !audioSets.length) {
  648. throw new shaka.util.Error(
  649. shaka.util.Error.Severity.CRITICAL,
  650. shaka.util.Error.Category.MANIFEST,
  651. shaka.util.Error.Code.DASH_EMPTY_PERIOD);
  652. }
  653. // In case of audio-only or video-only content, we create an array of one item
  654. // containing a null. This way, the double-loop works for all kinds of
  655. // content.
  656. if (!audioSets.length) {
  657. audioSets = [null];
  658. }
  659. if (!videoSets.length) {
  660. videoSets = [null];
  661. }
  662. // TODO: Limit number of combinations. Come up with a heuristic
  663. // to decide which audio tracks to combine with which video tracks.
  664. var variants = [];
  665. for (var i = 0; i < audioSets.length; i++) {
  666. for (var j = 0; j < videoSets.length; j++) {
  667. var audioSet = audioSets[i];
  668. var videoSet = videoSets[j];
  669. this.createVariants_(audioSet, videoSet, variants);
  670. }
  671. }
  672. var textSets = this.getSetsOfType_(normalAdaptationSets, ContentType.TEXT);
  673. var textStreams = [];
  674. for (var i = 0; i < textSets.length; i++) {
  675. textStreams.push.apply(textStreams, textSets[i].streams);
  676. }
  677. return {
  678. startTime: periodInfo.start,
  679. textStreams: textStreams,
  680. variants: variants
  681. };
  682. };
  683. /**
  684. * @param {!Array.<!shaka.dash.DashParser.AdaptationInfo>} adaptationSets
  685. * @param {string} type
  686. * @return {!Array.<!shaka.dash.DashParser.AdaptationInfo>}
  687. * @private
  688. */
  689. shaka.dash.DashParser.prototype.getSetsOfType_ = function(
  690. adaptationSets, type) {
  691. return adaptationSets.filter(function(as) {
  692. return as.contentType == type;
  693. });
  694. };
  695. /**
  696. * Combines Streams into Variants
  697. *
  698. * @param {?shaka.dash.DashParser.AdaptationInfo} audio
  699. * @param {?shaka.dash.DashParser.AdaptationInfo} video
  700. * @param {!Array.<shakaExtern.Variant>} variants New variants are pushed onto
  701. * this array.
  702. * @private
  703. */
  704. shaka.dash.DashParser.prototype.createVariants_ =
  705. function(audio, video, variants) {
  706. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  707. // Since both audio and video are of the same type, this assertion will catch
  708. // certain mistakes at runtime that the compiler would miss.
  709. goog.asserts.assert(!audio || audio.contentType == ContentType.AUDIO,
  710. 'Audio parameter mismatch!');
  711. goog.asserts.assert(!video || video.contentType == ContentType.VIDEO,
  712. 'Video parameter mismatch!');
  713. /** @type {number} */
  714. var bandwidth;
  715. /** @type {shakaExtern.Variant} */
  716. var variant;
  717. if (!audio && !video) {
  718. return;
  719. } else if (audio && video) {
  720. // Audio+video variants
  721. var DrmEngine = shaka.media.DrmEngine;
  722. if (DrmEngine.areDrmCompatible(audio.drmInfos, video.drmInfos)) {
  723. var drmInfos = DrmEngine.getCommonDrmInfos(audio.drmInfos,
  724. video.drmInfos);
  725. for (var i = 0; i < audio.streams.length; i++) {
  726. for (var j = 0; j < video.streams.length; j++) {
  727. // Explicit cast, followed by assertion. These should both be defined
  728. // in the case of DASH, but the type of Stream.bandwidth allows for
  729. // undefined in order to support HLS.
  730. bandwidth = /** @type {number} */(
  731. video.streams[j].bandwidth +
  732. audio.streams[i].bandwidth);
  733. goog.asserts.assert(bandwidth,
  734. 'Bandwidth must be defined and non-zero!');
  735. variant = {
  736. id: this.globalId_++,
  737. language: audio.language,
  738. primary: audio.main || video.main,
  739. audio: audio.streams[i],
  740. video: video.streams[j],
  741. bandwidth: bandwidth,
  742. drmInfos: drmInfos,
  743. allowedByApplication: true,
  744. allowedByKeySystem: true
  745. };
  746. variants.push(variant);
  747. }
  748. }
  749. }
  750. } else {
  751. // Audio or video only variants
  752. var set = audio || video;
  753. for (var i = 0; i < set.streams.length; i++) {
  754. // Explicit cast, followed by assertion. These should both be defined
  755. // in the case of DASH, but the type allows for undefined in order to
  756. // support HLS.
  757. bandwidth = /** @type {number} */(set.streams[i].bandwidth);
  758. goog.asserts.assert(bandwidth,
  759. 'Bandwidth must be defined and non-zero!');
  760. variant = {
  761. id: this.globalId_++,
  762. language: set.language || 'und',
  763. primary: set.main,
  764. audio: audio ? set.streams[i] : null,
  765. video: video ? set.streams[i] : null,
  766. bandwidth: bandwidth,
  767. drmInfos: set.drmInfos,
  768. allowedByApplication: true,
  769. allowedByKeySystem: true
  770. };
  771. variants.push(variant);
  772. }
  773. }
  774. };
  775. /**
  776. * Parses an AdaptationSet XML element.
  777. *
  778. * @param {shaka.dash.DashParser.Context} context
  779. * @param {!Element} elem The AdaptationSet element.
  780. * @return {?shaka.dash.DashParser.AdaptationInfo}
  781. * @throws shaka.util.Error When there is a parsing error.
  782. * @private
  783. */
  784. shaka.dash.DashParser.prototype.parseAdaptationSet_ = function(context, elem) {
  785. var XmlUtils = shaka.util.XmlUtils;
  786. var ManifestParserUtils = shaka.util.ManifestParserUtils;
  787. context.adaptationSet = this.createFrame_(elem, context.period, null);
  788. var main = false;
  789. var roles = XmlUtils.findChildren(elem, 'Role');
  790. // Default kind for text streams is 'subtitle' if unspecified in the manifest.
  791. var kind = undefined;
  792. if (context.adaptationSet.contentType == ManifestParserUtils.ContentType.TEXT)
  793. kind = ManifestParserUtils.TextStreamKind.SUBTITLE;
  794. for (var i = 0; i < roles.length; i++) {
  795. var scheme = roles[i].getAttribute('schemeIdUri');
  796. if (scheme == null || scheme == 'urn:mpeg:dash:role:2011') {
  797. // These only apply for the given scheme, but allow them to be specified
  798. // if there is no scheme specified.
  799. // See: DASH section 5.8.5.5
  800. var value = roles[i].getAttribute('value');
  801. switch (value) {
  802. case 'main':
  803. main = true;
  804. break;
  805. case 'caption':
  806. case 'subtitle':
  807. kind = value;
  808. break;
  809. }
  810. }
  811. }
  812. var essentialProperties = XmlUtils.findChildren(elem, 'EssentialProperty');
  813. // ID of real AdaptationSet if this is a trick mode set:
  814. var trickModeFor = null;
  815. var unrecognizedEssentialProperty = false;
  816. essentialProperties.forEach(function(prop) {
  817. var schemeId = prop.getAttribute('schemeIdUri');
  818. if (schemeId == 'http://dashif.org/guidelines/trickmode') {
  819. trickModeFor = prop.getAttribute('value');
  820. } else {
  821. unrecognizedEssentialProperty = true;
  822. }
  823. });
  824. // According to DASH spec (2014) section 5.8.4.8, "the successful processing
  825. // of the descriptor is essential to properly use the information in the
  826. // parent element". According to DASH IOP v3.3, section 3.3.4, "if the scheme
  827. // or the value" for EssentialProperty is not recognized, "the DASH client
  828. // shall ignore the parent element."
  829. if (unrecognizedEssentialProperty) {
  830. // Stop parsing this AdaptationSet and let the caller filter out the nulls.
  831. return null;
  832. }
  833. var contentProtectionElems = XmlUtils.findChildren(elem, 'ContentProtection');
  834. var contentProtection = shaka.dash.ContentProtection.parseFromAdaptationSet(
  835. contentProtectionElems, this.config_.dash.customScheme,
  836. this.config_.dash.ignoreDrmInfo);
  837. var language =
  838. shaka.util.LanguageUtils.normalize(elem.getAttribute('lang') || 'und');
  839. // Parse Representations into Streams.
  840. var representations = XmlUtils.findChildren(elem, 'Representation');
  841. var streams = representations
  842. .map(this.parseRepresentation_.bind(
  843. this, context, contentProtection, kind, language, main))
  844. .filter(function(s) { return !!s; });
  845. if (streams.length == 0) {
  846. throw new shaka.util.Error(
  847. shaka.util.Error.Severity.CRITICAL,
  848. shaka.util.Error.Category.MANIFEST,
  849. shaka.util.Error.Code.DASH_EMPTY_ADAPTATION_SET);
  850. }
  851. if (!context.adaptationSet.contentType) {
  852. // Guess the AdaptationSet's content type.
  853. var mimeType = streams[0].mimeType;
  854. var codecs = streams[0].codecs;
  855. context.adaptationSet.contentType =
  856. shaka.dash.DashParser.guessContentType_(mimeType, codecs);
  857. streams.forEach(function(stream) {
  858. stream.type = context.adaptationSet.contentType;
  859. });
  860. }
  861. streams.forEach(function(stream) {
  862. // Some DRM license providers require that we have a default
  863. // key ID from the manifest in the wrapped license request.
  864. // Thus, it should be put in drmInfo to be accessible to request filters.
  865. contentProtection.drmInfos.forEach(function(drmInfo) {
  866. if (stream.keyId) {
  867. drmInfo.keyIds.push(stream.keyId);
  868. }
  869. });
  870. });
  871. var repIds = representations
  872. .map(function(node) { return node.getAttribute('id'); })
  873. .filter(shaka.util.Functional.isNotNull);
  874. return {
  875. id: context.adaptationSet.id || ('__fake__' + this.globalId_++),
  876. contentType: context.adaptationSet.contentType,
  877. language: language,
  878. main: main,
  879. streams: streams,
  880. drmInfos: contentProtection.drmInfos,
  881. trickModeFor: trickModeFor,
  882. representationIds: repIds
  883. };
  884. };
  885. /**
  886. * Parses a Representation XML element.
  887. *
  888. * @param {shaka.dash.DashParser.Context} context
  889. * @param {shaka.dash.ContentProtection.Context} contentProtection
  890. * @param {(string|undefined)} kind
  891. * @param {string} language
  892. * @param {boolean} isPrimary
  893. * @param {!Element} node
  894. * @return {?shakaExtern.Stream} The Stream, or null when there is a
  895. * non-critical parsing error.
  896. * @throws shaka.util.Error When there is a parsing error.
  897. * @private
  898. */
  899. shaka.dash.DashParser.prototype.parseRepresentation_ = function(
  900. context, contentProtection, kind, language, isPrimary, node) {
  901. var XmlUtils = shaka.util.XmlUtils;
  902. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  903. context.representation = this.createFrame_(node, context.adaptationSet, null);
  904. if (!this.verifyRepresentation_(context.representation)) {
  905. shaka.log.warning('Skipping Representation', context.representation);
  906. return null;
  907. }
  908. context.bandwidth =
  909. XmlUtils.parseAttr(node, 'bandwidth', XmlUtils.parsePositiveInt) ||
  910. undefined;
  911. /** @type {?shaka.dash.DashParser.StreamInfo} */
  912. var streamInfo;
  913. var requestInitSegment = this.requestInitSegment_.bind(this);
  914. if (context.representation.segmentBase) {
  915. streamInfo = shaka.dash.SegmentBase.createStream(
  916. context, requestInitSegment);
  917. } else if (context.representation.segmentList) {
  918. streamInfo = shaka.dash.SegmentList.createStream(
  919. context, this.segmentIndexMap_);
  920. } else if (context.representation.segmentTemplate) {
  921. streamInfo = shaka.dash.SegmentTemplate.createStream(
  922. context, requestInitSegment, this.segmentIndexMap_, !!this.manifest_);
  923. } else {
  924. goog.asserts.assert(
  925. context.representation.contentType == ContentType.TEXT ||
  926. context.representation.contentType == ContentType.APPLICATION,
  927. 'Must have Segment* with non-text streams.');
  928. var baseUris = context.representation.baseUris;
  929. var duration = context.periodInfo.duration || 0;
  930. streamInfo = {
  931. createSegmentIndex: Promise.resolve.bind(Promise),
  932. findSegmentPosition:
  933. /** @return {?number} */ function(/** number */ time) {
  934. if (time >= 0 && time < duration)
  935. return 1;
  936. else
  937. return null;
  938. },
  939. getSegmentReference:
  940. /** @return {shaka.media.SegmentReference} */
  941. function(/** number */ ref) {
  942. if (ref != 1)
  943. return null;
  944. return new shaka.media.SegmentReference(
  945. 1, 0, duration, function() { return baseUris; }, 0, null);
  946. },
  947. initSegmentReference: null,
  948. presentationTimeOffset: 0
  949. };
  950. }
  951. var contentProtectionElems = XmlUtils.findChildren(node, 'ContentProtection');
  952. var keyId = shaka.dash.ContentProtection.parseFromRepresentation(
  953. contentProtectionElems, this.config_.dash.customScheme,
  954. contentProtection, this.config_.dash.ignoreDrmInfo);
  955. return {
  956. id: this.globalId_++,
  957. createSegmentIndex: streamInfo.createSegmentIndex,
  958. findSegmentPosition: streamInfo.findSegmentPosition,
  959. getSegmentReference: streamInfo.getSegmentReference,
  960. initSegmentReference: streamInfo.initSegmentReference,
  961. presentationTimeOffset: streamInfo.presentationTimeOffset,
  962. mimeType: context.representation.mimeType,
  963. codecs: context.representation.codecs,
  964. frameRate: context.representation.frameRate,
  965. bandwidth: context.bandwidth,
  966. width: context.representation.width,
  967. height: context.representation.height,
  968. kind: kind,
  969. encrypted: contentProtection.drmInfos.length > 0,
  970. keyId: keyId,
  971. language: language,
  972. type: context.adaptationSet.contentType,
  973. primary: isPrimary,
  974. trickModeVideo: null,
  975. containsEmsgBoxes: context.representation.containsEmsgBoxes
  976. };
  977. };
  978. /**
  979. * Called when the update timer ticks.
  980. *
  981. * @private
  982. */
  983. shaka.dash.DashParser.prototype.onUpdate_ = function() {
  984. goog.asserts.assert(this.updateTimer_, 'Should only be called by timer');
  985. goog.asserts.assert(this.updatePeriod_ >= 0,
  986. 'There should be an update period');
  987. shaka.log.info('Updating manifest...');
  988. this.updateTimer_ = null;
  989. var startTime = Date.now();
  990. this.requestManifest_().then(function() {
  991. // Detect a call to stop()
  992. if (!this.playerInterface_)
  993. return;
  994. // Ensure the next update occurs within |updatePeriod_| seconds by taking
  995. // into account the time it took to update the manifest.
  996. var endTime = Date.now();
  997. this.setUpdateTimer_((endTime - startTime) / 1000.0);
  998. }.bind(this)).catch(function(error) {
  999. goog.asserts.assert(error instanceof shaka.util.Error,
  1000. 'Should only receive a Shaka error');
  1001. // Try updating again, but ensure we haven't been destroyed.
  1002. if (this.playerInterface_) {
  1003. // We will retry updating, so override the severity of the error.
  1004. error.severity = shaka.util.Error.Severity.RECOVERABLE;
  1005. this.playerInterface_.onError(error);
  1006. this.setUpdateTimer_(0);
  1007. }
  1008. }.bind(this));
  1009. };
  1010. /**
  1011. * Sets the update timer. Does nothing if the manifest does not specify an
  1012. * update period.
  1013. *
  1014. * @param {number} offset An offset, in seconds, to apply to the manifest's
  1015. * update period.
  1016. * @private
  1017. */
  1018. shaka.dash.DashParser.prototype.setUpdateTimer_ = function(offset) {
  1019. // NOTE: An updatePeriod_ of -1 means the attribute was missing.
  1020. // An attribute which is present and set to 0 should still result in periodic
  1021. // updates. For more, see: https://github.com/google/shaka-player/issues/331
  1022. if (this.updatePeriod_ < 0)
  1023. return;
  1024. goog.asserts.assert(this.updateTimer_ == null,
  1025. 'Timer should not be already set');
  1026. var period =
  1027. Math.max(shaka.dash.DashParser.MIN_UPDATE_PERIOD_, this.updatePeriod_);
  1028. var interval = Math.max(period - offset, 0);
  1029. shaka.log.debug('updateInterval', interval);
  1030. var callback = this.onUpdate_.bind(this);
  1031. this.updateTimer_ = window.setTimeout(callback, 1000 * interval);
  1032. };
  1033. /**
  1034. * Creates a new inheritance frame for the given element.
  1035. *
  1036. * @param {!Element} elem
  1037. * @param {?shaka.dash.DashParser.InheritanceFrame} parent
  1038. * @param {Array.<string>} baseUris
  1039. * @return {shaka.dash.DashParser.InheritanceFrame}
  1040. * @private
  1041. */
  1042. shaka.dash.DashParser.prototype.createFrame_ = function(
  1043. elem, parent, baseUris) {
  1044. goog.asserts.assert(parent || baseUris,
  1045. 'Must provide either parent or baseUris');
  1046. var ManifestParserUtils = shaka.util.ManifestParserUtils;
  1047. var XmlUtils = shaka.util.XmlUtils;
  1048. parent = parent || /** @type {shaka.dash.DashParser.InheritanceFrame} */ ({
  1049. contentType: '',
  1050. mimeType: '',
  1051. codecs: '',
  1052. containsEmsgBoxes: false,
  1053. frameRate: undefined
  1054. });
  1055. baseUris = baseUris || parent.baseUris;
  1056. var parseNumber = XmlUtils.parseNonNegativeInt;
  1057. var evalDivision = XmlUtils.evalDivision;
  1058. var uris = XmlUtils.findChildren(elem, 'BaseURL').map(XmlUtils.getContents);
  1059. var contentType = elem.getAttribute('contentType') || parent.contentType;
  1060. var mimeType = elem.getAttribute('mimeType') || parent.mimeType;
  1061. var codecs = elem.getAttribute('codecs') || parent.codecs;
  1062. var frameRate =
  1063. XmlUtils.parseAttr(elem, 'frameRate', evalDivision) || parent.frameRate;
  1064. var containsEmsgBoxes =
  1065. !!XmlUtils.findChildren(elem, 'InbandEventStream').length;
  1066. if (!contentType) {
  1067. contentType = shaka.dash.DashParser.guessContentType_(mimeType, codecs);
  1068. }
  1069. return {
  1070. baseUris: ManifestParserUtils.resolveUris(baseUris, uris),
  1071. segmentBase: XmlUtils.findChild(elem, 'SegmentBase') || parent.segmentBase,
  1072. segmentList: XmlUtils.findChild(elem, 'SegmentList') || parent.segmentList,
  1073. segmentTemplate:
  1074. XmlUtils.findChild(elem, 'SegmentTemplate') || parent.segmentTemplate,
  1075. width: XmlUtils.parseAttr(elem, 'width', parseNumber) || parent.width,
  1076. height: XmlUtils.parseAttr(elem, 'height', parseNumber) || parent.height,
  1077. contentType: contentType,
  1078. mimeType: mimeType,
  1079. codecs: codecs,
  1080. frameRate: frameRate,
  1081. containsEmsgBoxes: containsEmsgBoxes || parent.containsEmsgBoxes,
  1082. id: elem.getAttribute('id')
  1083. };
  1084. };
  1085. /**
  1086. * Verifies that a Representation has exactly one Segment* element. Prints
  1087. * warnings if there is a problem.
  1088. *
  1089. * @param {shaka.dash.DashParser.InheritanceFrame} frame
  1090. * @return {boolean} True if the Representation is usable; otherwise return
  1091. * false.
  1092. * @private
  1093. */
  1094. shaka.dash.DashParser.prototype.verifyRepresentation_ = function(frame) {
  1095. var ContentType = shaka.util.ManifestParserUtils.ContentType;
  1096. var n = 0;
  1097. n += frame.segmentBase ? 1 : 0;
  1098. n += frame.segmentList ? 1 : 0;
  1099. n += frame.segmentTemplate ? 1 : 0;
  1100. if (n == 0) {
  1101. // TODO: extend with the list of MIME types registered to TextEngine.
  1102. if (frame.contentType == ContentType.TEXT ||
  1103. frame.contentType == ContentType.APPLICATION) {
  1104. return true;
  1105. } else {
  1106. shaka.log.warning(
  1107. 'Representation does not contain a segment information source:',
  1108. 'the Representation must contain one of SegmentBase, SegmentList,',
  1109. 'SegmentTemplate, or explicitly indicate that it is "text".',
  1110. frame);
  1111. return false;
  1112. }
  1113. }
  1114. if (n != 1) {
  1115. shaka.log.warning(
  1116. 'Representation contains multiple segment information sources:',
  1117. 'the Representation should only contain one of SegmentBase,',
  1118. 'SegmentList, or SegmentTemplate.',
  1119. frame);
  1120. if (frame.segmentBase) {
  1121. shaka.log.info('Using SegmentBase by default.');
  1122. frame.segmentList = null;
  1123. frame.segmentTemplate = null;
  1124. } else {
  1125. goog.asserts.assert(frame.segmentList, 'There should be a SegmentList');
  1126. shaka.log.info('Using SegmentList by default.');
  1127. frame.segmentTemplate = null;
  1128. }
  1129. }
  1130. return true;
  1131. };
  1132. /**
  1133. * Makes a request to the given URI and calculates the clock offset.
  1134. *
  1135. * @param {!Array.<string>} baseUris
  1136. * @param {string} uri
  1137. * @param {string} method
  1138. * @return {!Promise.<number>}
  1139. * @private
  1140. */
  1141. shaka.dash.DashParser.prototype.requestForTiming_ =
  1142. function(baseUris, uri, method) {
  1143. var requestUris = shaka.util.ManifestParserUtils.resolveUris(baseUris, [uri]);
  1144. var request = shaka.net.NetworkingEngine.makeRequest(
  1145. requestUris, this.config_.retryParameters);
  1146. request.method = method;
  1147. var type = shaka.net.NetworkingEngine.RequestType.MANIFEST;
  1148. return this.playerInterface_.networkingEngine.request(type, request)
  1149. .then(function(response) {
  1150. var text;
  1151. if (method == 'HEAD') {
  1152. if (!response.headers || !response.headers['date']) return 0;
  1153. text = response.headers['date'];
  1154. } else {
  1155. text = shaka.util.StringUtils.fromUTF8(response.data);
  1156. }
  1157. var date = Date.parse(text);
  1158. return isNaN(date) ? 0 : (date - Date.now());
  1159. });
  1160. };
  1161. /**
  1162. * Parses an array of UTCTiming elements.
  1163. *
  1164. * @param {!Array.<string>} baseUris
  1165. * @param {!Array.<!Element>} elems
  1166. * @param {boolean} isLive
  1167. * @return {!Promise.<number>}
  1168. * @private
  1169. */
  1170. shaka.dash.DashParser.prototype.parseUtcTiming_ =
  1171. function(baseUris, elems, isLive) {
  1172. var schemesAndValues = elems.map(function(elem) {
  1173. return {
  1174. scheme: elem.getAttribute('schemeIdUri'),
  1175. value: elem.getAttribute('value')
  1176. };
  1177. });
  1178. // If there's nothing specified in the manifest, but we have a default from
  1179. // the config, use that.
  1180. var clockSyncUri = this.config_.dash.clockSyncUri;
  1181. if (isLive && !schemesAndValues.length && clockSyncUri) {
  1182. schemesAndValues.push({
  1183. scheme: 'urn:mpeg:dash:utc:http-head:2014',
  1184. value: clockSyncUri
  1185. });
  1186. }
  1187. var Functional = shaka.util.Functional;
  1188. return Functional.createFallbackPromiseChain(schemesAndValues, function(sv) {
  1189. var scheme = sv.scheme;
  1190. var value = sv.value;
  1191. switch (scheme) {
  1192. // See DASH IOP Guidelines Section 4.7
  1193. // http://goo.gl/CQFNJT
  1194. case 'urn:mpeg:dash:utc:http-head:2014':
  1195. // Some old ISO23009-1 drafts used 2012.
  1196. case 'urn:mpeg:dash:utc:http-head:2012':
  1197. return this.requestForTiming_(baseUris, value, 'HEAD');
  1198. case 'urn:mpeg:dash:utc:http-xsdate:2014':
  1199. case 'urn:mpeg:dash:utc:http-iso:2014':
  1200. case 'urn:mpeg:dash:utc:http-xsdate:2012':
  1201. case 'urn:mpeg:dash:utc:http-iso:2012':
  1202. return this.requestForTiming_(baseUris, value, 'GET');
  1203. case 'urn:mpeg:dash:utc:direct:2014':
  1204. case 'urn:mpeg:dash:utc:direct:2012':
  1205. var date = Date.parse(value);
  1206. return isNaN(date) ? 0 : (date - Date.now());
  1207. case 'urn:mpeg:dash:utc:http-ntp:2014':
  1208. case 'urn:mpeg:dash:utc:ntp:2014':
  1209. case 'urn:mpeg:dash:utc:sntp:2014':
  1210. shaka.log.warning('NTP UTCTiming scheme is not supported');
  1211. return Promise.reject();
  1212. default:
  1213. shaka.log.warning(
  1214. 'Unrecognized scheme in UTCTiming element', scheme);
  1215. return Promise.reject();
  1216. }
  1217. }.bind(this)).catch(function() {
  1218. if (isLive) {
  1219. shaka.log.warning(
  1220. 'A UTCTiming element should always be given in live manifests! ' +
  1221. 'This content may not play on clients with bad clocks!');
  1222. }
  1223. return 0;
  1224. });
  1225. };
  1226. /**
  1227. * Parses an EventStream element.
  1228. *
  1229. * @param {number} periodStart
  1230. * @param {?number} periodDuration
  1231. * @param {!Element} elem
  1232. * @private
  1233. */
  1234. shaka.dash.DashParser.prototype.parseEventStream_ = function(
  1235. periodStart, periodDuration, elem) {
  1236. var XmlUtils = shaka.util.XmlUtils;
  1237. var parseNumber = XmlUtils.parseNonNegativeInt;
  1238. var schemeIdUri = elem.getAttribute('schemeIdUri') || '';
  1239. var value = elem.getAttribute('value') || '';
  1240. var timescale = XmlUtils.parseAttr(elem, 'timescale', parseNumber) || 1;
  1241. XmlUtils.findChildren(elem, 'Event').forEach(function(eventNode) {
  1242. var presentationTime =
  1243. XmlUtils.parseAttr(eventNode, 'presentationTime', parseNumber) || 0;
  1244. var duration = XmlUtils.parseAttr(eventNode, 'duration', parseNumber) || 0;
  1245. var startTime = presentationTime / timescale + periodStart;
  1246. var endTime = startTime + (duration / timescale);
  1247. if (periodDuration != null) {
  1248. // An event should not go past the Period, even if the manifest says so.
  1249. // See: Dash sec. 5.10.2.1
  1250. startTime = Math.min(startTime, periodStart + periodDuration);
  1251. endTime = Math.min(endTime, periodStart + periodDuration);
  1252. }
  1253. /** @type {shakaExtern.TimelineRegionInfo} */
  1254. var region = {
  1255. schemeIdUri: schemeIdUri,
  1256. value: value,
  1257. startTime: startTime,
  1258. endTime: endTime,
  1259. id: eventNode.getAttribute('id') || '',
  1260. eventElement: eventNode
  1261. };
  1262. this.playerInterface_.onTimelineRegionAdded(region);
  1263. }.bind(this));
  1264. };
  1265. /**
  1266. * Makes a network request on behalf of SegmentBase.createStream.
  1267. *
  1268. * @param {!Array.<string>} uris
  1269. * @param {?number} startByte
  1270. * @param {?number} endByte
  1271. * @return {!Promise.<!ArrayBuffer>}
  1272. * @private
  1273. */
  1274. shaka.dash.DashParser.prototype.requestInitSegment_ = function(
  1275. uris, startByte, endByte) {
  1276. var requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
  1277. var request = shaka.net.NetworkingEngine.makeRequest(
  1278. uris, this.config_.retryParameters);
  1279. if (startByte != null) {
  1280. var end = (endByte != null ? endByte : '');
  1281. request.headers['Range'] = 'bytes=' + startByte + '-' + end;
  1282. }
  1283. return this.playerInterface_.networkingEngine.request(requestType, request)
  1284. .then(function(response) { return response.data; });
  1285. };
  1286. /**
  1287. * Guess the content type based on MIME type and codecs.
  1288. *
  1289. * @param {string} mimeType
  1290. * @param {string} codecs
  1291. * @return {string}
  1292. * @private
  1293. */
  1294. shaka.dash.DashParser.guessContentType_ = function(mimeType, codecs) {
  1295. var fullMimeType = shaka.util.StreamUtils.getFullMimeType(mimeType, codecs);
  1296. if (shaka.media.TextEngine.isTypeSupported(fullMimeType)) {
  1297. // If it's supported by TextEngine, it's definitely text.
  1298. // We don't check MediaSourceEngine, because that would report support
  1299. // for platform-supported video and audio types as well.
  1300. return shaka.util.ManifestParserUtils.ContentType.TEXT;
  1301. }
  1302. // Otherwise, just split the MIME type. This handles video and audio
  1303. // types well.
  1304. return mimeType.split('/')[0];
  1305. };
  1306. shaka.media.ManifestParser.registerParserByExtension(
  1307. 'mpd', shaka.dash.DashParser);
  1308. shaka.media.ManifestParser.registerParserByMime(
  1309. 'application/dash+xml', shaka.dash.DashParser);