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} 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} */
  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. this.startTime_ += offset;
  172. }
  173. }
  174. /** @return {?number} */
  175. getStartTime() {
  176. return this.startTime_;
  177. }
  178. /** @return {number} */
  179. getStartTimeOfDRM() {
  180. return this.startTimeOfDrm_;
  181. }
  182. /** @return {?string} */
  183. getMimeType() {
  184. return this.mimeType_;
  185. }
  186. /** @return {string} */
  187. getAssetUri() {
  188. return this.assetUri_;
  189. }
  190. /** @return {?shaka.extern.Manifest} */
  191. getManifest() {
  192. return this.manifest_;
  193. }
  194. /** @return {?shaka.extern.ManifestParser.Factory} */
  195. getParserFactory() {
  196. return this.parserFactory_;
  197. }
  198. /** @return {?shaka.media.AdaptationSetCriteria} */
  199. getCurrentAdaptationSetCriteria() {
  200. return this.currentAdaptationSetCriteria_;
  201. }
  202. /** @return {?shaka.extern.AbrManager.Factory} */
  203. getAbrManagerFactory() {
  204. return this.abrManagerFactory_;
  205. }
  206. /**
  207. * Gets the abr manager, if it exists. Also marks that the abr manager should
  208. * not be stopped if this manager is destroyed.
  209. * @return {?shaka.extern.AbrManager}
  210. */
  211. receiveAbrManager() {
  212. this.abrManagerEntrusted_ = true;
  213. return this.abrManager_;
  214. }
  215. /**
  216. * @return {?shaka.extern.AbrManager}
  217. */
  218. getAbrManager() {
  219. return this.abrManager_;
  220. }
  221. /**
  222. * Gets the parser, if it exists. Also marks that the parser should not be
  223. * stopped if this manager is destroyed.
  224. * @return {?shaka.extern.ManifestParser}
  225. */
  226. receiveParser() {
  227. this.parserEntrusted_ = true;
  228. return this.parser_;
  229. }
  230. /**
  231. * @return {?shaka.extern.ManifestParser}
  232. */
  233. getParser() {
  234. return this.parser_;
  235. }
  236. /**
  237. * Gets the region timeline, if it exists. Also marks that the timeline should
  238. * not be released if this manager is destroyed.
  239. * @return {?shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>}
  240. */
  241. receiveRegionTimeline() {
  242. this.regionTimelineEntrusted_ = true;
  243. return this.regionTimeline_;
  244. }
  245. /**
  246. * @return {?shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>}
  247. */
  248. getRegionTimeline() {
  249. return this.regionTimeline_;
  250. }
  251. /** @return {?shaka.media.QualityObserver} */
  252. getQualityObserver() {
  253. return this.qualityObserver_;
  254. }
  255. /** @return {!shaka.util.Stats} */
  256. getStats() {
  257. return this.stats_;
  258. }
  259. /** @return {!shaka.media.ManifestFilterer} */
  260. getManifestFilterer() {
  261. return this.manifestFilterer_;
  262. }
  263. /**
  264. * Gets the drm engine, if it exists. Also marks that the drm engine should
  265. * not be destroyed if this manager is destroyed.
  266. * @return {?shaka.drm.DrmEngine}
  267. */
  268. receiveDrmEngine() {
  269. this.drmEngineEntrusted_ = true;
  270. return this.drmEngine_;
  271. }
  272. /**
  273. * @return {?shaka.drm.DrmEngine}
  274. */
  275. getDrmEngine() {
  276. return this.drmEngine_;
  277. }
  278. /**
  279. * @return {?shaka.extern.Variant}
  280. */
  281. getPrefetchedVariant() {
  282. return this.prefetchedVariant_;
  283. }
  284. /**
  285. * Gets the preloaded variant track if it exists.
  286. *
  287. * @return {?shaka.extern.Track}
  288. * @export
  289. */
  290. getPrefetchedVariantTrack() {
  291. if (!this.prefetchedVariant_) {
  292. return null;
  293. }
  294. return shaka.util.StreamUtils.variantToTrack(this.prefetchedVariant_);
  295. }
  296. /**
  297. * Gets the preloaded text track if it exists.
  298. *
  299. * @return {?shaka.extern.Track}
  300. * @export
  301. */
  302. getPrefetchedTextTrack() {
  303. if (!this.prefetchedTextStream_) {
  304. return null;
  305. }
  306. return shaka.util.StreamUtils.textStreamToTrack(this.prefetchedTextStream_);
  307. }
  308. /**
  309. * Gets the SegmentPrefetch objects for the initial stream ids. Also marks
  310. * that those objects should not be aborted if this manager is destroyed.
  311. * @return {!Map<number, shaka.media.SegmentPrefetch>}
  312. */
  313. receiveSegmentPrefetchesById() {
  314. this.segmentPrefetchEntrusted_ = true;
  315. return this.segmentPrefetchById_;
  316. }
  317. /**
  318. * @param {?shaka.extern.AbrManager} abrManager
  319. * @param {?shaka.extern.AbrManager.Factory} abrFactory
  320. */
  321. attachAbrManager(abrManager, abrFactory) {
  322. this.abrManager_ = abrManager;
  323. this.abrManagerFactory_ = abrFactory;
  324. }
  325. /**
  326. * @param {?shaka.media.AdaptationSetCriteria} adaptationSetCriteria
  327. */
  328. attachAdaptationSetCriteria(adaptationSetCriteria) {
  329. this.currentAdaptationSetCriteria_ = adaptationSetCriteria;
  330. }
  331. /**
  332. * @param {!shaka.extern.Manifest} manifest
  333. * @param {!shaka.extern.ManifestParser} parser
  334. * @param {!shaka.extern.ManifestParser.Factory} parserFactory
  335. */
  336. attachManifest(manifest, parser, parserFactory) {
  337. this.manifest_ = manifest;
  338. this.parser_ = parser;
  339. this.parserFactory_ = parserFactory;
  340. }
  341. /**
  342. * Starts the process of loading the asset.
  343. * Success or failure will be measured through waitForFinish()
  344. */
  345. start() {
  346. (async () => {
  347. // Force a context switch, to give the player a chance to hook up events
  348. // immediately if desired.
  349. await Promise.resolve();
  350. // Perform the preloading process.
  351. try {
  352. await this.parseManifestInner_();
  353. this.throwIfDestroyed_();
  354. if (!shaka.drm.DrmUtils.isMediaKeysPolyfilled('webkit')) {
  355. await this.initializeDrm();
  356. this.throwIfDestroyed_();
  357. }
  358. await this.chooseInitialVariantAndPrefetchInner_();
  359. this.throwIfDestroyed_();
  360. // We don't need the drm keys to load completely for the initial variant
  361. // to be chosen, but we won't mark the load as a success until it has
  362. // been loaded. So wait for it here, not inside initializeDrmInner_.
  363. if (this.allowPrefetch_ && this.drmEngine_) {
  364. await this.drmEngine_.waitForActiveRequests();
  365. this.throwIfDestroyed_();
  366. }
  367. this.successPromise_.resolve();
  368. } catch (error) {
  369. // Ignore OPERATION_ABORTED and OBJECT_DESTROYED errors.
  370. if (!(error instanceof shaka.util.Error) ||
  371. (error.code != shaka.util.Error.Code.OPERATION_ABORTED &&
  372. error.code != shaka.util.Error.Code.OBJECT_DESTROYED)) {
  373. this.successPromise_.reject(error);
  374. }
  375. }
  376. })();
  377. }
  378. /**
  379. * @param {!Event} event
  380. * @return {boolean}
  381. * @override
  382. */
  383. dispatchEvent(event) {
  384. if (this.eventHandoffTarget_) {
  385. return this.eventHandoffTarget_.dispatchEvent(event);
  386. } else {
  387. return super.dispatchEvent(event);
  388. }
  389. }
  390. /**
  391. * @param {!shaka.util.Error} error
  392. */
  393. onError(error) {
  394. if (error.severity === shaka.util.Error.Severity.CRITICAL) {
  395. // Cancel the loading process.
  396. this.successPromise_.reject(error);
  397. this.destroy();
  398. }
  399. const eventName = shaka.util.FakeEvent.EventName.Error;
  400. const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
  401. this.dispatchEvent(event);
  402. if (event.defaultPrevented) {
  403. error.handled = true;
  404. }
  405. }
  406. /**
  407. * Throw if destroyed, to interrupt processes with a recognizable error.
  408. *
  409. * @private
  410. */
  411. throwIfDestroyed_() {
  412. if (this.isDestroyed()) {
  413. throw new shaka.util.Error(
  414. shaka.util.Error.Severity.CRITICAL,
  415. shaka.util.Error.Category.PLAYER,
  416. shaka.util.Error.Code.OBJECT_DESTROYED);
  417. }
  418. }
  419. /**
  420. * Makes a fires an event corresponding to entering a state of the loading
  421. * process.
  422. * @param {string} nodeName
  423. * @private
  424. */
  425. makeStateChangeEvent_(nodeName) {
  426. this.dispatchEvent(new shaka.util.FakeEvent(
  427. /* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
  428. /* data= */ (new Map()).set('state', nodeName)));
  429. }
  430. /**
  431. * @param {!shaka.util.FakeEvent.EventName} name
  432. * @param {Map<string, Object>=} data
  433. * @return {!shaka.util.FakeEvent}
  434. * @private
  435. */
  436. makeEvent_(name, data) {
  437. return new shaka.util.FakeEvent(name, data);
  438. }
  439. /**
  440. * Pick and initialize a manifest parser, then have it download and parse the
  441. * manifest.
  442. *
  443. * @return {!Promise}
  444. * @private
  445. */
  446. async parseManifestInner_() {
  447. this.makeStateChangeEvent_('manifest-parser');
  448. if (!this.parser_) {
  449. // Create the parser that we will use to parse the manifest.
  450. this.parserFactory_ = shaka.media.ManifestParser.getFactory(
  451. this.assetUri_, this.mimeType_);
  452. goog.asserts.assert(this.parserFactory_, 'Must have manifest parser');
  453. this.parser_ = this.parserFactory_();
  454. this.parser_.configure(this.config_.manifest, () => this.isPreload_);
  455. }
  456. const startTime = Date.now() / 1000;
  457. this.makeStateChangeEvent_('manifest');
  458. if (!this.manifest_) {
  459. this.manifest_ = await this.parser_.start(
  460. this.assetUri_, this.manifestPlayerInterface_);
  461. if (this.manifest_.variants.length == 1) {
  462. const createSegmentIndexPromises = [];
  463. const variant = this.manifest_.variants[0];
  464. for (const stream of [variant.video, variant.audio]) {
  465. if (stream && !stream.segmentIndex) {
  466. createSegmentIndexPromises.push(stream.createSegmentIndex());
  467. }
  468. }
  469. if (createSegmentIndexPromises.length > 0) {
  470. await Promise.all(createSegmentIndexPromises);
  471. }
  472. }
  473. }
  474. this.manifestPromise_.resolve();
  475. // This event is fired after the manifest is parsed, but before any
  476. // filtering takes place.
  477. const event =
  478. this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
  479. // Delay event to ensure manifest has been properly propagated
  480. // to the player.
  481. await Promise.resolve();
  482. this.dispatchEvent(event);
  483. // We require all manifests to have at least one variant.
  484. if (this.manifest_.variants.length == 0) {
  485. throw new shaka.util.Error(
  486. shaka.util.Error.Severity.CRITICAL,
  487. shaka.util.Error.Category.MANIFEST,
  488. shaka.util.Error.Code.NO_VARIANTS);
  489. }
  490. // Make sure that all variants are either: audio-only, video-only, or
  491. // audio-video.
  492. shaka.media.PreloadManager.filterForAVVariants_(this.manifest_);
  493. const now = Date.now() / 1000;
  494. const delta = now - startTime;
  495. this.stats_.setManifestTime(delta);
  496. }
  497. /**
  498. * Initializes the DRM engine.
  499. * @param {?HTMLMediaElement=} media
  500. * @return {!Promise}
  501. */
  502. async initializeDrm(media) {
  503. if (!this.manifest_ || this.drmEngine_) {
  504. return;
  505. }
  506. this.makeStateChangeEvent_('drm-engine');
  507. this.startTimeOfDrm_ = Date.now() / 1000;
  508. this.drmEngine_ = this.createDrmEngine_();
  509. this.manifestFilterer_.setDrmEngine(this.drmEngine_);
  510. this.drmEngine_.configure(this.config_.drm, () => this.isPreload_);
  511. const tracksChangedInitial = this.manifestFilterer_.applyRestrictions(
  512. this.manifest_);
  513. if (tracksChangedInitial) {
  514. const event = this.makeEvent_(
  515. shaka.util.FakeEvent.EventName.TracksChanged);
  516. await Promise.resolve();
  517. this.throwIfDestroyed_();
  518. this.dispatchEvent(event);
  519. }
  520. const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
  521. this.manifest_.variants);
  522. let isLive = true;
  523. // In HLS we need to parse the media playlist to know if it is Live or not.
  524. // So at this point we don't know yet. By default we assume it is Live.
  525. if (this.manifest_ && this.manifest_.presentationTimeline &&
  526. this.manifest_.type != shaka.media.ManifestParser.HLS) {
  527. isLive = this.manifest_.presentationTimeline.isLive();
  528. }
  529. await this.drmEngine_.initForPlayback(
  530. playableVariants,
  531. this.manifest_.offlineSessionIds,
  532. isLive);
  533. this.throwIfDestroyed_();
  534. if (media) {
  535. await this.drmEngine_.attach(media);
  536. this.throwIfDestroyed_();
  537. }
  538. // Now that we have drm information, filter the manifest (again) so that
  539. // we can ensure we only use variants with the selected key system.
  540. const tracksChangedAfter = await this.manifestFilterer_.filterManifest(
  541. this.manifest_);
  542. if (tracksChangedAfter) {
  543. const event = this.makeEvent_(
  544. shaka.util.FakeEvent.EventName.TracksChanged);
  545. await Promise.resolve();
  546. this.dispatchEvent(event);
  547. }
  548. }
  549. /** @param {!shaka.extern.PlayerConfiguration} config */
  550. reconfigure(config) {
  551. this.config_ = config;
  552. }
  553. /**
  554. * @param {string} name
  555. * @param {*=} value
  556. */
  557. configure(name, value) {
  558. const config = shaka.util.ConfigUtils.convertToConfigObject(name, value);
  559. shaka.util.PlayerConfiguration.mergeConfigObjects(this.config_, config);
  560. }
  561. /**
  562. * Return a copy of the current configuration.
  563. *
  564. * @return {shaka.extern.PlayerConfiguration}
  565. */
  566. getConfiguration() {
  567. return shaka.util.ObjectUtils.cloneObject(this.config_);
  568. }
  569. /**
  570. * Performs a final filtering of the manifest, and chooses the initial
  571. * variant. Also prefetches segments.
  572. *
  573. * @return {!Promise}
  574. * @private
  575. */
  576. async chooseInitialVariantAndPrefetchInner_() {
  577. goog.asserts.assert(
  578. this.manifest_, 'The manifest should already be parsed.');
  579. // This step does not have any associated events, as it is only part of the
  580. // "load" state in the old state graph.
  581. if (!this.currentAdaptationSetCriteria_) {
  582. // Copy preferred languages from the config again, in case the config was
  583. // changed between construction and playback.
  584. this.currentAdaptationSetCriteria_ =
  585. this.config_.adaptationSetCriteriaFactory();
  586. this.currentAdaptationSetCriteria_.configure({
  587. language: this.config_.preferredAudioLanguage,
  588. role: this.config_.preferredVariantRole,
  589. channelCount: this.config_.preferredAudioChannelCount,
  590. hdrLevel: this.config_.preferredVideoHdrLevel,
  591. spatialAudio: this.config_.preferSpatialAudio,
  592. videoLayout: this.config_.preferredVideoLayout,
  593. audioLabel: this.config_.preferredAudioLabel,
  594. videoLabel: this.config_.preferredVideoLabel,
  595. codecSwitchingStrategy:
  596. this.config_.mediaSource.codecSwitchingStrategy,
  597. audioCodec: '',
  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. if (this.allowPrefetch_) {
  608. const isLive = this.manifest_.presentationTimeline.isLive();
  609. // Prefetch segments for the predicted first variant.
  610. // We start these here, but don't wait for them; it's okay to start the
  611. // full load process while the segments are being prefetched.
  612. const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
  613. this.manifest_.variants);
  614. const adaptationSet = this.currentAdaptationSetCriteria_.create(
  615. playableVariants);
  616. // Guess what the first variant will be, based on a SimpleAbrManager.
  617. this.abrManager_.configure(this.config_.abr);
  618. this.abrManager_.setVariants(Array.from(adaptationSet.values()));
  619. const variant = this.abrManager_.chooseVariant();
  620. if (variant) {
  621. const promises = [];
  622. this.prefetchedVariant_ = variant;
  623. if (variant.video) {
  624. promises.push(this.prefetchStream_(variant.video, isLive));
  625. }
  626. if (variant.audio) {
  627. promises.push(this.prefetchStream_(variant.audio, isLive));
  628. }
  629. const textStream = this.chooseTextStream_();
  630. if (textStream && shaka.util.StreamUtils.shouldInitiallyShowText(
  631. variant.audio, textStream, this.config_)) {
  632. promises.push(this.prefetchStream_(textStream, isLive));
  633. this.prefetchedTextStream_ = textStream;
  634. }
  635. await Promise.all(promises);
  636. }
  637. }
  638. }
  639. /**
  640. * @return {?shaka.extern.Stream}
  641. * @private
  642. */
  643. chooseTextStream_() {
  644. const subset = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
  645. this.manifest_.textStreams,
  646. this.config_.preferredTextLanguage,
  647. this.config_.preferredTextRole,
  648. this.config_.preferForcedSubs);
  649. return subset[0] || null;
  650. }
  651. /**
  652. * @param {!shaka.extern.Stream} stream
  653. * @param {boolean} isLive
  654. * @return {!Promise}
  655. * @private
  656. */
  657. async prefetchStream_(stream, isLive) {
  658. // Use the prefetch limit from the config if this is set, otherwise use 2.
  659. const prefetchLimit = this.config_.streaming.segmentPrefetchLimit || 2;
  660. const prefetch = new shaka.media.SegmentPrefetch(
  661. prefetchLimit, stream, (reference, stream, streamDataCallback) => {
  662. return shaka.media.StreamingEngine.dispatchFetch(
  663. reference, stream, streamDataCallback || null,
  664. this.config_.streaming.retryParameters, this.networkingEngine_,
  665. this.isPreload_);
  666. }, /* reverse= */ false);
  667. this.segmentPrefetchById_.set(stream.id, prefetch);
  668. // Start prefetching a bit.
  669. if (!stream.segmentIndex) {
  670. await stream.createSegmentIndex();
  671. }
  672. const startTime = this.startTime_ || 0;
  673. const prefetchSegmentIterator =
  674. stream.segmentIndex.getIteratorForTime(startTime);
  675. let prefetchSegment =
  676. prefetchSegmentIterator ? prefetchSegmentIterator.current() : null;
  677. if (!prefetchSegment) {
  678. // If we can't get a segment at the desired spot, at least get a segment,
  679. // so we can get the init segment.
  680. prefetchSegment = stream.segmentIndex.earliestReference();
  681. }
  682. if (prefetchSegment) {
  683. if (isLive) {
  684. // Preload only the init segment for Live
  685. if (prefetchSegment.initSegmentReference) {
  686. await prefetch.prefetchInitSegment(
  687. prefetchSegment.initSegmentReference);
  688. }
  689. } else {
  690. // Preload a segment, too... either the first segment, or the segment
  691. // that corresponds with this.startTime_, as appropriate.
  692. // Note: this method also preload the init segment
  693. await prefetch.prefetchSegmentsByTime(prefetchSegment.startTime);
  694. }
  695. }
  696. }
  697. /**
  698. * Waits for the loading to be finished (or to fail with an error).
  699. * @return {!Promise}
  700. * @export
  701. */
  702. waitForFinish() {
  703. return this.successPromise_;
  704. }
  705. /**
  706. * Waits for the manifest to be loaded (or to fail with an error).
  707. * @return {!Promise}
  708. */
  709. waitForManifest() {
  710. const promises = [
  711. this.manifestPromise_,
  712. this.successPromise_,
  713. ];
  714. return Promise.race(promises);
  715. }
  716. /**
  717. * Releases or stops all non-entrusted resources.
  718. *
  719. * @override
  720. * @export
  721. */
  722. async destroy() {
  723. this.destroyed_ = true;
  724. if (this.parser_ && !this.parserEntrusted_) {
  725. await this.parser_.stop();
  726. }
  727. if (this.abrManager_ && !this.abrManagerEntrusted_) {
  728. await this.abrManager_.stop();
  729. }
  730. if (this.regionTimeline_ && !this.regionTimelineEntrusted_) {
  731. this.regionTimeline_.release();
  732. }
  733. if (this.drmEngine_ && !this.drmEngineEntrusted_) {
  734. await this.drmEngine_.destroy();
  735. }
  736. if (this.segmentPrefetchById_.size > 0 && !this.segmentPrefetchEntrusted_) {
  737. for (const segmentPrefetch of this.segmentPrefetchById_.values()) {
  738. segmentPrefetch.clearAll();
  739. }
  740. }
  741. // this.eventHandoffTarget_ is not unset, so that events and errors fired
  742. // after the preload manager is destroyed will still be routed to the
  743. // player, if it was once linked up.
  744. }
  745. /** @return {boolean} */
  746. isDestroyed() {
  747. return this.destroyed_;
  748. }
  749. /** @return {boolean} */
  750. hasBeenAttached() {
  751. return this.hasBeenAttached_;
  752. }
  753. /**
  754. * Take a series of variants and ensure that they only contain one type of
  755. * variant. The different options are:
  756. * 1. Audio-Video
  757. * 2. Audio-Only
  758. * 3. Video-Only
  759. *
  760. * A manifest can only contain a single type because once we initialize media
  761. * source to expect specific streams, it must always have content for those
  762. * streams. If we were to start with audio+video and switch to an audio-only
  763. * variant, media source would block waiting for video content.
  764. *
  765. * @param {shaka.extern.Manifest} manifest
  766. * @private
  767. */
  768. static filterForAVVariants_(manifest) {
  769. const isAVVariant = (variant) => {
  770. // Audio-video variants may include both streams separately or may be
  771. // single multiplexed streams with multiple codecs.
  772. return (variant.video && variant.audio) ||
  773. (variant.video && variant.video.codecs.includes(','));
  774. };
  775. if (manifest.variants.some(isAVVariant)) {
  776. shaka.log.debug('Found variant with audio and video content, ' +
  777. 'so filtering out audio-only content.');
  778. manifest.variants = manifest.variants.filter(isAVVariant);
  779. }
  780. }
  781. };
  782. /**
  783. * @typedef {{
  784. * config: !shaka.extern.PlayerConfiguration,
  785. * manifestPlayerInterface: !shaka.extern.ManifestParser.PlayerInterface,
  786. * regionTimeline: !shaka.media.RegionTimeline<
  787. * shaka.extern.TimelineRegionInfo>,
  788. * qualityObserver: ?shaka.media.QualityObserver,
  789. * createDrmEngine: function():!shaka.drm.DrmEngine,
  790. * networkingEngine: !shaka.net.NetworkingEngine,
  791. * manifestFilterer: !shaka.media.ManifestFilterer,
  792. * allowPrefetch: boolean,
  793. * allowMakeAbrManager: boolean
  794. * }}
  795. *
  796. * @property {!shaka.extern.PlayerConfiguration} config
  797. * @property {!shaka.extern.ManifestParser.PlayerInterface
  798. * } manifestPlayerInterface
  799. * @property {!shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>
  800. * } regionTimeline
  801. * @property {?shaka.media.QualityObserver} qualityObserver
  802. * @property {function():!shaka.drm.DrmEngine} createDrmEngine
  803. * @property {!shaka.net.NetworkingEngine} networkingEngine
  804. * @property {!shaka.media.ManifestFilterer} manifestFilterer
  805. * @property {boolean} allowPrefetch
  806. * @property {boolean} allowMakeAbrManager
  807. */
  808. shaka.media.PreloadManager.PlayerInterface;