Soud zatnul Albertu tipec se „slevou“, která byla ve skutečnosti zdražením

17. 1. 2025
Doba čtení: 8 minut

Sdílet

Soud s obchodníkem, který má na krku ceduli s falešnou slevou.
Autor: Podnikatel.cz s využitím DALL-E
Ilustrační obrázek
Obchodní řetězec Albert neuspěl se žalobou na Českou obchodní inspekci, která mu udělila pokutu mimo jiné za uvádění falešných slev.

Krajský soud v Plzni totiž v půlce loňského listopadu rozhodl, že Albert skutečně porušil při prezentaci cen a slev zákon a pokutu dostal oprávněně.

Co se dozvíte v článku
  1. Albert dostal statisícovou pokutu 
  2. Nikde není řečeno, z jaké ceny se má sleva počítat, argumentoval Albert
  3. Cílem úpravy byla transparentnost
  4. Průměrný spotřebitel nečeká při slevě zdražení
  5. Proti falešným slevám vystoupil i evropský soud

Albert dostal statisícovou pokutu 

Česká obchodní inspekce (ČOI) potrestala v roce 2023 obchodní řetězec Albert za to, že porušil § 3 odst. 1 písm. c), § 12a odst. 1 písm. a) a § 12 zákona o ochraně spotřebitele, pokutou ve výši 400 000 Kč. Uvedených přestupků se řetězec dopustil tím, že:

  • nesprávně účtoval jeden výrobek zakoupený do kontrolního nákupu
  • neinformoval spotřebitele pravdivě o nejnižší ceně jednoho druhu výrobku, nabízeného k prodeji ve slevě, za kterou jej jako prodávající nabízel a prodával v době 30 dnů před poskytnutím slevy
  • neinformoval spotřebitele o ceně výrobků uplatňované v okamžiku nabídky, když cena nebyla spotřebiteli nijak zpřístupněna a spotřebitel tak neměl možnost se s ní seznámit před samotnou koupí výrobků.

U porušení zákona ohledně slev šlo o to, že řetězec 1 kg kávy Tchibo Barista nabízel a prodával v době 30 dnů před poskytnutím slevy, přičemž v prodejní ceně 399 Kč po slevě deklarované nápisem „- 44 % AKCE“, s uvedenou původní přeškrtnutou cenou před slevou 719 Kč. V pravém dolním rohu však byla menším písmem uvedena nejnižší částka za posledních 30 dnů ve výši 379 Kč a v závorce zvýšení ceny o 6 %. Podle ČOI se případná sleva měla počítat právě z 379 Kč a Albert tak měl uvést, že fakticky zboží zdražil. 

Pro zajištění souladu s čl. 6a směrnice musí prodávající oznamující slevu z ceny identifikovat nejnižší cenu, kterou účtoval za příslušné zboží nebo zboží po dobu nejméně posledních 30 dnů před uplatněním slevy z ceny. Tato nejnižší cena zahrnuje jakoukoliv předchozí cenu „po slevě“ během tohoto období. Při identifikaci nejnižší ceny není podstatné, zda se výrobek za určitou cenu skutečně prodal, postačuje, že byl za danou cenu prodávajícím nabízen. Sleva z ceny musí být prezentována s použitím uvedené „předchozí“ ceny jako ceny referenční, a rovněž jakákoliv uvedená procentuální sleva z ceny musí být založena na „předchozí“ ceně stanovené v souladu s čl. 6a směrnice, doplnila ČOI, která i po odvolání setrvala na svém právním názoru, pokutu však snížila na 200 000 Kč.

Zákon o ochraně spotřebitele

§ 12a
(1) Informace o slevě z ceny výrobku obsahuje informaci o nejnižší ceně výrobku, za kterou jej prodávající nabízel a prodával
a) v době 30 dnů před poskytnutím slevy,
b) od okamžiku, kdy začal výrobek nabízet a prodávat, do okamžiku poskytnutí slevy, pokud je výrobek v prodeji dobu kratší než 30 dnů, nebo
c) v době 30 dnů před prvním poskytnutím slevy, zvyšuje-li prodávající slevu z ceny postupně.
(2) Odstavec 1 se nepoužije pro výrobky, které podléhají rychlé zkáze, nebo pro výrobky s krátkou dobou spotřeby.

