Source: lib/dash/dash_parser.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.dash.DashParser');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.Deprecate');
  9. goog.require('shaka.abr.Ewma');
  10. goog.require('shaka.dash.ContentProtection');
  11. goog.require('shaka.dash.MpdUtils');
  12. goog.require('shaka.dash.SegmentBase');
  13. goog.require('shaka.dash.SegmentList');
  14. goog.require('shaka.dash.SegmentTemplate');
  15. goog.require('shaka.log');
  16. goog.require('shaka.media.Capabilities');
  17. goog.require('shaka.media.ManifestParser');
  18. goog.require('shaka.media.PresentationTimeline');
  19. goog.require('shaka.media.SegmentIndex');
  20. goog.require('shaka.media.SegmentUtils');
  21. goog.require('shaka.net.NetworkingEngine');
  22. goog.require('shaka.text.TextEngine');
  23. goog.require('shaka.util.ContentSteeringManager');
  24. goog.require('shaka.util.Error');
  25. goog.require('shaka.util.Functional');
  26. goog.require('shaka.util.LanguageUtils');
  27. goog.require('shaka.util.ManifestParserUtils');
  28. goog.require('shaka.util.MimeUtils');
  29. goog.require('shaka.util.Networking');
  30. goog.require('shaka.util.OperationManager');
  31. goog.require('shaka.util.PeriodCombiner');
  32. goog.require('shaka.util.PlayerConfiguration');
  33. goog.require('shaka.util.StringUtils');
  34. goog.require('shaka.util.Timer');
  35. goog.require('shaka.util.TXml');
  36. goog.require('shaka.util.XmlUtils');
  37. /**
  38. * Creates a new DASH parser.
  39. *
  40. * @implements {shaka.extern.ManifestParser}
  41. * @export
  42. */
  43. shaka.dash.DashParser = class {
  44. /** Creates a new DASH parser. */
  45. constructor() {
  46. /** @private {?shaka.extern.ManifestConfiguration} */
  47. this.config_ = null;
  48. /** @private {?shaka.extern.ManifestParser.PlayerInterface} */
  49. this.playerInterface_ = null;
  50. /** @private {!Array.<string>} */
  51. this.manifestUris_ = [];
  52. /** @private {?shaka.extern.Manifest} */
  53. this.manifest_ = null;
  54. /** @private {number} */
  55. this.globalId_ = 1;
  56. /** @private {!Array<shaka.extern.xml.Node>} */
  57. this.patchLocationNodes_ = [];
  58. /**
  59. * A context of the living manifest used for processing
  60. * Patch MPD's
  61. * @private {!shaka.dash.DashParser.PatchContext}
  62. */
  63. this.manifestPatchContext_ = {
  64. mpdId: '',
  65. type: '',
  66. profiles: [],
  67. mediaPresentationDuration: null,
  68. availabilityTimeOffset: 0,
  69. getBaseUris: null,
  70. publishTime: 0,
  71. };
  72. /**
  73. * This is a cache is used the store a snapshot of the context
  74. * object which is built up throughout node traversal to maintain
  75. * a current state. This data needs to be preserved for parsing
  76. * patches.
  77. * The key is a combination period and representation id's.
  78. * @private {!Map<string, !shaka.dash.DashParser.Context>}
  79. */
  80. this.contextCache_ = new Map();
  81. /**
  82. * A map of IDs to Stream objects.
  83. * ID: Period@id,AdaptationSet@id,@Representation@id
  84. * e.g.: '1,5,23'
  85. * @private {!Object.<string, !shaka.extern.Stream>}
  86. */
  87. this.streamMap_ = {};
  88. /**
  89. * A map of period ids to their durations
  90. * @private {!Object.<string, number>}
  91. */
  92. this.periodDurations_ = {};
  93. /** @private {shaka.util.PeriodCombiner} */
  94. this.periodCombiner_ = new shaka.util.PeriodCombiner();
  95. /**
  96. * The update period in seconds, or 0 for no updates.
  97. * @private {number}
  98. */
  99. this.updatePeriod_ = 0;
  100. /**
  101. * An ewma that tracks how long updates take.
  102. * This is to mitigate issues caused by slow parsing on embedded devices.
  103. * @private {!shaka.abr.Ewma}
  104. */
  105. this.averageUpdateDuration_ = new shaka.abr.Ewma(5);
  106. /** @private {shaka.util.Timer} */
  107. this.updateTimer_ = new shaka.util.Timer(() => {
  108. this.onUpdate_();
  109. });
  110. /** @private {!shaka.util.OperationManager} */
  111. this.operationManager_ = new shaka.util.OperationManager();
  112. /**
  113. * Largest period start time seen.
  114. * @private {?number}
  115. */
  116. this.largestPeriodStartTime_ = null;
  117. /**
  118. * Period IDs seen in previous manifest.
  119. * @private {!Array.<string>}
  120. */
  121. this.lastManifestUpdatePeriodIds_ = [];
  122. /**
  123. * The minimum of the availabilityTimeOffset values among the adaptation
  124. * sets.
  125. * @private {number}
  126. */
  127. this.minTotalAvailabilityTimeOffset_ = Infinity;
  128. /** @private {boolean} */
  129. this.lowLatencyMode_ = false;
  130. /** @private {?shaka.util.ContentSteeringManager} */
  131. this.contentSteeringManager_ = null;
  132. }
  133. /**
  134. * @override
  135. * @exportInterface
  136. */
  137. configure(config) {
  138. goog.asserts.assert(config.dash != null,
  139. 'DashManifestConfiguration should not be null!');
  140. const needFireUpdate = this.playerInterface_ &&
  141. config.dash.updatePeriod != this.config_.dash.updatePeriod &&
  142. config.dash.updatePeriod >= 0;
  143. this.config_ = config;
  144. if (needFireUpdate && this.manifest_ &&
  145. this.manifest_.presentationTimeline.isLive()) {
  146. this.updateNow_();
  147. }
  148. if (this.contentSteeringManager_) {
  149. this.contentSteeringManager_.configure(this.config_);
  150. }
  151. if (this.periodCombiner_) {
  152. this.periodCombiner_.setAllowMultiTypeVariants(
  153. this.config_.dash.multiTypeVariantsAllowed &&
  154. shaka.media.Capabilities.isChangeTypeSupported());
  155. this.periodCombiner_.setUseStreamOnce(
  156. this.config_.dash.useStreamOnceInPeriodFlattening);
  157. }
  158. }
  159. /**
  160. * @override
  161. * @exportInterface
  162. */
  163. async start(uri, playerInterface) {
  164. goog.asserts.assert(this.config_, 'Must call configure() before start()!');
  165. this.lowLatencyMode_ = playerInterface.isLowLatencyMode();
  166. this.manifestUris_ = [uri];
  167. this.playerInterface_ = playerInterface;
  168. const updateDelay = await this.requestManifest_();
  169. if (this.playerInterface_) {
  170. this.setUpdateTimer_(updateDelay);
  171. }
  172. // Make sure that the parser has not been destroyed.
  173. if (!this.playerInterface_) {
  174. throw new shaka.util.Error(
  175. shaka.util.Error.Severity.CRITICAL,
  176. shaka.util.Error.Category.PLAYER,
  177. shaka.util.Error.Code.OPERATION_ABORTED);
  178. }
  179. goog.asserts.assert(this.manifest_, 'Manifest should be non-null!');
  180. return this.manifest_;
  181. }
  182. /**
  183. * @override
  184. * @exportInterface
  185. */
  186. stop() {
  187. // When the parser stops, release all segment indexes, which stops their
  188. // timers, as well.
  189. for (const stream of Object.values(this.streamMap_)) {
  190. if (stream.segmentIndex) {
  191. stream.segmentIndex.release();
  192. }
  193. }
  194. if (this.periodCombiner_) {
  195. this.periodCombiner_.release();
  196. }
  197. this.playerInterface_ = null;
  198. this.config_ = null;
  199. this.manifestUris_ = [];
  200. this.manifest_ = null;
  201. this.streamMap_ = {};
  202. this.contextCache_.clear();
  203. this.manifestPatchContext_ = {
  204. mpdId: '',
  205. type: '',
  206. profiles: [],
  207. mediaPresentationDuration: null,
  208. availabilityTimeOffset: 0,
  209. getBaseUris: null,
  210. publishTime: 0,
  211. };
  212. this.periodCombiner_ = null;
  213. if (this.updateTimer_ != null) {
  214. this.updateTimer_.stop();
  215. this.updateTimer_ = null;
  216. }
  217. if (this.contentSteeringManager_) {
  218. this.contentSteeringManager_.destroy();
  219. }
  220. return this.operationManager_.destroy();
  221. }
  222. /**
  223. * @override
  224. * @exportInterface
  225. */
  226. async update() {
  227. try {
  228. await this.requestManifest_();
  229. } catch (error) {
  230. if (!this.playerInterface_ || !error) {
  231. return;
  232. }
  233. goog.asserts.assert(error instanceof shaka.util.Error, 'Bad error type');
  234. this.playerInterface_.onError(error);
  235. }
  236. }
  237. /**
  238. * @override
  239. * @exportInterface
  240. */
  241. onExpirationUpdated(sessionId, expiration) {
  242. // No-op
  243. }
  244. /**
  245. * @override
  246. * @exportInterface
  247. */
  248. onInitialVariantChosen(variant) {
  249. // For live it is necessary that the first time we update the manifest with
  250. // a shorter time than indicated to take into account that the last segment
  251. // added could be halfway, for example
  252. if (this.manifest_ && this.manifest_.presentationTimeline.isLive()) {
  253. const stream = variant.video || variant.audio;
  254. if (stream && stream.segmentIndex) {
  255. const availabilityEnd =
  256. this.manifest_.presentationTimeline.getSegmentAvailabilityEnd();
  257. const position = stream.segmentIndex.find(availabilityEnd);
  258. if (position == null) {
  259. return;
  260. }
  261. const reference = stream.segmentIndex.get(position);
  262. if (!reference) {
  263. return;
  264. }
  265. this.updatePeriod_ = reference.endTime - availabilityEnd;
  266. this.setUpdateTimer_(/* offset= */ 0);
  267. }
  268. }
  269. }
  270. /**
  271. * @override
  272. * @exportInterface
  273. */
  274. banLocation(uri) {
  275. if (this.contentSteeringManager_) {
  276. this.contentSteeringManager_.banLocation(uri);
  277. }
  278. }
  279. /**
  280. * Makes a network request for the manifest and parses the resulting data.
  281. *
  282. * @return {!Promise.<number>} Resolves with the time it took, in seconds, to
  283. * fulfill the request and parse the data.
  284. * @private
  285. */
  286. async requestManifest_() {
  287. const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
  288. const type = shaka.net.NetworkingEngine.AdvancedRequestType.MPD;
  289. let rootElement = 'MPD';
  290. const patchLocationUris = this.getPatchLocationUris_();
  291. let manifestUris = this.manifestUris_;
  292. if (patchLocationUris.length) {
  293. manifestUris = patchLocationUris;
  294. rootElement = 'Patch';
  295. } else if (this.manifestUris_.length > 1 && this.contentSteeringManager_) {
  296. const locations = this.contentSteeringManager_.getLocations(
  297. 'Location', /* ignoreBaseUrls= */ true);
  298. if (locations.length) {
  299. manifestUris = locations;
  300. }
  301. }
  302. const request = shaka.net.NetworkingEngine.makeRequest(
  303. manifestUris, this.config_.retryParameters);
  304. const startTime = Date.now();
  305. const response = await this.makeNetworkRequest_(
  306. request, requestType, {type});
  307. // Detect calls to stop().
  308. if (!this.playerInterface_) {
  309. return 0;
  310. }
  311. // For redirections add the response uri to the first entry in the
  312. // Manifest Uris array.
  313. if (response.uri && response.uri != response.originalUri &&
  314. !this.manifestUris_.includes(response.uri)) {
  315. this.manifestUris_.unshift(response.uri);
  316. }
  317. // This may throw, but it will result in a failed promise.
  318. await this.parseManifest_(response.data, response.uri, rootElement);
  319. // Keep track of how long the longest manifest update took.
  320. const endTime = Date.now();
  321. const updateDuration = (endTime - startTime) / 1000.0;
  322. this.averageUpdateDuration_.sample(1, updateDuration);
  323. // Let the caller know how long this update took.
  324. return updateDuration;
  325. }
  326. /**
  327. * Parses the manifest XML. This also handles updates and will update the
  328. * stored manifest.
  329. *
  330. * @param {BufferSource} data
  331. * @param {string} finalManifestUri The final manifest URI, which may
  332. * differ from this.manifestUri_ if there has been a redirect.
  333. * @param {string} rootElement MPD or Patch, depending on context
  334. * @return {!Promise}
  335. * @private
  336. */
  337. async parseManifest_(data, finalManifestUri, rootElement) {
  338. let manifestData = data;
  339. const manifestPreprocessor = this.config_.dash.manifestPreprocessor;
  340. const defaultManifestPreprocessor =
  341. shaka.util.PlayerConfiguration.defaultManifestPreprocessor;
  342. if (manifestPreprocessor != defaultManifestPreprocessor) {
  343. shaka.Deprecate.deprecateFeature(5,
  344. 'manifest.dash.manifestPreprocessor configuration',
  345. 'Please Use manifest.dash.manifestPreprocessorTXml instead.');
  346. const mpdElement =
  347. shaka.util.XmlUtils.parseXml(manifestData, rootElement);
  348. if (!mpdElement) {
  349. throw new shaka.util.Error(
  350. shaka.util.Error.Severity.CRITICAL,
  351. shaka.util.Error.Category.MANIFEST,
  352. shaka.util.Error.Code.DASH_INVALID_XML,
  353. finalManifestUri);
  354. }
  355. manifestPreprocessor(mpdElement);
  356. manifestData = shaka.util.XmlUtils.toArrayBuffer(mpdElement);
  357. }
  358. const mpd = shaka.util.TXml.parseXml(manifestData, rootElement);
  359. if (!mpd) {
  360. throw new shaka.util.Error(
  361. shaka.util.Error.Severity.CRITICAL,
  362. shaka.util.Error.Category.MANIFEST,
  363. shaka.util.Error.Code.DASH_INVALID_XML,
  364. finalManifestUri);
  365. }
  366. const manifestPreprocessorTXml =
  367. this.config_.dash.manifestPreprocessorTXml;
  368. const defaultManifestPreprocessorTXml =
  369. shaka.util.PlayerConfiguration.defaultManifestPreprocessorTXml;
  370. if (manifestPreprocessorTXml != defaultManifestPreprocessorTXml) {
  371. manifestPreprocessorTXml(mpd);
  372. }
  373. if (rootElement === 'Patch') {
  374. return this.processPatchManifest_(mpd);
  375. }
  376. const disableXlinkProcessing = this.config_.dash.disableXlinkProcessing;
  377. if (disableXlinkProcessing) {
  378. return this.processManifest_(mpd, finalManifestUri);
  379. }
  380. // Process the mpd to account for xlink connections.
  381. const failGracefully = this.config_.dash.xlinkFailGracefully;
  382. const xlinkOperation = shaka.dash.MpdUtils.processXlinks(
  383. mpd, this.config_.retryParameters, failGracefully, finalManifestUri,
  384. this.playerInterface_.networkingEngine);
  385. this.operationManager_.manage(xlinkOperation);
  386. const finalMpd = await xlinkOperation.promise;
  387. return this.processManifest_(finalMpd, finalManifestUri);
  388. }
  389. /**
  390. * Takes a formatted MPD and converts it into a manifest.
  391. *
  392. * @param {!shaka.extern.xml.Node} mpd
  393. * @param {string} finalManifestUri The final manifest URI, which may
  394. * differ from this.manifestUri_ if there has been a redirect.
  395. * @return {!Promise}
  396. * @private
  397. */
  398. async processManifest_(mpd, finalManifestUri) {
  399. const TXml = shaka.util.TXml;
  400. goog.asserts.assert(this.config_,
  401. 'Must call configure() before processManifest_()!');
  402. if (this.contentSteeringManager_) {
  403. this.contentSteeringManager_.clearPreviousLocations();
  404. }
  405. // Get any Location elements. This will update the manifest location and
  406. // the base URI.
  407. /** @type {!Array.<string>} */
  408. let manifestBaseUris = [finalManifestUri];
  409. /** @type {!Array.<string>} */
  410. const locations = [];
  411. /** @type {!Map.<string, string>} */
  412. const locationsMapping = new Map();
  413. const locationsObjs = TXml.findChildren(mpd, 'Location');
  414. for (const locationsObj of locationsObjs) {
  415. const serviceLocation = locationsObj.attributes['serviceLocation'];
  416. const uri = TXml.getContents(locationsObj);
  417. if (!uri) {
  418. continue;
  419. }
  420. const finalUri = shaka.util.ManifestParserUtils.resolveUris(
  421. manifestBaseUris, [uri])[0];
  422. if (serviceLocation) {
  423. if (this.contentSteeringManager_) {
  424. this.contentSteeringManager_.addLocation(
  425. 'Location', serviceLocation, finalUri);
  426. } else {
  427. locationsMapping.set(serviceLocation, finalUri);
  428. }
  429. }
  430. locations.push(finalUri);
  431. }
  432. if (this.contentSteeringManager_) {
  433. const steeringlocations = this.contentSteeringManager_.getLocations(
  434. 'Location', /* ignoreBaseUrls= */ true);
  435. if (steeringlocations.length > 0) {
  436. this.manifestUris_ = steeringlocations;
  437. manifestBaseUris = steeringlocations;
  438. }
  439. } else if (locations.length) {
  440. this.manifestUris_ = locations;
  441. manifestBaseUris = locations;
  442. }
  443. this.manifestPatchContext_.mpdId = mpd.attributes['id'] || '';
  444. this.manifestPatchContext_.publishTime =
  445. TXml.parseAttr(mpd, 'publishTime', TXml.parseDate) || 0;
  446. this.patchLocationNodes_ = TXml.findChildren(mpd, 'PatchLocation');
  447. let contentSteeringPromise = Promise.resolve();
  448. const contentSteering = TXml.findChild(mpd, 'ContentSteering');
  449. if (contentSteering && this.playerInterface_) {
  450. const defaultPathwayId =
  451. contentSteering.attributes['defaultServiceLocation'];
  452. if (!this.contentSteeringManager_) {
  453. this.contentSteeringManager_ =
  454. new shaka.util.ContentSteeringManager(this.playerInterface_);
  455. this.contentSteeringManager_.configure(this.config_);
  456. this.contentSteeringManager_.setManifestType(
  457. shaka.media.ManifestParser.DASH);
  458. this.contentSteeringManager_.setBaseUris(manifestBaseUris);
  459. this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
  460. const uri = TXml.getContents(contentSteering);
  461. if (uri) {
  462. const queryBeforeStart =
  463. TXml.parseAttr(contentSteering, 'queryBeforeStart',
  464. TXml.parseBoolean, /* defaultValue= */ false);
  465. if (queryBeforeStart) {
  466. contentSteeringPromise =
  467. this.contentSteeringManager_.requestInfo(uri);
  468. } else {
  469. this.contentSteeringManager_.requestInfo(uri);
  470. }
  471. }
  472. } else {
  473. this.contentSteeringManager_.setBaseUris(manifestBaseUris);
  474. this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
  475. }
  476. for (const serviceLocation of locationsMapping.keys()) {
  477. const uri = locationsMapping.get(serviceLocation);
  478. this.contentSteeringManager_.addLocation(
  479. 'Location', serviceLocation, uri);
  480. }
  481. }
  482. const uriObjs = TXml.findChildren(mpd, 'BaseURL');
  483. let calculatedBaseUris;
  484. let someLocationValid = false;
  485. if (this.contentSteeringManager_) {
  486. for (const uriObj of uriObjs) {
  487. const serviceLocation = uriObj.attributes['serviceLocation'];
  488. const uri = TXml.getContents(uriObj);
  489. if (serviceLocation && uri) {
  490. this.contentSteeringManager_.addLocation(
  491. 'BaseURL', serviceLocation, uri);
  492. someLocationValid = true;
  493. }
  494. }
  495. }
  496. if (!someLocationValid || !this.contentSteeringManager_) {
  497. const uris = uriObjs.map(TXml.getContents);
  498. calculatedBaseUris = shaka.util.ManifestParserUtils.resolveUris(
  499. manifestBaseUris, uris);
  500. }
  501. const getBaseUris = () => {
  502. if (this.contentSteeringManager_ && someLocationValid) {
  503. return this.contentSteeringManager_.getLocations('BaseURL');
  504. }
  505. if (calculatedBaseUris) {
  506. return calculatedBaseUris;
  507. }
  508. return [];
  509. };
  510. this.manifestPatchContext_.getBaseUris = getBaseUris;
  511. let availabilityTimeOffset = 0;
  512. if (uriObjs && uriObjs.length) {
  513. availabilityTimeOffset = TXml.parseAttr(uriObjs[0],
  514. 'availabilityTimeOffset', TXml.parseFloat) || 0;
  515. }
  516. this.manifestPatchContext_.availabilityTimeOffset = availabilityTimeOffset;
  517. const ignoreMinBufferTime = this.config_.dash.ignoreMinBufferTime;
  518. let minBufferTime = 0;
  519. if (!ignoreMinBufferTime) {
  520. minBufferTime =
  521. TXml.parseAttr(mpd, 'minBufferTime', TXml.parseDuration) || 0;
  522. }
  523. this.updatePeriod_ = /** @type {number} */ (TXml.parseAttr(
  524. mpd, 'minimumUpdatePeriod', TXml.parseDuration, -1));
  525. const presentationStartTime = TXml.parseAttr(
  526. mpd, 'availabilityStartTime', TXml.parseDate);
  527. let segmentAvailabilityDuration = TXml.parseAttr(
  528. mpd, 'timeShiftBufferDepth', TXml.parseDuration);
  529. const ignoreSuggestedPresentationDelay =
  530. this.config_.dash.ignoreSuggestedPresentationDelay;
  531. let suggestedPresentationDelay = null;
  532. if (!ignoreSuggestedPresentationDelay) {
  533. suggestedPresentationDelay = TXml.parseAttr(
  534. mpd, 'suggestedPresentationDelay', TXml.parseDuration);
  535. }
  536. const ignoreMaxSegmentDuration =
  537. this.config_.dash.ignoreMaxSegmentDuration;
  538. let maxSegmentDuration = null;
  539. if (!ignoreMaxSegmentDuration) {
  540. maxSegmentDuration = TXml.parseAttr(
  541. mpd, 'maxSegmentDuration', TXml.parseDuration);
  542. }
  543. const mpdType = mpd.attributes['type'] || 'static';
  544. this.manifestPatchContext_.type = mpdType;
  545. /** @type {!shaka.media.PresentationTimeline} */
  546. let presentationTimeline;
  547. if (this.manifest_) {
  548. presentationTimeline = this.manifest_.presentationTimeline;
  549. // Before processing an update, evict from all segment indexes. Some of
  550. // them may not get updated otherwise if their corresponding Period
  551. // element has been dropped from the manifest since the last update.
  552. // Without this, playback will still work, but this is necessary to
  553. // maintain conditions that we assert on for multi-Period content.
  554. // This gives us confidence that our state is maintained correctly, and
  555. // that the complex logic of multi-Period eviction and period-flattening
  556. // is correct. See also:
  557. // https://github.com/shaka-project/shaka-player/issues/3169#issuecomment-823580634
  558. for (const stream of Object.values(this.streamMap_)) {
  559. if (stream.segmentIndex) {
  560. stream.segmentIndex.evict(
  561. presentationTimeline.getSegmentAvailabilityStart());
  562. }
  563. }
  564. } else {
  565. // DASH IOP v3.0 suggests using a default delay between minBufferTime
  566. // and timeShiftBufferDepth. This is literally the range of all
  567. // feasible choices for the value. Nothing older than
  568. // timeShiftBufferDepth is still available, and anything less than
  569. // minBufferTime will cause buffering issues.
  570. //
  571. // We have decided that our default will be the configured value, or
  572. // 1.5 * minBufferTime if not configured. This is fairly conservative.
  573. // Content providers should provide a suggestedPresentationDelay whenever
  574. // possible to optimize the live streaming experience.
  575. const defaultPresentationDelay =
  576. this.config_.defaultPresentationDelay || minBufferTime * 1.5;
  577. const presentationDelay = suggestedPresentationDelay != null ?
  578. suggestedPresentationDelay : defaultPresentationDelay;
  579. presentationTimeline = new shaka.media.PresentationTimeline(
  580. presentationStartTime, presentationDelay,
  581. this.config_.dash.autoCorrectDrift);
  582. }
  583. presentationTimeline.setStatic(mpdType == 'static');
  584. const isLive = presentationTimeline.isLive();
  585. // If it's live, we check for an override.
  586. if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
  587. segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
  588. }
  589. // If it's null, that means segments are always available. This is always
  590. // the case for VOD, and sometimes the case for live.
  591. if (segmentAvailabilityDuration == null) {
  592. segmentAvailabilityDuration = Infinity;
  593. }
  594. presentationTimeline.setSegmentAvailabilityDuration(
  595. segmentAvailabilityDuration);
  596. const profiles = mpd.attributes['profiles'] || '';
  597. this.manifestPatchContext_.profiles = profiles.split(',');
  598. /** @type {shaka.dash.DashParser.Context} */
  599. const context = {
  600. // Don't base on updatePeriod_ since emsg boxes can cause manifest
  601. // updates.
  602. dynamic: mpdType != 'static',
  603. presentationTimeline: presentationTimeline,
  604. period: null,
  605. periodInfo: null,
  606. adaptationSet: null,
  607. representation: null,
  608. bandwidth: 0,
  609. indexRangeWarningGiven: false,
  610. availabilityTimeOffset: availabilityTimeOffset,
  611. mediaPresentationDuration: null,
  612. profiles: profiles.split(','),
  613. };
  614. const periodsAndDuration = this.parsePeriods_(context, getBaseUris, mpd);
  615. const duration = periodsAndDuration.duration;
  616. const periods = periodsAndDuration.periods;
  617. if (mpdType == 'static' ||
  618. !periodsAndDuration.durationDerivedFromPeriods) {
  619. // Ignore duration calculated from Period lengths if this is dynamic.
  620. presentationTimeline.setDuration(duration || Infinity);
  621. }
  622. // The segments are available earlier than the availability start time.
  623. // If the stream is low latency and the user has not configured the
  624. // lowLatencyMode, but if it has been configured to activate the
  625. // lowLatencyMode if a stream of this type is detected, we automatically
  626. // activate the lowLatencyMode.
  627. if (this.minTotalAvailabilityTimeOffset_ && !this.lowLatencyMode_) {
  628. const autoLowLatencyMode = this.playerInterface_.isAutoLowLatencyMode();
  629. if (autoLowLatencyMode) {
  630. this.playerInterface_.enableLowLatencyMode();
  631. this.lowLatencyMode_ = this.playerInterface_.isLowLatencyMode();
  632. }
  633. }
  634. if (this.lowLatencyMode_) {
  635. presentationTimeline.setAvailabilityTimeOffset(
  636. this.minTotalAvailabilityTimeOffset_);
  637. } else if (this.minTotalAvailabilityTimeOffset_) {
  638. // If the playlist contains AvailabilityTimeOffset value, the
  639. // streaming.lowLatencyMode value should be set to true to stream with low
  640. // latency mode.
  641. shaka.log.alwaysWarn('Low-latency DASH live stream detected, but ' +
  642. 'low-latency streaming mode is not enabled in Shaka Player. ' +
  643. 'Set streaming.lowLatencyMode configuration to true, and see ' +
  644. 'https://bit.ly/3clctcj for details.');
  645. }
  646. // Use @maxSegmentDuration to override smaller, derived values.
  647. presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
  648. if (goog.DEBUG) {
  649. presentationTimeline.assertIsValid();
  650. }
  651. await contentSteeringPromise;
  652. // Set minBufferTime to 0 for low-latency DASH live stream to achieve the
  653. // best latency
  654. if (this.lowLatencyMode_) {
  655. minBufferTime = 0;
  656. const presentationDelay = suggestedPresentationDelay != null ?
  657. suggestedPresentationDelay : this.config_.defaultPresentationDelay;
  658. presentationTimeline.setDelay(presentationDelay);
  659. }
  660. // These steps are not done on manifest update.
  661. if (!this.manifest_) {
  662. await this.periodCombiner_.combinePeriods(periods, context.dynamic);
  663. this.manifest_ = {
  664. presentationTimeline: presentationTimeline,
  665. variants: this.periodCombiner_.getVariants(),
  666. textStreams: this.periodCombiner_.getTextStreams(),
  667. imageStreams: this.periodCombiner_.getImageStreams(),
  668. offlineSessionIds: [],
  669. minBufferTime: minBufferTime || 0,
  670. sequenceMode: this.config_.dash.sequenceMode,
  671. ignoreManifestTimestampsInSegmentsMode: false,
  672. type: shaka.media.ManifestParser.DASH,
  673. serviceDescription: this.parseServiceDescription_(mpd),
  674. nextUrl: this.parseMpdChaining_(mpd),
  675. };
  676. // We only need to do clock sync when we're using presentation start
  677. // time. This condition also excludes VOD streams.
  678. if (presentationTimeline.usingPresentationStartTime()) {
  679. const TXml = shaka.util.TXml;
  680. const timingElements = TXml.findChildren(mpd, 'UTCTiming');
  681. const offset = await this.parseUtcTiming_(getBaseUris, timingElements);
  682. // Detect calls to stop().
  683. if (!this.playerInterface_) {
  684. return;
  685. }
  686. presentationTimeline.setClockOffset(offset);
  687. }
  688. // This is the first point where we have a meaningful presentation start
  689. // time, and we need to tell PresentationTimeline that so that it can
  690. // maintain consistency from here on.
  691. presentationTimeline.lockStartTime();
  692. } else {
  693. await this.postPeriodProcessing_(
  694. periodsAndDuration.periods, /* isPatchUpdate= */ false);
  695. }
  696. // Add text streams to correspond to closed captions. This happens right
  697. // after period combining, while we still have a direct reference, so that
  698. // any new streams will appear in the period combiner.
  699. this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
  700. }
  701. /**
  702. * Handles common procedures after processing new periods.
  703. *
  704. * @param {!Array<shaka.extern.Period>} periods to be appended
  705. * @param {boolean} isPatchUpdate does call comes from mpd patch update
  706. * @private
  707. */
  708. async postPeriodProcessing_(periods, isPatchUpdate) {
  709. await this.periodCombiner_.combinePeriods(periods, true, isPatchUpdate);
  710. // Just update the variants and text streams, which may change as periods
  711. // are added or removed.
  712. this.manifest_.variants = this.periodCombiner_.getVariants();
  713. const textStreams = this.periodCombiner_.getTextStreams();
  714. if (textStreams.length > 0) {
  715. this.manifest_.textStreams = textStreams;
  716. }
  717. this.manifest_.imageStreams = this.periodCombiner_.getImageStreams();
  718. // Re-filter the manifest. This will check any configured restrictions on
  719. // new variants, and will pass any new init data to DrmEngine to ensure
  720. // that key rotation works correctly.
  721. this.playerInterface_.filter(this.manifest_);
  722. }
  723. /**
  724. * Takes a formatted Patch MPD and converts it into a manifest.
  725. *
  726. * @param {!shaka.extern.xml.Node} mpd
  727. * @return {!Promise}
  728. * @private
  729. */
  730. async processPatchManifest_(mpd) {
  731. const TXml = shaka.util.TXml;
  732. const mpdId = mpd.attributes['mpdId'];
  733. const originalPublishTime = TXml.parseAttr(mpd, 'originalPublishTime',
  734. TXml.parseDate);
  735. if (!mpdId || mpdId !== this.manifestPatchContext_.mpdId ||
  736. originalPublishTime !== this.manifestPatchContext_.publishTime) {
  737. // Clean patch location nodes, so it will force full MPD update.
  738. this.patchLocationNodes_ = [];
  739. throw new shaka.util.Error(
  740. shaka.util.Error.Severity.RECOVERABLE,
  741. shaka.util.Error.Category.MANIFEST,
  742. shaka.util.Error.Code.DASH_INVALID_PATCH);
  743. }
  744. /** @type {!Array<shaka.extern.Period>} */
  745. const newPeriods = [];
  746. /** @type {!Array<shaka.extern.xml.Node>} */
  747. const periodAdditions = [];
  748. /** @type {!Set<string>} */
  749. const modifiedTimelines = new Set();
  750. for (const patchNode of TXml.getChildNodes(mpd)) {
  751. let handled = true;
  752. const paths = TXml.parseXpath(patchNode.attributes['sel'] || '');
  753. const node = paths[paths.length - 1];
  754. const content = TXml.getContents(patchNode) || '';
  755. if (node.name === 'MPD') {
  756. if (node.attribute === 'mediaPresentationDuration') {
  757. const content = TXml.getContents(patchNode) || '';
  758. this.parsePatchMediaPresentationDurationChange_(content);
  759. } else if (node.attribute === 'type') {
  760. this.parsePatchMpdTypeChange_(content);
  761. } else if (node.attribute === 'publishTime') {
  762. this.manifestPatchContext_.publishTime = TXml.parseDate(content) || 0;
  763. } else if (node.attribute === null && patchNode.tagName === 'add') {
  764. periodAdditions.push(patchNode);
  765. } else {
  766. handled = false;
  767. }
  768. } else if (node.name === 'PatchLocation') {
  769. this.updatePatchLocationNodes_(patchNode);
  770. } else if (node.name === 'Period') {
  771. if (patchNode.tagName === 'add') {
  772. periodAdditions.push(patchNode);
  773. } else if (patchNode.tagName === 'remove' && node.id) {
  774. this.removePatchPeriod_(node.id);
  775. }
  776. } else if (node.name === 'SegmentTemplate') {
  777. const timelines = this.modifySegmentTemplate_(patchNode);
  778. for (const timeline of timelines) {
  779. modifiedTimelines.add(timeline);
  780. }
  781. } else if (node.name === 'SegmentTimeline' || node.name === 'S') {
  782. const timelines = this.modifyTimepoints_(patchNode);
  783. for (const timeline of timelines) {
  784. modifiedTimelines.add(timeline);
  785. }
  786. } else {
  787. handled = false;
  788. }
  789. if (!handled) {
  790. shaka.log.warning('Unhandled ' + patchNode.tagName + ' operation',
  791. patchNode.attributes['sel']);
  792. }
  793. }
  794. for (const timeline of modifiedTimelines) {
  795. this.parsePatchSegment_(timeline);
  796. }
  797. // Add new periods after extending timelines, as new periods
  798. // remove context cache of previous periods.
  799. for (const periodAddition of periodAdditions) {
  800. newPeriods.push(...this.parsePatchPeriod_(periodAddition));
  801. }
  802. if (newPeriods.length) {
  803. await this.postPeriodProcessing_(newPeriods, /* isPatchUpdate= */ true);
  804. }
  805. if (this.manifestPatchContext_.type == 'static') {
  806. const duration = this.manifestPatchContext_.mediaPresentationDuration;
  807. this.manifest_.presentationTimeline.setDuration(duration || Infinity);
  808. }
  809. }
  810. /**
  811. * Handles manifest type changes, this transition is expected to be
  812. * "dyanmic" to "static".
  813. *
  814. * @param {!string} mpdType
  815. * @private
  816. */
  817. parsePatchMpdTypeChange_(mpdType) {
  818. this.manifest_.presentationTimeline.setStatic(mpdType == 'static');
  819. this.manifestPatchContext_.type = mpdType;
  820. for (const context of this.contextCache_.values()) {
  821. context.dynamic = mpdType == 'dynamic';
  822. }
  823. if (mpdType == 'static') {
  824. // Manifest is no longer dynamic, so stop live updates.
  825. this.updatePeriod_ = -1;
  826. }
  827. }
  828. /**
  829. * @param {string} durationString
  830. * @private
  831. */
  832. parsePatchMediaPresentationDurationChange_(durationString) {
  833. const duration = shaka.util.TXml.parseDuration(durationString);
  834. if (duration == null) {
  835. return;
  836. }
  837. this.manifestPatchContext_.mediaPresentationDuration = duration;
  838. for (const context of this.contextCache_.values()) {
  839. context.mediaPresentationDuration = duration;
  840. }
  841. }
  842. /**
  843. * Ingests a full MPD period element from a patch update
  844. *
  845. * @param {!shaka.extern.xml.Node} periods
  846. * @private
  847. */
  848. parsePatchPeriod_(periods) {
  849. goog.asserts.assert(this.manifestPatchContext_.getBaseUris,
  850. 'Must provide getBaseUris on manifestPatchContext_');
  851. /** @type {shaka.dash.DashParser.Context} */
  852. const context = {
  853. dynamic: this.manifestPatchContext_.type == 'dynamic',
  854. presentationTimeline: this.manifest_.presentationTimeline,
  855. period: null,
  856. periodInfo: null,
  857. adaptationSet: null,
  858. representation: null,
  859. bandwidth: 0,
  860. indexRangeWarningGiven: false,
  861. availabilityTimeOffset: this.manifestPatchContext_.availabilityTimeOffset,
  862. profiles: this.manifestPatchContext_.profiles,
  863. mediaPresentationDuration:
  864. this.manifestPatchContext_.mediaPresentationDuration,
  865. };
  866. const periodsAndDuration = this.parsePeriods_(context,
  867. this.manifestPatchContext_.getBaseUris, periods);
  868. return periodsAndDuration.periods;
  869. }
  870. /**
  871. * @param {string} periodId
  872. * @private
  873. */
  874. removePatchPeriod_(periodId) {
  875. const SegmentTemplate = shaka.dash.SegmentTemplate;
  876. for (const contextId of this.contextCache_.keys()) {
  877. if (contextId.startsWith(periodId)) {
  878. const context = this.contextCache_.get(contextId);
  879. SegmentTemplate.removeTimepoints(context);
  880. this.parsePatchSegment_(contextId);
  881. this.contextCache_.delete(contextId);
  882. }
  883. }
  884. }
  885. /**
  886. * @param {!Array<shaka.util.TXml.PathNode>} paths
  887. * @return {!Array<string>}
  888. * @private
  889. */
  890. getContextIdsFromPath_(paths) {
  891. let periodId = '';
  892. let adaptationSetId = '';
  893. let representationId = '';
  894. for (const node of paths) {
  895. if (node.name === 'Period') {
  896. periodId = node.id;
  897. } else if (node.name === 'AdaptationSet') {
  898. adaptationSetId = node.id;
  899. } else if (node.name === 'Representation') {
  900. representationId = node.id;
  901. }
  902. }
  903. /** @type {!Array<string>} */
  904. const contextIds = [];
  905. if (representationId) {
  906. contextIds.push(periodId + ',' + representationId);
  907. } else {
  908. for (const context of this.contextCache_.values()) {
  909. if (context.period.id === periodId &&
  910. context.adaptationSet.id === adaptationSetId &&
  911. context.representation.id) {
  912. contextIds.push(periodId + ',' + context.representation.id);
  913. }
  914. }
  915. }
  916. return contextIds;
  917. }
  918. /**
  919. * Modifies SegmentTemplate based on MPD patch.
  920. *
  921. * @param {!shaka.extern.xml.Node} patchNode
  922. * @return {!Array<string>} context ids with updated timeline
  923. * @private
  924. */
  925. modifySegmentTemplate_(patchNode) {
  926. const TXml = shaka.util.TXml;
  927. const paths = TXml.parseXpath(patchNode.attributes['sel'] || '');
  928. const lastPath = paths[paths.length - 1];
  929. if (!lastPath.attribute) {
  930. return [];
  931. }
  932. const contextIds = this.getContextIdsFromPath_(paths);
  933. const content = TXml.getContents(patchNode) || '';
  934. for (const contextId of contextIds) {
  935. /** @type {shaka.dash.DashParser.Context} */
  936. const context = this.contextCache_.get(contextId);
  937. goog.asserts.assert(context && context.representation.segmentTemplate,
  938. 'cannot modify segment template');
  939. TXml.modifyNodeAttribute(context.representation.segmentTemplate,
  940. patchNode.tagName, lastPath.attribute, content);
  941. }
  942. return contextIds;
  943. }
  944. /**
  945. * Ingests Patch MPD segments into timeline.
  946. *
  947. * @param {!shaka.extern.xml.Node} patchNode
  948. * @return {!Array<string>} context ids with updated timeline
  949. * @private
  950. */
  951. modifyTimepoints_(patchNode) {
  952. const TXml = shaka.util.TXml;
  953. const SegmentTemplate = shaka.dash.SegmentTemplate;
  954. const paths = TXml.parseXpath(patchNode.attributes['sel'] || '');
  955. const contextIds = this.getContextIdsFromPath_(paths);
  956. for (const contextId of contextIds) {
  957. /** @type {shaka.dash.DashParser.Context} */
  958. const context = this.contextCache_.get(contextId);
  959. SegmentTemplate.modifyTimepoints(context, patchNode);
  960. }
  961. return contextIds;
  962. }
  963. /**
  964. * Parses modified segments.
  965. *
  966. * @param {string} contextId
  967. * @private
  968. */
  969. parsePatchSegment_(contextId) {
  970. /** @type {shaka.dash.DashParser.Context} */
  971. const context = this.contextCache_.get(contextId);
  972. const currentStream = this.streamMap_[contextId];
  973. goog.asserts.assert(currentStream, 'stream should exist');
  974. if (currentStream.segmentIndex) {
  975. currentStream.segmentIndex.evict(
  976. this.manifest_.presentationTimeline.getSegmentAvailabilityStart());
  977. }
  978. try {
  979. const requestSegment = (uris, startByte, endByte, isInit) => {
  980. return this.requestSegment_(uris, startByte, endByte, isInit);
  981. };
  982. // TODO we should obtain lastSegmentNumber if possible
  983. const streamInfo = shaka.dash.SegmentTemplate.createStreamInfo(
  984. context, requestSegment, this.streamMap_, /* isUpdate= */ true,
  985. this.config_.dash.initialSegmentLimit, this.periodDurations_,
  986. context.representation.aesKey, /* lastSegmentNumber= */ null,
  987. /* isPatchUpdate= */ true);
  988. currentStream.createSegmentIndex = async () => {
  989. if (!currentStream.segmentIndex) {
  990. currentStream.segmentIndex =
  991. await streamInfo.generateSegmentIndex();
  992. }
  993. };
  994. } catch (error) {
  995. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  996. const contentType = context.representation.contentType;
  997. const isText = contentType == ContentType.TEXT ||
  998. contentType == ContentType.APPLICATION;
  999. const isImage = contentType == ContentType.IMAGE;
  1000. if (!(isText || isImage) ||
  1001. error.code != shaka.util.Error.Code.DASH_NO_SEGMENT_INFO) {
  1002. // We will ignore any DASH_NO_SEGMENT_INFO errors for text/image
  1003. throw error;
  1004. }
  1005. }
  1006. }
  1007. /**
  1008. * Reads maxLatency and maxPlaybackRate properties from service
  1009. * description element.
  1010. *
  1011. * @param {!shaka.extern.xml.Node} mpd
  1012. * @return {?shaka.extern.ServiceDescription}
  1013. * @private
  1014. */
  1015. parseServiceDescription_(mpd) {
  1016. const TXml = shaka.util.TXml;
  1017. const elem = TXml.findChild(mpd, 'ServiceDescription');
  1018. if (!elem ) {
  1019. return null;
  1020. }
  1021. const latencyNode = TXml.findChild(elem, 'Latency');
  1022. const playbackRateNode = TXml.findChild(elem, 'PlaybackRate');
  1023. if (!latencyNode && !playbackRateNode) {
  1024. return null;
  1025. }
  1026. const description = {};
  1027. if (latencyNode) {
  1028. if ('target' in latencyNode.attributes) {
  1029. description.targetLatency =
  1030. parseInt(latencyNode.attributes['target'], 10) / 1000;
  1031. }
  1032. if ('max' in latencyNode.attributes) {
  1033. description.maxLatency =
  1034. parseInt(latencyNode.attributes['max'], 10) / 1000;
  1035. }
  1036. if ('min' in latencyNode.attributes) {
  1037. description.minLatency =
  1038. parseInt(latencyNode.attributes['min'], 10) / 1000;
  1039. }
  1040. }
  1041. if (playbackRateNode) {
  1042. if ('max' in playbackRateNode.attributes) {
  1043. description.maxPlaybackRate =
  1044. parseFloat(playbackRateNode.attributes['max']);
  1045. }
  1046. if ('min' in playbackRateNode.attributes) {
  1047. description.minPlaybackRate =
  1048. parseFloat(playbackRateNode.attributes['min']);
  1049. }
  1050. }
  1051. return description;
  1052. }
  1053. /**
  1054. * Reads chaining url.
  1055. *
  1056. * @param {!shaka.extern.xml.Node} mpd
  1057. * @return {?string}
  1058. * @private
  1059. */
  1060. parseMpdChaining_(mpd) {
  1061. const TXml = shaka.util.TXml;
  1062. const supplementalProperties =
  1063. TXml.findChildren(mpd, 'SupplementalProperty');
  1064. if (!supplementalProperties.length) {
  1065. return null;
  1066. }
  1067. for (const prop of supplementalProperties) {
  1068. const schemeId = prop.attributes['schemeIdUri'];
  1069. if (schemeId == 'urn:mpeg:dash:chaining:2016') {
  1070. return prop.attributes['value'];
  1071. }
  1072. }
  1073. return null;
  1074. }
  1075. /**
  1076. * Reads and parses the periods from the manifest. This first does some
  1077. * partial parsing so the start and duration is available when parsing
  1078. * children.
  1079. *
  1080. * @param {shaka.dash.DashParser.Context} context
  1081. * @param {function():!Array.<string>} getBaseUris
  1082. * @param {!shaka.extern.xml.Node} mpd
  1083. * @return {{
  1084. * periods: !Array.<shaka.extern.Period>,
  1085. * duration: ?number,
  1086. * durationDerivedFromPeriods: boolean
  1087. * }}
  1088. * @private
  1089. */
  1090. parsePeriods_(context, getBaseUris, mpd) {
  1091. const TXml = shaka.util.TXml;
  1092. let presentationDuration = context.mediaPresentationDuration;
  1093. if (!presentationDuration) {
  1094. presentationDuration = TXml.parseAttr(
  1095. mpd, 'mediaPresentationDuration', TXml.parseDuration);
  1096. this.manifestPatchContext_.mediaPresentationDuration =
  1097. presentationDuration;
  1098. }
  1099. const periods = [];
  1100. let prevEnd = 0;
  1101. const periodNodes = TXml.findChildren(mpd, 'Period');
  1102. for (let i = 0; i < periodNodes.length; i++) {
  1103. const elem = periodNodes[i];
  1104. const next = periodNodes[i + 1];
  1105. const start = /** @type {number} */ (
  1106. TXml.parseAttr(elem, 'start', TXml.parseDuration, prevEnd));
  1107. const periodId = elem.attributes['id'];
  1108. const givenDuration =
  1109. TXml.parseAttr(elem, 'duration', TXml.parseDuration);
  1110. let periodDuration = null;
  1111. if (next) {
  1112. // "The difference between the start time of a Period and the start time
  1113. // of the following Period is the duration of the media content
  1114. // represented by this Period."
  1115. const nextStart =
  1116. TXml.parseAttr(next, 'start', TXml.parseDuration);
  1117. if (nextStart != null) {
  1118. periodDuration = nextStart - start;
  1119. }
  1120. } else if (presentationDuration != null) {
  1121. // "The Period extends until the Period.start of the next Period, or
  1122. // until the end of the Media Presentation in the case of the last
  1123. // Period."
  1124. periodDuration = presentationDuration - start;
  1125. }
  1126. const threshold =
  1127. shaka.util.ManifestParserUtils.GAP_OVERLAP_TOLERANCE_SECONDS;
  1128. if (periodDuration && givenDuration &&
  1129. Math.abs(periodDuration - givenDuration) > threshold) {
  1130. shaka.log.warning('There is a gap/overlap between Periods', elem);
  1131. }
  1132. // Only use the @duration in the MPD if we can't calculate it. We should
  1133. // favor the @start of the following Period. This ensures that there
  1134. // aren't gaps between Periods.
  1135. if (periodDuration == null) {
  1136. periodDuration = givenDuration;
  1137. }
  1138. /**
  1139. * This is to improve robustness when the player observes manifest with
  1140. * past periods that are inconsistent to previous ones.
  1141. *
  1142. * This may happen when a CDN or proxy server switches its upstream from
  1143. * one encoder to another redundant encoder.
  1144. *
  1145. * Skip periods that match all of the following criteria:
  1146. * - Start time is earlier than latest period start time ever seen
  1147. * - Period ID is never seen in the previous manifest
  1148. * - Not the last period in the manifest
  1149. *
  1150. * Periods that meet the aforementioned criteria are considered invalid
  1151. * and should be safe to discard.
  1152. */
  1153. if (this.largestPeriodStartTime_ !== null &&
  1154. periodId !== null && start !== null &&
  1155. start < this.largestPeriodStartTime_ &&
  1156. !this.lastManifestUpdatePeriodIds_.includes(periodId) &&
  1157. i + 1 != periodNodes.length) {
  1158. shaka.log.debug(
  1159. `Skipping Period with ID ${periodId} as its start time is smaller` +
  1160. ' than the largest period start time that has been seen, and ID ' +
  1161. 'is unseen before');
  1162. continue;
  1163. }
  1164. // Save maximum period start time if it is the last period
  1165. if (start !== null &&
  1166. (this.largestPeriodStartTime_ === null ||
  1167. start > this.largestPeriodStartTime_)) {
  1168. this.largestPeriodStartTime_ = start;
  1169. }
  1170. // Parse child nodes.
  1171. const info = {
  1172. start: start,
  1173. duration: periodDuration,
  1174. node: elem,
  1175. isLastPeriod: periodDuration == null || !next,
  1176. };
  1177. const period = this.parsePeriod_(context, getBaseUris, info);
  1178. periods.push(period);
  1179. if (context.period.id && periodDuration) {
  1180. this.periodDurations_[context.period.id] = periodDuration;
  1181. }
  1182. if (periodDuration == null) {
  1183. if (next) {
  1184. // If the duration is still null and we aren't at the end, then we
  1185. // will skip any remaining periods.
  1186. shaka.log.warning(
  1187. 'Skipping Period', i + 1, 'and any subsequent Periods:', 'Period',
  1188. i + 1, 'does not have a valid start time.', next);
  1189. }
  1190. // The duration is unknown, so the end is unknown.
  1191. prevEnd = null;
  1192. break;
  1193. }
  1194. prevEnd = start + periodDuration;
  1195. } // end of period parsing loop
  1196. // Replace previous seen periods with the current one.
  1197. this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
  1198. if (presentationDuration != null) {
  1199. if (prevEnd != presentationDuration) {
  1200. shaka.log.warning(
  1201. '@mediaPresentationDuration does not match the total duration of ',
  1202. 'all Periods.');
  1203. // Assume @mediaPresentationDuration is correct.
  1204. }
  1205. return {
  1206. periods: periods,
  1207. duration: presentationDuration,
  1208. durationDerivedFromPeriods: false,
  1209. };
  1210. } else {
  1211. return {
  1212. periods: periods,
  1213. duration: prevEnd,
  1214. durationDerivedFromPeriods: true,
  1215. };
  1216. }
  1217. }
  1218. /**
  1219. * Parses a Period XML element. Unlike the other parse methods, this is not
  1220. * given the Node; it is given a PeriodInfo structure. Also, partial parsing
  1221. * was done before this was called so start and duration are valid.
  1222. *
  1223. * @param {shaka.dash.DashParser.Context} context
  1224. * @param {function():!Array.<string>} getBaseUris
  1225. * @param {shaka.dash.DashParser.PeriodInfo} periodInfo
  1226. * @return {shaka.extern.Period}
  1227. * @private
  1228. */
  1229. parsePeriod_(context, getBaseUris, periodInfo) {
  1230. const Functional = shaka.util.Functional;
  1231. const TXml = shaka.util.TXml;
  1232. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  1233. context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
  1234. context.periodInfo = periodInfo;
  1235. context.period.availabilityTimeOffset = context.availabilityTimeOffset;
  1236. // If the period doesn't have an ID, give it one based on its start time.
  1237. if (!context.period.id) {
  1238. shaka.log.info(
  1239. 'No Period ID given for Period with start time ' + periodInfo.start +
  1240. ', Assigning a default');
  1241. context.period.id = '__shaka_period_' + periodInfo.start;
  1242. }
  1243. const eventStreamNodes =
  1244. TXml.findChildren(periodInfo.node, 'EventStream');
  1245. const availabilityStart =
  1246. context.presentationTimeline.getSegmentAvailabilityStart();
  1247. for (const node of eventStreamNodes) {
  1248. this.parseEventStream_(
  1249. periodInfo.start, periodInfo.duration, node, availabilityStart);
  1250. }
  1251. const adaptationSetNodes =
  1252. TXml.findChildren(periodInfo.node, 'AdaptationSet');
  1253. const adaptationSets = adaptationSetNodes
  1254. .map((node) => this.parseAdaptationSet_(context, node))
  1255. .filter(Functional.isNotNull);
  1256. // For dynamic manifests, we use rep IDs internally, and they must be
  1257. // unique.
  1258. if (context.dynamic) {
  1259. const ids = [];
  1260. for (const set of adaptationSets) {
  1261. for (const id of set.representationIds) {
  1262. ids.push(id);
  1263. }
  1264. }
  1265. const uniqueIds = new Set(ids);
  1266. if (ids.length != uniqueIds.size) {
  1267. throw new shaka.util.Error(
  1268. shaka.util.Error.Severity.CRITICAL,
  1269. shaka.util.Error.Category.MANIFEST,
  1270. shaka.util.Error.Code.DASH_DUPLICATE_REPRESENTATION_ID);
  1271. }
  1272. }
  1273. const normalAdaptationSets = adaptationSets
  1274. .filter((as) => { return !as.trickModeFor; });
  1275. const trickModeAdaptationSets = adaptationSets
  1276. .filter((as) => { return as.trickModeFor; });
  1277. // Attach trick mode tracks to normal tracks.
  1278. for (const trickModeSet of trickModeAdaptationSets) {
  1279. const targetIds = trickModeSet.trickModeFor.split(' ');
  1280. for (const normalSet of normalAdaptationSets) {
  1281. if (targetIds.includes(normalSet.id)) {
  1282. for (const stream of normalSet.streams) {
  1283. // There may be multiple trick mode streams, but we do not
  1284. // currently support that. Just choose one.
  1285. // TODO: https://github.com/shaka-project/shaka-player/issues/1528
  1286. stream.trickModeVideo = trickModeSet.streams.find((trickStream) =>
  1287. shaka.util.MimeUtils.getNormalizedCodec(stream.codecs) ==
  1288. shaka.util.MimeUtils.getNormalizedCodec(trickStream.codecs));
  1289. }
  1290. }
  1291. }
  1292. }
  1293. const audioStreams = this.getStreamsFromSets_(
  1294. this.config_.disableAudio,
  1295. normalAdaptationSets,
  1296. ContentType.AUDIO);
  1297. const videoStreams = this.getStreamsFromSets_(
  1298. this.config_.disableVideo,
  1299. normalAdaptationSets,
  1300. ContentType.VIDEO);
  1301. const textStreams = this.getStreamsFromSets_(
  1302. this.config_.disableText,
  1303. normalAdaptationSets,
  1304. ContentType.TEXT);
  1305. const imageStreams = this.getStreamsFromSets_(
  1306. this.config_.disableThumbnails,
  1307. normalAdaptationSets,
  1308. ContentType.IMAGE);
  1309. if (videoStreams.length === 0 && audioStreams.length === 0) {
  1310. throw new shaka.util.Error(
  1311. shaka.util.Error.Severity.CRITICAL,
  1312. shaka.util.Error.Category.MANIFEST,
  1313. shaka.util.Error.Code.DASH_EMPTY_PERIOD,
  1314. );
  1315. }
  1316. return {
  1317. id: context.period.id,
  1318. audioStreams,
  1319. videoStreams,
  1320. textStreams,
  1321. imageStreams,
  1322. };
  1323. }
  1324. /**
  1325. * Gets the streams from the given sets or returns an empty array if disabled
  1326. * or no streams are found.
  1327. * @param {boolean} disabled
  1328. * @param {!Array.<!shaka.dash.DashParser.AdaptationInfo>} adaptationSets
  1329. * @param {string} contentType
  1330. @private
  1331. */
  1332. getStreamsFromSets_(disabled, adaptationSets, contentType) {
  1333. if (disabled || !adaptationSets.length) {
  1334. return [];
  1335. }
  1336. return adaptationSets.reduce((all, part) => {
  1337. if (part.contentType != contentType) {
  1338. return all;
  1339. }
  1340. all.push(...part.streams);
  1341. return all;
  1342. }, []);
  1343. }
  1344. /**
  1345. * Parses an AdaptationSet XML element.
  1346. *
  1347. * @param {shaka.dash.DashParser.Context} context
  1348. * @param {!shaka.extern.xml.Node} elem The AdaptationSet element.
  1349. * @return {?shaka.dash.DashParser.AdaptationInfo}
  1350. * @private
  1351. */
  1352. parseAdaptationSet_(context, elem) {
  1353. const TXml = shaka.util.TXml;
  1354. const Functional = shaka.util.Functional;
  1355. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  1356. const ContentType = ManifestParserUtils.ContentType;
  1357. const ContentProtection = shaka.dash.ContentProtection;
  1358. context.adaptationSet = this.createFrame_(elem, context.period, null);
  1359. let main = false;
  1360. const roleElements = TXml.findChildren(elem, 'Role');
  1361. const roleValues = roleElements.map((role) => {
  1362. return role.attributes['value'];
  1363. }).filter(Functional.isNotNull);
  1364. // Default kind for text streams is 'subtitle' if unspecified in the
  1365. // manifest.
  1366. let kind = undefined;
  1367. const isText = context.adaptationSet.contentType == ContentType.TEXT;
  1368. if (isText) {
  1369. kind = ManifestParserUtils.TextStreamKind.SUBTITLE;
  1370. }
  1371. for (const roleElement of roleElements) {
  1372. const scheme = roleElement.attributes['schemeIdUri'];
  1373. if (scheme == null || scheme == 'urn:mpeg:dash:role:2011') {
  1374. // These only apply for the given scheme, but allow them to be specified
  1375. // if there is no scheme specified.
  1376. // See: DASH section 5.8.5.5
  1377. const value = roleElement.attributes['value'];
  1378. switch (value) {
  1379. case 'main':
  1380. main = true;
  1381. break;
  1382. case 'caption':
  1383. case 'subtitle':
  1384. kind = value;
  1385. break;
  1386. }
  1387. }
  1388. }
  1389. // Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
  1390. let videoRange;
  1391. let colorGamut;
  1392. // Ref. https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf
  1393. // If signaled, a Supplemental or Essential Property descriptor
  1394. // shall be used, with the schemeIdUri set to
  1395. // urn:mpeg:mpegB:cicp:<Parameter> as defined in
  1396. // ISO/IEC 23001-8 [49] and <Parameter> one of the
  1397. // following: ColourPrimaries, TransferCharacteristics,
  1398. // or MatrixCoefficients.
  1399. const scheme = 'urn:mpeg:mpegB:cicp';
  1400. const transferCharacteristicsScheme = `${scheme}:TransferCharacteristics`;
  1401. const colourPrimariesScheme = `${scheme}:ColourPrimaries`;
  1402. const matrixCoefficientsScheme = `${scheme}:MatrixCoefficients`;
  1403. const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
  1404. switch (cicp) {
  1405. case 1:
  1406. case 6:
  1407. case 13:
  1408. case 14:
  1409. case 15:
  1410. return 'SDR';
  1411. case 16:
  1412. return 'PQ';
  1413. case 18:
  1414. return 'HLG';
  1415. }
  1416. return undefined;
  1417. };
  1418. const getColorGamutFromColourPrimariesCICP = (cicp) => {
  1419. switch (cicp) {
  1420. case 1:
  1421. case 5:
  1422. case 6:
  1423. case 7:
  1424. return 'srgb';
  1425. case 9:
  1426. return 'rec2020';
  1427. case 11:
  1428. case 12:
  1429. return 'p3';
  1430. }
  1431. return undefined;
  1432. };
  1433. const essentialProperties =
  1434. TXml.findChildren(elem, 'EssentialProperty');
  1435. // ID of real AdaptationSet if this is a trick mode set:
  1436. let trickModeFor = null;
  1437. let isFastSwitching = false;
  1438. let unrecognizedEssentialProperty = false;
  1439. for (const prop of essentialProperties) {
  1440. const schemeId = prop.attributes['schemeIdUri'];
  1441. if (schemeId == 'http://dashif.org/guidelines/trickmode') {
  1442. trickModeFor = prop.attributes['value'];
  1443. } else if (schemeId == transferCharacteristicsScheme) {
  1444. videoRange = getVideoRangeFromTransferCharacteristicCICP(
  1445. parseInt(prop.attributes['value'], 10),
  1446. );
  1447. } else if (schemeId == colourPrimariesScheme) {
  1448. colorGamut = getColorGamutFromColourPrimariesCICP(
  1449. parseInt(prop.attributes['value'], 10),
  1450. );
  1451. } else if (schemeId == matrixCoefficientsScheme) {
  1452. continue;
  1453. } else if (schemeId == 'urn:mpeg:dash:ssr:2023' &&
  1454. this.config_.dash.enableFastSwitching) {
  1455. isFastSwitching = true;
  1456. } else {
  1457. unrecognizedEssentialProperty = true;
  1458. }
  1459. }
  1460. let lastSegmentNumber = null;
  1461. const supplementalProperties =
  1462. TXml.findChildren(elem, 'SupplementalProperty');
  1463. for (const prop of supplementalProperties) {
  1464. const schemeId = prop.attributes['schemeIdUri'];
  1465. if (schemeId == 'http://dashif.org/guidelines/last-segment-number') {
  1466. lastSegmentNumber = parseInt(prop.attributes['value'], 10) - 1;
  1467. } else if (schemeId == transferCharacteristicsScheme) {
  1468. videoRange = getVideoRangeFromTransferCharacteristicCICP(
  1469. parseInt(prop.attributes['value'], 10),
  1470. );
  1471. } else if (schemeId == colourPrimariesScheme) {
  1472. colorGamut = getColorGamutFromColourPrimariesCICP(
  1473. parseInt(prop.attributes['value'], 10),
  1474. );
  1475. }
  1476. }
  1477. const accessibilities = TXml.findChildren(elem, 'Accessibility');
  1478. const LanguageUtils = shaka.util.LanguageUtils;
  1479. const closedCaptions = new Map();
  1480. /** @type {?shaka.media.ManifestParser.AccessibilityPurpose} */
  1481. let accessibilityPurpose;
  1482. for (const prop of accessibilities) {
  1483. const schemeId = prop.attributes['schemeIdUri'];
  1484. const value = prop.attributes['value'];
  1485. if (schemeId == 'urn:scte:dash:cc:cea-608:2015' ) {
  1486. let channelId = 1;
  1487. if (value != null) {
  1488. const channelAssignments = value.split(';');
  1489. for (const captionStr of channelAssignments) {
  1490. let channel;
  1491. let language;
  1492. // Some closed caption descriptions have channel number and
  1493. // language ("CC1=eng") others may only have language ("eng,spa").
  1494. if (!captionStr.includes('=')) {
  1495. // When the channel assignemnts are not explicitly provided and
  1496. // there are only 2 values provided, it is highly likely that the
  1497. // assignments are CC1 and CC3 (most commonly used CC streams).
  1498. // Otherwise, cycle through all channels arbitrarily (CC1 - CC4)
  1499. // in order of provided langs.
  1500. channel = `CC${channelId}`;
  1501. if (channelAssignments.length == 2) {
  1502. channelId += 2;
  1503. } else {
  1504. channelId ++;
  1505. }
  1506. language = captionStr;
  1507. } else {
  1508. const channelAndLanguage = captionStr.split('=');
  1509. // The channel info can be '1' or 'CC1'.
  1510. // If the channel info only has channel number(like '1'), add 'CC'
  1511. // as prefix so that it can be a full channel id (like 'CC1').
  1512. channel = channelAndLanguage[0].startsWith('CC') ?
  1513. channelAndLanguage[0] : `CC${channelAndLanguage[0]}`;
  1514. // 3 letters (ISO 639-2). In b/187442669, we saw a blank string
  1515. // (CC2=;CC3=), so default to "und" (the code for "undetermined").
  1516. language = channelAndLanguage[1] || 'und';
  1517. }
  1518. closedCaptions.set(channel, LanguageUtils.normalize(language));
  1519. }
  1520. } else {
  1521. // If channel and language information has not been provided, assign
  1522. // 'CC1' as channel id and 'und' as language info.
  1523. closedCaptions.set('CC1', 'und');
  1524. }
  1525. } else if (schemeId == 'urn:scte:dash:cc:cea-708:2015') {
  1526. let serviceNumber = 1;
  1527. if (value != null) {
  1528. for (const captionStr of value.split(';')) {
  1529. let service;
  1530. let language;
  1531. // Similar to CEA-608, it is possible that service # assignments
  1532. // are not explicitly provided e.g. "eng;deu;swe" In this case,
  1533. // we just cycle through the services for each language one by one.
  1534. if (!captionStr.includes('=')) {
  1535. service = `svc${serviceNumber}`;
  1536. serviceNumber ++;
  1537. language = captionStr;
  1538. } else {
  1539. // Otherwise, CEA-708 caption values take the form "
  1540. // 1=lang:eng;2=lang:deu" i.e. serviceNumber=lang:threelettercode.
  1541. const serviceAndLanguage = captionStr.split('=');
  1542. service = `svc${serviceAndLanguage[0]}`;
  1543. // The language info can be different formats, lang:eng',
  1544. // or 'lang:eng,war:1,er:1'. Extract the language info.
  1545. language = serviceAndLanguage[1].split(',')[0].split(':').pop();
  1546. }
  1547. closedCaptions.set(service, LanguageUtils.normalize(language));
  1548. }
  1549. } else {
  1550. // If service and language information has not been provided, assign
  1551. // 'svc1' as service number and 'und' as language info.
  1552. closedCaptions.set('svc1', 'und');
  1553. }
  1554. } else if (schemeId == 'urn:mpeg:dash:role:2011') {
  1555. // See DASH IOP 3.9.2 Table 4.
  1556. if (value != null) {
  1557. roleValues.push(value);
  1558. if (value == 'captions') {
  1559. kind = ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
  1560. }
  1561. }
  1562. } else if (schemeId == 'urn:tva:metadata:cs:AudioPurposeCS:2007') {
  1563. // See DASH DVB Document A168 Rev.6 Table 5.
  1564. if (value == '1') {
  1565. accessibilityPurpose =
  1566. shaka.media.ManifestParser.AccessibilityPurpose.VISUALLY_IMPAIRED;
  1567. } else if (value == '2') {
  1568. accessibilityPurpose =
  1569. shaka.media.ManifestParser.AccessibilityPurpose.HARD_OF_HEARING;
  1570. }
  1571. }
  1572. }
  1573. // According to DASH spec (2014) section 5.8.4.8, "the successful processing
  1574. // of the descriptor is essential to properly use the information in the
  1575. // parent element". According to DASH IOP v3.3, section 3.3.4, "if the
  1576. // scheme or the value" for EssentialProperty is not recognized, "the DASH
  1577. // client shall ignore the parent element."
  1578. if (unrecognizedEssentialProperty) {
  1579. // Stop parsing this AdaptationSet and let the caller filter out the
  1580. // nulls.
  1581. return null;
  1582. }
  1583. const contentProtectionElems =
  1584. TXml.findChildren(elem, 'ContentProtection');
  1585. const contentProtection = ContentProtection.parseFromAdaptationSet(
  1586. contentProtectionElems,
  1587. this.config_.dash.ignoreDrmInfo,
  1588. this.config_.dash.keySystemsByURI);
  1589. const language = shaka.util.LanguageUtils.normalize(
  1590. context.adaptationSet.language || 'und');
  1591. // This attribute is currently non-standard, but it is supported by Kaltura.
  1592. let label = elem.attributes['label'];
  1593. // See DASH IOP 4.3 here https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf (page 35)
  1594. const labelElements = TXml.findChildren(elem, 'Label');
  1595. if (labelElements && labelElements.length) {
  1596. // NOTE: Right now only one label field is supported.
  1597. const firstLabelElement = labelElements[0];
  1598. const textContent = shaka.util.TXml.getTextContents(firstLabelElement);
  1599. if (textContent) {
  1600. label = textContent;
  1601. }
  1602. }
  1603. // Parse Representations into Streams.
  1604. const representations = TXml.findChildren(elem, 'Representation');
  1605. const streams = representations.map((representation) => {
  1606. const parsedRepresentation = this.parseRepresentation_(context,
  1607. contentProtection, kind, language, label, main, roleValues,
  1608. closedCaptions, representation, accessibilityPurpose,
  1609. lastSegmentNumber);
  1610. if (parsedRepresentation) {
  1611. parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
  1612. parsedRepresentation.colorGamut =
  1613. parsedRepresentation.colorGamut || colorGamut;
  1614. parsedRepresentation.fastSwitching = isFastSwitching;
  1615. }
  1616. return parsedRepresentation;
  1617. }).filter((s) => !!s);
  1618. if (streams.length == 0) {
  1619. const isImage = context.adaptationSet.contentType == ContentType.IMAGE;
  1620. // Ignore empty AdaptationSets if ignoreEmptyAdaptationSet is true
  1621. // or they are for text/image content.
  1622. if (this.config_.dash.ignoreEmptyAdaptationSet || isText || isImage) {
  1623. return null;
  1624. }
  1625. throw new shaka.util.Error(
  1626. shaka.util.Error.Severity.CRITICAL,
  1627. shaka.util.Error.Category.MANIFEST,
  1628. shaka.util.Error.Code.DASH_EMPTY_ADAPTATION_SET);
  1629. }
  1630. // If AdaptationSet's type is unknown or is ambiguously "application",
  1631. // guess based on the information in the first stream. If the attributes
  1632. // mimeType and codecs are split across levels, they will both be inherited
  1633. // down to the stream level by this point, so the stream will have all the
  1634. // necessary information.
  1635. if (!context.adaptationSet.contentType ||
  1636. context.adaptationSet.contentType == ContentType.APPLICATION) {
  1637. const mimeType = streams[0].mimeType;
  1638. const codecs = streams[0].codecs;
  1639. context.adaptationSet.contentType =
  1640. shaka.dash.DashParser.guessContentType_(mimeType, codecs);
  1641. for (const stream of streams) {
  1642. stream.type = context.adaptationSet.contentType;
  1643. }
  1644. }
  1645. const adaptationId = context.adaptationSet.id ||
  1646. ('__fake__' + this.globalId_++);
  1647. for (const stream of streams) {
  1648. // Some DRM license providers require that we have a default
  1649. // key ID from the manifest in the wrapped license request.
  1650. // Thus, it should be put in drmInfo to be accessible to request filters.
  1651. for (const drmInfo of contentProtection.drmInfos) {
  1652. drmInfo.keyIds = drmInfo.keyIds && stream.keyIds ?
  1653. new Set([...drmInfo.keyIds, ...stream.keyIds]) :
  1654. drmInfo.keyIds || stream.keyIds;
  1655. }
  1656. if (this.config_.dash.enableAudioGroups) {
  1657. stream.groupId = adaptationId;
  1658. }
  1659. }
  1660. const repIds = representations
  1661. .map((node) => { return node.attributes['id']; })
  1662. .filter(shaka.util.Functional.isNotNull);
  1663. return {
  1664. id: adaptationId,
  1665. contentType: context.adaptationSet.contentType,
  1666. language: language,
  1667. main: main,
  1668. streams: streams,
  1669. drmInfos: contentProtection.drmInfos,
  1670. trickModeFor: trickModeFor,
  1671. representationIds: repIds,
  1672. };
  1673. }
  1674. /**
  1675. * Parses a Representation XML element.
  1676. *
  1677. * @param {shaka.dash.DashParser.Context} context
  1678. * @param {shaka.dash.ContentProtection.Context} contentProtection
  1679. * @param {(string|undefined)} kind
  1680. * @param {string} language
  1681. * @param {string} label
  1682. * @param {boolean} isPrimary
  1683. * @param {!Array.<string>} roles
  1684. * @param {Map.<string, string>} closedCaptions
  1685. * @param {!shaka.extern.xml.Node} node
  1686. * @param {?shaka.media.ManifestParser.AccessibilityPurpose}
  1687. * accessibilityPurpose
  1688. * @param {?number} lastSegmentNumber
  1689. *
  1690. * @return {?shaka.extern.Stream} The Stream, or null when there is a
  1691. * non-critical parsing error.
  1692. * @private
  1693. */
  1694. parseRepresentation_(context, contentProtection, kind, language, label,
  1695. isPrimary, roles, closedCaptions, node, accessibilityPurpose,
  1696. lastSegmentNumber) {
  1697. const TXml = shaka.util.TXml;
  1698. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  1699. context.representation =
  1700. this.createFrame_(node, context.adaptationSet, null);
  1701. const representationId = context.representation.id;
  1702. this.minTotalAvailabilityTimeOffset_ =
  1703. Math.min(this.minTotalAvailabilityTimeOffset_,
  1704. context.representation.availabilityTimeOffset);
  1705. if (!this.verifyRepresentation_(context.representation)) {
  1706. shaka.log.warning('Skipping Representation', context.representation);
  1707. return null;
  1708. }
  1709. const periodStart = context.periodInfo.start;
  1710. // NOTE: bandwidth is a mandatory attribute according to the spec, and zero
  1711. // does not make sense in the DASH spec's bandwidth formulas.
  1712. // In some content, however, the attribute is missing or zero.
  1713. // To avoid NaN at the variant level on broken content, fall back to zero.
  1714. // https://github.com/shaka-project/shaka-player/issues/938#issuecomment-317278180
  1715. context.bandwidth =
  1716. TXml.parseAttr(node, 'bandwidth', TXml.parsePositiveInt) || 0;
  1717. /** @type {?shaka.dash.DashParser.StreamInfo} */
  1718. let streamInfo;
  1719. const contentType = context.representation.contentType;
  1720. const isText = contentType == ContentType.TEXT ||
  1721. contentType == ContentType.APPLICATION;
  1722. const isImage = contentType == ContentType.IMAGE;
  1723. try {
  1724. /** @type {shaka.extern.aesKey|undefined} */
  1725. let aesKey = undefined;
  1726. if (contentProtection.aes128Info) {
  1727. const getBaseUris = context.representation.getBaseUris;
  1728. const uris = shaka.util.ManifestParserUtils.resolveUris(
  1729. getBaseUris(), [contentProtection.aes128Info.keyUri]);
  1730. const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
  1731. const request = shaka.net.NetworkingEngine.makeRequest(
  1732. uris, this.config_.retryParameters);
  1733. aesKey = {
  1734. bitsKey: 128,
  1735. blockCipherMode: 'CBC',
  1736. iv: contentProtection.aes128Info.iv,
  1737. firstMediaSequenceNumber: 0,
  1738. };
  1739. // Don't download the key object until the segment is parsed, to
  1740. // avoid a startup delay for long manifests with lots of keys.
  1741. aesKey.fetchKey = async () => {
  1742. const keyResponse =
  1743. await this.makeNetworkRequest_(request, requestType);
  1744. // keyResponse.status is undefined when URI is
  1745. // "data:text/plain;base64,"
  1746. if (!keyResponse.data || keyResponse.data.byteLength != 16) {
  1747. throw new shaka.util.Error(
  1748. shaka.util.Error.Severity.CRITICAL,
  1749. shaka.util.Error.Category.MANIFEST,
  1750. shaka.util.Error.Code.AES_128_INVALID_KEY_LENGTH);
  1751. }
  1752. const algorithm = {
  1753. name: 'AES-CBC',
  1754. };
  1755. aesKey.cryptoKey = await window.crypto.subtle.importKey(
  1756. 'raw', keyResponse.data, algorithm, true, ['decrypt']);
  1757. aesKey.fetchKey = undefined; // No longer needed.
  1758. };
  1759. }
  1760. context.representation.aesKey = aesKey;
  1761. const requestSegment = (uris, startByte, endByte, isInit) => {
  1762. return this.requestSegment_(uris, startByte, endByte, isInit);
  1763. };
  1764. if (context.representation.segmentBase) {
  1765. streamInfo = shaka.dash.SegmentBase.createStreamInfo(
  1766. context, requestSegment, aesKey);
  1767. } else if (context.representation.segmentList) {
  1768. streamInfo = shaka.dash.SegmentList.createStreamInfo(
  1769. context, this.streamMap_, aesKey);
  1770. } else if (context.representation.segmentTemplate) {
  1771. const hasManifest = !!this.manifest_;
  1772. streamInfo = shaka.dash.SegmentTemplate.createStreamInfo(
  1773. context, requestSegment, this.streamMap_, hasManifest,
  1774. this.config_.dash.initialSegmentLimit, this.periodDurations_,
  1775. aesKey, lastSegmentNumber, /* isPatchUpdate= */ false);
  1776. } else {
  1777. goog.asserts.assert(isText,
  1778. 'Must have Segment* with non-text streams.');
  1779. const duration = context.periodInfo.duration || 0;
  1780. const getBaseUris = context.representation.getBaseUris;
  1781. streamInfo = {
  1782. generateSegmentIndex: () => {
  1783. const segmentIndex = shaka.media.SegmentIndex.forSingleSegment(
  1784. periodStart, duration, getBaseUris());
  1785. segmentIndex.forEachTopLevelReference((ref) => {
  1786. ref.mimeType = context.representation.mimeType;
  1787. ref.codecs = context.representation.codecs;
  1788. });
  1789. return Promise.resolve(segmentIndex);
  1790. },
  1791. };
  1792. }
  1793. } catch (error) {
  1794. if ((isText || isImage) &&
  1795. error.code == shaka.util.Error.Code.DASH_NO_SEGMENT_INFO) {
  1796. // We will ignore any DASH_NO_SEGMENT_INFO errors for text/image
  1797. // streams.
  1798. return null;
  1799. }
  1800. // For anything else, re-throw.
  1801. throw error;
  1802. }
  1803. const contentProtectionElems =
  1804. TXml.findChildren(node, 'ContentProtection');
  1805. const keyId = shaka.dash.ContentProtection.parseFromRepresentation(
  1806. contentProtectionElems, contentProtection,
  1807. this.config_.dash.ignoreDrmInfo,
  1808. this.config_.dash.keySystemsByURI);
  1809. const keyIds = new Set(keyId ? [keyId] : []);
  1810. // Detect the presence of E-AC3 JOC audio content, using DD+JOC signaling.
  1811. // See: ETSI TS 103 420 V1.2.1 (2018-10)
  1812. const supplementalPropertyElems =
  1813. TXml.findChildren(node, 'SupplementalProperty');
  1814. const hasJoc = supplementalPropertyElems.some((element) => {
  1815. const expectedUri = 'tag:dolby.com,2018:dash:EC3_ExtensionType:2018';
  1816. const expectedValue = 'JOC';
  1817. return element.attributes['schemeIdUri'] == expectedUri &&
  1818. element.attributes['value'] == expectedValue;
  1819. });
  1820. let spatialAudio = false;
  1821. if (hasJoc) {
  1822. spatialAudio = true;
  1823. }
  1824. let forced = false;
  1825. if (isText) {
  1826. // See: https://github.com/shaka-project/shaka-player/issues/2122 and
  1827. // https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/165
  1828. forced = roles.includes('forced_subtitle') ||
  1829. roles.includes('forced-subtitle');
  1830. }
  1831. let tilesLayout;
  1832. if (isImage) {
  1833. const essentialPropertyElems =
  1834. TXml.findChildren(node, 'EssentialProperty');
  1835. const thumbnailTileElem = essentialPropertyElems.find((element) => {
  1836. const expectedUris = [
  1837. 'http://dashif.org/thumbnail_tile',
  1838. 'http://dashif.org/guidelines/thumbnail_tile',
  1839. ];
  1840. return expectedUris.includes(element.attributes['schemeIdUri']);
  1841. });
  1842. if (thumbnailTileElem) {
  1843. tilesLayout = thumbnailTileElem.attributes['value'];
  1844. }
  1845. // Filter image adaptation sets that has no tilesLayout.
  1846. if (!tilesLayout) {
  1847. return null;
  1848. }
  1849. }
  1850. let hdr;
  1851. const profiles = context.profiles;
  1852. const codecs = context.representation.codecs;
  1853. const hevcHDR = 'http://dashif.org/guidelines/dash-if-uhd#hevc-hdr-pq10';
  1854. if (profiles.includes(hevcHDR) && (codecs.includes('hvc1.2.4.L153.B0') ||
  1855. codecs.includes('hev1.2.4.L153.B0'))) {
  1856. hdr = 'PQ';
  1857. }
  1858. const contextId = context.representation.id ?
  1859. context.period.id + ',' + context.representation.id : '';
  1860. if (this.patchLocationNodes_.length && representationId) {
  1861. this.contextCache_.set(`${context.period.id},${representationId}`,
  1862. this.cloneContext_(context));
  1863. }
  1864. /** @type {shaka.extern.Stream} */
  1865. let stream;
  1866. if (contextId && this.streamMap_[contextId]) {
  1867. stream = this.streamMap_[contextId];
  1868. } else {
  1869. stream = {
  1870. id: this.globalId_++,
  1871. originalId: context.representation.id,
  1872. groupId: null,
  1873. createSegmentIndex: () => Promise.resolve(),
  1874. closeSegmentIndex: () => {
  1875. if (stream.segmentIndex) {
  1876. stream.segmentIndex.release();
  1877. stream.segmentIndex = null;
  1878. }
  1879. },
  1880. segmentIndex: null,
  1881. mimeType: context.representation.mimeType,
  1882. codecs,
  1883. frameRate: context.representation.frameRate,
  1884. pixelAspectRatio: context.representation.pixelAspectRatio,
  1885. bandwidth: context.bandwidth,
  1886. width: context.representation.width,
  1887. height: context.representation.height,
  1888. kind,
  1889. encrypted: contentProtection.drmInfos.length > 0,
  1890. drmInfos: contentProtection.drmInfos,
  1891. keyIds,
  1892. language,
  1893. originalLanguage: context.adaptationSet.language,
  1894. label,
  1895. type: context.adaptationSet.contentType,
  1896. primary: isPrimary,
  1897. trickModeVideo: null,
  1898. emsgSchemeIdUris:
  1899. context.representation.emsgSchemeIdUris,
  1900. roles,
  1901. forced,
  1902. channelsCount: context.representation.numChannels,
  1903. audioSamplingRate: context.representation.audioSamplingRate,
  1904. spatialAudio,
  1905. closedCaptions,
  1906. hdr,
  1907. colorGamut: undefined,
  1908. videoLayout: undefined,
  1909. tilesLayout,
  1910. accessibilityPurpose,
  1911. external: false,
  1912. fastSwitching: false,
  1913. fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
  1914. context.representation.mimeType, context.representation.codecs)]),
  1915. };
  1916. }
  1917. stream.createSegmentIndex = async () => {
  1918. if (!stream.segmentIndex) {
  1919. stream.segmentIndex = await streamInfo.generateSegmentIndex();
  1920. }
  1921. };
  1922. if (contextId && context.dynamic && !this.streamMap_[contextId]) {
  1923. this.streamMap_[contextId] = stream;
  1924. }
  1925. return stream;
  1926. }
  1927. /**
  1928. * Clone context and remove xml document references.
  1929. *
  1930. * @param {!shaka.dash.DashParser.Context} context
  1931. * @return {!shaka.dash.DashParser.Context}
  1932. * @private
  1933. */
  1934. cloneContext_(context) {
  1935. const contextClone = /** @type {!shaka.dash.DashParser.Context} */({});
  1936. for (const k of Object.keys(context)) {
  1937. if (['period', 'adaptationSet', 'representation'].includes(k)) {
  1938. /** @type {shaka.dash.DashParser.InheritanceFrame} */
  1939. const frameRef = context[k];
  1940. contextClone[k] = {
  1941. segmentBase: null,
  1942. segmentList: null,
  1943. segmentTemplate: frameRef.segmentTemplate,
  1944. getBaseUris: frameRef.getBaseUris,
  1945. width: frameRef.width,
  1946. height: frameRef.height,
  1947. contentType: frameRef.contentType,
  1948. mimeType: frameRef.mimeType,
  1949. language: frameRef.language,
  1950. codecs: frameRef.codecs,
  1951. frameRate: frameRef.frameRate,
  1952. pixelAspectRatio: frameRef.pixelAspectRatio,
  1953. emsgSchemeIdUris: frameRef.emsgSchemeIdUris,
  1954. id: frameRef.id,
  1955. numChannels: frameRef.numChannels,
  1956. audioSamplingRate: frameRef.audioSamplingRate,
  1957. availabilityTimeOffset: frameRef.availabilityTimeOffset,
  1958. initialization: frameRef.initialization,
  1959. };
  1960. } else if (k == 'periodInfo') {
  1961. /** @type {shaka.dash.DashParser.PeriodInfo} */
  1962. const frameRef = context[k];
  1963. contextClone[k] = {
  1964. start: frameRef.start,
  1965. duration: frameRef.duration,
  1966. node: null,
  1967. isLastPeriod: frameRef.isLastPeriod,
  1968. };
  1969. } else {
  1970. contextClone[k] = context[k];
  1971. }
  1972. }
  1973. return contextClone;
  1974. }
  1975. /**
  1976. * Called when the update timer ticks.
  1977. *
  1978. * @return {!Promise}
  1979. * @private
  1980. */
  1981. async onUpdate_() {
  1982. goog.asserts.assert(this.updatePeriod_ >= 0,
  1983. 'There should be an update period');
  1984. shaka.log.info('Updating manifest...');
  1985. // Default the update delay to 0 seconds so that if there is an error we can
  1986. // try again right away.
  1987. let updateDelay = 0;
  1988. try {
  1989. updateDelay = await this.requestManifest_();
  1990. } catch (error) {
  1991. goog.asserts.assert(error instanceof shaka.util.Error,
  1992. 'Should only receive a Shaka error');
  1993. // Try updating again, but ensure we haven't been destroyed.
  1994. if (this.playerInterface_) {
  1995. if (this.config_.raiseFatalErrorOnManifestUpdateRequestFailure) {
  1996. this.playerInterface_.onError(error);
  1997. return;
  1998. }
  1999. // We will retry updating, so override the severity of the error.
  2000. error.severity = shaka.util.Error.Severity.RECOVERABLE;
  2001. this.playerInterface_.onError(error);
  2002. }
  2003. }
  2004. // Detect a call to stop()
  2005. if (!this.playerInterface_) {
  2006. return;
  2007. }
  2008. this.playerInterface_.onManifestUpdated();
  2009. this.setUpdateTimer_(updateDelay);
  2010. }
  2011. /**
  2012. * Update now the manifest
  2013. *
  2014. * @private
  2015. */
  2016. updateNow_() {
  2017. this.updateTimer_.tickNow();
  2018. }
  2019. /**
  2020. * Sets the update timer. Does nothing if the manifest does not specify an
  2021. * update period.
  2022. *
  2023. * @param {number} offset An offset, in seconds, to apply to the manifest's
  2024. * update period.
  2025. * @private
  2026. */
  2027. setUpdateTimer_(offset) {
  2028. // NOTE: An updatePeriod_ of -1 means the attribute was missing.
  2029. // An attribute which is present and set to 0 should still result in
  2030. // periodic updates. For more, see:
  2031. // https://github.com/Dash-Industry-Forum/Guidelines-TimingModel/issues/48
  2032. if (this.updatePeriod_ < 0) {
  2033. return;
  2034. }
  2035. let updateTime = this.updatePeriod_;
  2036. if (this.config_.dash.updatePeriod >= 0) {
  2037. updateTime = this.config_.dash.updatePeriod;
  2038. }
  2039. const finalDelay = Math.max(
  2040. updateTime - offset,
  2041. this.averageUpdateDuration_.getEstimate());
  2042. // We do not run the timer as repeating because part of update is async and
  2043. // we need schedule the update after it finished.
  2044. this.updateTimer_.tickAfter(/* seconds= */ finalDelay);
  2045. }
  2046. /**
  2047. * Creates a new inheritance frame for the given element.
  2048. *
  2049. * @param {!shaka.extern.xml.Node} elem
  2050. * @param {?shaka.dash.DashParser.InheritanceFrame} parent
  2051. * @param {?function():!Array.<string>} getBaseUris
  2052. * @return {shaka.dash.DashParser.InheritanceFrame}
  2053. * @private
  2054. */
  2055. createFrame_(elem, parent, getBaseUris) {
  2056. goog.asserts.assert(parent || getBaseUris,
  2057. 'Must provide either parent or getBaseUris');
  2058. const SCTE214 = shaka.dash.DashParser.SCTE214_;
  2059. const SegmentUtils = shaka.media.SegmentUtils;
  2060. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  2061. const TXml = shaka.util.TXml;
  2062. parent = parent || /** @type {shaka.dash.DashParser.InheritanceFrame} */ ({
  2063. contentType: '',
  2064. mimeType: '',
  2065. codecs: '',
  2066. emsgSchemeIdUris: [],
  2067. frameRate: undefined,
  2068. pixelAspectRatio: undefined,
  2069. numChannels: null,
  2070. audioSamplingRate: null,
  2071. availabilityTimeOffset: 0,
  2072. segmentSequenceCadence: 0,
  2073. });
  2074. getBaseUris = getBaseUris || parent.getBaseUris;
  2075. const parseNumber = TXml.parseNonNegativeInt;
  2076. const evalDivision = TXml.evalDivision;
  2077. const id = elem.attributes['id'];
  2078. const uriObjs = TXml.findChildren(elem, 'BaseURL');
  2079. let calculatedBaseUris;
  2080. let someLocationValid = false;
  2081. if (this.contentSteeringManager_) {
  2082. for (const uriObj of uriObjs) {
  2083. const serviceLocation = uriObj.attributes['serviceLocation'];
  2084. const uri = TXml.getContents(uriObj);
  2085. if (serviceLocation && uri) {
  2086. this.contentSteeringManager_.addLocation(
  2087. id, serviceLocation, uri);
  2088. someLocationValid = true;
  2089. }
  2090. }
  2091. }
  2092. if (!someLocationValid || !this.contentSteeringManager_) {
  2093. calculatedBaseUris = uriObjs.map(TXml.getContents);
  2094. }
  2095. const getFrameUris = () => {
  2096. if (!uriObjs.length) {
  2097. return [];
  2098. }
  2099. if (this.contentSteeringManager_ && someLocationValid) {
  2100. return this.contentSteeringManager_.getLocations(id);
  2101. }
  2102. if (calculatedBaseUris) {
  2103. return calculatedBaseUris;
  2104. }
  2105. return [];
  2106. };
  2107. let contentType = elem.attributes['contentType'] || parent.contentType;
  2108. const mimeType = elem.attributes['mimeType'] || parent.mimeType;
  2109. const allCodecs = [
  2110. elem.attributes['codecs'] || parent.codecs,
  2111. ];
  2112. const supplementalCodecs =
  2113. TXml.getAttributeNS(elem, SCTE214, 'supplementalCodecs');
  2114. if (supplementalCodecs) {
  2115. allCodecs.push(supplementalCodecs);
  2116. }
  2117. const codecs = SegmentUtils.codecsFiltering(allCodecs).join(',');
  2118. const frameRate =
  2119. TXml.parseAttr(elem, 'frameRate', evalDivision) || parent.frameRate;
  2120. const pixelAspectRatio =
  2121. elem.attributes['sar'] || parent.pixelAspectRatio;
  2122. const emsgSchemeIdUris = this.emsgSchemeIdUris_(
  2123. TXml.findChildren(elem, 'InbandEventStream'),
  2124. parent.emsgSchemeIdUris);
  2125. const audioChannelConfigs =
  2126. TXml.findChildren(elem, 'AudioChannelConfiguration');
  2127. const numChannels =
  2128. this.parseAudioChannels_(audioChannelConfigs) || parent.numChannels;
  2129. const audioSamplingRate =
  2130. TXml.parseAttr(elem, 'audioSamplingRate', parseNumber) ||
  2131. parent.audioSamplingRate;
  2132. if (!contentType) {
  2133. contentType = shaka.dash.DashParser.guessContentType_(mimeType, codecs);
  2134. }
  2135. const segmentBase = TXml.findChild(elem, 'SegmentBase');
  2136. const segmentTemplate = TXml.findChild(elem, 'SegmentTemplate');
  2137. // The availabilityTimeOffset is the sum of all @availabilityTimeOffset
  2138. // values that apply to the adaptation set, via BaseURL, SegmentBase,
  2139. // or SegmentTemplate elements.
  2140. const segmentBaseAto = segmentBase ?
  2141. (TXml.parseAttr(segmentBase, 'availabilityTimeOffset',
  2142. TXml.parseFloat) || 0) : 0;
  2143. const segmentTemplateAto = segmentTemplate ?
  2144. (TXml.parseAttr(segmentTemplate, 'availabilityTimeOffset',
  2145. TXml.parseFloat) || 0) : 0;
  2146. const baseUriAto = uriObjs && uriObjs.length ?
  2147. (TXml.parseAttr(uriObjs[0], 'availabilityTimeOffset',
  2148. TXml.parseFloat) || 0) : 0;
  2149. const availabilityTimeOffset = parent.availabilityTimeOffset + baseUriAto +
  2150. segmentBaseAto + segmentTemplateAto;
  2151. let segmentSequenceCadence = null;
  2152. const segmentSequenceProperties =
  2153. TXml.findChild(elem, 'SegmentSequenceProperties');
  2154. if (segmentSequenceProperties) {
  2155. const sap = TXml.findChild(segmentSequenceProperties, 'SAP');
  2156. if (sap) {
  2157. segmentSequenceCadence = TXml.parseAttr(sap, 'cadence',
  2158. TXml.parseInt);
  2159. }
  2160. }
  2161. return {
  2162. getBaseUris:
  2163. () => ManifestParserUtils.resolveUris(getBaseUris(), getFrameUris()),
  2164. segmentBase: segmentBase || parent.segmentBase,
  2165. segmentList:
  2166. TXml.findChild(elem, 'SegmentList') || parent.segmentList,
  2167. segmentTemplate: segmentTemplate || parent.segmentTemplate,
  2168. width: TXml.parseAttr(elem, 'width', parseNumber) || parent.width,
  2169. height: TXml.parseAttr(elem, 'height', parseNumber) || parent.height,
  2170. contentType: contentType,
  2171. mimeType: mimeType,
  2172. codecs: codecs,
  2173. frameRate: frameRate,
  2174. pixelAspectRatio: pixelAspectRatio,
  2175. emsgSchemeIdUris: emsgSchemeIdUris,
  2176. id: id,
  2177. language: elem.attributes['lang'],
  2178. numChannels: numChannels,
  2179. audioSamplingRate: audioSamplingRate,
  2180. availabilityTimeOffset: availabilityTimeOffset,
  2181. initialization: null,
  2182. segmentSequenceCadence:
  2183. segmentSequenceCadence || parent.segmentSequenceCadence,
  2184. };
  2185. }
  2186. /**
  2187. * Returns a new array of InbandEventStream schemeIdUri containing the union
  2188. * of the ones parsed from inBandEventStreams and the ones provided in
  2189. * emsgSchemeIdUris.
  2190. *
  2191. * @param {!Array.<!shaka.extern.xml.Node>} inBandEventStreams
  2192. * Array of InbandEventStream
  2193. * elements to parse and add to the returned array.
  2194. * @param {!Array.<string>} emsgSchemeIdUris Array of parsed
  2195. * InbandEventStream schemeIdUri attributes to add to the returned array.
  2196. * @return {!Array.<string>} schemeIdUris Array of parsed
  2197. * InbandEventStream schemeIdUri attributes.
  2198. * @private
  2199. */
  2200. emsgSchemeIdUris_(inBandEventStreams, emsgSchemeIdUris) {
  2201. const schemeIdUris = emsgSchemeIdUris.slice();
  2202. for (const event of inBandEventStreams) {
  2203. const schemeIdUri = event.attributes['schemeIdUri'];
  2204. if (!schemeIdUris.includes(schemeIdUri)) {
  2205. schemeIdUris.push(schemeIdUri);
  2206. }
  2207. }
  2208. return schemeIdUris;
  2209. }
  2210. /**
  2211. * @param {!Array.<!shaka.extern.xml.Node>} audioChannelConfigs An array of
  2212. * AudioChannelConfiguration elements.
  2213. * @return {?number} The number of audio channels, or null if unknown.
  2214. * @private
  2215. */
  2216. parseAudioChannels_(audioChannelConfigs) {
  2217. for (const elem of audioChannelConfigs) {
  2218. const scheme = elem.attributes['schemeIdUri'];
  2219. if (!scheme) {
  2220. continue;
  2221. }
  2222. const value = elem.attributes['value'];
  2223. if (!value) {
  2224. continue;
  2225. }
  2226. switch (scheme) {
  2227. case 'urn:mpeg:dash:outputChannelPositionList:2012':
  2228. // A space-separated list of speaker positions, so the number of
  2229. // channels is the length of this list.
  2230. return value.trim().split(/ +/).length;
  2231. case 'urn:mpeg:dash:23003:3:audio_channel_configuration:2011':
  2232. case 'urn:dts:dash:audio_channel_configuration:2012': {
  2233. // As far as we can tell, this is a number of channels.
  2234. const intValue = parseInt(value, 10);
  2235. if (!intValue) { // 0 or NaN
  2236. shaka.log.warning('Channel parsing failure! ' +
  2237. 'Ignoring scheme and value', scheme, value);
  2238. continue;
  2239. }
  2240. return intValue;
  2241. }
  2242. case 'tag:dolby.com,2014:dash:audio_channel_configuration:2011':
  2243. case 'urn:dolby:dash:audio_channel_configuration:2011': {
  2244. // A hex-encoded 16-bit integer, in which each bit represents a
  2245. // channel.
  2246. let hexValue = parseInt(value, 16);
  2247. if (!hexValue) { // 0 or NaN
  2248. shaka.log.warning('Channel parsing failure! ' +
  2249. 'Ignoring scheme and value', scheme, value);
  2250. continue;
  2251. }
  2252. // Count the 1-bits in hexValue.
  2253. let numBits = 0;
  2254. while (hexValue) {
  2255. if (hexValue & 1) {
  2256. ++numBits;
  2257. }
  2258. hexValue >>= 1;
  2259. }
  2260. return numBits;
  2261. }
  2262. // Defined by https://dashif.org/identifiers/audio_source_metadata/ and clause 8.2, in ISO/IEC 23001-8.
  2263. case 'urn:mpeg:mpegB:cicp:ChannelConfiguration': {
  2264. const noValue = 0;
  2265. const channelCountMapping = [
  2266. noValue, 1, 2, 3, 4, 5, 6, 8, 2, 3, /* 0--9 */
  2267. 4, 7, 8, 24, 8, 12, 10, 12, 14, 12, /* 10--19 */
  2268. 14, /* 20 */
  2269. ];
  2270. const intValue = parseInt(value, 10);
  2271. if (!intValue) { // 0 or NaN
  2272. shaka.log.warning('Channel parsing failure! ' +
  2273. 'Ignoring scheme and value', scheme, value);
  2274. continue;
  2275. }
  2276. if (intValue > noValue && intValue < channelCountMapping.length) {
  2277. return channelCountMapping[intValue];
  2278. }
  2279. continue;
  2280. }
  2281. default:
  2282. shaka.log.warning(
  2283. 'Unrecognized audio channel scheme:', scheme, value);
  2284. continue;
  2285. }
  2286. }
  2287. return null;
  2288. }
  2289. /**
  2290. * Verifies that a Representation has exactly one Segment* element. Prints
  2291. * warnings if there is a problem.
  2292. *
  2293. * @param {shaka.dash.DashParser.InheritanceFrame} frame
  2294. * @return {boolean} True if the Representation is usable; otherwise return
  2295. * false.
  2296. * @private
  2297. */
  2298. verifyRepresentation_(frame) {
  2299. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  2300. let n = 0;
  2301. n += frame.segmentBase ? 1 : 0;
  2302. n += frame.segmentList ? 1 : 0;
  2303. n += frame.segmentTemplate ? 1 : 0;
  2304. if (n == 0) {
  2305. // TODO: Extend with the list of MIME types registered to TextEngine.
  2306. if (frame.contentType == ContentType.TEXT ||
  2307. frame.contentType == ContentType.APPLICATION) {
  2308. return true;
  2309. } else {
  2310. shaka.log.warning(
  2311. 'Representation does not contain a segment information source:',
  2312. 'the Representation must contain one of SegmentBase, SegmentList,',
  2313. 'SegmentTemplate, or explicitly indicate that it is "text".',
  2314. frame);
  2315. return false;
  2316. }
  2317. }
  2318. if (n != 1) {
  2319. shaka.log.warning(
  2320. 'Representation contains multiple segment information sources:',
  2321. 'the Representation should only contain one of SegmentBase,',
  2322. 'SegmentList, or SegmentTemplate.',
  2323. frame);
  2324. if (frame.segmentBase) {
  2325. shaka.log.info('Using SegmentBase by default.');
  2326. frame.segmentList = null;
  2327. frame.segmentTemplate = null;
  2328. } else {
  2329. goog.asserts.assert(frame.segmentList, 'There should be a SegmentList');
  2330. shaka.log.info('Using SegmentList by default.');
  2331. frame.segmentTemplate = null;
  2332. }
  2333. }
  2334. return true;
  2335. }
  2336. /**
  2337. * Makes a request to the given URI and calculates the clock offset.
  2338. *
  2339. * @param {function():!Array.<string>} getBaseUris
  2340. * @param {string} uri
  2341. * @param {string} method
  2342. * @return {!Promise.<number>}
  2343. * @private
  2344. */
  2345. async requestForTiming_(getBaseUris, uri, method) {
  2346. const uris = [shaka.util.StringUtils.htmlUnescape(uri)];
  2347. const requestUris =
  2348. shaka.util.ManifestParserUtils.resolveUris(getBaseUris(), uris);
  2349. const request = shaka.net.NetworkingEngine.makeRequest(
  2350. requestUris, this.config_.retryParameters);
  2351. request.method = method;
  2352. const type = shaka.net.NetworkingEngine.RequestType.TIMING;
  2353. const operation =
  2354. this.playerInterface_.networkingEngine.request(type, request);
  2355. this.operationManager_.manage(operation);
  2356. const response = await operation.promise;
  2357. let text;
  2358. if (method == 'HEAD') {
  2359. if (!response.headers || !response.headers['date']) {
  2360. shaka.log.warning('UTC timing response is missing',
  2361. 'expected date header');
  2362. return 0;
  2363. }
  2364. text = response.headers['date'];
  2365. } else {
  2366. text = shaka.util.StringUtils.fromUTF8(response.data);
  2367. }
  2368. const date = Date.parse(text);
  2369. if (isNaN(date)) {
  2370. shaka.log.warning('Unable to parse date from UTC timing response');
  2371. return 0;
  2372. }
  2373. return (date - Date.now());
  2374. }
  2375. /**
  2376. * Parses an array of UTCTiming elements.
  2377. *
  2378. * @param {function():!Array.<string>} getBaseUris
  2379. * @param {!Array.<!shaka.extern.xml.Node>} elems
  2380. * @return {!Promise.<number>}
  2381. * @private
  2382. */
  2383. async parseUtcTiming_(getBaseUris, elems) {
  2384. const schemesAndValues = elems.map((elem) => {
  2385. return {
  2386. scheme: elem.attributes['schemeIdUri'],
  2387. value: elem.attributes['value'],
  2388. };
  2389. });
  2390. // If there's nothing specified in the manifest, but we have a default from
  2391. // the config, use that.
  2392. const clockSyncUri = this.config_.dash.clockSyncUri;
  2393. if (!schemesAndValues.length && clockSyncUri) {
  2394. schemesAndValues.push({
  2395. scheme: 'urn:mpeg:dash:utc:http-head:2014',
  2396. value: clockSyncUri,
  2397. });
  2398. }
  2399. for (const sv of schemesAndValues) {
  2400. try {
  2401. const scheme = sv.scheme;
  2402. const value = sv.value;
  2403. switch (scheme) {
  2404. // See DASH IOP Guidelines Section 4.7
  2405. // https://bit.ly/DashIop3-2
  2406. // Some old ISO23009-1 drafts used 2012.
  2407. case 'urn:mpeg:dash:utc:http-head:2014':
  2408. case 'urn:mpeg:dash:utc:http-head:2012':
  2409. // eslint-disable-next-line no-await-in-loop
  2410. return await this.requestForTiming_(getBaseUris, value, 'HEAD');
  2411. case 'urn:mpeg:dash:utc:http-xsdate:2014':
  2412. case 'urn:mpeg:dash:utc:http-iso:2014':
  2413. case 'urn:mpeg:dash:utc:http-xsdate:2012':
  2414. case 'urn:mpeg:dash:utc:http-iso:2012':
  2415. // eslint-disable-next-line no-await-in-loop
  2416. return await this.requestForTiming_(getBaseUris, value, 'GET');
  2417. case 'urn:mpeg:dash:utc:direct:2014':
  2418. case 'urn:mpeg:dash:utc:direct:2012': {
  2419. const date = Date.parse(value);
  2420. return isNaN(date) ? 0 : (date - Date.now());
  2421. }
  2422. case 'urn:mpeg:dash:utc:http-ntp:2014':
  2423. case 'urn:mpeg:dash:utc:ntp:2014':
  2424. case 'urn:mpeg:dash:utc:sntp:2014':
  2425. shaka.log.alwaysWarn('NTP UTCTiming scheme is not supported');
  2426. break;
  2427. default:
  2428. shaka.log.alwaysWarn(
  2429. 'Unrecognized scheme in UTCTiming element', scheme);
  2430. break;
  2431. }
  2432. } catch (e) {
  2433. shaka.log.warning('Error fetching time from UTCTiming elem', e.message);
  2434. }
  2435. }
  2436. shaka.log.alwaysWarn(
  2437. 'A UTCTiming element should always be given in live manifests! ' +
  2438. 'This content may not play on clients with bad clocks!');
  2439. return 0;
  2440. }
  2441. /**
  2442. * Parses an EventStream element.
  2443. *
  2444. * @param {number} periodStart
  2445. * @param {?number} periodDuration
  2446. * @param {!shaka.extern.xml.Node} elem
  2447. * @param {number} availabilityStart
  2448. * @private
  2449. */
  2450. parseEventStream_(periodStart, periodDuration, elem, availabilityStart) {
  2451. const TXml = shaka.util.TXml;
  2452. const parseNumber = shaka.util.TXml.parseNonNegativeInt;
  2453. const schemeIdUri = elem.attributes['schemeIdUri'] || '';
  2454. const value = elem.attributes['value'] || '';
  2455. const timescale = TXml.parseAttr(elem, 'timescale', parseNumber) || 1;
  2456. for (const eventNode of TXml.findChildren(elem, 'Event')) {
  2457. const presentationTime =
  2458. TXml.parseAttr(eventNode, 'presentationTime', parseNumber) || 0;
  2459. const duration =
  2460. TXml.parseAttr(eventNode, 'duration', parseNumber) || 0;
  2461. let startTime = presentationTime / timescale + periodStart;
  2462. let endTime = startTime + (duration / timescale);
  2463. if (periodDuration != null) {
  2464. // An event should not go past the Period, even if the manifest says so.
  2465. // See: Dash sec. 5.10.2.1
  2466. startTime = Math.min(startTime, periodStart + periodDuration);
  2467. endTime = Math.min(endTime, periodStart + periodDuration);
  2468. }
  2469. // Don't add unavailable regions to the timeline.
  2470. if (endTime < availabilityStart) {
  2471. continue;
  2472. }
  2473. /** @type {shaka.extern.TimelineRegionInfo} */
  2474. const region = {
  2475. schemeIdUri: schemeIdUri,
  2476. value: value,
  2477. startTime: startTime,
  2478. endTime: endTime,
  2479. id: eventNode.attributes['id'] || '',
  2480. eventElement: TXml.txmlNodeToDomElement(eventNode),
  2481. eventNode: eventNode,
  2482. };
  2483. this.playerInterface_.onTimelineRegionAdded(region);
  2484. }
  2485. }
  2486. /**
  2487. * Makes a network request on behalf of SegmentBase.createStreamInfo.
  2488. *
  2489. * @param {!Array.<string>} uris
  2490. * @param {?number} startByte
  2491. * @param {?number} endByte
  2492. * @param {boolean} isInit
  2493. * @return {!Promise.<BufferSource>}
  2494. * @private
  2495. */
  2496. async requestSegment_(uris, startByte, endByte, isInit) {
  2497. const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
  2498. const type = isInit ?
  2499. shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT :
  2500. shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
  2501. const request = shaka.util.Networking.createSegmentRequest(
  2502. uris,
  2503. startByte,
  2504. endByte,
  2505. this.config_.retryParameters);
  2506. const response = await this.makeNetworkRequest_(
  2507. request, requestType, {type});
  2508. return response.data;
  2509. }
  2510. /**
  2511. * Guess the content type based on MIME type and codecs.
  2512. *
  2513. * @param {string} mimeType
  2514. * @param {string} codecs
  2515. * @return {string}
  2516. * @private
  2517. */
  2518. static guessContentType_(mimeType, codecs) {
  2519. const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
  2520. if (shaka.text.TextEngine.isTypeSupported(fullMimeType)) {
  2521. // If it's supported by TextEngine, it's definitely text.
  2522. // We don't check MediaSourceEngine, because that would report support
  2523. // for platform-supported video and audio types as well.
  2524. return shaka.util.ManifestParserUtils.ContentType.TEXT;
  2525. }
  2526. // Otherwise, just split the MIME type. This handles video and audio
  2527. // types well.
  2528. return mimeType.split('/')[0];
  2529. }
  2530. /**
  2531. * Create a networking request. This will manage the request using the
  2532. * parser's operation manager.
  2533. *
  2534. * @param {shaka.extern.Request} request
  2535. * @param {shaka.net.NetworkingEngine.RequestType} type
  2536. * @param {shaka.extern.RequestContext=} context
  2537. * @return {!Promise.<shaka.extern.Response>}
  2538. * @private
  2539. */
  2540. makeNetworkRequest_(request, type, context) {
  2541. const op = this.playerInterface_.networkingEngine.request(
  2542. type, request, context);
  2543. this.operationManager_.manage(op);
  2544. return op.promise;
  2545. }
  2546. /**
  2547. * @param {!shaka.extern.xml.Node} patchNode
  2548. * @private
  2549. */
  2550. updatePatchLocationNodes_(patchNode) {
  2551. const TXml = shaka.util.TXml;
  2552. TXml.modifyNodes(this.patchLocationNodes_, patchNode);
  2553. }
  2554. /**
  2555. * @return {!Array<string>}
  2556. * @private
  2557. */
  2558. getPatchLocationUris_() {
  2559. const TXml = shaka.util.TXml;
  2560. const mpdId = this.manifestPatchContext_.mpdId;
  2561. const publishTime = this.manifestPatchContext_.publishTime;
  2562. if (!mpdId || !publishTime || !this.patchLocationNodes_.length) {
  2563. return [];
  2564. }
  2565. const now = Date.now() / 1000;
  2566. const patchLocations = this.patchLocationNodes_.filter((patchLocation) => {
  2567. const ttl = TXml.parseNonNegativeInt(patchLocation.attributes['ttl']);
  2568. return !ttl || publishTime + ttl > now;
  2569. })
  2570. .map(TXml.getContents)
  2571. .filter(shaka.util.Functional.isNotNull);
  2572. if (!patchLocations.length) {
  2573. return [];
  2574. }
  2575. return shaka.util.ManifestParserUtils.resolveUris(
  2576. this.manifestUris_, patchLocations);
  2577. }
  2578. };
  2579. /**
  2580. * @typedef {{
  2581. * mpdId: string,
  2582. * type: string,
  2583. * mediaPresentationDuration: ?number,
  2584. * profiles: !Array.<string>,
  2585. * availabilityTimeOffset: number,
  2586. * getBaseUris: ?function():!Array.<string>,
  2587. * publishTime: number
  2588. * }}
  2589. *
  2590. * @property {string} mpdId
  2591. * ID of the original MPD file.
  2592. * @property {string} type
  2593. * Specifies the type of the dash manifest i.e. "static"
  2594. * @property {?number} mediaPresentationDuration
  2595. * Media presentation duration, or null if unknown.
  2596. * @property {!Array.<string>} profiles
  2597. * Profiles of DASH are defined to enable interoperability and the
  2598. * signaling of the use of features.
  2599. * @property {number} availabilityTimeOffset
  2600. * Specifies the total availabilityTimeOffset of the segment.
  2601. * @property {?function():!Array.<string>} getBaseUris
  2602. * An array of absolute base URIs.
  2603. * @property {number} publishTime
  2604. * Time when manifest has been published, in seconds.
  2605. */
  2606. shaka.dash.DashParser.PatchContext;
  2607. /**
  2608. * @const {string}
  2609. * @private
  2610. */
  2611. shaka.dash.DashParser.SCTE214_ = 'urn:scte:dash:scte214-extensions';
  2612. /**
  2613. * @typedef {
  2614. * function(!Array.<string>, ?number, ?number, boolean):
  2615. * !Promise.<BufferSource>
  2616. * }
  2617. */
  2618. shaka.dash.DashParser.RequestSegmentCallback;
  2619. /**
  2620. * @typedef {{
  2621. * segmentBase: ?shaka.extern.xml.Node,
  2622. * segmentList: ?shaka.extern.xml.Node,
  2623. * segmentTemplate: ?shaka.extern.xml.Node,
  2624. * getBaseUris: function():!Array.<string>,
  2625. * width: (number|undefined),
  2626. * height: (number|undefined),
  2627. * contentType: string,
  2628. * mimeType: string,
  2629. * codecs: string,
  2630. * frameRate: (number|undefined),
  2631. * pixelAspectRatio: (string|undefined),
  2632. * emsgSchemeIdUris: !Array.<string>,
  2633. * id: ?string,
  2634. * language: ?string,
  2635. * numChannels: ?number,
  2636. * audioSamplingRate: ?number,
  2637. * availabilityTimeOffset: number,
  2638. * initialization: ?string,
  2639. * aesKey: (shaka.extern.aesKey|undefined),
  2640. * segmentSequenceCadence: number
  2641. * }}
  2642. *
  2643. * @description
  2644. * A collection of elements and properties which are inherited across levels
  2645. * of a DASH manifest.
  2646. *
  2647. * @property {?shaka.extern.xml.Node} segmentBase
  2648. * The XML node for SegmentBase.
  2649. * @property {?shaka.extern.xml.Node} segmentList
  2650. * The XML node for SegmentList.
  2651. * @property {?shaka.extern.xml.Node} segmentTemplate
  2652. * The XML node for SegmentTemplate.
  2653. * @property {function():!Array.<string>} getBaseUris
  2654. * Function than returns an array of absolute base URIs for the frame.
  2655. * @property {(number|undefined)} width
  2656. * The inherited width value.
  2657. * @property {(number|undefined)} height
  2658. * The inherited height value.
  2659. * @property {string} contentType
  2660. * The inherited media type.
  2661. * @property {string} mimeType
  2662. * The inherited MIME type value.
  2663. * @property {string} codecs
  2664. * The inherited codecs value.
  2665. * @property {(number|undefined)} frameRate
  2666. * The inherited framerate value.
  2667. * @property {(string|undefined)} pixelAspectRatio
  2668. * The inherited pixel aspect ratio value.
  2669. * @property {!Array.<string>} emsgSchemeIdUris
  2670. * emsg registered schemeIdUris.
  2671. * @property {?string} id
  2672. * The ID of the element.
  2673. * @property {?string} language
  2674. * The original language of the element.
  2675. * @property {?number} numChannels
  2676. * The number of audio channels, or null if unknown.
  2677. * @property {?number} audioSamplingRate
  2678. * Specifies the maximum sampling rate of the content, or null if unknown.
  2679. * @property {number} availabilityTimeOffset
  2680. * Specifies the total availabilityTimeOffset of the segment, or 0 if unknown.
  2681. * @property {?string} initialization
  2682. * Specifies the file where the init segment is located, or null.
  2683. * @property {(shaka.extern.aesKey|undefined)} aesKey
  2684. * AES-128 Content protection key
  2685. * @property {number} segmentSequenceCadence
  2686. * Specifies the cadence of independent segments in Segment Sequence
  2687. * Representation.
  2688. */
  2689. shaka.dash.DashParser.InheritanceFrame;
  2690. /**
  2691. * @typedef {{
  2692. * dynamic: boolean,
  2693. * presentationTimeline: !shaka.media.PresentationTimeline,
  2694. * period: ?shaka.dash.DashParser.InheritanceFrame,
  2695. * periodInfo: ?shaka.dash.DashParser.PeriodInfo,
  2696. * adaptationSet: ?shaka.dash.DashParser.InheritanceFrame,
  2697. * representation: ?shaka.dash.DashParser.InheritanceFrame,
  2698. * bandwidth: number,
  2699. * indexRangeWarningGiven: boolean,
  2700. * availabilityTimeOffset: number,
  2701. * mediaPresentationDuration: ?number,
  2702. * profiles: !Array.<string>
  2703. * }}
  2704. *
  2705. * @description
  2706. * Contains context data for the streams. This is designed to be
  2707. * shallow-copyable, so the parser must overwrite (not modify) each key as the
  2708. * parser moves through the manifest and the parsing context changes.
  2709. *
  2710. * @property {boolean} dynamic
  2711. * True if the MPD is dynamic (not all segments available at once)
  2712. * @property {!shaka.media.PresentationTimeline} presentationTimeline
  2713. * The PresentationTimeline.
  2714. * @property {?shaka.dash.DashParser.InheritanceFrame} period
  2715. * The inheritance from the Period element.
  2716. * @property {?shaka.dash.DashParser.PeriodInfo} periodInfo
  2717. * The Period info for the current Period.
  2718. * @property {?shaka.dash.DashParser.InheritanceFrame} adaptationSet
  2719. * The inheritance from the AdaptationSet element.
  2720. * @property {?shaka.dash.DashParser.InheritanceFrame} representation
  2721. * The inheritance from the Representation element.
  2722. * @property {number} bandwidth
  2723. * The bandwidth of the Representation, or zero if missing.
  2724. * @property {boolean} indexRangeWarningGiven
  2725. * True if the warning about SegmentURL@indexRange has been printed.
  2726. * @property {number} availabilityTimeOffset
  2727. * The sum of the availabilityTimeOffset values that apply to the element.
  2728. * @property {!Array.<string>} profiles
  2729. * Profiles of DASH are defined to enable interoperability and the signaling
  2730. * of the use of features.
  2731. * @property {?number} mediaPresentationDuration
  2732. * Media presentation duration, or null if unknown.
  2733. */
  2734. shaka.dash.DashParser.Context;
  2735. /**
  2736. * @typedef {{
  2737. * start: number,
  2738. * duration: ?number,
  2739. * node: !shaka.extern.xml.Node,
  2740. * isLastPeriod: boolean
  2741. * }}
  2742. *
  2743. * @description
  2744. * Contains information about a Period element.
  2745. *
  2746. * @property {number} start
  2747. * The start time of the period.
  2748. * @property {?number} duration
  2749. * The duration of the period; or null if the duration is not given. This
  2750. * will be non-null for all periods except the last.
  2751. * @property {!shaka.extern.xml.Node} node
  2752. * The XML Node for the Period.
  2753. * @property {boolean} isLastPeriod
  2754. * Whether this Period is the last one in the manifest.
  2755. */
  2756. shaka.dash.DashParser.PeriodInfo;
  2757. /**
  2758. * @typedef {{
  2759. * id: string,
  2760. * contentType: ?string,
  2761. * language: string,
  2762. * main: boolean,
  2763. * streams: !Array.<shaka.extern.Stream>,
  2764. * drmInfos: !Array.<shaka.extern.DrmInfo>,
  2765. * trickModeFor: ?string,
  2766. * representationIds: !Array.<string>
  2767. * }}
  2768. *
  2769. * @description
  2770. * Contains information about an AdaptationSet element.
  2771. *
  2772. * @property {string} id
  2773. * The unique ID of the adaptation set.
  2774. * @property {?string} contentType
  2775. * The content type of the AdaptationSet.
  2776. * @property {string} language
  2777. * The language of the AdaptationSet.
  2778. * @property {boolean} main
  2779. * Whether the AdaptationSet has the 'main' type.
  2780. * @property {!Array.<shaka.extern.Stream>} streams
  2781. * The streams this AdaptationSet contains.
  2782. * @property {!Array.<shaka.extern.DrmInfo>} drmInfos
  2783. * The DRM info for the AdaptationSet.
  2784. * @property {?string} trickModeFor
  2785. * If non-null, this AdaptationInfo represents trick mode tracks. This
  2786. * property is the ID of the normal AdaptationSet these tracks should be
  2787. * associated with.
  2788. * @property {!Array.<string>} representationIds
  2789. * An array of the IDs of the Representations this AdaptationSet contains.
  2790. */
  2791. shaka.dash.DashParser.AdaptationInfo;
  2792. /**
  2793. * @typedef {function():!Promise.<shaka.media.SegmentIndex>}
  2794. * @description
  2795. * An async function which generates and returns a SegmentIndex.
  2796. */
  2797. shaka.dash.DashParser.GenerateSegmentIndexFunction;
  2798. /**
  2799. * @typedef {{
  2800. * generateSegmentIndex: shaka.dash.DashParser.GenerateSegmentIndexFunction
  2801. * }}
  2802. *
  2803. * @description
  2804. * Contains information about a Stream. This is passed from the createStreamInfo
  2805. * methods.
  2806. *
  2807. * @property {shaka.dash.DashParser.GenerateSegmentIndexFunction}
  2808. * generateSegmentIndex
  2809. * An async function to create the SegmentIndex for the stream.
  2810. */
  2811. shaka.dash.DashParser.StreamInfo;
  2812. shaka.media.ManifestParser.registerParserByMime(
  2813. 'application/dash+xml', () => new shaka.dash.DashParser());
  2814. shaka.media.ManifestParser.registerParserByMime(
  2815. 'video/vnd.mpeg.dash.mpd', () => new shaka.dash.DashParser());