1/** 2 * Copyright 2014 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * IMA SDK integration plugin for Video.js. For more information see 17 * https://www.github.com/googleads/videojs-ima 18 */ 19 20(function(vjs) { 21 'use strict'; 22 var extend = function(obj) { 23 var arg; 24 var index; 25 var key; 26 for (index = 1; index < arguments.length; index++) { 27 arg = arguments[index]; 28 for (key in arg) { 29 if (arg.hasOwnProperty(key)) { 30 obj[key] = arg[key]; 31 } 32 } 33 } 34 return obj; 35 }, 36 37 ima_defaults = { 38 debug: false, 39 timeout: 5000, 40 prerollTimeout: 100, 41 adLabel: 'Advertisement' 42 }, 43 44 imaPlugin = function(options, readyCallback) { 45 var player = this; 46 47 /** 48 * Creates the ad container passed to the IMA SDK. 49 * @private 50 */ 51 player.ima.createAdContainer_ = function() { 52 // The adContainerDiv is the DOM of the element that will house 53 // the ads and ad controls. 54 vjsControls = player.getChild('controlBar'); 55 adContainerDiv = 56 vjsControls.el().parentNode.appendChild( 57 document.createElement('div')); 58 adContainerDiv.id = 'ima-ad-container'; 59 adContainerDiv.style.position = "absolute"; 60 adContainerDiv.style.zIndex = 1111; 61 adContainerDiv.addEventListener( 62 'mouseover', 63 player.ima.showAdControls_, 64 false); 65 adContainerDiv.addEventListener( 66 'mouseout', 67 player.ima.hideAdControls_, 68 false); 69 player.ima.createControls_(); 70 adDisplayContainer = 71 new google.ima.AdDisplayContainer(adContainerDiv, contentPlayer); 72 }; 73 74 /** 75 * Creates the controls for the ad. 76 * @private 77 */ 78 player.ima.createControls_ = function() { 79 controlsDiv = document.createElement('div'); 80 controlsDiv.id = 'ima-controls-div'; 81 controlsDiv.style.width = '100%'; 82 countdownDiv = document.createElement('div'); 83 countdownDiv.id = 'ima-countdown-div'; 84 countdownDiv.innerHTML = settings.adLabel; 85 countdownDiv.style.display = showCountdown ? 'block' : 'none'; 86 seekBarDiv = document.createElement('div'); 87 seekBarDiv.id = 'ima-seek-bar-div'; 88 seekBarDiv.style.width = '100%'; 89 progressDiv = document.createElement('div'); 90 progressDiv.id = 'ima-progress-div'; 91 playPauseDiv = document.createElement('div'); 92 playPauseDiv.id = 'ima-play-pause-div'; 93 playPauseDiv.className = 'ima-playing'; 94 playPauseDiv.addEventListener( 95 'click', 96 player.ima.onAdPlayPauseClick_, 97 false); 98 muteDiv = document.createElement('div'); 99 muteDiv.id = 'ima-mute-div'; 100 muteDiv.className = 'ima-non-muted'; 101 muteDiv.addEventListener( 102 'click', 103 player.ima.onAdMuteClick_, 104 false); 105 sliderDiv = document.createElement('div'); 106 sliderDiv.id = 'ima-slider-div'; 107 sliderDiv.addEventListener( 108 'mousedown', 109 player.ima.onAdVolumeSliderMouseDown_, 110 false); 111 sliderLevelDiv = document.createElement('div'); 112 sliderLevelDiv.id = 'ima-slider-level-div'; 113 fullscreenDiv = document.createElement('div'); 114 fullscreenDiv.id = 'ima-fullscreen-div'; 115 fullscreenDiv.className = 'ima-non-fullscreen'; 116 fullscreenDiv.addEventListener( 117 'click', 118 player.ima.onAdFullscreenClick_, 119 false); 120 adContainerDiv.appendChild(controlsDiv); 121 controlsDiv.appendChild(countdownDiv); 122 controlsDiv.appendChild(seekBarDiv); 123 controlsDiv.appendChild(playPauseDiv); 124 controlsDiv.appendChild(muteDiv); 125 controlsDiv.appendChild(sliderDiv); 126 controlsDiv.appendChild(fullscreenDiv); 127 seekBarDiv.appendChild(progressDiv); 128 sliderDiv.appendChild(sliderLevelDiv); 129 }; 130 131 /** 132 * Initializes the AdDisplayContainer. On mobile, this must be done as a 133 * result of user action. 134 */ 135 player.ima.initializeAdDisplayContainer = function() { 136 adDisplayContainerInitialized = true; 137 adDisplayContainer.initialize(); 138 } 139 140 /** 141 * Creates the AdsRequest and request ads through the AdsLoader. 142 */ 143 player.ima.requestAds = function() { 144 if (!adDisplayContainerInitialized) { 145 adDisplayContainer.initialize(); 146 } 147 var adsRequest = new google.ima.AdsRequest(); 148 adsRequest.adTagUrl = settings.adTagUrl; 149 if (settings.forceNonLinearFullSlot) { 150 adsRequest.forceNonLinearFullSlot = true; 151 } 152 153 adsRequest.linearAdSlotWidth = player.ima.getPlayerWidth(); 154 adsRequest.linearAdSlotHeight = player.ima.getPlayerHeight(); 155 adsRequest.nonLinearAdSlotWidth = 156 settings.nonLinearWidth || player.ima.getPlayerWidth(); 157 adsRequest.nonLinearAdSlotHeight = 158 settings.nonLinearHeight || (player.ima.getPlayerHeight() / 3); 159 160 adsLoader.requestAds(adsRequest); 161 }; 162 163 /** 164 * Listener for the ADS_MANAGER_LOADED event. Creates the AdsManager, 165 * sets up event listeners, and triggers the 'adsready' event for 166 * videojs-ads-contrib. 167 * @private 168 */ 169 player.ima.onAdsManagerLoaded_ = function(adsManagerLoadedEvent) { 170 adsManager = adsManagerLoadedEvent.getAdsManager( 171 contentPlayheadTracker, adsRenderingSettings); 172 173 adsManager.addEventListener( 174 google.ima.AdErrorEvent.Type.AD_ERROR, 175 player.ima.onAdError_); 176 adsManager.addEventListener( 177 google.ima.AdEvent.Type.AD_BREAK_READY, 178 player.ima.onAdBreakReady_); 179 adsManager.addEventListener( 180 google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, 181 player.ima.onContentPauseRequested_); 182 adsManager.addEventListener( 183 google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, 184 player.ima.onContentResumeRequested_); 185 adsManager.addEventListener( 186 google.ima.AdEvent.Type.ALL_ADS_COMPLETED, 187 player.ima.onAllAdsCompleted_); 188 189 adsManager.addEventListener( 190 google.ima.AdEvent.Type.LOADED, 191 player.ima.onAdLoaded_); 192 adsManager.addEventListener( 193 google.ima.AdEvent.Type.STARTED, 194 player.ima.onAdStarted_); 195 adsManager.addEventListener( 196 google.ima.AdEvent.Type.CLICK, 197 player.ima.onAdPlayPauseClick_); 198 adsManager.addEventListener( 199 google.ima.AdEvent.Type.COMPLETE, 200 player.ima.onAdComplete_); 201 adsManager.addEventListener( 202 google.ima.AdEvent.Type.SKIPPED, 203 player.ima.onAdComplete_); 204 205 if (!autoPlayAdBreaks) { 206 try { 207 var initWidth = player.ima.getPlayerWidth(); 208 var initHeight = player.ima.getPlayerHeight(); 209 adsManagerDimensions.width = initWidth; 210 adsManagerDimensions.height = initHeight; 211 adsManager.init( 212 initWidth, 213 initHeight, 214 google.ima.ViewMode.NORMAL); 215 adsManager.setVolume(player.muted() ? 0 : player.volume()); 216 } catch (adError) { 217 player.ima.onAdError_(adError); 218 } 219 } 220 221 player.trigger('adsready'); 222 }; 223 224 /** 225 * Start ad playback, or content video playback in the absence of a 226 * pre-roll. 227 */ 228 player.ima.start = function() { 229 if (autoPlayAdBreaks) { 230 try { 231 adsManager.init( 232 player.ima.getPlayerWidth(), 233 player.ima.getPlayerHeight(), 234 google.ima.ViewMode.NORMAL); 235 adsManager.setVolume(player.muted() ? 0 : player.volume()); 236 adsManager.start(); 237 } catch (adError) { 238 player.ima.onAdError_(adError); 239 } 240 } 241 }; 242 243 /** 244 * Listener for errors fired by the AdsLoader. 245 * @param {google.ima.AdErrorEvent} event The error event thrown by the 246 * AdsLoader. See 247 * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdError.Type 248 * @private 249 */ 250 player.ima.onAdsLoaderError_ = function(event) { 251 window.console.log('AdsLoader error: ' + event.getError()); 252 if (adsManager) { 253 adsManager.destroy(); 254 } 255 player.trigger('adserror'); 256 }; 257 258 /** 259 * Listener for errors thrown by the AdsManager. 260 * @param {google.ima.AdErrorEvent} adErrorEvent The error event thrown by 261 * the AdsManager. 262 * @private 263 */ 264 player.ima.onAdError_ = function(adErrorEvent) { 265 window.console.log('Ad error: ' + adErrorEvent.getError()); 266 vjsControls.show(); 267 adsManager.destroy(); 268 adContainerDiv.style.display = 'none'; 269 player.trigger('adserror'); 270 }; 271 272 /** 273 * Listener for AD_BREAK_READY. Passes event on to publisher's listener. 274 * @param {google.ima.AdEvent} adEvent AdEvent thrown by the AdsManager. 275 * @private 276 */ 277 player.ima.onAdBreakReady_ = function(adEvent) { 278 adBreakReadyListener(adEvent); 279 }; 280 281 /** 282 * Called by publishers in manual ad break playback mode to start an ad 283 * break. 284 */ 285 player.ima.playAdBreak = function() { 286 if (!autoPlayAdBreaks) { 287 adsManager.start(); 288 } 289 } 290 291 /** 292 * Pauses the content video and displays the ad container so ads can play. 293 * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager. 294 * @private 295 */ 296 player.ima.onContentPauseRequested_ = function(adEvent) { 297 adsActive = true; 298 adPlaying = true; 299 player.off('ended', localContentEndedListener); 300 if (adEvent.getAd().getAdPodInfo().getPodIndex() != -1) { 301 // Skip this call for post-roll ads 302 player.ads.startLinearAdMode(); 303 } 304 adContainerDiv.style.display = 'block'; 305 controlsDiv.style.display = 'block'; 306 vjsControls.hide(); 307 player.pause(); 308 }; 309 310 /** 311 * Resumes content video and hides the ad container. 312 * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager. 313 * @private 314 */ 315 player.ima.onContentResumeRequested_ = function(adEvent) { 316 adsActive = false;
317 adPlaying = false; 318 player.on('ended', localContentEndedListener); 319 if (currentAd && currentAd.isLinear()) { 320 adContainerDiv.style.display = 'none'; 321 } 322 vjsControls.show(); 323 if (!currentAd) { 324 // Something went wrong playing the ad 325 player.ads.endLinearAdMode(); 326 } else if (!contentComplete && 327 // Don't exit linear mode after post-roll or content will auto-replay 328 currentAd.getAdPodInfo().getPodIndex() != -1 ) { 329 player.ads.endLinearAdMode(); 330 } 331 countdownDiv.innerHTML = ''; 332 }; 333 334 /** 335 * Records that ads have completed and calls contentAndAdsEndedListeners 336 * if content is also complete. 337 * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager. 338 * @private 339 */ 340 player.ima.onAllAdsCompleted_ = function(adEvent) { 341 allAdsCompleted = true; 342 if (contentComplete == true) { 343 for (var index in contentAndAdsEndedListeners) { 344 contentAndAdsEndedListeners[index](); 345 } 346 } 347 } 348 349 /** 350 * Starts the content video when a non-linear ad is loaded. 351 * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager. 352 * @private 353 */ 354 player.ima.onAdLoaded_ = function(adEvent) { 355 if (!adEvent.getAd().isLinear()) { 356 player.play(); 357 } 358 }; 359 360 /** 361 * Starts the interval timer to check the current ad time when an ad starts 362 * playing. 363 * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager. 364 * @private 365 */ 366 player.ima.onAdStarted_ = function(adEvent) { 367 currentAd = adEvent.getAd(); 368 if (currentAd.isLinear()) { 369 adTrackingTimer = setInterval( 370 player.ima.onAdPlayheadTrackerInterval_, 250); 371 // Don't bump container when controls are shown 372 adContainerDiv.className = ''; 373 } else { 374 // Bump container when controls are shown 375 adContainerDiv.className = 'bumpable-ima-ad-container'; 376 } 377 }; 378 379 /** 380 * Clears the interval timer for current ad time when an ad completes. 381 * @param {google.ima.AdEvent} adEvent The AdEvent thrown by the AdsManager. 382 * @private 383 */ 384 player.ima.onAdComplete_ = function(adEvent) { 385 if (currentAd.isLinear()) { 386 clearInterval(adTrackingTimer); 387 } 388 }; 389 390 /** 391 * Gets the current time and duration of the ad and calls the method to 392 * update the ad UI. 393 * @private 394 */ 395 player.ima.onAdPlayheadTrackerInterval_ = function() { 396 var remainingTime = adsManager.getRemainingTime(); 397 var duration = currentAd.getDuration(); 398 var currentTime = duration - remainingTime; 399 currentTime = currentTime > 0 ? currentTime : 0; 400 var isPod = false; 401 var adPosition, totalAds; 402 if (currentAd.getAdPodInfo()) { 403 isPod = true; 404 adPosition = currentAd.getAdPodInfo().getAdPosition(); 405 totalAds = currentAd.getAdPodInfo().getTotalAds(); 406 } 407 408 // Update countdown timer data 409 var remainingMinutes = Math.floor(remainingTime / 60); 410 var remainingSeconds = Math.floor(remainingTime % 60); 411 if (remainingSeconds.toString().length < 2) { 412 remainingSeconds = '0' + remainingSeconds; 413 } 414 var podCount = ': '; 415 if (isPod) { 416 podCount = ' (' + adPosition + ' of ' + totalAds + '): '; 417 } 418 countdownDiv.innerHTML = 419 settings.adLabel + podCount + 420 remainingMinutes + ':' + remainingSeconds; 421 422 // Update UI 423 var playProgressRatio = currentTime / duration; 424 var playProgressPercent = playProgressRatio * 100; 425 progressDiv.style.width = playProgressPercent + '%'; 426 }; 427 428 player.ima.getPlayerWidth = function() { 429 var retVal = parseInt(getComputedStyle(player.el()).width, 10) || 430 player.width(); 431 return retVal; 432 }; 433 434 player.ima.getPlayerHeight = function() { 435 var retVal = parseInt(getComputedStyle(player.el()).height, 10) || 436 player.height(); 437 return retVal; 438 } 439 440 /** 441 * Hides the ad controls on mouseout. 442 * @private 443 */ 444 player.ima.hideAdControls_ = function() { 445 playPauseDiv.style.display = 'none'; 446 muteDiv.style.display = 'none'; 447 fullscreenDiv.style.display = 'none'; 448 controlsDiv.style.height = '14px'; 449 }; 450 451 /** 452 * Shows ad controls on mouseover. 453 * @private 454 */ 455 player.ima.showAdControls_ = function() { 456 controlsDiv.style.height = '37px'; 457 playPauseDiv.style.display = 'block'; 458 muteDiv.style.display = 'block'; 459 sliderDiv.style.display = 'block'; 460 fullscreenDiv.style.display = 'block'; 461 }; 462 463 /** 464 * Listener for clicks on the play/pause button during ad playback. 465 * @private 466 */ 467 player.ima.onAdPlayPauseClick_ = function() { 468 if (adPlaying) { 469 playPauseDiv.className = 'ima-paused'; 470 adsManager.pause(); 471 adPlaying = false;
472 } else { 473 playPauseDiv.className = 'ima-playing'; 474 adsManager.resume(); 475 adPlaying = true; 476 } 477 }; 478 479 /** 480 * Listener for clicks on the mute button during ad playback. 481 * @private 482 */ 483 player.ima.onAdMuteClick_ = function() { 484 if (adMuted) { 485 muteDiv.className = 'ima-non-muted'; 486 adsManager.setVolume(1); 487 // Bubble down to content player 488 player.muted(false); 489 adMuted = false; 490 sliderLevelDiv.style.width = player.volume() * 100 + "%"; 491 } else { 492 muteDiv.className = 'ima-muted'; 493 adsManager.setVolume(0); 494 // Bubble down to content player 495 player.muted(true); 496 adMuted = true; 497 sliderLevelDiv.style.width = "0%"; 498 } 499 }; 500 501 /* Listener for mouse down events during ad playback. Used for volume. 502 * @private 503 */ 504 player.ima.onAdVolumeSliderMouseDown_ = function() { 505 document.addEventListener('mouseup', player.ima.onMouseUp_, false); 506 document.addEventListener('mousemove', player.ima.onMouseMove_, false); 507 } 508 509 /* Mouse movement listener used for volume slider. 510 * @private 511 */ 512 player.ima.onMouseMove_ = function(event) { 513 player.ima.setVolumeSlider_(event); 514 } 515 516 /* Mouse release listener used for volume slider. 517 * @private 518 */ 519 player.ima.onMouseUp_ = function(event) { 520 player.ima.setVolumeSlider_(event); 521 document.removeEventListener('mousemove', player.ima.onMouseMove_); 522 document.removeEventListener('mouseup', player.ima.onMouseUp_); 523 } 524 525 /* Utility function to set volume and associated UI 526 * @private 527 */ 528 player.ima.setVolumeSlider_ = function(event) { 529 var percent = 530 (event.clientX - sliderDiv.getBoundingClientRect().left) / 531 sliderDiv.offsetWidth; 532 percent *= 100; 533 //Bounds value 0-100 if mouse is outside slider region. 534 percent = Math.min(Math.max(percent, 0), 100); 535 sliderLevelDiv.style.width = percent + "%"; 536 player.volume(percent / 100); //0-1 537 adsManager.setVolume(percent / 100); 538 if (player.volume() == 0) { 539 muteDiv.className = 'ima-muted'; 540 player.muted(true); 541 adMuted = true; 542 } 543 else 544 { 545 muteDiv.className = 'ima-non-muted'; 546 player.muted(false); 547 adMuted = false; 548 } 549 } 550 551 /** 552 * Listener for clicks on the fullscreen button during ad playback. 553 * @private 554 */ 555 player.ima.onAdFullscreenClick_ = function() { 556 if (player.isFullscreen()) { 557 player.exitFullscreen(); 558 } else { 559 player.requestFullscreen(); 560 } 561 }; 562 563 /** 564 * Listens for the video.js player to change its fullscreen status. This 565 * keeps the fullscreen-ness of the AdContainer in sync with the player. 566 * @private 567 */ 568 player.ima.onFullscreenChange_ = function() { 569 if (player.isFullscreen()) { 570 fullscreenDiv.className = 'ima-fullscreen'; 571 if (adsManager) { 572 adsManager.resize( 573 window.screen.width, 574 window.screen.height, 575 google.ima.ViewMode.FULLSCREEN); 576 } 577 } else { 578 fullscreenDiv.className = 'ima-non-fullscreen'; 579 if (adsManager) { 580 adsManager.resize( 581 player.ima.getPlayerWidth(), 582 player.ima.getPlayerHeight(), 583 google.ima.ViewMode.NORMAL); 584 } 585 } 586 }; 587 588 /** 589 * Listens for the video.js player to change its volume. This keeps the ad 590 * volume in sync with the content volume if the volume of the player is 591 * changed while content is playing 592 * @private 593 */ 594 player.ima.onVolumeChange_ = function() { 595 var newVolume = player.muted() ? 0 : player.volume(); 596 if (adsManager) { 597 adsManager.setVolume(newVolume); 598 } 599 // Update UI 600 if (newVolume == 0) { 601 adMuted = true; 602 muteDiv.className = 'ima-muted'; 603 sliderLevelDiv.style.width = '0%'; 604 } else { 605 adMuted = false; 606 muteDiv.className = 'ima-non-muted'; 607 sliderLevelDiv.style.width = newVolume * 100 + '%'; 608 } 609 }; 610 611 /** 612 * Seeks content to 00:00:00. This is used as an event handler for the 613 * loadedmetadata event, since seeking is not possible until that event has 614 * fired. 615 * @private 616 */ 617 player.ima.seekContentToZero_ = function() { 618 player.off('loadedmetadata', player.ima.seekContentToZero_); 619 player.currentTime(0); 620 }; 621 622 /** 623 * Seeks content to 00:00:00 and starts playback. This is used as an event 624 * handler for the loadedmetadata event, since seeking is not possible until 625 * that event has fired. 626 * @private 627 */ 628 player.ima.playContentFromZero_ = function() { 629 player.off('loadedmetadata', player.ima.playContentFromZero_); 630 player.currentTime(0); 631 player.play(); 632 }; 633 634 /** 635 * Destroys the AdsManager, sets it to null, and calls contentComplete to 636 * reset correlators. Once this is done it requests ads again to keep the 637 * inventory available. 638 * @private 639 */ 640 player.ima.resetIMA_ = function() { 641 adsActive = false;
642 adPlaying = false; 643 player.on('ended', localContentEndedListener); 644 if (currentAd && currentAd.isLinear()) { 645 adContainerDiv.style.display = 'none'; 646 } 647 vjsControls.show(); 648 player.ads.endLinearAdMode(); 649 if (adTrackingTimer) { 650 // If this is called while an ad is playing, stop trying to get that 651 // ad's current time. 652 clearInterval(adTrackingTimer); 653 } 654 if (adsManager) { 655 adsManager.destroy(); 656 adsManager = null; 657 } 658 if (adsLoader && !contentComplete) { 659 adsLoader.contentComplete(); 660 } 661 contentComplete = false; 662 allAdsCompleted = false; 663 }; 664 665 /** 666 * Ads an EventListener to the AdsManager. For a list of available events, 667 * see 668 * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type 669 * @param {google.ima.AdEvent.Type} event The AdEvent.Type for which to listen. 670 * @param {function} callback The method to call when the event is fired. 671 */ 672 player.ima.addEventListener = function(event, callback) { 673 if (adsManager) { 674 adsManager.addEventListener(event, callback); 675 } 676 }; 677 678 /** 679 * Returns the instance of the AdsManager. 680 * @return {google.ima.AdsManager} The AdsManager being used by the plugin. 681 */ 682 player.ima.getAdsManager = function() { 683 return adsManager; 684 }; 685 686 /** 687 * Sets the content of the video player. You should use this method instead 688 * of setting the content src directly to ensure the proper ad tag is 689 * requested when the video content is loaded. 690 * @param {?string} contentSrc The URI for the content to be played. Leave 691 * blank to use the existing content. 692 * @param {?string} adTag The ad tag to be requested when the content loads. 693 * Leave blank to use the existing ad tag. 694 * @param {?boolean} playOnLoad True to play the content once it has loaded, 695 * false to only load the content but not start playback. 696 */ 697 player.ima.setContent = 698 function(contentSrc, adTag, playOnLoad) { 699 player.ima.resetIMA_(); 700 settings.adTagUrl = adTag ? adTag : settings.adTagUrl; 701 //only try to pause the player when initialised with a source already 702 if (!!player.currentSrc()) { 703 player.currentTime(0); 704 player.pause(); 705 } 706 if (contentSrc) { 707 player.src(contentSrc); 708 } 709 if (playOnLoad) { 710 player.on('loadedmetadata', player.ima.playContentFromZero_); 711 } else { 712 player.on('loadedmetadata', player.ima.seekContentToZero_); 713 } 714 }; 715 716 /** 717 * Adds a listener for the 'ended' event of the video player. This should be 718 * used instead of setting an 'ended' listener directly to ensure that the 719 * ima can do proper cleanup of the SDK before other event listeners 720 * are called. 721 * @param {function} listener The listener to be called when content completes. 722 */ 723 player.ima.addContentEndedListener = function(listener) { 724 contentEndedListeners.push(listener); 725 }; 726 727 /** 728 * Adds a listener that will be called when content and all ads have 729 * finished playing. 730 * @param {function} listener The listener to be called when content and ads complete. 731 */ 732 player.ima.addContentAndAdsEndedListener = function(listener) { 733 contentAndAdsEndedListeners.push(listener); 734 } 735 736 /** 737 * Sets the listener to be called to trigger manual ad break playback. 738 * @param {function} listener The listener to be called to trigger manual ad break playback. 739 */ 740 player.ima.setAdBreakReadyListener = function(listener) { 741 adBreakReadyListener = listener; 742 } 743 744 /** 745 * Pauses the ad. 746 */ 747 player.ima.pauseAd = function() { 748 if (adsActive && adPlaying) { 749 playPauseDiv.className = 'ima-paused'; 750 adsManager.pause(); 751 adPlaying = false; 752 } 753 }; 754 755 /** 756 * Resumes the ad. 757 */ 758 player.ima.resumeAd = function() { 759 if (adsActive && !adPlaying) { 760 playPauseDiv.className = 'ima-playing'; 761 adsManager.resume(); 762 adPlaying = true; 763 } 764 }; 765 766 /** 767 * Set up intervals to check for seeking and update current video time. 768 * @private 769 */ 770 player.ima.setUpPlayerIntervals_ = function() { 771 updateTimeIntervalHandle = 772 setInterval(player.ima.updateCurrentTime_, seekCheckInterval); 773 seekCheckIntervalHandle = 774 setInterval(player.ima.checkForSeeking_, seekCheckInterval); 775 resizeCheckIntervalHandle = 776 setInterval(player.ima.checkForResize_, resizeCheckInterval); 777 }; 778 779 /** 780 * Updates the current time of the video 781 * @private 782 */ 783 player.ima.updateCurrentTime_ = function() { 784 if (!contentPlayheadTracker.seeking) { 785 contentPlayheadTracker.currentTime = player.currentTime(); 786 } 787 }; 788 789 /** 790 * Detects when the user is seeking through a video. 791 * This is used to prevent mid-rolls from playing while a user is seeking. 792 * 793 * There *is* a seeking property of the HTML5 video element, but it's not 794 * properly implemented on all platforms (e.g. mobile safari), so we have to 795 * check ourselves to be sure. 796 * 797 * @private 798 */ 799 player.ima.checkForSeeking_ = function() { 800 var tempCurrentTime = player.currentTime(); 801 var diff = (tempCurrentTime - contentPlayheadTracker.previousTime) * 1000; 802 if (Math.abs(diff) > seekCheckInterval + seekThreshold) { 803 contentPlayheadTracker.seeking = true; 804 } else { 805 contentPlayheadTracker.seeking = false;
806 } 807 contentPlayheadTracker.previousTime = player.currentTime(); 808 }; 809 810 /** 811 * Detects when the player is resized (for fluid support) and resizes the 812 * ads manager to match. 813 * 814 * @private 815 */ 816 player.ima.checkForResize_ = function() { 817 var currentWidth = player.ima.getPlayerWidth(); 818 var currentHeight = player.ima.getPlayerHeight(); 819 820 if (adsManager && (currentWidth != adsManagerDimensions.width || 821 currentHeight != adsManagerDimensions.height)) { 822 adsManagerDimensions.width = currentWidth; 823 adsManagerDimensions.height = currentHeight; 824 adsManager.resize(currentWidth, currentHeight, google.ima.ViewMode.NORMAL); 825 } 826 } 827 828 /** 829 * Changes the flag to show or hide the ad countdown timer. 830 * 831 * @param {boolean} showCountdownIn Show or hide the countdown timer. 832 */ 833 player.ima.setShowCountdown = function(showCountdownIn) { 834 showCountdown = showCountdownIn; 835 countdownDiv.style.display = showCountdown ? 'block' : 'none'; 836 }; 837 838 /** 839 * Current plugin version. 840 */ 841 var VERSION = '0.2.0'; 842 843 /** 844 * Stores user-provided settings. 845 */ 846 var settings; 847 848 /** 849 * Video element playing content. 850 */ 851 var contentPlayer; 852 853 /** 854 * Boolean flag to show or hide the ad countdown timer. 855 */ 856 var showCountdown; 857 858 /** 859 * Boolena flag to enable manual ad break playback. 860 */ 861 var autoPlayAdBreaks; 862 863 /** 864 * Video.js control bar. 865 */ 866 var vjsControls; 867 868 /** 869 * Div used as an ad container. 870 */ 871 var adContainerDiv; 872 873 /** 874 * Div used to display ad controls. 875 */ 876 var controlsDiv; 877 878 /** 879 * Div used to display ad countdown timer. 880 */ 881 var countdownDiv; 882 883 /** 884 * Div used to display add seek bar. 885 */ 886 var seekBarDiv; 887 888 /** 889 * Div used to display ad progress (in seek bar). 890 */ 891 var progressDiv; 892 893 /** 894 * Div used to display ad play/pause button. 895 */ 896 var playPauseDiv; 897 898 /** 899 * Div used to display ad mute button. 900 */ 901 var muteDiv; 902 903 /** 904 * Div used by the volume slider. 905 */ 906 var sliderDiv; 907 908 /** 909 * Volume slider level visuals 910 */ 911 var sliderLevelDiv; 912 913 /** 914 * Div used to display ad fullscreen button. 915 */ 916 var fullscreenDiv; 917 918 /** 919 * IMA SDK AdDisplayContainer. 920 */ 921 var adDisplayContainer; 922 923 /** 924 * True if the AdDisplayContainer has been initialized. False otherwise. 925 */ 926 var adDisplayContainerInitialized = false; 927 928 /** 929 * IMA SDK AdsLoader 930 */ 931 var adsLoader; 932 933 /** 934 * IMA SDK AdsManager 935 */ 936 var adsManager; 937 938 /** 939 * IMA SDK AdsRenderingSettings. 940 */ 941 var adsRenderingSettings = null; 942 943 /** 944 * Ad tag URL. Should return VAST, VMAP, or ad rules. 945 */ 946 var adTagUrl; 947 948 /** 949 * Current IMA SDK Ad. 950 */ 951 var currentAd; 952 953 /** 954 * Timer used to track content progress. 955 */ 956 var contentTrackingTimer; 957 958 /** 959 * Timer used to track ad progress. 960 */ 961 var adTrackingTimer; 962 963 /** 964 * True if ads are currently displayed, false otherwise. 965 * True regardless of ad pause state if an ad is currently being displayed. 966 */ 967 var adsActive = false; 968 969 /** 970 * True if ad is currently playing, false if ad is paused or ads are not 971 * currently displayed. 972 */ 973 var adPlaying = false; 974 975 /** 976 * True if the ad is muted, false otherwise. 977 */ 978 var adMuted = false; 979 980 /** 981 * True if our content video has completed, false otherwise. 982 */ 983 var contentComplete = false; 984 985 /** 986 * True if ALL_ADS_COMPLETED has fired, false until then. 987 */ 988 var allAdsCompleted = false; 989 990 /** 991 * Handle to interval that repeatedly updates current time. 992 */ 993 var updateTimeIntervalHandle; 994 995 /** 996 * Handle to interval that repeatedly checks for seeking. 997 */ 998 var seekCheckIntervalHandle; 999 1000 /** 1001 * Interval (ms) on which to check if the user is seeking through the 1002 * content. 1003 */ 1004 var seekCheckInterval = 1000; 1005 1006 /** 1007 * Handle to interval that repeatedly checks for player resize. 1008 */ 1009 var resizeCheckIntervalHandle; 1010 1011 /** 1012 * Interval (ms) to check for player resize for fluid support. 1013 */ 1014 var resizeCheckInterval = 250; 1015 1016 /** 1017 * Threshold by which to judge user seeking. We check every 1000 ms to see 1018 * if the user is seeking. In order for us to decide that they are *not* 1019 * seeking, the content video playhead must only change by 900-1100 ms 1020 * between checks. Any greater change and we assume the user is seeking 1021 * through the video. 1022 */ 1023 var seekThreshold = 100; 1024 1025 /** 1026 * Stores data for the content playhead tracker. 1027 */ 1028 var contentPlayheadTracker = { 1029 currentTime: 0, 1030 previousTime: 0, 1031 seeking: false, 1032 duration: 0 1033 }; 1034 1035 /** 1036 * Stores data for the ad playhead tracker. 1037 */ 1038 var adPlayheadTracker = { 1039 currentTime: 0, 1040 duration: 0, 1041 isPod: false, 1042 adPosition: 0, 1043 totalAds: 0 1044 }; 1045 1046 /** 1047 * Stores the dimensions for the ads manager. 1048 */ 1049 var adsManagerDimensions = { 1050 width: 0, 1051 height: 0 1052 }; 1053 1054 /** 1055 * Content ended listeners passed by the publisher to the plugin. Publishers 1056 * should allow the plugin to handle content ended to ensure proper support 1057 * of custom ad playback. 1058 */ 1059 var contentEndedListeners = []; 1060 1061 /** 1062 * Content and ads ended listeners passed by the publisher to the plugin. 1063 * These will be called when the plugin detects that content *and all 1064 * ads* have completed. This differs from the contentEndedListeners in that 1065 * contentEndedListeners will fire between content ending and a post-roll 1066 * playing, whereas the contentAndAdsEndedListeners will fire after the 1067 * post-roll completes. 1068 */ 1069 var contentAndAdsEndedListeners = []; 1070 1071 /** 1072 * Listener to be called to trigger manual ad break playback. 1073 */ 1074 var adBreakReadyListener = undefined; 1075 1076 /** 1077 * Local content ended listener for contentComplete. 1078 */ 1079 var localContentEndedListener = function() { 1080 if (adsLoader && !contentComplete) { 1081 adsLoader.contentComplete(); 1082 contentComplete = true; 1083 } 1084 for (var index in contentEndedListeners) { 1085 contentEndedListeners[index](); 1086 } 1087 if (allAdsCompleted) { 1088 for (var index in contentAndAdsEndedListeners) { 1089 contentAndAdsEndedListeners[index](); 1090 } 1091 } 1092 clearInterval(updateTimeIntervalHandle); 1093 clearInterval(seekCheckIntervalHandle); 1094 clearInterval(resizeCheckIntervalHandle); 1095 player.one('play', player.ima.setUpPlayerIntervals_); 1096 }; 1097 1098 settings = extend({}, ima_defaults, options || {}); 1099 1100 // Currently this isn't used but I can see it being needed in the future, so 1101 // to avoid implementation problems with later updates I'm requiring it. 1102 if (!settings['id']) { 1103 window.console.log('Error: must provide id of video.js div'); 1104 return; 1105 } 1106 contentPlayer = document.getElementById(settings['id'] + '_html5_api'); 1107 // Default showing countdown timer to true. 1108 showCountdown = true; 1109 if (settings['showCountdown'] == false) { 1110 showCountdown = false;
1111 } 1112 1113 autoPlayAdBreaks = true; 1114 if (settings['autoPlayAdBreaks'] == false) { 1115 autoPlayAdBreaks = false; 1116 } 1117 1118 player.one('play', player.ima.setUpPlayerIntervals_); 1119 1120 player.on('ended', localContentEndedListener); 1121 1122 var contrib_ads_defaults = { 1123 debug: settings.debug, 1124 timeout: settings.timeout, 1125 prerollTimeout: settings.prerollTimeout 1126 }; 1127 1128 var ads_plugin_settings = 1129 extend({}, contrib_ads_defaults, options['contribAdsSettings'] || {}); 1130 1131 player.ads(ads_plugin_settings); 1132 1133 adsRenderingSettings = new google.ima.AdsRenderingSettings(); 1134 adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; 1135 if (settings['adsRenderingSettings']) { 1136 for (var setting in settings['adsRenderingSettings']) { 1137 adsRenderingSettings[setting] = 1138 settings['adsRenderingSettings'][setting]; 1139 } 1140 } 1141 1142 if (settings['locale']) { 1143 google.ima.settings.setLocale(settings['locale']); 1144 } 1145 1146 player.ima.createAdContainer_(); 1147 1148 adsLoader = new google.ima.AdsLoader(adDisplayContainer); 1149 1150 adsLoader.getSettings().setVpaidMode( 1151 google.ima.ImaSdkSettings.VpaidMode.ENABLED); 1152 if (settings.vpaidAllowed == false) { 1153 adsLoader.getSettings().setVpaidMode( 1154 google.ima.ImaSdkSettings.VpaidMode.DISABLED); 1155 } 1156 if (settings.vpaidMode) { 1157 adsLoader.getSettings().setVpaidMode(settings.vpaidMode); 1158 } 1159 1160 if (settings.locale) { 1161 adsLoader.getSettings().setLocale(settings.locale); 1162 } 1163 1164 if (settings.numRedirects) { 1165 adsLoader.getSettings().setNumRedirects(settings.numRedirects); 1166 } 1167 1168 adsLoader.getSettings().setPlayerType('videojs-ima'); 1169 adsLoader.getSettings().setPlayerVersion(VERSION); 1170 adsLoader.getSettings().setAutoPlayAdBreaks(autoPlayAdBreaks); 1171 1172 adsLoader.addEventListener( 1173 google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, 1174 player.ima.onAdsManagerLoaded_, 1175 false); 1176 adsLoader.addEventListener( 1177 google.ima.AdErrorEvent.Type.AD_ERROR, 1178 player.ima.onAdsLoaderError_, 1179 false); 1180 1181 if (!readyCallback) { 1182 readyCallback = player.ima.start; 1183 } 1184 player.on('readyforpreroll', readyCallback); 1185 player.ready(function() { 1186 player.on('fullscreenchange', player.ima.onFullscreenChange_); 1187 player.on('volumechange', player.ima.onVolumeChange_); 1188 }); 1189 }; 1190 1191 videojs.plugin('ima', imaPlugin); 1192}(window.videojs));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.