1/** 2 * videojs-contrib-ads 3 * @version 4.1.6 4 * @copyright 2016 Brightcove 5 * @license Apache-2.0 6 */ 7(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsContribAds = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 8(function (global){ 9'use strict'; 10 11var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 12 13var _video2 = _interopRequireDefault(_video); 14 15function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 17var cancelContentPlay = function cancelContentPlay(player) { 18 if (player.ads.cancelPlayTimeout) { 19 // another cancellation is already in flight, so do nothing 20 return; 21 } 22 23 // Avoid content flash on non-iPad iOS and iPhones on iOS10 with playsinline 24 if (_video2.default.browser.IS_IOS && _video2.default.browser.IS_IPHONE && !player.el_.hasAttribute('playsinline')) { 25 (function () { 26 27 var width = player.currentWidth ? player.currentWidth() : player.width(); 28 var height = player.currentHeight ? player.currentHeight() : player.height(); 29 30 // A placeholder black box will be shown in the document while the player is hidden. 31 var placeholder = document.createElement('div'); 32 33 placeholder.style.width = width + 'px'; 34 placeholder.style.height = height + 'px'; 35 placeholder.style.background = 'black'; 36 player.el_.parentNode.insertBefore(placeholder, player.el_); 37 38 // Hide the player. While in full-screen video playback mode on iOS, this 39 // makes the player show a black screen instead of content flash. 40 player.el_.style.display = 'none'; 41 42 // Unhide the player and remove the placeholder once we're ready to move on. 43 player.one(['adstart', 'adplaying', 'adtimeout', 'adserror', 'adscanceled', 'adskip', 'playing'], function () { 44 player.el_.style.display = 'block'; 45 placeholder.remove(); 46 }); 47 48 // Detect fullscreen change, if returning from fullscreen and placeholder exists, 49 // remove placeholder and show player whether or not playsinline was attached. 50 player.on('fullscreenchange', function () { 51 if (placeholder && player.hasClass('vjs-fullscreen')) { 52 player.el_.style.display = 'block'; 53 placeholder.remove(); 54 } 55 }); 56 })(); 57 } 58 59 // The timeout is necessary because pausing a video element while processing a `play` 60 // event on iOS can cause the video element to continuously toggle between playing and 61 // paused states. 62 player.ads.cancelPlayTimeout = window.setTimeout(function () { 63 // deregister the cancel timeout so subsequent cancels are scheduled 64 player.ads.cancelPlayTimeout = null; 65 66 // pause playback so ads can be handled. 67 if (!player.paused()) { 68 player.pause(); 69 } 70 71 // When the 'content-playback' state is entered, this will let us know to play 72 player.ads.cancelledPlay = true; 73 }, 1); 74}; /* 75 This feature makes sure the player is paused during ad loading. 76 77 It does this by pausing the player immediately after a "play" where ads will be requested, 78 then signalling that we should play after the ad is done. 79 */ 80 81module.exports = cancelContentPlay; 82}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 83},{}],2:[function(require,module,exports){ 84'use strict'; 85 86/* 87This feature sends a `contentupdate` event when the player source changes. 88*/ 89 90// Start sending contentupdate events 91var initializeContentupdate = function initializeContentupdate(player) { 92 93 // Keep track of the current content source 94 // If you want to change the src of the video without triggering 95 // the ad workflow to restart, you can update this variable before
96 // modifying the player's source 97 player.ads.contentSrc = player.currentSrc(); 98 99 // Check if a new src has been set, if so, trigger contentupdate 100 var checkSrc = function checkSrc() { 101 if (player.ads.state !== 'ad-playback') { 102 var src = player.currentSrc(); 103 104 if (src !== player.ads.contentSrc) { 105 player.trigger({ 106 type: 'contentupdate', 107 oldValue: player.ads.contentSrc, 108 newValue: src 109 }); 110 player.ads.contentSrc = src; 111 } 112 } 113 }; 114 115 // loadstart reliably indicates a new src has been set 116 player.on('loadstart', checkSrc); 117 // check immediately in case we missed the loadstart 118 window.setTimeout(checkSrc, 1); 119}; 120 121module.exports = initializeContentupdate; 122},{}],3:[function(require,module,exports){ 123(function (global){ 124'use strict'; 125 126var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /* 127 This feature provides an optional method for ad integrations to insert run-time values 128 into an ad server URL or configuration. 129 */ 130 131var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 132 133var _video2 = _interopRequireDefault(_video); 134 135function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 136 137// Return URI encoded version of value if uriEncode is true 138var uriEncodeIfNeeded = function uriEncodeIfNeeded(value, uriEncode) { 139 if (uriEncode) { 140 return encodeURIComponent(value); 141 } 142 return value; 143}; 144 145// Add custom field macros to macros object 146// based on given name for custom fields property of mediainfo object. 147var customFields = function customFields(mediainfo, macros, customFieldsName) { 148 if (mediainfo && mediainfo[customFieldsName]) { 149 var _customFields = mediainfo[customFieldsName]; 150 var fieldNames = Object.keys(_customFields); 151 152 for (var i = 0; i < fieldNames.length; i++) { 153 var tag = '{mediainfo.' + customFieldsName + '.' + fieldNames[i] + '}'; 154 155 macros[tag] = _customFields[fieldNames[i]]; 156 } 157 } 158}; 159 160// Public method that integrations use for ad macros. 161// "string" is any string with macros to be replaced 162// "uriEncode" if true will uri encode macro values when replaced 163// "customMacros" is a object with custom macros and values to map them to 164// - For example: {'{five}': 5} 165// Return value is is "string" with macros replaced 166// - For example: adMacroReplacement('{player.id}') returns a string of the player id 167var adMacroReplacement = function adMacroReplacement(string, uriEncode, customMacros) { 168 169 if (uriEncode === undefined) { 170 uriEncode = false; 171 } 172 173 var macros = {}; 174 175 if (customMacros !== undefined) { 176 macros = customMacros; 177 } 178 179 // Static macros 180 macros['{player.id}'] = this.options_['data-player']; 181 macros['{mediainfo.id}'] = this.mediainfo ? this.mediainfo.id : ''; 182 macros['{mediainfo.name}'] = this.mediainfo ? this.mediainfo.name : ''; 183 macros['{mediainfo.description}'] = this.mediainfo ? this.mediainfo.description : ''; 184 macros['{mediainfo.tags}'] = this.mediainfo ? this.mediainfo.tags : ''; 185 macros['{mediainfo.reference_id}'] = this.mediainfo ? this.mediainfo.reference_id : ''; 186 macros['{mediainfo.duration}'] = this.mediainfo ? this.mediainfo.duration : ''; 187 macros['{mediainfo.ad_keys}'] = this.mediainfo ? this.mediainfo.ad_keys : ''; 188 macros['{player.duration}'] = this.duration(); 189 macros['{timestamp}'] = new Date().getTime(); 190 macros['{document.referrer}'] = document.referrer; 191 macros['{window.location.href}'] = window.location.href; 192 macros['{random}'] = Math.floor(Math.random() * 1000000000000); 193 194 // Custom fields in mediainfo 195 customFields(this.mediainfo, macros, 'custom_fields'); 196 customFields(this.mediainfo, macros, 'customFields'); 197 198 // Go through all the replacement macros and apply them to the string. 199 // This will replace all occurrences of the replacement macros. 200 for (var i in macros) { 201 string = string.split(i).join(uriEncodeIfNeeded(macros[i], uriEncode)); 202 } 203 204 // Page variables 205 string = string.replace(/{pageVariable\.([^}]+)}/g, function (match, name) { 206 var value = void 0; 207 var context = window; 208 var names = name.split('.'); 209 210 // Iterate down multiple levels of selector without using eval 211 // This makes things like pageVariable.foo.bar work 212 for (var _i = 0; _i < names.length; _i++) { 213 if (_i === names.length - 1) { 214 value = context[names[_i]]; 215 } else { 216 context = context[names[_i]]; 217 } 218 } 219 220 var type = typeof value === 'undefined' ? 'undefined' : _typeof(value); 221 222 // Only allow certain types of values. Anything else is probably a mistake. 223 if (value === null) { 224 return 'null';
225 } else if (value === undefined) { 226 _video2.default.log.warn('Page variable "' + name + '" not found'); 227 return ''; 228 } else if (type !== 'string' && type !== 'number' && type !== 'boolean') { 229 _video2.default.log.warn('Page variable "' + name + '" is not a supported type'); 230 return ''; 231 } 232 233 return uriEncodeIfNeeded(String(value), uriEncode); 234 }); 235 236 return string; 237}; 238 239module.exports = adMacroReplacement; 240}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 241},{}],4:[function(require,module,exports){ 242(function (global){ 243'use strict'; 244 245var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 246 247var _video2 = _interopRequireDefault(_video); 248 249function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 250 251// Stop propogation for an event 252var cancelEvent = function cancelEvent(player, event) { 253 // Pretend we called stopImmediatePropagation because we want the native 254 // element events to continue propagating 255 event.isImmediatePropagationStopped = function () { 256 return true; 257 }; 258 event.cancelBubble = true; 259 event.isPropagationStopped = function () { 260 return true; 261 }; 262}; 263 264// Stop propogation for an event, then send a new event with the type of the original 265// event with the given prefix added. 266/* 267The goal of this feature is to make player events work as an integrator would 268expect despite the presense of ads. For example, an integrator would expect 269an `ended` event to happen once the content is ended. If an `ended` event is sent 270as a result of an ad ending, that is a bug. The `redispatch` method should recognize 271such `ended` events and prefix them so they are sent as `adended`, and so on with 272all other player events. 273*/ 274 275var prefixEvent = function prefixEvent(player, prefix, event) { 276 cancelEvent(player, event); 277 player.trigger({ 278 type: prefix + event.type, 279 state: player.ads.state, 280 originalEvent: event 281 }); 282}; 283 284// Handle a player event, either by redispatching it with a prefix, or by 285// letting it go on its way without any meddling. 286var redispatch = function redispatch(event) { 287 288 // We do a quick play/pause before we check for prerolls. This creates a "playing" 289 // event. This conditional block prefixes that event so it's "adplaying" if it 290 // happens while we're in the "preroll?" state. Not every browser is in the 291 // "preroll?" state for this event, so the following browsers come through here: 292 // * iPad 293 // * iPhone 294 // * Android 295 // * Safari 296 // This is too soon to check videoElementRecycled because there is no snapshot 297 // yet. We rely on the coincidence that all browsers for which 298 // videoElementRecycled would be true also happen to send their initial playing 299 // event during "preroll?" 300 if (event.type === 'playing' && this.ads.state === 'preroll?') { 301 prefixEvent(this, 'ad', event); 302 303 // Here we send "adplaying" for browsers that send their initial "playing" event 304 // (caused by the the initial play/pause) during the "ad-playback" state. 305 // The following browsers come through here: 306 // * Chrome 307 // * IE11 308 // If the ad plays in the content tech (aka videoElementRecycled) there will be 309 // another playing event when the ad starts. We check videoElementRecycled to 310 // avoid a second adplaying event. Thankfully, at this point a snapshot exists 311 // so we can safely check videoElementRecycled. 312 } else if (event.type === 'playing' && this.ads.state === 'ad-playback' && !this.ads.videoElementRecycled()) { 313 prefixEvent(this, 'ad', event); 314 315 // If the ad takes a long time to load, "playing" caused by play/pause can happen 316 // during "ads-ready?" instead of "preroll?" or "ad-playback", skipping the 317 // other conditions that would normally catch it 318 } else if (event.type === 'playing' && this.ads.state === 'ads-ready?') { 319 prefixEvent(this, 'ad', event); 320 321 // When an ad is playing in content tech, we would normally prefix 322 // "playing" with "ad" to send "adplaying". However, when we did a play/pause 323 // before the preroll, we already sent "adplaying". This condition prevents us 324 // from sending another. 325 } else if (event.type === 'playing' && this.ads.state === 'ad-playback' && this.ads.videoElementRecycled()) { 326 cancelEvent(this, event); 327 return; 328
329 // When ad is playing in content tech, prefix everything with "ad". 330 // This block catches many events such as emptied, play, timeupdate, and ended. 331 } else if (this.ads.state === 'ad-playback') { 332 if (this.ads.videoElementRecycled() || this.ads.stitchedAds()) { 333 prefixEvent(this, 'ad', event); 334 } 335 336 // Send contentended if ended happens during content. 337 // We will make sure an ended event is sent after postrolls. 338 } else if (this.ads.state === 'content-playback' && event.type === 'ended') { 339 prefixEvent(this, 'content', event); 340 341 // Event prefixing during content resuming is complicated 342 } else if (this.ads.state === 'content-resuming') { 343 344 // This does not happen during normal circumstances. I wasn't able to reproduce 345 // it, but the working theory is that it handles cases where restoring the 346 // snapshot takes a long time, such as in iOS7 and older Firefox. 347 if (this.ads.snapshot && this.currentSrc() !== this.ads.snapshot.currentSrc) { 348 349 // Don't prefix `loadstart` event 350 if (event.type === 'loadstart') { 351 return; 352 } 353 354 // All other events get "content" prefix 355 return prefixEvent(this, 'content', event); 356 357 // Content resuming after postroll 358 } else if (this.ads.snapshot && this.ads.snapshot.ended) { 359 360 // Don't prefix `pause` and `ended` events 361 // They don't always happen during content-resuming, but they might. 362 // It seems to happen most often on iOS and Android. 363 if (event.type === 'pause' || event.type === 'ended') { 364 return; 365 } 366 367 // All other events get "content" prefix 368 return prefixEvent(this, 'content', event); 369 } 370 371 // Content resuming after preroll or midroll 372 // Events besides "playing" get "content" prefix 373 if (event.type !== 'playing') { 374 prefixEvent(this, 'content', event); 375 } 376 } 377}; 378 379module.exports = redispatch; 380}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 381},{}],5:[function(require,module,exports){ 382(function (global){ 383'use strict'; 384 385var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 386 387var _video2 = _interopRequireDefault(_video); 388 389function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 390 391var snapshot = {}; 392 393/** 394 * Returns an object that captures the portions of player state relevant to 395 * video playback. The result of this function can be passed to 396 * restorePlayerSnapshot with a player to return the player to the state it 397 * was in when this function was invoked. 398 * @param {object} player The videojs player object 399 */ 400/* 401The snapshot feature is responsible for saving the player state before an ad, then 402restoring the player state after an ad. 403*/ 404 405snapshot.getPlayerSnapshot = function (player) { 406 407 var currentTime = void 0; 408 409 if (_video2.default.browser.IS_IOS && player.ads.isLive(player)) { 410 // Record how far behind live we are 411 if (player.seekable().length > 0) { 412 currentTime = player.currentTime() - player.seekable().end(0); 413 } else { 414 currentTime = player.currentTime(); 415 } 416 } else { 417 currentTime = player.currentTime(); 418 } 419 420 var tech = player.$('.vjs-tech'); 421 var tracks = player.remoteTextTracks ? player.remoteTextTracks() : []; 422 var suppressedTracks = []; 423 var snapshot = { 424 ended: player.ended(), 425 currentSrc: player.currentSrc(), 426 src: player.src(), 427 currentTime: currentTime, 428 type: player.currentType() 429 }; 430 431 if (tech) { 432 snapshot.nativePoster = tech.poster; 433 snapshot.style = tech.getAttribute('style'); 434 } 435 436 for (var i = 0; i < tracks.length; i++) { 437 var track = tracks[i]; 438 439 suppressedTracks.push({ 440 track: track, 441 mode: track.mode 442 }); 443 track.mode = 'disabled'; 444 } 445 snapshot.suppressedTracks = suppressedTracks; 446 447 return snapshot; 448}; 449 450/** 451 * Attempts to modify the specified player so that its state is equivalent to 452 * the state of the snapshot. 453 * @param {object} snapshot - the player state to apply 454 */ 455snapshot.restorePlayerSnapshot = function (player, snapshot) { 456 457 if (player.ads.disableNextSnapshotRestore === true) { 458 player.ads.disableNextSnapshotRestore = false;
459 return; 460 } 461 462 // The playback tech 463 var tech = player.$('.vjs-tech'); 464 465 // the number of[ remaining attempts to restore the snapshot 466 var attempts = 20; 467 468 var suppressedTracks = snapshot.suppressedTracks; 469 var trackSnapshot = void 0; 470 var restoreTracks = function restoreTracks() { 471 for (var i = 0; i < suppressedTracks.length; i++) { 472 trackSnapshot = suppressedTracks[i]; 473 trackSnapshot.track.mode = trackSnapshot.mode; 474 } 475 }; 476 477 // finish restoring the playback state 478 var resume = function resume() { 479 var currentTime = void 0; 480 481 if (_video2.default.browser.IS_IOS && player.ads.isLive(player)) { 482 if (snapshot.currentTime < 0) { 483 // Playback was behind real time, so seek backwards to match 484 if (player.seekable().length > 0) { 485 currentTime = player.seekable().end(0) + snapshot.currentTime; 486 } else { 487 currentTime = player.currentTime(); 488 } 489 player.currentTime(currentTime); 490 } 491 } else { 492 player.currentTime(snapshot.ended ? player.duration() : snapshot.currentTime); 493 } 494 495 // Resume playback if this wasn't a postroll 496 if (!snapshot.ended) { 497 player.play(); 498 } 499 }; 500 501 // determine if the video element has loaded enough of the snapshot source 502 // to be ready to apply the rest of the state 503 var tryToResume = function tryToResume() { 504 505 // tryToResume can either have been called through the `contentcanplay` 506 // event or fired through setTimeout. 507 // When tryToResume is called, we should make sure to clear out the other 508 // way it could've been called by removing the listener and clearing out 509 // the timeout. 510 player.off('contentcanplay', tryToResume); 511 if (player.ads.tryToResumeTimeout_) { 512 player.clearTimeout(player.ads.tryToResumeTimeout_); 513 player.ads.tryToResumeTimeout_ = null; 514 } 515 516 // Tech may have changed depending on the differences in sources of the 517 // original video and that of the ad 518 tech = player.el().querySelector('.vjs-tech'); 519 520 if (tech.readyState > 1) { 521 // some browsers and media aren't "seekable". 522 // readyState greater than 1 allows for seeking without exceptions 523 return resume(); 524 } 525 526 if (tech.seekable === undefined) { 527 // if the tech doesn't expose the seekable time ranges, try to 528 // resume playback immediately 529 return resume(); 530 } 531 532 if (tech.seekable.length > 0) { 533 // if some period of the video is seekable, resume playback 534 return resume(); 535 } 536 537 // delay a bit and then check again unless we're out of attempts 538 if (attempts--) { 539 window.setTimeout(tryToResume, 50); 540 } else { 541 try { 542 resume(); 543 } catch (e) { 544 _video2.default.log.warn('Failed to resume the content after an advertisement', e); 545 } 546 } 547 }; 548 549 if (snapshot.nativePoster) { 550 tech.poster = snapshot.nativePoster; 551 } 552 553 if ('style' in snapshot) { 554 // overwrite all css style properties to restore state precisely 555 tech.setAttribute('style', snapshot.style || ''); 556 } 557 558 // Determine whether the player needs to be restored to its state 559 // before ad playback began. With a custom ad display or burned-in 560 // ads, the content player state hasn't been modified and so no 561 // restoration is required 562 563 if (player.ads.videoElementRecycled()) { 564 // on ios7, fiddling with textTracks too early will cause safari to crash 565 player.one('contentloadedmetadata', restoreTracks); 566 567 // if the src changed for ad playback, reset it 568 player.src({ src: snapshot.currentSrc, type: snapshot.type }); 569 // safari requires a call to `load` to pick up a changed source 570 player.load(); 571 // and then resume from the snapshots time once the original src has loaded 572 // in some browsers (firefox) `canplay` may not fire correctly. 573 // Reace the `canplay` event with a timeout. 574 player.one('contentcanplay', tryToResume); 575 player.ads.tryToResumeTimeout_ = player.setTimeout(tryToResume, 2000); 576 } else if (!player.ended() || !snapshot.ended) { 577 // if we didn't change the src, just restore the tracks 578 restoreTracks(); 579 // the src didn't change and this wasn't a postroll 580 // just resume playback at the current time. 581 player.play(); 582 } 583}; 584 585module.exports = snapshot; 586}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 587},{}],6:[function(require,module,exports){ 588(function (global){ 589'use strict'; 590 591var _video = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 592 593var _video2 = _interopRequireDefault(_video); 594 595var _redispatch = require('./redispatch.js'); 596 597var _redispatch2 = _interopRequireDefault(_redispatch); 598 599var _snapshot = require('./snapshot.js'); 600 601var _snapshot2 = _interopRequireDefault(_snapshot); 602 603var _contentupdate = require('./contentupdate.js'); 604 605var _contentupdate2 = _interopRequireDefault(_contentupdate); 606
607var _cancelContentPlay = require('./cancelContentPlay.js'); 608 609var _cancelContentPlay2 = _interopRequireDefault(_cancelContentPlay); 610 611var _macros = require('./macros.js'); 612 613var _macros2 = _interopRequireDefault(_macros); 614 615function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 616 617/* 618This main plugin file is responsible for integration logic and enabling the features 619that live in in separate files. 620*/ 621 622var VIDEO_EVENTS = _video2.default.getComponent('Html5').Events; 623 624/** 625 * Remove the poster attribute from the video element tech, if present. When 626 * reusing a video element for multiple videos, the poster image will briefly 627 * reappear while the new source loads. Removing the attribute ahead of time 628 * prevents the poster from showing up between videos. 629 * @param {object} player The videojs player object 630 */ 631var removeNativePoster = function removeNativePoster(player) { 632 var tech = player.$('.vjs-tech'); 633 634 if (tech) { 635 tech.removeAttribute('poster'); 636 } 637}; 638 639// --------------------------------------------------------------------------- 640// Ad Framework 641// --------------------------------------------------------------------------- 642 643// default framework settings 644var defaults = { 645 // maximum amount of time in ms to wait to receive `adsready` from the ad 646 // implementation after play has been requested. Ad implementations are 647 // expected to load any dynamic libraries and make any requests to determine 648 // ad policies for a video during this time. 649 timeout: 5000, 650 651 // maximum amount of time in ms to wait for the ad implementation to start 652 // linear ad mode after `readyforpreroll` has fired. This is in addition to 653 // the standard timeout. 654 prerollTimeout: 100, 655 656 // maximum amount of time in ms to wait for the ad implementation to start 657 // linear ad mode after `contentended` has fired. 658 postrollTimeout: 100, 659 660 // when truthy, instructs the plugin to output additional information about 661 // plugin state to the video.js log. On most devices, the video.js log is 662 // the same as the developer console. 663 debug: false, 664 665 // set this to true when using ads that are part of the content video 666 stitchedAds: false 667}; 668 669var contribAdsPlugin = function contribAdsPlugin(options) { 670 671 var player = this; // eslint-disable-line consistent-this 672 673 var settings = _video2.default.mergeOptions(defaults, options); 674 675 // prefix all video element events during ad playback 676 // if the video element emits ad-related events directly, 677 // plugins that aren't ad-aware will break. prefixing allows 678 // plugins that wish to handle ad events to do so while 679 // avoiding the complexity for common usage 680 var videoEvents = VIDEO_EVENTS.concat(['firstplay', 'loadedalldata', 'playing']); 681 682 // Set up redispatching of player events 683 player.on(videoEvents, _redispatch2.default); 684 685 // "vjs-has-started" should be present at the end of a video. This makes sure it's 686 // always there. 687 player.on('ended', function () { 688 if (!player.hasClass('vjs-has-started')) { 689 player.addClass('vjs-has-started'); 690 } 691 }); 692 693 // We now auto-play when an ad gets loaded if we're playing ads in the same video 694 // element as the content. 695 // The problem is that in IE11, we cannot play in addurationchange but in iOS8, we 696 // cannot play from adcanplay. 697 // This will prevent ad-integrations from needing to do this themselves. 698 player.on(['addurationchange', 'adcanplay'], function () { 699 if (player.currentSrc() === player.ads.snapshot.currentSrc) { 700 return; 701 } 702 703 player.play(); 704 }); 705 706 player.on('nopreroll', function () { 707 player.ads.nopreroll_ = true; 708 }); 709 710 player.on('nopostroll', function () { 711 player.ads.nopostroll_ = true; 712 }); 713 714 // Remove ad-loading class when ad plays or when content plays (in case there was no ad) 715 // If you remove this class too soon you can get a flash of content! 716 player.on(['ads-ad-started', 'playing'], function () { 717 player.removeClass('vjs-ad-loading'); 718 }); 719 720 // Replace the plugin constructor with the ad namespace 721 player.ads = { 722 state: 'content-set', 723 disableNextSnapshotRestore: false, 724 725 // This is set to true if the content has ended once. After that, the user can 726 // seek backwards and replay content, but _contentHasEnded remains true. 727 _contentHasEnded: false, 728 729 VERSION: '4.1.6', 730 731 reset: function reset() { 732 player.ads.disableNextSnapshotRestore = false;
733 player.ads._contentHasEnded = false; 734 player.ads.snapshot = null; 735 }, 736 737 738 // Call this when an ad response has been received and there are 739 // linear ads ready to be played. 740 startLinearAdMode: function startLinearAdMode() { 741 if (player.ads.state === 'preroll?' || player.ads.state === 'content-playback' || player.ads.state === 'postroll?') { 742 player.trigger('adstart'); 743 } 744 }, 745 746 747 // Call this when a linear ad pod has finished playing. 748 endLinearAdMode: function endLinearAdMode() { 749 if (player.ads.state === 'ad-playback') { 750 player.trigger('adend'); 751 // In the case of an empty ad response, we want to make sure that 752 // the vjs-ad-loading class is always removed. We could probably check for 753 // duration on adPlayer for an empty ad but we remove it here just to make sure 754 player.removeClass('vjs-ad-loading'); 755 } 756 }, 757 758 759 // Call this when an ad response has been received but there are no 760 // linear ads to be played (i.e. no ads available, or overlays). 761 // This has no effect if we are already in a linear ad mode. Always 762 // use endLinearAdMode() to exit from linear ad-playback state. 763 skipLinearAdMode: function skipLinearAdMode() { 764 if (player.ads.state !== 'ad-playback') { 765 player.trigger('adskip'); 766 } 767 }, 768 stitchedAds: function stitchedAds(arg) { 769 if (arg !== undefined) { 770 this._stitchedAds = !!arg; 771 } 772 return this._stitchedAds; 773 }, 774 775 776 // Returns whether the video element has been modified since the 777 // snapshot was taken. 778 // We test both src and currentSrc because changing the src attribute to a URL that 779 // AdBlocker is intercepting doesn't update currentSrc. 780 videoElementRecycled: function videoElementRecycled() { 781 var srcChanged = void 0; 782 var currentSrcChanged = void 0; 783 784 if (!this.snapshot) { 785 throw new Error('You cannot use videoElementRecycled while there is no snapshot.'); 786 } 787 788 srcChanged = player.src() !== this.snapshot.src; 789 currentSrcChanged = player.currentSrc() !== this.snapshot.currentSrc; 790 791 return srcChanged || currentSrcChanged; 792 }, 793 794 795 // Returns a boolean indicating if given player is in live mode. 796 // Can be replaced when this is fixed: https://github.com/videojs/video.js/issues/3262 797 isLive: function isLive(somePlayer) { 798 if (somePlayer.duration() === Infinity) { 799 return true; 800 } else if (_video2.default.browser.IOS_VERSION === '8' && somePlayer.duration() === 0) { 801 return true; 802 } 803 return false; 804 }, 805 806 807 // Return true if content playback should mute and continue during ad breaks. 808 // This is only done during live streams on platforms where it's supported. 809 // This improves speed and accuracy when returning from an ad break. 810 shouldPlayContentBehindAd: function shouldPlayContentBehindAd(somePlayer) { 811 return !_video2.default.browser.IS_IOS && !_video2.default.browser.IS_ANDROID && somePlayer.duration() === Infinity; 812 } 813 }; 814 815 player.ads.stitchedAds(settings.stitchedAds); 816 817 player.ads.adMacroReplacement = _macros2.default.bind(player); 818 819 // Start sending contentupdate events for this player 820 (0, _contentupdate2.default)(player); 821 822 // Global contentupdate handler for resetting plugin state 823 player.on('contentupdate', player.ads.reset); 824 825 // Ad Playback State Machine 826 var states = { 827 'content-set': { 828 events: { 829 adscanceled: function adscanceled() { 830 this.state = 'content-playback'; 831 }, 832 adsready: function adsready() { 833 this.state = 'ads-ready'; 834 }, 835 play: function play() { 836 this.state = 'ads-ready?'; 837 (0, _cancelContentPlay2.default)(player); 838 // remove the poster so it doesn't flash between videos 839 removeNativePoster(player); 840 }, 841 adserror: function adserror() { 842 this.state = 'content-playback'; 843 }, 844 adskip: function adskip() { 845 this.state = 'content-playback'; 846 } 847 } 848 }, 849 'ads-ready': { 850 events: { 851 play: function play() { 852 this.state = 'preroll?'; 853 (0, _cancelContentPlay2.default)(player); 854 }, 855 adskip: function adskip() { 856 this.state = 'content-playback'; 857 }, 858 adserror: function adserror() { 859 this.state = 'content-playback'; 860 } 861 } 862 }, 863 'preroll?': { 864 enter: function enter() { 865 if (player.ads.nopreroll_) { 866 // This will start the ads manager in case there are later ads 867 player.trigger('readyforpreroll'); 868 869 // If we don't wait a tick, entering content-playback will cancel 870 // cancelPlayTimeout, causing the video to not pause for the ad 871 window.setTimeout(function () { 872 // Don't wait for a preroll 873 player.trigger('nopreroll'); 874 }, 1); 875 } else { 876 // change class to show that we're waiting on ads 877 player.addClass('vjs-ad-loading'); 878 // schedule an adtimeout event to fire if we waited too long 879 player.ads.adTimeoutTimeout = window.setTimeout(function () { 880 player.trigger('adtimeout'); 881 }, settings.prerollTimeout); 882 // signal to ad plugin that it's their opportunity to play a preroll 883 player.trigger('readyforpreroll'); 884 } 885 }, 886 leave: function leave() { 887 window.clearTimeout(player.ads.adTimeoutTimeout); 888 }, 889 890 events: { 891 play: function play() { 892 (0, _cancelContentPlay2.default)(player); 893 }, 894 adstart: function adstart() { 895 this.state = 'ad-playback'; 896 }, 897 adskip: function adskip() { 898 this.state = 'content-playback'; 899 }, 900 adtimeout: function adtimeout() { 901 this.state = 'content-playback'; 902 }, 903 adserror: function adserror() { 904 this.state = 'content-playback'; 905 }, 906 nopreroll: function nopreroll() { 907 this.state = 'content-playback'; 908 } 909 } 910 }, 911 'ads-ready?': { 912 enter: function enter() { 913 player.addClass('vjs-ad-loading'); 914 player.ads.adTimeoutTimeout = window.setTimeout(function () { 915 player.trigger('adtimeout'); 916 }, settings.timeout); 917 }, 918 leave: function leave() { 919 window.clearTimeout(player.ads.adTimeoutTimeout); 920 player.removeClass('vjs-ad-loading'); 921 }, 922 923 events: { 924 play: function play() { 925 (0, _cancelContentPlay2.default)(player); 926 }, 927 adscanceled: function adscanceled() { 928 this.state = 'content-playback'; 929 }, 930 adsready: function adsready() { 931 this.state = 'preroll?'; 932 }, 933 adskip: function adskip() { 934 this.state = 'content-playback'; 935 }, 936 adtimeout: function adtimeout() { 937 this.state = 'content-playback'; 938 }, 939 adserror: function adserror() { 940 this.state = 'content-playback'; 941 } 942 } 943 }, 944 'ad-playback': { 945 enter: function enter() { 946 // capture current player state snapshot (playing, currentTime, src) 947 if (!player.ads.shouldPlayContentBehindAd(player)) { 948 this.snapshot = _snapshot2.default.getPlayerSnapshot(player); 949 } 950 951 // Mute the player behind the ad 952 if (player.ads.shouldPlayContentBehindAd(player)) { 953 this.preAdVolume_ = player.volume(); 954 player.volume(0); 955 } 956 957 // add css to the element to indicate and ad is playing. 958 player.addClass('vjs-ad-playing'); 959 960 // We should remove the vjs-live class if it has been added in order to 961 // show the adprogress control bar on Android devices for falsely 962 // determined LIVE videos due to the duration incorrectly reported as Infinity 963 if (player.hasClass('vjs-live')) { 964 player.removeClass('vjs-live'); 965 } 966 967 // remove the poster so it doesn't flash between ads 968 removeNativePoster(player); 969 970 // We no longer need to supress play events once an ad is playing. 971 // Clear it if we were. 972 if (player.ads.cancelPlayTimeout) { 973 // If we don't wait a tick, we could cancel the pause for cancelContentPlay, 974 // resulting in content playback behind the ad 975 window.setTimeout(function () { 976 window.clearTimeout(player.ads.cancelPlayTimeout); 977 player.ads.cancelPlayTimeout = null; 978 }, 1); 979 } 980 }, 981 leave: function leave() { 982 player.removeClass('vjs-ad-playing'); 983 984 // We should add the vjs-live class back if the video is a LIVE video 985 // If we dont do this, then for a LIVE Video, we will get an incorrect 986 // styled control, which displays the time for the video 987 if (player.ads.isLive(player)) { 988 player.addClass('vjs-live'); 989 } 990 if (!player.ads.shouldPlayContentBehindAd(player)) { 991 _snapshot2.default.restorePlayerSnapshot(player, this.snapshot); 992 } 993 994 // Reset the volume to pre-ad levels 995 if (player.ads.shouldPlayContentBehindAd(player)) { 996 player.volume(this.preAdVolume_); 997 } 998 }, 999 1000 events: { 1001 adend: function adend() { 1002 this.state = 'content-resuming';
1003 }, 1004 adserror: function adserror() { 1005 this.state = 'content-resuming'; 1006 // Trigger 'adend' to notify that we are exiting 'ad-playback' 1007 player.trigger('adend'); 1008 } 1009 } 1010 }, 1011 'content-resuming': { 1012 enter: function enter() { 1013 if (this._contentHasEnded) { 1014 window.clearTimeout(player.ads._fireEndedTimeout); 1015 // in some cases, ads are played in a swf or another video element 1016 // so we do not get an ended event in this state automatically. 1017 // If we don't get an ended event we can use, we need to trigger 1018 // one ourselves or else we won't actually ever end the current video. 1019 player.ads._fireEndedTimeout = window.setTimeout(function () { 1020 player.trigger('ended'); 1021 }, 1000); 1022 } 1023 }, 1024 leave: function leave() { 1025 window.clearTimeout(player.ads._fireEndedTimeout); 1026 }, 1027 1028 events: { 1029 contentupdate: function contentupdate() { 1030 this.state = 'content-set'; 1031 }, 1032 contentresumed: function contentresumed() { 1033 this.state = 'content-playback'; 1034 }, 1035 playing: function playing() { 1036 this.state = 'content-playback'; 1037 }, 1038 ended: function ended() { 1039 this.state = 'content-playback'; 1040 } 1041 } 1042 }, 1043 'postroll?': { 1044 enter: function enter() { 1045 this.snapshot = _snapshot2.default.getPlayerSnapshot(player); 1046 if (player.ads.nopostroll_) { 1047 window.setTimeout(function () { 1048 // content-resuming happens after the timeout for backward-compatibility 1049 // with plugins that relied on a postrollTimeout before nopostroll was 1050 // implemented 1051 player.ads.state = 'content-resuming'; 1052 player.trigger('ended'); 1053 }, 1); 1054 } else { 1055 player.addClass('vjs-ad-loading'); 1056 1057 player.ads.adTimeoutTimeout = window.setTimeout(function () { 1058 player.trigger('adtimeout'); 1059 }, settings.postrollTimeout); 1060 } 1061 }, 1062 leave: function leave() { 1063 window.clearTimeout(player.ads.adTimeoutTimeout); 1064 player.removeClass('vjs-ad-loading'); 1065 }, 1066 1067 events: { 1068 adstart: function adstart() { 1069 this.state = 'ad-playback'; 1070 }, 1071 adskip: function adskip() { 1072 this.state = 'content-resuming'; 1073 window.setTimeout(function () { 1074 player.trigger('ended'); 1075 }, 1); 1076 }, 1077 adtimeout: function adtimeout() { 1078 this.state = 'content-resuming'; 1079 window.setTimeout(function () { 1080 player.trigger('ended'); 1081 }, 1); 1082 }, 1083 adserror: function adserror() { 1084 this.state = 'content-resuming'; 1085 window.setTimeout(function () { 1086 player.trigger('ended'); 1087 }, 1); 1088 }, 1089 contentupdate: function contentupdate() { 1090 this.state = 'ads-ready?'; 1091 } 1092 } 1093 }, 1094 'content-playback': { 1095 enter: function enter() { 1096 // make sure that any cancelPlayTimeout is cleared 1097 if (player.ads.cancelPlayTimeout) { 1098 window.clearTimeout(player.ads.cancelPlayTimeout); 1099 player.ads.cancelPlayTimeout = null; 1100 } 1101 1102 // This was removed because now that "playing" is fixed to only play after 1103 // preroll, any integration should just use the "playing" event. However, 1104 // we found out some 3rd party code relied on this event, so we've temporarily 1105 // added it back in to give people more time to update their code. 1106 player.trigger({ 1107 type: 'contentplayback', 1108 triggerevent: player.ads.triggerevent 1109 }); 1110 1111 // Play the content 1112 if (player.ads.cancelledPlay) { 1113 player.ads.cancelledPlay = false; 1114 if (player.paused()) { 1115 player.play(); 1116 } 1117 } 1118 }, 1119 1120 events: { 1121 // In the case of a timeout, adsready might come in late. 1122 // This assumes the behavior that if an ad times out, it c
1122ould still 1123 // interrupt the content and start playing. An integration could 1124 // still decide to behave otherwise. 1125 adsready: function adsready() { 1126 player.trigger('readyforpreroll'); 1127 }, 1128 adstart: function adstart() { 1129 this.state = 'ad-playback'; 1130 }, 1131 contentupdate: function contentupdate() { 1132 // We know sources have changed, so we call CancelContentPlay 1133 // to avoid playback of video in the background of an ad. Playback Occurs on 1134 // Android devices if we do not call cancelContentPlay. This is because 1135 // the sources do not get updated in time on Android due to timing issues. 1136 // So instead of checking if the sources have changed in the play handler 1137 // and calling cancelContentPlay() there we call it here. 1138 // This does not happen on Desktop as the sources do get updated in time. 1139 if (!player.ads.shouldPlayContentBehindAd(player)) { 1140 (0, _cancelContentPlay2.default)(player); 1141 } 1142 if (player.paused()) { 1143 this.state = 'content-set'; 1144 } else { 1145 this.state = 'ads-ready?'; 1146 } 1147 }, 1148 contentended: function contentended() { 1149 1150 // If _contentHasEnded is true it means we already checked for postrolls and 1151 // played postrolls if needed, so now we're ready to send an ended event 1152 if (this._contentHasEnded) { 1153 // Causes ended event to trigger in content-resuming.enter. 1154 // From there, the ended event event is not redispatched. 1155 // Then we end up back in content-playback state. 1156 this.state = 'content-resuming'; 1157 return; 1158 } 1159 1160 this._contentHasEnded = true; 1161 this.state = 'postroll?'; 1162 } 1163 } 1164 } 1165 }; 1166 1167 var processEvent = function processEvent(event) { 1168 1169 var state = player.ads.state; 1170 1171 // Execute the current state's handler for this event 1172 var eventHandlers = states[state].events; 1173 1174 if (eventHandlers) { 1175 var handler = eventHandlers[event.type]; 1176 1177 if (handler) { 1178 handler.apply(player.ads); 1179 } 1180 } 1181 1182 // If the state has changed... 1183 if (state !== player.ads.state) { 1184 var previousState = state; 1185 var newState = player.ads.state; 1186 1187 // Record the event that caused the state transition 1188 player.ads.triggerevent = event.type; 1189 1190 // Execute "leave" method for the previous state 1191 if (states[previousState].leave) { 1192 states[previousState].leave.apply(player.ads); 1193 } 1194 1195 // Execute "enter" method for the new state 1196 if (states[newState].enter) { 1197 states[newState].enter.apply(player.ads); 1198 } 1199 1200 // Debug log message for state changes 1201 if (settings.debug) { 1202 _video2.default.log('ads', player.ads.triggerevent + ' triggered: ' + previousState + ' -> ' + newState); 1203 } 1204 } 1205 }; 1206 1207 // Register our handler for the events that the state machine will process 1208 player.on(VIDEO_EVENTS.concat([ 1209 // Events emitted by this plugin 1210 'adtimeout', 'contentupdate', 'contentplaying', 'contentended', 'contentresumed', 1211 // Triggered by startLinearAdMode() 1212 'adstart', 1213 // Triggered by endLinearAdMode() 1214 'adend', 1215 // Triggered by skipLinearAdMode() 1216 'adskip', 1217 1218 // Events emitted by integrations 1219 'adsready', 'adserror', 'adscanceled', 'nopreroll']), processEvent); 1220 1221 // If we're autoplaying, the state machine will immidiately process 1222 // a synthetic play event 1223 if (!player.paused()) { 1224 processEvent({ type: 'play' }); 1225 } 1226}; 1227 1228// Register this plugin with videojs 1229_video2.default.plugin('ads', contribAdsPlugin); 1230}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1231},{"./cancelContentPlay.js":1,"./contentupdate.js":2,"./macros.js":3,"./redispatch.js":4,"./snapshot.js":5}]},{},[6])(6) 1232});
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.