Co je nového u Raspberry Pi? Všechno!

11. 2. 2015
Doba čtení: 6 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Jsou to už necelé tři roky, co byl vypuštěn do světa první jednodeskový mikropočítač z rodiny Raspberry Pi. Za tu dobu se jeho vývoj nezastavil, postupně byly představeny jak hardwarové, tak i softwarové inovace. Pojďme si dnes, u příležitosti vydání druhé generace, připomenout nejzajímavější novinky.

Přesně 29. února 2012 byl zahájen prodej první verze Raspberry Pi. Druhého února 2015, tedy zhruba po třech letech, byl zahájen prodej druhé generace s mnohem výkonnějším procesorem, avšak stále za stejnou cenu 35 USD. To ale rozhodně není jediná inovace.

Snadná instalace díky NOOBS

Ukázalo se, že cílovou skupinou Raspberry Pi je kromě počítačových nadšenců také spousta lidí, pro které jde o první kontakt s počítačem vůbec. Pro takové lidi může být nepřekonatelný problém už jen nahrání staženého obrazu na SD kartu. NOOBS je něco mezi boot-loaderem a živým prostředím, které stačí nainstalovat rozbalením ZIP archivu na obyčejnou FAT32 formátovanou SD kartu.

Využívá se zde toho, že vestavěný bootloader v Raspberry Pi dokáže načíst firmware pro GPU právě z FAT32 oddílu na SD kartě. Odtamtud se spustí minimalistický linuxový systém, který zmenší FAT32 oddíl na SD kartě a připraví tak místo pro instalaci vlastních distribucí. Následně je uživateli prezentován výběr distribucí k instalaci, které mohou být staženy z internetu a nainstalovány do volného místa.

Po volbě distribuce, která má být spuštěna, zapíše NOOBS do speciálního registru číslo FAT32 oddílu, ze kterého má být příště startováno, a provede restart voláním reboot(2). Zajímavostí je, že součástí NOOBS je také odlehčený webový prohlížeč arora, takže chcete-li jen něco zkontrolovat na internetu, vystačíte si se samotným bootloaderem.

Vylepšený model B+

První vydání spíše než počítač připomínalo více vývojovou desku, na které bylo kromě samotného SoC minimum externích součástek a komplikovaný členitý systém konektorů ze všech stran. Přidání dvou montážních děr u revize 2 prvního modelu situaci nijak zásadně nevylepšilo. Zásadní změna naopak nastala v červenci 2014 představením modelu B+ – finálního vývojového stupně první generace Raspberry Pi.

Raspberry Pi B+

Raspberry Pi B+

Na první pohled zaujme pravoúhlý tvar a čtyři montážní otvory v pravidelném uspořádání. Rozšířen byl GPIO konektor, přičemž jeho levá část je zpětně kompatibilní s předchozím modelem. Vylepšen byl také napájecí zdroj, který vstupní napětí 5 V snižuje na 1,8 V pro jádro SoC a 3,3 V pro ostatní zařízení. Ten byl původně realizován lineárními regulátory, nově jde o spínané měniče s vyšší účinností.

Také byl po zkušenostech s nekvalitními napájecími zdroji přidán brown-out detektor, který detekuje i jen krátkodobý pokles vstupního napětí pod 4,65 V. Ten je signalizován zhasnutím červené LEDky a také rozsvícením duhového čtverečku v pravém horním rohu obrazovky. Tahle funkce je přímo součástí firmwaru GPU a tedy zcela nezávislá na běžícím operačním systému. Další podobnou funkcí je červený čtvereček, který se rozsvítí při překročení teploty 85 °C.

Přechod na Device Tree a HATy

Původně se specifika každé desky, včetně Raspberry Pi, zapisovala do definičního souboru desky, který byl součástí zdrojových souborů jádra. Tak se chovala i první jádra pro Rapsberry Pi. Chtěli jste softwarově ovládané 1-Wire rozhraní? Musíte použít GPIO číslo 4. Pokud jste chtěli použít jiné, nezbývalo než upravit definiční soubor a překompilovat jádro. Zvláštní obraz jádra tak musel existovat i pro různé desky se stejným SoC. Spolu s rozmachem ARMovských platforem začal narůstat i počet konfliktů, kdy přidání podpory jedné desky znefunkčnilo podporu jiné. Až tomu jednoho dne učinil přítrž samotný Linus Torvalds, když prohlásil, že žádné další definiční soubory desek přidávat nebude.

Tímto krokem jsou postupně všechny platformy nuceny k přechodu na Device Tree. Jde o binární datovou strukturu, která popisuje hardware konkrétní desky. Tato struktura je nahrána zavaděčem do paměti při startu spolu s obrazem jádra a případně i initramfs  a Linux ji za běhu interpretuje. Velkou výhodou je zde oddělení definice hardwaru od zbytku jádra, takže stejné jádro je možné zavést na více různých deskách, které používají stejnou architekturu.

