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