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.
- package/.eslintignore +4 -0
- package/.snyk +19 -0
- package/.travis.yml +8 -0
- package/CHANGELOG.md +329 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/cld-video-player.css +2110 -0
- package/dist/cld-video-player.js +5249 -0
- package/dist/cld-video-player.light.css +1766 -0
- package/dist/cld-video-player.light.js +1399 -0
- package/dist/cld-video-player.light.min.css +1 -0
- package/dist/cld-video-player.light.min.js +2 -0
- package/dist/cld-video-player.light.min.js.LICENSE.txt +23 -0
- package/dist/cld-video-player.min.css +1 -0
- package/dist/cld-video-player.min.js +2 -0
- package/dist/cld-video-player.min.js.LICENSE.txt +26 -0
- package/dist/fonts/cloudinary_icon_for_black_bg.svg +69 -0
- package/dist/fonts/cloudinary_icon_for_white_bg.svg +69 -0
- package/docs/360.html +102 -0
- package/docs/_template.html +93 -0
- package/docs/adaptive-streaming.html +297 -0
- package/docs/analytics.html +140 -0
- package/docs/api.html +302 -0
- package/docs/audio.html +136 -0
- package/docs/autoplay-fallback.html +138 -0
- package/docs/autoplay-on-scroll.html +107 -0
- package/docs/codec-fallback.html +158 -0
- package/docs/colors.html +135 -0
- package/docs/components.html +284 -0
- package/docs/custom-cld-errors.html +134 -0
- package/docs/floating-player.html +98 -0
- package/docs/fluid.html +117 -0
- package/docs/force-hls-subtitles-ios.html +159 -0
- package/docs/index.html +83 -0
- package/docs/interaction-area.html +398 -0
- package/docs/live-customer.html +128 -0
- package/docs/multiple-players.html +125 -0
- package/docs/playlist-by-tag-cap.html +182 -0
- package/docs/playlist-by-tag.html +133 -0
- package/docs/playlist.html +133 -0
- package/docs/poster.html +155 -0
- package/docs/raw-url.html +104 -0
- package/docs/recommendations.html +155 -0
- package/docs/scripts.js +156 -0
- package/docs/seek-thumbs.html +90 -0
- package/docs/shoppable.html +335 -0
- package/docs/subtitles-and-captions.html +267 -0
- package/docs/transformations.html +171 -0
- package/docs/ui-config.html +108 -0
- package/docs/vast-vpaid.html +149 -0
- package/env.example.js +6 -0
- package/env.js +6 -0
- package/jest-puppeteer.config.js +14 -0
- package/jest.config.js +196 -0
- package/package.json +99 -0
- package/sandbox.config.json +3 -0
- package/setupJest.js +1 -0
- package/src/assets/fonts/VideoJS.svg +120 -0
- package/src/assets/fonts/VideoJS.ttf +0 -0
- package/src/assets/fonts/VideoJS.woff +0 -0
- package/src/assets/fonts/icons.json +120 -0
- package/src/assets/icons/cloudinary_icon_for_black_bg.svg +69 -0
- package/src/assets/icons/cloudinary_icon_for_white_bg.svg +69 -0
- package/src/assets/icons/cloudinary_logo_for_dark_bg.svg +188 -0
- package/src/assets/icons/cloudinary_logo_for_white_bg.svg +188 -0
- package/src/assets/icons/info-circle.svg +17 -0
- package/src/assets/styles/ads-label.scss +16 -0
- package/src/assets/styles/components/interaction-areas.scss +158 -0
- package/src/assets/styles/components/playlist.scss +213 -0
- package/src/assets/styles/components/themedButton.scss +48 -0
- package/src/assets/styles/components/thumbnail.scss +94 -0
- package/src/assets/styles/components/title-bar.scss +67 -0
- package/src/assets/styles/components/triangle-volume-bar.scss +52 -0
- package/src/assets/styles/icons.scss +257 -0
- package/src/assets/styles/main.scss +324 -0
- package/src/assets/styles/mixins/aspect-ratio.scss +16 -0
- package/src/assets/styles/mixins/disable-transition.scss +3 -0
- package/src/assets/styles/mixins/mixins.scss +5 -0
- package/src/assets/styles/mixins/skin.scss +64 -0
- package/src/assets/styles/variables.scss +2 -0
- package/src/assets/styles/videojs-ima.scss +252 -0
- package/src/components/component-utils.js +20 -0
- package/src/components/index.js +21 -0
- package/src/components/interaction-area/interaction-area.const.js +30 -0
- package/src/components/interaction-area/interaction-area.service.js +223 -0
- package/src/components/interaction-area/interaction-area.utils.js +236 -0
- package/src/components/jumpButtons/jump-10-minus.js +21 -0
- package/src/components/jumpButtons/jump-10-plus.js +20 -0
- package/src/components/logoButton/logo-button.const.js +3 -0
- package/src/components/logoButton/logo-button.js +30 -0
- package/src/components/logoButton/logo-button.scss +15 -0
- package/src/components/playlist/components/playlist-button.js +34 -0
- package/src/components/playlist/components/playlist-next-button.js +18 -0
- package/src/components/playlist/components/playlist-previous-button.js +18 -0
- package/src/components/playlist/components/playlist.js +5 -0
- package/src/components/playlist/components/playlist.scss +15 -0
- package/src/components/playlist/components/upcoming-video-overlay.js +149 -0
- package/src/components/playlist/components/upcoming-video-overlay.scss +86 -0
- package/src/components/playlist/layout/playlist-layout-custom.js +21 -0
- package/src/components/playlist/layout/playlist-layout-horizontal.js +16 -0
- package/src/components/playlist/layout/playlist-layout-vertical.js +19 -0
- package/src/components/playlist/layout/playlist-layout.js +110 -0
- package/src/components/playlist/panel/playlist-panel-item.js +86 -0
- package/src/components/playlist/panel/playlist-panel.js +92 -0
- package/src/components/playlist/playlist-widget.js +119 -0
- package/src/components/playlist/playlist.const.js +14 -0
- package/src/components/playlist/playlist.js +413 -0
- package/src/components/playlist/thumbnail/thumbnail.js +69 -0
- package/src/components/progress-control-events-blocker/progress-control-events-blocker.js +17 -0
- package/src/components/qualitySelector/quality-selector.scss +10 -0
- package/src/components/qualitySelector/qualitySelector.js +152 -0
- package/src/components/recommendations-overlay/index.js +3 -0
- package/src/components/recommendations-overlay/recommendations-overlay-content.js +57 -0
- package/src/components/recommendations-overlay/recommendations-overlay-hide-button.js +18 -0
- package/src/components/recommendations-overlay/recommendations-overlay-item.js +35 -0
- package/src/components/recommendations-overlay/recommendations-overlay-primary-item.js +81 -0
- package/src/components/recommendations-overlay/recommendations-overlay-secondary-item.js +48 -0
- package/src/components/recommendations-overlay/recommendations-overlay-secondary-items-container.js +35 -0
- package/src/components/recommendations-overlay/recommendations-overlay.js +94 -0
- package/src/components/recommendations-overlay/recommendations-overlay.scss +182 -0
- package/src/components/shoppable-bar/layout/bar-layout.js +111 -0
- package/src/components/shoppable-bar/layout/shoppable-panel-toggle.js +64 -0
- package/src/components/shoppable-bar/layout/shoppable-products-overlay.js +87 -0
- package/src/components/shoppable-bar/panel/shoppable-panel-item.js +105 -0
- package/src/components/shoppable-bar/panel/shoppable-panel.js +172 -0
- package/src/components/shoppable-bar/shoppable-post-widget.js +110 -0
- package/src/components/shoppable-bar/shoppable-widget.const.js +52 -0
- package/src/components/shoppable-bar/shoppable-widget.js +111 -0
- package/src/components/shoppable-bar/shoppable-widget.scss +359 -0
- package/src/components/themeButton/themedButton.const.js +3 -0
- package/src/components/themeButton/themedButton.js +25 -0
- package/src/components/title-bar/title-bar.js +79 -0
- package/src/config/defaults.js +25 -0
- package/src/extended-events.js +228 -0
- package/src/index.js +18 -0
- package/src/mixins/eventable.js +54 -0
- package/src/mixins/playlistable.js +106 -0
- package/src/plugins/analytics/index.js +245 -0
- package/src/plugins/autoplay-on-scroll/index.js +86 -0
- package/src/plugins/cloudinary/common.js +216 -0
- package/src/plugins/cloudinary/event-handler-registry.js +46 -0
- package/src/plugins/cloudinary/index.js +345 -0
- package/src/plugins/cloudinary/models/audio-source/audio-source.const.js +11 -0
- package/src/plugins/cloudinary/models/audio-source/audio-source.js +82 -0
- package/src/plugins/cloudinary/models/base-source.js +107 -0
- package/src/plugins/cloudinary/models/image-source.js +26 -0
- package/src/plugins/cloudinary/models/video-source/video-source.const.js +32 -0
- package/src/plugins/cloudinary/models/video-source/video-source.js +239 -0
- package/src/plugins/cloudinary/models/video-source/video-source.utils.js +57 -0
- package/src/plugins/colors/index.js +303 -0
- package/src/plugins/context-menu/components/context-menu-item.js +12 -0
- package/src/plugins/context-menu/components/context-menu.js +63 -0
- package/src/plugins/context-menu/context-menu.scss +30 -0
- package/src/plugins/context-menu/contextMenuContent.js +53 -0
- package/src/plugins/context-menu/index.js +134 -0
- package/src/plugins/dash/index.js +26 -0
- package/src/plugins/dash/setup-audio-tracks.js +112 -0
- package/src/plugins/dash/setup-text-tracks.js +195 -0
- package/src/plugins/dash/videojs-dash.js +372 -0
- package/src/plugins/floating-player/floating-player.scss +74 -0
- package/src/plugins/floating-player/index.js +129 -0
- package/src/plugins/ima/index.js +1775 -0
- package/src/plugins/index.js +31 -0
- package/src/plugins/interactive-plugin/index.js +10 -0
- package/src/plugins/videojs-http-source-selector/components/SourceMenuButton.js +98 -0
- package/src/plugins/videojs-http-source-selector/components/SourceMenuItem.js +52 -0
- package/src/plugins/videojs-http-source-selector/plugin.js +82 -0
- package/src/plugins/videojs-http-source-selector/plugin.scss +9 -0
- package/src/plugins/vtt-thumbnails/index.js +526 -0
- package/src/plugins/vtt-thumbnails/vtt-thumbnails.scss +29 -0
- package/src/utils/api.js +32 -0
- package/src/utils/apply-with-props.js +32 -0
- package/src/utils/array.js +22 -0
- package/src/utils/assign.js +27 -0
- package/src/utils/attributes-normalizer.js +72 -0
- package/src/utils/cloudinary.js +165 -0
- package/src/utils/css-prefix.js +43 -0
- package/src/utils/dom.js +74 -0
- package/src/utils/find.js +28 -0
- package/src/utils/fontFace.js +25 -0
- package/src/utils/groupBy.js +12 -0
- package/src/utils/index.js +29 -0
- package/src/utils/matches.js +11 -0
- package/src/utils/mixin.js +5 -0
- package/src/utils/object.js +26 -0
- package/src/utils/playButton.js +9 -0
- package/src/utils/positioning.js +78 -0
- package/src/utils/querystring.js +12 -0
- package/src/utils/slicing.js +21 -0
- package/src/utils/string.js +15 -0
- package/src/utils/throttle.js +30 -0
- package/src/utils/time.js +77 -0
- package/src/utils/type-inference.js +35 -0
- package/src/validators/validators-functions.js +48 -0
- package/src/validators/validators-types.js +78 -0
- package/src/validators/validators.js +110 -0
- package/src/video-player.const.js +68 -0
- package/src/video-player.js +761 -0
- package/src/video-player.utils.js +123 -0
- package/test/adaptive-streaming.test.js +38 -0
- package/test/ads.test.js +35 -0
- package/test/analytics.test.js +111 -0
- package/test/api.test.js +111 -0
- package/test/autoplay.scroll.test.js +23 -0
- package/test/basic-ui.test.js +59 -0
- package/test/colors.test.js +58 -0
- package/test/components.test.js +21 -0
- package/test/custom-error.test.js +24 -0
- package/test/fluid.test.js +36 -0
- package/test/isValidConfig.test.js +224 -0
- package/test/mocks/cloudinary-core-mock.js +0 -0
- package/test/mocks/styleMock.js +1 -0
- package/test/multiplayer.test.js +25 -0
- package/test/playlist.test.js +60 -0
- package/test/puppeteer/vp-env.js +19 -0
- package/test/recommendations.test.js +38 -0
- package/test/title-bar.test.js +28 -0
- package/test/ui-conf.test.js +49 -0
- package/test/unit/cloudinaryConfig.test.js +22 -0
- package/test/unit/cloudinaryUtils.test.js +53 -0
- package/test/unit/utils.test.js +27 -0
- package/test/unit/videoSource.test.js +454 -0
- package/tsconfig.json +15 -0
- package/types/video-player-tests.js +12 -0
- package/types/video-player-tests.ts +31 -0
- 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
|
+
});
|