S Device Tree také souvisí podpora standardizovaných rozšiřujících desek nazvaných Hardware Attached on Top (HAT). Desky podle tohoto standardu musí obsahovat identifikační EEPROM paměť, jejíž obsah během startu vyčte bootloader. Tahle paměť může obsahovat i právě fragment Device Tree, který je tak automaticky sloučen s Device Tree základní desky. Tak je možné snadno a automaticky zpřístupnit hardware na přídavných deskách.

Vzorová deska podle specifikace HAT

Pod čarou: zprovoznění PoEpi na Device Tree

Změna na Device Tree mě zaskočila nepřipraveného. Po nedávné aktualizaci Raspbianu přestaly být na mé rozšiřující desce PoEpi dostupné hodiny reálného času a 1-Wire převodník DS2482. Při bližším zkoumání se ukázalo, že Linux nadále už nevidí ani I²C sběrnici. Pomohlo právě sestavení následujícího fragmentu Device Tree

/dts-v1/;
/ {
        compatible = "brcm,bcm2708";
        fragment@0 {
                target = <&i2c1>;
                __overlay__ {
                        status = "okay";
                        pcf8583@50 {
                                compatible = "nxp,pcf8583";
                                reg = <0x50>;
                                status = "okay";
                        };
                        ds2482@18 {
                                compatible = "maxim,ds2482"
                                reg = <0x18>;
                                status = "okay";
                        };
                };
        };
};

Tento fragment je po kompilaci nástrojem dtc uložen do /boot/overlay/poepi-overlay.dtb a zaveden při bootu příkazem dtoverlay=poepi v souboru /boot/config.txt. Po restartu tak jádro už přesně ví, že na dané I²C sběrnici jsou zapojeny dvě zařízení s danými adresami a zavede pro ně příslušné ovladače.

Nezávislý VGA výstup z modelu B+

Velmi zajímavá je nová možnost použít rozšiřující konektor modelu B+ jako samostatný VGA výstup. SoC takové použití podporuje, stačí připojit D/A převodník, což může být v nejjednodušším případě jen několik rezistorů. Výstup je řízen přímo grafickým procesorem, takže je výkonově zcela ekvivalentní displeji připojenému na HDMI nebo kompozitní video výstup. Kromě toho je zde možnost zobrazovat nezávislé výstupy na VGA a HDMI. K tomuto však zatím stále chybí softwarová podpora. Destička s adaptérem je k dispozici jako open hardware, obsahuje pouze dva konektory a 20 rezistorů, takže není problém vyrobit ji i v domácích podmínkách.

Druhá generace: rychlejší SoC, jinak vše při starém

Tím se dostáváme k události oznámené v pondělí 2. února, totiž představení druhé generace Raspberry Pi. Je velice překvapivé, že se celou událost podařilo držet do poslední chvíle pod pokličkou, takže v době, kdy k oznámení došlo, byly už desky k dispozici v logistických skladech a byly už připraveny první recenze. I zákazníci z ČR tak obdrželi první kousky hned následující den.

Nová deska konečně opustila zastaralý SoC BCM2835, který byl nahrazen za BCM2836. Jedná se o čtyřjádrový ARM Cortex-A7, taktovaný na 900 MHz. K němu je nově připojena LPDDR2 paměť o kapacitě 1 GiB. Kromě toho se od modelu B+ první generace vlastně nic moc nezměnilo, dokonce ani cena. Jediná na první pohled výrazná změna spočívá v tom, že výrobce opustil koncepci package on package pro připojení paměti, která je nově připájena ze spodní strany desky.

Asi by se slušelo na závěr napsat nějaké dojmy z nového SoCu. Nebudu místní čtenáře obtěžovat tím, že bych se snažil vymýšlet nějaké nové benchmarky, místo toho bych odkázal například na benchmark na stránkách Hack a Day, nebo hrubé porovnání spotřeby a výkonu. Osobně bych krátké zkušenosti s používáním druhé generace shrnul do věty: Je to stále pomalé, ale aspoň už to není nepoužitelně pomalé. Pro velmi nenáročné úlohy si RPi druhé generace dokážu představit i v roli stolního počítače.

Blbinka: nové Raspberry Pi se bojí bouřek

Z návštěvy zoologických zahrad si pamatuji cedulky s nápisem:

hacking_tip

Bojíme se bouřek, nefoťte nás s bleskem.

Podobný nápis můžete dát na své nové Raspberry Pi 2. Také se z nějakého důvodu blesků bojí a vypálení xenonového blesku v jeho blízkosti způsobí jeho okamžité vypnutí. Vyzkoušeno, funguje. ☺

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.

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