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