cloudinary-video-player 1.6.2-edge.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/.eslintignore +4 -0
  2. package/.snyk +19 -0
  3. package/.travis.yml +8 -0
  4. package/CHANGELOG.md +329 -0
  5. package/LICENSE +21 -0
  6. package/README.md +87 -0
  7. package/dist/cld-video-player.css +2110 -0
  8. package/dist/cld-video-player.js +5249 -0
  9. package/dist/cld-video-player.light.css +1766 -0
  10. package/dist/cld-video-player.light.js +1399 -0
  11. package/dist/cld-video-player.light.min.css +1 -0
  12. package/dist/cld-video-player.light.min.js +2 -0
  13. package/dist/cld-video-player.light.min.js.LICENSE.txt +23 -0
  14. package/dist/cld-video-player.min.css +1 -0
  15. package/dist/cld-video-player.min.js +2 -0
  16. package/dist/cld-video-player.min.js.LICENSE.txt +26 -0
  17. package/dist/fonts/cloudinary_icon_for_black_bg.svg +69 -0
  18. package/dist/fonts/cloudinary_icon_for_white_bg.svg +69 -0
  19. package/docs/360.html +102 -0
  20. package/docs/_template.html +93 -0
  21. package/docs/adaptive-streaming.html +297 -0
  22. package/docs/analytics.html +140 -0
  23. package/docs/api.html +302 -0
  24. package/docs/audio.html +136 -0
  25. package/docs/autoplay-fallback.html +138 -0
  26. package/docs/autoplay-on-scroll.html +107 -0
  27. package/docs/codec-fallback.html +158 -0
  28. package/docs/colors.html +135 -0
  29. package/docs/components.html +284 -0
  30. package/docs/custom-cld-errors.html +134 -0
  31. package/docs/floating-player.html +98 -0
  32. package/docs/fluid.html +117 -0
  33. package/docs/force-hls-subtitles-ios.html +159 -0
  34. package/docs/index.html +83 -0
  35. package/docs/interaction-area.html +398 -0
  36. package/docs/live-customer.html +128 -0
  37. package/docs/multiple-players.html +125 -0
  38. package/docs/playlist-by-tag-cap.html +182 -0
  39. package/docs/playlist-by-tag.html +133 -0
  40. package/docs/playlist.html +133 -0
  41. package/docs/poster.html +155 -0
  42. package/docs/raw-url.html +104 -0
  43. package/docs/recommendations.html +155 -0
  44. package/docs/scripts.js +156 -0
  45. package/docs/seek-thumbs.html +90 -0
  46. package/docs/shoppable.html +335 -0
  47. package/docs/subtitles-and-captions.html +267 -0
  48. package/docs/transformations.html +171 -0
  49. package/docs/ui-config.html +108 -0
  50. package/docs/vast-vpaid.html +149 -0
  51. package/env.example.js +6 -0
  52. package/env.js +6 -0
  53. package/jest-puppeteer.config.js +14 -0
  54. package/jest.config.js +196 -0
  55. package/package.json +99 -0
  56. package/sandbox.config.json +3 -0
  57. package/setupJest.js +1 -0
  58. package/src/assets/fonts/VideoJS.svg +120 -0
  59. package/src/assets/fonts/VideoJS.ttf +0 -0
  60. package/src/assets/fonts/VideoJS.woff +0 -0
  61. package/src/assets/fonts/icons.json +120 -0
  62. package/src/assets/icons/cloudinary_icon_for_black_bg.svg +69 -0
  63. package/src/assets/icons/cloudinary_icon_for_white_bg.svg +69 -0
  64. package/src/assets/icons/cloudinary_logo_for_dark_bg.svg +188 -0
  65. package/src/assets/icons/cloudinary_logo_for_white_bg.svg +188 -0
  66. package/src/assets/icons/info-circle.svg +17 -0
  67. package/src/assets/styles/ads-label.scss +16 -0
  68. package/src/assets/styles/components/interaction-areas.scss +158 -0
  69. package/src/assets/styles/components/playlist.scss +213 -0
  70. package/src/assets/styles/components/themedButton.scss +48 -0
  71. package/src/assets/styles/components/thumbnail.scss +94 -0
  72. package/src/assets/styles/components/title-bar.scss +67 -0
  73. package/src/assets/styles/components/triangle-volume-bar.scss +52 -0
  74. package/src/assets/styles/icons.scss +257 -0
  75. package/src/assets/styles/main.scss +324 -0
  76. package/src/assets/styles/mixins/aspect-ratio.scss +16 -0
  77. package/src/assets/styles/mixins/disable-transition.scss +3 -0
  78. package/src/assets/styles/mixins/mixins.scss +5 -0
  79. package/src/assets/styles/mixins/skin.scss +64 -0
  80. package/src/assets/styles/variables.scss +2 -0
  81. package/src/assets/styles/videojs-ima.scss +252 -0
  82. package/src/components/component-utils.js +20 -0
  83. package/src/components/index.js +21 -0
  84. package/src/components/interaction-area/interaction-area.const.js +30 -0
  85. package/src/components/interaction-area/interaction-area.service.js +223 -0
  86. package/src/components/interaction-area/interaction-area.utils.js +236 -0
  87. package/src/components/jumpButtons/jump-10-minus.js +21 -0
  88. package/src/components/jumpButtons/jump-10-plus.js +20 -0
  89. package/src/components/logoButton/logo-button.const.js +3 -0
  90. package/src/components/logoButton/logo-button.js +30 -0
  91. package/src/components/logoButton/logo-button.scss +15 -0
  92. package/src/components/playlist/components/playlist-button.js +34 -0
  93. package/src/components/playlist/components/playlist-next-button.js +18 -0
  94. package/src/components/playlist/components/playlist-previous-button.js +18 -0
  95. package/src/components/playlist/components/playlist.js +5 -0
  96. package/src/components/playlist/components/playlist.scss +15 -0
  97. package/src/components/playlist/components/upcoming-video-overlay.js +149 -0
  98. package/src/components/playlist/components/upcoming-video-overlay.scss +86 -0
  99. package/src/components/playlist/layout/playlist-layout-custom.js +21 -0
  100. package/src/components/playlist/layout/playlist-layout-horizontal.js +16 -0
  101. package/src/components/playlist/layout/playlist-layout-vertical.js +19 -0
  102. package/src/components/playlist/layout/playlist-layout.js +110 -0
  103. package/src/components/playlist/panel/playlist-panel-item.js +86 -0
  104. package/src/components/playlist/panel/playlist-panel.js +92 -0
  105. package/src/components/playlist/playlist-widget.js +119 -0
  106. package/src/components/playlist/playlist.const.js +14 -0
  107. package/src/components/playlist/playlist.js +413 -0
  108. package/src/components/playlist/thumbnail/thumbnail.js +69 -0
  109. package/src/components/progress-control-events-blocker/progress-control-events-blocker.js +17 -0
  110. package/src/components/qualitySelector/quality-selector.scss +10 -0
  111. package/src/components/qualitySelector/qualitySelector.js +152 -0
  112. package/src/components/recommendations-overlay/index.js +3 -0
  113. package/src/components/recommendations-overlay/recommendations-overlay-content.js +57 -0
  114. package/src/components/recommendations-overlay/recommendations-overlay-hide-button.js +18 -0
  115. package/src/components/recommendations-overlay/recommendations-overlay-item.js +35 -0
  116. package/src/components/recommendations-overlay/recommendations-overlay-primary-item.js +81 -0
  117. package/src/components/recommendations-overlay/recommendations-overlay-secondary-item.js +48 -0
  118. package/src/components/recommendations-overlay/recommendations-overlay-secondary-items-container.js +35 -0
  119. package/src/components/recommendations-overlay/recommendations-overlay.js +94 -0
  120. package/src/components/recommendations-overlay/recommendations-overlay.scss +182 -0
  121. package/src/components/shoppable-bar/layout/bar-layout.js +111 -0
  122. package/src/components/shoppable-bar/layout/shoppable-panel-toggle.js +64 -0
  123. package/src/components/shoppable-bar/layout/shoppable-products-overlay.js +87 -0
  124. package/src/components/shoppable-bar/panel/shoppable-panel-item.js +105 -0
  125. package/src/components/shoppable-bar/panel/shoppable-panel.js +172 -0
  126. package/src/components/shoppable-bar/shoppable-post-widget.js +110 -0
  127. package/src/components/shoppable-bar/shoppable-widget.const.js +52 -0
  128. package/src/components/shoppable-bar/shoppable-widget.js +111 -0
  129. package/src/components/shoppable-bar/shoppable-widget.scss +359 -0
  130. package/src/components/themeButton/themedButton.const.js +3 -0
  131. package/src/components/themeButton/themedButton.js +25 -0
  132. package/src/components/title-bar/title-bar.js +79 -0
  133. package/src/config/defaults.js +25 -0
  134. package/src/extended-events.js +228 -0
  135. package/src/index.js +18 -0
  136. package/src/mixins/eventable.js +54 -0
  137. package/src/mixins/playlistable.js +106 -0
  138. package/src/plugins/analytics/index.js +245 -0
  139. package/src/plugins/autoplay-on-scroll/index.js +86 -0
  140. package/src/plugins/cloudinary/common.js +216 -0
  141. package/src/plugins/cloudinary/event-handler-registry.js +46 -0
  142. package/src/plugins/cloudinary/index.js +345 -0
  143. package/src/plugins/cloudinary/models/audio-source/audio-source.const.js +11 -0
  144. package/src/plugins/cloudinary/models/audio-source/audio-source.js +82 -0
  145. package/src/plugins/cloudinary/models/base-source.js +107 -0
  146. package/src/plugins/cloudinary/models/image-source.js +26 -0
  147. package/src/plugins/cloudinary/models/video-source/video-source.const.js +32 -0
  148. package/src/plugins/cloudinary/models/video-source/video-source.js +239 -0
  149. package/src/plugins/cloudinary/models/video-source/video-source.utils.js +57 -0
  150. package/src/plugins/colors/index.js +303 -0
  151. package/src/plugins/context-menu/components/context-menu-item.js +12 -0
  152. package/src/plugins/context-menu/components/context-menu.js +63 -0
  153. package/src/plugins/context-menu/context-menu.scss +30 -0
  154. package/src/plugins/context-menu/contextMenuContent.js +53 -0
  155. package/src/plugins/context-menu/index.js +134 -0
  156. package/src/plugins/dash/index.js +26 -0
  157. package/src/plugins/dash/setup-audio-tracks.js +112 -0
  158. package/src/plugins/dash/setup-text-tracks.js +195 -0
  159. package/src/plugins/dash/videojs-dash.js +372 -0
  160. package/src/plugins/floating-player/floating-player.scss +74 -0
  161. package/src/plugins/floating-player/index.js +129 -0
  162. package/src/plugins/ima/index.js +1775 -0
  163. package/src/plugins/index.js +31 -0
  164. package/src/plugins/interactive-plugin/index.js +10 -0
  165. package/src/plugins/videojs-http-source-selector/components/SourceMenuButton.js +98 -0
  166. package/src/plugins/videojs-http-source-selector/components/SourceMenuItem.js +52 -0
  167. package/src/plugins/videojs-http-source-selector/plugin.js +82 -0
  168. package/src/plugins/videojs-http-source-selector/plugin.scss +9 -0
  169. package/src/plugins/vtt-thumbnails/index.js +526 -0
  170. package/src/plugins/vtt-thumbnails/vtt-thumbnails.scss +29 -0
  171. package/src/utils/api.js +32 -0
  172. package/src/utils/apply-with-props.js +32 -0
  173. package/src/utils/array.js +22 -0
  174. package/src/utils/assign.js +27 -0
  175. package/src/utils/attributes-normalizer.js +72 -0
  176. package/src/utils/cloudinary.js +165 -0
  177. package/src/utils/css-prefix.js +43 -0
  178. package/src/utils/dom.js +74 -0
  179. package/src/utils/find.js +28 -0
  180. package/src/utils/fontFace.js +25 -0
  181. package/src/utils/groupBy.js +12 -0
  182. package/src/utils/index.js +29 -0
  183. package/src/utils/matches.js +11 -0
  184. package/src/utils/mixin.js +5 -0
  185. package/src/utils/object.js +26 -0
  186. package/src/utils/playButton.js +9 -0
  187. package/src/utils/positioning.js +78 -0
  188. package/src/utils/querystring.js +12 -0
  189. package/src/utils/slicing.js +21 -0
  190. package/src/utils/string.js +15 -0
  191. package/src/utils/throttle.js +30 -0
  192. package/src/utils/time.js +77 -0
  193. package/src/utils/type-inference.js +35 -0
  194. package/src/validators/validators-functions.js +48 -0
  195. package/src/validators/validators-types.js +78 -0
  196. package/src/validators/validators.js +110 -0
  197. package/src/video-player.const.js +68 -0
  198. package/src/video-player.js +761 -0
  199. package/src/video-player.utils.js +123 -0
  200. package/test/adaptive-streaming.test.js +38 -0
  201. package/test/ads.test.js +35 -0
  202. package/test/analytics.test.js +111 -0
  203. package/test/api.test.js +111 -0
  204. package/test/autoplay.scroll.test.js +23 -0
  205. package/test/basic-ui.test.js +59 -0
  206. package/test/colors.test.js +58 -0
  207. package/test/components.test.js +21 -0
  208. package/test/custom-error.test.js +24 -0
  209. package/test/fluid.test.js +36 -0
  210. package/test/isValidConfig.test.js +224 -0
  211. package/test/mocks/cloudinary-core-mock.js +0 -0
  212. package/test/mocks/styleMock.js +1 -0
  213. package/test/multiplayer.test.js +25 -0
  214. package/test/playlist.test.js +60 -0
  215. package/test/puppeteer/vp-env.js +19 -0
  216. package/test/recommendations.test.js +38 -0
  217. package/test/title-bar.test.js +28 -0
  218. package/test/ui-conf.test.js +49 -0
  219. package/test/unit/cloudinaryConfig.test.js +22 -0
  220. package/test/unit/cloudinaryUtils.test.js +53 -0
  221. package/test/unit/utils.test.js +27 -0
  222. package/test/unit/videoSource.test.js +454 -0
  223. package/tsconfig.json +15 -0
  224. package/types/video-player-tests.js +12 -0
  225. package/types/video-player-tests.ts +31 -0
  226. package/types/video-player.d.ts +570 -0
