Programování pro X Window System (3)

15. 4. 2004
Doba čtení: 8 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
V dnešním dílu si ukážeme hierarchické uspořádání widgetů. Dále probereme základní manipulaci s widgety - vytvoření, zobrazení, zrušení. S tím souvisí i správa paměti pomocí počítání referencí. Nakonec se podíváme na kontejnerové widgety, především na boxy a tabulky.

Hierarchické uspořádání widgetů a tříd

Widgety v GTK+ jsou hierarchicky uspořádané. Existují dvě hierarchie – widgetů a tříd. Widgety v programu jsou uspořádány do stromové struktury. V kořeni stromu je vždy top-level okno. Je to většinou hlavní okno aplikace nebo dialog. Program má tolik stromů widgetů, kolik má top-level oken. V uzlech stromu jsou widgety uvnitř top-level okna. Všechny uzly kromě listů jsou takzvané kontejnery, tedy widgety, do nichž lze vložit další jeden nebo více widgetů. Vzhled oken na obrazovce přímo odpovídá hierarchii widgetů. Okna potomků leží vždy uvnitř okna rodičovského widgetu. Na obr. 1 je příklad top-level okna programu a odpovídající strom widgetů je na obr2.

top-level okno
Obr. 1: Příklad top-level oknastrom widgetů Obr. 2: Příklad stromu widgetů

Obrázek 2 není úplně přesný, protože objekty typu GtkButton a GtkCheckButton nejsou pravé listy. Ve skutečnosti jsou to kontejnerové widgety, které obvykle obsahují nápis typuGtkLabel.

Hierarchie widgetů je v každém programu jiná a dynamicky se mění podle toho, jak vznikají a zanikají jednotlivé widgety. Druhý typ hierarchie je daný vztahy dědičnosti mezi třídami widgetů. Společným předkem všech tříd je GObject definovaný ve stejnojmenné knihovně. V knihovně GTK+ je od GObject odvozená třída GtkObject a z ní dále GtkWidget, což je bázová třída pro všechny widgety. Základ stromu widgetů je vždy stejný, odlišnosti vznikají, pokud program definuje vlastní nové typy widgetů. Malý výřez hierarchie typů widgetů je na obr. 3.

strom tříd
Obr. 3: Část stromu tříd widgetů

Manipulace s widgety

Pro každou třídu existuje jedna nebo více funkcí pro vytvoření objektů této třídy. Např. prázdné tlačítko se vytvoří voláním gtk_button_new. Funkce gtk_button_new_wit­h_label vyrobí tlačítko s nápisem. Další možnosti jsou tlačítko s akcelerátorem

gtk_button_new_wit­h_mnemonic nebo předdefinované tlačítko s nápisem a ikonou gtk_button_new_from­_stock. Zavoláním funkce pro vytvoření widgetu vznikne objekt uvnitř programu – X klienta. Aby byl widget vidět na obrazovce, je třeba ho realizovat, tj. vytvořit pro něj GDK okno a dále X okno, které existuje v X serveru. Realizaci provádí funkce gtk_widget_realize. Následně je widget zobrazen pomocí gtk_widget_show, popř. gtk_widget_show_all zobrazí widget včetně všech potomků. Tyto dvě funkce se postarají i o vytvoření X oken, proto není nutné explicitně volat gtk_widget_realize. Existující widget lze opakovaně zobrazovat a schovávat pomocí

gtk_widget_show a gtk_widget_hide. Existenci widgetu – včetně GDK a X okna – ukončí funkce gtk_widget_destroy.

GTK+ používá při správě paměti počítání referencí na objekty (reference counting). U každého objektu se pamatuje počet referencí na něj, tedy počet ukazatelů na objekt. Při uložení odkazu na objekt do nějakého ukazatele je třeba voláním funkce g_object_ref zvýšit o 1 počet referencí. Naopak, pokud se hodnota ukazatele změní, je nutné snížit počet referencí pomocí g_object_unref. Když počet referencí klesne na nulu, znamená to, že objekt nadále není dostupný přes žádný ukazatel. GTK+ takový objekt automaticky zruší. Reference counting nefunguje dobře pro cyklické datové struktury. Jestliže existuje několik objektů, které tvoří cyklus vzájemných odkazů, počet referencí na každý objekt v cyklu je vždy aspoň jedna, i když „zvenku“ na žádný z objektů neexistuje žádný odkaz a datová struktura jako celek je nedostupná. V takovém případě je možné některé ukazatele nezahrnovat do počtu referencí. Potom ovšem při zrušení objektu zbude neplatný ukazatel. Lepším řešením je používání tzv. slabých referencí (weak reference). Při nastavení hodnoty ukazatele se volág_object_we­akref. Tím se zaregistruje funkce, která bude automaticky zavolána při zrušení objektu. Tato funkce může např. vynulovat ukazatel. Při změně hodnoty ukazatele je možné odregistrovat slabou referenci voláním g_object_weakunref.

