Lukáš Němčík (COOP): Letos budeme mít až stovku automatizovaných prodejen

2. 2. 2024
Doba čtení: 7 minut

Sdílet

Na naše automatizované prodejny se jezdí dívat konkurence ze zahraničí, říká Lukáš Němčík ze skupiny COOP. Je to důkaz, že naše řešení je životaschopné, dodává.

Družstevní svaz COOP slaví úspěch se svými automatizovanými prodejnami. První takový obchod otevřela před dvěma roky, nyní jich provozuje už čtyřiadvacet a letos jich chce přidat několik desítek.

Podle Lukáše Němčíka, ředitele marketingu a rozvoje celé skupiny, se díky automatizovaným obchodům podařilo udržet prodejny i v místech, kde dřív byly nerentabilní. V podcastu pro server Lupa.cz detailně popisuje, jak obchody fungují, jak řeší krádeže a jak firma při sledování chování zákazníků využívá čím dál víc umělou inteligenci.

Za spuštění obchodů vyhrála skupina COOP i kategorii E-commerce inspirace v naší anketě Křišťálová Lupa 2023.

Část rozhovoru jsme přepsali do textu, celý si jej můžete poslechnout ve formě podcastu na službách Spotify, Apple Podcast, Google Podcasts nebo přímo zde. Sledovat můžete i náš kanál na YouTube.

Nečekali jsme, že zájem bude tak velký. To je vaše citace z loňské konference o budoucnosti maloobchodu. Jaká tedy byla vaše očekávání, když jste předloni v březnu první automatizovanou prodejnu otevřeli?

Nulová, protože jsme absolutně nevěděli, co máme očekávat. Neměli jsme žádný referenční rámec, vůči kterému bychom se vymezili. Když jsme se na spuštění obchodu připravovali, dělali jsme si samozřejmě rešerši. Zkoumali jsme podobná řešení v zahraničí, hodně jsme se inspirovali od Amazonu nebo podobnými službami ve Skandinávii. Jenže všechny byla trochu jiné, fungovaly jinak a my věděli, že to chceme dělat odlišně. Ale nevěděli jsme, jak se zachová český zákazník.

Vzpomínám si, když jsme otevírali první prodejnu ve Strakonicích, tak nám starosta Strakonic říkal při otevření: „Já vám tady radši ty měšťáky nechám postavené celou noc, protože oni to do rána rozeberou. Nevíte, co se nám tady pohybuje za lidi.“ Nyní jsme o dva roky později a situace je taková, že máme dnes čtyřiadvacet prodejen, jako je ta ve Strakonicích, neměli jsme jediný výjezd a zjišťujeme, že s krádežemi není žádný problém. 

Zároveň ale víme, že prodejny lidé využijí. Dopředu jsme ale vůbec netušili, jaký bude o prodejny zájem. Nevěděli jsme, jestli přijde jeden zákazník denně, jeden za týden, nebo tisíc denně. Nyní výsledek už víme. Během vánočního provozu se podíl automatizovaného režimu na celkovém režimu pohybuje mezi 10 až 40 procenty. Samozřejmě záleží na lokalitě. 

Největší radost mi ale udělalo, že naši kolegové ze zahraniční konkurence se chodí dívat, jak takové prodejny fungují. Nestává se úplně často, že by český obchodník ukazoval do zahraničí, jak by měla vypadat prodejna budoucnosti.

Lukáš Němčík, ředitel marketingu a rozvoje skupiny COOP.

Lukáš Němčík, ředitel marketingu a rozvoje skupiny COOP.

Autor: COOP, publikováno se svolením

Když jsme na Lupě popisovali před dvěma roky otevření strakonické prodejny, kolega použil obrat, že od svazu družstev COOP asi málokdo čekal, že se stane technologickým průkopníkem. Kde se tedy vzal nápad, že otevřete automatizovanou prodejnu?

