Pošta pro každého (1)

1. 7. 2002
Doba čtení: 6 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Doručování pošty patří mezi nejdůležitější funkce počítače. Dnes si popíšeme cestu pošty od odesílatele k příjemci a úkoly jednotlivých programů, které se na této činnosti podílejí. Nakonec si přiblížíme cestu zprávy programem Sendmail.

Jak putuje e-mail

Odchozí pošta, napsaná v poštovním programu (MUA – Mail User Agent), je v unixovém světě předávána odesílacímu programu (MSP – Message Submission Program), který se stará o její odeslání (býval jím /usr/bin/mail, /usr/lib/sendmail, dnes to standardně bývá /usr/sbin/sen­dmail). Je to program, který předá poštu dále. Výhoda je v tom, že uživatelský program nemusí vědět nic o poštovních protokolech a stará se pouze o interakci s uživatelem. Navíc se o konfiguraci a správu odchozí pošty stará správce systému.

MSP předává poštu dále programu pro přenos pošty (MTA – Mail Transport Agent). V dobách izolovaných sítí procházela pošta přes více MTA, než byla doručena. Dnes se většinou omezuje na jeden server (tzv. SMTP server) – místo, kde se pošta přechovává před doručením (Relay).

Z klikacích operačních systémů pronikl systém vše v jednom (All In One) – mnoho z těchto systémů MSP obchází a poštu předává rovnou vzdálenému SMTP serveru, platí však za to nutností konfigurace odchozí pošty pro každého uživatele zvlášť a nutností odesílat poštu v době připojení a aktualizovat nastavení všem uživatelům při každé změně SMTP serveru (následkem toho řešení vše v jednom potřebují zvláštní identitu pro připojení přes různé providery). Zvlášť nepříjemné to je tehdy, používáme-li k vyřizování pošty více takových programů. Naštěstí lze naprostou většinu programů vše v jednom přimět, aby spolupracovaly s místním MTA, a tím problémy s obcházením MSP odpadnou.

MTA komunikuje s cílovým serverem (nebo dalšími MTA), aby doručil poštu pro příjemce. Na cílovém serveru předá dalšímu článku štafety – doručovacímu programu (MDA – Mail Delivery Agent). MDA může poštu ještě dále třídit a naloží s ní podle uživatelem zadaných pravidel – nejčastěji putuje do přidělené schránky. Pak již pošta čeká jen na to, až si ji vyzvedneme. Schránka může být i na pracovním počítači, ale většinou je to nepraktické – pokud by nebyl připojen v době, kdy nám dojde nová pošta, snadno by se mohlo stát, že se vrátí odesílateli jako nedoručitelná. Na cílovém serveru však pošta počká, dokud si ji nevyzvedneme.

Veškerá komunikace, kterou jsme doposud popsali, probíhá v protokolu SMTP (Simple Mail Transport Protocol), jemuž je vyhrazen TCP port 25, nebo jeho rozšířenou verzí ESMTP (mimo jiné s podporou osmibitového přenosu).

Ke konečnému doručení z poštovního serveru do našeho počítače se již používají jiné protokoly: POP (Post Office Protocol; nejčastěji se lze setkat s verzí POP3), protokol, jehož úkolem je stáhnout poštu do pracovního počítače, nebo IMAP (Internet Mail Access Protocol, dnes ve verzi IMAP4), který je navržen nejen ke stahování pošty, ale i pro dálkovou práci s ní. Na straně poštovního serveru k tomu samozřejmě musí běžet příslušný démon, který v těchto protokolech komunikuje. Existuje mnoho rozšíření protokolu POP3, spočívající na přihlašování bezpečnějším než je heslo v textové podobě – APOP (MD5), RPOP (RPOP), KPOP (Kerberos), POP3+OPIE (OTP) a další protokoly, spočívající na rozšíření ESMTP – ETRN a ODMR (On-Demand Mail Relay). Pro přístup k poště lze samozřejmě použít i standardní metody vzdáleného přístupu jako NFS nebo LDAP.

Ke stažení pošty lze použít specializované programy nebo opět řešení vše v jednom.

Někdy je výhodné, aby tento program přeposlal poštu dále opět v protokolu SMTP (například v malé firmě s vytáčeným připojením, kdy jeden počítač vyzvedne poštu pro všechny a rozešle ji po firmě). Nejčastěji však poštu předá MDA, a ten ji konečně doručí až na místo.

A teprve pak se pošta objeví v poštovním programu příjemce.

Klasické programy

Ve většině distribucí GNU-Linuxu naleznete „klasické“ programy pro většinu těchto činností.

Historická role MSP a MTA připadá ve většině distribucí na Sendmail. Je to robustní, schopný a dnes již snad i bezpečný monolitický program.

Role standardního MDA připadá programu Procmail. Jeho schopností filtrovat poštu využívá denně množství uživatelů na třídění pošty a programů a k odhalování nevyžádané pošty.