Mechanismus počítání referencí poněkud komplikují tzv. plovoucí reference. Funkce pro vytvoření widgetu vrátí ukazatel na objekt, který představuje jednu referenci. Obvykle je nový widget následně vložen do kontejneru, který přidá další referenci. Při zrušení kontejneru většinou chceme automaticky zrušit i všechny v něm vložené widgety. Abychom nemuseli vždy po vložení widgetu do kontejneru volat g_object_unref, je první reference na objekt vytvořena jako plovoucí (floating). Kontejner tuto referenci zruší pomocí gtk_object_sink poté, co přidá vlastní referenci. Je třeba dávat pozor na to, že při vyjmutí widgetu z kontejneru (funkcígtk_con­tainer_remove) zruší kontejner svou referenci. Pokud nechceme, aby se vyjmutý widget zrušil, musíme před voláním gtk_container_re­move použít g_object_ref. Takto vytvořená reference už není plovoucí, proto zůstane platná i při opětovném vložení widgetu do kontejneru. Plovoucí je vždy pouze první reference na objekt. První volání gtk_object_sink ji odstraní, opakovaná volání této funkce pro stejný objekt už nedělají nic.

Kontejnery

Kontejnery jsou widgety, do nichž lze vkládat jiné widgety (včetně dalších kontejnerů). Všechny kontejnery vycházejí ze společného předka GtkContainer. Dají se rozdělit do dvou hlavních skupin. Jednu skupinu tvoří třídy odvozené z GtkBin a vyznačují se tím, že mohou obsahovat maximálně jeden synovský widget, přístupný přes položku GtkBin.child. Asi nejdůležitějším zástupcem této skupiny je třída GtkWindow, tedy top-level okno. Do kontejnerů z druhé skupiny lze vložit více widgetů. Jejími nejpoužívanějšími členy jsou boxy (GtkHBox a GtkVBox) obsahující widgety uspořádané do řádku nebo sloupce a tabulky (GtkTable), které umísťují synovské widgety do dvojrozměrné mřížky.

Kontejnery zajišťují automatické přidělování místa pro jednotlivé widgety. Než se zobrazí top-level okno, zjistí GTK+ požadovanou velikost okna a všech jeho potomků. K tomu se používá metoda gtk_widget_si­ze_request, která vrátí minimální widgetem požadovanou velikost. Kontejnery mají tuto metodu předefinovanou tak, že se nejprve zavolá pro všechny synovské widgety a z jejich požadavků kontejner spočítá, jak musí být velký, aby se do něj všichni potomci vešli. Např. horizontální box vrátí požadovanou šířku rovnou součtu šířek synovských widgetů plus velikost mezer podle parametrů boxu a výšku rovnou maximu výšek synovských widgetů. Následně GTK+ ve spolupráci s window managerem nastaví velikost top-level okna. Skutečná velikost se může lišit od požadované, např. pokud uživatel pomocí window manageru změnil rozměry okna. Nakonec GTK+ oznámí oknu skutečně přidělenou velikost voláním metody gtk_widget_si­ze_allocate. V kontejnerech tato metoda rozdělí oblast widgetu mezi jednotlivé synovské widgety a výsledek rozdělení jim oznámí opět voláním gtk_widget_si­ze_allocate.

Některé funkce jsou společné pro všechny kontejnery a jsou definovány ve třídě GtkContainer.

void gtk_container_ad­d(GtkContainer *container, GtkWidget *widget);
Vloží widget do kontejneru. Obvykle se používá pro kontejnery odvozené z GtkBin. Každá kontejnerová třída má navíc definované vlastní funkce pro vkládání widgetů.
void gtk_container_re­move(GtkConta­iner *container, GtkWidget *widget);
Odebere widget z kontejneru. Sníží o 1 počet referencí na widget, takže pokud jedinou referenci držel kontejner, bude widget zrušen.
void gtk_container_set_bor­der_width(GtkCon­tainer *container, guint border_width);
Nastaví šířku okraje (v pixelech), tj. oblasti, do níž nebudou zasahovat vložené widgety.
void gtk_widget_set_si­ze_request(GtkWid­get *widget, gint width, gint height);
Nastaví minimální velikost widgetu. Během procesu přidělování místa widgetům se bude rodičovský kontejner snažit nezmenšit widget pod tuto velikost. Funkce je definovaná pro všechny widgety, nejen kontejnery.