Prvotní geneze je trošinku složitější. My jsme před několika roky začali řešit, jak do prodejen přidávat doprovodné služby. Máme oproti korporátům jeden hendikep, že se dobře marketingově neprodáváme. Přitom jsme před mnoha lety zavedli například službu cashback a dnes na našich prodejnách tvoříme šedesát procent výběrů hotovosti v rámci celé České republiky. Naše prodejny jsou tak minibankomaty. Nebo platba složenek. Dnes je to už na ústupu, ale když jsme to přes dvaceti roky zaváděli, tak to byla vlastně alternativa České pošty. Naším cílem vždy bylo, abychom udělali z prodejny centralizovaný bod, kde lidé najdou vše, co potřebují.

To nás dovedlo před několika roky k e-shopu. Ale když už jsme spustili e-shop, tak jsme řešili, jak zboží dostat k zákazníkovi. Rozvozy pro nás nejsou ideální cestou, protože vidíme nejlepší cestu v krátkém obchodním řetězci – tedy v místě vyrobit, v místě prodat a v místě spotřebovat a v místě zaměstnat. Nedává nám tedy smysl vyrábět jogurt třeba v Hlinsku a pak ho převážet na druhý okraj republiky přes centrální sklad v Praze. Navíc když naše prodejny mají omezenou otevírací dobu. To nás proto přivedlo k automatickým výdejním boxům na potraviny, které jsme zavedli jako první v Česku. Do dnešního dne mám mail jako od kolegů z Rohlíku, kteří někde uvedli, že boxy zavádí jako první. Jenže my je měli už pár měsíců předtím, takže se mi za tu citaci omluvili.

Boxy byly v určitém čase populární, ale dělat je pro potraviny je náročné a drahé na provoz. Ukázalo se, že je to slepá ulička. Nicméně to pro nás bylo jako předstupeň k automatizovaným prodejnám. Zvažovali jsme, jak to udělat, a našli jsme firmu Knowinstore, která nad těmito obchody také přemýšlela. A rozhodli jsme se udělat společně vzorovou prodejnu a sledovat, co to udělá. A ukázalo se, že to funguje a že je to životaschopné.

Většina lidí si asi nedokáže představit, jak taková prodejna funguje. Lze to stručně popsat?

Naše prodejna má tu výhodu, že je opravdu jednoduchá. Říkal jsem, že jsme hledali inspiraci na různých místech a na každém řešení nám něco vadilo. Nejvíce právě složitost. Amazon Go je velmi funkční a krásné zařízení, v českém prostředí je ale úplně mimo jakoukoliv ekonomickou rentabilitu. Myslím tu regionální. Na Václavském náměstí v Praze by to možná fungovalo, ale my máme 2400 prodejen po republice a řešíme každou tisícikorunu.  Skandinávská řešení zase měla tu nevýhodu, že to byly vlastně zvětšené prodejní automaty, kde byl úzký sortiment kolem stovky položek. 

My jsme navíc nechtěli prodejnu pro technologii, ale technologii pro prodejnu. A chtěli jsme jednoduché řešení a právě to se s kolegy z Knowinstore podařilo. U vchodu jsme udělali čtečky, přes které se dostanete s pomocí aplikace. Dnes používáme aplikaci Do kapsy od ČSOB, kterou si spárujete s Bankovní identitou.

Když chcete do některé prodejny jít, tak u vchodu ukážete vygenerovaný QR kód, který se zároveň pravidelně mění, abyste ho nemohl nasdílet někomu jinému. V obchodě si pak vyberete, co chcete. U automatické pokladny pak naskakujete jednotlivé položky, zaplatíte kartou a znovu naskenujete QR kód, aby se potvrdilo, že jde o toho, kdo do prodejny vstoupil. A při odchodu ten kód ukážete ještě jednou, abyste mohl z prodejny odejít.

