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