Source: lib/ads/client_side_ad.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.ads.ClientSideAd');
  7. goog.require('shaka.util.EventManager');
  8. /**
  9. * @implements {shaka.extern.IAd}
  10. * @export
  11. */
  12. shaka.ads.ClientSideAd = class {
  13. /**
  14. * @param {!google.ima.Ad} imaAd
  15. * @param {!google.ima.AdsManager} imaAdManager
  16. * @param {HTMLMediaElement} video
  17. */
  18. constructor(imaAd, imaAdManager, video) {
  19. /** @private {google.ima.Ad} */
  20. this.ad_ = imaAd;
  21. /** @private {google.ima.AdsManager} */
  22. this.manager_ = imaAdManager;
  23. /** @private {HTMLMediaElement} */
  24. this.video_ = video;
  25. /** @private {boolean} */
  26. this.isPaused_ = false;
  27. /** @private {number} */
  28. this.volume_ = this.manager_.getVolume();
  29. /** @private {shaka.util.EventManager} */
  30. this.eventManager_ = new shaka.util.EventManager();
  31. this.eventManager_.listen(this.manager_,
  32. google.ima.AdEvent.Type.PAUSED, () => {
  33. this.isPaused_ = true;
  34. });
  35. this.eventManager_.listen(this.manager_,
  36. google.ima.AdEvent.Type.RESUMED, () => {
  37. this.isPaused_ = false;
  38. });
  39. }
  40. /**
  41. * @override
  42. * @export
  43. */
  44. needsSkipUI() {
  45. return false;
  46. }
  47. /**
  48. * @override
  49. * @export
  50. */
  51. isClientRendering() {
  52. return true;
  53. }
  54. /**
  55. * @override
  56. * @export
  57. */
  58. hasCustomClick() {
  59. return true;
  60. }
  61. /**
  62. * @override
  63. * @export
  64. */
  65. isUsingAnotherMediaElement() {
  66. return true;
  67. }
  68. /**
  69. * @override
  70. * @export
  71. */
  72. getDuration() {
  73. return this.ad_.getDuration();
  74. }
  75. /**
  76. * @override
  77. * @export
  78. */
  79. getMinSuggestedDuration() {
  80. return this.ad_.getMinSuggestedDuration();
  81. }
  82. /**
  83. * @override
  84. * @export
  85. */
  86. getRemainingTime() {
  87. return this.manager_.getRemainingTime();
  88. }
  89. /**
  90. * @override
  91. * @export
  92. */
  93. isPaused() {
  94. return this.isPaused_;
  95. }
  96. /**
  97. * @override
  98. * @export
  99. */
  100. isSkippable() {
  101. // IMA returns -1 for non-skippable ads. Any positive number is a genuine
  102. // skip offset, meaning the ad is skippable.
  103. return this.ad_.getSkipTimeOffset() >= 0;
  104. }
  105. /**
  106. * @override
  107. * @export
  108. */
  109. getTimeUntilSkippable() {
  110. const skipOffset = this.ad_.getSkipTimeOffset();
  111. const canSkipIn = this.getRemainingTime() - skipOffset;
  112. return Math.max(canSkipIn, 0);
  113. }
  114. /**
  115. * @override
  116. * @export
  117. */
  118. canSkipNow() {
  119. return this.manager_.getAdSkippableState();
  120. }
  121. /**
  122. * @override
  123. * @export
  124. */
  125. skip() {
  126. return this.manager_.skip();
  127. }
  128. /**
  129. * @param {boolean} paused
  130. */
  131. setPaused(paused) {
  132. this.isPaused_ = paused;
  133. }
  134. /**
  135. * @override
  136. * @export
  137. */
  138. pause() {
  139. return this.manager_.pause();
  140. }
  141. /**
  142. * @override
  143. * @export
  144. */
  145. play() {
  146. return this.manager_.resume();
  147. }
  148. /**
  149. * @override
  150. * @export
  151. */
  152. getVolume() {
  153. return this.manager_.getVolume();
  154. }
  155. /**
  156. * @override
  157. * @export
  158. */
  159. setVolume(volume) {
  160. this.video_.volume = volume;
  161. return this.manager_.setVolume(volume);
  162. }
  163. /**
  164. * @override
  165. * @export
  166. */
  167. isMuted() {
  168. return this.manager_.getVolume() == 0;
  169. }
  170. /**
  171. * @override
  172. * @export
  173. */
  174. isLinear() {
  175. return this.ad_.isLinear();
  176. }
  177. /**
  178. * @override
  179. * @export
  180. */
  181. resize(width, height) {
  182. let isInFullscreen = false;
  183. const video = /** @type {HTMLVideoElement} */(this.video_);
  184. if (document.fullscreenEnabled) {
  185. isInFullscreen = !!document.fullscreenElement;
  186. } else if (video.webkitSupportsFullscreen) {
  187. isInFullscreen = video.webkitDisplayingFullscreen;
  188. }
  189. const viewMode = isInFullscreen ?
  190. google.ima.ViewMode.FULLSCREEN : google.ima.ViewMode.NORMAL;
  191. this.manager_.resize(width, height, viewMode);
  192. }
  193. /**
  194. * @override
  195. * @export
  196. */
  197. setMuted(muted) {
  198. this.video_.muted = muted;
  199. // Emulate the "mute" functionality, where current, pre-mute
  200. // volume is saved and can be restored on unmute.
  201. if (muted) {
  202. this.volume_ = this.getVolume();
  203. this.manager_.setVolume(0);
  204. } else {
  205. this.manager_.setVolume(this.volume_);
  206. }
  207. }
  208. /**
  209. * It's required for a muted ad to start when autoplaying.
  210. *
  211. * @param {number} videoVolume
  212. */
  213. setInitialMuted(videoVolume) {
  214. this.volume_ = videoVolume;
  215. this.manager_.setVolume(0);
  216. }
  217. /**
  218. * @override
  219. * @export
  220. */
  221. getSequenceLength() {
  222. const podInfo = this.ad_.getAdPodInfo();
  223. if (podInfo == null) {
  224. // No pod, just one ad.
  225. return 1;
  226. }
  227. return podInfo.getTotalAds();
  228. }
  229. /**
  230. * @override
  231. * @export
  232. */
  233. getPositionInSequence() {
  234. const podInfo = this.ad_.getAdPodInfo();
  235. if (podInfo == null) {
  236. // No pod, just one ad.
  237. return 1;
  238. }
  239. return podInfo.getAdPosition();
  240. }
  241. /**
  242. * @override
  243. * @export
  244. */
  245. getTitle() {
  246. return this.ad_.getTitle();
  247. }
  248. /**
  249. * @override
  250. * @export
  251. */
  252. getDescription() {
  253. return this.ad_.getDescription();
  254. }
  255. /**
  256. * @override
  257. * @export
  258. */
  259. getVastMediaBitrate() {
  260. return this.ad_.getVastMediaBitrate();
  261. }
  262. /**
  263. * @override
  264. * @export
  265. */
  266. getVastMediaHeight() {
  267. return this.ad_.getVastMediaHeight();
  268. }
  269. /**
  270. * @override
  271. * @export
  272. */
  273. getVastMediaWidth() {
  274. return this.ad_.getVastMediaWidth();
  275. }
  276. /**
  277. * @override
  278. * @export
  279. */
  280. getAdId() {
  281. return this.ad_.getAdId();
  282. }
  283. /**
  284. * @override
  285. * @export
  286. */
  287. getCreativeAdId() {
  288. return this.ad_.getCreativeAdId();
  289. }
  290. /**
  291. * @override
  292. * @export
  293. */
  294. getAdvertiserName() {
  295. return this.ad_.getAdvertiserName();
  296. }
  297. /**
  298. * @override
  299. * @export
  300. */
  301. getMediaUrl() {
  302. return this.ad_.getMediaUrl();
  303. }
  304. /**
  305. * @override
  306. * @export
  307. */
  308. getTimeOffset() {
  309. const podInfo = this.ad_.getAdPodInfo();
  310. if (podInfo == null) {
  311. // Defaults to 0 if this ad is not part of a pod, or the pod is not part
  312. // of an ad playlist.
  313. return 0;
  314. }
  315. return podInfo.getTimeOffset();
  316. }
  317. /**
  318. * @override
  319. * @export
  320. */
  321. getPodIndex() {
  322. const podInfo = this.ad_.getAdPodInfo();
  323. if (podInfo == null) {
  324. // Defaults to 0 if this ad is not part of a pod, or the pod is not part
  325. // of an ad playlist.
  326. return 0;
  327. }
  328. return podInfo.getPodIndex();
  329. }
  330. /**
  331. * @override
  332. * @export
  333. */
  334. release() {
  335. this.ad_ = null;
  336. this.manager_ = null;
  337. }
  338. };