Roli programu pro vyzvedávání pošty pak většinou plní Fetchmail (dříve zvaný popclient).

Ke každému z těchto programů existují alternativy, jako například Qmail, Maidrop a Postfix nebo Vpopmail a další.

Původní Unixový MUA – mail nebo jeho následníka mailx již dnes skoro nikdo nepoužívá, neboť existuje obrovské množství lepších programů. Program tohoto jména najdete v distribucích nanejvýš pro potřeby skriptů.

POP a IMAP servery takového jednoznačného favorita nemají a existuje velké množství implementací.

Sendmail

Sendmail je program s historickými kořeny, a tak zvládá i věci, které dnes, v době světové pavučiny, ztratily svůj význam – doručování pošty do různých typů sítí, jakými byly Bitnet, Decnet, UUCP, a dokonce i doručování faxem.

Ještě v nedávných letech patřila k programátorskému folklóru pravidelná aktualizace Sendmailu kvůli nově nalezené bezpečnostní díře. To je však dnes již minulostí.

Při typické konfiguraci moderních instalací běží sendmail jako dva démony – jeden, privilegovaný, běží jako SMTP server, čeká na spojení na TCP portu 25 a stará se o odeslání čekající pošty z fronty v /var/spool/mqu­eue. Druhý, neprivilegovaný, občas nahlédne do neprivilegované fronty /var/spool/cli­entmqueue s poštou nepřijatou k odeslání (většinou proto, že se nepodařilo kontaktovat cílový server) a pokusí se o její opětovné odeslání.

Jak probíhá činnost Sendmailu při odesílání pošty? Poštovní program spustí neprivilegovanou instanci programu Sendmail (program je sgid a běží pod skupinou smmsp) a předá mu data zprávy. Sendmail se pokusí kontaktovat SMTP server a předat mu poštu k vyřízení. Pokud se to povede, SMTP server data poměrně rychle převezme a další osud pošty je na něm. Pokud se to nepovede, uloží poštu k pozdějšímu doručení do neprivilegované fronty /var/spool/cli­entmqueue a dále se o její osud nestará.

Jak se chová démon Sendmail jako SMTP server? Když mu dojde požadavek na port 25, ověří si oprávnění (většinou bývá povoleno uživatelům z místní sítě odeslat poštu kamkoliv a uživatelům zvnějšku doručit poštu místním uživatelům). Provede další testy, a pokud je vše v pořádku, poštu převezme a pokouší se ji doručit. Přitom používá frontu /var/spool/mqueue, kam si odkládá procházející poštu a poštu, kterou se zatím nepodařilo odeslat. Pokud se odeslání podaří, smaže ji z fronty a jeho úkol je splněn. Pokud ne, periodicky se pokouší odeslat ji znovu. Pokud se to nepovede ve stanoveném čase, pošle nejdříve odesílateli varování, po delší době pak chybové hlášení a poštu z fronty vyřadí.

Druhá, neprivilegovaná instance démonu Sendmail běží pod skupinou smmsp a uživatelem smmsp a hlídá, zda do neprivilegované fronty /var/spool/cli­entmqueue nepřibyla nějaká neodeslaná pošta. Tu se pak periodicky pokouší odeslat místnímu SMTP serveru. V ostatních ohledech se chová podobně jako první instance (až na to, že nehlídá port 25).

Typická přístupová práva pro adresáře a soubory Sendmailu vypadají asi takto:

-r-xr-sr-x  root  smmsp  /usr/sbin/sendmail*
drwxrwx---  smmsp smmsp  /var/spool/clientmqueue/
drwx------  root  root   /var/spool/mqueue/

Stav neprivilegované fronty lze kontrolovat příkazem mailq -Ac, stav privilegované fronty může superuživatel ověřit příkazem mailq. Ke statistice mailového provozu slouží mailstats. S dalšími programy se seznámíme později.

U starších instalací programu Sendmail s jedinou frontou se spouštěl jediný démon. Všechny instance byly spouštěny jako suid s právy superuživatele a nepřijatou poštu ukládaly rovnou do fronty /var/spool/mqueue. Od této konfigurace bylo z bezpečnostních důvodů v nových verzích upuštěno.

linux_sprava_tip

Většina správců systému nahlédne do konfiguračního souboru, zhrozí se a již se více nepokouší Sendmail konfigurovat. A přitom existuje jednoduchý a elegantní postup, jak sendmail přizpůsobit svým požadavkům. Je jim množina m4 maker, s jejichž pomocí vytvoříme krátký konfigurační soubor, a ten pak zpracujeme makroprocesorem. Příslušná makra možná naleznete ve vaší instalaci, ale určitě v balíčku se zdrojovým kódem. Spolu s ním nalezneme i README, kde jsou základní informace o jednotlivých volbách.

Příště si ukážeme, jak lze Sendmail s jejich pomocí nakonfigurovat.

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 »