Slony1 - Replikace pro PostgreSQL (7)

16. 9. 2004
Doba čtení: 8 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Dnes započneme další éru - replikaci máme již zvládnutou, a tak se pustíme do administrace replikacniho clusteru.

V předchozích dílech jsme se naučili vytvářet databáze, přidávat je do replikačního clusteru, spouštět replikační agenty, nastavovat vzájemné spojení jednotlivých databází a provádět replikaci dat mezi libovolnými uzly. Naučili jsme se také řešit možné problémy, se kterými se můžeme při těchto operacích setkat. Umíme už dost na to, abychom si bez problémů nakonfigurovali svůj vlastní replikační cluster tak, jak nám to vyhovuje, a nasadili ho do produkce.

Ačkoliv předchozí operace se také dají nazývat administrací, já bych je nazval spíše instalací. Pod pojmem administrace si představuji údržbu již pracujícího clusteru. Touto údržbou se budeme společně dále zabývat.

Odpojení databáze z clusteru

Náš demonstrační cluster má strukturu, kterou vidíte na začátku šestého dílu. Rozhodli jsme se uzel slave2 z clusteru odpojit. Tuto akci nelze provést přímo prostředky PostgreSQL, tj. DROP DATABASE. Ostatním uzlům je nutné tuto změnu předem oznámit, aby si poupravili routing zpráv. Také je možné, že si tuto databázi chceme ponechat, protože se v ní kromě replikovaných dat nachází i něco jiného. Můžeme si navíc chtít ponechat i zreplikované tabulky.

Databázi není možné odpojit z clusteru tak, že k ní pouze zrušíme spojení příkazy DROP PATH a DROP LISTEN. Pokud to provedeme, začnou se nám hromadit na ostatních uzlech zprávy, protože v clusteru musí být každý s každým propojen.

Správný postup pro odpojení databáze z clusteru je použití příkazu DROP NODE. Tento příkaz kompletně odstraní konfiguraci daného uzlu z konfigurace clusteru a stopne replikačního agenta, pokud běží.

DROP NODE (ID = 3);

> slonik < setup.slonik
<stdin>:7: PGRES_FATAL_ERROR select "_dharma".dropNode(3);  - ERROR:  Slony-I: N
ode 3 is still configured as data provider 

Tímto nám administrační program slonik oznamuje, že některé uzly odebírají od slave2 data, a proto není možné tento uzel odpojit. Uzel slave2 je použit jako data provider poskytující set Godnames uzlu master a set pgbench tables uzlu c1slave2.

Tento problém je možné řešit dvěma způsoby:

  1. ručně předrátovat spojení jednotlivých uzlů
  2. označit uzel jako failed a nechat jiný uzel převzít jeho funkci

Pokud máte dostatek času, doporučuji používat metodu č. 1. Pokud hodně spěcháte a máte navíc komplikovanější strukturu clusteru, je nejrychlejší uzel odbouchnout pomocí FAILOVER a následným DROP NODE na něj zapomenout. Metodu č. 2 není možné použít v případě, kdy je daný uzel masterem pro daný set, jelikož bychom ztratili doposud nezreplikovaná data. V našem případě tomu tak není – žádný ze dvou zmíněných setů není na slave2 odemknut pro zápis.

Metoda č. 1 není náročná na čas počítače, ten to zvládne za pár sekund, ale na čas člověka, který tyto příkazy vydává. Pokud máte složitější stukturu clusteru čítající více uzlů a větší databáze, je nutné u všech akcí alespoň dvakrát měřit a pak teprve řezat. Pokud říznete vedle, tak vzhledem k řekněme netypickému konfiguračnímu systému Slony1 se tyto chyby obtížně vyhledávají, protože pro získání aktuálního stavu replikačního clusteru je nutné prolézat systémový replikační katalog na jednotlivých uzlech pomocí psql. Abychom to měli ještě zajímavější, je Slony1 naprogramován tak, že se všechny konfigurační eventy generují lokálně na cílových uzlech a nikoliv na masteru, a tak je kromě obsahu katalogu nutné překontrolovat i jeho synchronizaci s ostatnímy uzly.

V rámci výuky použijeme metodu č. 1, dvojku si necháme až na probírání failoveru. Zahajíme tedy operaci s využitím methody č. 1.

Fáze 1 – propojit přímo a obousměrně c1slave2 a master

