UNPKG

videojs-contrib-ads

Version:

A framework that provides common functionality needed by video advertisement libraries working with video.js.

184 lines (156 loc) 6.47 kB
/* The goal of this feature is to make player events work as an integrator would expect despite the presense of ads. For example, an integrator would expect an `ended` event to happen once the content is ended. If an `ended` event is sent as a result of a preroll ending, that is a bug. The `redispatch` method should recognize such `ended` events and prefix them so they are sent as `adended`, and so on with all other player events. */ // Cancel an event. // Video.js wraps native events. This technique stops propagation for the Video.js event // (AKA player event or wrapper event) while native events continue propagating. const cancelEvent = (player, event) => { event.isImmediatePropagationStopped = function() { return true; }; event.cancelBubble = true; event.isPropagationStopped = function() { return true; }; }; // Redispatch an event with a prefix. // Cancels the event, then sends a new event with the type of the original // event with the given prefix added. // The inclusion of the "state" property should be removed in a future // major version update with instructions to migrate any code that relies on it. // It is an implementation detail and relying on it creates fragility. const prefixEvent = (player, prefix, event) => { cancelEvent(player, event); player.trigger({ type: prefix + event.type, originalEvent: event }); }; // Playing event // Requirements: // * Normal playing event when there is no preroll // * No playing event before preroll // * At least one playing event after preroll const handlePlaying = (player, event) => { if (player.ads.isInAdMode()) { if (player.ads.isContentResuming()) { // Prefix playing event when switching back to content after postroll. if (player.ads._contentEnding) { prefixEvent(player, 'content', event); } // Prefix all other playing events during ads. } else { prefixEvent(player, 'ad', event); } } }; // Ended event // Requirements: // * A single ended event when there is no postroll // * No ended event before postroll // * A single ended event after postroll const handleEnded = (player, event) => { if (player.ads.isInAdMode()) { // Cancel ended events during content resuming. Normally we would // prefix them, but `contentended` has a special meaning. In the // future we'd like to rename the existing `contentended` to // `readyforpostroll`, then we could remove the special `resumeended` // and do a conventional content prefix here. if (player.ads.isContentResuming()) { cancelEvent(player, event); // Important: do not use this event outside of videojs-contrib-ads. // It will be removed and your code will break. // Ideally this would simply be `contentended`, but until // `contentended` no longer has a special meaning it cannot be // changed. player.trigger('resumeended'); // Ad prefix in ad mode } else { prefixEvent(player, 'ad', event); } // Prefix ended due to content ending before postroll check } else if (!player.ads._contentHasEnded && !player.ads.stitchedAds()) { // This will change to cancelEvent after the contentended deprecation // period (contrib-ads 7) prefixEvent(player, 'content', event); // Content ended for the first time, time to check for postrolls player.trigger('readyforpostroll'); } }; // handleLoadEvent is used for loadstart, loadeddata, and loadedmetadata // Requirements: // * Initial event is not prefixed // * Event due to ad loading is prefixed // * Event due to content source change is not prefixed // * Event due to content resuming is prefixed const handleLoadEvent = (player, event) => { // Initial event if (event.type === 'loadstart' && !player.ads._hasThereBeenALoadStartDuringPlayerLife || event.type === 'loadeddata' && !player.ads._hasThereBeenALoadedData || event.type === 'loadedmetadata' && !player.ads._hasThereBeenALoadedMetaData) { return; // Ad playing } else if (player.ads.inAdBreak()) { prefixEvent(player, 'ad', event); // Source change } else if (player.currentSrc() !== player.ads.contentSrc) { return; // Content resuming } else { prefixEvent(player, 'content', event); } }; // Play event // Requirements: // * Play events have the "ad" prefix when an ad is playing // * Play events have the "content" prefix when content is resuming // Play requests are unique because they represent user intention to play. They happen // because the user clicked play, or someone called player.play(), etc. It could happen // multiple times during ad loading, regardless of where we are in the process. With our // current architecture, this could cause the content to start playing. // Therefore, contrib-ads must always either: // - cancelContentPlay if there is any possible chance the play caused the // content to start playing, even if we are technically in ad mode. In order for // that to happen, play events need to be unprefixed until the last possible moment. // - use playMiddleware to stop the play from reaching the Tech so there is no risk // of the content starting to play. // Currently, playMiddleware is only supported on desktop browsers with // video.js after version 6.7.1. const handlePlay = (player, event) => { if (player.ads.inAdBreak()) { prefixEvent(player, 'ad', event); // Content resuming } else if (player.ads.isContentResuming()) { prefixEvent(player, 'content', event); } }; // Handle a player event, either by redispatching it with a prefix, or by // letting it go on its way without any meddling. export default function redispatch(event) { // Events with special treatment if (event.type === 'playing') { handlePlaying(this, event); } else if (event.type === 'ended') { handleEnded(this, event); } else if (event.type === 'loadstart' || event.type === 'loadeddata' || event.type === 'loadedmetadata') { handleLoadEvent(this, event); } else if (event.type === 'play') { handlePlay(this, event); // Standard handling for all other events } else if (this.ads.isInAdMode()) { if (this.ads.isContentResuming()) { // Event came from snapshot restore after an ad, use "content" prefix prefixEvent(this, 'content', event); } else { // Event came from ad playback, use "ad" prefix prefixEvent(this, 'ad', event); } } }