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