V prodejně jste víceméně sami, ale zároveň to není úplně pravda, protože jste pořád ve formě vzdálené správy a hodně dnes využíváme umělou inteligenci. V obchodech jsou kamery, které jsou napojené na centrální dispečink, a umělá inteligence umí dispečera v případě nějakého problému upozornit. Následná reakce je ve formě hlasu shůry, kdy na dálku dispečer může zákazníka oslovit. Třeba řekne: „Pane, vidím vás, jak si tam otevíráte pivo a pijete ho. To není povoleno, zaplaťte ho a pak si ho odneste, ať se nic nestane.“ Dispečer má také třeba možnost na dálku zavírat a otevírat dveře a ovládat i pokladnu. Splňujeme tím legislativní požadavky, které jsou na provoz prodejen dány.

Bez vstupní aplikace se tedy nikdo do prodejny nedostane. Jak je to řešené s lidmi, kterým je méně než osmnáct, nebo jsou naopak starší a nemají třeba chytrý telefon nebo bankovní identitu?

Když je vám pod osmnáct, tak nemáte šanci se do prodejny dostat. To je rozdíl od prodejen v zahraničí, kde se ale skutečně jedná spíš o zvětšené automaty s omezeným sortimentem. My ale nabízíme v prodejnách i alkohol, a právě proto pouštíme dovnitř v automatizovaném režimu pouze osoby nad osmnáct let. 

My provozujeme oproti zahraničí hybridní režim, kdy je část dne v prodejně personál. A díky tomu si tam mohou nakoupit i lidé, kteří nemají bankovní identitu nebo nemají patřičné technologie. Zároveň ale víme, že řada starších zákazníků si jde s příbuznými vyzkoušet, jak automatizovaný režim funguje, a většinou to s jejich pomocí nastaví a následně i zvládnou. Také si uvědomme, že se do důchodového věku dostávají lidé, kteří už běžně nové technologie používají.

Zmiňovali jsme, že první automatizovanou prodejnu jste otevřeli už před dvěma roky. Máte už za tu dobu nějaká data, který by ukazovala na změnu chování zákazníků, kteří do automatizovaných obchodů chodí?

Ono strašně záleží, o jakou prodejnu jde, protože když se jedná o prodejnu vesnického typu, tak tam tento automatizovaný režim supluje běžnou otevírací dobu. Chování se tedy nezměnilo, jen se v čase rozložilo, kdy lidé nakupují běžné položky jako v minulosti. Typicky jsou to třeba zaměstnanci, kteří se vracejí třeba z okresního města domů, kde ale už naše prodejna měla předtím zavřeno. Takže to bylo tak, že nakoupili ve městě a s plnými taškami pak jeli půl hodiny autobusem domů. Dnes je to tak, že domů jedou bez nákupu a bez problému to vyřeší až v jejich vsi. 

Asi nejvíc ale tu možnost kvitují zákazníci u městských prodejen, které říkáme párty skupina. Týká se to lidí, kteří mají třeba nečekanou návštěvu a něco jim dojde. Nebo mají večer na něco chuť a chtěli by si něco koupit za ne „benzinkové“ ceny. V takovém případě naše automatické prodejny suplují celonoční provoz.

MM Influenceři

Co dalšího v rozhovoru zaznělo?

  • O kolik se společnosti zvýšily díky automatizovaným obchodům tržby
  • Jak přesně funguje umělá inteligence, kterou COOP ve svých automatizovaných prodejnách využívá
  • Kolik stojí vybudování automatizované prodejny a jaké s nimi má skupina další plány
  • Jaké situace už museli dispečeři u automatizovaných obchodů řešit

Celý podcast si můžete poslechnout zde:

  • Chcete mít Lupu bez bannerů?
  • Chcete dostávat speciální týdenní newsletter o zákulisí českého internetu?
  • Chcete mít k dispozici strojové přepisy podcastů?
  • Chcete získat slevu 1 000 Kč na jednu z našich konferencí?

Staňte se naším podporovatelem

Seriál: Rozhovory
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Autor je od ledna 2018 ředitelem médií vydavatelství Internet Info. Předtím 6 let vedl zpravodajskou sekci portálu iDNES.cz, ještě předtím byl několik let reportérem celostátní redakce MF DNES. Občas si rád něco napíše.

'; 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'; } }
Upozorníme vás na články, které by vám neměly uniknout (maximálně 2x týdně).
'; 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 »