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.log');
  9. goog.require('shaka.util.Error');
  10. goog.require('shaka.media.ManifestFilterer');
  11. goog.require('shaka.media.ManifestParser');
  12. goog.require('shaka.util.PublicPromise');
  13. goog.require('shaka.media.PreferenceBasedCriteria');
  14. goog.require('shaka.util.Stats');
  15. goog.require('shaka.media.SegmentPrefetch');
  16. goog.require('shaka.util.IDestroyable');
  17. goog.require('shaka.net.NetworkingEngine');
  18. goog.require('shaka.media.AdaptationSetCriteria');
  19. goog.require('shaka.media.DrmEngine');
  20. goog.require('shaka.media.RegionTimeline');
  21. goog.require('shaka.media.QualityObserver');
  22. goog.require('shaka.util.StreamUtils');
  23. goog.require('shaka.media.StreamingEngine');
  24. goog.require('shaka.media.SegmentPrefetch');
  25. goog.require('shaka.util.ConfigUtils');
  26. goog.require('shaka.util.FakeEvent');
  27. goog.require('shaka.util.FakeEventTarget');
  28. goog.require('shaka.util.ObjectUtils');
  29. goog.require('shaka.util.PlayerConfiguration');
  30. /**
  31. * @implements {shaka.util.IDestroyable}
  32. * @export
  33. */
  34. shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
  35. /**
  36. * @param {string} assetUri
  37. * @param {?string} mimeType
  38. * @param {number} startTimeOfLoad
  39. * @param {?number} startTime
  40. * @param {*} playerInterface
  41. */
  42. constructor(assetUri, mimeType, startTimeOfLoad, startTime, playerInterface) {
  43. super();
  44. // Making the playerInterface a * and casting it to the right type allows
  45. // for the PlayerInterface for this class to not be exported.
  46. // Unfortunately, the constructor is exported by default.
  47. const typedPlayerInterface =
  48. /** @type {!shaka.media.PreloadManager.PlayerInterface} */ (
  49. playerInterface);
  50. /** @private {string} */
  51. this.assetUri_ = assetUri;
  52. /** @private {?string} */
  53. this.mimeType_ = mimeType;
  54. /** @private {!shaka.net.NetworkingEngine} */
  55. this.networkingEngine_ = typedPlayerInterface.networkingEngine;
  56. /** @private {?number} */
  57. this.startTime_ = startTime;
  58. /** @private {?shaka.media.AdaptationSetCriteria} */
  59. this.currentAdaptationSetCriteria_ = null;
  60. /** @private {number} */
  61. this.startTimeOfLoad_ = startTimeOfLoad;
  62. /** @private {number} */
  63. this.startTimeOfDrm_ = 0;
  64. /** @private {function():!shaka.media.DrmEngine} */
  65. this.createDrmEngine_ = typedPlayerInterface.createDrmEngine;
  66. /** @private {!shaka.media.ManifestFilterer} */
  67. this.manifestFilterer_ = typedPlayerInterface.manifestFilterer;
  68. /** @private {!shaka.extern.ManifestParser.PlayerInterface} */
  69. this.manifestPlayerInterface_ =
  70. typedPlayerInterface.manifestPlayerInterface;
  71. /** @private {!shaka.extern.PlayerConfiguration} */
  72. this.config_ = typedPlayerInterface.config;
  73. /** @private {?shaka.extern.Manifest} */
  74. this.manifest_ = null;
  75. /** @private {?shaka.extern.ManifestParser.Factory} */
  76. this.parserFactory_ = null;
  77. /** @private {?shaka.extern.ManifestParser} */
  78. this.parser_ = null;
  79. /** @private {boolean} */
  80. this.parserEntrusted_ = false;
  81. /** @private {!shaka.media.RegionTimeline} */
  82. this.regionTimeline_ = typedPlayerInterface.regionTimeline;
  83. /** @private {boolean} */
  84. this.regionTimelineEntrusted_ = false;
  85. /** @private {?shaka.media.DrmEngine} */
  86. this.drmEngine_ = null;
  87. /** @private {boolean} */
  88. this.drmEngineEntrusted_ = false;
  89. /** @private {?shaka.extern.AbrManager.Factory} */
  90. this.abrManagerFactory_ = null;
  91. /** @private {shaka.extern.AbrManager} */
  92. this.abrManager_ = null;
  93. /** @private {boolean} */
  94. this.abrManagerEntrusted_ = false;
  95. /** @private {!Map.<number, shaka.media.SegmentPrefetch>} */
  96. this.segmentPrefetchById_ = new Map();
  97. /** @private {boolean} */
  98. this.segmentPrefetchEntrusted_ = false;
  99. /** @private {?shaka.media.QualityObserver} */
  100. this.qualityObserver_ = typedPlayerInterface.qualityObserver;
  101. /** @private {!shaka.util.Stats} */
  102. this.stats_ = new shaka.util.Stats();
  103. /** @private {!shaka.util.PublicPromise} */
  104. this.successPromise_ = new shaka.util.PublicPromise();
  105. /** @private {?shaka.util.FakeEventTarget} */
  106. this.eventHandoffTarget_ = null;
  107. /** @private {boolean} */
  108. this.destroyed_ = false;
  109. /** @private {boolean} */
  110. this.allowPrefetch_ = typedPlayerInterface.allowPrefetch;
  111. /** @private {?shaka.extern.Variant} */
  112. this.prefetchedVariant_ = null;
  113. /** @private {boolean} */
  114. this.allowMakeAbrManager_ = typedPlayerInterface.allowMakeAbrManager;
  115. /** @private {boolean} */
  116. this.hasBeenAttached_ = false;
  117. /** @private {?Array.<function()>} */
  118. this.queuedOperations_ = [];
  119. /** @private {?Array.<function()>} */
  120. this.latePhaseQueuedOperations_ = [];
  121. }
  122. /**
  123. * @param {boolean} latePhase
  124. * @param {function()} callback
  125. */
  126. addQueuedOperation(latePhase, callback) {
  127. const queue =
  128. latePhase ? this.latePhaseQueuedOperations_ : this.queuedOperations_;
  129. if (queue) {
  130. queue.push(callback);
  131. } else {
  132. callback();
  133. }
  134. }
  135. /** Calls all late phase queued operations, and stops queueing them. */
  136. stopQueuingLatePhaseQueuedOperations() {
  137. if (this.latePhaseQueuedOperations_) {
  138. for (const callback of this.latePhaseQueuedOperations_) {
  139. callback();
  140. }
  141. }
  142. this.latePhaseQueuedOperations_ = null;
  143. }
  144. /** @param {!shaka.util.FakeEventTarget} eventHandoffTarget */
  145. setEventHandoffTarget(eventHandoffTarget) {
  146. this.eventHandoffTarget_ = eventHandoffTarget;
  147. this.hasBeenAttached_ = true;
  148. // Also call all queued operations, and stop queuing them in the future.
  149. if (this.queuedOperations_) {
  150. for (const callback of this.queuedOperations_) {
  151. callback();
  152. }
  153. }
  154. this.queuedOperations_ = null;
  155. }
  156. /** @return {?number} */
  157. getStartTime() {
  158. return this.startTime_;
  159. }
  160. /** @return {number} */
  161. getStartTimeOfLoad() {
  162. return this.startTimeOfLoad_;
  163. }
  164. /** @return {number} */
  165. getStartTimeOfDRM() {
  166. return this.startTimeOfDrm_;
  167. }
  168. /** @return {?string} */
  169. getMimeType() {
  170. return this.mimeType_;
  171. }
  172. /** @return {string} */
  173. getAssetUri() {
  174. return this.assetUri_;
  175. }
  176. /** @return {?shaka.extern.Manifest} */
  177. getManifest() {
  178. return this.manifest_;
  179. }
  180. /** @return {?shaka.extern.ManifestParser.Factory} */
  181. getParserFactory() {
  182. return this.parserFactory_;
  183. }
  184. /** @return {?shaka.media.AdaptationSetCriteria} */
  185. getCurrentAdaptationSetCriteria() {
  186. return this.currentAdaptationSetCriteria_;
  187. }
  188. /** @return {?shaka.extern.AbrManager.Factory} */
  189. getAbrManagerFactory() {
  190. return this.abrManagerFactory_;
  191. }
  192. /**
  193. * Gets the abr manager, if it exists. Also marks that the abr manager should
  194. * not be stopped if this manager is destroyed.
  195. * @return {?shaka.extern.AbrManager}
  196. */
  197. receiveAbrManager() {
  198. this.abrManagerEntrusted_ = true;
  199. return this.abrManager_;
  200. }
  201. /**
  202. * @return {?shaka.extern.AbrManager}
  203. */
  204. getAbrManager() {
  205. return this.abrManager_;
  206. }
  207. /**
  208. * Gets the parser, if it exists. Also marks that the parser should not be
  209. * stopped if this manager is destroyed.
  210. * @return {?shaka.extern.ManifestParser}
  211. */
  212. receiveParser() {
  213. this.parserEntrusted_ = true;
  214. return this.parser_;
  215. }
  216. /**
  217. * @return {?shaka.extern.ManifestParser}
  218. */
  219. getParser() {
  220. return this.parser_;
  221. }
  222. /**
  223. * Gets the region timeline, if it exists. Also marks that the timeline should
  224. * not be released if this manager is destroyed.
  225. * @return {?shaka.media.RegionTimeline}
  226. */
  227. receiveRegionTimeline() {
  228. this.regionTimelineEntrusted_ = true;
  229. return this.regionTimeline_;
  230. }
  231. /**
  232. * @return {?shaka.media.RegionTimeline}
  233. */
  234. getRegionTimeline() {
  235. return this.regionTimeline_;
  236. }
  237. /** @return {?shaka.media.QualityObserver} */
  238. getQualityObserver() {
  239. return this.qualityObserver_;
  240. }
  241. /** @return {!shaka.util.Stats} */
  242. getStats() {
  243. return this.stats_;
  244. }
  245. /** @return {!shaka.media.ManifestFilterer} */
  246. getManifestFilterer() {
  247. return this.manifestFilterer_;
  248. }
  249. /**
  250. * Gets the drm engine, if it exists. Also marks that the drm engine should
  251. * not be destroyed if this manager is destroyed.
  252. * @return {?shaka.media.DrmEngine}
  253. */
  254. receiveDrmEngine() {
  255. this.drmEngineEntrusted_ = true;
  256. return this.drmEngine_;
  257. }
  258. /**
  259. * @return {?shaka.media.DrmEngine}
  260. */
  261. getDrmEngine() {
  262. return this.drmEngine_;
  263. }
  264. /**
  265. * @return {?shaka.extern.Variant}
  266. */
  267. getPrefetchedVariant() {
  268. return this.prefetchedVariant_;
  269. }
  270. /**
  271. * Gets the SegmentPrefetch objects for the initial stream ids. Also marks
  272. * that those objects should not be aborted if this manager is destroyed.
  273. * @return {!Map.<number, shaka.media.SegmentPrefetch>}
  274. */
  275. receiveSegmentPrefetchesById() {
  276. this.segmentPrefetchEntrusted_ = true;
  277. return this.segmentPrefetchById_;
  278. }
  279. /**
  280. * @param {?shaka.extern.AbrManager} abrManager
  281. * @param {?shaka.extern.AbrManager.Factory} abrFactory
  282. */
  283. attachAbrManager(abrManager, abrFactory) {
  284. this.abrManager_ = abrManager;
  285. this.abrManagerFactory_ = abrFactory;
  286. }
  287. /**
  288. * @param {?shaka.media.AdaptationSetCriteria} adaptationSetCriteria
  289. */
  290. attachAdaptationSetCriteria(adaptationSetCriteria) {
  291. this.currentAdaptationSetCriteria_ = adaptationSetCriteria;
  292. }
  293. /**
  294. * @param {!shaka.extern.Manifest} manifest
  295. * @param {!shaka.extern.ManifestParser} parser
  296. * @param {!shaka.extern.ManifestParser.Factory} parserFactory
  297. */
  298. attachManifest(manifest, parser, parserFactory) {
  299. this.manifest_ = manifest;
  300. this.parser_ = parser;
  301. this.parserFactory_ = parserFactory;
  302. }
  303. /**
  304. * Starts the process of loading the asset.
  305. * Success or failure will be measured through waitForFinish()
  306. */
  307. start() {
  308. (async () => {
  309. // Force a context switch, to give the player a chance to hook up events
  310. // immediately if desired.
  311. await Promise.resolve();
  312. // Perform the preloading process.
  313. try {
  314. await this.parseManifestInner_();
  315. this.throwIfDestroyed_();
  316. await this.initializeDrmInner_();
  317. this.throwIfDestroyed_();
  318. await this.chooseInitialVariantInner_();
  319. this.throwIfDestroyed_();
  320. this.successPromise_.resolve();
  321. } catch (error) {
  322. this.successPromise_.reject(error);
  323. }
  324. })();
  325. }
  326. /**
  327. * @param {!Event} event
  328. * @return {boolean}
  329. * @override
  330. */
  331. dispatchEvent(event) {
  332. if (this.eventHandoffTarget_) {
  333. return this.eventHandoffTarget_.dispatchEvent(event);
  334. } else {
  335. return super.dispatchEvent(event);
  336. }
  337. }
  338. /**
  339. * @param {!shaka.util.Error} error
  340. */
  341. onError(error) {
  342. if (error.severity === shaka.util.Error.Severity.CRITICAL) {
  343. // Cancel the loading process.
  344. this.successPromise_.reject(error);
  345. this.destroy();
  346. }
  347. const eventName = shaka.util.FakeEvent.EventName.Error;
  348. const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
  349. this.dispatchEvent(event);
  350. if (event.defaultPrevented) {
  351. error.handled = true;
  352. }
  353. }
  354. /**
  355. * Throw if destroyed, to interrupt processes with a recognizable error.
  356. *
  357. * @private
  358. */
  359. throwIfDestroyed_() {
  360. if (this.isDestroyed()) {
  361. throw new shaka.util.Error(
  362. shaka.util.Error.Severity.CRITICAL,
  363. shaka.util.Error.Category.PLAYER,
  364. shaka.util.Error.Code.OBJECT_DESTROYED);
  365. }
  366. }
  367. /**
  368. * Makes a fires an event corresponding to entering a state of the loading
  369. * process.
  370. * @param {string} nodeName
  371. * @private
  372. */
  373. makeStateChangeEvent_(nodeName) {
  374. this.dispatchEvent(new shaka.util.FakeEvent(
  375. /* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
  376. /* data= */ (new Map()).set('state', nodeName)));
  377. }
  378. /**
  379. * @param {!shaka.util.FakeEvent.EventName} name
  380. * @param {Map.<string, Object>=} data
  381. * @return {!shaka.util.FakeEvent}
  382. * @private
  383. */
  384. makeEvent_(name, data) {
  385. return new shaka.util.FakeEvent(name, data);
  386. }
  387. /**
  388. * Pick and initialize a manifest parser, then have it download and parse the
  389. * manifest.
  390. *
  391. * @return {!Promise}
  392. * @private
  393. */
  394. async parseManifestInner_() {
  395. this.makeStateChangeEvent_('manifest-parser');
  396. if (!this.parser_) {
  397. // Create the parser that we will use to parse the manifest.
  398. this.parserFactory_ = shaka.media.ManifestParser.getFactory(
  399. this.assetUri_, this.mimeType_);
  400. goog.asserts.assert(this.parserFactory_, 'Must have manifest parser');
  401. this.parser_ = this.parserFactory_();
  402. this.parser_.configure(this.config_.manifest);
  403. }
  404. const startTime = Date.now() / 1000;
  405. this.makeStateChangeEvent_('manifest');
  406. if (!this.manifest_) {
  407. this.manifest_ = await this.parser_.start(
  408. this.assetUri_, this.manifestPlayerInterface_);
  409. }
  410. // This event is fired after the manifest is parsed, but before any
  411. // filtering takes place.
  412. const event =
  413. this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
  414. this.dispatchEvent(event);
  415. // We require all manifests to have at least one variant.
  416. if (this.manifest_.variants.length == 0) {
  417. throw new shaka.util.Error(
  418. shaka.util.Error.Severity.CRITICAL,
  419. shaka.util.Error.Category.MANIFEST,
  420. shaka.util.Error.Code.NO_VARIANTS);
  421. }
  422. // Make sure that all variants are either: audio-only, video-only, or
  423. // audio-video.
  424. shaka.media.PreloadManager.filterForAVVariants_(this.manifest_);
  425. const now = Date.now() / 1000;
  426. const delta = now - startTime;
  427. this.stats_.setManifestTime(delta);
  428. }
  429. /**
  430. * Initializes the DRM engine.
  431. *
  432. * @return {!Promise}
  433. * @private
  434. */
  435. async initializeDrmInner_() {
  436. goog.asserts.assert(
  437. this.manifest_, 'The manifest should already be parsed.');
  438. this.makeStateChangeEvent_('drm-engine');
  439. this.startTimeOfDrm_ = Date.now() / 1000;
  440. this.drmEngine_ = this.createDrmEngine_();
  441. this.manifestFilterer_.setDrmEngine(this.drmEngine_);
  442. this.drmEngine_.configure(this.config_.drm);
  443. const tracksChangedInitial = this.manifestFilterer_.applyRestrictions(
  444. this.manifest_);
  445. if (tracksChangedInitial) {
  446. const event = this.makeEvent_(
  447. shaka.util.FakeEvent.EventName.TracksChanged);
  448. await Promise.resolve();
  449. this.throwIfDestroyed_();
  450. this.dispatchEvent(event);
  451. }
  452. const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
  453. this.manifest_.variants);
  454. await this.drmEngine_.initForPlayback(
  455. playableVariants,
  456. this.manifest_.offlineSessionIds);
  457. this.throwIfDestroyed_();
  458. // Now that we have drm information, filter the manifest (again) so that
  459. // we can ensure we only use variants with the selected key system.
  460. const tracksChangedAfter = await this.manifestFilterer_.filterManifest(
  461. this.manifest_);
  462. if (tracksChangedAfter) {
  463. const event = this.makeEvent_(
  464. shaka.util.FakeEvent.EventName.TracksChanged);
  465. await Promise.resolve();
  466. this.dispatchEvent(event);
  467. }
  468. }
  469. /** @param {!shaka.extern.PlayerConfiguration} config */
  470. reconfigure(config) {
  471. this.config_ = config;
  472. }
  473. /**
  474. * @param {string} name
  475. * @param {*=} value
  476. */
  477. configure(name, value) {
  478. const config = shaka.util.ConfigUtils.convertToConfigObject(name, value);
  479. shaka.util.PlayerConfiguration.mergeConfigObjects(this.config_, config);
  480. }
  481. /**
  482. * Return a copy of the current configuration.
  483. *
  484. * @return {shaka.extern.PlayerConfiguration}
  485. */
  486. getConfiguration() {
  487. return shaka.util.ObjectUtils.cloneObject(this.config_);
  488. }
  489. /**
  490. * Performs a final filtering of the manifest, and chooses the initial
  491. * variant.
  492. *
  493. * @private
  494. */
  495. chooseInitialVariantInner_() {
  496. goog.asserts.assert(
  497. this.manifest_, 'The manifest should already be parsed.');
  498. // This step does not have any associated events, as it is only part of the
  499. // "load" state in the old state graph.
  500. if (!this.currentAdaptationSetCriteria_) {
  501. // Copy preferred languages from the config again, in case the config was
  502. // changed between construction and playback.
  503. this.currentAdaptationSetCriteria_ =
  504. new shaka.media.PreferenceBasedCriteria(
  505. this.config_.preferredAudioLanguage,
  506. this.config_.preferredVariantRole,
  507. this.config_.preferredAudioChannelCount,
  508. this.config_.preferredVideoHdrLevel,
  509. this.config_.preferSpatialAudio,
  510. this.config_.preferredVideoLayout,
  511. this.config_.preferredAudioLabel,
  512. this.config_.preferredVideoLabel,
  513. this.config_.mediaSource.codecSwitchingStrategy,
  514. this.config_.manifest.dash.enableAudioGroups);
  515. }
  516. // Make the ABR manager.
  517. if (this.allowMakeAbrManager_) {
  518. const abrFactory = this.config_.abrFactory;
  519. this.abrManagerFactory_ = abrFactory;
  520. this.abrManager_ = abrFactory();
  521. this.abrManager_.configure(this.config_.abr);
  522. }
  523. if (this.allowPrefetch_) {
  524. const isLive = this.manifest_.presentationTimeline.isLive();
  525. // Prefetch segments for the predicted first variant.
  526. // We start these here, but don't wait for them; it's okay to start the
  527. // full load process while the segments are being prefetched.
  528. const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
  529. this.manifest_.variants);
  530. const adaptationSet = this.currentAdaptationSetCriteria_.create(
  531. playableVariants);
  532. // Guess what the first variant will be, based on a SimpleAbrManager.
  533. this.abrManager_.configure(this.config_.abr);
  534. this.abrManager_.setVariants(Array.from(adaptationSet.values()));
  535. const variant =
  536. this.abrManager_.chooseVariant(/* preferFastSwitching= */ true);
  537. if (variant) {
  538. this.prefetchedVariant_ = variant;
  539. if (variant.video) {
  540. this.makePrefetchForStream_(variant.video, isLive);
  541. }
  542. if (variant.audio) {
  543. this.makePrefetchForStream_(variant.audio, isLive);
  544. }
  545. }
  546. }
  547. }
  548. /**
  549. * @param {!shaka.extern.Stream} stream
  550. * @param {boolean} isLive
  551. * @return {!Promise}
  552. * @private
  553. */
  554. async makePrefetchForStream_(stream, isLive) {
  555. // Use the prefetch limit from the config if this is set, otherwise use 2.
  556. const prefetchLimit = this.config_.streaming.segmentPrefetchLimit || 2;
  557. const prefetch = new shaka.media.SegmentPrefetch(
  558. prefetchLimit, stream, (reference, stream, streamDataCallback) => {
  559. return shaka.media.StreamingEngine.dispatchFetch(
  560. reference, stream, streamDataCallback || null,
  561. this.config_.streaming.retryParameters, this.networkingEngine_);
  562. });
  563. this.segmentPrefetchById_.set(stream.id, prefetch);
  564. // Start prefetching a bit.
  565. await stream.createSegmentIndex();
  566. const startTime = this.startTime_ || 0;
  567. const prefetchSegmentIterator =
  568. stream.segmentIndex.getIteratorForTime(startTime);
  569. let prefetchSegment =
  570. prefetchSegmentIterator ? prefetchSegmentIterator.current() : null;
  571. if (!prefetchSegment) {
  572. // If we can't get a segment at the desired spot, at least get a segment,
  573. // so we can get the init segment.
  574. prefetchSegment = stream.segmentIndex.get(0);
  575. }
  576. if (prefetchSegment) {
  577. if (isLive) {
  578. // Preload only the init segment for Live
  579. if (prefetchSegment.initSegmentReference) {
  580. prefetch.prefetchInitSegment(prefetchSegment.initSegmentReference);
  581. }
  582. } else {
  583. // Preload a segment, too... either the first segment, or the segment
  584. // that corresponds with this.startTime_, as appropriate.
  585. // Note: this method also preload the init segment
  586. prefetch.prefetchSegmentsByTime(prefetchSegment.startTime);
  587. }
  588. }
  589. }
  590. /**
  591. * Waits for the loading to be finished (or to fail with an error).
  592. * @return {!Promise}
  593. * @export
  594. */
  595. waitForFinish() {
  596. return this.successPromise_;
  597. }
  598. /**
  599. * Releases or stops all non-entrusted resources.
  600. *
  601. * @override
  602. * @export
  603. */
  604. async destroy() {
  605. this.destroyed_ = true;
  606. if (this.parser_ && !this.parserEntrusted_) {
  607. await this.parser_.stop();
  608. }
  609. if (this.abrManager_ && !this.abrManagerEntrusted_) {
  610. await this.abrManager_.stop();
  611. }
  612. if (this.regionTimeline_ && !this.regionTimelineEntrusted_) {
  613. this.regionTimeline_.release();
  614. }
  615. if (this.drmEngine_ && !this.drmEngineEntrusted_) {
  616. await this.drmEngine_.destroy();
  617. }
  618. if (this.segmentPrefetchById_.size > 0 && !this.segmentPrefetchEntrusted_) {
  619. for (const segmentPrefetch of this.segmentPrefetchById_.values()) {
  620. segmentPrefetch.clearAll();
  621. }
  622. }
  623. // this.eventHandoffTarget_ is not unset, so that events and errors fired
  624. // after the preload manager is destroyed will still be routed to the
  625. // player, if it was once linked up.
  626. }
  627. /** @return {boolean} */
  628. isDestroyed() {
  629. return this.destroyed_;
  630. }
  631. /** @return {boolean} */
  632. hasBeenAttached() {
  633. return this.hasBeenAttached_;
  634. }
  635. /**
  636. * Take a series of variants and ensure that they only contain one type of
  637. * variant. The different options are:
  638. * 1. Audio-Video
  639. * 2. Audio-Only
  640. * 3. Video-Only
  641. *
  642. * A manifest can only contain a single type because once we initialize media
  643. * source to expect specific streams, it must always have content for those
  644. * streams. If we were to start with audio+video and switch to an audio-only
  645. * variant, media source would block waiting for video content.
  646. *
  647. * @param {shaka.extern.Manifest} manifest
  648. * @private
  649. */
  650. static filterForAVVariants_(manifest) {
  651. const isAVVariant = (variant) => {
  652. // Audio-video variants may include both streams separately or may be
  653. // single multiplexed streams with multiple codecs.
  654. return (variant.video && variant.audio) ||
  655. (variant.video && variant.video.codecs.includes(','));
  656. };
  657. if (manifest.variants.some(isAVVariant)) {
  658. shaka.log.debug('Found variant with audio and video content, ' +
  659. 'so filtering out audio-only content.');
  660. manifest.variants = manifest.variants.filter(isAVVariant);
  661. }
  662. }
  663. };
  664. /**
  665. * @typedef {{
  666. * config: !shaka.extern.PlayerConfiguration,
  667. * manifestPlayerInterface: !shaka.extern.ManifestParser.PlayerInterface,
  668. * regionTimeline: !shaka.media.RegionTimeline,
  669. * qualityObserver: ?shaka.media.QualityObserver,
  670. * createDrmEngine: function():!shaka.media.DrmEngine,
  671. * networkingEngine: !shaka.net.NetworkingEngine,
  672. * manifestFilterer: !shaka.media.ManifestFilterer,
  673. * allowPrefetch: boolean,
  674. * allowMakeAbrManager: boolean
  675. * }}
  676. *
  677. * @property {!shaka.extern.PlayerConfiguration} config
  678. * @property {!shaka.extern.ManifestParser.PlayerInterface}
  679. * manifestPlayerInterface
  680. * @property {!shaka.media.RegionTimeline} regionTimeline
  681. * @property {?shaka.media.QualityObserver} qualityObserver
  682. * @property {function():!shaka.media.DrmEngine} createDrmEngine
  683. * @property {!shaka.net.NetworkingEngine} networkingEngine
  684. * @property {!shaka.media.ManifestFilterer} manifestFilterer
  685. * @property {boolean} allowPrefetch
  686. * @property {boolean} allowMakeAbrManager
  687. */
  688. shaka.media.PreloadManager.PlayerInterface;