Článek 6a evropské směrnice 98/6/ES o ochraně spotřebitelů při označování cen výrobků nabízených spotřebiteli

  1. Veškerá oznámení o slevě z ceny musí uvádět předchozí cenu, kterou obchodník uplatňoval po určité období před uplatněním slevy z ceny.
  2. Předchozí cenou se rozumí nejnižší cena, kterou obchodník uplatňoval během období ne kratšího než 30 dnů před uplatněním slevy z ceny.
  3. Členské státy mohou stanovit odlišná pravidla pro zboží, které snadno podléhá zkáze nebo má krátkou dobu spotřeby.
  4. Pokud byl výrobek na trhu po dobu kratší než 30 dnů, členské státy mohou rovněž stanovit kratší období, než je období uvedené v odstavci 2. 
  5. Členské státy mohou stanovit, že v případech, kdy se sleva z ceny postupně zvyšuje, rozumí se předchozí cenou cena bez slevy z ceny před jejím prvním uplatněním;

Nikde není řečeno, z jaké ceny se má sleva počítat, argumentoval Albert

Obchodnímu řetězci se však ani toto rozhodnutí ČOI nezamlouvalo a podal žalobu k soudu. V ní rozporoval jen právě sankci za nepravdivě uvedenou slevu. V žalobě řetězec argumentoval, že obchodníci nemají povinnost vztahovat procentuální výši slevy primárně k nejnižší ceně, jelikož takovou povinnost nestanoví žádný závazný právní předpis. Není zakázáno uvádět jiný referenční údaj než nejnižší cenu a vztahovat k němu procentuální výši slevy (…). Proto je možné vztahovat slevu primárně k jinému referenčnímu údaji než k nejnižší ceně, jelikož právní předpisy takový postup nezakazují. Též je možné uvést informace o jiném referenčním údaji výrazněji než informace o běžné ceně, jelikož právní předpisy takový postup nezakazují, uvedl v žalobě Albert.

Podle řetězce tak byla zákonná povinnost splněná už jen tím, že u zlevněných výrobků řádně uvedl informace o nejnižší ceně. Jak doplnil Albert, nad rámec právních předpisů k nejnižší ceně vyjádřil i procentuální výši slevy. Spotřebitel tak mohl snadno zjistit, jaká byla nejnižší cena a jaká je výše slevy vzhledem k nejnižší ceně. Porovnáním poskytnutých informací o nejnižší ceně a běžné ceně mohl spotřebitel zjistit, jestli cena daného výrobku byla uměle navýšena, dodal Albert ke slevě – 6 % z nejnižší ceny, která znamenala faktické zdražení.

Stříteský: Obchodníci dostávají v zahraničí vyšší sankce a slevy mají výrazně omezené Přečtěte si také:

Stříteský: Obchodníci dostávají v zahraničí vyšší sankce a slevy mají výrazně omezené

Cílem úpravy byla transparentnost

U Krajského soudu v Plzni však Albert neuspěl a veškerou jeho argumentaci soud odmítl. Jak upozornil soud, účelem právní úpravy slev je především transparentnost. Šlo o reakci na častý jev tzv. falešných slev. Obchodníci nejprve cenu zvýšili a poté deklarovali slevu na původní úroveň, čímž klamali spotřebitele, nebo uváděli slevy z cen, které v praxi nikdy nebyly uplatňovány či byly uplatňovány jen po extrémně krátkou dobu. Nově proto dle zákona (potažmo evropské směrnice) platí, že prodávající, který oznamuje slevu, musí uvést nejnižší cenu uplatněnou v předchozích 30 dnech. Pojem „nejnižší cena“ je jasný, je to cena, za kterou byl výrobek v posledních 30 dnech reálně nabízen a prodáván.

Spotřebitel má jasnou představu o tom, z jaké cenové hladiny byla sleva skutečně poskytnuta. Místo klamání marketingovými triky se spotřebitel dozví, zda je sleva opravdová. Zadruhé, prevence zneužití. Ustanovení zabraňuje obchodníkům zneužívat informační asymetrii, kdy spotřebitel nezná cenovou historii a mohl by být oklamán zdánlivou slevou, jež je však počítána z fiktivního či krátkodobě uměle zvýšeného cenového základu. Zatřetí, důvěra v trh. Dodržování pravidel vytváří důvěru spotřebitelů v trh, což dlouhodobě prospívá férové hospodářské soutěži i reputaci obchodníka, doplnil soud.

Podle soudu je tudíž klíčové, aby spotřebitel reálně chápal, kolik mohl dříve za zboží zaplatit a kolik stojí nyní. Jakékoli jednání, které tento cíl maří, například uvedením nerealistické původní ceny nebo její skrývání před zákazníkem, která nebyla nejnižší za posledních 30 dní, přímo popírá podle soudu účel a smysl dotyčného ustanovení.