STORE PATH ( client = 4,server = 1,  conninfo = 'dbname=master host=localhost us
er=slony password=iehovah');
STORE PATH ( client = 1,server = 4,  conninfo = 'dbname=c1slave2 host=localhost
user=slony password=iehovah');
STORE LISTEN ( origin = 4, receiver = 1);
STORE LISTEN ( origin = 1, receiver = 4); 

Po provedení těchto příkazů dojde k zajímavé situaci. Uzly c1slave2 a master budou propojeny dvakrát. Jednou přímo a podruhé prostřednictvím uzlu slave2. Master db si to přebere takto:

DEBUG2 remoteListenThread_3: queue event 4,455 STORE_PATH
DEBUG2 remoteWorkerThread_4: Received event 4,455 STORE_PATH
DEBUG2 remoteWorkerThread_3: forward confirm 4,455 received by 3
DEBUG2 localListenThread: Received event 1,2854 STORE_PATH
CONFIG storePath: pa_server=4 pa_client=1 pa_conninfo="dbname=c1slave2 host=localhost user=slony password=iehovah" pa_connretry=10
DEBUG2 remoteWorkerThread_3: forward confirm 1,2854 received by 3
DEBUG2 remoteWorkerThread_3: forward confirm 1,2854 received by 4
DEBUG2 localListenThread: Received event 1,2855 STORE_LISTEN
CONFIG storeListen: li_origin=4 li_receiver=1 li_provider=4
DEBUG1 remoteListenThread_4: thread starts
DEBUG2 remoteListenThread_4: start listening for event origin 4
DEBUG1 remoteListenThread_4: connected to 'dbname=c1slave2 host=localhost user=slony password=iehovah'
DEBUG2 remoteListenThread_4: queue event 4,456 STORE_LISTEN
DEBUG2 remoteWorkerThread_4: Received event 4,456 STORE_LISTEN
DEBUG2 remoteListenThread_3: queue event 4,456 STORE_LISTEN
DEBUG2 remoteWorker_event: event 4,456 ignored - duplicate
DEBUG2 remoteWorkerThread_3: forward confirm 1,2855 received by 3
DEBUG2 remoteWorkerThread_3: forward confirm 4,456 received by 3
DEBUG2 remoteWorkerThread_4: forward confirm 1,2855 received by 4
DEBUG2 remoteListenThread_4: queue event 4,457 SYNC
DEBUG2 remoteWorkerThread_4: Received event 4,457 SYNC
DEBUG2 remoteWorkerThread_4: SYNC 457 processing
DEBUG1 remoteWorkerThread_4: data provider 3 only confirmed up to ev_seqno 456 for ev_origin 4
DEBUG2 remoteWorkerThread_3: forward confirm 4,457 received by 3
DEBUG2 syncThread: new sl_action_seq 505542 - SYNC 2856
DEBUG2 localListenThread: Received event 1,2856 SYNC
DEBUG2 remoteWorkerThread_3: forward confirm 1,2856 received by 3
DEBUG2 remoteWorkerThread_4: SYNC 457 processing
DEBUG2 remoteWorkerThread_4: data provider 3 confirmed up to ev_seqno 457 for ev_origin 4 - OK 

Srozumitelněji řečeno:

Slony1 nebere tyto dvě spojení jako alternativní, tj. když zpráva dorazí na cílový uzel jedním ze spojení, nestačí to k jejímu zrušení na straně odesilatele. Zpráva musí dorazit každým konfigurovaným spojením. Proto je současná situace chybná a v praxi se jí snažte vždy vyvarovat. Musíte si zapamatovat, že je bezpodmínečně nutné používat DROP LISTEN na odstraňování starých spojení.

.

Situace se navíc ještě zkomplikuje, až tuto konfiguraci zjistí replikační agent slave2 databáze, který se odpojí od c1slave2 s chybou Bad file descriptor, což zruší fukčnost jednoho z námi nakonfigurovaných spojení. Po restartu agenta se situace znormalizuje. Nadbytečná propojení odstraníme příkazem:

DROP LISTEN ( origin = 4, receiver = 4, provider = 3);
DROP LISTEN ( origin = 1, receiver = 1, provider = 3);

Uzel c1slave2 využívá uzel slave2, který chceme zrušit, ještě k předávání zpráv od slave1. Jelikož slave1 je přímo připojen k master a c1slave2 už také, je třeba přehodit i toto spojení.

DROP LISTEN  ( origin = 2, receiver = 4, provider = 3);
STORE LISTEN ( origin = 2, receiver = 4, provider = 1);

Fáze 2 – změnit replikaci pgbench setu

c1slave2 replikuje pgbench set z uzlu slave2. Vzhledem k tomu, že již máme funkční spojení do db master, je možné změnit zdroj replikace toho setu ze slave2 na master.

V tomto případě je situace odlišná. Nepoužívejte UNSUBSCRIBE SET, protože po jeho použití bude nutné opětovně provést full copy setu. Zdroj setu se přehodí pouhým příkazem SUBSCRIBE.

SUBSCRIBE SET ( ID = 1, PROVIDER = 1, RECEIVER = 4, FORWARD = NO ); 

Zde vidíme výsledek této akce

DEBUG2 localListenThread: Received event 4,533 SUBSCRIBE_SET
CONFIG storeSubscribe: sub_set=1 sub_provider=1 sub_forward='f'
DEBUG2 sched_wakeup_node(): no_id=3 (0 threads + worker signaled)
DEBUG2 sched_wakeup_node(): no_id=1 (0 threads + worker signaled)
DEBUG1 remoteWorkerThread_1: helper thread for provider 1 created
DEBUG1 remoteWorkerThread_1: helper thread for provider 3 terminated 

Fáze 3 – změnit replikaci Godnames setu

Podobné jako v předcházejícím případě. Master bude godnames set odebírat od c1slave2 přímo a nikoliv prostřednictvím slave2.

Jelikož od master odebírá set godnames uzel slave1, je nutné uvést parametr forward – yes. V případě, že ho neuvedeme, tak si desynchronizujeme tento set napříč clusterem.

SUBSCRIBE SET ( ID = 2, PROVIDER = 4, RECEIVER = 1, FORWARD = YES ); 

Fáze 4 – Díky za všechny SYNCy

Poděkujeme uzlu č. 3 za jeho členství v replikačním clusteru.

DROP NODE (ID = 3);

Tímto příkazem nejenže vymažeme uzel z konfigurace clusteru, ale dojte též ke zrušení replikačního katalogu na tomto uzlu. Agent se po zrušení katalogu odpojí od ostatních databází a ukončí se.

linux_sprava_tip

DEBUG2 remoteListenThread_1: queue event 1,2925 DROP_NODE
DEBUG2 remoteWorkerThread_1: Received event 1,2925 DROP_NODE
WARN   remoteWorkerThread_1: got DROP NODE for local node ID
DEBUG2 remoteWorkerThread_4: forward confirm 1,2925 received by 4
NOTICE:  Slony-I: Please drop schema "_dharma"
NOTICE:  drop cascades to function _dharma.tablehasserialkey(text)
...
INFO   remoteListenThread_1: disconnecting from 'dbname=master host=localhost user=slony password=iehovah'
DEBUG1 remoteListenThread_1: thread done
INFO   remoteListenThread_4: disconnecting from 'dbname=c1slave2 host=localhost user=slony password=iehovah'
DEBUG1 remoteListenThread_4: thread done
DEBUG1 syncThread: thread done
DEBUG1 localListenThread: thread done
DEBUG1 cleanupThread: thread done
DEBUG1 main: scheduler mainloop returned
DEBUG2 sched_wakeup_node(): no_id=1 (0 threads + worker signaled)
DEBUG1 remoteWorkerThread_2: thread done
DEBUG2 sched_wakeup_node(): no_id=4 (0 threads + worker signaled)
DEBUG1 remoteWorkerThread_4: helper thread for provider 4 terminated
DEBUG1 remoteWorkerThread_4: disconnecting from data provider 4
DEBUG1 remoteWorkerThread_4: thread done
DEBUG1 main: done 

Na všech ostatních uzlech dojde k restartu replikačního agenta, přesněji řečeno mělo by dojít. Je nutné tento údaj ručně překontrolovat. Restarty agentů jsou v replikačních systémech vždy rizikové, Slony1, nyní ve verzi 1.0.2, spadne.

INFO   localListenThread: got restart notification - signal scheduler
DEBUG2 localListenThread: Received event 1,2926 SYNC
DEBUG1 localListenThread: thread done
DEBUG1 syncThread: thread done
INFO   remoteListenThread_4: disconnecting from 'dbname=c1slave2 host=localhost user=slony password=iehovah'
DEBUG1 remoteListenThread_4: thread done
INFO   remoteListenThread_2: disconnecting from 'dbname=slave1 host=localhost user=slony password=iehovah'
DEBUG1 cleanupThread: thread done
DEBUG1 main: scheduler mainloop returned
DEBUG2 sched_wakeup_node(): no_id=2 (0 threads + worker signaled)
DEBUG1 remoteListenThread_2: thread done
DEBUG1 remoteWorkerThread_2: thread done
DEBUG2 sched_wakeup_node(): no_id=3 (0 threads + worker signaled)
DEBUG1 remoteWorkerThread_3: thread done
DEBUG2 sched_wakeup_node(): no_id=4 (0 threads + worker signaled)
DEBUG1 remoteWorkerThread_4: helper thread for provider 4 terminated
DEBUG1 remoteWorkerThread_4: disconnecting from data provider 4
DEBUG1 remoteWorkerThread_4: thread done
DEBUG1 main: restart requested
CONFIG main: local node id = 1
Illegal instruction (core dumped) 

Pokud jste po přečtení tohoto dílu dospěli k názoru, že administrace replikačního systému Slony1 vyžaduje kvalifikovanou osobu, máte pravdu.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »