Embedded databáze - Gadfly

31. 5. 2004
Doba čtení: 6 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Pokračování seriálu o embedded databázových enginech. V dnešním dílu nebude předem ohlášená SQLite, ale pythonská databáze Gadfly.

Gadfy

Databáze Gadfly byla vybrána pro pokračování seriálu z těchto důvodů:

  1. Byla zmíněna v diskusi k prvnímu dílu.
  2. Od napsání prvního dílu jsem se s ní setkal i v komerčních aplikacích.
  3. Je obsažena v Zope.

Databáze Gadfly je malá, relační embedded databáze pro Python, podporující dotazovací jazyk SQL. Databáze je šířena pod příznivou BSD licencí, a je proto velmi vhodná pro začlenění do komerčních produktů naprogramovaných v tomtéž jazyce.

Zvláštní na této databázi je to, že je jako jedna z mála kompletně naprogramována v interpretovaném jazyce Python. Pro Python je totiž nabídka embedded databází mnohem menší než pro Javu, o C ani nemluvě. Gadfly používá pro zrychlení datových operací platformově závislou knihovnu kjbuckets. V případě, že tato knihovna není k dispozici, použije se přenositelná pure-Python verze, která je funkčně ekvivalentní. Gadfly v Zope je naneštěstí dodávána v pomalé variantě.

Instalace

GadFly 1.0 je ke stažení na sourceforge.net. Ačkoliv je tato verze datována 10 Jul 2002, na jejím vývoji se stále pracuje, soudě podle aktivity v CVS. Pro Python 2.3 budete potřebovat ještě dva oneline fixy pro utility gfplus a gfserver.

Python 2.2 jsem nezkoušel, ale protože vyšel v prosinci 2001, měl by fungovat bez problémů. Instalace se provádí klasicky: tj. python setup.py install. Pokud chceme zrychlenou verzi (což chceme), provedeme stejný příkaz i v podadresáři kjbuckets.

Spuštění

Gadfly obsahuje kromě vlastní knihovny implementující databázi ještě dva pomocné programy: gfplus a gfserver. gfplus je interaktivní SQL interpretr a gfserver je server pro víceuživatelský přístup.

(hsn@sanatana):% gfplus

gfplus $Revision: 1.6 $ -- Interactive gadfly shell  [readline]

Friday April 23, 2004 07:53 PM
Using:
DB Name: netmag
DB Location: /tmp
Unable to locate database "netmag" at location "/tmp".
Create? (Yy/Nn) y
GF> create table clanky(id integer, cteno integer, text varchar);
OK
GF> insert into clanky values(1,0,'Nas prvni clanek...');
OK
GF> create unique index clanky1 on clanky(id);
OK
GF> insert into clanky values(1,0,'Nas druhy clanek...');
StorageError: uniqueness violation: 1 UNIQUE index CLANKY1 on ('ID',)
GF> desc clanky

COLUMN_NAME
===========
ID
CTENO
TEXT

GF> select * from  __table_names__;
TABLE_NAME      | IS_VIEW
=========================
__INDEXCOLS__   | 1
__COLUMNS__     | 1
__TABLE_NAMES__ | 1
CLANKY          | 0
DUAL            | 1
__INDICES__     | 1
__DATADEFS__    | 1
GF> select *  from __indices__;
TABLE_NAME | IS_UNIQUE | INDEX_NAME
===================================
CLANKY     | 1         | CLANKY1
GF> 

Omezení Gadfly

  • vůbec nepodporuje hodnotu NULL
  • nedporuje žádné operace s datumy
  • nepodporuje Outer join
  • Typeless databaze, při operacích jsou akceptována data libovolného podporovaného typu bez ohledu na typ sloupce.
  • nepodporuje operator LIKE
  • nepodporuje přístupová práva k db objektům
  • nepodporuje DELETE, UPDATE přes cursory (podobně jako postgresql)
  • neškálovatelná databáze. Celý obsah databáze je při startu nahrán z disku do operační paměti

Nejvíce nemilá jsou první dvě omezení. Datumy je možné ještě oželet s výmluvou na typeless databázi, ale nepodporování hodnoty NULL je vážným omezením. V praxi to znamená, že pokud chcete používat Gadfly jako alternativní db stroj, musíte aplikaci předělat, aby je nepoužívala, což se nemusí vždy podařit.

Podporované vlastnosti

Na druhé straně Gadfly podporuje u embedded databází méně běžné vlastnosti, jako jsou subselecty, agregační funkce včetně nadstandardního MEDIAN, operátor DISTINCT, a to i v agregačních funkcích, prepared statementy, pohledy – včetně user defined v Pythonu (!!), operátory BETWEEN, EXISTS, ANY, ALL a IN. Transakce jsou podporovány včetně crash recovery. Roll forward (nazývané též media) recovery podporováno není.

Použití

Databáze Gadfly má interface pouze do jednoho jazyka, a to do Pythonu. S databází se pracuje standardně pomocí Python Database API.

Příklad použití