Obchody a e-shopy už nebudou moct „čarovat“ se slevami. Jaká budou nová pravidla? Přečtěte si také:

Obchody a e-shopy už nebudou moct „čarovat“ se slevami. Jaká budou nová pravidla?

Průměrný spotřebitel nečeká při slevě zdražení

Soud dále odkázal na koncept „průměrného spotřebitele“, který je dostatečně pozorný, informovaný a opatrný, avšak stále zranitelný vůči klamavým informacím. Pokud zákon stanoví povinnost uvést nejnižší cenu za 30 dní před slevou, je to podle soudu proto, že průměrný spotřebitel sám nemůže tuto informaci spolehlivě zjistit a je odkázán na serióznost prodejce. Pokud by prodejce neuváděl tuto informaci správně nebo ji skrýval, uvede tak průměrného spotřebitele v omyl a tím ohrozí zájem, který právní předpis chrání. Z pohledu průměrného spotřebitele je klíčové, že informace o slevě má být srozumitelná, transparentní a věrohodná. Právě proto zákon výslovně upravuje povinnost srovnání s nejnižší cenou v posledních 30 dnech. Průměrný spotřebitel nemá detailní přehled o historických cenách a spolehne se na to, co mu prodejce prezentuje. Cílem je, že průměrný spotřebitel pozná, že sleva je reálná, a dokáže na jejím základě rozumně a objektivně posoudit výhodnost nákupu, vysvětlil soud.

Z předmětné cenovky, kterou měl Albert u kávy, jednoznačně podle soudu vyplývá, že se jedná o informaci o slevě z ceny výrobku. Nikoli však o slevě ve výši 44 %, nýbrž o slevě v záporné hodnotě 6 %. Touto zápornou slevou nedošlo ke snížení ceny nejnižší ceny výroku, za kterou jej obchod nabízel a prodával v době 30 dnů před poskytnutím slevy, nýbrž k jejímu faktickému navýšení o 6 %.

Takto vypadala cenovka v Albertu:

Cenovka v Albertu s falešnou slevou.

Cenovka v Albertu s falešnou slevou.

Autor: Česká obchodní inspekce, podle licence: Rights Managed

Vyplývá tato skutečnost průměrnému spotřebiteli z předmětné cenovky jasně a srozumitelně? Soud má za to, že nikoli. Cenovka uvádí jako „původní“ a přeškrtnutou částku 719 Kč, z níž je deklarována sleva na 399 Kč, tj. pokles o 44 %. Nicméně ve spodní části téhož cenového štítku je uvedeno, že nejnižší cena za posledních 30 dnů byla 379 Kč, tedy nižší, než aktuálně nabízená cena po „slevě“. To znamená, že oněch 719 Kč nebylo v dotčeném 30denním období nejnižší skutečně účtovanou cenou, nýbrž cenou vyšší, od které je sleva kalkulována. Spotřebitel je tak uváděn v omyl, jelikož mu je předkládána informace o slevě z částky, která neodráží reálný cenový vývoj v zákonem požadovaném období, podotkl soud a žalobu obchodního řetězce zamítl.

školení červen - ochrana os. údajů

Proti falešným slevám vystoupil i evropský soud

Albert se nicméně s rozhodnutím soudu nesmířil a podal kasační stížnost. O případu tak bude rozhodovat Nejvyšší správní soud. Vzhledem k tomu, že stejně jako krajský soud však loni rozhodl i Evropský soudní dvůr, pravděpodobnost, že by Albert s kasační stížností uspěl, není vysoká. 

Evropský soud totiž na konci září potvrdil, že článek 6a odst. 1 a 2 evropské směrnice o ochraně spotřebitelů při označování cen výrobků nabízených spotřebiteli musí být vykládán v tom smyslu, že vyžaduje, aby sleva z ceny zboží, která je oznamována obchodníkem v procentní výši nebo ve formě reklamního sdělení, které má zdůraznit výhodnost cenové nabídky, byla určena na základě „předchozí ceny“. „Předchozí cenou“ se pak podle evropského soudu rozumí nejnižší cena, kterou obchodník uplatňoval během období ne kratšího než 30 dnů před uplatněním slevy z ceny.

Souhlasíte s novými pravidly pro stanovování slev?

Autor článku

Daniel Morávek píše o daních, zákonech a důchodech. Vysvětluje, jak legislativa ovlivňuje podnikatele a živnostníky. Profil autora →

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