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