Source: lib/ads/interstitial_ad_manager.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.ads.InterstitialAdManager');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.Player');
  9. goog.require('shaka.ads.InterstitialAd');
  10. goog.require('shaka.ads.Utils');
  11. goog.require('shaka.log');
  12. goog.require('shaka.media.PreloadManager');
  13. goog.require('shaka.net.NetworkingEngine');
  14. goog.require('shaka.util.Dom');
  15. goog.require('shaka.util.EventManager');
  16. goog.require('shaka.util.FakeEvent');
  17. goog.require('shaka.util.IReleasable');
  18. goog.require('shaka.util.Platform');
  19. goog.require('shaka.util.PublicPromise');
  20. goog.require('shaka.util.StringUtils');
  21. goog.require('shaka.util.Timer');
  22. /**
  23. * A class responsible for Interstitial ad interactions.
  24. *
  25. * @implements {shaka.util.IReleasable}
  26. */
  27. shaka.ads.InterstitialAdManager = class {
  28. /**
  29. * @param {HTMLElement} adContainer
  30. * @param {shaka.Player} basePlayer
  31. * @param {HTMLMediaElement} baseVideo
  32. * @param {function(!shaka.util.FakeEvent)} onEvent
  33. */
  34. constructor(adContainer, basePlayer, baseVideo, onEvent) {
  35. /** @private {HTMLElement} */
  36. this.adContainer_ = adContainer;
  37. /** @private {shaka.Player} */
  38. this.basePlayer_ = basePlayer;
  39. /** @private {HTMLMediaElement} */
  40. this.baseVideo_ = baseVideo;
  41. /** @private {?HTMLMediaElement} */
  42. this.adVideo_ = null;
  43. /** @private {boolean} */
  44. this.usingBaseVideo_ = true;
  45. /** @private {HTMLMediaElement} */
  46. this.video_ = this.baseVideo_;
  47. /** @private {function(!shaka.util.FakeEvent)} */
  48. this.onEvent_ = onEvent;
  49. /** @private {!Set.<string>} */
  50. this.interstitialIds_ = new Set();
  51. /** @private {!Set.<shaka.ads.InterstitialAdManager.Interstitial>} */
  52. this.interstitials_ = new Set();
  53. /**
  54. * @private {!Map.<shaka.ads.InterstitialAdManager.Interstitial,
  55. * Promise<?shaka.media.PreloadManager>>}
  56. */
  57. this.preloadManagerInterstitials_ = new Map();
  58. /** @private {shaka.Player} */
  59. this.player_ = new shaka.Player();
  60. this.updatePlayerConfig_();
  61. /** @private {shaka.util.EventManager} */
  62. this.eventManager_ = new shaka.util.EventManager();
  63. /** @private {shaka.util.EventManager} */
  64. this.adEventManager_ = new shaka.util.EventManager();
  65. /** @private {boolean} */
  66. this.playingAd_ = false;
  67. /** @private {?number} */
  68. this.lastTime_ = null;
  69. this.eventManager_.listen(this.baseVideo_, 'timeupdate', () => {
  70. if (this.playingAd_) {
  71. return;
  72. }
  73. const needPreRoll = this.lastTime_ == null || this.lastTime_ == 0;
  74. this.lastTime_ = this.baseVideo_.currentTime;
  75. const currentInterstitial = this.getCurrentInterstitial_(needPreRoll);
  76. if (currentInterstitial) {
  77. this.setupAd_(currentInterstitial, /* sequenceLength= */ 1,
  78. /* adPosition= */ 1, /* initialTime= */ Date.now());
  79. }
  80. });
  81. /** @private {shaka.util.Timer} */
  82. this.pollTimer_ = new shaka.util.Timer(async () => {
  83. if (this.interstitials_.size && !this.playingAd_ &&
  84. this.lastTime_ != null) {
  85. let cuepointsChanged = false;
  86. const interstitials = Array.from(this.interstitials_);
  87. const seekRange = this.basePlayer_.seekRange();
  88. for (const interstitial of interstitials) {
  89. const comparisonTime = interstitial.endTime || interstitial.startTime;
  90. if ((seekRange.start - comparisonTime) >= 1) {
  91. if (this.preloadManagerInterstitials_.has(interstitial)) {
  92. const preloadManager =
  93. // eslint-disable-next-line no-await-in-loop
  94. await this.preloadManagerInterstitials_.get(interstitial);
  95. if (preloadManager) {
  96. preloadManager.destroy();
  97. }
  98. this.preloadManagerInterstitials_.delete(interstitial);
  99. }
  100. const interstitialId = JSON.stringify(interstitial);
  101. if (this.interstitialIds_.has(interstitialId)) {
  102. this.interstitialIds_.delete(interstitialId);
  103. }
  104. this.interstitials_.delete(interstitial);
  105. cuepointsChanged = true;
  106. } else {
  107. const difference = interstitial.startTime - this.lastTime_;
  108. if (difference > 0 && difference <= 10) {
  109. if (!this.preloadManagerInterstitials_.has(interstitial)) {
  110. this.preloadManagerInterstitials_.set(
  111. interstitial, this.player_.preload(interstitial.uri));
  112. }
  113. }
  114. }
  115. }
  116. if (cuepointsChanged) {
  117. this.cuepointsChanged_();
  118. }
  119. }
  120. }).tickEvery(/* seconds= */ 1);
  121. }
  122. /**
  123. * Called by the AdManager to provide an updated configuration any time it
  124. * changes.
  125. *
  126. * @param {shaka.extern.AdsConfiguration} config
  127. */
  128. configure(config) {
  129. if (!this.adContainer_ ||
  130. this.usingBaseVideo_ != config.supportsMultipleMediaElements) {
  131. return;
  132. }
  133. this.usingBaseVideo_ = !config.supportsMultipleMediaElements;
  134. if (this.usingBaseVideo_) {
  135. this.video_ = this.baseVideo_;
  136. if (this.adVideo_) {
  137. if (this.adVideo_.parentElement) {
  138. this.adContainer_.removeChild(this.adVideo_);
  139. }
  140. this.adVideo_ = null;
  141. }
  142. } else {
  143. if (!this.adVideo_) {
  144. this.adVideo_ = this.createMediaElement_();
  145. }
  146. this.video_ = this.adVideo_;
  147. }
  148. }
  149. /**
  150. * Resets the Interstitial manager and removes any continuous polling.
  151. */
  152. stop() {
  153. if (this.adEventManager_) {
  154. this.adEventManager_.removeAll();
  155. }
  156. this.interstitialIds_.clear();
  157. this.interstitials_.clear();
  158. this.player_.destroyAllPreloads();
  159. this.preloadManagerInterstitials_.clear();
  160. this.player_.detach();
  161. this.playingAd_ = false;
  162. this.lastTime_ = null;
  163. }
  164. /** @override */
  165. release() {
  166. this.stop();
  167. if (this.eventManager_) {
  168. this.eventManager_.release();
  169. }
  170. if (this.adEventManager_) {
  171. this.adEventManager_.release();
  172. }
  173. if (this.adContainer_) {
  174. shaka.util.Dom.removeAllChildren(this.adContainer_);
  175. }
  176. if (this.pollTimer_) {
  177. this.pollTimer_.stop();
  178. this.pollTimer_ = null;
  179. }
  180. this.player_.destroy();
  181. }
  182. /**
  183. * @param {shaka.extern.Interstitial} interstitial
  184. */
  185. async addMetadata(interstitial) {
  186. if (this.basePlayer_.getLoadMode() == shaka.Player.LoadMode.SRC_EQUALS &&
  187. this.usingBaseVideo_) {
  188. shaka.log.alwaysWarn(
  189. 'Unsupported interstitial when using single media element',
  190. interstitial);
  191. return;
  192. }
  193. this.updatePlayerConfig_();
  194. let cuepointsChanged = false;
  195. const interstitialsAd = await this.getInterstitialsInfo_(interstitial);
  196. if (interstitialsAd.length) {
  197. for (const interstitialAd of interstitialsAd) {
  198. const interstitialId = JSON.stringify(interstitialAd);
  199. if (this.interstitialIds_.has(interstitialId)) {
  200. continue;
  201. }
  202. cuepointsChanged = true;
  203. this.interstitialIds_.add(interstitialId);
  204. this.interstitials_.add(interstitialAd);
  205. let shouldPreload = false;
  206. if (interstitial.pre && this.lastTime_ == null) {
  207. shouldPreload = true;
  208. } else if (interstitialAd.startTime == 0 && !interstitialAd.canJump) {
  209. shouldPreload = true;
  210. } else if (this.lastTime_ != null) {
  211. const difference = interstitial.startTime - this.lastTime_;
  212. if (difference > 0 && difference <= 10) {
  213. shouldPreload = true;
  214. }
  215. }
  216. if (shouldPreload) {
  217. if (!this.preloadManagerInterstitials_.has(interstitialAd)) {
  218. this.preloadManagerInterstitials_.set(
  219. interstitialAd, this.player_.preload(interstitialAd.uri));
  220. }
  221. }
  222. }
  223. } else {
  224. shaka.log.alwaysWarn('Unsupported interstitial', interstitial);
  225. }
  226. if (cuepointsChanged) {
  227. this.cuepointsChanged_();
  228. }
  229. }
  230. /**
  231. * @return {!HTMLMediaElement}
  232. * @private
  233. */
  234. createMediaElement_() {
  235. const video = /** @type {!HTMLMediaElement} */(
  236. document.createElement(this.baseVideo_.tagName));
  237. video.autoplay = true;
  238. video.style.position = 'absolute';
  239. video.style.top = '0';
  240. video.style.left = '0';
  241. video.style.width = '100%';
  242. video.style.height = '100%';
  243. video.style.backgroundColor = 'rgb(0, 0, 0)';
  244. video.setAttribute('playsinline', '');
  245. return video;
  246. }
  247. /**
  248. * @param {boolean} needPreRoll
  249. * @param {number=} numberToSkip
  250. * @return {?shaka.ads.InterstitialAdManager.Interstitial}
  251. * @private
  252. */
  253. getCurrentInterstitial_(needPreRoll, numberToSkip = 0) {
  254. let skipped = 0;
  255. let currentInterstitial = null;
  256. if (this.interstitials_.size && this.lastTime_ != null) {
  257. const isEnded = this.baseVideo_.ended;
  258. const interstitials = Array.from(this.interstitials_).sort((a, b) => {
  259. return b.startTime - a.startTime;
  260. });
  261. const roundDecimals = (number) => {
  262. return Math.round(number * 1000) / 1000;
  263. };
  264. for (const interstitial of interstitials) {
  265. let isValid = false;
  266. if (needPreRoll) {
  267. isValid = interstitial.pre;
  268. } else if (isEnded) {
  269. isValid = interstitial.post;
  270. } else if (!interstitial.pre && !interstitial.post) {
  271. const difference =
  272. this.lastTime_ - roundDecimals(interstitial.startTime);
  273. isValid = difference > 0 &&
  274. (difference <= 1 || !interstitial.canJump);
  275. }
  276. if (isValid) {
  277. if (skipped == numberToSkip) {
  278. currentInterstitial = interstitial;
  279. break;
  280. }
  281. skipped++;
  282. }
  283. }
  284. }
  285. return currentInterstitial;
  286. }
  287. /**
  288. * @param {shaka.ads.InterstitialAdManager.Interstitial} interstitial
  289. * @param {number} sequenceLength
  290. * @param {number} adPosition
  291. * @param {number} initialTime the clock time the ad started at
  292. * @param {number=} oncePlayed
  293. * @private
  294. */
  295. async setupAd_(interstitial, sequenceLength, adPosition, initialTime,
  296. oncePlayed = 0) {
  297. goog.asserts.assert(this.video_, 'Must have video');
  298. const startTime = Date.now();
  299. if (!this.video_.parentElement && this.adContainer_) {
  300. this.adContainer_.appendChild(this.video_);
  301. }
  302. if (adPosition == 1 && sequenceLength == 1) {
  303. sequenceLength = Array.from(this.interstitials_).filter((i) => {
  304. if (interstitial.pre) {
  305. return i.pre == interstitial.pre;
  306. } else if (interstitial.post) {
  307. return i.post == interstitial.post;
  308. }
  309. return Math.abs(i.startTime - interstitial.startTime) < 0.001;
  310. }).length;
  311. }
  312. if (interstitial.once || interstitial.pre) {
  313. oncePlayed++;
  314. this.interstitials_.delete(interstitial);
  315. this.cuepointsChanged_();
  316. }
  317. this.playingAd_ = true;
  318. if (this.usingBaseVideo_ && adPosition == 1) {
  319. this.onEvent_(new shaka.util.FakeEvent(
  320. shaka.ads.Utils.AD_CONTENT_PAUSE_REQUESTED,
  321. (new Map()).set('saveLivePosition', true)));
  322. const detachBasePlayerPromise = new shaka.util.PublicPromise();
  323. const checkState = async (e) => {
  324. if (e['state'] == 'detach') {
  325. if (shaka.util.Platform.isSmartTV()) {
  326. await new Promise(
  327. (resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
  328. }
  329. detachBasePlayerPromise.resolve();
  330. this.adEventManager_.unlisten(
  331. this.basePlayer_, 'onstatechange', checkState);
  332. }
  333. };
  334. this.adEventManager_.listen(
  335. this.basePlayer_, 'onstatechange', checkState);
  336. await detachBasePlayerPromise;
  337. }
  338. if (!this.usingBaseVideo_) {
  339. this.baseVideo_.pause();
  340. if (interstitial.resumeOffset != null &&
  341. interstitial.resumeOffset != 0) {
  342. this.baseVideo_.currentTime += interstitial.resumeOffset;
  343. }
  344. }
  345. let unloadingInterstitial = false;
  346. /** @type {?shaka.util.Timer} */
  347. let playoutLimitTimer = null;
  348. const updateBaseVideoTime = () => {
  349. if (!this.usingBaseVideo_) {
  350. if (interstitial.resumeOffset == null) {
  351. if (interstitial.timelineRange && interstitial.endTime &&
  352. interstitial.endTime != Infinity) {
  353. if (this.baseVideo_.currentTime != interstitial.endTime) {
  354. this.baseVideo_.currentTime = interstitial.endTime;
  355. }
  356. } else {
  357. const now = Date.now();
  358. this.baseVideo_.currentTime += (now - initialTime) / 1000;
  359. initialTime = now;
  360. }
  361. }
  362. }
  363. };
  364. const basicTask = async () => {
  365. updateBaseVideoTime();
  366. if (playoutLimitTimer) {
  367. playoutLimitTimer.stop();
  368. }
  369. goog.asserts.assert(typeof(oncePlayed) == 'number',
  370. 'Should be an number!');
  371. // Optimization to avoid returning to main content when there is another
  372. // interstitial below.
  373. const nextCurrentInterstitial = this.getCurrentInterstitial_(
  374. interstitial.pre, adPosition - oncePlayed);
  375. if (nextCurrentInterstitial) {
  376. this.onEvent_(
  377. new shaka.util.FakeEvent(shaka.ads.Utils.AD_STOPPED));
  378. this.adEventManager_.removeAll();
  379. this.setupAd_(nextCurrentInterstitial, sequenceLength,
  380. ++adPosition, initialTime, oncePlayed);
  381. } else {
  382. if (this.usingBaseVideo_) {
  383. await this.player_.detach();
  384. } else {
  385. await this.player_.unload();
  386. }
  387. if (this.usingBaseVideo_) {
  388. let offset = interstitial.resumeOffset;
  389. if (offset == null) {
  390. if (interstitial.timelineRange && interstitial.endTime &&
  391. interstitial.endTime != Infinity) {
  392. offset = interstitial.endTime - (this.lastTime_ || 0);
  393. } else {
  394. offset = (Date.now() - initialTime) / 1000;
  395. }
  396. }
  397. this.onEvent_(new shaka.util.FakeEvent(
  398. shaka.ads.Utils.AD_CONTENT_RESUME_REQUESTED,
  399. (new Map()).set('offset', offset)));
  400. }
  401. this.onEvent_(
  402. new shaka.util.FakeEvent(shaka.ads.Utils.AD_STOPPED));
  403. this.adEventManager_.removeAll();
  404. this.playingAd_ = false;
  405. if (!this.usingBaseVideo_) {
  406. updateBaseVideoTime();
  407. if (!this.baseVideo_.ended) {
  408. this.baseVideo_.play();
  409. }
  410. }
  411. }
  412. };
  413. const error = async (e) => {
  414. if (unloadingInterstitial) {
  415. return;
  416. }
  417. unloadingInterstitial = true;
  418. this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.AD_ERROR,
  419. (new Map()).set('originalEvent', e)));
  420. await basicTask();
  421. };
  422. const complete = async () => {
  423. if (unloadingInterstitial) {
  424. return;
  425. }
  426. unloadingInterstitial = true;
  427. await basicTask();
  428. this.onEvent_(
  429. new shaka.util.FakeEvent(shaka.ads.Utils.AD_COMPLETE));
  430. };
  431. const onSkip = async () => {
  432. if (unloadingInterstitial) {
  433. return;
  434. }
  435. unloadingInterstitial = true;
  436. this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.AD_SKIPPED));
  437. await basicTask();
  438. };
  439. const ad = new shaka.ads.InterstitialAd(this.video_,
  440. interstitial.isSkippable, onSkip, sequenceLength, adPosition);
  441. if (!this.usingBaseVideo_) {
  442. ad.setMuted(this.baseVideo_.muted);
  443. ad.setVolume(this.baseVideo_.volume);
  444. }
  445. this.onEvent_(
  446. new shaka.util.FakeEvent(shaka.ads.Utils.AD_STARTED,
  447. (new Map()).set('ad', ad)));
  448. if (ad.canSkipNow()) {
  449. this.onEvent_(new shaka.util.FakeEvent(
  450. shaka.ads.Utils.AD_SKIP_STATE_CHANGED));
  451. }
  452. this.adEventManager_.listenOnce(this.player_, 'error', error);
  453. this.adEventManager_.listenOnce(this.player_, 'firstquartile', () => {
  454. updateBaseVideoTime();
  455. this.onEvent_(
  456. new shaka.util.FakeEvent(shaka.ads.Utils.AD_FIRST_QUARTILE));
  457. });
  458. this.adEventManager_.listenOnce(this.player_, 'midpoint', () => {
  459. updateBaseVideoTime();
  460. this.onEvent_(
  461. new shaka.util.FakeEvent(shaka.ads.Utils.AD_MIDPOINT));
  462. });
  463. this.adEventManager_.listenOnce(this.player_, 'thirdquartile', () => {
  464. updateBaseVideoTime();
  465. this.onEvent_(
  466. new shaka.util.FakeEvent(shaka.ads.Utils.AD_THIRD_QUARTILE));
  467. });
  468. this.adEventManager_.listenOnce(this.player_, 'complete', complete);
  469. this.adEventManager_.listen(this.video_, 'play', () => {
  470. this.onEvent_(
  471. new shaka.util.FakeEvent(shaka.ads.Utils.AD_RESUMED));
  472. });
  473. this.adEventManager_.listen(this.video_, 'pause', () => {
  474. this.onEvent_(
  475. new shaka.util.FakeEvent(shaka.ads.Utils.AD_PAUSED));
  476. });
  477. this.adEventManager_.listen(this.video_, 'volumechange', () => {
  478. if (this.video_.muted) {
  479. this.onEvent_(
  480. new shaka.util.FakeEvent(shaka.ads.Utils.AD_MUTED));
  481. } else {
  482. this.onEvent_(
  483. new shaka.util.FakeEvent(shaka.ads.Utils.AD_VOLUME_CHANGED));
  484. }
  485. });
  486. try {
  487. if (interstitial.playoutLimit) {
  488. playoutLimitTimer = new shaka.util.Timer(() => {
  489. ad.skip();
  490. }).tickAfter(interstitial.playoutLimit);
  491. }
  492. this.updatePlayerConfig_();
  493. await this.player_.attach(this.video_);
  494. if (this.preloadManagerInterstitials_.has(interstitial)) {
  495. const preloadManager =
  496. await this.preloadManagerInterstitials_.get(interstitial);
  497. this.preloadManagerInterstitials_.delete(interstitial);
  498. if (preloadManager) {
  499. await this.player_.load(preloadManager);
  500. } else {
  501. await this.player_.load(interstitial.uri);
  502. }
  503. } else {
  504. await this.player_.load(interstitial.uri);
  505. }
  506. const loadTime = (Date.now() - startTime) / 1000;
  507. this.onEvent_(new shaka.util.FakeEvent(shaka.ads.Utils.ADS_LOADED,
  508. (new Map()).set('loadTime', loadTime)));
  509. if (this.usingBaseVideo_) {
  510. this.baseVideo_.play();
  511. }
  512. } catch (e) {
  513. error(e);
  514. }
  515. }
  516. /**
  517. * @param {shaka.extern.Interstitial} interstitial
  518. * @return {!Promise.<!Array.<shaka.ads.InterstitialAdManager.Interstitial>>}
  519. * @private
  520. */
  521. async getInterstitialsInfo_(interstitial) {
  522. const interstitialsAd = [];
  523. if (!interstitial) {
  524. return interstitialsAd;
  525. }
  526. const assetUri = interstitial.values.find((v) => v.key == 'X-ASSET-URI');
  527. const assetList = interstitial.values.find((v) => v.key == 'X-ASSET-LIST');
  528. if (!assetUri && !assetList) {
  529. return interstitialsAd;
  530. }
  531. const restrict = interstitial.values.find((v) => v.key == 'X-RESTRICT');
  532. let isSkippable = true;
  533. let canJump = true;
  534. if (restrict && restrict.data) {
  535. const data = /** @type {string} */(restrict.data);
  536. isSkippable = !data.includes('SKIP');
  537. canJump = !data.includes('JUMP');
  538. }
  539. let resumeOffset = null;
  540. const resume = interstitial.values.find((v) => v.key == 'X-RESUME-OFFSET');
  541. if (resume) {
  542. const resumeOffsetString = /** @type {string} */(resume.data);
  543. resumeOffset = parseFloat(resumeOffsetString);
  544. if (isNaN(resumeOffset)) {
  545. resumeOffset = null;
  546. }
  547. }
  548. let playoutLimit = null;
  549. const playout = interstitial.values.find((v) => v.key == 'X-PLAYOUT-LIMIT');
  550. if (playout) {
  551. const playoutLimitString = /** @type {string} */(playout.data);
  552. playoutLimit = parseFloat(playoutLimitString);
  553. if (isNaN(playoutLimit)) {
  554. playoutLimit = null;
  555. }
  556. }
  557. let once = false;
  558. let pre = false;
  559. let post = false;
  560. const cue = interstitial.values.find((v) => v.key == 'CUE');
  561. if (cue) {
  562. const data = /** @type {string} */(cue.data);
  563. once = data.includes('ONCE');
  564. pre = data.includes('PRE');
  565. post = data.includes('POST');
  566. }
  567. let timelineRange = false;
  568. const timelineOccupies =
  569. interstitial.values.find((v) => v.key == 'X-TIMELINE-OCCUPIES');
  570. if (timelineOccupies) {
  571. const data = /** @type {string} */(timelineOccupies.data);
  572. timelineRange = data.includes('RANGE');
  573. } else if (!resume && this.basePlayer_.isLive()) {
  574. timelineRange = !pre && !post;
  575. }
  576. if (assetUri) {
  577. const uri = /** @type {string} */(assetUri.data);
  578. if (!uri) {
  579. return interstitialsAd;
  580. }
  581. interstitialsAd.push({
  582. startTime: interstitial.startTime,
  583. endTime: interstitial.endTime,
  584. uri,
  585. isSkippable,
  586. canJump,
  587. resumeOffset,
  588. playoutLimit,
  589. once,
  590. pre,
  591. post,
  592. timelineRange,
  593. });
  594. } else if (assetList) {
  595. const uri = /** @type {string} */(assetList.data);
  596. if (!uri) {
  597. return interstitialsAd;
  598. }
  599. const type = shaka.net.NetworkingEngine.RequestType.ADS;
  600. const request = shaka.net.NetworkingEngine.makeRequest(
  601. [uri],
  602. shaka.net.NetworkingEngine.defaultRetryParameters());
  603. const op = this.basePlayer_.getNetworkingEngine().request(type, request);
  604. try {
  605. const response = await op.promise;
  606. const data = shaka.util.StringUtils.fromUTF8(response.data);
  607. const dataAsJson =
  608. /** @type {!shaka.ads.InterstitialAdManager.AssetsList} */ (
  609. JSON.parse(data));
  610. for (const asset of dataAsJson['ASSETS']) {
  611. if (asset['URI']) {
  612. interstitialsAd.push({
  613. startTime: interstitial.startTime,
  614. endTime: interstitial.endTime,
  615. uri: asset['URI'],
  616. isSkippable,
  617. canJump,
  618. resumeOffset,
  619. playoutLimit,
  620. once,
  621. pre,
  622. post,
  623. timelineRange,
  624. });
  625. }
  626. }
  627. } catch (e) {
  628. // Ignore errors
  629. }
  630. }
  631. return interstitialsAd;
  632. }
  633. /**
  634. * @private
  635. */
  636. cuepointsChanged_() {
  637. /** @type {!Array.<!shaka.extern.AdCuePoint>} */
  638. const cuePoints = [];
  639. for (const interstitial of this.interstitials_) {
  640. /** @type {shaka.extern.AdCuePoint} */
  641. const shakaCuePoint = {
  642. start: interstitial.startTime,
  643. end: null,
  644. };
  645. if (interstitial.pre) {
  646. shakaCuePoint.start = 0;
  647. shakaCuePoint.end = null;
  648. } else if (interstitial.post) {
  649. shakaCuePoint.start = -1;
  650. shakaCuePoint.end = null;
  651. } else if (interstitial.timelineRange) {
  652. shakaCuePoint.end = interstitial.endTime;
  653. }
  654. const isValid = !cuePoints.find((c) => {
  655. return shakaCuePoint.start == c.start && shakaCuePoint.end == c.end;
  656. });
  657. if (isValid) {
  658. cuePoints.push(shakaCuePoint);
  659. }
  660. }
  661. this.onEvent_(new shaka.util.FakeEvent(
  662. shaka.ads.Utils.CUEPOINTS_CHANGED,
  663. (new Map()).set('cuepoints', cuePoints)));
  664. }
  665. /**
  666. * @private
  667. */
  668. updatePlayerConfig_() {
  669. goog.asserts.assert(this.player_, 'Must have player');
  670. goog.asserts.assert(this.basePlayer_, 'Must have base player');
  671. this.player_.configure(this.basePlayer_.getNonDefaultConfiguration());
  672. const netEngine = this.player_.getNetworkingEngine();
  673. goog.asserts.assert(netEngine, 'Need networking engine');
  674. netEngine.clearAllRequestFilters();
  675. netEngine.clearAllResponseFilters();
  676. this.basePlayer_.getNetworkingEngine().copyFiltersInto(netEngine);
  677. }
  678. };
  679. /**
  680. * @typedef {{
  681. * ASSETS: !Array.<shaka.ads.InterstitialAdManager.Asset>
  682. * }}
  683. *
  684. * @property {!Array.<shaka.ads.InterstitialAdManager.Asset>} ASSETS
  685. */
  686. shaka.ads.InterstitialAdManager.AssetsList;
  687. /**
  688. * @typedef {{
  689. * URI: string
  690. * }}
  691. *
  692. * @property {string} URI
  693. */
  694. shaka.ads.InterstitialAdManager.Asset;
  695. /**
  696. * @typedef {{
  697. * startTime: number,
  698. * endTime: ?number,
  699. * uri: string,
  700. * isSkippable: boolean,
  701. * canJump: boolean,
  702. * resumeOffset: ?number,
  703. * playoutLimit: ?number,
  704. * once: boolean,
  705. * pre: boolean,
  706. * post: boolean,
  707. * timelineRange: boolean
  708. * }}
  709. *
  710. * @property {number} startTime
  711. * @property {?number} endTime
  712. * @property {string} uri
  713. * @property {boolean} isSkippable
  714. * @property {boolean} canJump
  715. * @property {?number} resumeOffset
  716. * @property {?number} playoutLimit
  717. * @property {boolean} once
  718. * @property {boolean} pre
  719. * @property {boolean} post
  720. * @property {boolean} timelineRange
  721. */
  722. shaka.ads.InterstitialAdManager.Interstitial;