Záruka letitých aktualizací je zbytečná, výrobce by měl garantovat otevřenost

9. 2. 2024
Doba čtení: 6 minut

Sdílet

Autor: Fairphone
Na co vše máme jako uživatelé nárok od výrobců zařízení? Nemají i výrobci právo ukončit po nějaké době podporu? Ale pokud tak učiní, neměli by předat plný přístup k zařízení uživateli?

Žijeme v době, kdy každá druhá věc má aktualizace firmwaru. Bude to už dobrých 15 let, co Logitech vydal aktualizaci firmwaru pro jednu svoji myš, zlepšující u ní kompatibilitu s jedním typem podložek pod myš. Tehdy jsem nad tím kroutil hlavou, během těch 15 let rezignoval a smířil se se skutečností, že holt žijeme v době, kdy takové věci existují.

Ostatně počítač, možná nejvýznamnější vynález 20. století, má právě tuto jedinou mega-zásadní výhodu: možnost softwarově zlepšit chod produktu jím poháněného. Jenže to předpokládá, že práce výrobce neskončí momentem, kdy tu věc vyexpedoval z fabriky.

Chtít stále více

Zvykli jsme si na podporu produktů. Kdo chce u chytrého telefonu dlouhou dobu podpory, sáhne po iPhonu, nebo Fairphonu, který je podporován až 10 let. Samsung také postupně u řady modelů prodlužuje dobu podpory, která se netýká jen aktualizací dané verze Androidu, ale záruky vydávání nových verzí.

Na druhém pólu tohoto spektra máme „levné čínské neznačky“, u kterých jediná jistota je verze Androidu v době koupě a má-li uživatel štěstí, planety jsou ve správné konjunkci a je pravidelně na oltáři prováděna rituální oběť nevinného malého koťátka, výrobce alespoň tu a tam vydává aktualizace té jedné jediné verze Androidu, se kterou zařízení kdysi vrhl v trh.

Digitální nevyhnutelnost

Zkrátka a dobře, od chvíle, kdy telefon byla analogová krabička připojená doma do zásuvky ve zdi, která mohla být klidně desítky let stará, jsme se hodně posunuli. Na druhou stranu ale čím více roste komplexnost zařízení, tím více klesá schopnost si jej doma opravovat, ať už kvůli složitosti takové opravy, nebo kvůli nutnosti mít k dispozici náhradní díly.

Kdysi dávno jsme byli schopni opravovat defekty na základních deskách 75W trafopáječkou z 60. let za pomoci těžce olovnatého cínu a kalafuny. To není vtip. Dnes už bych si něco takového netroufl. Dnes už je na opravy komponent potřeba řádově lepší vybavení, které si člověk rozhodně nevyrobí doma na koleně, a řádově lepší schopnosti v pájení SMD či BGA komponent.

Po Sudkovsku to už spíš nejde

Josef Sudek fotil takovým specifickým způsobem. Exponovat „po Sudkovsku“ znamenalo mít plně otevřenou závěrku a exponovat sejmutím krytky z objektivu. Dělal to tak Sudek, dělali to tak jeho asáci (jako Petr Helbich), dělal to tak i Jan Reich (kdo chce, najde na svém oblíbeném webovém úložišti pořad „příběh Sudkovy kamery“). Podle mě to Sudek nedělal proto, že by to byl lepší postup, ale proto, že fotil mnoho desítek let starými objektivy, kterým už odešla závěrka, a tak si prostě expozici řídil sám. 

Ostatně Sudek typicky filmový materiál brutálně „přeexponoval“ a následně „podvolal“. Měl svoji cestu.

Dnes s digitální bezzrcadlovkou by takto fungovat nemohl. Dnešní fotoaparát není „trubka s čočkama“, za kterou následuje filmová skleněná deska, ale složitý počítač řídící chod velice jemných mechanických částí a pokud něco nefunguje, prostě nefotíte. Je jedno, jestli jde o závadu na Nikonu Z9 z roku 2023, nebo na Olympusu E-10 z roku 2001. 

Můžeme jít i dál, do první poloviny 90. let, kdy se objevily první digitální zrcadlovky Kodak, tehdy „podigitálničené“ filmové zrcadlovky Nikon. Ani ty už nebylo možné opravovat podomácku, což ostatně platí pro řekněme tři čtvrtiny filmových zrcadlovek.

Opravitelnost takových závad doma je téměř nulová, protože i kdyby si člověk poradil se superjemnou mechanikou, doma si prostě náhradní díly z Merkuru ve svěráku nevyrobí. Proto musí chtě nechtě nastoupit požadavek na výrobce, aby svá zařízení prostě po nějakou dobu podporovali. 

Mezi foťáky v tomto drží prim Leica. Když jí pošlete 70 let starý dálkoměr, tak vám jej ruce špičkového německého technika opraví, seřídí, promažou. Pravda, bude to stát tolik, co nová Dacia, ale výrobce vám tuto možnost dává. Stejně jako koupit si zbrusu novou kinofilmovou Leicu M6. Opravdu novou, opět ji předloni zařadili v inovované verzi do výroby.

Softwarová podpora nutná

