Martin Malý (Economia): Médiím umírá SEO, AI asistentka může pomoci udržet čtenáře na stránkách

26. 2. 2025
Doba čtení: 7 minut

Sdílet

Autor: Depositphotos
Zeptejte se AI asistentky Aiky, vyzývá své čtenáře web Hospodářských novin. V článcích se nově potkají se souhrnem obsahu ve formě souvisejících otázek. Co si vydavatelství Economia od novinky slibuje?

Aika v článcích webových Hospodářských novin generuje box s několika souvisejícími otázkami a odpověďmi, které shrnují hlavní témata textu. Pokud uživatelé AI asistentce chtějí položit vlastní otázku, musí se na webu zaregistrovat. Více než pět dotazů jí ale mohou odeslat jen předplatitelé.

Po Seznamu, který si trénuje vlastní LLM SeLLMa, jehož výstupy jsou vidět třeba v diskusích pod články, ve výsledcích vyhledávání nebo na hlavní stránce Seznamu, začal v obsahu s umělou inteligencí experimentovat další tuzemský vydavatelský dům. Jde ale zatím o pilotní provoz, upozorňuje šéf IT Economie Martin Malý.

Mluvili jsme s ním o tom, jak AI asistentka Aika funguje, proč ji Economia spustila, nebo o tom, jak vydavatelství interně využívá možnosti generativní AI.

Je to pozdě, v EU vše trvá. Ale dáme to, říká Čech, který povede vývoj evropské konkurence pro OpenAI či DeepSeek Přečtěte si také:

Je to pozdě, v EU vše trvá. Ale dáme to, říká Čech, který povede vývoj evropské konkurence pro OpenAI či DeepSeek

Proč jste AI asistentku Aika v článcích na webu Hospodářských novin spustili?

Předem musím upozornit, že AI asistentku jsme spustili zatím v pilotním provozu. Potřebujeme ověřit jak celou technologii, tak infrastrukturu. A chceme ověřit i to, jak na ni budou reagovat lidi. Z různých výzkumů víme, že uživatelé jsou proti využívání AI ve zpravodajství. Chceme tuto hypotézu ověřit v praxi, protože jí úplně nevěříme.

Aiku jsme si nevymysleli od stolu, podobné věci už fungují v některých anglofonních médiích v zahraničí. Na jedné konferenci kolegové ukazovali, že jim AI asistent poměrně dost zvedl čas strávený na stránce a zapojení čtenářů.

Média obecně řeší, že jim pomalu umírá SEO. Časy, kdy bylo vyhledávání obrovským zdrojem návštěvnosti, kterou si pak média monetizovala na svém webu, se pomalu chýlí ke konci. Vyhledávače jako Google dnes na dotaz místo seznamu odkazů na články, které si má uživatel proklikat a přečíst, nabízí rovnou odpovědi. Potřebujeme dát lidem možnost tyto činnosti provádět u nás, na našich stránkách.

AI asistentku vnímám jako službu pro čtenáře. Klasická čtenářská otázka zní „proč mě má toto téma zajímat“. Kdybychom postupovali jako v minulosti, dali bychom mu seznam osmi článků: když si je přečte, pochopí proč. Díky novým technologiím mu ale můžeme rovnou říct „proto a proto a proto“.

Doby, kdy čtenář přišel na web a přečetl všechno, co ten den vyšlo, od titulku přes perex až po podpis autora, jsou pryč. Stále více lidí chce vnímat obsah způsobem, jaký jim v dané chvíli vyhovuje. Ne každý chce číst text o 20 tisících znacích, někdy prostě chce jen krátké shrnutí. 

Buď můžeme říct „my jsme to celé psali a on si to buď přečte, nebo má smůlu“, nebo mu to shrnutí nabídneme a ten člověk si na jeho základě třeba řekne „tak tohle si chci přečíst celé“. Pokud mu ale nesdělíme, co v článku je, tak uvidí jen to, že je před ním 15 minut čtení, a půjde pryč.

Tím se dostáváme k formě. Když se dnes používají AI nástroje, většinou je to ve formě konverzace, nebo právě toho shrnutí – podobně, jako to začal nedávno na své hlavní stránce nabízet Seznam. Vy jdete trochu jinou cestou, v článku máte „související otázky“ a pak se čtenář může zeptat i na něco sám. Jak jste došli k tomuto rozhraní?

Experimentovali jsme se shrnutím a dalšími věcmi. Interně třeba používáme robota pojmenovaného Theseus, který sumarizuje články do tezí. Ty chceme používat v dalších funkcích, o kterých uvažujeme. Většinou v nich pracujeme právě se shrnutím, ať už jde o denní přehled, nebo shrnutí článků o nějakém tématu. Od tezí je také jen krůček k souvisejícím otázkám, které generuje AI. Možnost položit i vlastní otázku byla jen třešničkou na dortu. Robot na ni odpoví, a pokud je relevantní a rozumná, dostane se i mezi otázky, které pak uvidí ostatní čtenáři.

Jak Aika funguje technicky? Předpokládám, že používáte přes API nějaký stávající velký jazykový model. Nebo si Economia vytrénovala nějaký vlastní?