Python 2.3.2 (#1, Dec  5 2003, 03:04:50)
[GCC 3.3.3 [FreeBSD] 20031106] on freebsd5
Type "help", "copyright", "credits" or "license" for more information.
>>> import gadfly
>>> c=gadfly.gadfly("netmag","/tmp")
>>> rs=c.cursor()
>>> rs.execute("select * from clanky")
>>> rs.fetchone()
('Nas prvni clanek...', 0, 1) 

Maličkosti

Bystří čtenáři si jistě povšimli, že pořadí vrácených sloupců není při použití operátoru * shodné s pořadím, v jakém byly původně sloupce definovány. Toto chování je odlišné od chování většiny databází a je třeba na něj při programování myslet. Dalším nedostatkem je, že pokud vytvoříte dvě databáze v jednom adresáři, což je možné, přepíší se vám navzájem tabulky se stejným jménem.

Callback view

Jak již bylo psáno, Gadfly umí použít pro definici pohledu místo SQL statementu pythonskou třídu. To nám sice plně user defined funkce a stored procedury v databázi nenahradí, ale pro embedded databáze je to zcela postačující.

Vložíme si do tabulky pár dalších záznamů:

GF> select * from clanky;
TEXT                                 | CTENO | ID
=================================================
Nas prvni clanek...                  | 300   | 1
Novy design!                         | 500   | 2
Vratili jsem tam radsi ten stary.... | 800   | 3
Co takhle zkusit oba dva?            | 321   | 4
Radsi najmeme webdesignera           | 456   | 5

Vytvoříme si pohled, který nám zobrazí jen články s čteností nad 400.

import gadfly
import gadfly.introspection

class hodnectene(gadfly.introspection.RemoteView):
  def __init__(self,db):
    self.column_names=['id']
    self.db=db
  def listing(self):
    rs=self.db.cursor()
    rs.execute("SELECT id FROM clanky WHERE cteno>400")
    return map(lambda x:x[0],rs.fetchall())

c=gadfly.gadfly("netmag","/tmp")
c.add_remote_view("nejlepsi", hodnectene(c))

rs=c.cursor()
rs.execute("SELECT * FROM nejlepsi");
print rs.fetchall() 

Víceuživatelský přístup

Databáze gadfly obsahuje server gfserver, který umožnuje víceuživatelský, vzdálený přístup k databázi. Spíše než databázový server je to jednoduchý multiplexer jednotlivých databázových operací. Umožňuje však vytvářet rozdílné security policy, a jednoduchým způsobem tak omezovat příkazy, které je možné v databázi provádět.

Malá ukázka:

(hsn@sanatana):% gfserver 2222 netmag /tmp tajneheslo
gfserve: starting the server
main loop on <socket._socketobject object at 0x8309a2c> <gadfly.database.gadfly instance at 0x81d21ac>
server: waiting for connection(s)

Přístup k serveru. /admin/ je jméno policy.
Python 2.3.3 (#2, Apr 18 2004, 08:35:27)
[GCC 3.3.3 [FreeBSD] 20031106] on freebsd5
Type "help", "copyright", "credits" or "license" for more information.
>>> import gadfly.client
>>> conn = gadfly.client.gfclient("admin", "tajneheslo", "localhost",2222)
>>> c=conn.cursor()
>>> c.execute("SELECT * from clanky")
>>> c.fetchall()
[('Nas prvni clanek...', 300, 1), ('Novy design!', 500, 2), ('Vratili jsem tam radsi ten stary....', 800, 3), ('Co takhle zkusit oba dva?', 321, 4), ('Radsi najmeme webdesignera', 456, 5)] 

Rychlost

Realnému otestování rychlosti bránil fakt, že Gadfly nepodporuje hodnoty NULL. Nemohl jsem tak pouhou změnou db driveru provést test v reálné aplikaci.

zabbix_tip

Pro hrubou ilustraci jsem změřil inserty za sekundu na Pentiu 200 Mhz. U databáze PostgreSQL jsem prepared statementy netestoval, protože každý driver používá odlišnou syntaxi a mapování na server-side prepared statementy není podporováno. V našem případě by server-side prepared statementpřidal 15 % Prepared statementy se vyplácejí jen u krátkých dotazů, u déletrvajících dotazů je jejich vliv prakticky nulový. Z tabulky je vidět, že výkon Gadfly je pro běžné použití rozhodně dostačující.

Tabulka č. 569
postgresql 7.4 psycopg driver 750
postgresql 7.4 PyGreSQL driver 614
postgresql 7.4 pyPgSQL driver 462
gadfly – immediate 98
gadfly – prepared statement 229

Závěr

Proč použít Gadfly databázi? Odpověd jsem získal od jednoho výrobce komerčního software, který ji používá.

  • Nemusí se nic instalovat
  • Rychlost je přijatelná
  • Běží na všech platformách podporovaných Pythonem
  • Moje aplikace tak není závislá na SQL serveru
  • Umí SQL

Autor článku

'; 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 »