Horizontální (GtkHBox) a vertikální (GtkVBox) boxy jsou odvozené ze společného předka GtkBox. Pro vytvoření boxu slouží funkce

GtkWidget* gtk_hbox_new(gboolean homogeneous, gint spacing);
GtkWidget* gtk_vbox_new(gboolean homogeneous, gint spacing); 

Parametr homogeneous říká, zda všechny synovské hodnoty budou stejně velké. Parametr spacing nastavuje velikost mezery (v pixelech) ponechané mezi každou dvojicí widgetů. Pro vkládání widgetů do boxů jsou definovány funkce

void gtk_box_pack_start(GtkBox *box, GtkWidget *child, gboolean expand,
                        gboolean fill, guint padding);
void gtk_box_pack_end(GtkBox *box, GtkWidget *child, gboolean expand,
                      gboolean fill, guint padding); 

První z nich vkládá widgety od levého/horního okraje boxu směrem doprava/dolů, druhá vkládá od pravého/dolního okraje směrem doleva/nahoru. Parametr expand říká, zda widget může zabírat více místa, než je jeho minimální velikost. Pokud je nastaven na TRUE a fill je takéTRUE, bude widget zvětšen tak, aby zaplnil veškeré dostupné místo. Při fill rovném FALSE nebude widget zvětšen a případné nadbytečné místo se stane součástí mezer kolem widgetu. Parametr padding definuje šířku prázdného místa po obou stranách widgetu. Parametry ovlivňují pouze jeden rozměr vkládaných widgetů. Výška všech synů hboxu, resp. šířka synů vboxu, je stejná a je rovna výšce (šířce) boxu zmenšené o okraj.

Význam jednotlivých parametrů boxu je zobrazen na obr. 4. Pro experimentování s boxy lze využít program boxes.c, který nejdříve přečte ze standardního vstupu specifikace boxu a jeho synovských widgetů a následně box zobrazí.

box
Obr. 4: Parametry boxu

Tabulka (GtkTable) se vytvoří funkcí

GtkWidget* gtk_table_new(guint rows, guint columns, gboolean homogeneous); 

Parametry definují počet řádků a sloupců a to, zda všechna políčka tabulky mají být stejně velká. Při vkládání widgetů pomocí

void gtk_table_attach_defaults(GtkTable *table, GtkWidget *widget,
                               guint left_attach, guint right_attach,
                               guint top_attach, guint bottom_attach); 

se definuje, ve kterých sloupcích a řádcích budou ležet jednotlivé strany widgetu. Existuje i funkce gtk_table_attach, která umožňuje specifikovat ještě další parametry. Fungování tabulky si lze představit tak, že sloupce a řádky jsou pevné tyče, které se mohou volně pohybovat, ale nesmí si vyměnit pořadí. Jednotlivé widgety se připevňují svými okraji k tyčím. Každý widget je gumový obdélník, může být libovolně roztažen, ale nedá se stlačit pod svou minimální velikost. Po vložení všech widgetů se tyče představující sloupce a řádky rozmístí tak, aby žádný widget nebyl menší, než je jeho povolené minimum, a aby zároveň žádný widget nebyl větší, než je nezbytně nutné. Na obr. 5 jsou vlevo znázorněny minimální velikosti čtyř widgetů. Vpravo je tabulka, jež vznikne posloupností příkazů

prace_s_linuxem_tip

table = gtk_table_new(3, 3, FALSE);
gtk_table_attach_defaults(table, widget1, 0, 3, 0, 1);
gtk_table_attach_defaults(table, widget2, 0, 1, 1, 3);
gtk_table_attach_defaults(table, widget3, 1, 2, 1, 2);
gtk_table_attach_defaults(table, widget4, 2, 3, 2, 3); 

tabulka
Obr. 5: Vytvoření tabulky

Automatické řízení rozměrů a pozic widgetů je výhodné, protože se programátor nemusí zabývat přesným umístěním jednotlivých widgetů. Navíc, když uživatel změní velikost okna, GTK+ na to zareaguje a rozumným způsobem upraví rozmístění widgetů v okně. Pro seznámení se s fungováním kontejnerů je užitečné spustit si program Glade a v něm si vyzkoušet vkládání widgetů do kontejnerů. Jeho velkou výhodou je, že lze interaktivně měnit parametry kontejnerů a okamžitě vidět na obrazovce, jak se změní vzhled okna.

Autor článku

Vystudoval informatiku na MFF UK v Praze, kde následně několik let učil programování v Unixu. Poté se dlouhá léta věnoval síťové bezpečnosti a programování firewallů. V současnosti se zabývá vývojem interních backendových systémů ve společnosti Gen (dříve Avast).

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