Source: lib/mss/mss_parser.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.mss.MssParser');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.Deprecate');
  9. goog.require('shaka.abr.Ewma');
  10. goog.require('shaka.log');
  11. goog.require('shaka.media.InitSegmentReference');
  12. goog.require('shaka.media.ManifestParser');
  13. goog.require('shaka.media.PresentationTimeline');
  14. goog.require('shaka.media.SegmentIndex');
  15. goog.require('shaka.media.SegmentReference');
  16. goog.require('shaka.mss.ContentProtection');
  17. goog.require('shaka.net.NetworkingEngine');
  18. goog.require('shaka.util.Error');
  19. goog.require('shaka.util.LanguageUtils');
  20. goog.require('shaka.util.ManifestParserUtils');
  21. goog.require('shaka.util.MimeUtils');
  22. goog.require('shaka.util.Mp4Generator');
  23. goog.require('shaka.util.OperationManager');
  24. goog.require('shaka.util.PlayerConfiguration');
  25. goog.require('shaka.util.Timer');
  26. goog.require('shaka.util.TXml');
  27. goog.require('shaka.util.XmlUtils');
  28. /**
  29. * Creates a new MSS parser.
  30. *
  31. * @implements {shaka.extern.ManifestParser}
  32. * @export
  33. */
  34. shaka.mss.MssParser = class {
  35. /** Creates a new MSS parser. */
  36. constructor() {
  37. /** @private {?shaka.extern.ManifestConfiguration} */
  38. this.config_ = null;
  39. /** @private {?shaka.extern.ManifestParser.PlayerInterface} */
  40. this.playerInterface_ = null;
  41. /** @private {!Array.<string>} */
  42. this.manifestUris_ = [];
  43. /** @private {?shaka.extern.Manifest} */
  44. this.manifest_ = null;
  45. /** @private {number} */
  46. this.globalId_ = 1;
  47. /**
  48. * The update period in seconds, or 0 for no updates.
  49. * @private {number}
  50. */
  51. this.updatePeriod_ = 0;
  52. /** @private {?shaka.media.PresentationTimeline} */
  53. this.presentationTimeline_ = null;
  54. /**
  55. * An ewma that tracks how long updates take.
  56. * This is to mitigate issues caused by slow parsing on embedded devices.
  57. * @private {!shaka.abr.Ewma}
  58. */
  59. this.averageUpdateDuration_ = new shaka.abr.Ewma(5);
  60. /** @private {shaka.util.Timer} */
  61. this.updateTimer_ = new shaka.util.Timer(() => {
  62. this.onUpdate_();
  63. });
  64. /** @private {!shaka.util.OperationManager} */
  65. this.operationManager_ = new shaka.util.OperationManager();
  66. /**
  67. * @private {!Map.<number, !BufferSource>}
  68. */
  69. this.initSegmentDataByStreamId_ = new Map();
  70. }
  71. /**
  72. * @override
  73. * @exportInterface
  74. */
  75. configure(config) {
  76. goog.asserts.assert(config.mss != null,
  77. 'MssManifestConfiguration should not be null!');
  78. this.config_ = config;
  79. }
  80. /**
  81. * @override
  82. * @exportInterface
  83. */
  84. async start(uri, playerInterface) {
  85. goog.asserts.assert(this.config_, 'Must call configure() before start()!');
  86. this.manifestUris_ = [uri];
  87. this.playerInterface_ = playerInterface;
  88. await this.requestManifest_();
  89. // Make sure that the parser has not been destroyed.
  90. if (!this.playerInterface_) {
  91. throw new shaka.util.Error(
  92. shaka.util.Error.Severity.CRITICAL,
  93. shaka.util.Error.Category.PLAYER,
  94. shaka.util.Error.Code.OPERATION_ABORTED);
  95. }
  96. this.setUpdateTimer_();
  97. goog.asserts.assert(this.manifest_, 'Manifest should be non-null!');
  98. return this.manifest_;
  99. }
  100. /**
  101. * Called when the update timer ticks.
  102. *
  103. * @return {!Promise}
  104. * @private
  105. */
  106. async onUpdate_() {
  107. goog.asserts.assert(this.updatePeriod_ >= 0,
  108. 'There should be an update period');
  109. shaka.log.info('Updating manifest...');
  110. try {
  111. await this.requestManifest_();
  112. } catch (error) {
  113. goog.asserts.assert(error instanceof shaka.util.Error,
  114. 'Should only receive a Shaka error');
  115. // Try updating again, but ensure we haven't been destroyed.
  116. if (this.playerInterface_) {
  117. // We will retry updating, so override the severity of the error.
  118. error.severity = shaka.util.Error.Severity.RECOVERABLE;
  119. this.playerInterface_.onError(error);
  120. }
  121. }
  122. // Detect a call to stop()
  123. if (!this.playerInterface_) {
  124. return;
  125. }
  126. this.setUpdateTimer_();
  127. }
  128. /**
  129. * Sets the update timer. Does nothing if the manifest is not live.
  130. *
  131. * @private
  132. */
  133. setUpdateTimer_() {
  134. if (this.updatePeriod_ <= 0) {
  135. return;
  136. }
  137. const finalDelay = Math.max(
  138. shaka.mss.MssParser.MIN_UPDATE_PERIOD_,
  139. this.updatePeriod_,
  140. this.averageUpdateDuration_.getEstimate());
  141. // We do not run the timer as repeating because part of update is async and
  142. // we need schedule the update after it finished.
  143. this.updateTimer_.tickAfter(/* seconds= */ finalDelay);
  144. }
  145. /**
  146. * @override
  147. * @exportInterface
  148. */
  149. stop() {
  150. this.playerInterface_ = null;
  151. this.config_ = null;
  152. this.manifestUris_ = [];
  153. this.manifest_ = null;
  154. if (this.updateTimer_ != null) {
  155. this.updateTimer_.stop();
  156. this.updateTimer_ = null;
  157. }
  158. this.initSegmentDataByStreamId_.clear();
  159. return this.operationManager_.destroy();
  160. }
  161. /**
  162. * @override
  163. * @exportInterface
  164. */
  165. async update() {
  166. try {
  167. await this.requestManifest_();
  168. } catch (error) {
  169. if (!this.playerInterface_ || !error) {
  170. return;
  171. }
  172. goog.asserts.assert(error instanceof shaka.util.Error, 'Bad error type');
  173. this.playerInterface_.onError(error);
  174. }
  175. }
  176. /**
  177. * @override
  178. * @exportInterface
  179. */
  180. onExpirationUpdated(sessionId, expiration) {
  181. // No-op
  182. }
  183. /**
  184. * @override
  185. * @exportInterface
  186. */
  187. onInitialVariantChosen(variant) {
  188. // No-op
  189. }
  190. /**
  191. * @override
  192. * @exportInterface
  193. */
  194. banLocation(uri) {
  195. // No-op
  196. }
  197. /**
  198. * Makes a network request for the manifest and parses the resulting data.
  199. *
  200. * @private
  201. */
  202. async requestManifest_() {
  203. const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
  204. const type = shaka.net.NetworkingEngine.AdvancedRequestType.MSS;
  205. const request = shaka.net.NetworkingEngine.makeRequest(
  206. this.manifestUris_, this.config_.retryParameters);
  207. const networkingEngine = this.playerInterface_.networkingEngine;
  208. const startTime = Date.now();
  209. const operation = networkingEngine.request(requestType, request, {type});
  210. this.operationManager_.manage(operation);
  211. const response = await operation.promise;
  212. // Detect calls to stop().
  213. if (!this.playerInterface_) {
  214. return;
  215. }
  216. // For redirections add the response uri to the first entry in the
  217. // Manifest Uris array.
  218. if (response.uri && !this.manifestUris_.includes(response.uri)) {
  219. this.manifestUris_.unshift(response.uri);
  220. }
  221. // This may throw, but it will result in a failed promise.
  222. this.parseManifest_(response.data, response.uri);
  223. // Keep track of how long the longest manifest update took.
  224. const endTime = Date.now();
  225. const updateDuration = (endTime - startTime) / 1000.0;
  226. this.averageUpdateDuration_.sample(1, updateDuration);
  227. }
  228. /**
  229. * Parses the manifest XML. This also handles updates and will update the
  230. * stored manifest.
  231. *
  232. * @param {BufferSource} data
  233. * @param {string} finalManifestUri The final manifest URI, which may
  234. * differ from this.manifestUri_ if there has been a redirect.
  235. * @return {!Promise}
  236. * @private
  237. */
  238. parseManifest_(data, finalManifestUri) {
  239. let manifestData = data;
  240. const manifestPreprocessor = this.config_.mss.manifestPreprocessor;
  241. const defaultManifestPreprocessor =
  242. shaka.util.PlayerConfiguration.defaultManifestPreprocessor;
  243. if (manifestPreprocessor != defaultManifestPreprocessor) {
  244. shaka.Deprecate.deprecateFeature(5,
  245. 'manifest.mss.manifestPreprocessor configuration',
  246. 'Please Use manifest.mss.manifestPreprocessorTXml instead.');
  247. const mssElement =
  248. shaka.util.XmlUtils.parseXml(manifestData, 'SmoothStreamingMedia');
  249. if (!mssElement) {
  250. throw new shaka.util.Error(
  251. shaka.util.Error.Severity.CRITICAL,
  252. shaka.util.Error.Category.MANIFEST,
  253. shaka.util.Error.Code.MSS_INVALID_XML,
  254. finalManifestUri);
  255. }
  256. manifestPreprocessor(mssElement);
  257. manifestData = shaka.util.XmlUtils.toArrayBuffer(mssElement);
  258. }
  259. const mss = shaka.util.TXml.parseXml(manifestData, 'SmoothStreamingMedia');
  260. if (!mss) {
  261. throw new shaka.util.Error(
  262. shaka.util.Error.Severity.CRITICAL,
  263. shaka.util.Error.Category.MANIFEST,
  264. shaka.util.Error.Code.MSS_INVALID_XML,
  265. finalManifestUri);
  266. }
  267. const manifestPreprocessorTXml = this.config_.mss.manifestPreprocessorTXml;
  268. const defaultManifestPreprocessorTXml =
  269. shaka.util.PlayerConfiguration.defaultManifestPreprocessorTXml;
  270. if (manifestPreprocessorTXml != defaultManifestPreprocessorTXml) {
  271. manifestPreprocessorTXml(mss);
  272. }
  273. this.processManifest_(mss, finalManifestUri);
  274. return Promise.resolve();
  275. }
  276. /**
  277. * Takes a formatted MSS and converts it into a manifest.
  278. *
  279. * @param {!shaka.extern.xml.Node} mss
  280. * @param {string} finalManifestUri The final manifest URI, which may
  281. * differ from this.manifestUri_ if there has been a redirect.
  282. * @private
  283. */
  284. processManifest_(mss, finalManifestUri) {
  285. const TXml = shaka.util.TXml;
  286. if (!this.presentationTimeline_) {
  287. this.presentationTimeline_ = new shaka.media.PresentationTimeline(
  288. /* presentationStartTime= */ null, /* delay= */ 0);
  289. }
  290. const isLive = TXml.parseAttr(mss, 'IsLive',
  291. TXml.parseBoolean, /* defaultValue= */ false);
  292. if (isLive) {
  293. throw new shaka.util.Error(
  294. shaka.util.Error.Severity.CRITICAL,
  295. shaka.util.Error.Category.MANIFEST,
  296. shaka.util.Error.Code.MSS_LIVE_CONTENT_NOT_SUPPORTED);
  297. }
  298. this.presentationTimeline_.setStatic(!isLive);
  299. const timescale = TXml.parseAttr(mss, 'TimeScale',
  300. TXml.parseNonNegativeInt, shaka.mss.MssParser.DEFAULT_TIME_SCALE_);
  301. goog.asserts.assert(timescale && timescale >= 0,
  302. 'Timescale must be defined!');
  303. let dvrWindowLength = TXml.parseAttr(mss, 'DVRWindowLength',
  304. TXml.parseNonNegativeInt);
  305. // If the DVRWindowLength field is omitted for a live presentation or set
  306. // to 0, the DVR window is effectively infinite
  307. if (isLive && (dvrWindowLength === 0 || isNaN(dvrWindowLength))) {
  308. dvrWindowLength = Infinity;
  309. }
  310. // Start-over
  311. const canSeek = TXml.parseAttr(mss, 'CanSeek',
  312. TXml.parseBoolean, /* defaultValue= */ false);
  313. if (dvrWindowLength === 0 && canSeek) {
  314. dvrWindowLength = Infinity;
  315. }
  316. let segmentAvailabilityDuration = null;
  317. if (dvrWindowLength && dvrWindowLength > 0) {
  318. segmentAvailabilityDuration = dvrWindowLength / timescale;
  319. }
  320. // If it's live, we check for an override.
  321. if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
  322. segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
  323. }
  324. // If it's null, that means segments are always available. This is always
  325. // the case for VOD, and sometimes the case for live.
  326. if (segmentAvailabilityDuration == null) {
  327. segmentAvailabilityDuration = Infinity;
  328. }
  329. this.presentationTimeline_.setSegmentAvailabilityDuration(
  330. segmentAvailabilityDuration);
  331. // Duration in timescale units.
  332. const duration = TXml.parseAttr(mss, 'Duration',
  333. TXml.parseNonNegativeInt, Infinity);
  334. goog.asserts.assert(duration && duration >= 0,
  335. 'Duration must be defined!');
  336. if (!isLive) {
  337. this.presentationTimeline_.setDuration(duration / timescale);
  338. }
  339. /** @type {!shaka.mss.MssParser.Context} */
  340. const context = {
  341. variants: [],
  342. textStreams: [],
  343. timescale: timescale,
  344. duration: duration / timescale,
  345. };
  346. this.parseStreamIndexes_(mss, context);
  347. // These steps are not done on manifest update.
  348. if (!this.manifest_) {
  349. this.manifest_ = {
  350. presentationTimeline: this.presentationTimeline_,
  351. variants: context.variants,
  352. textStreams: context.textStreams,
  353. imageStreams: [],
  354. offlineSessionIds: [],
  355. minBufferTime: 0,
  356. sequenceMode: this.config_.mss.sequenceMode,
  357. ignoreManifestTimestampsInSegmentsMode: false,
  358. type: shaka.media.ManifestParser.MSS,
  359. serviceDescription: null,
  360. nextUrl: null,
  361. };
  362. // This is the first point where we have a meaningful presentation start
  363. // time, and we need to tell PresentationTimeline that so that it can
  364. // maintain consistency from here on.
  365. this.presentationTimeline_.lockStartTime();
  366. } else {
  367. // Just update the variants and text streams.
  368. this.manifest_.variants = context.variants;
  369. this.manifest_.textStreams = context.textStreams;
  370. // Re-filter the manifest. This will check any configured restrictions on
  371. // new variants, and will pass any new init data to DrmEngine to ensure
  372. // that key rotation works correctly.
  373. this.playerInterface_.filter(this.manifest_);
  374. }
  375. }
  376. /**
  377. * @param {!shaka.extern.xml.Node} mss
  378. * @param {!shaka.mss.MssParser.Context} context
  379. * @private
  380. */
  381. parseStreamIndexes_(mss, context) {
  382. const ContentProtection = shaka.mss.ContentProtection;
  383. const TXml = shaka.util.TXml;
  384. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  385. const protectionElems = TXml.findChildren(mss, 'Protection');
  386. const drmInfos = ContentProtection.parseFromProtection(
  387. protectionElems, this.config_.mss.keySystemsBySystemId);
  388. const audioStreams = [];
  389. const videoStreams = [];
  390. const textStreams = [];
  391. const streamIndexes = TXml.findChildren(mss, 'StreamIndex');
  392. for (const streamIndex of streamIndexes) {
  393. const qualityLevels = TXml.findChildren(streamIndex, 'QualityLevel');
  394. const timeline = this.createTimeline_(
  395. streamIndex, context.timescale, context.duration);
  396. // For each QualityLevel node, create a stream element
  397. for (const qualityLevel of qualityLevels) {
  398. const stream = this.createStream_(
  399. streamIndex, qualityLevel, timeline, drmInfos, context);
  400. if (!stream) {
  401. // Skip unsupported stream
  402. continue;
  403. }
  404. if (stream.type == ContentType.AUDIO &&
  405. !this.config_.disableAudio) {
  406. audioStreams.push(stream);
  407. } else if (stream.type == ContentType.VIDEO &&
  408. !this.config_.disableVideo) {
  409. videoStreams.push(stream);
  410. } else if (stream.type == ContentType.TEXT &&
  411. !this.config_.disableText) {
  412. textStreams.push(stream);
  413. }
  414. }
  415. }
  416. const variants = [];
  417. for (const audio of (audioStreams.length > 0 ? audioStreams : [null])) {
  418. for (const video of (videoStreams.length > 0 ? videoStreams : [null])) {
  419. variants.push(this.createVariant_(audio, video));
  420. }
  421. }
  422. context.variants = variants;
  423. context.textStreams = textStreams;
  424. }
  425. /**
  426. * @param {!shaka.extern.xml.Node} streamIndex
  427. * @param {!shaka.extern.xml.Node} qualityLevel
  428. * @param {!Array.<shaka.mss.MssParser.TimeRange>} timeline
  429. * @param {!Array.<shaka.extern.DrmInfo>} drmInfos
  430. * @param {!shaka.mss.MssParser.Context} context
  431. * @return {?shaka.extern.Stream}
  432. * @private
  433. */
  434. createStream_(streamIndex, qualityLevel, timeline, drmInfos, context) {
  435. const TXml = shaka.util.TXml;
  436. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  437. const MssParser = shaka.mss.MssParser;
  438. const type = streamIndex.attributes['Type'];
  439. const isValidType = type === 'audio' || type === 'video' ||
  440. type === 'text';
  441. if (!isValidType) {
  442. shaka.log.alwaysWarn('Ignoring unrecognized type:', type);
  443. return null;
  444. }
  445. const lang = streamIndex.attributes['Language'];
  446. const id = this.globalId_++;
  447. const bandwidth = TXml.parseAttr(
  448. qualityLevel, 'Bitrate', TXml.parsePositiveInt);
  449. const width = TXml.parseAttr(
  450. qualityLevel, 'MaxWidth', TXml.parsePositiveInt);
  451. const height = TXml.parseAttr(
  452. qualityLevel, 'MaxHeight', TXml.parsePositiveInt);
  453. const channelsCount = TXml.parseAttr(
  454. qualityLevel, 'Channels', TXml.parsePositiveInt);
  455. const audioSamplingRate = TXml.parseAttr(
  456. qualityLevel, 'SamplingRate', TXml.parsePositiveInt);
  457. let duration = context.duration;
  458. if (timeline.length) {
  459. const start = timeline[0].start;
  460. const end = timeline[timeline.length - 1].end;
  461. duration = end - start;
  462. }
  463. const presentationDuration = this.presentationTimeline_.getDuration();
  464. this.presentationTimeline_.setDuration(
  465. Math.min(duration, presentationDuration));
  466. /** @type {!shaka.extern.Stream} */
  467. const stream = {
  468. id: id,
  469. originalId: streamIndex.attributes['Name'] || String(id),
  470. groupId: null,
  471. createSegmentIndex: () => Promise.resolve(),
  472. closeSegmentIndex: () => Promise.resolve(),
  473. segmentIndex: null,
  474. mimeType: '',
  475. codecs: '',
  476. frameRate: undefined,
  477. pixelAspectRatio: undefined,
  478. bandwidth: bandwidth || 0,
  479. width: width || undefined,
  480. height: height || undefined,
  481. kind: '',
  482. encrypted: drmInfos.length > 0,
  483. drmInfos: drmInfos,
  484. keyIds: new Set(),
  485. language: shaka.util.LanguageUtils.normalize(lang || 'und'),
  486. originalLanguage: lang,
  487. label: '',
  488. type: '',
  489. primary: false,
  490. trickModeVideo: null,
  491. emsgSchemeIdUris: [],
  492. roles: [],
  493. forced: false,
  494. channelsCount: channelsCount,
  495. audioSamplingRate: audioSamplingRate,
  496. spatialAudio: false,
  497. closedCaptions: null,
  498. hdr: undefined,
  499. colorGamut: undefined,
  500. videoLayout: undefined,
  501. tilesLayout: undefined,
  502. matchedStreams: [],
  503. mssPrivateData: {
  504. duration: duration,
  505. timescale: context.timescale,
  506. codecPrivateData: null,
  507. },
  508. accessibilityPurpose: null,
  509. external: false,
  510. fastSwitching: false,
  511. fullMimeTypes: new Set(),
  512. };
  513. // This is specifically for text tracks.
  514. const subType = streamIndex.attributes['Subtype'];
  515. if (subType) {
  516. const role = MssParser.ROLE_MAPPING_[subType];
  517. if (role) {
  518. stream.roles.push(role);
  519. }
  520. if (role === 'main') {
  521. stream.primary = true;
  522. }
  523. }
  524. let fourCCValue = qualityLevel.attributes['FourCC'];
  525. // If FourCC not defined at QualityLevel level,
  526. // then get it from StreamIndex level
  527. if (fourCCValue === null || fourCCValue === '') {
  528. fourCCValue = streamIndex.attributes['FourCC'];
  529. }
  530. // If still not defined (optional for audio stream,
  531. // see https://msdn.microsoft.com/en-us/library/ff728116%28v=vs.95%29.aspx),
  532. // then we consider the stream is an audio AAC stream
  533. if (!fourCCValue) {
  534. if (type === 'audio') {
  535. fourCCValue = 'AAC';
  536. } else if (type === 'video') {
  537. shaka.log.alwaysWarn('FourCC is not defined whereas it is required ' +
  538. 'for a QualityLevel element for a StreamIndex of type "video"');
  539. return null;
  540. }
  541. }
  542. // Check if codec is supported
  543. if (!MssParser.SUPPORTED_CODECS_.includes(fourCCValue.toUpperCase())) {
  544. shaka.log.alwaysWarn('Codec not supported:', fourCCValue);
  545. return null;
  546. }
  547. const codecPrivateData = this.getCodecPrivateData_(
  548. qualityLevel, type, fourCCValue, stream);
  549. stream.mssPrivateData.codecPrivateData = codecPrivateData;
  550. switch (type) {
  551. case 'audio':
  552. if (!codecPrivateData) {
  553. shaka.log.alwaysWarn('Quality unsupported without CodecPrivateData',
  554. type);
  555. return null;
  556. }
  557. stream.type = ContentType.AUDIO;
  558. // This mimetype is fake to allow the transmuxing.
  559. stream.mimeType = 'mss/audio/mp4';
  560. stream.codecs = this.getAACCodec_(
  561. qualityLevel, fourCCValue, codecPrivateData);
  562. break;
  563. case 'video':
  564. if (!codecPrivateData) {
  565. shaka.log.alwaysWarn('Quality unsupported without CodecPrivateData',
  566. type);
  567. return null;
  568. }
  569. stream.type = ContentType.VIDEO;
  570. // This mimetype is fake to allow the transmuxing.
  571. stream.mimeType = 'mss/video/mp4';
  572. stream.codecs = this.getH264Codec_(
  573. qualityLevel, codecPrivateData);
  574. break;
  575. case 'text':
  576. stream.type = ContentType.TEXT;
  577. stream.mimeType = 'application/mp4';
  578. if (fourCCValue === 'TTML' || fourCCValue === 'DFXP') {
  579. stream.codecs = 'stpp';
  580. }
  581. break;
  582. }
  583. stream.fullMimeTypes.add(shaka.util.MimeUtils.getFullType(
  584. stream.mimeType, stream.codecs));
  585. // Lazy-Load the segment index to avoid create all init segment at the
  586. // same time
  587. stream.createSegmentIndex = () => {
  588. if (stream.segmentIndex) {
  589. return Promise.resolve();
  590. }
  591. let initSegmentData;
  592. if (this.initSegmentDataByStreamId_.has(stream.id)) {
  593. initSegmentData = this.initSegmentDataByStreamId_.get(stream.id);
  594. } else {
  595. let videoNalus = [];
  596. if (stream.type == ContentType.VIDEO) {
  597. const codecPrivateData = stream.mssPrivateData.codecPrivateData;
  598. videoNalus = codecPrivateData.split('00000001').slice(1);
  599. }
  600. /** @type {shaka.util.Mp4Generator.StreamInfo} */
  601. const streamInfo = {
  602. id: stream.id,
  603. type: stream.type,
  604. codecs: stream.codecs,
  605. encrypted: stream.encrypted,
  606. timescale: stream.mssPrivateData.timescale,
  607. duration: stream.mssPrivateData.duration,
  608. videoNalus: videoNalus,
  609. audioConfig: new Uint8Array([]),
  610. videoConfig: new Uint8Array([]),
  611. hSpacing: 0,
  612. vSpacing: 0,
  613. data: null, // Data is not necessary for init segement.
  614. stream: stream,
  615. };
  616. const mp4Generator = new shaka.util.Mp4Generator([streamInfo]);
  617. initSegmentData = mp4Generator.initSegment();
  618. this.initSegmentDataByStreamId_.set(stream.id, initSegmentData);
  619. }
  620. const initSegmentRef = new shaka.media.InitSegmentReference(
  621. () => [],
  622. /* startByte= */ 0,
  623. /* endByte= */ null,
  624. /* mediaQuality= */ null,
  625. /* timescale= */ undefined,
  626. initSegmentData);
  627. const segments = this.createSegments_(initSegmentRef,
  628. stream, streamIndex, timeline);
  629. stream.segmentIndex = new shaka.media.SegmentIndex(segments);
  630. return Promise.resolve();
  631. };
  632. stream.closeSegmentIndex = () => {
  633. // If we have a segment index, release it.
  634. if (stream.segmentIndex) {
  635. stream.segmentIndex.release();
  636. stream.segmentIndex = null;
  637. }
  638. };
  639. return stream;
  640. }
  641. /**
  642. * @param {!shaka.extern.xml.Node} qualityLevel
  643. * @param {string} type
  644. * @param {string} fourCCValue
  645. * @param {!shaka.extern.Stream} stream
  646. * @return {?string}
  647. * @private
  648. */
  649. getCodecPrivateData_(qualityLevel, type, fourCCValue, stream) {
  650. const codecPrivateData = qualityLevel.attributes['CodecPrivateData'];
  651. if (codecPrivateData) {
  652. return codecPrivateData;
  653. }
  654. if (type !== 'audio') {
  655. return null;
  656. }
  657. // For the audio we can reconstruct the CodecPrivateData
  658. // By default stereo
  659. const channels = stream.channelsCount || 2;
  660. // By default 44,1kHz.
  661. const samplingRate = stream.audioSamplingRate || 44100;
  662. const samplingFrequencyIndex = {
  663. 96000: 0x0,
  664. 88200: 0x1,
  665. 64000: 0x2,
  666. 48000: 0x3,
  667. 44100: 0x4,
  668. 32000: 0x5,
  669. 24000: 0x6,
  670. 22050: 0x7,
  671. 16000: 0x8,
  672. 12000: 0x9,
  673. 11025: 0xA,
  674. 8000: 0xB,
  675. 7350: 0xC,
  676. };
  677. const indexFreq = samplingFrequencyIndex[samplingRate];
  678. if (fourCCValue === 'AACH') {
  679. // High Efficiency AAC Profile
  680. const objectType = 0x05;
  681. // 4 bytes :
  682. // XXXXX XXXX XXXX XXXX
  683. // 'ObjectType' 'Freq Index' 'Channels value' 'Extens Sampl Freq'
  684. // XXXXX XXX XXXXXXX
  685. // 'ObjectType' 'GAS' 'alignment = 0'
  686. const data = new Uint8Array(4);
  687. // In HE AAC Extension Sampling frequence
  688. // equals to SamplingRate * 2
  689. const extensionSamplingFrequencyIndex =
  690. samplingFrequencyIndex[samplingRate * 2];
  691. // Freq Index is present for 3 bits in the first byte, last bit is in
  692. // the second
  693. data[0] = (objectType << 3) | (indexFreq >> 1);
  694. data[1] = (indexFreq << 7) | (channels << 3) |
  695. (extensionSamplingFrequencyIndex >> 1);
  696. // Origin object type equals to 2 => AAC Main Low Complexity
  697. data[2] = (extensionSamplingFrequencyIndex << 7) | (0x02 << 2);
  698. // Slignment bits
  699. data[3] = 0x0;
  700. // Put the 4 bytes in an 16 bits array
  701. const arr16 = new Uint16Array(2);
  702. arr16[0] = (data[0] << 8) + data[1];
  703. arr16[1] = (data[2] << 8) + data[3];
  704. // Convert decimal to hex value
  705. return arr16[0].toString(16) + arr16[1].toString(16);
  706. } else {
  707. // AAC Main Low Complexity
  708. const objectType = 0x02;
  709. // 2 bytes:
  710. // XXXXX XXXX XXXX XXX
  711. // 'ObjectType' 'Freq Index' 'Channels value' 'GAS = 000'
  712. const data = new Uint8Array(2);
  713. // Freq Index is present for 3 bits in the first byte, last bit is in
  714. // the second
  715. data[0] = (objectType << 3) | (indexFreq >> 1);
  716. data[1] = (indexFreq << 7) | (channels << 3);
  717. // Put the 2 bytes in an 16 bits array
  718. const arr16 = new Uint16Array(1);
  719. arr16[0] = (data[0] << 8) + data[1];
  720. // Convert decimal to hex value
  721. return arr16[0].toString(16);
  722. }
  723. }
  724. /**
  725. * @param {!shaka.extern.xml.Node} qualityLevel
  726. * @param {string} fourCCValue
  727. * @param {?string} codecPrivateData
  728. * @return {string}
  729. * @private
  730. */
  731. getAACCodec_(qualityLevel, fourCCValue, codecPrivateData) {
  732. let objectType = 0;
  733. // Chrome problem, in implicit AAC HE definition, so when AACH is detected
  734. // in FourCC set objectType to 5 => strange, it should be 2
  735. if (fourCCValue === 'AACH') {
  736. objectType = 0x05;
  737. }
  738. if (!codecPrivateData) {
  739. // AAC Main Low Complexity => object Type = 2
  740. objectType = 0x02;
  741. if (fourCCValue === 'AACH') {
  742. // High Efficiency AAC Profile = object Type = 5 SBR
  743. objectType = 0x05;
  744. }
  745. } else if (objectType === 0) {
  746. objectType = (parseInt(codecPrivateData.substr(0, 2), 16) & 0xF8) >> 3;
  747. }
  748. return 'mp4a.40.' + objectType;
  749. }
  750. /**
  751. * @param {!shaka.extern.xml.Node} qualityLevel
  752. * @param {?string} codecPrivateData
  753. * @return {string}
  754. * @private
  755. */
  756. getH264Codec_(qualityLevel, codecPrivateData) {
  757. // Extract from the CodecPrivateData field the hexadecimal representation
  758. // of the following three bytes in the sequence parameter set NAL unit.
  759. // => Find the SPS nal header
  760. const nalHeader = /00000001[0-9]7/.exec(codecPrivateData);
  761. if (!nalHeader.length) {
  762. return '';
  763. }
  764. if (!codecPrivateData) {
  765. return '';
  766. }
  767. // => Find the 6 characters after the SPS nalHeader (if it exists)
  768. const avcoti = codecPrivateData.substr(
  769. codecPrivateData.indexOf(nalHeader[0]) + 10, 6);
  770. return 'avc1.' + avcoti;
  771. }
  772. /**
  773. * @param {!shaka.media.InitSegmentReference} initSegmentRef
  774. * @param {!shaka.extern.Stream} stream
  775. * @param {!shaka.extern.xml.Node} streamIndex
  776. * @param {!Array.<shaka.mss.MssParser.TimeRange>} timeline
  777. * @return {!Array.<!shaka.media.SegmentReference>}
  778. * @private
  779. */
  780. createSegments_(initSegmentRef, stream, streamIndex, timeline) {
  781. const ManifestParserUtils = shaka.util.ManifestParserUtils;
  782. const url = streamIndex.attributes['Url'];
  783. goog.asserts.assert(url, 'Missing URL for segments');
  784. const mediaUrl = url.replace('{bitrate}', String(stream.bandwidth));
  785. const segments = [];
  786. for (const time of timeline) {
  787. const getUris = () => {
  788. return ManifestParserUtils.resolveUris(this.manifestUris_,
  789. [mediaUrl.replace('{start time}', String(time.unscaledStart))]);
  790. };
  791. segments.push(new shaka.media.SegmentReference(
  792. time.start,
  793. time.end,
  794. getUris,
  795. /* startByte= */ 0,
  796. /* endByte= */ null,
  797. initSegmentRef,
  798. /* timestampOffset= */ 0,
  799. /* appendWindowStart= */ 0,
  800. /* appendWindowEnd= */ stream.mssPrivateData.duration));
  801. }
  802. return segments;
  803. }
  804. /**
  805. * Expands a streamIndex into an array-based timeline. The results are in
  806. * seconds.
  807. *
  808. * @param {!shaka.extern.xml.Node} streamIndex
  809. * @param {number} timescale
  810. * @param {number} duration The duration in seconds.
  811. * @return {!Array.<shaka.mss.MssParser.TimeRange>}
  812. * @private
  813. */
  814. createTimeline_(streamIndex, timescale, duration) {
  815. goog.asserts.assert(
  816. timescale > 0 && timescale < Infinity,
  817. 'timescale must be a positive, finite integer');
  818. goog.asserts.assert(
  819. duration > 0, 'duration must be a positive integer');
  820. const TXml = shaka.util.TXml;
  821. const timePoints = TXml.findChildren(streamIndex, 'c');
  822. /** @type {!Array.<shaka.mss.MssParser.TimeRange>} */
  823. const timeline = [];
  824. let lastEndTime = 0;
  825. for (let i = 0; i < timePoints.length; ++i) {
  826. const timePoint = timePoints[i];
  827. const next = timePoints[i + 1];
  828. const t =
  829. TXml.parseAttr(timePoint, 't', TXml.parseNonNegativeInt);
  830. const d =
  831. TXml.parseAttr(timePoint, 'd', TXml.parseNonNegativeInt);
  832. const r = TXml.parseAttr(timePoint, 'r', TXml.parseInt);
  833. if (!d) {
  834. shaka.log.warning(
  835. '"c" element must have a duration:',
  836. 'ignoring the remaining "c" elements.', timePoint);
  837. return timeline;
  838. }
  839. let startTime = t != null ? t : lastEndTime;
  840. let repeat = r || 0;
  841. // Unlike in DASH, in MSS r does not start counting repetitions at 0 but
  842. // at 1, to maintain the code equivalent to DASH if r exists we
  843. // subtract 1.
  844. if (repeat) {
  845. repeat--;
  846. }
  847. if (repeat < 0) {
  848. if (next) {
  849. const nextStartTime =
  850. TXml.parseAttr(next, 't', TXml.parseNonNegativeInt);
  851. if (nextStartTime == null) {
  852. shaka.log.warning(
  853. 'An "c" element cannot have a negative repeat',
  854. 'if the next "c" element does not have a valid start time:',
  855. 'ignoring the remaining "c" elements.', timePoint);
  856. return timeline;
  857. } else if (startTime >= nextStartTime) {
  858. shaka.log.warning(
  859. 'An "c" element cannot have a negative repeatif its start ',
  860. 'time exceeds the next "c" element\'s start time:',
  861. 'ignoring the remaining "c" elements.', timePoint);
  862. return timeline;
  863. }
  864. repeat = Math.ceil((nextStartTime - startTime) / d) - 1;
  865. } else {
  866. if (duration == Infinity) {
  867. // The MSS spec. actually allows the last "c" element to have a
  868. // negative repeat value even when it has an infinite
  869. // duration. No one uses this feature and no one ever should,
  870. // ever.
  871. shaka.log.warning(
  872. 'The last "c" element cannot have a negative repeat',
  873. 'if the Period has an infinite duration:',
  874. 'ignoring the last "c" element.', timePoint);
  875. return timeline;
  876. } else if (startTime / timescale >= duration) {
  877. shaka.log.warning(
  878. 'The last "c" element cannot have a negative repeat',
  879. 'if its start time exceeds the duration:',
  880. 'igoring the last "c" element.', timePoint);
  881. return timeline;
  882. }
  883. repeat = Math.ceil((duration * timescale - startTime) / d) - 1;
  884. }
  885. }
  886. for (let j = 0; j <= repeat; ++j) {
  887. const endTime = startTime + d;
  888. const item = {
  889. start: startTime / timescale,
  890. end: endTime / timescale,
  891. unscaledStart: startTime,
  892. };
  893. timeline.push(item);
  894. startTime = endTime;
  895. lastEndTime = endTime;
  896. }
  897. }
  898. return timeline;
  899. }
  900. /**
  901. * @param {?shaka.extern.Stream} audioStream
  902. * @param {?shaka.extern.Stream} videoStream
  903. * @return {!shaka.extern.Variant}
  904. * @private
  905. */
  906. createVariant_(audioStream, videoStream) {
  907. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  908. goog.asserts.assert(!audioStream ||
  909. audioStream.type == ContentType.AUDIO, 'Audio parameter mismatch!');
  910. goog.asserts.assert(!videoStream ||
  911. videoStream.type == ContentType.VIDEO, 'Video parameter mismatch!');
  912. let bandwidth = 0;
  913. if (audioStream && audioStream.bandwidth && audioStream.bandwidth > 0) {
  914. bandwidth += audioStream.bandwidth;
  915. }
  916. if (videoStream && videoStream.bandwidth && videoStream.bandwidth > 0) {
  917. bandwidth += videoStream.bandwidth;
  918. }
  919. return {
  920. id: this.globalId_++,
  921. language: audioStream ? audioStream.language : 'und',
  922. disabledUntilTime: 0,
  923. primary: (!!audioStream && audioStream.primary) ||
  924. (!!videoStream && videoStream.primary),
  925. audio: audioStream,
  926. video: videoStream,
  927. bandwidth: bandwidth,
  928. allowedByApplication: true,
  929. allowedByKeySystem: true,
  930. decodingInfos: [],
  931. };
  932. }
  933. };
  934. /**
  935. * Contains the minimum amount of time, in seconds, between manifest update
  936. * requests.
  937. *
  938. * @private
  939. * @const {number}
  940. */
  941. shaka.mss.MssParser.MIN_UPDATE_PERIOD_ = 3;
  942. /**
  943. * @private
  944. * @const {number}
  945. */
  946. shaka.mss.MssParser.DEFAULT_TIME_SCALE_ = 1e7;
  947. /**
  948. * MSS supported codecs.
  949. *
  950. * @private
  951. * @const {!Array.<string>}
  952. */
  953. shaka.mss.MssParser.SUPPORTED_CODECS_ = [
  954. 'AAC',
  955. 'AACL',
  956. 'AACH',
  957. 'AACP',
  958. 'AVC1',
  959. 'H264',
  960. 'TTML',
  961. 'DFXP',
  962. ];
  963. /**
  964. * MPEG-DASH Role and accessibility mapping for text tracks according to
  965. * ETSI TS 103 285 v1.1.1 (section 7.1.2)
  966. *
  967. * @const {!Object.<string, string>}
  968. * @private
  969. */
  970. shaka.mss.MssParser.ROLE_MAPPING_ = {
  971. 'CAPT': 'main',
  972. 'SUBT': 'alternate',
  973. 'DESC': 'main',
  974. };
  975. /**
  976. * @typedef {{
  977. * variants: !Array.<shaka.extern.Variant>,
  978. * textStreams: !Array.<shaka.extern.Stream>,
  979. * timescale: number,
  980. * duration: number
  981. * }}
  982. *
  983. * @property {!Array.<shaka.extern.Variant>} variants
  984. * The presentation's Variants.
  985. * @property {!Array.<shaka.extern.Stream>} textStreams
  986. * The presentation's text streams.
  987. * @property {number} timescale
  988. * The presentation's timescale.
  989. * @property {number} duration
  990. * The presentation's duration.
  991. */
  992. shaka.mss.MssParser.Context;
  993. /**
  994. * @typedef {{
  995. * start: number,
  996. * unscaledStart: number,
  997. * end: number
  998. * }}
  999. *
  1000. * @description
  1001. * Defines a time range of a media segment. Times are in seconds.
  1002. *
  1003. * @property {number} start
  1004. * The start time of the range.
  1005. * @property {number} unscaledStart
  1006. * The start time of the range in representation timescale units.
  1007. * @property {number} end
  1008. * The end time (exclusive) of the range.
  1009. */
  1010. shaka.mss.MssParser.TimeRange;
  1011. shaka.media.ManifestParser.registerParserByMime(
  1012. 'application/vnd.ms-sstr+xml', () => new shaka.mss.MssParser());