Jedna věc je železo, druhá ale funkcionalita. V dobách, kdy nebylo potřeba chránit uživatelskou identitu, protože se státní správou, bankou či nikým jiným podobně důležitým se prostě elektronicky nekomunikovalo, nikoho netrápilo, že operační systém je jednouživatelský a nijak data nešifruje a nechrání. To je možné i dnes u mnoha typů užití (třeba u starší digitální zrcadlovky), ale není to možné typicky u chytrého telefonu.

Pokud si EU už dnes vynucuje univerzální konektor typu USB-C a do budoucna zavádí povinnost uživatelsky vyměnitelného akumulátoru, musí chtě-nechtě do budoucna donutit výrobce telefonů (a nejen jich), aby například poté, co ukončí softwarovou podporu daného zařízení, toto zařízení odemkli uživateli pro další užití.

Ze své osobní zkušenosti mohu říci, že kdyby bylo možné snadno u Kodaku Ektra vyměnit akumulátor (což bych býval tehdy v klidu zvládl) a poskytnou uživateli otevřený přístup k výměně operačního systému (což nebylo podporováno), pak bych tento telefon nejspíš používal ještě dnes – šlo o přístroj, který šel na trh v roce 2016 s Androidem 6.0 (a nikdy novější nedostal), já jej koupil v roce 2017 a používal do roku 2020, kdy definitivně uhynul akumulátor.

O dva roky později, kdy jsem jej zkusil oprášit, se definitivně sesypal celý telefon po sw stránce. Mít možnost na něm mít roota a dostat tam něco novějšího typu LineageOS, neváhal bych ani minutu, protože telefon měl vynikající 21Mpix f/2 foťáček s podporou 4k H.265 videa a šestiosou optickou stabilizací.

Opravdu doufám, že EU jednoho dne nařídí výrobcům telefonů, aby po ukončení životnosti daného přístroje otevřeli poslední aktualizací jeho bootloader a dali uživateli svobodu v dalším nakládání s tímto zařízením. Protože cokoli jiného je plýtváním jinak hardwarově vyhovujícími zařízeními, která uživatelů jinak vyřadí prostě proto, že jim jejich bankovní aplikace oznámí, že na tomto starém systému už nepoběží.

Očekávání není záruka

Například fotoaparátů by se toto ale týkat nemohlo, v jejich algoritmech mají výrobci často ukryté unikátní know-how. Každý majitel některé vyšší bezzrcadlovky Olympus řady E-M1 dobře ví, jak s novými firmwary dále přibývá funkcionalita. Nejinak na tom jsou třeba i majitelé Nikonů, Panasoniců. A třeba nejnovější bezzrcadlovka OM-1 Mark II je vlastně z hlediska nabízených schopností z poloviny hardware a z poloviny software.

Jedna věc ale je tichý předpoklad, že výrobce bude přidávat další a další funkcionalitu (protože tak vždy tradičně činil, protože kolovaly zvěsti, že tak bude, a podobně). Zcela jiná věc je ale garance, jak píše Dale Baskin na DPReview. K této garanci určité doby aktualizací nyní EU nutí výrobce telefonů (a existoval na to kdysi i program Android One), avšak garance aktualizací nemůže běžet do nekonečna, pouze do nějaké smysluplně vzdálené budoucnosti. Na co by dnes byla majiteli telefonu s jednojádrovým procesorem ARM a 1GB RAM podpora aktualizací? Někde leží rozumná hranice, avšak za ní by dle mého soudu mělo následovat plné otevření přístroje uživateli, jak jsem psal výše.

Ve fotoaparátech existuje pěkná ukázka toho, že komunita může nejen držet při životě staré přístroje, ale dokonce jim poskytovat funkcionalitu, kterou výrobce ani nezahrnul. Jde o projekt Magic Lantern pro (bez)zrcadlovky Canon.

Všechno je dnes počítač, tak k tomu tak přistupujme

Zkrátka a dobře: počítač je počítač (jak filosofické, že?). Ale i telefon je dnes jen počítač. Automobil je také počítač, akorát místo monitoru má okno, místo klávesnice volant, místo ventilátorů kola a místo zdroje akumulátor či nádrž. Pračka je dnes taky počítač, fotoaparát je taky počítač a tak dále.

Proč jednomu zařízení dáváme takřka naprostou svobodu, na čem poběží (Windows, Linux, BSD, …) a u jiného dovolujeme výrobcům absolutní omezování této svobody? Proč si já jako uživatel chytrého telefonu XYZ nemohu místo dodaného zapleveleného Androidu nainstalovat LineageOS a zprovoznit věci, jak já chci? To je věc, kterou by po USB-C a akumulátorech měla EU / EK řešit, hned jako další v pořadí.

bitcoin_smenarna

U automobilů to lze omluvit nutností bezpečnosti a garance za parametry vozidla. U telefonů není důvod uživateli ve změnách bránit, klidně nechť předtím odsouhlasí, že se svobodou přebírá i odpovědnost.

Nenuťme výrobce telefonů k mnoha a mnohaleté garanci aktualizací. Nuťme je pouze k tomu, aby den-týden-měsíc po vydání poslední plánované aktualizace nabídl uživateli otevření rootovský přístupu po odsouhlasení výše uvedeného zbavení odpovědnosti.

Autor článku

Příznivec open-source rád píšící i o ne-IT tématech. Odpůrce softwarových patentů a omezování občanských svobod ve prospěch korporací.

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