@@ -0,0 +1,1775 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Copyright 2014 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ *
17
+ * IMA SDK integration plugin for Video.js. For more information see
18
+ * https://www.github.com/googleads/videojs-ima
19
+ */
20
+
21
+ (function(factory) {
22
+ if (typeof define === 'function' && define['amd']) {
23
+ define(['video.js', 'videojs-contrib-ads'], function(videojs){ factory(window, document, (videojs.default || videojs)) });
24
+ } else if (typeof exports === 'object' && typeof module === 'object') {
25
+ var vjs = require('video.js');
26
+ require('videojs-contrib-ads');
27
+ factory(window, document, vjs);
28
+ } else {
29
+ factory(window, document, videojs);
30
+ }
31
+ })(function(window, document, videojs) {
32
+ "use strict";
33
+
34
+ var extend = function(obj) {
35
+ var arg;
36
+ var index;
37
+ var key;
38
+ for (index = 1; index < arguments.length; index++) {
39
+ arg = arguments[index];
40
+ for (key in arg) {
41
+ if (arg.hasOwnProperty(key)) {
42
+ obj[key] = arg[key];
43
+ }
44
+ }
45
+ }
46
+ return obj;
47
+ };
48
+
49
+ var ima_defaults = {
50
+ debug: false,
51
+ timeout: 5000,
52
+ prerollTimeout: 100,
53
+ adLabel: 'Advertisement',
54
+ showControlsForAds: true,
55
+ showControlsForJSAds: true,
56
+ adsRenderingSettings: {
57
+ uiElements: [],
58
+ },
59
+ };
60
+
61
+ var eventTypes = (videojs.browser.IS_ANDROID || videojs.browser.IS_IOS) ? {
62
+ click: 'touchend',
63
+ mousedown: 'touchstart',
64
+ mouseup: 'touchend',
65
+ mousemove: 'touchmove'
66
+ } : {
67
+ click: 'click',
68
+ mousedown: 'mousedown',
69
+ mouseup: 'mouseup',
70
+ mousemove: 'mousemove'
71
+ };
72
+
73
+ var init = function(options, readyCallback) {
74
+ this.ima = new ImaPlugin(this, options, readyCallback);
75
+ };
76
+
77
+ var ImaPlugin = function(player, options, readyCallback) {
78
+ this.player = player;
79
+
80
+ /**
81
+ * Assigns the unique id and class names to the given element as well as the style class
82
+ * @param element
83
+ * @param controlName
84
+ * @private
85
+ */
86
+ var assignControlAttributes_ = function(element, controlName) {
87
+ element.id = this.controlPrefix + controlName;
88
+ element.className = this.controlPrefix + controlName + ' ' + controlName;
89
+ }.bind(this);
90
+
91
+ /**
92
+ * Returns a regular expression to test a string for the given className
93
+ * @param className
94
+ * @returns {RegExp}
95
+ * @private
96
+ */
97
+ var getClassRegexp_ = function(className){
98
+ // Matches on
99
+ // (beginning of string OR NOT word char)
100
+ // classname
101
+ // (negative lookahead word char OR end of string)
102
+ return new RegExp('(^|[^A-Za-z-])' + className + '((?![A-Za-z-])|$)', 'gi');
103
+ };
104
+
105
+ /**
106
+ * Adds a class to the given element if it doesn't already have the class
107
+ * @param element
108
+ * @param classToAdd
109
+ * @private
110
+ */
111
+ var addClass_ = function(element, classToAdd){
112
+ if(getClassRegexp_(classToAdd).test(element.className)){
113
+ return element;
114
+ }
115
+
116
+ return element.className = element.className.trim() + ' ' + classToAdd;
117
+ };
118
+
119
+ /**
120
+ * Removes a class from the given element if it has the given class
121
+ * @param element
122
+ * @param classToRemove
123
+ * @private
124
+ */
125
+ var removeClass_ = function(element, classToRemove){
126
+ var classRegexp = getClassRegexp_(classToRemove);
127
+
128
+ if(!classRegexp.test(element.className)){
129
+ return element;
130
+ }
131
+
132
+ return element.className = element.className.trim().replace(classRegexp, '');
133
+ };
134
+
135
+ /**
136
+ * Creates the ad container passed to the IMA SDK.
137
+ * @private
138
+ */
139
+ var createAdContainer_ = function() {
140
+ // The adContainerDiv is the DOM of the element that will house
141
+ // the ads and ad controls.
142
+ this.vjsControls = this.player.getChild('controlBar');
143
+ this.adContainerDiv =
144
+ this.vjsControls.el().parentNode.appendChild(
145
+ document.createElement('div'));
146
+ assignControlAttributes_(this.adContainerDiv, 'ima-ad-container');
147
+ this.adContainerDiv.style.position = "absolute";
148
+ this.adContainerDiv.addEventListener(
149
+ 'mouseenter',
150
+ showAdControls_,
151
+ false);
152
+ this.adContainerDiv.addEventListener(
153
+ 'mouseleave',
154
+ hideAdControls_,
155
+ false);
156
+ createControls_();
157
+ this.adDisplayContainer =
158
+ new google.ima.AdDisplayContainer(this.adContainerDiv, this.contentPlayer);
159
+ this.showAdContainer(!this.settings.manual);
160
+ }.bind(this);
161
+
162
+ /**
163
+ * Creates the controls for the ad.
164
+ * @private
165
+ */
166
+ var createControls_ = function() {
167
+ this.controlsDiv = document.createElement('div');
168
+ assignControlAttributes_(this.controlsDiv, 'ima-controls-div');
169
+ this.controlsDiv.style.width = '100%';
170
+ this.countdownDiv = document.createElement('div');
171
+ assignControlAttributes_(this.countdownDiv, 'ima-countdown-div');
172
+ this.countdownDiv.innerHTML = this.settings.adLabel;
173
+ this.countdownDiv.style.display = this.showCountdown ? '' : 'none';
174
+ this.seekBarDiv = document.createElement('div');
175
+ assignControlAttributes_(this.seekBarDiv, 'ima-seek-bar-div');
176
+ this.seekBarDiv.style.width = '100%';
177
+ this.progressDiv = document.createElement('div');
178
+ assignControlAttributes_(this.progressDiv, 'ima-progress-div');
179
+ this.playPauseDiv = document.createElement('div');
180
+ assignControlAttributes_(this.playPauseDiv, 'ima-play-pause-div');
181
+ addClass_(this.playPauseDiv, 'ima-playing');
182
+ this.playPauseDiv.addEventListener(
183
+ eventTypes.click,
184
+ onAdPlayPauseClick_,
185
+ false);
186
+ this.muteDiv = document.createElement('div');
187
+ assignControlAttributes_(this.muteDiv, 'ima-mute-div');
188
+ addClass_(this.muteDiv, 'ima-non-muted');
189
+ this.muteDiv.addEventListener(
190
+ eventTypes.click,
191
+ onAdMuteClick_,
192
+ false);
193
+ this.sliderDiv = document.createElement('div');
194
+ assignControlAttributes_(this.sliderDiv, 'ima-slider-div');
195
+ this.sliderDiv.addEventListener(
196
+ eventTypes.mousedown,
197
+ onAdVolumeSliderMouseDown_,
198
+ false);
199
+ this.sliderLevelDiv = document.createElement('div');
200
+ assignControlAttributes_(this.sliderLevelDiv, 'ima-slider-level-div');
201
+ this.fullscreenDiv = document.createElement('div');
202
+ assignControlAttributes_(this.fullscreenDiv, 'ima-fullscreen-div');
203
+ addClass_(this.fullscreenDiv, 'ima-non-fullscreen');
204
+ this.fullscreenDiv.addEventListener(
205
+ eventTypes.click,
206
+ onAdFullscreenClick_,
207
+ false);
208
+ this.adContainerDiv.appendChild(this.controlsDiv);
209
+ this.controlsDiv.appendChild(this.seekBarDiv);
210
+ this.controlsDiv.appendChild(this.playPauseDiv);
211
+ this.controlsDiv.appendChild(this.muteDiv);
212
+ this.controlsDiv.appendChild(this.sliderDiv);
213
+ this.controlsDiv.appendChild(this.fullscreenDiv);
214
+ this.seekBarDiv.appendChild(this.progressDiv);
215
+ this.sliderDiv.appendChild(this.sliderLevelDiv);
216
+ if (this.settings.vjsControls) {
217
+ this.initVjsControls();
218
+ this.controlsDiv.style.display = 'none';
219
+ this.vjsControls.el().appendChild(this.countdownDiv);
220
+ } else {
221
+ this.controlsDiv.appendChild(this.countdownDiv);
222
+ }
223
+ }.bind(this);
224
+
225
+ this.showAdContainer = function(show) {
226
+ this.adContainerDiv.style.display = show ? 'block' : 'none';
227
+ this.player.toggleClass('vjs-ima-ad', show);
228
+ }.bind(this);
229
+
230
+ /**
231
+ * Initializes the AdDisplayContainer. On mobile, this must be done as a
232
+ * result of user action.
233
+ */
234
+ this.initializeAdDisplayContainer = function() {
235
+ this.adDisplayContainerInitialized = true;
236
+ this.adDisplayContainer.initialize();
237
+ }.bind(this);
238
+
239
+ /**
240
+ * Creates the AdsRequest and request ads through the AdsLoader.
241
+ */
242
+ this.requestAds = function() {
243
+ if (!this.adDisplayContainerInitialized) {
244
+ this.adDisplayContainer.initialize();
245
+ }
246
+ var adsRequest = new google.ima.AdsRequest();
247
+ if (this.settings.adTagUrl) {
248
+ adsRequest.adTagUrl = this.settings.adTagUrl;
249
+ } else {
250
+ adsRequest.adsResponse = this.settings.adsResponse;
251
+ }
252
+ if (this.settings.forceNonLinearFullSlot) {
253
+ adsRequest.forceNonLinearFullSlot = true;
254
+ }
255
+
256
+ adsRequest.linearAdSlotWidth = this.getPlayerWidth();
257
+ adsRequest.linearAdSlotHeight = this.getPlayerHeight();
258
+ adsRequest.nonLinearAdSlotWidth =
259
+ this.settings.nonLinearWidth || this.getPlayerWidth();
260
+ adsRequest.nonLinearAdSlotHeight =
261
+ this.settings.nonLinearHeight || (this.getPlayerHeight() / 3);
262
+ adsRequest.vastLoadTimeout = Math.max(this.settings.prerollTimeout,
263
+ this.settings.postrollTimeout);
264
+
265
+ this.adsLoader.requestAds(adsRequest);
266
+ }.bind(this);
267
+
268
+ /**
269
+ * Listener for the ADS_MANAGER_LOADED event. Creates the AdsManager,
270
+ * sets up event listeners, and triggers the 'adsready' event for
271
+ * videojs-ads-contrib.
272
+ * @private
273
+ */
274
+ var onAdsManagerLoaded_ = function(adsManagerLoadedEvent) {
275
+ this.adsManager = adsManagerLoadedEvent.getAdsManager(
276
+ this.contentPlayheadTracker, this.adsRenderingSettings);
277
+
278
+ this.adsManager.addEventListener(
279
+ google.ima.AdErrorEvent.Type.AD_ERROR,
280
+ onAdError_);
281
+ this.adsManager.addEventListener(
282
+ google.ima.AdEvent.Type.AD_BREAK_READY,
283
+ onAdBreakReady_);
284
+ this.adsManager.addEventListener(
285
+ google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED,
286
+ this.onContentPauseRequested_);
287
+ this.adsManager.addEventListener(
288
+ google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED,
289
+ this.onContentResumeRequested_);
290
+ this.adsManager.addEventListener(
291
+ google.ima.AdEvent.Type.ALL_ADS_COMPLETED,
292
+ onAllAdsCompleted_);
293
+
294
+ this.adsManager.addEventListener(
295
+ google.ima.AdEvent.Type.LOADED,
296
+ onAdLoaded_);
297
+ this.adsManager.addEventListener(
298
+ google.ima.AdEvent.Type.STARTED,
299
+ onAdStarted_);
300
+ this.adsManager.addEventListener(
301
+ google.ima.AdEvent.Type.CLICK,
302
+ onAdPlayPauseClick_);
303
+ this.adsManager.addEventListener(
304
+ google.ima.AdEvent.Type.COMPLETE,
305
+ this.onAdComplete_);
306
+ this.adsManager.addEventListener(
307
+ google.ima.AdEvent.Type.SKIPPED,
308
+ this.onAdComplete_);
309
+ this.adsManager.addEventListener(
310
+ google.ima.AdEvent.Type.PAUSED,
311
+ this.onAdPaused_);
312
+ this.adsManager.addEventListener(
313
+ google.ima.AdEvent.Type.RESUMED,
314
+ this.onAdResumed_);
315
+
316
+ var eventsMap = {
317
+ 'load': google.ima.AdEvent.Type.LOADED,
318
+ 'ad-started': google.ima.AdEvent.Type.STARTED,
319
+ 'click': google.ima.AdEvent.Type.CLICK,
320
+ 'ad-ended': google.ima.AdEvent.Type.COMPLETE,
321
+ 'ad-skipped': google.ima.AdEvent.Type.SKIPPED,
322
+ 'first-quartile': google.ima.AdEvent.Type.FIRST_QUARTILE,
323
+ 'midpoint': google.ima.AdEvent.Type.MIDPOINT,
324
+ 'third-quartile': google.ima.AdEvent.Type.THIRD_QUARTILE,
325
+ 'impression': google.ima.AdEvent.Type.IMPRESSION,
326
+ 'pause': google.ima.AdEvent.Type.PAUSED,
327
+ 'play': google.ima.AdEvent.Type.RESUMED,
328
+ 'mute': google.ima.AdEvent.Type.VOLUME_MUTED,
329
+ 'allpods-completed': google.ima.AdEvent.Type.ALL_ADS_COMPLETED
330
+ };
331
+
332
+ Object.keys(eventsMap).forEach(function(event){
333
+ this.adsManager.addEventListener(eventsMap[event], function(){
334
+ this.player.trigger('ads-'+event);
335
+ }.bind(this));
336
+
337
+ }.bind(this));
338
+
339
+ setAdMuted(this.player.muted());
340
+
341
+ if (!this.autoPlayAdBreaks) {
342
+ try {
343
+ var initWidth = this.getPlayerWidth();
344
+ var initHeight = this.getPlayerHeight();
345
+ this.adsManagerDimensions.width = initWidth;
346
+ this.adsManagerDimensions.height = initHeight;
347
+ this.adsManager.init(
348
+ initWidth,
349
+ initHeight,
350
+ google.ima.ViewMode.NORMAL);
351
+ this.adsManager.setVolume(this.player.muted() ? 0 : this.player.volume());
352
+ } catch (adError) {
353
+ onAdError_(adError);
354
+ }
355
+ }
356
+
357
+ var cuepoints = this.adsManager.getCuePoints();
358
+ var foundpreroll = !cuepoints.length; // no playlist, just preroll
359
+ var foundpostroll = false;;
360
+ cuepoints.forEach(function(offset){
361
+ if (!offset)
362
+ foundpreroll = true;
363
+ else if (offset==-1)
364
+ foundpostroll = true;
365
+ });
366
+ if (!foundpreroll)
367
+ this.player.trigger('nopreroll');
368
+ if (!foundpostroll)
369
+ this.player.trigger('nopostroll');
370
+ if (cuepoints.length)
371
+ this.player.trigger('ads-cuepoints', cuepoints);
372
+
373
+ this.player.trigger('adsready');
374
+ }.bind(this);
375
+
376
+ /**
377
+ * DEPRECATED: Use startFromReadyCallback
378
+ * Start ad playback, or content video playback in the absence of a
379
+ * pre-roll.
380
+ */
381
+ this.start = function() {
382
+ window.console.log(
383
+ 'WARNING: player.ima.start is deprecated. Use ' +
384
+ 'player.ima.startFromReadyCallback instead.');
385
+ };
386
+
387
+ /**
388
+ * Start ad playback, or content video playback in the absence of a
389
+ * pre-roll. **NOTE**: This method only needs to be called if you provide
390
+ * your own readyCallback as the second parameter to player.ima(). If you
391
+ * only provide options and do not provide your own readyCallback,
392
+ * **DO NOT** call this method. If you do provide your own readyCallback,
393
+ * you should call this method in the last line of that callback. For more
394
+ * info, see this method's usage in our advanced and playlist examples.
395
+ */
396
+ this.startFromReadyCallback = function() {
397
+ if (this.autoPlayAdBreaks && this.adsManager) {
398
+ try {
399
+ this.adsManager.init(
400
+ this.getPlayerWidth(),
401
+ this.getPlayerHeight(),
402
+ google.ima.ViewMode.NORMAL);
403
+ this.adsManager.setVolume(this.player.muted() ? 0 : this.player.volume());
404
+ this.adsManager.start();
405
+ } catch (adError) {
406
+ onAdError_(adError);
407
+ }
408
+ }
409
+ }.bind(this);
410
+
411
+ /**
412
+ * Listener for errors fired by the AdsLoader.
413
+ * @param {google.ima.AdErrorEvent} event The error event thrown by the
414
+ * AdsLoader. See
415
+ * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdError.Type
416
+ * @private
417
+ */
418
+ var onAdsLoaderError_ = function(event) {
419
+ console.log('AdsLoader error: ' + event.getError());
420
+ this.showAdContainer(false);
421
+ if (this.adsManager) {
422
+ this.adsManager.destroy();
423
+ }
424
+ this.player.trigger({type: 'adserror', data: { AdError: event.getError(), AdErrorEvent: event }});
425
+ }.bind(this);
426
+
427
+ /**
428
+ * Listener for errors thrown by the AdsManager.
429
+ * @param {google.ima.AdErrorEvent} adErrorEvent The error event thrown by
430
+ * the AdsManager.
431
+ * @private
432
+ */
433
+ var onAdError_ = function(adErrorEvent) {
434
+ var errorMessage = adErrorEvent.getError !== undefined ? adErrorEvent.getError() : adErrorEvent.stack;
435
+ console.log('Ad error: ' + errorMessage);
436
+ this.adsActive = false;
437
+ this.adPlaying = false;
438
+ this.restoreLoop();
439
+ this.vjsControls.show();
440
+ this.adsManager.destroy();
441
+ this.showAdContainer(false);
442
+ this.updateVjsControls();
443
+ this.player.trigger({ type: 'adserror', data: { AdError: errorMessage, AdErrorEvent: adErrorEvent }});
444
+ }.bind(this);
445
+
446
+ /**
447
+ * Listener for AD_BREAK_READY. Passes event on to publisher's listener.
448
+ * @param {google.ima.AdEvent} adEvent AdEvent thrown by the AdsManager.
449
+ * @private
450
+ */
451
+ var onAdBreakReady_ = function(adEvent) {
452
+ this.adBreakReadyListener(adEvent);
453
+ }.bind(this);
454
+
455
+ /**
456
+ * Called by publishers in manual ad break playback mode to start an ad
457
+ * break.
458
+ */
459
+ this.playAdBreak = function() {
460
+ if (!this.autoPlayAdBreaks) {
461
+ this.adsManager.start();
462
+ }
463
+ }.bind(this);
464
+
465
+ this.resetLoop = function() {
466
+ this.contentLoop = this.contentPlayer && this.contentPlayer.loop;
467
+ if (this.contentLoop) {
468
+ this.contentPlayer.loop = false;
469
+ }
470
+ }.bind(this);
471
+
472
+ this.restoreLoop = function() {
473
+ if (this.contentLoop) {
474
+ this.contentPlayer.loop = true;
475
+ this.contentLoop = false;
476
+ }
477
+ }.bind(this);
478
+
479
+ /**
480
+ * Pauses the content video and displays the ad container so ads can play.
481
+ * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
482
+ * @private
483
+ */
484
+ this.onContentPauseRequested_ = function(adEvent) {
485
+ this.contentSource = this.player.currentSrc();
486
+ this.resetLoop();
487
+ this.player.off('contentended', this.localContentEndedListener);
488
+ this.player.ads.startLinearAdMode();
489
+ this.showAdContainer(true);
490
+
491
+ var contentType = adEvent.getAd().getContentType();
492
+ if (!this.settings.vjsControls || !this.settings.showControlsForAds){
493
+ if (!this.settings.showControlsForAds
494
+ || ((contentType === 'application/javascript') && !this.settings.showControlsForJSAds)) {
495
+ this.controlsDiv.style.display = 'none';
496
+ } else {
497
+ this.controlsDiv.style.display = 'block';
498
+ }
499
+ this.vjsControls.hide();
500
+ }
501
+ showPlayButton();
502
+ this.player.pause();
503
+ this.adsActive = true;
504
+ this.adPlaying = true;
505
+ this.updateVjsControls();
506
+ }.bind(this);
507
+
508
+ /**
509
+ * Resumes content video and hides the ad container.
510
+ * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
511
+ * @private
512
+ */
513
+ this.onContentResumeRequested_ = function(adEvent) {
514
+ this.contentResumeTimer = clearTimeout(this.contentResumeTimer);
515
+ this.restoreLoop();
516
+ this.adsActive = false;
517
+ this.adPlaying = false;
518
+ this.player.on('contentended', this.localContentEndedListener);
519
+ if (this.currentAd == null || // hide for post-roll only playlist
520
+ this.currentAd.isLinear()) { // don't hide for non-linear ads
521
+ this.showAdContainer(false);
522
+ }
523
+ this.vjsControls.show();
524
+ this.player.ads.endLinearAdMode();
525
+ this.countdownDiv.innerHTML = '';
526
+ this.updateVjsControls();
527
+ }.bind(this);
528
+
529
+ /**
530
+ * Records that ads have completed and calls contentAndAdsEndedListeners
531
+ * if content is also complete.
532
+ * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
533
+ * @private
534
+ */
535
+ var onAllAdsCompleted_ = function(adEvent) {
536
+ this.allAdsCompleted = true;
537
+ this.showAdContainer(false);
538
+ if (this.contentComplete == true) {
539
+ if (this.contentPlayer.src && !/^blob:/.test(this.contentPlayer.src) &&
540
+ this.contentSource && this.contentPlayer.src != this.contentSource) {
541
+ this.player.src(this.contentSource);
542
+ }
543
+ this.player.trigger('')
544
+ for (var index in this.contentAndAdsEndedListeners) {
545
+ this.contentAndAdsEndedListeners[index]();
546
+ }
547
+ }
548
+ }.bind(this);
549
+
550
+ /**
551
+ * Starts the content video when a non-linear ad is loaded.
552
+ * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
553
+ * @private
554
+ */
555
+ var onAdLoaded_ = function(adEvent) {
556
+ if (!adEvent.getAd().isLinear() && !this.player.ended()) {
557
+ this.player.ads.endLinearAdMode();
558
+ this.player.play();
559
+ }
560
+ }.bind(this);
561
+
562
+ /**
563
+ * Starts the interval timer to check the current ad time when an ad starts
564
+ * playing.
565
+ * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
566
+ * @private
567
+ */
568
+ var onAdStarted_ = function(adEvent) {
569
+ this.currentAd = adEvent.getAd();
570
+ if (this.currentAd.isLinear()) {
571
+ this.adTrackingTimer = setInterval(
572
+ onAdPlayheadTrackerInterval_, 250);
573
+ // Don't bump container when controls are shown
574
+ removeClass_(this.adContainerDiv, 'bumpable-ima-ad-container');
575
+ } else {
576
+ // Bump container when controls are shown
577
+ addClass_(this.adContainerDiv, 'bumpable-ima-ad-container');
578
+ this.player.addClass('vjs-ima-non-linear');
579
+ this.showAdContainer(true);
580
+ }
581
+ }.bind(this);
582
+
583
+ /**
584
+ * Clears the interval timer for current ad time when an ad completes.
585
+ * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager.
586
+ * @private
587
+ */
588
+ this.onAdComplete_ = function(adEvent) {
589
+ if (this.currentAd.isLinear()) {
590
+ clearInterval(this.adTrackingTimer);
591
+ var pod = this.currentAd.getAdPodInfo();
592
+ if (pod && pod.getAdPosition() < pod.getTotalAds()) {
593
+ this.player.trigger('ads-pod-ended')
594
+ return;
595
+ }
596
+
597
+ // this is the final ad so we excpect ima sdk to trigger
598
+ // CONTENT_RESUME_REQUESTED, but for some reason it isn't triggered
599
+ // reliably on iOS, so we fake it
600
+
601
+ this.contentResumeTimer = setTimeout(function(){
602
+ this.onContentResumeRequested_(null);
603
+ }.bind(this), 1000);
604
+ } else {
605
+ this.player.removeClass('vjs-ima-non-linear');
606
+ }
607
+ }.bind(this);
608
+
609
+ this.onAdPaused_ = function(adEvent) {
610
+ showPauseButton();
611
+ this.adPlaying = false;
612
+ }.bind(this);
613
+
614
+ this.onAdResumed_ = function(adEvent) {
615
+ showPlayButton();
616
+ this.adPlaying = true;
617
+ }.bind(this);
618
+
619
+ var formatTime = function(time) {
620
+ var m = Math.floor(time / 60);
621
+ var s = Math.floor(time % 60);
622
+ if (s.toString().length < 2) {
623
+ s = '0' + s;
624
+ }
625
+ return m + ':' + s;
626
+ };
627
+
628
+ /**
629
+ * Gets the current time and duration of the ad and calls the method to
630
+ * update the ad UI.
631
+ * @private
632
+ */
633
+ var onAdPlayheadTrackerInterval_ = function() {
634
+ var remainingTime = this.adsManager.getRemainingTime();
635
+ var duration = this.currentAd.getDuration();
636
+ var currentTime = duration - remainingTime;
637
+ currentTime = currentTime > 0 ? currentTime : 0;
638
+ var isPod = false;
639
+ var totalAds = 0;
640
+ var adPosition;
641
+ if (this.currentAd.getAdPodInfo()) {
642
+ isPod = true;
643
+ adPosition = this.currentAd.getAdPodInfo().getAdPosition();
644
+ totalAds = this.currentAd.getAdPodInfo().getTotalAds();
645
+ }
646
+
647
+ // Update countdown timer data
648
+ var podCount = ': ';
649
+ if (isPod && (totalAds > 1)) {
650
+ podCount = ' (' + adPosition + ' of ' + totalAds + '): ';
651
+ }
652
+ this.countdownDiv.innerHTML =
653
+ this.settings.adLabel + podCount + formatTime(remainingTime);
654
+
655
+ // Update UI
656
+ var playProgressRatio = currentTime / duration;
657
+ var playProgressPercent = playProgressRatio * 100;
658
+ this.progressDiv.style.width = playProgressPercent + '%';
659
+ this.updateVjsControls();
660
+ }.bind(this);
661
+
662
+ this.getPlayerWidth = function() {
663
+ var retVal = parseInt(getComputedStyle(this.player.el()).width, 10) ||
664
+ this.player.width();
665
+ return retVal;
666
+ }.bind(this);
667
+
668
+ this.getPlayerHeight = function() {
669
+ var retVal = parseInt(getComputedStyle(this.player.el()).height, 10) ||
670
+ this.player.height();
671
+ return retVal;
672
+ }.bind(this);
673
+
674
+ /**
675
+ * Hides the ad controls on mouseout.
676
+ * @private
677
+ */
678
+ var hideAdControls_ = function() {
679
+ this.controlsDiv.style.height = '14px';
680
+ this.playPauseDiv.style.display = 'none';
681
+ this.muteDiv.style.display = 'none';
682
+ this.sliderDiv.style.display = 'none';
683
+ this.fullscreenDiv.style.display = 'none';
684
+ }.bind(this);
685
+
686
+ /**
687
+ * Shows ad controls on mouseover.
688
+ * @private
689
+ */
690
+ var showAdControls_ = function() {
691
+ this.controlsDiv.style.height = '37px';
692
+ this.playPauseDiv.style.display = 'block';
693
+ this.muteDiv.style.display = 'block';
694
+ this.sliderDiv.style.display = 'block';
695
+ this.fullscreenDiv.style.display = 'block';
696
+ }.bind(this);
697
+
698
+ /**
699
+ * Show pause and hide play button
700
+ */
701
+ var showPauseButton = function() {
702
+ addClass_(this.playPauseDiv, 'ima-paused');
703
+ removeClass_(this.playPauseDiv, 'ima-playing');
704
+ }.bind(this);
705
+
706
+ /**
707
+ * Show play and hide pause button
708
+ */
709
+ var showPlayButton = function() {
710
+ addClass_(this.playPauseDiv, 'ima-playing');
711
+ removeClass_(this.playPauseDiv, 'ima-paused');
712
+ }.bind(this);
713
+
714
+ /**
715
+ * Listener for clicks on the play/pause button during ad playback.
716
+ * @private
717
+ */
718
+ var onAdPlayPauseClick_ = function() {
719
+ if (this.adPlaying) {
720
+ showPauseButton();
721
+ this.adsManager.pause();
722
+ this.adPlaying = false;
723
+ } else {
724
+ showPlayButton();
725
+ this.adsManager.resume();
726
+ this.adPlaying = true;
727
+ }
728
+ }.bind(this);
729
+
730
+ /**
731
+ * Listener for clicks on the mute button during ad playback.
732
+ * @private
733
+ */
734
+ var onAdMuteClick_ = function() {
735
+ setAdMuted(!this.adMuted);
736
+ }.bind(this);
737
+
738
+ /* Listener for mouse down events during ad playback. Used for volume.
739
+ * @private
740
+ */
741
+ var onAdVolumeSliderMouseDown_ = function() {
742
+ document.addEventListener(eventTypes.mouseup, onMouseUp_, false);
743
+ document.addEventListener(eventTypes.mousemove, onMouseMove_, false);
744
+ };
745
+
746
+ /* Mouse movement listener used for volume slider.
747
+ * @private
748
+ */
749
+ var onMouseMove_ = function(event) {
750
+ setVolumeSlider_(event);
751
+ };
752
+
753
+ /* Mouse release listener used for volume slider.
754
+ * @private
755
+ */
756
+ var onMouseUp_ = function(event) {
757
+ setVolumeSlider_(event);
758
+ document.removeEventListener(eventTypes.mousemove, onMouseMove_);
759
+ document.removeEventListener(eventTypes.mouseup, onMouseUp_);
760
+ };
761
+
762
+ /* Utility function to set volume and associated UI
763
+ * @private
764
+ */
765
+ var setVolumeSlider_ = function(event) {
766
+ var clientX = event.changedTouches ? event.changedTouches[0].clientX :
767
+ event.clientX;
768
+ var percent = (clientX - this.sliderDiv.getBoundingClientRect().left) /
769
+ this.sliderDiv.offsetWidth;
770
+ percent *= 100;
771
+ //Bounds value 0-100 if mouse is outside slider region.
772
+ percent = Math.min(Math.max(percent, 0), 100);
773
+ this.sliderLevelDiv.style.width = percent + "%";
774
+ this.player.volume(percent / 100); //0-1
775
+ this.adsManager.setVolume(percent / 100);
776
+ if (this.player.volume() == 0) {
777
+ addClass_(this.muteDiv, 'ima-muted');
778
+ removeClass_(this.muteDiv, 'ima-non-muted');
779
+ this.player.muted(true);
780
+ this.adMuted = true;
781
+ }
782
+ else
783
+ {
784
+ addClass_(this.muteDiv, 'ima-non-muted');
785
+ removeClass_(this.muteDiv, 'ima-muted');
786
+ this.player.muted(false);
787
+ this.adMuted = false;
788
+ }
789
+ }.bind(this);
790
+
791
+ /**
792
+ * Listener for clicks on the fullscreen button during ad playback.
793
+ * @private
794
+ */
795
+ var onAdFullscreenClick_ = function() {
796
+ if (this.player.isFullscreen()) {
797
+ this.player.exitFullscreen();
798
+ } else {
799
+ this.player.requestFullscreen();
800
+ }
801
+ }.bind(this);
802
+
803
+ /**
804
+ * Listens for the video.js player to change its fullscreen status. This
805
+ * keeps the fullscreen-ness of the AdContainer in sync with the player.
806
+ * @private
807
+ */
808
+ var onFullscreenChange_ = function() {
809
+ if (this.player.isFullscreen()) {
810
+ addClass_(this.fullscreenDiv, 'ima-fullscreen');
811
+ removeClass_(this.fullscreenDiv, 'ima-non-fullscreen');
812
+ if (this.adsManager) {
813
+ this.adsManager.resize(
814
+ window.screen.width,
815
+ window.screen.height,
816
+ google.ima.ViewMode.FULLSCREEN);
817
+ }
818
+ } else {
819
+ addClass_(this.fullscreenDiv, 'ima-non-fullscreen');
820
+ removeClass_(this.fullscreenDiv, 'ima-fullscreen');
821
+ if (this.adsManager) {
822
+ this.adsManager.resize(
823
+ this.getPlayerWidth(),
824
+ this.getPlayerHeight(),
825
+ google.ima.ViewMode.NORMAL);
826
+ }
827
+ }
828
+ }.bind(this);
829
+
830
+ /**
831
+ * Listens for the video.js player to change its volume. This keeps the ad
832
+ * volume in sync with the content volume if the volume of the player is
833
+ * changed while content is playing
834
+ * @private
835
+ */
836
+ var onVolumeChange_ = function() {
837
+ var newVolume = this.player.muted() ? 0 : this.player.volume();
838
+ if (this.adsManager) {
839
+ this.adsManager.setVolume(newVolume);
840
+ }
841
+ // Update UI
842
+ if (newVolume == 0) {
843
+ this.adMuted = true;
844
+ addClass_(this.muteDiv, 'ima-muted');
845
+ removeClass_(this.muteDiv, 'ima-non-muted');
846
+ this.sliderLevelDiv.style.width = '0%';
847
+ } else {
848
+ this.adMuted = false;
849
+ addClass_(this.muteDiv, 'ima-non-muted');
850
+ removeClass_(this.muteDiv, 'ima-muted');
851
+ this.sliderLevelDiv.style.width = newVolume * 100 + '%';
852
+ }
853
+ }.bind(this);
854
+
855
+ /**
856
+ * Seeks content to 00:00:00. This is used as an event handler for the
857
+ * loadedmetadata event, since seeking is not possible until that event has
858
+ * fired.
859
+ * @private
860
+ */
861
+ var seekContentToZero_ = function() {
862
+ this.player.off('loadedmetadata', seekContentToZero_);
863
+ this.player.currentTime(0);
864
+ }.bind(this);
865
+
866
+ /**
867
+ * Seeks content to 00:00:00 and starts playback. This is used as an event
868
+ * handler for the loadedmetadata event, since seeking is not possible until
869
+ * that event has fired.
870
+ * @private
871
+ */
872
+ var playContentFromZero_ = function() {
873
+ this.player.off('loadedmetadata', playContentFromZero_);
874
+ this.player.currentTime(0);
875
+ this.player.play();
876
+ }.bind(this);
877
+
878
+ /**
879
+ * Destroys the AdsManager, sets it to null, and calls contentComplete to
880
+ * reset correlators. Once this is done it requests ads again to keep the
881
+ * inventory available.
882
+ * @private
883
+ */
884
+ var resetIMA_ = function() {
885
+ this.adsActive = false;
886
+ this.adPlaying = false;
887
+ this.restoreLoop();
888
+ this.player.on('contentended', this.localContentEndedListener);
889
+ if (this.currentAd && this.currentAd.isLinear()) {
890
+ this.showAdContainer(false);
891
+ }
892
+ this.vjsControls.show();
893
+ this.player.ads.endLinearAdMode();
894
+ this.contentPlayheadTracker.currentTime = 0;
895
+ this.countdownDiv.innerHTML = '';
896
+ this.updateVjsControls();
897
+ if (this.adTrackingTimer) {
898
+ // If this is called while an ad is playing, stop trying to get that
899
+ // ad's current time.
900
+ clearInterval(this.adTrackingTimer);
901
+ }
902
+ if (this.adsManager) {
903
+ this.adsManager.destroy();
904
+ this.adsManager = null;
905
+ }
906
+ if (this.adsLoader && !this.contentComplete) {
907
+ this.adsLoader.contentComplete();
908
+ }
909
+ this.contentComplete = false;
910
+ this.allAdsCompleted = false;
911
+ }.bind(this);
912
+
913
+ /**
914
+ * Ads an EventListener to the AdsManager. For a list of available events,
915
+ * see
916
+ * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type
917
+ * @param {google.ima.AdEvent.Type} event The AdEvent.Type for which to listen.
918
+ * @param {function} callback The method to call when the event is fired.
919
+ */
920
+ this.addEventListener = function(event, callback) {
921
+ if (this.adsManager) {
922
+ this.adsManager.addEventListener(event, callback);
923
+ }
924
+ }.bind(this);
925
+
926
+ /**
927
+ * Returns the instance of the AdsManager.
928
+ * @return {google.ima.AdsManager} The AdsManager being used by the plugin.
929
+ */
930
+ this.getAdsManager = function() {
931
+ return this.adsManager;
932
+ }.bind(this);
933
+
934
+ /**
935
+ * DEPRECATED: Use setContentWithAdTag.
936
+ * Sets the content of the video player. You should use this method instead
937
+ * of setting the content src directly to ensure the proper ad tag is
938
+ * requested when the video content is loaded.
939
+ * @param {?string} contentSrc The URI for the content to be played. Leave
940
+ * blank to use the existing content.
941
+ * @param {?string} adTag The ad tag to be requested when the content loads.
942
+ * Leave blank to use the existing ad tag.
943
+ * @param {?boolean} playOnLoad True to play the content once it has loaded,
944
+ * false to only load the content but not start playback.
945
+ */
946
+ this.setContent = function(contentSrc, adTag, playOnLoad) {
947
+ window.console.log(
948
+ 'WARNING: player.ima.setContent is deprecated. Use ' +
949
+ 'player.ima.setContentWithAdTag instead.');
950
+ this.setContentWithAdTag(contentSrc, adTag, playOnLoad);
951
+ }.bind(this);
952
+
953
+ /**
954
+ * Sets the content of the video player. You should use this method instead
955
+ * of setting the content src directly to ensure the proper ad tag is
956
+ * requested when the video content is loaded.
957
+ * @param {?string} contentSrc The URI for the content to be played. Leave
958
+ * blank to use the existing content.
959
+ * @param {?string} adTag The ad tag to be requested when the content loads.
960
+ * Leave blank to use the existing ad tag.
961
+ * @param {?boolean} playOnLoad True to play the content once it has loaded,
962
+ * false to only load the content but not start playback.
963
+ */
964
+ this.setContentWithAdTag = function(contentSrc, adTag, playOnLoad) {
965
+ resetIMA_();
966
+ this.settings.adTagUrl = adTag ? adTag : this.settings.adTagUrl;
967
+ changeSource_(contentSrc, playOnLoad);
968
+ }.bind(this);
969
+
970
+ /**
971
+ * Sets the content of the video player. You should use this method instead
972
+ * of setting the content src directly to ensure the proper ads response is
973
+ * used when the video content is loaded.
974
+ * @param {?string} contentSrc The URI for the content to be played. Leave
975
+ * blank to use the existing content.
976
+ * @param {?string} adsResponse The ads response to be requested when the
977
+ * content loads. Leave blank to use the existing ads response.
978
+ * @param {?boolean} playOnLoad True to play the content once it has loaded,
979
+ * false to only load the content but not start playback.
980
+ */
981
+ this.setContentWithAdsResponse = function(contentSrc, adsResponse, playOnLoad) {
982
+ resetIMA_();
983
+ this.settings.adsResponse = adsResponse ? adsResponse : this.settings.adsResponse;
984
+ changeSource_(contentSrc, playOnLoad);
985
+ }.bind(this);
986
+
987
+ /**
988
+ * Plays an ad immediately
989
+ * @param {?string} adTag The ad tag to be requested.
990
+ * Leave blank to use the existing ad tag.
991
+ */
992
+ this.playAd = function(adTag) {
993
+ resetIMA_();
994
+ this.settings.adTagUrl = adTag ? adTag : this.settings.adTagUrl;
995
+ // this.showAdContainer(true);
996
+ // this.vjsControls.hide();
997
+ this.requestAds();
998
+ }.bind(this);
999
+
1000
+ /**
1001
+ * Changes the player source.
1002
+ * @param {?string} contentSrc The URI for the content to be played. Leave
1003
+ * blank to use the existing content.
1004
+ * @param {?boolean} playOnLoad True to play the content once it has loaded,
1005
+ * false to only load the content but not start playback.
1006
+ * @private
1007
+ */
1008
+ var changeSource_ = function(contentSrc, playOnLoad) {
1009
+ // Only try to pause the player when initialised with a source already
1010
+ if (!!this.player.currentSrc()) {
1011
+ this.player.currentTime(0);
1012
+ this.player.pause();
1013
+ }
1014
+ if (contentSrc) {
1015
+ this.player.src(contentSrc);
1016
+ }
1017
+ if (playOnLoad) {
1018
+ this.player.on('loadedmetadata', playContentFromZero_);
1019
+ } else {
1020
+ this.player.on('loadedmetadata', seekContentToZero_);
1021
+ }
1022
+ }.bind(this);
1023
+
1024
+ var setAdMuted = function(mute) {
1025
+ if (mute) {
1026
+ addClass_(this.muteDiv, 'ima-muted');
1027
+ removeClass_(this.muteDiv, 'ima-non-muted');
1028
+ this.adsManager.setVolume(0);
1029
+ // Bubble down to content player
1030
+ this.player.muted(true);
1031
+ this.adMuted = true;
1032
+ this.sliderLevelDiv.style.width = "0%";
1033
+ } else {
1034
+ addClass_(this.muteDiv, 'ima-non-muted');
1035
+ removeClass_(this.muteDiv, 'ima-muted');
1036
+ this.adsManager.setVolume(this.player.volume());
1037
+ // Bubble down to content player
1038
+ this.player.muted(false);
1039
+ this.adMuted = false;
1040
+ this.sliderLevelDiv.style.width = this.player.volume() * 100 + "%";
1041
+ }
1042
+ }.bind(this);
1043
+ /**
1044
+ * Adds a listener for the 'contentended' event of the video player. This should be
1045
+ * used instead of setting an 'contentended' listener directly to ensure that the
1046
+ * ima can do proper cleanup of the SDK before other event listeners
1047
+ * are called.
1048
+ * @param {function} listener The listener to be called when content completes.
1049
+ */
1050
+ this.addContentEndedListener = function(listener) {
1051
+ this.contentEndedListeners.push(listener);
1052
+ }.bind(this);
1053
+
1054
+ /**
1055
+ * Adds a listener that will be called when content and all ads have
1056
+ * finished playing.
1057
+ * @param {function} listener The listener to be called when content and ads complete.
1058
+ */
1059
+ this.addContentAndAdsEndedListener = function(listener) {
1060
+ this.contentAndAdsEndedListeners.push(listener);
1061
+ }.bind(this);
1062
+
1063
+ /**
1064
+ * Sets the listener to be called to trigger manual ad break playback.
1065
+ * @param {function} listener The listener to be called to trigger manual ad break playback.
1066
+ */
1067
+ this.setAdBreakReadyListener = function(listener) {
1068
+ this.adBreakReadyListener = listener;
1069
+ }.bind(this);
1070
+
1071
+ /**
1072
+ * Pauses the ad.
1073
+ */
1074
+ this.pauseAd = function() {
1075
+ if (this.adsActive && this.adPlaying) {
1076
+ showPauseButton();
1077
+ this.adsManager.pause();
1078
+ this.adPlaying = false;
1079
+ }
1080
+ }.bind(this);
1081
+
1082
+ /**
1083
+ * Resumes the ad.
1084
+ */
1085
+ this.resumeAd = function() {
1086
+ if (this.adsActive && !this.adPlaying) {
1087
+ showPlayButton();
1088
+ this.adsManager.resume();
1089
+ this.adPlaying = true;
1090
+ }
1091
+ }.bind(this);
1092
+
1093
+ /**
1094
+ * Set up intervals to check for seeking and update current video time.
1095
+ * @private
1096
+ */
1097
+ var setUpPlayerIntervals_ = function() {
1098
+ this.updateTimeIntervalHandle =
1099
+ setInterval(updateCurrentTime_, this.seekCheckInterval);
1100
+ this.seekCheckIntervalHandle =
1101
+ setInterval(checkForSeeking_, this.seekCheckInterval);
1102
+ this.resizeCheckIntervalHandle =
1103
+ setInterval(checkForResize_, this.resizeCheckInterval);
1104
+ }.bind(this);
1105
+
1106
+ /**
1107
+ * Updates the start time of the video
1108
+ * @private
1109
+ */
1110
+ var updateStartTime_ = function(){
1111
+ var cur = this.player.currentTime();
1112
+ if (!cur || this.player.ads.state!='content-playback')
1113
+ return;
1114
+ // first time that isn't zero is our start time, but only if it's
1115
+ // more than the 1sec
1116
+ if (cur<1)
1117
+ cur = 0;
1118
+ this.contentPlayheadTracker.startTime = cur;
1119
+ this.player.off('timeupdate', updateStartTime_);
1120
+ }.bind(this);
1121
+
1122
+ /**
1123
+ * Updates the current time of the video
1124
+ * @private
1125
+ */
1126
+ var updateCurrentTime_ = function() {
1127
+ if (this.player.ads.state=='content-playback' &&
1128
+ !this.contentPlayheadTracker.seeking &&
1129
+ this.contentPlayheadTracker.startTime>=0) {
1130
+ this.contentPlayheadTracker.currentTime = this.player.currentTime() -
1131
+ this.contentPlayheadTracker.startTime;
1132
+ }
1133
+ }.bind(this);
1134
+
1135
+ /**
1136
+ * Detects when the user is seeking through a video.
1137
+ * This is used to prevent mid-rolls from playing while a user is seeking.
1138
+ *
1139
+ * There *is* a seeking property of the HTML5 video element, but it's not
1140
+ * properly implemented on all platforms (e.g. mobile safari), so we have to
1141
+ * check ourselves to be sure.
1142
+ *
1143
+ * @private
1144
+ */
1145
+ var checkForSeeking_ = function() {
1146
+ if (this.player.ads.state!='content-playback')
1147
+ return;
1148
+ var tempCurrentTime = this.player.currentTime();
1149
+ var diff = (tempCurrentTime - this.contentPlayheadTracker.previousTime) * 1000;
1150
+ if (Math.abs(diff) > this.seekCheckInterval + this.seekThreshold) {
1151
+ this.contentPlayheadTracker.seeking = true;
1152
+ } else {
1153
+ this.contentPlayheadTracker.seeking = false;
1154
+ }
1155
+ this.contentPlayheadTracker.previousTime = this.player.currentTime();
1156
+ }.bind(this);
1157
+
1158
+ /**
1159
+ * Detects when the player is resized (for fluid support) and resizes the
1160
+ * ads manager to match.
1161
+ *
1162
+ * @private
1163
+ */
1164
+ var checkForResize_ = function() {
1165
+ var currentWidth = this.getPlayerWidth();
1166
+ var currentHeight = this.getPlayerHeight();
1167
+
1168
+ if (this.adsManager && (currentWidth != this.adsManagerDimensions.width ||
1169
+ currentHeight != this.adsManagerDimensions.height)) {
1170
+ this.adsManagerDimensions.width = currentWidth;
1171
+ this.adsManagerDimensions.height = currentHeight;
1172
+ this.adsManager.resize(currentWidth, currentHeight, google.ima.ViewMode.NORMAL);
1173
+ }
1174
+ }.bind(this);
1175
+
1176
+ /**
1177
+ * Changes the flag to show or hide the ad countdown timer.
1178
+ *
1179
+ * @param {boolean} showCountdownIn Show or hide the countdown timer.
1180
+ */
1181
+ this.setShowCountdown = function(showCountdownIn) {
1182
+ this.showCountdown = showCountdownIn;
1183
+ this.countdownDiv.style.display = this.showCountdown ? '' : 'none';
1184
+ }.bind(this);
1185
+
1186
+ /**
1187
+ * Current plugin version.
1188
+ */
1189
+ this.VERSION = '0.2.0';
1190
+
1191
+ /**
1192
+ * Stores user-provided settings.
1193
+ */
1194
+ this.settings;
1195
+
1196
+ /**
1197
+ * Used to prefix videojs ima
1198
+ */
1199
+ this.controlPrefix;
1200
+
1201
+ /**
1202
+ * Video element playing content.
1203
+ */
1204
+ this.contentPlayer;
1205
+
1206
+ /**
1207
+ * Boolean flag to show or hide the ad countdown timer.
1208
+ */
1209
+ this.showCountdown;
1210
+
1211
+ /**
1212
+ * Boolena flag to enable manual ad break playback.
1213
+ */
1214
+ this.autoPlayAdBreaks;
1215
+
1216
+ /**
1217
+ * Video.js control bar.
1218
+ */
1219
+ this.vjsControls;
1220
+
1221
+ /**
1222
+ * Div used as an ad container.
1223
+ */
1224
+ this.adContainerDiv;
1225
+
1226
+ /**
1227
+ * Div used to display ad controls.
1228
+ */
1229
+ this.controlsDiv;
1230
+
1231
+ /**
1232
+ * Div used to display ad countdown timer.
1233
+ */
1234
+ this.countdownDiv;
1235
+
1236
+ /**
1237
+ * Div used to display add seek bar.
1238
+ */
1239
+ this.seekBarDiv;
1240
+
1241
+ /**
1242
+ * Div used to display ad progress (in seek bar).
1243
+ */
1244
+ this.progressDiv;
1245
+
1246
+ /**
1247
+ * Div used to display ad play/pause button.
1248
+ */
1249
+ this.playPauseDiv;
1250
+
1251
+ /**
1252
+ * Div used to display ad mute button.
1253
+ */
1254
+ this.muteDiv;
1255
+
1256
+ /**
1257
+ * Div used by the volume slider.
1258
+ */
1259
+ this.sliderDiv;
1260
+
1261
+ /**
1262
+ * Volume slider level visuals
1263
+ */
1264
+ this.sliderLevelDiv;
1265
+
1266
+ /**
1267
+ * Div used to display ad fullscreen button.
1268
+ */
1269
+ this.fullscreenDiv;
1270
+
1271
+ /**
1272
+ * IMA SDK AdDisplayContainer.
1273
+ */
1274
+ this.adDisplayContainer;
1275
+
1276
+ /**
1277
+ * True if the AdDisplayContainer has been initialized. False otherwise.
1278
+ */
1279
+ this.adDisplayContainerInitialized = false;
1280
+
1281
+ /**
1282
+ * IMA SDK AdsLoader
1283
+ */
1284
+ this.adsLoader;
1285
+
1286
+ /**
1287
+ * IMA SDK AdsManager
1288
+ */
1289
+ this.adsManager;
1290
+
1291
+ /**
1292
+ * IMA SDK AdsRenderingSettings.
1293
+ */
1294
+ this.adsRenderingSettings = null;
1295
+
1296
+ /**
1297
+ * Ad tag URL. Should return VAST, VMAP, or ad rules.
1298
+ */
1299
+ this.adTagUrl;
1300
+
1301
+ /**
1302
+ * VAST, VMAP, or ad rules response. Used in lieu of fetching a response
1303
+ * from an ad tag URL.
1304
+ */
1305
+ this.adsResponse;
1306
+
1307
+ /**
1308
+ * Current IMA SDK Ad.
1309
+ */
1310
+ this.currentAd;
1311
+
1312
+ /**
1313
+ * Timer used to track content progress.
1314
+ */
1315
+ this.contentTrackingTimer;
1316
+
1317
+ /**
1318
+ * Timer used to track ad progress.
1319
+ */
1320
+ this.adTrackingTimer;
1321
+
1322
+ /**
1323
+ * True if ads are currently displayed, false otherwise.
1324
+ * True regardless of ad pause state if an ad is currently being displayed.
1325
+ */
1326
+ this.adsActive = false;
1327
+
1328
+ /**
1329
+ * True if ad is currently playing, false if ad is paused or ads are not
1330
+ * currently displayed.
1331
+ */
1332
+ this.adPlaying = false;
1333
+
1334
+ /**
1335
+ * True if the ad is muted, false otherwise.
1336
+ */
1337
+ this.adMuted = false;
1338
+
1339
+ /**
1340
+ * True if our content video has completed, false otherwise.
1341
+ */
1342
+ this.contentComplete = false;
1343
+
1344
+ /**
1345
+ * True if ALL_ADS_COMPLETED has fired, false until then.
1346
+ */
1347
+ this.allAdsCompleted = false;
1348
+
1349
+ /**
1350
+ * Handle to interval that repeatedly updates current time.
1351
+ */
1352
+ this.updateTimeIntervalHandle;
1353
+
1354
+ /**
1355
+ * Handle to interval that repeatedly checks for seeking.
1356
+ */
1357
+ this.seekCheckIntervalHandle;
1358
+
1359
+ /**
1360
+ * Interval (ms) on which to check if the user is seeking through the
1361
+ * content.
1362
+ */
1363
+ this.seekCheckInterval = 1000;
1364
+
1365
+ /**
1366
+ * Handle to interval that repeatedly checks for player resize.
1367
+ */
1368
+ this.resizeCheckIntervalHandle;
1369
+
1370
+ /**
1371
+ * Interval (ms) to check for player resize for fluid support.
1372
+ */
1373
+ this.resizeCheckInterval = 250;
1374
+
1375
+ /**
1376
+ * Threshold by which to judge user seeking. We check every 1000 ms to see
1377
+ * if the user is seeking. In order for us to decide that they are *not*
1378
+ * seeking, the content video playhead must only change by 900-1100 ms
1379
+ * between checks. Any greater change and we assume the user is seeking
1380
+ * through the video.
1381
+ */
1382
+ this.seekThreshold = 100;
1383
+
1384
+ /**
1385
+ * Stores data for the content playhead tracker.
1386
+ */
1387
+ this.contentPlayheadTracker = {
1388
+ currentTime: 0,
1389
+ previousTime: 0,
1390
+ seeking: false,
1391
+ duration: 0,
1392
+ startTime: -1
1393
+ };
1394
+
1395
+ /**
1396
+ * Stores data for the ad playhead tracker.
1397
+ */
1398
+ this.adPlayheadTracker = {
1399
+ currentTime: 0,
1400
+ duration: 0,
1401
+ isPod: false,
1402
+ adPosition: 0,
1403
+ totalAds: 0
1404
+ };
1405
+
1406
+ /**
1407
+ * Stores the dimensions for the ads manager.
1408
+ */
1409
+ this.adsManagerDimensions = {
1410
+ width: 0,
1411
+ height: 0
1412
+ };
1413
+
1414
+ /**
1415
+ * Content ended listeners passed by the publisher to the plugin. Publishers
1416
+ * should allow the plugin to handle content ended to ensure proper support
1417
+ * of custom ad playback.
1418
+ */
1419
+ this.contentEndedListeners = [];
1420
+
1421
+ /**
1422
+ * Content and ads ended listeners passed by the publisher to the plugin.
1423
+ * These will be called when the plugin detects that content *and all
1424
+ * ads* have completed. This differs from the contentEndedListeners in that
1425
+ * contentEndedListeners will fire between content ending and a post-roll
1426
+ * playing, whereas the contentAndAdsEndedListeners will fire after the
1427
+ * post-roll completes.
1428
+ */
1429
+ this.contentAndAdsEndedListeners = [];
1430
+
1431
+ /**
1432
+ * Listener to be called to trigger manual ad break playback.
1433
+ */
1434
+ this.adBreakReadyListener = function() {
1435
+ console.log('Please set adBreakReadyListener');
1436
+ };
1437
+
1438
+ /**
1439
+ * Stores the content source so we can re-populate it manually after a
1440
+ * post-roll on iOS.
1441
+ */
1442
+ this.contentSource = '';
1443
+
1444
+ /**
1445
+ * Local content ended listener for contentComplete.
1446
+ */
1447
+ this.localContentEndedListener = function() {
1448
+ if (this.adsLoader && !this.contentComplete) {
1449
+ this.adsLoader.contentComplete();
1450
+ this.contentComplete = true;
1451
+ }
1452
+ for (var index in this.contentEndedListeners) {
1453
+ this.contentEndedListeners[index]();
1454
+ }
1455
+ if (this.allAdsCompleted) {
1456
+ for (var index in this.contentAndAdsEndedListeners) {
1457
+ this.contentAndAdsEndedListeners[index]();
1458
+ }
1459
+ }
1460
+ clearInterval(this.updateTimeIntervalHandle);
1461
+ clearInterval(this.seekCheckIntervalHandle);
1462
+ clearInterval(this.resizeCheckIntervalHandle);
1463
+ if(this.player.el()) {
1464
+ this.player.one('play', setUpPlayerIntervals_);
1465
+ }
1466
+ }.bind(this);
1467
+
1468
+ this.playerDisposedListener = function(){
1469
+ this.contentEndedListeners, this.contentAndAdsEndedListeners = [], [];
1470
+ this.contentComplete = true;
1471
+ this.player.off('contentended', this.localContentEndedListener);
1472
+ this.player.off('timeupdate', updateStartTime_);
1473
+
1474
+ // Bug fix: https://github.com/googleads/videojs-ima/issues/306
1475
+ if (this.player.ads.adTimeoutTimeout) {
1476
+ clearTimeout(this.player.ads.adTimeoutTimeout);
1477
+ }
1478
+
1479
+ var intervalsToClear = [this.updateTimeIntervalHandle, this.seekCheckIntervalHandle,
1480
+ this.adTrackingTimer, this.resizeCheckIntervalHandle];
1481
+ for (var index in intervalsToClear) {
1482
+ var interval = intervalsToClear[index];
1483
+ if (interval) {
1484
+ clearInterval(interval);
1485
+ }
1486
+ }
1487
+ if (this.adsManager) {
1488
+ this.adsManager.destroy();
1489
+ this.adsManager = null;
1490
+ }
1491
+ }.bind(this);
1492
+
1493
+ this.initVjsControls = function() {
1494
+ var _this = this;
1495
+ var override = function(cls, obj, method, fn, always) {
1496
+ var orig = cls.prototype[method];
1497
+ return obj[method] = function() {
1498
+ return _this.adsActive || always ? fn && fn.apply(this, arguments) :
1499
+ orig && orig.apply(this, arguments);
1500
+ };
1501
+ };
1502
+ var overrideHandler = function(cls, obj, target, event, method, fn, always) {
1503
+ var orig = cls.prototype[method];
1504
+ var handler = override(cls, obj, method, fn);
1505
+ if (target) {
1506
+ obj.off(target, event, orig);
1507
+ obj.on(target, event, handler);
1508
+ } else {
1509
+ obj.off(event, orig);
1510
+ obj.on(event, handler);
1511
+ }
1512
+ };
1513
+ var PlayToggle = videojs.getComponent('PlayToggle');
1514
+ var playToggle = this.vjsControls.playToggle;
1515
+ overrideHandler(PlayToggle, playToggle, null, ['tap', 'click'],
1516
+ 'handleClick', function() {
1517
+ onAdPlayPauseClick_();
1518
+ if (_this.adPlaying) {
1519
+ this.handlePlay();
1520
+ } else {
1521
+ this.handlePause();
1522
+ }
1523
+ });
1524
+ override(PlayToggle, playToggle, 'update', function() {
1525
+ var paused = _this.adsActive ? !_this.adPlaying : player.paused();
1526
+ this.toggleClass('vjs-play-control-ad', _this.adsActive);
1527
+ this.toggleClass('vjs-paused', paused);
1528
+ this.toggleClass('vjs-playing', !paused);
1529
+ var text = paused ? 'Play' : 'Pause';
1530
+ if (text != this.controlText())
1531
+ this.controlText(text);
1532
+ }, true);
1533
+ overrideHandler(PlayToggle, playToggle, player, 'play',
1534
+ 'handlePlay', function() { this.update(); }, true);
1535
+ overrideHandler(PlayToggle, playToggle, player, 'pause',
1536
+ 'handlePause', function() { this.update(); }, true);
1537
+
1538
+ var SeekBar = videojs.getComponent('SeekBar');
1539
+ var DvrSeekBar = videojs.getComponent('DvrSeekBar');
1540
+ var seekBar = this.vjsControls.progressControl.seekBar;
1541
+ var getPercent = function() {
1542
+ var duration = _this.currentAd && _this.currentAd.getDuration();
1543
+ if (!duration || duration<0) {
1544
+ return 0;
1545
+ }
1546
+ var remainingTime = _this.adsManager.getRemainingTime();
1547
+ var currentTime = Math.max(duration - remainingTime, 0);
1548
+ return currentTime / duration;
1549
+ };
1550
+ override(SeekBar, seekBar, 'getPercent', getPercent);
1551
+ if (DvrSeekBar) {
1552
+ override(DvrSeekBar, seekBar, 'getPercent', getPercent);
1553
+ }
1554
+ overrideHandler(SeekBar, seekBar, null, ['mousedown', 'touchstart'],
1555
+ 'handleMouseDown', null);
1556
+ overrideHandler(SeekBar, seekBar, null, 'focus', 'handleFocus', null);
1557
+
1558
+ var DurationDisplay = videojs.getComponent('DurationDisplay');
1559
+ var durationDisplay = this.vjsControls.durationDisplay;
1560
+ overrideHandler(DurationDisplay, durationDisplay, player,
1561
+ ['timeupdate', 'loadedmetadata'], 'updateContent', function() {
1562
+ var duration = _this.currentAd && _this.currentAd.getDuration();
1563
+ if (duration && duration != this.duration_) {
1564
+ this.duration_ = duration;
1565
+ this.contentEl_.innerHTML = '<span class="vjs-control-text">'+
1566
+ this.localize('Duration Time')+'</span> '+formatTime(duration);
1567
+ }
1568
+ });
1569
+
1570
+ var CurrentTimeDisplay = videojs.getComponent('CurrentTimeDisplay');
1571
+ var currentTimeDisplay = this.vjsControls.currentTimeDisplay;
1572
+ overrideHandler(CurrentTimeDisplay, currentTimeDisplay, player,
1573
+ ['timeupdate', 'loadedmetadata'], 'updateContent', function() {
1574
+ var duration = _this.currentAd && _this.currentAd.getDuration();
1575
+ if (!duration) {
1576
+ return;
1577
+ }
1578
+ var time = duration - _this.adsManager.getRemainingTime();
1579
+ var formattedTime = formatTime(time);
1580
+ if (formattedTime !== this.formattedTime_) {
1581
+ this.formattedTime_ = formattedTime;
1582
+ this.contentEl_.innerHTML = '<span class="vjs-control-text">'+
1583
+ this.localize('Current Time')+'</span> '+formattedTime;
1584
+ }
1585
+ });
1586
+ }.bind(this);
1587
+
1588
+ this.updateVjsControls = function() {
1589
+ if (!this.settings.vjsControls) {
1590
+ return;
1591
+ }
1592
+ this.player.toggleClass('vjs-ad-paused',
1593
+ this.adsActive && !this.adPlaying);
1594
+ var controls = this.vjsControls;
1595
+ controls.playToggle.update();
1596
+ controls.progressControl.seekBar.update();
1597
+ controls.durationDisplay.updateContent();
1598
+ controls.currentTimeDisplay.updateContent();
1599
+ var duration = this.currentAd && this.currentAd.getDuration();
1600
+ var display = !this.adsActive || duration && duration>=0 ? '' : 'none';
1601
+ controls.durationDisplay.el().style.display = display;
1602
+ controls.currentTimeDisplay.el().style.display = display;
1603
+ controls.timeDivider.el().style.display = display;
1604
+ }.bind(this);
1605
+
1606
+ var getPosition = function(el) {
1607
+ var box = el.getBoundingClientRect();
1608
+ var docEl = document.documentElement;
1609
+ var body = document.body;
1610
+ var clientLeft = docEl.clientLeft || body.clientLeft || 0;
1611
+ var scrollLeft = window.pageXOffset || body.scrollLeft;
1612
+ var left = box.left + scrollLeft - clientLeft;
1613
+ var clientTop = docEl.clientTop || body.clientTop || 0;
1614
+ var scrollTop = window.pageYOffset || body.scrollTop;
1615
+ var top = box.top + scrollTop - clientTop;
1616
+ return {
1617
+ left: left,
1618
+ top: top,
1619
+ width: box.width,
1620
+ height: box.height,
1621
+ };
1622
+ };
1623
+
1624
+ // proxy click events to the video element when non-linear ad is active
1625
+ this.proxyClickEvents = function() {
1626
+ var events = (videojs && videojs.browser && videojs.browser.IS_ANDROID || videojs.browser.IS_IOS) ?
1627
+ ['touchstart', 'touchend'] :
1628
+ ['click', 'dblclick', 'mousedown', 'mouseup'];
1629
+ var player = this.player, el = player.el(), _this = this;
1630
+ events.forEach(function(eventName) {
1631
+ el.addEventListener(eventName, function(e) {
1632
+ var ad = _this.currentAd, t = e.target;
1633
+ if (!ad || ad.isLinear() || t.nodeName!='IFRAME' || e.isTrusted) {
1634
+ return;
1635
+ }
1636
+ // ignore clicks on ad ui elements
1637
+ var adWidth = ad.getWidth() || ad.getVastMediaWidth();
1638
+ var adHeight = ad.getHeight() || ad.getVastMediaHeight();
1639
+ var pos = getPosition(t);
1640
+ var touch = e.touches && e.touches[0];
1641
+ var x = touch ? touch.pageX : e.clientX;
1642
+ var y = touch ? touch.pageY : e.clientY;
1643
+ var adRight = pos.left+pos.width-(pos.width-adWidth)/2;
1644
+ var adTop = pos.top+pos.height-adHeight-4;
1645
+ // click on close button
1646
+ if (x<adRight && x>(adRight-40) && y>adTop && y<(adTop+30)) {
1647
+ return;
1648
+ }
1649
+ // click on recall button
1650
+ if (x>(pos.left+pos.width/2-15) && x<(pos.left+pos.width/2+15) &&
1651
+ y>(pos.top+pos.height-15)) {
1652
+ return;
1653
+ }
1654
+ var newEvent;
1655
+ var opt = {};
1656
+ for (var key in e) {
1657
+ opt[key] = e[key];
1658
+ }
1659
+ opt.bubbles = false;
1660
+ try {
1661
+ newEvent = new e.constructor(e.type, opt);
1662
+ } catch (err) {
1663
+ // special case for IE11
1664
+ newEvent = document.createEvent('MouseEvent');
1665
+ newEvent.initMouseEvent(e.type, opt.bubbles, opt.cancelable,
1666
+ opt.view, opt.detail, opt.screenX, opt.screenY, opt.clientX,
1667
+ opt.clientY, opt.ctrlKey, opt.altKey, opt.shiftKey, opt.metaKey,
1668
+ opt.button, null);
1669
+ }
1670
+ newEvent.stopPropagation();
1671
+ player.tech_.trigger(newEvent);
1672
+ });
1673
+ });
1674
+ }.bind(this);
1675
+
1676
+ this.settings = extend({}, ima_defaults, options || {});
1677
+ this.settings.adLabel = this.player.localize(this.settings.adLabel);
1678
+
1679
+ // Currently this isn't used but I can see it being needed in the future, so
1680
+ // to avoid implementation problems with later updates I'm requiring it.
1681
+ if (!this.settings['id']) {
1682
+ window.console.log('Error: must provide id of video.js div');
1683
+ return;
1684
+ }
1685
+
1686
+ this.controlPrefix = (this.settings.id + '_') || '';
1687
+
1688
+ this.contentPlayer = this.player.$('.vjs-tech');
1689
+ // Default showing countdown timer to true.
1690
+ this.showCountdown = true;
1691
+ if (this.settings['showCountdown'] === false) {
1692
+ this.showCountdown = false;
1693
+ }
1694
+
1695
+ this.autoPlayAdBreaks = true;
1696
+ if (this.settings['autoPlayAdBreaks'] === false) {
1697
+ this.autoPlayAdBreaks = false;
1698
+ }
1699
+
1700
+ var contrib_ads_defaults = {
1701
+ debug: this.settings.debug,
1702
+ timeout: this.settings.timeout,
1703
+ prerollTimeout: this.settings.prerollTimeout,
1704
+ postrollTimeout: this.settings.postrollTimeout
1705
+ };
1706
+
1707
+ var ads_plugin_settings =
1708
+ extend({}, contrib_ads_defaults, options['contribAdsSettings'] || {});
1709
+
1710
+ player.ads(ads_plugin_settings);
1711
+
1712
+ player.one('play', setUpPlayerIntervals_);
1713
+ player.on('contentended', this.localContentEndedListener);
1714
+ player.on('dispose', this.playerDisposedListener);
1715
+ player.on('timeupdate', updateStartTime_);
1716
+ this.adsRenderingSettings = new google.ima.AdsRenderingSettings();
1717
+ this.adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
1718
+ if (this.settings['adsRenderingSettings']) {
1719
+ for (var setting in this.settings['adsRenderingSettings']) {
1720
+ this.adsRenderingSettings[setting] =
1721
+ this.settings['adsRenderingSettings'][setting];
1722
+ }
1723
+ }
1724
+
1725
+ if (this.settings['locale']) {
1726
+ google.ima.settings.setLocale(this.settings['locale']);
1727
+ }
1728
+
1729
+ createAdContainer_();
1730
+ this.adsLoader = new google.ima.AdsLoader(this.adDisplayContainer);
1731
+
1732
+ this.adsLoader.getSettings().setVpaidMode(
1733
+ google.ima.ImaSdkSettings.VpaidMode.ENABLED);
1734
+ if (this.settings.vpaidAllowed == false) {
1735
+ this.adsLoader.getSettings().setVpaidMode(
1736
+ google.ima.ImaSdkSettings.VpaidMode.DISABLED);
1737
+ }
1738
+ if (this.settings.vpaidMode) {
1739
+ this.adsLoader.getSettings().setVpaidMode(this.settings.vpaidMode);
1740
+ }
1741
+
1742
+ if (this.settings.locale) {
1743
+ this.adsLoader.getSettings().setLocale(this.settings.locale);
1744
+ }
1745
+
1746
+ if (this.settings.numRedirects) {
1747
+ this.adsLoader.getSettings().setNumRedirects(this.settings.numRedirects);
1748
+ }
1749
+
1750
+ this.adsLoader.getSettings().setPlayerType('videojs-ima');
1751
+ this.adsLoader.getSettings().setPlayerVersion(this.VERSION);
1752
+ this.adsLoader.getSettings().setAutoPlayAdBreaks(this.autoPlayAdBreaks);
1753
+
1754
+ this.adsLoader.addEventListener(
1755
+ google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
1756
+ onAdsManagerLoaded_,
1757
+ false);
1758
+ this.adsLoader.addEventListener(
1759
+ google.ima.AdErrorEvent.Type.AD_ERROR,
1760
+ onAdsLoaderError_,
1761
+ false);
1762
+
1763
+ if (!readyCallback) {
1764
+ readyCallback = this.startFromReadyCallback;
1765
+ }
1766
+ player.on('readyforpreroll', readyCallback);
1767
+ player.ready(function() {
1768
+ player.on('fullscreenchange', onFullscreenChange_);
1769
+ player.on('volumechange', onVolumeChange_);
1770
+ });
1771
+ this.proxyClickEvents();
1772
+ };
1773
+
1774
+ videojs.registerPlugin('ima', init);
1775
+ });