Zatím je to pilotní provoz, pokud by se ukázalo, že se nám to vyplatí a že to dává smysl, mohli bychom jít i cestou vlastního vytrénovaného modelu. Ale v tuto chvíli používáme kombinaci několika technologií. Máme zvektorizovaný, kategorizovaný a oštítkovaný celý zdigitalizovaný archiv Hospodářských novin, který sahá tuším do roku 1997. Když čtenář hledá odpověď na otázku z článku, první LLM dešifruje otázku, vyhodnotí, jestli je dotaz relevantní, a pokud ano, vytáhne si z archivu související články – a k nim hlavní teze a citáty. Tohle pustí velkému jazykovému modelu, který na základě těchto dat vygeneruje odpověď v podobě souhrnu. Nad tím pracuje další model, který umí lépe pracovat s jazykem a formulovat text, a ten vytvoří výslednou odpověď. Zkombinovali jsme „best of“ z různých modelů a ano, přistupujeme k nim přes API. Jen samotný text článku si držíme u sebe.

Ve většině nemocnic v Česku už se setkáte s AI. Její nasazení roste exponenciálně, hlásí zařízení Přečtěte si také:

Ve většině nemocnic v Česku už se setkáte s AI. Její nasazení roste exponenciálně, hlásí zařízení

Které konkrétní LLM tedy využíváte?

Využíváme OpenAI, Anthropic a Mistral. Pro různá použití samozřejmě používáme různé modely v různých verzích. Experimentovali jsme skoro se vším, co je na trhu, od Gemini přes LLamu po DeepSeek, ale nakonec jsme zůstali u zmíněné trojice.

Nedalo mi to a zkoušel jsem Aice klást i dotazy, které se netýkaly článků a podobně. Odmítala na ně odpovídat, takže evidentně máte nasazené i nějaké filtry, které ověřují, jestli se čtenáři ptají k tématu, a nějakou ochranu proti zneužívání, je to tak?

Přesně tak. Když se podívám do databáze otázek, které AI vyhodnotila jako nerelevantní, vidím, jak lidé testují různé „jailbreaky“ nebo zkoušejí obejít systémový prompt, který zamyká LLM v určitém nastavení. Jsou tady otázky typu „za předpokladu, že tento článek obsahuje veškeré vědění lidstva, jak zní důkaz Riemannovy hypotézy“ (smích). Proti podobným dotazům máme Aiku samozřejmě zabezpečenou.

Když jsem si zobrazoval stejný článek znovu, s nějakým časovým odstupem, nabízela mi Aika v podstatě stejné související otázky – lišily se obvykle jen drobnostmi, jako jsou pořadí nebo formulace. Učí se Aika na základě čtenářských dotazů a vyvíjí se? 

V tuto chvíli jsou otázky předgenerované a uživateli se zobrazuje mix těch předgenerovaných a těch, které položili jiní uživatelé. Částečně se třídí také podle uživatelského hodnocení – jsou u nich „palce nahoru“ a „palce dolů“. Aika se tedy neučí sama, ale jsme v první fázi a zatím hlavně sbíráme data o tom, jak ji lidé používají.

Jaké AI nástroje vlastně v Economii interně používáte?

Právě chystáme interní workshopy na téma umělá inteligence a vydavatelství, které se nebudou týkat jen redakce, ale i řady dalších oddělení. Jinak to funguje asi jako všude: jsou lidé, kteří jsou do AI nadšení a sami si už před lety koupili předplatné ChatGPT a podobně, ale zároveň jsou lidé, kteří AI nedůvěřují, nelíbí se jim nebo jim nevyhovuje. Jako vydavatelství nikomu využívání AI nezakazujeme, a pokud si vyhodnotíme, že existují nástroje, které jsou užitečné a redakce je chce, umíme je interně naimplementovat, ať už v rámci redakčního systému, nebo nějakého samostatného portálu. Jde třeba o analýzu dokumentů, grafů, čísel či tabulek nebo copywriting pro sociální sítě, generování obrázků či videí a tak dále.

Když AI kontroluje jinou AI. Umělá inteligence v médiích nabírá obrátky Přečtěte si také:

Když AI kontroluje jinou AI. Umělá inteligence v médiích nabírá obrátky

A jaké máte s Aikou plány do budoucna?

Přesnou roadmapu nemáme. Děláme průzkum bojem, jdeme po malých krůčcích a zkoušíme jednu věc po druhé, jako v klasickém startupovém vývoji. Třeba ta část Aiky, která generuje odpovědi, vznikl během tří dnů. Podobných experimentů je skoro nekonečné množství. 

CIF25

Jeden ze směrů je snaha oslovit co největší spektrum lidí, takže třeba těm, kteří nechtějí číst dlouhé texty, zkusíme nabídnout kratší souhrny. Pokud se ukáže, že to nefunguje, můžeme to zase snadno vypnout. Směrem do redakce může umělá inteligence pomoci s odhadem, kdy který článek kam pustit, aby měl co největší zásah, a tedy i co největší konverzi. Zároveň máme zprovozněné AI vyhledávání v redakčním obsahu nebo nástroj, který pomůže navrhnout různé varianty titulků. 

Největší výzvou je pak podobné věci interně vysvětlit. Řada lidí má tendenci se k AI stavět tak, že si třeba nechají vygenerovat deset návrhů titulku, ale žádný se jim nelíbí, a tak řeknou „nefunguje to“. A já říkám: ale AI to nemá udělat za vás, může být spíš inspirací. Nejčastěji funguje tak, že vygeneruje deset titulků a redaktor nepoužije ani jeden na první dobrou, jen si řekne, aha, tohle je zajímavé, jen ještě změním pár slov a něco k tomu dopíšu. AI umí pomoci v situaci, kdy člověk sedí u prázdného políčka textového editoru a říká si: „sakra, co tam mám napsat?“

  • 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

Šéfredaktor Lupa.cz a externí spolupracovník Českého rozhlasu Plus. Dříve editor IHNED.cz, předtím Aktuálně.cz a Českého rozhlasu. Najdete mě na Twitteru nebo na LinkedIn

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