Source: lib/media/preload_manager.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2023 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.PreloadManager');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.drm.DrmEngine');
  9. goog.require('shaka.drm.DrmUtils');
  10. goog.require('shaka.log');
  11. goog.require('shaka.media.AdaptationSetCriteria');
  12. goog.require('shaka.media.ManifestFilterer');
  13. goog.require('shaka.media.ManifestParser');
  14. goog.require('shaka.media.QualityObserver');
  15. goog.require('shaka.media.RegionTimeline');
  16. goog.require('shaka.media.SegmentPrefetch');
  17. goog.require('shaka.media.StreamingEngine');
  18. goog.require('shaka.net.NetworkingEngine');
  19. goog.require('shaka.util.ConfigUtils');
  20. goog.require('shaka.util.Error');
  21. goog.require('shaka.util.FakeEvent');
  22. goog.require('shaka.util.FakeEventTarget');
  23. goog.require('shaka.util.IDestroyable');
  24. goog.require('shaka.util.ObjectUtils');
  25. goog.require('shaka.util.PlayerConfiguration');
  26. goog.require('shaka.util.PublicPromise');
  27. goog.require('shaka.util.Stats');
  28. goog.require('shaka.util.StreamUtils');
  29. /**
  30. * @implements {shaka.util.IDestroyable}
  31. * @export
  32. */
  33. shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
  34. /**
  35. * @param {string} assetUri
  36. * @param {?string} mimeType
  37. * @param {?number|Date} startTime
  38. * @param {*} playerInterface
  39. */
  40. constructor(assetUri, mimeType, startTime, playerInterface) {
  41. super();
  42. // Making the playerInterface a * and casting it to the right type allows
  43. // for the PlayerInterface for this class to not be exported.
  44. // Unfortunately, the constructor is exported by default.
  45. const typedPlayerInterface =
  46. /** @type {!shaka.media.PreloadManager.PlayerInterface} */ (
  47. playerInterface);
  48. /** @private {string} */
  49. this.assetUri_ = assetUri;
  50. /** @private {?string} */
  51. this.mimeType_ = mimeType;
  52. /** @private {!shaka.net.NetworkingEngine} */
  53. this.networkingEngine_ = typedPlayerInterface.networkingEngine;
  54. /** @private {?number|Date} */
  55. this.startTime_ = startTime;
  56. /** @private {?shaka.media.AdaptationSetCriteria} */
  57. this.currentAdaptationSetCriteria_ = null;
  58. /** @private {number} */
  59. this.startTimeOfDrm_ = 0;
  60. /** @private {function():!shaka.drm.DrmEngine} */
  61. this.createDrmEngine_ = typedPlayerInterface.createDrmEngine;
  62. /** @private {!shaka.media.ManifestFilterer} */
  63. this.manifestFilterer_ = typedPlayerInterface.manifestFilterer;
  64. /** @private {!shaka.extern.ManifestParser.PlayerInterface} */
  65. this.manifestPlayerInterface_ =
  66. typedPlayerInterface.manifestPlayerInterface;
  67. /** @private {!shaka.extern.PlayerConfiguration} */
  68. this.config_ = typedPlayerInterface.config;
  69. /** @private {?shaka.extern.Manifest} */
  70. this.manifest_ = null;
  71. /** @private {?shaka.extern.ManifestParser.Factory} */
  72. this.parserFactory_ = null;
  73. /** @private {?shaka.extern.ManifestParser} */
  74. this.parser_ = null;
  75. /** @private {boolean} */
  76. this.parserEntrusted_ = false;
  77. /**
  78. * @private {!shaka.media.RegionTimeline<
  79. * shaka.extern.TimelineRegionInfo>}
  80. */
  81. this.regionTimeline_ = typedPlayerInterface.regionTimeline;
  82. /** @private {boolean} */
  83. this.regionTimelineEntrusted_ = false;
  84. /** @private {?shaka.drm.DrmEngine} */
  85. this.drmEngine_ = null;
  86. /** @private {boolean} */
  87. this.drmEngineEntrusted_ = false;
  88. /** @private {?shaka.extern.AbrManager.Factory} */
  89. this.abrManagerFactory_ = null;
  90. /** @private {shaka.extern.AbrManager} */
  91. this.abrManager_ = null;
  92. /** @private {boolean} */
  93. this.abrManagerEntrusted_ = false;
  94. /** @private {!Map<number, shaka.media.SegmentPrefetch>} */
  95. this.segmentPrefetchById_ = new Map();
  96. /** @private {boolean} */
  97. this.segmentPrefetchEntrusted_ = false;
  98. /** @private {?shaka.media.QualityObserver} */
  99. this.qualityObserver_ = typedPlayerInterface.qualityObserver;
  100. /** @private {!shaka.util.Stats} */
  101. this.stats_ = new shaka.util.Stats();
  102. /** @private {!shaka.util.PublicPromise} */
  103. this.manifestPromise_ = new shaka.util.PublicPromise();
  104. /** @private {!shaka.util.PublicPromise} */
  105. this.successPromise_ = new shaka.util.PublicPromise();
  106. /** @private {?shaka.util.FakeEventTarget} */
  107. this.eventHandoffTarget_ = null;
  108. /** @private {boolean} */
  109. this.destroyed_ = false;
  110. /** @private {boolean} */
  111. this.allowPrefetch_ = typedPlayerInterface.allowPrefetch;
  112. /** @private {?shaka.extern.Variant} */
  113. this.prefetchedVariant_ = null;
  114. /** @private {?shaka.extern.Stream} */
  115. this.prefetchedTextStream_ = null;
  116. /** @private {boolean} */
  117. this.allowMakeAbrManager_ = typedPlayerInterface.allowMakeAbrManager;
  118. /** @private {boolean} */
  119. this.hasBeenAttached_ = false;
  120. /** @private {?Array<function()>} */
  121. this.queuedOperations_ = [];
  122. /** @private {?Array<function()>} */
  123. this.latePhaseQueuedOperations_ = [];
  124. /** @private {boolean} */
  125. this.isPreload_ = true;
  126. }
  127. /**
  128. * Makes it so that net requests launched from this load will no longer be
  129. * marked as "isPreload"
  130. */
  131. markIsLoad() {
  132. this.isPreload_ = false;
  133. }
  134. /**
  135. * @param {boolean} latePhase
  136. * @param {function()} callback
  137. */
  138. addQueuedOperation(latePhase, callback) {
  139. const queue =
  140. latePhase ? this.latePhaseQueuedOperations_ : this.queuedOperations_;
  141. if (queue) {
  142. queue.push(callback);
  143. } else {
  144. callback();
  145. }
  146. }
  147. /** Calls all late phase queued operations, and stops queueing them. */
  148. stopQueuingLatePhaseQueuedOperations() {
  149. if (this.latePhaseQueuedOperations_) {
  150. for (const callback of this.latePhaseQueuedOperations_) {
  151. callback();
  152. }
  153. }
  154. this.latePhaseQueuedOperations_ = null;
  155. }
  156. /** @param {!shaka.util.FakeEventTarget} eventHandoffTarget */
  157. setEventHandoffTarget(eventHandoffTarget) {
  158. this.eventHandoffTarget_ = eventHandoffTarget;
  159. this.hasBeenAttached_ = true;
  160. // Also call all queued operations, and stop queuing them in the future.
  161. if (this.queuedOperations_) {
  162. for (const callback of this.queuedOperations_) {
  163. callback();
  164. }
  165. }
  166. this.queuedOperations_ = null;
  167. }
  168. /** @param {number} offset */
  169. setOffsetToStartTime(offset) {
  170. if (this.startTime_ && offset) {
  171. if (typeof this.startTime_ === 'number') {
  172. this.startTime_ += offset;
  173. } else {
  174. this.startTime_.setTime(this.startTime_.getTime() + offset * 1000);
  175. }
  176. }
  177. }
  178. /** @return {?number|Date} */
  179. getStartTime() {
  180. return this.startTime_;
  181. }
  182. /** @return {number} */
  183. getStartTimeOfDRM() {
  184. return this.startTimeOfDrm_;
  185. }
  186. /** @return {?string} */
  187. getMimeType() {
  188. return this.mimeType_;
  189. }
  190. /** @return {string} */
  191. getAssetUri() {
  192. return this.assetUri_;
  193. }
  194. /** @return {?shaka.extern.Manifest} */
  195. getManifest() {
  196. return this.manifest_;
  197. }
  198. /** @return {?shaka.extern.ManifestParser.Factory} */
  199. getParserFactory() {
  200. return this.parserFactory_;
  201. }
  202. /** @return {?shaka.media.AdaptationSetCriteria} */
  203. getCurrentAdaptationSetCriteria() {
  204. return this.currentAdaptationSetCriteria_;
  205. }
  206. /** @return {?shaka.extern.AbrManager.Factory} */
  207. getAbrManagerFactory() {
  208. return this.abrManagerFactory_;
  209. }
  210. /**
  211. * Gets the abr manager, if it exists. Also marks that the abr manager should
  212. * not be stopped if this manager is destroyed.
  213. * @return {?shaka.extern.AbrManager}
  214. */
  215. receiveAbrManager() {
  216. this.abrManagerEntrusted_ = true;
  217. return this.abrManager_;
  218. }
  219. /**
  220. * @return {?shaka.extern.AbrManager}
  221. */
  222. getAbrManager() {
  223. return this.abrManager_;
  224. }
  225. /**
  226. * Gets the parser, if it exists. Also marks that the parser should not be
  227. * stopped if this manager is destroyed.
  228. * @return {?shaka.extern.ManifestParser}
  229. */
  230. receiveParser() {
  231. this.parserEntrusted_ = true;
  232. return this.parser_;
  233. }
  234. /**
  235. * @return {?shaka.extern.ManifestParser}
  236. */
  237. getParser() {
  238. return this.parser_;
  239. }
  240. /**
  241. * Gets the region timeline, if it exists. Also marks that the timeline should
  242. * not be released if this manager is destroyed.
  243. * @return {?shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>}
  244. */
  245. receiveRegionTimeline() {
  246. this.regionTimelineEntrusted_ = true;
  247. return this.regionTimeline_;
  248. }
  249. /**
  250. * @return {?shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>}
  251. */
  252. getRegionTimeline() {
  253. return this.regionTimeline_;
  254. }
  255. /** @return {?shaka.media.QualityObserver} */
  256. getQualityObserver() {
  257. return this.qualityObserver_;
  258. }
  259. /** @return {!shaka.util.Stats} */
  260. getStats() {
  261. return this.stats_;
  262. }
  263. /** @return {!shaka.media.ManifestFilterer} */
  264. getManifestFilterer() {
  265. return this.manifestFilterer_;
  266. }
  267. /**
  268. * Gets the drm engine, if it exists. Also marks that the drm engine should
  269. * not be destroyed if this manager is destroyed.
  270. * @return {?shaka.drm.DrmEngine}
  271. */
  272. receiveDrmEngine() {
  273. this.drmEngineEntrusted_ = true;
  274. return this.drmEngine_;
  275. }
  276. /**
  277. * @return {?shaka.drm.DrmEngine}
  278. */
  279. getDrmEngine() {
  280. return this.drmEngine_;
  281. }
  282. /**
  283. * @param {shaka.extern.Variant} variant
  284. */
  285. setPrefetchVariant(variant) {
  286. this.prefetchedVariant_ = variant;
  287. }
  288. /**
  289. * @return {?shaka.extern.Variant}
  290. */
  291. getPrefetchedVariant() {
  292. return this.prefetchedVariant_;
  293. }
  294. /**
  295. * Gets the preloaded variant track if it exists.
  296. *
  297. * @return {?shaka.extern.Track}
  298. * @export
  299. */
  300. getPrefetchedVariantTrack() {
  301. if (!this.prefetchedVariant_) {
  302. return null;
  303. }
  304. return shaka.util.StreamUtils.variantToTrack(this.prefetchedVariant_);
  305. }
  306. /**
  307. * Gets the preloaded text track if it exists.
  308. *
  309. * @return {?shaka.extern.TextTrack}
  310. * @export
  311. */
  312. getPrefetchedTextTrack() {
  313. if (!this.prefetchedTextStream_) {
  314. return null;
  315. }
  316. return shaka.util.StreamUtils.textStreamToTrack(this.prefetchedTextStream_);
  317. }
  318. /**
  319. * Gets the SegmentPrefetch objects for the initial stream ids. Also marks
  320. * that those objects should not be aborted if this manager is destroyed.
  321. * @return {!Map<number, shaka.media.SegmentPrefetch>}
  322. */
  323. receiveSegmentPrefetchesById() {
  324. this.segmentPrefetchEntrusted_ = true;
  325. return this.segmentPrefetchById_;
  326. }
  327. /**
  328. * @param {?shaka.extern.AbrManager} abrManager
  329. * @param {?shaka.extern.AbrManager.Factory} abrFactory
  330. */
  331. attachAbrManager(abrManager, abrFactory) {
  332. this.abrManager_ = abrManager;
  333. this.abrManagerFactory_ = abrFactory;
  334. }
  335. /**
  336. * @param {?shaka.media.AdaptationSetCriteria} adaptationSetCriteria
  337. */
  338. attachAdaptationSetCriteria(adaptationSetCriteria) {
  339. this.currentAdaptationSetCriteria_ = adaptationSetCriteria;
  340. }
  341. /**
  342. * @param {!shaka.extern.Manifest} manifest
  343. * @param {!shaka.extern.ManifestParser} parser
  344. * @param {!shaka.extern.ManifestParser.Factory} parserFactory
  345. */
  346. attachManifest(manifest, parser, parserFactory) {
  347. this.manifest_ = manifest;
  348. this.parser_ = parser;
  349. this.parserFactory_ = parserFactory;
  350. }
  351. /**
  352. * Starts the process of loading the asset.
  353. * Success or failure will be measured through waitForFinish()
  354. */
  355. start() {
  356. (async () => {
  357. // Force a context switch, to give the player a chance to hook up events
  358. // immediately if desired.
  359. await Promise.resolve();
  360. // Perform the preloading process.
  361. try {
  362. await this.parseManifestInner_();
  363. this.throwIfDestroyed_();
  364. if (!shaka.drm.DrmUtils.isMediaKeysPolyfilled('webkit')) {
  365. await this.initializeDrm();
  366. this.throwIfDestroyed_();
  367. }
  368. if (this.allowPrefetch_) {
  369. await this.prefetchInner_();
  370. this.throwIfDestroyed_();
  371. }
  372. // We don't need the drm keys to load completely for the initial variant
  373. // to be chosen, but we won't mark the load as a success until it has
  374. // been loaded. So wait for it here, not inside initializeDrmInner_.
  375. if (this.allowPrefetch_ && this.drmEngine_) {
  376. await this.drmEngine_.waitForActiveRequests();
  377. this.throwIfDestroyed_();
  378. }
  379. this.successPromise_.resolve();
  380. } catch (error) {
  381. // Ignore OPERATION_ABORTED and OBJECT_DESTROYED errors.
  382. if (!(error instanceof shaka.util.Error) ||
  383. (error.code != shaka.util.Error.Code.OPERATION_ABORTED &&
  384. error.code != shaka.util.Error.Code.OBJECT_DESTROYED)) {
  385. this.successPromise_.reject(error);
  386. }
  387. }
  388. })();
  389. }
  390. /**
  391. * @param {!Event} event
  392. * @return {boolean}
  393. * @override
  394. */
  395. dispatchEvent(event) {
  396. if (this.eventHandoffTarget_) {
  397. return this.eventHandoffTarget_.dispatchEvent(event);
  398. } else {
  399. return super.dispatchEvent(event);
  400. }
  401. }
  402. /**
  403. * @param {!shaka.util.Error} error
  404. */
  405. onError(error) {
  406. if (error.severity === shaka.util.Error.Severity.CRITICAL) {
  407. // Cancel the loading process.
  408. this.successPromise_.reject(error);
  409. this.destroy();
  410. }
  411. const eventName = shaka.util.FakeEvent.EventName.Error;
  412. const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
  413. this.dispatchEvent(event);
  414. if (event.defaultPrevented) {
  415. error.handled = true;
  416. }
  417. }
  418. /**
  419. * Throw if destroyed, to interrupt processes with a recognizable error.
  420. *
  421. * @private
  422. */
  423. throwIfDestroyed_() {
  424. if (this.isDestroyed()) {
  425. throw new shaka.util.Error(
  426. shaka.util.Error.Severity.CRITICAL,
  427. shaka.util.Error.Category.PLAYER,
  428. shaka.util.Error.Code.OBJECT_DESTROYED);
  429. }
  430. }
  431. /**
  432. * Makes a fires an event corresponding to entering a state of the loading
  433. * process.
  434. * @param {string} nodeName
  435. * @private
  436. */
  437. makeStateChangeEvent_(nodeName) {
  438. this.dispatchEvent(new shaka.util.FakeEvent(
  439. /* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
  440. /* data= */ (new Map()).set('state', nodeName)));
  441. }
  442. /**
  443. * @param {!shaka.util.FakeEvent.EventName} name
  444. * @param {Map<string, Object>=} data
  445. * @return {!shaka.util.FakeEvent}
  446. * @private
  447. */
  448. makeEvent_(name, data) {
  449. return new shaka.util.FakeEvent(name, data);
  450. }
  451. /**
  452. * Pick and initialize a manifest parser, then have it download and parse the
  453. * manifest.
  454. *
  455. * @return {!Promise}
  456. * @private
  457. */
  458. async parseManifestInner_() {
  459. this.makeStateChangeEvent_('manifest-parser');
  460. if (!this.parser_) {
  461. // Create the parser that we will use to parse the manifest.
  462. this.parserFactory_ = shaka.media.ManifestParser.getFactory(
  463. this.assetUri_, this.mimeType_);
  464. goog.asserts.assert(this.parserFactory_, 'Must have manifest parser');
  465. this.parser_ = this.parserFactory_();
  466. this.parser_.configure(this.config_.manifest, () => this.isPreload_);
  467. }
  468. const startTime = Date.now() / 1000;
  469. this.makeStateChangeEvent_('manifest');
  470. if (!this.manifest_) {
  471. this.manifest_ = await this.parser_.start(
  472. this.assetUri_, this.manifestPlayerInterface_);
  473. await this.chooseInitialVariant_();
  474. }
  475. this.manifestPromise_.resolve();
  476. // This event is fired after the manifest is parsed, but before any
  477. // filtering takes place.
  478. const event =
  479. this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
  480. // Delay event to ensure manifest has been properly propagated
  481. // to the player.
  482. await Promise.resolve();
  483. this.dispatchEvent(event);
  484. // We require all manifests to have at least one variant.
  485. if (this.manifest_.variants.length == 0) {
  486. throw new shaka.util.Error(
  487. shaka.util.Error.Severity.CRITICAL,
  488. shaka.util.Error.Category.MANIFEST,
  489. shaka.util.Error.Code.NO_VARIANTS);
  490. }
  491. // Make sure that all variants are either: audio-only, video-only, or
  492. // audio-video.
  493. shaka.media.PreloadManager.filterForAVVariants_(this.manifest_);
  494. const tracksChangedInitial = this.manifestFilterer_.applyRestrictions(
  495. this.manifest_);
  496. if (tracksChangedInitial) {
  497. const event = this.makeEvent_(
  498. shaka.util.FakeEvent.EventName.TracksChanged);
  499. await Promise.resolve();
  500. this.throwIfDestroyed_();
  501. this.dispatchEvent(event);
  502. }
  503. const now = Date.now() / 1000;
  504. const delta = now - startTime;
  505. this.stats_.setManifestTime(delta);
  506. }
  507. /**
  508. * Initializes the DRM engine.
  509. * @param {?HTMLMediaElement=} media
  510. * @return {!Promise}
  511. */
  512. async initializeDrm(media) {
  513. if (!this.manifest_ || this.drmEngine_) {
  514. return;
  515. }
  516. this.makeStateChangeEvent_('drm-engine');
  517. this.startTimeOfDrm_ = Date.now() / 1000;
  518. this.drmEngine_ = this.createDrmEngine_();
  519. this.manifestFilterer_.setDrmEngine(this.drmEngine_);
  520. this.drmEngine_.configure(this.config_.drm, () => this.isPreload_);
  521. const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
  522. this.manifest_.variants);
  523. let isLive = true;
  524. if (this.manifest_ && this.manifest_.presentationTimeline) {
  525. isLive = this.manifest_.presentationTimeline.isLive();
  526. }
  527. await this.drmEngine_.initForPlayback(
  528. playableVariants,
  529. this.manifest_.offlineSessionIds,
  530. isLive);
  531. this.throwIfDestroyed_();
  532. if (media) {
  533. await this.drmEngine_.attach(media);
  534. this.throwIfDestroyed_();
  535. }
  536. // Now that we have drm information, filter the manifest (again) so that
  537. // we can ensure we only use variants with the selected key system.
  538. const tracksChangedAfter = await this.manifestFilterer_.filterManifest(
  539. this.manifest_);
  540. if (tracksChangedAfter) {
  541. const event = this.makeEvent_(
  542. shaka.util.FakeEvent.EventName.TracksChanged);
  543. await Promise.resolve();
  544. this.dispatchEvent(event);
  545. }
  546. }
  547. /** @param {!shaka.extern.PlayerConfiguration} config */
  548. reconfigure(config) {
  549. this.config_ = config;
  550. }
  551. /**
  552. * @param {string} name
  553. * @param {*=} value
  554. */
  555. configure(name, value) {
  556. const config = shaka.util.ConfigUtils.convertToConfigObject(name, value);
  557. shaka.util.PlayerConfiguration.mergeConfigObjects(this.config_, config);
  558. }
  559. /**
  560. * Return a copy of the current configuration.
  561. *
  562. * @return {shaka.extern.PlayerConfiguration}
  563. */
  564. getConfiguration() {
  565. return shaka.util.ObjectUtils.cloneObject(this.config_);
  566. }
  567. /**
  568. * Performs a filtering of the manifest, and chooses the initial
  569. * variant.
  570. *
  571. * @return {!Promise}
  572. * @private
  573. */
  574. async chooseInitialVariant_() {
  575. // This step does not have any associated events, as it is only part of the
  576. // "load" state in the old state graph.
  577. if (!this.currentAdaptationSetCriteria_) {
  578. // Copy preferred languages from the config again, in case the config was
  579. // changed between construction and playback.
  580. this.currentAdaptationSetCriteria_ =
  581. this.config_.adaptationSetCriteriaFactory();
  582. this.currentAdaptationSetCriteria_.configure({
  583. language: this.config_.preferredAudioLanguage,
  584. role: this.config_.preferredVariantRole,
  585. channelCount: this.config_.preferredAudioChannelCount,
  586. hdrLevel: this.config_.preferredVideoHdrLevel,
  587. spatialAudio: this.config_.preferSpatialAudio,
  588. videoLayout: this.config_.preferredVideoLayout,
  589. audioLabel: this.config_.preferredAudioLabel,
  590. videoLabel: this.config_.preferredVideoLabel,
  591. codecSwitchingStrategy:
  592. this.config_.mediaSource.codecSwitchingStrategy,
  593. audioCodec: '',
  594. activeAudioCodec: '',
  595. activeAudioChannelCount: 0,
  596. preferredAudioCodecs: this.config_.preferredAudioCodecs,
  597. preferredAudioChannelCount: this.config_.preferredAudioChannelCount,
  598. });
  599. }
  600. // Make the ABR manager.
  601. if (this.allowMakeAbrManager_) {
  602. const abrFactory = this.config_.abrFactory;
  603. this.abrManagerFactory_ = abrFactory;
  604. this.abrManager_ = abrFactory();
  605. this.abrManager_.configure(this.config_.abr);
  606. }
  607. const variant = this.configureAbrManagerAndChooseVariant_();
  608. if (variant &&
  609. this.shouldCreateSegmentIndexBeforeDrmEngineInitialization_()) {
  610. const createSegmentIndexPromises = [];
  611. for (const stream of [variant.video, variant.audio]) {
  612. if (stream && !stream.segmentIndex) {
  613. createSegmentIndexPromises.push(stream.createSegmentIndex());
  614. }
  615. }
  616. if (createSegmentIndexPromises.length > 0) {
  617. await Promise.all(createSegmentIndexPromises);
  618. }
  619. }
  620. }
  621. /**
  622. * @return {boolean}
  623. * @private
  624. */
  625. shouldCreateSegmentIndexBeforeDrmEngineInitialization_() {
  626. goog.asserts.assert(
  627. this.manifest_, 'The manifest should already be parsed.');
  628. // If we only have one variant, it is useful to preload it, because it will
  629. // be the only one we can use.
  630. if (this.manifest_.variants.length == 1) {
  631. return true;
  632. }
  633. // In HLS, DRM information is usually included in the media playlist, so we
  634. // need to download the media playlist to get the real information.
  635. if (this.manifest_.type == shaka.media.ManifestParser.HLS) {
  636. return true;
  637. }
  638. return false;
  639. }
  640. /**
  641. * Prefetches segments.
  642. *
  643. * @return {!Promise}
  644. * @private
  645. */
  646. async prefetchInner_() {
  647. if (!this.prefetchedVariant_) {
  648. const variant = this.configureAbrManagerAndChooseVariant_();
  649. if (variant) {
  650. this.prefetchedVariant_ = variant;
  651. }
  652. }
  653. if (this.prefetchedVariant_) {
  654. const isLive = this.manifest_.presentationTimeline.isLive();
  655. const promises = [];
  656. const variant = this.prefetchedVariant_;
  657. if (variant.video) {
  658. promises.push(this.prefetchStream_(variant.video, isLive));
  659. }
  660. if (variant.audio) {
  661. promises.push(this.prefetchStream_(variant.audio, isLive));
  662. }
  663. const textStream = this.chooseTextStream_();
  664. if (textStream && shaka.util.StreamUtils.shouldInitiallyShowText(
  665. variant.audio, textStream, this.config_)) {
  666. promises.push(this.prefetchStream_(textStream, isLive));
  667. this.prefetchedTextStream_ = textStream;
  668. }
  669. await Promise.all(promises);
  670. }
  671. }
  672. /**
  673. * @return {shaka.extern.Variant}
  674. * @private
  675. */
  676. configureAbrManagerAndChooseVariant_() {
  677. goog.asserts.assert(this.currentAdaptationSetCriteria_,
  678. 'Must have an AdaptationSetCriteria');
  679. const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
  680. this.manifest_.variants);
  681. const adaptationSet = this.currentAdaptationSetCriteria_.create(
  682. playableVariants);
  683. // Guess what the first variant will be, based on a SimpleAbrManager.
  684. this.abrManager_.configure(this.config_.abr);
  685. this.abrManager_.setVariants(Array.from(adaptationSet.values()));
  686. return this.abrManager_.chooseVariant();
  687. }
  688. /**
  689. * @return {?shaka.extern.Stream}
  690. * @private
  691. */
  692. chooseTextStream_() {
  693. const subset = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
  694. this.manifest_.textStreams,
  695. this.config_.preferredTextLanguage,
  696. this.config_.preferredTextRole,
  697. this.config_.preferForcedSubs);
  698. return subset[0] || null;
  699. }
  700. /**
  701. * @param {!shaka.extern.Stream} stream
  702. * @param {boolean} isLive
  703. * @return {!Promise}
  704. * @private
  705. */
  706. async prefetchStream_(stream, isLive) {
  707. // Use the prefetch limit from the config if this is set, otherwise use 2.
  708. const prefetchLimit = this.config_.streaming.segmentPrefetchLimit || 2;
  709. const prefetch = new shaka.media.SegmentPrefetch(
  710. prefetchLimit, stream, (reference, stream, streamDataCallback) => {
  711. return shaka.media.StreamingEngine.dispatchFetch(
  712. reference, stream, streamDataCallback || null,
  713. this.config_.streaming.retryParameters, this.networkingEngine_,
  714. this.isPreload_);
  715. }, /* reverse= */ false);
  716. this.segmentPrefetchById_.set(stream.id, prefetch);
  717. // Start prefetching a bit.
  718. if (!stream.segmentIndex) {
  719. await stream.createSegmentIndex();
  720. }
  721. // Ignore if start time is a Date, as we do not prefetch segments for live
  722. // anyway.
  723. const startTime = typeof this.startTime_ === 'number' ? this.startTime_ : 0;
  724. const prefetchSegmentIterator =
  725. stream.segmentIndex.getIteratorForTime(startTime);
  726. let prefetchSegment = null;
  727. if (prefetchSegmentIterator) {
  728. prefetchSegment = prefetchSegmentIterator.current();
  729. if (!prefetchSegment) {
  730. prefetchSegment = prefetchSegmentIterator.next().value;
  731. }
  732. }
  733. if (!prefetchSegment) {
  734. // If we can't get a segment at the desired spot, at least get a segment,
  735. // so we can get the init segment.
  736. prefetchSegment = stream.segmentIndex.earliestReference();
  737. }
  738. if (prefetchSegment) {
  739. if (isLive) {
  740. // Preload only the init segment for Live
  741. if (prefetchSegment.initSegmentReference) {
  742. await prefetch.prefetchInitSegment(
  743. prefetchSegment.initSegmentReference);
  744. }
  745. } else {
  746. // Preload a segment, too... either the first segment, or the segment
  747. // that corresponds with this.startTime_, as appropriate.
  748. // Note: this method also preload the init segment
  749. await prefetch.prefetchSegmentsByTime(prefetchSegment.startTime);
  750. }
  751. }
  752. }
  753. /**
  754. * Waits for the loading to be finished (or to fail with an error).
  755. * @return {!Promise}
  756. * @export
  757. */
  758. waitForFinish() {
  759. return this.successPromise_;
  760. }
  761. /**
  762. * Waits for the manifest to be loaded (or to fail with an error).
  763. * @return {!Promise}
  764. */
  765. waitForManifest() {
  766. const promises = [
  767. this.manifestPromise_,
  768. this.successPromise_,
  769. ];
  770. return Promise.race(promises);
  771. }
  772. /**
  773. * Releases or stops all non-entrusted resources.
  774. *
  775. * @override
  776. * @export
  777. */
  778. async destroy() {
  779. this.destroyed_ = true;
  780. if (this.parser_ && !this.parserEntrusted_) {
  781. await this.parser_.stop();
  782. }
  783. if (this.abrManager_ && !this.abrManagerEntrusted_) {
  784. await this.abrManager_.stop();
  785. }
  786. if (this.regionTimeline_ && !this.regionTimelineEntrusted_) {
  787. this.regionTimeline_.release();
  788. }
  789. if (this.drmEngine_ && !this.drmEngineEntrusted_) {
  790. await this.drmEngine_.destroy();
  791. }
  792. if (this.segmentPrefetchById_.size > 0 && !this.segmentPrefetchEntrusted_) {
  793. for (const segmentPrefetch of this.segmentPrefetchById_.values()) {
  794. segmentPrefetch.clearAll();
  795. }
  796. }
  797. // this.eventHandoffTarget_ is not unset, so that events and errors fired
  798. // after the preload manager is destroyed will still be routed to the
  799. // player, if it was once linked up.
  800. }
  801. /** @return {boolean} */
  802. isDestroyed() {
  803. return this.destroyed_;
  804. }
  805. /** @return {boolean} */
  806. hasBeenAttached() {
  807. return this.hasBeenAttached_;
  808. }
  809. /**
  810. * Take a series of variants and ensure that they only contain one type of
  811. * variant. The different options are:
  812. * 1. Audio-Video
  813. * 2. Audio-Only
  814. * 3. Video-Only
  815. *
  816. * A manifest can only contain a single type because once we initialize media
  817. * source to expect specific streams, it must always have content for those
  818. * streams. If we were to start with audio+video and switch to an audio-only
  819. * variant, media source would block waiting for video content.
  820. *
  821. * @param {shaka.extern.Manifest} manifest
  822. * @private
  823. */
  824. static filterForAVVariants_(manifest) {
  825. const isAVVariant = (variant) => {
  826. // Audio-video variants may include both streams separately or may be
  827. // single multiplexed streams with multiple codecs.
  828. return (variant.video && variant.audio) ||
  829. (variant.video && variant.video.codecs.includes(','));
  830. };
  831. if (manifest.variants.some(isAVVariant)) {
  832. shaka.log.debug('Found variant with audio and video content, ' +
  833. 'so filtering out audio-only content.');
  834. manifest.variants = manifest.variants.filter(isAVVariant);
  835. }
  836. }
  837. };
  838. /**
  839. * @typedef {{
  840. * config: !shaka.extern.PlayerConfiguration,
  841. * manifestPlayerInterface: !shaka.extern.ManifestParser.PlayerInterface,
  842. * regionTimeline: !shaka.media.RegionTimeline<
  843. * shaka.extern.TimelineRegionInfo>,
  844. * qualityObserver: ?shaka.media.QualityObserver,
  845. * createDrmEngine: function():!shaka.drm.DrmEngine,
  846. * networkingEngine: !shaka.net.NetworkingEngine,
  847. * manifestFilterer: !shaka.media.ManifestFilterer,
  848. * allowPrefetch: boolean,
  849. * allowMakeAbrManager: boolean,
  850. * }}
  851. *
  852. * @property {!shaka.extern.PlayerConfiguration} config
  853. * @property {!shaka.extern.ManifestParser.PlayerInterface
  854. * } manifestPlayerInterface
  855. * @property {!shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>
  856. * } regionTimeline
  857. * @property {?shaka.media.QualityObserver} qualityObserver
  858. * @property {function():!shaka.drm.DrmEngine} createDrmEngine
  859. * @property {!shaka.net.NetworkingEngine} networkingEngine
  860. * @property {!shaka.media.ManifestFilterer} manifestFilterer
  861. * @property {boolean} allowPrefetch
  862. * @property {boolean} allowMakeAbrManager
  863. */
  864. shaka.media.PreloadManager.PlayerInterface;