betajs/betajs-media-components

View on GitHub
src/dynamics/ads_player/ads_player.js

Summary

Maintainability
D
2 days
Test Coverage
Scoped.define("module:Ads.Dynamics.Player", [
    "base:Objs",
    "browser:Info",
    "base:Maths",
    "base:Types",
    "base:Timers",
    "browser:Dom",
    "module:Assets",
    "dynamics:Dynamic",
    "module:Ads.IMALoader",
    "module:Ads.IMA.AdsManager"
], [
    "module:Ads.Dynamics.Controlbar"
], function(Objs, Info, Maths, Types, Timers, Dom, Assets, Class, IMALoader, AdsManager, scoped) {
    return Class.extend({
            scoped: scoped
        }, function(inherited) {
            return {
                template: "<%= template(dirname + '/ads_player.html') %>",

                attrs: {
                    dyncontrolbar: "ads-controlbar",
                    tmplcontrolbar: "",
                    cssadsplayer: "ba-adsplayer",
                    commoncss: "",
                    linear: true, // should be true to cover all the player container
                    playing: false,
                    currenttime: 0,
                    volume: 0,
                    isoutstream: false,
                    hidecontrolbar: false,
                    showactionbuttons: false,
                    showrepeatbutton: true,
                    showlearnmore: false,
                    hideclosebutton: false,
                    adscompleted: false,
                    moredetailslink: null,
                    moredetailstext: null,
                    repeatbuttontext: null,
                    adsplaying: false,
                    companionads: [],
                    companionadcontent: null,
                    customclickthrough: false,
                    persistentcompanionad: false
                },

                events: {
                    "change:volume": function(volume) {
                        // Muted should be pass only from the parent
                        if (!this.adsManager || !this.adsManager.setVolume) return;
                        volume = Maths.clamp(volume, 0, 1);
                        if (volume > 0 && this.get("unmuteonclick")) {
                            return setTimeout(function() {
                                if (!this.adsManager || !this.adsManager.setVolume) return;
                                this.adsManager.setVolume(volume);
                            }.bind(this), 200);
                        } else {
                            return this.adsManager.setVolume(volume);
                        }
                    },
                    "change:imaadsrenderingsetting": function(settings) {
                        if (!this.adsManager || !Types.is_object(settings)) return;
                        this.adsManager.updateAdsRenderingSettings(settings);
                    }
                },

                _deferActivate: function() {
                    if (this._loadedSDK) return false;
                    IMALoader.loadSDK().success(function() {
                        if (this.__iasConfig()) {
                            IMALoader.loadIAS().success(function() {
                                this._loadedSDK = true;
                                this.activate();
                            }, this);
                        } else {
                            this._loadedSDK = true;
                            this.activate();
                        }
                    }, this);
                    return true;
                },

                _baseRequestAdsOptions: function() {
                    const requestAdsOptions = {
                        adTagUrl: this.get("adtagurl"),
                        IMASettings: this.get("imasettings"),
                        inlinevastxml: this.get("inlinevastxml"),
                        continuousPlayback: true,
                        linearAdSlotWidth: this.getAdWidth(),
                        linearAdSlotHeight: this.getAdHeight(),
                        nonLinearAdSlotWidth: this.getAdWidth(),
                        nonLinearAdSlotHeight: this.getAdHeight() / 3,
                        adWillAutoPlay: this.getAdWillAutoPlay(),
                        adWillPlayMuted: this.getAdWillPlayMuted(),
                        autoPlayAdBreaks: true,
                        width: this.getAdWidth(),
                        height: this.getAdHeight(),
                        volume: this.getAdWillPlayMuted() ? 0 : this.get("volume")
                    };
                    if (this.get("adsrendertimeout") && this.get("adsrendertimeout") > 0)
                        requestAdsOptions.vastLoadTimeout = this.get("adsrendertimeout");
                    return requestAdsOptions;
                },

                channels: {
                    "ads:ad-error": function() {
                        this.set("adsplaying", false);
                        if (this.parent().get("outstream")) {
                            this.parent().hidePlayerContainer();
                        }
                    },
                    "ads:render-timeout": function() {
                        if (this.adsManager && typeof this.adsManager.destroy === "function" && !this.adsManager.destroyed()) {
                            this.adsManager.destroy();
                        }
                        const dyn = this.parent();
                        if (dyn) {
                            dyn.stopAdsRenderFailTimeout(true);
                            dyn.channel("ads").trigger("ad-error");
                        }
                    },
                    "ads:load": function() {
                        this.set("skipvisible", false);
                        this.call("load");
                        this.set("quartile", "first");
                    },
                    "ads:firstQuartile": function() {
                        this.set("quartile", "second");
                    },
                    "ads:midpoint": function() {
                        this.set("quartile", "third");
                    },
                    "ads:thirdQuartile": function() {
                        this.set("quartile", "fourth");
                    },
                    "ads:start": function(ev) {
                        this._onStart(ev);
                    },
                    "ads:skippableStateChanged": function(event) {
                        this.set("skipvisible", true);
                    },
                    "ads:complete": function(ev) {
                        this._onAdComplete(ev);
                    },
                    "ads:allAdsCompleted": function() {
                        if (this.parent() && (
                                this.parent().get("outstreamoptions").noEndCard ||
                                this.parent().get("outstreamoptions.allowRepeat")
                            )) return;
                        this.call("reset");
                    },
                    "ads:discardAdBreak": function() {
                        this.call("discardAdBreak");
                    },
                    "ads:contentComplete": function() {
                        this.call("contentComplete");
                    },
                    "ads:loaded": function(event) {
                        const ad = event.getAd();
                        this.set("ad", ad);
                        this.setEndCardBackground(this.getAdWidth(), this.getAdHeight());
                        const adData = event.getAdData();
                        const clickthroughUrl = adData.clickThroughUrl;
                        this.set("addata", adData);
                        // even we're asking ads via adWillPlayMuted:false and volume:1, it's always respond muted
                        if (this.get('adsunmuted')) {
                            this.adsManager.setVolume(this.get('volume'));
                        }
                        this._setVolume(this.adsManager.getVolume(), false);
                        this.set("duration", adData.duration);
                        this.set("moredetailslink", clickthroughUrl);
                        this.set("adsclicktroughurl", clickthroughUrl);
                    },
                    "ads:outstreamCompleted": function(dyn) {
                        this._outstreamCompleted(dyn);
                    },
                    "ads:outstreamStarted": function(dyn) {
                        this._outstreamStarted(dyn);
                    },
                    "ads:pause": function() {
                        if (this.get("playing")) {
                            this.call("pause");
                            this.set("playing", false);
                        }
                    },
                    "ads:resume": function() {
                        if (!this.get("playing")) {
                            this.call("resume");
                            this.set("playing", true);
                        }
                    },
                    "ads:contentResumeRequested": function() {
                        this.set("adsplaying", false);
                    },
                    "ads:contentPauseRequested": function() {
                        this.set("adsplaying", true);
                    }
                },

                __iasConfig: function() {
                    return this.parent().get("ias-config");
                },

                create: function() {
                    var dynamics = this.parent();
                    var adContainer = this.getAdContainer();
                    var adManagerOptions = {
                        adContainer: adContainer,
                        adsRenderingSettings: this.get("imaadsrenderingsetting"),
                        IMASettings: this.get("imasettings")
                    };
                    if (!Info.isMobile() && this.getVideoElement()) {
                        // It's optionalParameter
                        adManagerOptions.videoElement = this.getVideoElement();
                    } else {
                        adManagerOptions.customclickthrough = this.getClickTroughElement();
                        this.set("customclickthrough", !!adManagerOptions.customclickthrough);
                    }
                    this.adsManager = this.auto_destroy(
                        new AdsManager(adManagerOptions, dynamics));
                    this.adsManager.requestAds(this._baseRequestAdsOptions());
                    // Will list events which are require some additional actions,
                    // ignore events like adsProgress for additional statement checks
                    this.adsManager.on("all", function(event, data) {
                        if (event === "adsManagerLoaded") {
                            this.set("adsmanagerloaded", true);
                            if (this.__iasConfig() && typeof googleImaVansAdapter !== "undefined") {
                                googleImaVansAdapter.init(google, this.adsManager, this.getVideoElement(), Objs.extend({
                                    "campId": (this.getAdWidth() || 640) + "x" + (this.getAdHeight() || 360)
                                }, this.__iasConfig));
                            }
                            // If we're getting ad-error no need to set loadVideoTimeout
                            this._setLoadVideoTimeout();
                            // Makes active element not redirect to click through URL on first click
                            // if (!dynamics.get("userhadplayerinteraction") && dynamics.activeElement() && this.get("unmuteonclick")) {
                            //     dynamics.once("change:userhadplayerinteraction", function(hasInteraction) {
                            //         if (hasInteraction) {
                            //             if (dynamics.get("presetedtooltips.onclicktroughexistence")) {
                            //                 dynamics.showTooltip(dynamics.get("presetedtooltips.onclicktroughexistence"));
                            //             }
                            //         }
                            //     }, dynamics);
                            // }
                        }
                        this.channel("ads").trigger(event, data);
                    }, this);

                    if (this.shouldShowFirstFrameAsEndcard()) {
                        // attach listener to set endcard image
                        this.on("change:endcardbackgroundsrc", function(endcardbackgroundsrc) {
                            if (endcardbackgroundsrc) {
                                this.getAdContainer().style.backgroundImage = `url("${endcardbackgroundsrc}")`;
                            }
                        });
                    }

                    if (dynamics) {
                        // if we've already not started timer, we should start it here
                        dynamics.initAdsRenderFailTimeout();
                        dynamics.on("resize", function(dimensions) {
                            const width = this.getAdWidth();
                            const height = this.getAdHeight();
                            if (width && height) {
                                // This part will listen to the resize even after adsManger will be destroyed
                                if (this.adsManager && typeof this.adsManager.resize === "function") {
                                    this.adsManager.resize(
                                        width,
                                        height,
                                        google.ima.ViewMode.NORMAL
                                    );
                                }
                                this.setEndCardBackground(width, height);
                            }
                        }, this);
                    }
                },

                functions: {
                    load: function() {
                        if (!this.adsManager) return this.once("dynamic-activated", function() {
                            this.call("load");
                        }, this);
                        this.adsManager.start({
                            width: this.getAdWidth(),
                            height: this.getAdHeight(),
                            volume: this.getAdWillPlayMuted() ? 0 : this.get("volume")
                        });

                        // if (!this.adsManager.adDisplayContainerInitialized) this.adsManager.initializeAdDisplayContainer();
                        // this.call("requestAds");
                    },
                    ad_clicked: function() {
                        if (!this.get("userhadplayerinteraction")) {
                            this.parent().set("userhadplayerinteraction", true);
                        }
                    },
                    reset: function() {
                        this.set("linear", true);
                        this.set("adscompleted", true);
                        this.set("adsplaying", false);
                        this.adsManager.reset();
                        // this.adsManager.contentComplete();
                    },
                    reload: function() {
                        // this.adsManager.reset();
                        // this.adsManager.contentComplete();
                        this.call("requestAds");
                    },
                    discardAdBreak: function() {
                        this.adsManager.discardAdBreak();
                    },
                    requestAds: function(options) {
                        this.adsManager.requestAds(Objs.extend(this._baseRequestAdsOptions(), options));
                    },
                    contentComplete: function() {
                        this.adsManager.contentComplete();
                    },
                    pause: function() {
                        this.checkIfAdHasMediaUrl();
                        return this.adsManager.pause();
                    },
                    resume: function() {
                        return this.adsManager.resume();
                    },
                    set_volume: function(volume) {
                        this._onPlayerEngaged();
                        this._setVolume(Maths.clamp(volume, 0, 1), true);
                    },
                    stop: function() {
                        return this.adsManager.stop();
                    },
                    fullscreen: function() {
                        this.trigger('fullscreen');
                    },
                    toggle_volume: function() {
                        this._onPlayerEngaged();
                    },
                    replay: function() {
                        this._replay();
                    },
                    redirect: function(moredetailslink) {
                        if (moredetailslink) {
                            window.open(moredetailslink, '_blank');
                        }
                    },
                    close: function() {
                        return this._hideContentPlayer();
                    },
                    hideCompanionAd: function() {
                        return this._hideCompanionAd();
                    },
                    renderCompanionAd: function() {
                        return this._renderCompanionAd();
                    }
                },

                getAdWidth: function() {
                    if (!this.activeElement()) return null;
                    if ((this.get("sidebar_active") || (this.get("sticky") || this.get("floating"))) && this.parent()) {
                        return Dom.elementDimensions(this.parent().__playerContainer).width;
                    }
                    return this.activeElement().firstChild ? this.activeElement().firstChild.clientWidth : this.activeElement().clientWidth;
                },
                getAdHeight: function() {
                    if (!this.activeElement()) return null;
                    if ((this.get("sticky") || this.get("floating")) && this.parent()) {
                        return Dom.elementDimensions(this.parent().activeElement().firstChild).height;
                    }
                    return this.activeElement().firstChild ? this.activeElement().firstChild.clientHeight : this.activeElement().clientHeight;
                },

                getAdContainer: function() {
                    if (!this._adContainer) this._adContainer = this.activeElement().querySelector(".ba-ad-container");
                    return this._adContainer;
                },

                getVideoElement: function() {
                    if (!this._videoElement)
                        this._videoElement = this.parent() && this.parent().activeElement().querySelector("[data-video='video']"); // TODO video element for outstream
                    return this._videoElement;
                },
                isImageBlack: function(ctx, width, height) {
                    var imageData = ctx.getImageData(0, 0, width, height);
                    var pixels = imageData.data;
                    for (var i = 0; i < pixels.length; i += 4) {
                        var r = pixels[i];
                        var g = pixels[i + 1];
                        var b = pixels[i + 2];
                        if (r !== 0 || g !== 0 || b !== 0) {
                            return false;
                        }
                    }
                    return true;
                },
                checkIfAdHasMediaUrl: function() {
                    const adObj = this.get("ad");
                    const ad = adObj?.data?.mediaUrl;
                    // if (Info.isSafari() && ad) {
                    //     this.renderVideoFrame(ad, this.getAdWidth(), this.getAdHeight())
                    // }
                },
                renderVideoFrame: function(mediaUrl, width, height) {
                    const video = document.createElement("video");
                    video.crossOrigin = "anonymous";
                    video.src = mediaUrl;
                    video.muted = true;
                    video.play();
                    video.addEventListener("loadeddata", (event) => {
                        this.parent()._drawFrame(video, this.get('currenttime'), width, height, (canvas, ctx) => {
                            if (this.isImageBlack(ctx, width, height)) {
                                this.getAdContainer().style.backgroundImage = `url(${canvas.toDataURL("image/png")})`;
                            }
                        })
                    })
                },

                captureAdEndCardBackground: function(width, height) {
                    const ad = this.get("ad");
                    if (ad) {
                        this.generateEndCardImage(ad, width, height);
                    }
                },

                setEndCardBackground: function(width, height) {
                    if (this.shouldShowFirstFrameAsEndcard()) {
                        if (width && height) {
                            this.captureAdEndCardBackground(width, height);
                        }
                    }
                },

                generateEndCardImage: function(ad, width, height) {
                    // if we already captured the endcard once already
                    if (this._video && this._canvas && this._mediaUrl) {
                        this.resizeCanvas(width, height);
                        return;
                    }
                    this._video = document.createElement("video");
                    this._canvas = document.createElement("canvas");
                    this._mediaUrl = ad?.data?.mediaUrl;
                    if (this._mediaUrl) {
                        fetch(this._mediaUrl)
                            .then(response => response.blob())
                            .then(blob => {
                                this._video.src = URL.createObjectURL(blob);
                                this._video.crossOrigin = "anonymous";
                                this._video.muted = true;
                                return this._video.play()
                            })
                            .then(() => {
                                // add a small delay to handle cases where the beginning of video is a black screen
                                // 800ms delay + drawFrame has a timeout of 200ms = 1s into video
                                this.parent()._drawFrame(this._video, 0.8, width, height, (canvas, _ctx) => {
                                    this._canvas = canvas;
                                    this.set("endcardbackgroundsrc", this._canvas.toDataURL("image/png"));
                                    this._video.pause();
                                });
                            }).catch((e) => console.log(`Error: ${e}`));
                    }
                },

                resizeCanvas: function(newWidth, newHeight) {
                    this._canvas.width = newWidth;
                    this._canvas.height = newHeight;
                    this._canvas
                        .getContext("2d")
                        .drawImage(this._video, 0, 0, this._canvas.width, this._canvas.height);
                    this.set("endcardbackgroundsrc", this._canvas.toDataURL("image/png"))
                },

                shouldShowFirstFrameAsEndcard: function() {
                    const dyn = this.parent();
                    const showEndCard = !dyn.get("outstreamoptions").noEndCard;
                    const noRepeat = !dyn.get("outstreamoptions.allowRepeat");
                    const showFirstFrameAsEndCard = dyn.get("outstreamoptions.firstframeasendcard");
                    return dyn && (showEndCard || noRepeat) && showFirstFrameAsEndCard;
                },

                getClickTroughElement: function() {
                    return this.activeElement().querySelector('[data-selector="ba-ads-clickthrough-container"]') || null;
                },

                getAdWillAutoPlay: function() {
                    return this.parent() && (this.parent().get("autoplay-allowed") || this.parent().get("autoplay"));
                },

                getAdWillPlayMuted: function() {
                    return this.get("muted") || this.get("volume") === 0;
                },

                _onStart: function(ev) {
                    this.set("playing", true);
                    this.set("currenttime", 0);
                    this.set("remaining", this.get("duration"));
                    this.set("showactionbuttons", false);
                    this.set("adscompleted", false);

                    if (ev && Types.is_function(ev.getAd)) {
                        var ad = ev.getAd();
                        var isLinear = ad.isLinear();
                        this.set("linear", isLinear);
                        this.set("hidecontrolbar", !isLinear || this.get("hideadscontrolbar"));
                        if (!isLinear) {
                            this.set("non-linear-min-suggestion", ad.getMinSuggestedDuration());
                            // decrease a non-linear suggestion period, be able to show midroll
                            this._minSuggestionCalcualationTimer = this.auto_destroy(new Timers.Timer({ // This is being fired right before toggle_player
                                delay: 1000,
                                fire: function() {
                                    if (this.get("non-linear-min-suggestion") < 0) {
                                        this._minSuggestionCalcualationTimer.destroy();
                                    } else {
                                        this.set("non-linear-min-suggestion", this.get("non-linear-min-suggestion") - 1);
                                    }
                                }.bind(this)
                            }));
                        }

                        // this.set("minSuggestedDuration", ev);
                        // if ad is outstream and
                        if (!isLinear && this.get("isoutstream")) {
                            this.adsManager.reset();
                            this.adsManager.requestAds(this._baseRequestAdsOptions());
                        }

                        // Set companion ads array and render for normal content player viewport
                        if (ad) this._getCompanionAds(ad);
                    }

                    // reset adsrendertimeout
                    this.parent().stopAdsRenderFailTimeout(true);
                    // and set a new videoLoaded timeout for midroll and post-roll
                    this._setLoadVideoTimeout();

                    // Additional resize will fit ads fully inside the player container
                    if (this.get("sidebar_active") && this.adsManager && this.parent()) {
                        // NOTE: can cause console error on main player, uncomment if required separately
                        // this.parent().trigger("resize");
                    }
                },

                _onAdComplete: function(ev) {
                    if (Info.isSafari() && this.getAdContainer().style.backgroundImage) {
                        this.getAdContainer().style.backgroundImage = 'none';
                    }
                    // NOTE: As below codes only companion ads related code will be better return.
                    // Non companion ads code should be applied above of this line
                    if (this.get("persistentcompanionad")) return;
                    if (this.get("companionads") && this.get("companionads").length > 0) this.set("companionads", []);
                    if (this.get("multicompanionads") && this.get("multicompanionads").length > 0) {
                        Objs.iter(this.get("multicompanionads"), function(element, index) {
                            element.innerHTML = "";
                            delete this.get("multicompanionads")[index];
                        }, this);
                    }
                    if (this.__companionAdElement) {
                        this.__companionAdElement.innerHTML = "";
                    }
                },

                _setLoadVideoTimeout: function() {
                    if (this.get("adsrendertimeout") > 0 && this.get("adsrendertimeout") !== this.get("imaadsrenderingsetting.loadVideoTimeout")) {
                        const dyn = this.parent();
                        if (dyn) {
                            // If we've set or timer still exists
                            dyn.set("imaadsrenderingsetting", {
                                ...dyn.get("imaadsrenderingsetting"),
                                loadVideoTimeout: dyn.get("adsrendertimeout")
                            });
                        }
                    }
                },

                _onPlayerEngaged: function() {
                    const parentDyn = this.parent();
                    if (parentDyn && Types.is_function(parentDyn.setPlayerEngagement)) {
                        parentDyn.setPlayerEngagement();
                    }
                },

                _outstreamCompleted: function(dyn) {
                    dyn = dyn || this.parent();
                    if (Types.is_undefined(dyn.activeElement))
                        throw Error("Wrong dynamics instance was provided to _outstreamCompleted");
                    // TODO: add option for selection

                    if (dyn.get("outstreamoptions")) {
                        // Will handle via player State on ads completion
                        if (dyn.get("outstreamoptions.hideOnCompletion")) {
                            this._hideContentPlayer(dyn);
                            return;
                        } else {
                            const moreDetailsLink = dyn.get("outstreamoptions.moredetailslink");
                            if (dyn.get("outstreamoptions.noEndCard")) return;
                            if (dyn.get("outstreamoptions.moreText")) {
                                this.set("moredetailstext", dyn.get("outstreamoptions.moreText"));
                            }
                            this.set("showrepeatbutton", !!dyn.get("outstreamoptions.allowRepeat"));
                            if (dyn.get("outstreamoptions.repeatText")) {
                                this.set("repeatbuttontext", dyn.get("outstreamoptions.repeatText"));
                            }
                            if (moreDetailsLink) {
                                this.set("moredetailslink", moreDetailsLink);
                            }
                            this.set("hideclosebutton", !!dyn.get("outstreamoptions.hideclose"));
                            this.set("showlearnmorebutton", !!dyn.get("outstreamoptions.showlearnmore"));
                        }
                    }
                    this.set("showactionbuttons", true);
                },

                /**
                 * @param ad
                 * @private
                 */
                _getCompanionAds: function(ad) {
                    ad = ad || this.get("ad");
                    var companionAds = [];
                    if (google && google.ima && ad && Types.is_function(ad.getCompanionAds)) {
                        // if options is not boolean, then we have provided more options, like size and selector
                        var selectionCriteria = new google.ima.CompanionAdSelectionSettings();
                        // HTML,IFRAME,STATIC,ALL
                        selectionCriteria.resourceType = google.ima.CompanionAdSelectionSettings.ResourceType.ALL;
                        // CreativeType:IMAGE, FLASH, ALL
                        selectionCriteria.creativeType = google.ima.CompanionAdSelectionSettings.CreativeType.ALL;
                        // get all companionads
                        selectionCriteria.sizeCriteria = google.ima.CompanionAdSelectionSettings.SizeCriteria.IGNORE;
                        // get all available companion ads
                        companionAds = ad.getCompanionAds(0, 0, selectionCriteria);
                        if (companionAds && companionAds.length > 0) {
                            this.set("companionads", companionAds);
                        }
                        return companionAds;
                    } else {
                        return [];
                    }
                },

                /**
                 * @param ad
                 * @param options
                 * @return void
                 */
                _renderCompanionAd: function(ad, options) {
                    // Do not render anything if options is boolean and false
                    if (Types.is_boolean(options) && !Boolean(options)) return;

                    ad = ad || this.get("ad");
                    options = options || this.get("companionad");

                    var playerElement, position, selector, height, width, companionAdDimensions,
                        isFluid, containerDimensions, selectionCriteria, expectedAR,
                        companionAd, closestIndex, closestAr, parentStyles, companionAdContainerStyles;
                    var companionAds = [];
                    if (Types.is_string(options)) {
                        if (options.split('|').length > 0) {
                            position = options.split('|')[1] || 'bottom';
                        }
                        options = options.replace(/\].*/g, "$'").split('[');
                        selector = options[0];
                    } else {
                        // if it's floating and floatingoptions.device.companionad is set to boolean true,
                        // then it will be handled by sidebar.js
                        position = (this.get("sticky") || this.get("floating")) && this.get("withsidebar") ? null : 'bottom';
                    }
                    if (selector) {
                        this.__companionAdElement = document.getElementById(selector);
                    } else {
                        this.__companionAdElement = this.__companionAdElement || document.createElement('div');
                    }
                    if (!this.__companionAdElement) return;
                    // reset companion ad container
                    this.__companionAdElement.innerHTML = "";
                    // playerElement = this.get("floating") ? this.parent().activeElement().firstChild : this.parent().activeElement();
                    playerElement = this.parent().activeElement();
                    containerDimensions = Dom.elementDimensions(playerElement.firstChild);
                    if (this.get("companionads") && this.get("companionads").length <= 0) {
                        companionAdDimensions = options[1] ? options[1].split(',') : [0, 0];
                        isFluid = companionAdDimensions[0] === 'fluid';
                        // companionAdDimensions = companionAdDimensions.split(',');
                        if (!isFluid) {
                            width = Number((companionAdDimensions && companionAdDimensions[0] && companionAdDimensions[0] > 0) ?
                                companionAdDimensions[0] : containerDimensions.width);
                            height = Number((companionAdDimensions && companionAdDimensions[1] && companionAdDimensions[1] > 0) ?
                                companionAdDimensions[1] : containerDimensions.height);
                        }

                        selectionCriteria = new google.ima.CompanionAdSelectionSettings();
                        // HTML,IFRAME,STATIC,ALL
                        selectionCriteria.resourceType = google.ima.CompanionAdSelectionSettings.ResourceType.ALL;
                        // CreativeType:IMAGE, FLASH, ALL
                        selectionCriteria.creativeType = google.ima.CompanionAdSelectionSettings.CreativeType.ALL;
                        // SizeCriteria: IGNORE, SELECT_EXACT_MATCH, SELECT_NEAR_MATCH, SELECT_FLUID
                        if (!isFluid) {
                            // nearMatchPercent
                            selectionCriteria.sizeCriteria = google.ima.CompanionAdSelectionSettings.SizeCriteria.IGNORE;
                            if (width && height) {
                                // Get a list of companion ads for an ad slot size and CompanionAdSelectionSettings
                                companionAds = ad.getCompanionAds(width, height, selectionCriteria);
                            }
                        } else {
                            selectionCriteria.sizeCriteria = google.ima.CompanionAdSelectionSettings.SizeCriteria.SELECT_FLUID;
                            companionAds = ad.getCompanionAds(0, 0, selectionCriteria);
                        }

                        if (typeof companionAds[0] === "undefined") return;
                    } else {
                        companionAds = this.get("companionads");
                    }
                    expectedAR = containerDimensions.width / containerDimensions.height;
                    Objs.iter(companionAds, function(companion, index) {
                        var _data = companion.data;
                        var _ar = _data.width / _data.height;
                        var _currentDiff = Math.abs(_ar - expectedAR);
                        if (index === 0 || closestAr > _currentDiff) {
                            closestAr = _currentDiff;
                            closestIndex = index;
                        }
                        if (companionAds.length === index + 1) {
                            companionAd = companionAds[closestIndex];
                        }
                    }, this);

                    this.set("companionadcontainer", this.__companionAdElement);
                    // Get HTML content from the companion ad.
                    // Write the content to the companion ad slot.
                    this.__companionAdElement.innerHTML = companionAd.getContent();
                    Dom.elementAddClass(this.__companionAdElement, this.get("cssplayer") + "-companion-ad-container" + (this.get("mobileviewport") ? '-mobile' : '-desktop'));
                    var applyFloatingStyles = (this.get("sticky") || this.get("floating")) && !this.get("withsidebar");
                    if (applyFloatingStyles) {
                        // Mobile has to show in the sidebar
                        position = this.get("mobileviewport") ? null : 'top';
                        parentStyles = this.parent().get("containerSizingStyles");
                        // var floatingoptions = this.get("mobileviewport") ? this.get("floatingoptions.mobile") : this.get("floatingoptions.desktop");
                        companionAdContainerStyles = {
                            position: 'relative'
                        };
                        var image = this.__companionAdElement.querySelector('img');
                        if (image && containerDimensions && companionAd.data) {
                            var _ar = companionAd.data.width / companionAd.data.height;
                            var _h = containerDimensions.width * (_ar <= 1 ? _ar : companionAd.data.height / companionAd.data.width);
                            image.width = containerDimensions.width;
                            image.height = _h;
                            companionAdContainerStyles.bottom = (_h + 20) + 'px';
                        }
                    } else {
                        this.__companionAdElement.removeAttribute('style');
                    }
                    if ((this.get("sticky") || this.get("floating")) && !this.get("mobileviewport") && applyFloatingStyles) {
                        // On floating desktop attach to the player element
                        var _pl = this.parent().activeElement().querySelector('.ba-player-content');
                        if (_pl) playerElement = _pl;
                    }
                    if (position) {
                        switch (position) {
                            case 'left':
                                // Prevent on click though taking all the width of the div element
                                this.__companionAdElement.style.display = 'inline-block';
                                this.__companionAdElement.style['float'] = 'left';
                                playerElement.insertAdjacentElement("beforebegin", this.__companionAdElement);
                                break;
                            case 'top':
                                if (applyFloatingStyles && parentStyles) {
                                    this.parent()._applyStyles(this.__companionAdElement, companionAdContainerStyles);
                                }
                                playerElement.insertAdjacentElement("beforebegin", this.__companionAdElement);
                                break;
                            case 'right':
                                // Prevent on click though taking all the width of the div element
                                this.__companionAdElement.style.display = 'inline-block';
                                playerElement.style['float'] = 'left';
                                playerElement.insertAdjacentElement("afterend", this.__companionAdElement);
                                break;
                            default:
                                playerElement.insertAdjacentElement("afterend", this.__companionAdElement);
                        }
                    }
                },

                _hideCompanionAd: function() {
                    if (this.get("persistentcompanionad")) return;
                    // If there is any content in the companion ad container, remove it
                    if (this.__companionAdElement && Types.is_function(this.__companionAdElement.remove)) {
                        this.__companionAdElement.remove();
                    }
                },

                _outstreamStarted: function(dyn, options) {
                    this.set("isoutstream", true);
                    if (Types.is_defined(this.get("outstreamoptions.persistentcompanionad"))) {
                        this.set("persistentcompanionad", this.get("outstreamoptions.persistentcompanionad"));
                    }
                },

                _replay: function(dyn) {
                    dyn = dyn || this.parent();
                    if (Types.is_undefined(dyn.activeElement))
                        throw Error("Wrong dynamics instance was provided to _reply");
                    this.set("repeat", true);
                    this.channel("ads").trigger("replayOutstream");
                },

                _hideContentPlayer: function(dyn) {
                    dyn = dyn || this.parent();
                    if (Types.is_undefined(dyn.activeElement))
                        throw Error("Wrong dynamics instance was provided to _hideContentPlayer");
                    this._hideCompanionAd();
                    dyn.hidePlayerContainer();
                    // dyn.weakDestroy(); // << Create will not work as expected
                },

                /**
                 * @param {number} volume
                 * @param {boolean | null} applyToParent
                 * @private
                 */
                _setVolume: function(volume, applyToParent) {
                    applyToParent = applyToParent || false;
                    if (applyToParent && this.parent()) {
                        this.parent().set("volume", volume);
                        this.parent().set('muted', volume <= 0);
                    }
                    this.set("volume", volume);
                    this.set("adsunmuted", volume > 0);
                }
            };
        }).register("ba-adsplayer")
        .registerFunctions({
            /*<%= template_function_cache(dirname + '/ads_player.html') %>*/
        }).attachStringTable(Assets.strings)
        .addStrings({
            "replay-ad": "Replay Video",
            "close-ad": "Close",
            "ad-choices": "Ad Choices",
            "learn-more": "Learn More"
        });
});