1!function(){const e=document.getElementById("masthead"),t=document.getElementById("search-toggle");if(null!==t){const n=document.getElementById("header-search"),s=n.getElementsByTagName("input")[0],a=t.getElementsByTagName("span")[0],o=a.innerText;t.addEventListener("click",function(){e.classList.toggle("hide-header-search"),o===a.innerText?(a.innerText=newspackScreenReaderText.close_search,n.setAttribute("aria-expanded","true"),t.setAttribute("aria-expanded","true"),s.focus()):(a.innerText=o,n.setAttribute("aria-expanded","false"),t.setAttribute("aria-expanded","false"),t.focus())},!1)}const n=document.getElementsByClassName("mobile-menu-toggle"),s=document.body,a=document.getElementById("mobile-sidebar-fallback"),o=document.getElementsByClassName("desktop-menu-toggle"),d=document.getElementById("desktop-sidebar-fallback"),c=document.getElementsByClassName("subpage-toggle");function i(e){const t=document.getElementById(e);t.parentNode.removeChild(t)}function l(e,t,n){s.classList.add(e),t.focus(),function(e){const t=document.createElement("div");t.setAttribute("class","overlay-mask"),t.setAttribute("id",e),document.body.appendChild(t)}(n)}function r(e,t,n){s.classList.remove(e),t.focus(),i(n)}for(let t=0;t<n.length;t++){const o=e.querySelector(".mobile-menu-toggle"),d=a.querySelector(".mobile-menu-toggle");n[t].addEventListener("click",function(){s.classList.contains("mobile-menu-opened")?r("mobile-menu-opened",o,"mask-mobile"):l("mobile-menu-opened",d,"mask-mobile")},!1)}for(let t=0;t<o.length;t++){const n=e.querySelector(".desktop-menu-toggle"),a=d.querySelector(".desktop-menu-toggle");o[t].addEventListener("click",function(){s.classList.contains("desktop-menu-opened")?r("desktop-menu-opened",n,"mask-desktop"):l("desktop-menu-opened",a,"mask-desktop")},!1)}if(0<c.length&&e){const t=document.getElementById("subpage-sidebar-fallback"),n=e.querySelector(".subpage-toggle"),a=t.querySelector(".subpage-toggle");for(let e=0;e<c.length;e++)c[e].addEventListener("click",function(){s.classList.contains("subpage-menu-opened")?r("subpage-menu-opened",n,"mask-subpage"):l("subpage-menu-opened",a,"mask-subpage")},!1)}if(document.addEventListener("click",function(e){if(e.target&&"overlay-mask"===e.target.className){const t=e.target.id,n=t.split("-");s.classList.remove(n[1]+"-menu-opened"),i(t)}}),e){const t=e.getElementsByClassName("submenu-expand");if(0<t.length)for(let e=0;e<t.length;e++){const n=t[e].querySelector("span.screen-reader-text");t[e].addEventListener("click",function(){t[e].classList.contains("open-dropdown")?(t[e].classList.remove("open-dropdown"),t[e].setAttribute("aria-expanded","false"),n.innerText=newspackScreenReaderText.close_dropdown_menu):(t[e].classList.add("open-dropdown"),t[e].setAttribute("aria-expanded","true"),n.innerText=newspackScreenReaderText.open_dropdown_menu)},!1)}}if(s.classList.contains("h-stk")&&s.classList.contains("h-sub")&&(s.classList.contains("single-featured-image-behind")||s.classList.contains("single-featured-image-beside"))){let t,n=0;window.onscroll=function(){!function(){const s=window.pageYOffset,a=(new Date).getTime();t||(a-n>300&&(n=a),t=setTimeout(function(){t=null,n=(new Date).getTime()},100)),200>=s?e.classList.remove("head-scroll"):e.classList.add("head-scroll")}()}}const m=document.getElementById("comments-toggle");if(null!==m){const e=document.getElementById("comments-wrapper"),t=m.getElementsByTagName("span")[0];m.addEventListener("click",function(){e.classList.contains("comments-hide")?(e.classList.remove("comments-hide"),t.innerText=newspackScreenReaderText.collapse_comments):(e.classList.add("comments-hide"),t.innerText=newspackScreenReaderText.expand_comments)},!1)}const u=document.getElementById("toggle-order-details");if(null!==u){const e=document.getElementById("order-details-wrapper"),t=u.getElementsByTagName("span")[0],n=newspackScreenReaderText.hide_order_details,s=newspackScreenReaderText.show_order_details;u.addEventListener("click",function(){e.classList.contains("order-details-hidden")?(e.classList.remove("order-details-hidden"),u.classList.remove("order-details-hidden"),t.innerText=n):(e.classList.add("order-details-hidden"),u.classList.add("order-details-hidden"),t.innerText=s)},!1)}}();; 2!function(){function e(){const e=document.getElementById("masthead");if(e){const t=e.getElementsByClassName("submenu-expand");if(0<t.length)for(let e=0;e<t.length;e++){const n=t[e].getAttribute("data-toggle-parent-id"),s=t[e].nextElementSibling,i=n.replace("toggle-","submenu-");s.setAttribute("id",i),t[e].setAttribute("aria-controls",i)}}}if(newspackScreenReaderText.is_amp){const t=document.getElementById("site-navigation"),n=document.getElementById("secondary-nav-contain"),s={childList:!0},i=new MutationObserver(t=>{for(const n of t)"childList"===n.type&&(e(),i.disconnect())});t&&i.observe(t,s),n&&i.observe(n,s)}else e()}();; 3(()=>
3{const n=window.newspackFontLoading?.fonts||[];Promise.all(n.map(n=>{const o=String(n).replace(/"/g,'\\"');return document.fonts.load(`1rem "${o}"`)})).then(o=>{o.length===n.length&&document.body.classList.remove("newspack--font-loading")})})();; 4(function () { 5 'use strict'; 6 7 /** 8 * Check if debug logging is enabled via URL parameter ?_video_debug=1 9 */ 10 function isDebugEnabled() { 11 if (typeof URLSearchParams !== 'undefined') { 12 // phpcs:ignore WordPressVIPMinimum.JS.Window.location -- read-only query string check; no DOM output 13 return new URLSearchParams(window.location.search).get('_video_debug') === '1'; 14 } 15 // Fallback for older browsers 16 // phpcs:ignore WordPressVIPMinimum.JS.Window.location -- read-only query string check; no DOM output 17 return /[\?&]_video_debug=1/.test(window.location.search); 18 } 19 20 function fdLog() { 21 if (!isDebugEnabled() || !window.console) { 22 return; 23 } 24 25 if (typeof window.console.info === 'function') { 26 window.console.info.apply(window.console, arguments); 27 return; 28 } 29 30 if (typeof window.console.log === 'function') { 31 window.console.log.apply(window.console, arguments); 32 } 33 } 34 35 function debugWarn() { 36 if (!isDebugEnabled() || !window.console || typeof window.console.warn !== 'function') { 37 return; 38 } 39 window.console.warn.apply(window.console, arguments); 40 } 41 42 function debugDebug() { 43 if (!isDebugEnabled() || !window.console || typeof window.console.debug !== 'function') { 44 return; 45 } 46 window.console.debug.apply(window.console, arguments); 47 } 48 49 function debugError() { 50 if (!isDebugEnabled() || !window.console || typeof window.console.error !== 'function') { 51 return; 52 } 53 window.console.error.apply(window.console, arguments); 54 } 55 56 /** 57 * Check if current User-Agent matches the override pattern. 58 * When matched, use override player later 59 */ 60 function isUserAgentOverridden() { 61 var pattern = window.FDVideoTargetVideoConfig 62 && window.FDVideoTargetVideoConfig.useragentOverridePattern; 63 if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') { 64 return false; 65 } 66 try { 67 return new RegExp(pattern, 'i').test(navigator.userAgent); 68 } catch (e) { 69 // Invalid regex â no match 70 return false; 71 } 72 } 73 74 /** 75 * Get optional override player ID for UA-matched requests. 76 */ 77 function getUserAgentOverridePlayerId() { 78 var value = window.FDVideoTargetVideoConfig 79 && window.FDVideoTargetVideoConfig.useragentOverridePlayerId; 80 81 if (typeof value !== 'string') { 82 return ''; 83 } 84 85 return value.trim(); 86 } 87 88 function getUserAgentOverrideAutoplayPolicy() { 89 var value = window.FDVideoTargetVideoConfig 90 && window.FDVideoTargetVideoConfig.useragentOverrideAutoplayPolicy; 91 92 if (typeof value !== 'string') { 93 return 'default'; 94 } 95 96 value = value.trim(); 97 98 if ( 99 value !== 'default' 100 && value !== 'force_off_on_match' 101 && value !== 'force_off_on_match_with_override_player' 102 ) { 103 return 'default'; 104 } 105 106 return value; 107 } 108 109 function shouldForceClickToPlayForUaOverride(uaMatched, overridePlayerId) { 110 if (!uaMatched) { 111 return false; 112 } 113 114 var policy = getUserAgentOverrideAutoplayPolicy(); 115 116 if (policy === 'force_off_on_match') { 117 return true; 118 } 119 120 if (policy === 'force_off_on_match_with_override_player') { 121 return !!(typeof overridePlayerId === 'string' && overridePlayerId.trim() !== ''); 122 } 123 124 return false; 125 } 126 127 function loadScriptOnce(src, callback) { 128 if (!src) { 129 callback(false); 130 return; 131 } 132 133 var existing = document.querySelector('script[src="' + src + '"]'); 134 if (existing) { 135 if (typeof window.$bp === 'function') { 136 callback(true); 137 return; 138 } 139 140 var called = false; 141 function completeOnce(ready) { 142 if (called) { 143 return; 144 } 145 146 called = true; 147 callback(ready); 148 } 149 150 existing.addEventListener('load', function () { 151 completeOnce(typeof window.$bp === 'function'); 152 }); 153 existing.addEventListener('error', function () { 154 completeOnce(false); 155 }); 156 return; 157 } 158 159 var script = document.createElement('script'); 160 script.src = src; 161 script.async = true; 162 script.onload = function () { 163 callback(typeof window.$bp === 'function'); 164 }; 165 script.onerror = function () { 166 callback(false); 167 }; 168 document.head.appendChild(script); 169 } 170 171 function toPlayerConfig(config) { 172 var playerConfig = { 173 id: String(config.playerId || ''), 174 video: String(config.videoId || ''), 175 showSnapshotOnLoadingAd: true 176 }; 177 178 if (config.freqCap != null && config.freqCap !== '') { 179 playerConfig.freqCap = Number(config.freqCap); 180 } 181 182 if (config.prebidTimeout != null && config.prebidTimeout !== '') { 183 playerConfig.prebid_timeout = Number(config.prebidTimeout); 184 } 185 186 if (config.a9Timeout != null && config.a9Timeout !== '') { 187 playerConfig.a9_timeout = Number(config.a9Timeout); 188 } 189 190 if (config.useTcfApiLocator === true) { 191 playerConfig.use_tcfapi_locator = true; 192 } 193 194 if (config.playerWidth != null) { 195 playerConfig.width = String(config.playerWidth); 196 } 197
198 if (config.playerHeight != null) { 199 playerConfig.height = String(config.playerHeight); 200 } 201 202 // Explicitly set autoplay only when force_autoplay is requested. 203 // For 'provider' mode, omit the flag so the player SDK uses its own config. 204 if (config.initMode === 'force_autoplay') { 205 playerConfig.autoplay = true; 206 playerConfig.autoplay_desktop = true; 207 } 208 209 return playerConfig; 210 } 211 212 function applyAdTagConfig(playerConfig, adTagUrl) { 213 if (!adTagUrl || typeof adTagUrl !== 'string') { 214 return playerConfig; 215 } 216 217 // Use configurable mobile breakpoint (from WordPress settings, default 767px). 218 // Falls back to 767 if not provided. 219 var mobileBreakpointPx = 767; 220 if (window.FDVideoPlayerDirector && typeof window.FDVideoPlayerDirector.getMobileBreakpointPx === 'function') { 221 mobileBreakpointPx = window.FDVideoPlayerDirector.getMobileBreakpointPx(); 222 } 223 224 var isMobile = window.matchMedia && window.matchMedia('(max-width: ' + (mobileBreakpointPx - 1) + 'px)').matches; 225 // TargetVideo: mobile is used on both mobile+desktop when desktop is omitted/null. 226 // To target desktop only: set desktop array and leave mobile null. 227 var ad = { 228 mobile: isMobile ? [adTagUrl] : null, 229 desktop: isMobile ? null : [adTagUrl], 230 adType: '0', 231 adTimeType: 's', 232 cuepoints: null 233 }; 234 235 playerConfig.Ad = [ad]; 236 return playerConfig; 237 } 238 239 function hasConsentFromDmpEvent(eventPayload) { 240 if (!eventPayload || eventPayload.gdprApplies === false) { 241 return true; 242 } 243 244 var eventStatusOk = eventPayload.eventStatus === 'useractioncomplete' || eventPayload.eventStatus === 'tcloaded'; 245 var consents = eventPayload.vendor && eventPayload.vendor.consents; 246 var vendorConsent = !!(consents && (consents[1016] === true || consents[755] === true)); 247 248 return eventStatusOk && vendorConsent; 249 } 250 251 function isSparkReadyForVideoAds() { 252 return !!( 253 window.Spark && 254 window.Spark.definitionService && 255 typeof window.Spark.definitionService.VideoAdRequest === 'function' 256 ); 257 } 258 259 function buildSparkVideoAdRequestConfig(config) { 260 return { 261 videoID: config.videoID || config.videoId || '', 262 entryId: config.entryId || config.videoId || '', 263 tags: config.tags || config.videoTags || '', 264 adkeys: config.adkeys || config.pgname || '', 265 inline: config.inline === true, 266 playerPos: config.playerPos, 267 playerHeight: config.playerHeight, 268 playerWidth: config.playerWidth, 269 playerSize: config.playerSize, 270 videoHbPlayerSize: config.videoHbPlayerSize, 271 autoplay: config.initMode === 'force_autoplay' 272 || (config.initMode === 'provider' && (config.inline === true 273 ? !!config.providerModeIsAutoplayInline 274 : !!config.providerModeIsAutoplayFeatured)), 275 continuousAutoplay: config.continuousAutoplay, 276 verticalVideo: config.verticalVideo, 277 player: config.player, 278 playlist: config.playlist, 279 playlistId: config.playlistId, 280 playlistcount: config.playlistcount, 281 paid: config.paid 282 }; 283 } 284 285 function dispatchTrackingEvent(eventName, config) { 286 if (!window.FDVideoPlayerDirector || !document) { 287 return; 288 } 289 290 var eventMap = { 291 'mediaLoaded': 'fdvideo:media_loaded', 292 'play': 'fdvideo:play', 293 'pause': 'fdvideo:pause', 294 'seeked': 'fdvideo:seeked', 295 'ended': 'fdvideo:ended', 296 'error': 'fdvideo:error', 297 'firstPlay': 'fdvideo:first_play', 298 'enterFullscreen': 'fdvideo:enter_fullscreen', 299 'exitFullscreen': 'fdvideo:exit_fullscreen', 300 'adCanSkip': 'fdvideo:ad_can_skip', 301 'adLoaded': 'fdvideo:ad_loaded', 302 'adStarted': 'fdvideo:ad_started', 303 'adCompleted': 'fdvideo:ad_completed', 304 'adError': 'fdvideo:ad_error', 305 'playReached25': 'fdvideo:play_reached_25', 306 'playReached50': 'fdvideo:play_reached_50', 307 'playReached75': 'fdvideo:play_reached_75', 308 'playReached100': 'fdvideo:play_reached_100' 309 }; 310 311 fdLog('[fd-video][targetvideo] Dispatching tracking event', { 312 eventName: eventName, 313 config: config 314 }); 315 316 var customEventName = eventMap[eventName]; 317 if (!customEventName) { 318 return; 319 } 320 321 var event = new CustomEvent(customEventName, { detail: config, bubbles: true }); 322 document.dispatchEvent(event); 323 } 324 325 function initTargetVideo(runtime) { 326 if (!runtime || !runtime.element || !runtime.config) { 327 fdLog('[fd-video][targetvideo] Invalid runtime payload.', runtime); 328 return; 329 } 330 331 var element = runtime.element; 332 var config = runtime.config; 333 var uaOverrideMatched = isUserAgentOverridden(); 334 var overridePlayerId = ''; 335 336 // Override player ID from User-Agent if override pattern is configured and matched. 337 if (uaOverrideMatched) { 338 overridePlayerId = getUserAgentOverridePlayerId(); 339 340 if (overridePlayerId) { 341 config.playerId = overridePlayerId; 342 } 343 344 fdLog('[fd-video][targetvideo] User-Agent matched override pattern â applying override settings.', {
345 userAgent: navigator.userAgent.slice(0, 100), 346 overridePlayerId: overridePlayerId || null, 347 playerId: config.playerId || null 348 }); 349 } 350 351 if (shouldForceClickToPlayForUaOverride(uaOverrideMatched, overridePlayerId)) { 352 config.initMode = 'overlay'; 353 fdLog('[fd-video][targetvideo] User-Agent override autoplay policy forced click-to-play.', { 354 domId: element.id || null, 355 policy: getUserAgentOverrideAutoplayPolicy(), 356 uaMatched: uaOverrideMatched, 357 overridePlayerIdActive: !!overridePlayerId 358 }); 359 } 360 361 var pendingUserPlay = false; 362 var playerApi = null; 363 var instancePlayerId = ''; 364 var firstPlayTracked = false; 365 var playbackProgressTracked = { 366 25: false, 367 50: false, 368 75: false, 369 100: false 370 }; 371 var adRequest = null; 372 var playlistLoadedAfterEnd = false; 373 374 if (!element.id) { 375 element.id = 'fd-video-targetvideo-' + Date.now(); 376 } 377 378 instancePlayerId = String(element.id); 379 380 function pauseCurrentPlayer() { 381 if (!playerApi || typeof playerApi.pause !== 'function') { 382 return; 383 } 384 385 try { 386 playerApi.pause(); 387 } catch (pauseError) { 388 debugError('[fd-video][targetvideo] player.pause() failed on global pause request.', pauseError); 389 } 390 } 391 392 function canLoadPlaylistAfterEnd(player) { 393 return !!( 394 config.playlistAfterEndEnabled === true && 395 typeof config.playlistAfterEndSource === 'string' && 396 config.playlistAfterEndSource.trim() !== '' && 397 player && 398 typeof player.loadPlaylist === 'function' 399 ); 400 } 401 402 document.addEventListener('fdvideo:pause_others', function (e) { 403 var activeInstanceId = e && e.detail && e.detail.activeInstanceId ? String(e.detail.activeInstanceId) : ''; 404 405 if (!activeInstanceId || activeInstanceId === instancePlayerId) { 406 return; 407 } 408 409 pauseCurrentPlayer(); 410 }); 411 412 fdLog('[fd-video][targetvideo] Runtime config received', { 413 domId: element.id, 414 providerId: runtime.instance && runtime.instance.providerId ? runtime.instance.providerId : null, 415 videoObjectId: runtime.instance && runtime.instance.videoObjectId ? runtime.instance.videoObjectId : null, 416 placement: runtime.instance && runtime.instance.placementMeta ? runtime.instance.placementMeta.placement : null, 417 videoId: config.videoId || config.videoID || null, 418 playerId: config.playerId || null, 419 adsEnabled: config.adsEnabled !== false, 420 playerWidth: config.playerWidth || null, 421 playerHeight: config.playerHeight || null, 422 sparkEnabled: !!window.Spark 423 }); 424 425 if (!config.playerId || !config.videoId) { 426 fdLog('[fd-video][targetvideo] Missing playerId or videoId.', { 427 playerId: config.playerId || null, 428 videoId: config.videoId || null, 429 videoIDAlias: config.videoID || null, 430 videoObjectId: runtime.instance && runtime.instance.videoObjectId ? runtime.instance.videoObjectId : null 431 }); 432 return; 433 } 434 435 function runWhenCmpConsentGranted(callback) { 436 if (typeof callback !== 'function') { 437 return; 438 } 439 440 var called = false; 441 var rawTimeout = 6000; 442 443 if (config.cmpReadyTimeoutMs !== undefined && config.cmpReadyTimeoutMs !== null) { 444 rawTimeout = config.cmpReadyTimeoutMs; 445 } else if (config.sparkDmpReadyTimeoutMs !== undefined && config.sparkDmpReadyTimeoutMs !== null) { 446 rawTimeout = config.sparkDmpReadyTimeoutMs; 447 } 448 449 var timeoutMs = Number(rawTimeout); 450 if (!isFinite(timeoutMs) || timeoutMs < 0) { 451 timeoutMs = 6000; 452 } 453 454 function invokeOnce() { 455 if (called) { 456 return; 457 } 458 459 called = true; 460 window.clearTimeout(cmpTimeoutHandle); 461 callback(); 462 } 463 464 // If the CMP itself is blocked (adblockers commonly block the CMP 465 // script), dmpReady never fires and the player would stay stuck 466 // waiting forever. Treat a timeout the same as a blocked script. 467 var cmpTimeoutHandle = window.setTimeout(function () { 468 if (called) { 469 return; 470 } 471 472 called = true; 473 debugError('[fd-video][targetvideo] No CMP consent signal received within ' + timeoutMs + 'ms (CMP likely blocked).', { 474 domId: element.id 475 }); 476 477 if (window.FDVideoPlayerDirector && typeof window.FDVideoPlayerDirector.removePlayerElement === 'function') { 478 window.FDVideoPlayerDirector.removePlayerElement(element); 479 } 480 }, timeoutMs); 481 482 function handleConsentPayload(eventPayload) { 483 if (hasConsentFromDmpEvent(eventPayload)) { 484 window.clearTimeout(cmpTimeoutHandle); 485 } 486 487 debugWarn('[fd-video][targetvideo] CMP dmpReady received.', {
488 domId: element.id, 489 eventStatus: eventPayload && eventPayload.eventStatus ? eventPayload.eventStatus : null, 490 gdprApplies: eventPayload && eventPayload.gdprApplies !== undefined ? eventPayload.gdprApplies : null 491 }); 492 493 if (!hasConsentFromDmpEvent(eventPayload)) { 494 debugWarn('[fd-video][targetvideo] CMP consent missing. Player SDK load blocked.', { 495 domId: element.id, 496 hasPayload: !!eventPayload 497 }); 498 return; 499 } 500 501 debugWarn('[fd-video][targetvideo] CMP consent granted. Loading TargetVideo SDK.', { 502 domId: element.id, 503 playerSdkUrl: config.playerSdkUrl || null 504 }); 505 506 invokeOnce(); 507 } 508 509 if (window.FDVideoPlayerDirector && typeof window.FDVideoPlayerDirector.onDmpReady === 'function') { 510 window.FDVideoPlayerDirector.onDmpReady(handleConsentPayload); 511 return; 512 } 513 514 if (window.Spark && window.Spark.cmd && typeof window.Spark.cmd.push === 'function') { 515 window.Spark.cmd.push(function () { 516 window.Spark.addEventListener('dmpReady', handleConsentPayload); 517 }); 518 return; 519 } 520 521 debugWarn('[fd-video][targetvideo] No dmpReady source available. Player SDK load blocked until CMP is available.', { 522 domId: element.id 523 }); 524 } 525 526 function createClickToLoadOverlay(onStart) { 527 if (typeof onStart !== 'function') { 528 return; 529 } 530 531 var existingOverlay = element.querySelector('.fd-video-click-to-load-overlay'); 532 if (existingOverlay) { 533 return; 534 } 535 536 var elementPosition = window.getComputedStyle && window.getComputedStyle(element) 537 ? window.getComputedStyle(element).position 538 : ''; 539 if (!elementPosition || 'static' === elementPosition) { 540 element.style.position = 'relative'; 541 } 542 543 var overlay = document.createElement('button'); 544 overlay.type = 'button'; 545 overlay.className = 'fd-video-click-to-load-overlay'; 546 overlay.setAttribute('aria-label', 'Play video'); 547 overlay.style.position = 'absolute'; 548 overlay.style.inset = '0'; 549 overlay.style.width = '100%'; 550 overlay.style.height = '100%'; 551 overlay.style.display = 'flex'; 552 overlay.style.flexDirection = 'column'; 553 overlay.style.alignItems = 'center'; 554 overlay.style.justifyContent = 'center'; 555 overlay.style.gap = '0.5rem'; 556 overlay.style.border = '0'; 557 overlay.style.padding = '0'; 558 overlay.style.cursor = 'pointer'; 559 overlay.style.zIndex = '2'; 560 overlay.style.backgroundColor = '#000'; 561 overlay.style.color = '#fff'; 562 563 if (config.snapshotImage) { 564 overlay.style.backgroundImage = 'linear-gradient(rgba(0,0,0,0.25), rgba(0,0,0,0.45)), url("' + String(config.snapshotImage) + '")'; 565 overlay.style.backgroundSize = 'cover'; 566 overlay.style.backgroundPosition = 'center'; 567 overlay.style.backgroundRepeat = 'no-repeat'; 568 } 569 570 var playIcon = document.createElement('span'); 571 playIcon.textContent = 'â¶'; 572 playIcon.style.width = '64px'; 573 playIcon.style.height = '64px'; 574 playIcon.style.borderRadius = '50%'; 575 playIcon.style.display = 'inline-flex'; 576 playIcon.style.alignItems = 'center'; 577 playIcon.style.justifyContent = 'center'; 578 playIcon.style.fontSize = '30px'; 579 playIcon.style.background = 'rgba(0,0,0,0.72)'; 580 playIcon.style.paddingLeft = '4px'; 581 582 var label = document.createElement('span'); 583 label.textContent = 'Video laden'; 584 label.style.fontSize = '14px'; 585 label.style.fontWeight = '600'; 586 label.style.textShadow = '0 1px 2px rgba(0,0,0,0.6)'; 587 588 overlay.appendChild(playIcon); 589 overlay.appendChild(label); 590 591 overlay.addEventListener('click', function () { 592 overlay.disabled = true; 593 overlay.style.cursor = 'default'; 594 overlay.style.opacity = '0.9'; 595 pendingUserPlay = true; 596 onStart(function finishOverlay() { 597 if (overlay.parentNode) { 598 overlay.parentNode.removeChild(overlay); 599 } 600 }); 601 }); 602 603 element.appendChild(overlay); 604 debugWarn('[fd-video][targetvideo] Click-to-load overlay ready for non-autoplay video.', { 605 domId: element.id, 606 hasSnapshot: !!config.snapshotImage 607 }); 608 } 609 610 function initPlayer(adTagUrl) { 611 function resetPlaybackProgressMarkers() { 612 playbackProgressTracked[25] = false;
613 playbackProgressTracked[50] = false; 614 playbackProgressTracked[75] = false; 615 playbackProgressTracked[100] = false; 616 } 617 618 function trackPlaybackProgress(player) { 619 if (!player || typeof player.currentTime !== 'function' || typeof player.duration !== 'function') { 620 return; 621 } 622 623 var duration = Number(player.duration()); 624 if (!duration || !isFinite(duration) || duration <= 0) { 625 return; 626 } 627 628 var currentTime = Number(player.currentTime()); 629 if (!isFinite(currentTime) || currentTime < 0) { 630 return; 631 } 632 633 var percent = (currentTime / duration) * 100; 634 635 if (!playbackProgressTracked[25] && percent >= 25) { 636 playbackProgressTracked[25] = true; 637 dispatchTrackingEvent('playReached25', config); 638 } 639 640 if (!playbackProgressTracked[50] && percent >= 50) { 641 playbackProgressTracked[50] = true; 642 dispatchTrackingEvent('playReached50', config); 643 } 644 645 if (!playbackProgressTracked[75] && percent >= 75) { 646 playbackProgressTracked[75] = true; 647 dispatchTrackingEvent('playReached75', config); 648 } 649 650 if (!playbackProgressTracked[100] && percent >= 100) { 651 playbackProgressTracked[100] = true; 652 dispatchTrackingEvent('playReached100', config); 653 } 654 } 655 656 runWhenCmpConsentGranted(function () { 657 loadScriptOnce(config.playerSdkUrl, function (ready) { 658 if (!ready || typeof window.$bp !== 'function') { 659 debugError('[fd-video][targetvideo] TargetVideo SDK not available (likely blocked by an adblocker).'); 660 661 if (window.FDVideoPlayerDirector && typeof window.FDVideoPlayerDirector.removePlayerElement === 'function') { 662 window.FDVideoPlayerDirector.removePlayerElement(element); 663 } 664 665 return; 666 } 667 668 var playerConfig = toPlayerConfig(config); 669 applyAdTagConfig(playerConfig, adTagUrl); 670 671 debugWarn('[fd-video][targetvideo] >>> Calling $bp():', { 672 domId: element.id, 673 playerId: playerConfig.id, 674 videoId: playerConfig.video, 675 hasAdConfig: !!playerConfig.Ad, 676 adTagUrlPreview: adTagUrl ? adTagUrl.slice(0, 120) : null, 677 fullPlayerConfig: playerConfig 678 }); 679 680 var player; 681 let isAdRunning = false; 682 try { 683 player = window.$bp(element.id, playerConfig); 684 playerApi = player; 685 } catch (bpError) { 686 debugError('[fd-video][targetvideo] $bp() threw an error:', bpError); 687 return; 688 } 689 690 debugWarn('[fd-video][targetvideo] $bp() returned:', player); 691 692 if (!player || typeof player.on !== 'function') { 693 debugWarn('[fd-video][targetvideo] $bp() returned no player API (player.on not available). ' + 694 'This may be expected for async init â watching for player events via data-bp may still work.'); 695 return; 696 } 697 698 player.on('start', function () { 699 if (firstPlayTracked) { 700 return; 701 } 702 703 firstPlayTracked = true; 704 dispatchTrackingEvent('firstPlay', config); 705 }); 706 707 player.on('play', function () { 708 dispatchTrackingEvent('play', config); 709 710 if (window.FDVideoPlayerDirector && typeof window.FDVideoPlayerDirector.notifyPlay === 'function') { 711 window.FDVideoPlayerDirector.notifyPlay(instancePlayerId); 712 } 713 }); 714 715 player.on('pause', function () { 716 dispatchTrackingEvent('pause', config); 717 }); 718 719 player.on('seeked', function () { 720 dispatchTrackingEvent('seeked', config); 721 }); 722 723 player.on('ended', function () { 724 if (!playlistLoadedAfterEnd && canLoadPlaylistAfterEnd(player)) { 725 try { 726 player.loadPlaylist(config.playlistAfterEndSource.trim()); 727 player.play(); 728 playlistLoadedAfterEnd = true; 729 debugWarn('[fd-video][targetvideo] Playlist loaded after ended event.', { 730 domId: element.id, 731 playlistSource: config.playlistAfterEndSource 732 }); 733 } catch (playlistError) { 734 debugError('[fd-video][targetvideo] player.loadPlaylist() failed.', playlistError); 735 } 736 } 737 738 dispatchTrackingEvent('ended', config); 739 resetPlaybackProgressMarkers(); 740 }); 741 742 player.on('playbackerror', function (error) { 743 debugError('[fd-video][targetvideo] Playback error.', element.i
743d, error); 744 dispatchTrackingEvent('error', config); 745 }); 746 747 player.on('timeupdate', function () { 748 if(isAdRunning) return; 749 trackPlaybackProgress(player); 750 }); 751 752 player.on('fullscreenchange', function () { 753 var isFullscreen = false; 754 755 if (typeof player.isFullScreen === 'function') { 756 isFullscreen = !!player.isFullScreen(); 757 } else { 758 isFullscreen = !!player.isFullScreen; 759 } 760 761 dispatchTrackingEvent(isFullscreen ? 'enterFullscreen' : 'exitFullscreen', config); 762 }); 763 764 765 player.on('requestAd', function () { 766 // dispatchTrackingEvent('adLoaded', config); 767 isAdRunning = true; 768 }); 769 770 player.on('adImpression', function () { 771 dispatchTrackingEvent('adLoaded', config); 772 }); 773 774 player.on('adStart', function () { 775 dispatchTrackingEvent('adStarted', config); 776 }); 777 778 player.on('adComplete', function () { 779 isAdRunning = false; 780 dispatchTrackingEvent('adCompleted', config); 781 }); 782 783 player.on('adSkip', function () { 784 dispatchTrackingEvent('adCanSkip', config); 785 }); 786 787 player.on('ready', function () { 788 debugDebug('[fd-video][targetvideo] Player ready.', element.id); 789 dispatchTrackingEvent('mediaLoaded', config); 790 791 if (pendingUserPlay && typeof player.play === 'function') { 792 pendingUserPlay = false; 793 try { 794 player.play(); 795 debugWarn('[fd-video][targetvideo] Starting playback after user click-to-load.', { 796 domId: element.id, 797 initMode: config.initMode 798 }); 799 } catch (playError) { 800 debugError('[fd-video][targetvideo] player.play() failed after click-to-load.', playError); 801 } 802 } 803 804 var readyEvent = new CustomEvent('fdvideo:player_ready', { bubbles: true }); 805 element.dispatchEvent(readyEvent); 806 }); 807 808 /* 809 player.on('adEnd', function () { 810 dispatchTrackingEvent('adCompleted', config); 811 }); 812 */ 813 814 player.on('adError', function (error) { 815 debugError('[fd-video][targetvideo] Ad error.', element.id, error); 816 dispatchTrackingEvent('adError', config); 817 }); 818 819 player.on('newContent', function () { 820 resetPlaybackProgressMarkers(); 821 822 // Only handle newContent after the first video has started playing 823 if (!firstPlayTracked) { 824 debugWarn('[fd-video][targetvideo] newContent: first video has not started yet, skipping ad refresh.', { 825 domId: element.id 826 }); 827 return; 828 } 829 830 firstPlayTracked = false; 831 832 if (!adRequest || typeof adRequest.refreshAndThen !== 'function') { 833 debugWarn('[fd-video][targetvideo] newContent: no adRequest available, skipping ad refresh.', { 834 domId: element.id 835 }); 836 return; 837 } 838 839 debugWarn('[fd-video][targetvideo] newContent: requesting new ad via refreshAndThen.', { 840 domId: element.id 841 }); 842 843 if (player && player.config) { 844 player.config.Ad = null; 845 } 846 if (player && player.Advertising && Array.isArray(player.Advertising.adsPool)) { 847 player.Advertising.adsPool = []; 848 } 849 850 adRequest.refreshAndThen(null, function (adTagUrl) { 851 if (!adTagUrl || typeof player.playAd !== 'function') { 852 debugWarn('[fd-video][targetvideo] newContent: no adTagUrl or playAd unavailable.', { 853 domId: element.id, 854 hasAdTagUrl: !!adTagUrl 855 }); 856 return; 857 } 858 859 debugWarn('[fd-video][targetvideo] newContent: playing new ad via playAd().', { 860 domId: element.id, 861 adTagUrlPreview: adTagUrl.slice(0, 120) 862 }); 863 864 try { 865 player.playAd(adTagUrl); 866 } catch (playAdError) { 867 debugError('[fd-video][targetvideo] player.playAd() failed after newContent.', playAdError); 868 } 869 }); 870 }); 871 }); 872 }); 873 } 874 875 var playerStartTriggered = false; 876 877 function startPlayerFlow(done) { 878 if (playerStartTriggered) { 879 return; 880 } 881 882 playerStartTriggered = true; 883 var finish = typeof done === 'function' ? done : function () {}; 884 885 if (config.adsEnabled === false) { 886 fdLog('[fd-video][targetvideo] Ad integration disabled for this instance. Initializing player directly.'); 887 finish(); 888 initPlayer(null); 889 return; 890 } 891 892 if (!window.Spark || !window.Spark.cmd || typeof window.Spark.cmd.push !== 'function') { 893 fdLog('[fd-video][targetvideo] Spark unavailable, starting player without ads.', { 894 hasSpark: !!window.Spark, 895 hasSparkCmd: !!(window.Spark && window.Spark.cmd) 896 }); 897 finish(); 898 initPlayer(null); 899 return; 900 } 901 902 var started = false;
903 var timeoutMs = Number(config.sparkDmpReadyTimeoutMs || 6000); 904 905 function startWithoutAds() { 906 if (started) { 907 return; 908 } 909 910 started = true; 911 finish(); 912 debugWarn('[fd-video][targetvideo] >>> Starting player WITHOUT ads (timeout or no consent).', { 913 domId: element.id, 914 videoId: config.videoId || null, 915 playerId: config.playerId || null 916 }); 917 initPlayer(null); 918 } 919 920 function startWithSparkAds() { 921 if (started) { 922 return; 923 } 924 925 if (!isSparkReadyForVideoAds()) { 926 debugDebug('[fd-video][targetvideo] Spark VideoAdRequest not available after dmpReady â starting without ads.'); 927 startWithoutAds(); 928 return; 929 } 930 931 var req; 932 try { 933 const ad_request_config = buildSparkVideoAdRequestConfig(config); 934 req = new window.Spark.definitionService.VideoAdRequest(ad_request_config); 935 fdLog('[fd-video][targetvideo] VideoAdRequest created.', { 936 domId: element.id, 937 videoId: config.videoId || null, 938 playerPos: config.playerPos || null, 939 adRequestConfig: ad_request_config 940 }); 941 } catch (error) { 942 debugError('[fd-video][targetvideo] Failed to create VideoAdRequest.', error); 943 startWithoutAds(); 944 return; 945 } 946 947 adRequest = req; 948 949 try { 950 req.requestHb(); 951 debugWarn('[fd-video][targetvideo] requestHb() sent â waiting for onReady or timeout (' + timeoutMs + 'ms).', element.id); 952 req.onReady(function (adTagUrl) { 953 if (started) { 954 return; 955 } 956 957 started = true; 958 finish(); 959 debugWarn('[fd-video][targetvideo] >>> onReady received.', { 960 domId: element.id, 961 hasAdTagUrl: !!adTagUrl, 962 adTagUrlPreview: adTagUrl ? adTagUrl.slice(0, 120) : null 963 }); 964 initPlayer(adTagUrl || null); 965 }); 966 } catch (error) { 967 debugError('[fd-video][targetvideo] VideoAdRequest flow failed.', error); 968 startWithoutAds(); 969 } 970 } 971 972 var useBroker = window.FDVideoPlayerDirector && typeof window.FDVideoPlayerDirector.onDmpReady === 'function'; 973 974 if (useBroker) { 975 var timeoutHandle = window.setTimeout(startWithoutAds, timeoutMs); 976 fdLog('[fd-video][targetvideo] Waiting for dmpReady via PlayerDirector broker.', { 977 domId: element.id, 978 timeoutMs: timeoutMs 979 }); 980 981 window.FDVideoPlayerDirector.onDmpReady(function (eventPayload) { 982 window.clearTimeout(timeoutHandle); 983 fdLog('[fd-video][targetvideo] dmpReady received via broker.', { 984 domId: element.id, 985 hasPayload: !!eventPayload 986 }); 987 988 if (!hasConsentFromDmpEvent(eventPayload)) { 989 startWithoutAds(); 990 return; 991 } 992 993 startWithSparkAds(); 994 }); 995 } else { 996 fdLog('[fd-video][targetvideo] Using direct Spark listener fallback (no broker).', { 997 domId: element.id, 998 timeoutMs: timeoutMs 999 }); 1000 1001 window.Spark.cmd.push(function () { 1002 var dmpReadyFired = false; 1003 var timeoutHandle = window.setTimeout(function () { 1004 if (!dmpReadyFired) { 1005 startWithoutAds(); 1006 } 1007 }, timeoutMs); 1008 1009 window.Spark.addEventListener('dmpReady', function (eventPayload) { 1010 dmpReadyFired = true; 1011 window.clearTimeout(timeoutHandle); 1012 1013 if (!hasConsentFromDmpEvent(eventPayload)) { 1014 startWithoutAds(); 1015 return; 1016 } 1017 1018 startWithSparkAds(); 1019 }); 1020 }); 1021 } 1022 } 1023 1024 if (config.initMode === 'overlay') { 1025 runWhenCmpConsentGranted(function () { 1026 createClickToLoadOverlay(function (done) { 1027 debugWarn('[fd-video][targetvideo] User initiated playback for overlay video.', { 1028 domId: element.id 1029 }); 1030 startPlayerFlow(done); 1031 }); 1032 }); 1033 return; 1034 } 1035 1036 startPlayerFlow(); 1037 } 1038 1039 function register() { 1040 if (!window.FDVideoPlayerDirector || typeof window.FDVideoPlayerDirector.registerAdapter !== 'function') { 1041 return false; 1042 } 1043 1044 window.FDVideoPlayerDirector.registerAdapter('targetvideo', { 1045 init: initTargetVideo 1046 }); 1047 1048 if (typeof window.FDVideoPlayerDirector.scan === 'function') { 1049 window.FDVideoPlayerDirector.scan(document); 1050 } 1051 1052 return true; 1053 } 1054 1055 if (register()) { 1056 return; 1057 } 1058 1059 document.addEventListener('DOMContentLoaded', function () { 1060 register(); 1061 }); 1062})(); 1063; 1064(function () { 1065 'use strict'; 1066 1067 var adapters = {}; 1068 1069 /** 1070 * Check if debug logging is enabled via URL parameter ?_video_debug=1 1071 */ 1072 function isDebugEnabled() { 1073 if (typeof URLSearchParams !== 'undefined') { 1074 return new URLSearchParams(window.location.search).get('_video_debug')
1074=== '1'; 1075 } 1076 // Fallback for older browsers 1077 return /[\?&]_video_debug=1/.test(window.location.search); 1078 } 1079 1080 function fdLog() { 1081 if (!isDebugEnabled() || !window.console) { 1082 return; 1083 } 1084 1085 if (typeof window.console.info === 'function') { 1086 window.console.info.apply(window.console, arguments); 1087 return; 1088 } 1089 1090 if (typeof window.console.log === 'function') { 1091 window.console.log.apply(window.console, arguments); 1092 } 1093 } 1094 1095 function debugError() { 1096 if (!isDebugEnabled() || !window.console || typeof window.console.error !== 'function') { 1097 return; 1098 } 1099 window.console.error.apply(window.console, arguments); 1100 } 1101 1102 // dmpReady broker: receives the Spark dmpReady event once and replays 1103 // the payload to all subscribers, including those that register after 1104 // the event has already fired. 1105 var dmpReadyPayload = null; 1106 var dmpReadyFired = false; 1107 var dmpReadyCallbacks = []; 1108 1109 function fireDmpReady(payload) { 1110 if (dmpReadyFired) { 1111 return; 1112 } 1113 1114 dmpReadyFired = true; 1115 dmpReadyPayload = payload !== undefined ? payload : null; 1116 1117 var callbacks = dmpReadyCallbacks.slice(); 1118 dmpReadyCallbacks = []; 1119 1120 for (var i = 0; i < callbacks.length; i++) { 1121 try { 1122 callbacks[i](dmpReadyPayload); 1123 } catch (error) { 1124 debugError('[fd-video] dmpReady callback error:', error); 1125 } 1126 } 1127 } 1128 1129 function onDmpReady(callback) { 1130 if (typeof callback !== 'function') { 1131 return; 1132 } 1133 1134 if (dmpReadyFired) { 1135 // Event already received: replay immediately. 1136 try { 1137 callback(dmpReadyPayload); 1138 } catch (error) { 1139 debugError('[fd-video] dmpReady callback error:', error); 1140 } 1141 return; 1142 } 1143 1144 dmpReadyCallbacks.push(callback); 1145 } 1146 1147 // Register broker listener with Spark as soon as possible. 1148 // The cmd queue ensures we run after Spark.init. 1149 function installSparkDmpBroker() { 1150 if (!window.Spark || !window.Spark.cmd || typeof window.Spark.cmd.push !== 'function') { 1151 return; 1152 } 1153 1154 window.Spark.cmd.push(function () { 1155 window.Spark.addEventListener('dmpReady', function (payload) { 1156 fireDmpReady(payload); 1157 }); 1158 }); 1159 } 1160 1161 installSparkDmpBroker(); 1162 1163 // DataLayer Tracking Module: centralized handling of provider-agnostic video events. 1164 var trackingModule = (function () { 1165 var trackedEvents = { 1166 'fdvideo:media_loaded': 'MEDIA_LOADED', 1167 'fdvideo:play': 'PLAY', 1168 'fdvideo:pause': 'PAUSE', 1169 'fdvideo:seeked': 'SEEKED', 1170 'fdvideo:ended': 'ENDED', 1171 'fdvideo:enter_fullscreen': 'ENTER_FULLSCREEN', 1172 'fdvideo:exit_fullscreen': 'EXIT_FULLSCREEN', 1173 'fdvideo:error': 'ERROR', 1174 'fdvideo:first_play': 'FIRST_PLAY', 1175 'fdvideo:ad_can_skip': 'AD_CAN_SKIP', 1176 'fdvideo:ad_loaded': 'AD_LOADED', 1177 'fdvideo:ad_completed': 'AD_COMPLETED', 1178 'fdvideo:ad_started': 'AD_STARTED', 1179 'fdvideo:ad_error': 'AD_ERROR', 1180 'fdvideo:play_reached_25': 'PLAY_REACHED_25', 1181 'fdvideo:play_reached_50': 'PLAY_REACHED_50', 1182 'fdvideo:play_reached_75': 'PLAY_REACHED_75', 1183 'fdvideo:play_reached_100': 'PLAY_REACHED_100' 1184 }; 1185 1186 function normalizeDatalayerEvent(eventCategory, detail) { 1187 if (!detail || typeof detail !== 'object') { 1188 return null; 1189 } 1190 1191 // Map detail (resolvedConfig) to EVENTS.md schema. 1192 var obj = { 1193 event: 'targetvideo_video', 1194 video_event_category: eventCategory, 1195 playerid: detail.playerId || '', 1196 adkeys: detail.adkeys || detail.pgname || '', 1197 tags: detail.tags || detail.videoTags || '', 1198 videosource: detail.videosource || detail.player || 'unknown', 1199 accountid: detail.accountId || '', 1200 createddate: detail.createddate || '', 1201 videoid: detail.videoId || detail.videoID || '', 1202 videoname: detail.videoname || '', 1203 position: detail.playerPos || '', 1204 videoreferenceid: detail.videoreferenceid || '', 1205 videoduration: detail.videoduration || '' 1206 }; 1207 1208 return obj; 1209 } 1210 1211 function pushDataLayer(obj) { 1212 if (!obj || !window.dataLayer || !Array.isArray(window.dataLayer)) { 1213 return; 1214 } 1215 1216 window.dataLayer.push(obj); 1217 } 1218 1219 // Listen for custom events on document. 1220 Object.keys(trackedEvents).forEach(function (customEventName) { 1221 document.addEventListener(customEventName, function (e) { 1222 var obj = normalizeDatalayerEvent(trackedEvents[customEventName], e.detail); 1223 pushDataLayer(obj); 1224 }); 1225 }); 1226 1227 return { 1228 pushEvent: function (eventName, detail) { 1229 var event = new CustomEvent(eventName, { detail: detail, bubbles: true }); 1230 document.dispatchEvent(event); 1231 } 1232 }; 1233 })(); 1234 1235 function safeParseJson(rawValue) { 1236 if (!rawValue || typeof rawValue !== 'string') { 1237 return null; 1238 } 1239 1240 try { 1241 return JSON.parse(rawValue); 1242 } catch (error) { 1243 debugError('[fd-video] Invalid JSON payload in container data attribute.', error); 1244 return null; 1245 } 1246 } 1247 1248 function registerAdapter(providerId, adapter) { 1249 if (!providerId || typeof providerId !== 'string') { 1250 return; 1251 } 1252 1253 if (!adapter || typeof adapter.init !== 'function') { 1254 return; 1255 } 1256 1257 adapters[providerId] = adapter; 1258 } 1259 1260 function initContainer(element) { 1261 if (!element || element.nodeType !== 1) { 1262 return; 1263 } 1264 1265 if (element.dataset.fdVideoInitialized === '1') { 1266 return; 1267 } 1268 1269 var providerId = element.getAttribute('data-fd-provider') || ''; 1270 var instance = safeParseJson(element.getAttribute('data-fd-video-instance')); 1271 var config = safeParseJson(element.getAttribute('data-fd-config')) || {}; 1272 1273 fdLog('[fd-video] Container discovered', {
1274 domId: element.id || null, 1275 providerId: providerId, 1276 hasInstance: !!instance, 1277 hasConfig: !!config, 1278 videoObjectId: instance && instance.videoObjectId ? instance.videoObjectId : null, 1279 placement: instance && instance.placementMeta ? instance.placementMeta.placement : null, 1280 configVideoId: config.videoId || config.videoID || null, 1281 configPlayerId: config.playerId || null 1282 }); 1283 1284 if (!providerId || !instance) { 1285 return; 1286 } 1287 1288 element.dataset.fdVideoInitialized = '1'; 1289 if (isDeferredContainer(element)) { 1290 element.dataset.fdVideoInitPending = '1'; 1291 } 1292 1293 if (!adapters[providerId]) { 1294 fdLog('[fd-video] No adapter registered for provider:', providerId); 1295 failDeferredContainer(element, 'missing_adapter'); 1296 return; 1297 } 1298 1299 try { 1300 fdLog('[fd-video] Initializing adapter', { 1301 providerId: providerId, 1302 domId: element.id || null, 1303 videoObjectId: instance.videoObjectId || null, 1304 configVideoId: config.videoId || config.videoID || null, 1305 configPlayerId: config.playerId || null 1306 }); 1307 1308 armPlayerLoadTimeout(element, config); 1309 adapters[providerId].init({ 1310 element: element, 1311 instance: instance, 1312 config: config 1313 }); 1314 } catch (error) { 1315 debugError('[fd-video] Adapter initialization failed for provider:', providerId, error); 1316 failDeferredContainer(element, 'adapter_init_failed'); 1317 } 1318 } 1319 1320 function scan(root) { 1321 var scope = root && root.querySelectorAll ? root : document; 1322 var containers = scope.querySelectorAll('[data-fd-video-instance][data-fd-provider]'); 1323 1324 fdLog('[fd-video] Scan completed', { 1325 containerCount: containers.length, 1326 scope: scope === document ? 'document' : 'custom-root' 1327 }); 1328 1329 for (var i = 0; i < containers.length; i++) { 1330 initContainer(containers[i]); 1331 } 1332 } 1333 1334 /** 1335 * Notify all provider instances that one player started playback. 1336 * 1337 * Providers should call this from their native play event handler. 1338 * FD Video then broadcasts a single pause request to all instances, 1339 * excluding the currently active instance. 1340 * 1341 * @param {string} activeInstanceId The currently playing instance ID. 1342 */ 1343 function notifyPlay(activeInstanceId) { 1344 if (!activeInstanceId) { 1345 return; 1346 } 1347 1348 fdLog('[fd-video] notifyPlay received, broadcasting pause for other instances.', { 1349 activeInstanceId: activeInstanceId 1350 }); 1351 1352 var pauseEvent; 1353 try { 1354 pauseEvent = new CustomEvent('fdvideo:pause_others', { 1355 detail: { 1356 activeInstanceId: activeInstanceId 1357 }, 1358 bubbles: true 1359 }); 1360 } catch (error) { 1361 debugError('[fd-video] Failed to create pause broadcast event.', error); 1362 return; 1363 } 1364 1365 document.dispatchEvent(pauseEvent); 1366 } 1367 1368 var PLAYER_READY_TIMEOUT_MS = 6000; 1369 1370 function getContainerFromNode(node) { 1371 if (!node || node.nodeType !== 1) { 1372 return null; 1373 } 1374 1375 if (node.hasAttribute('data-fd-video-instance')) { 1376 return node; 1377 } 1378 1379 return node.closest ? node.closest('[data-fd-video-instance]') : null; 1380 } 1381 1382 function getContainerFromEvent(event) { 1383 var element = getContainerFromNode(event.target); 1384 if (!element && event.detail && event.detail.element) { 1385 element = getContainerFromNode(event.detail.element); 1386 } 1387 1388 return element; 1389 } 1390 1391 function isDeferredContainer(element) { 1392 return !!(element && element.closest && element.closest('.fd-video-deferred')); 1393 } 1394 1395 function getPlayerReadyTimeoutMs(config) { 1396 if (config && typeof config.playerReadyTimeoutMs === 'number' && config.playerReadyTimeoutMs > 0) { 1397 return config.playerReadyTimeoutMs; 1398 } 1399 1400 if (config && typeof config.sparkDmpReadyTimeoutMs === 'number' && config.sparkDmpReadyTimeoutMs > 0) { 1401 return config.sparkDmpReadyTimeoutMs; 1402 } 1403 1404 return PLAYER_READY_TIMEOUT_MS; 1405 } 1406 1407 function clearPlayerLoadTimeout(element) { 1408 if (!element || !element.__fdVideoLoadTimeoutId) { 1409 return; 1410 } 1411 1412 window.clearTimeout(element.__fdVideoLoadTimeoutId); 1413 element.__fdVideoLoadTimeoutId = null; 1414 } 1415 1416 function clearPendingPlayerState(element) { 1417 if (!element) { 1418 return; 1419 } 1420 1421 clearPlayerLoadTimeout(element); 1422 delete element.dataset.fdVideoInitPending; 1423 } 1424 1425 function failDeferredContainer(element, reason) { 1426 if (!isDeferredContainer(element) || element.dataset.fdVideoInitPending !== '1') { 1427 return; 1428 } 1429 1430 clearPendingPlayerState(element); 1431 fdLog('[fd-video] Removing deferred player after initialization failure.', {
1432 domId: element.id || null, 1433 reason: reason || 'unknown' 1434 }); 1435 removePlayerElement(element); 1436 } 1437 1438 function armPlayerLoadTimeout(element, config) { 1439 if (!isDeferredContainer(element)) { 1440 return; 1441 } 1442 1443 element.dataset.fdVideoInitPending = '1'; 1444 clearPlayerLoadTimeout(element); 1445 element.__fdVideoLoadTimeoutId = window.setTimeout(function () { 1446 failDeferredContainer(element, 'player_ready_timeout'); 1447 }, getPlayerReadyTimeoutMs(config)); 1448 } 1449 1450 /** 1451 * Remove a blocked player's container from the DOM (e.g. adblocker 1452 * prevented the SDK/adapter script from loading), so any element 1453 * underneath (thumbnail, teaser markup) becomes visible again. 1454 * 1455 * @param {Element} element The provider container element (data-fd-video-instance). 1456 */ 1457 function removePlayerElement(element) { 1458 if (!element || element.nodeType !== 1) { 1459 return; 1460 } 1461 1462 clearPendingPlayerState(element); 1463 1464 var domId = element.id || ''; 1465 var deferred = element.closest ? element.closest('.fd-video-deferred') : null; 1466 var toRemove = deferred || element; 1467 var slot = domId ? document.getElementById('fd-video-slot-' + domId) : null; 1468 var wrapper = element.closest ? element.closest('.header-featured-video-wrapper') : null; 1469 1470 if (toRemove.parentNode) { 1471 toRemove.parentNode.removeChild(toRemove); 1472 } 1473 1474 if (slot && slot.parentNode) { 1475 slot.parentNode.removeChild(slot); 1476 } 1477 1478 // Toggle a class instead of an inline style: survives figcaption 1479 // re-renders and always wins via !important, regardless of when 1480 // stylesheets finish loading relative to this script. 1481 if (wrapper) { 1482 wrapper.classList.add('fd-video-player-blocked'); 1483 } 1484 1485 fdLog('[fd-video] Player element removed after load failure.', { domId: domId }); 1486 1487 var blockedEvent; 1488 try { 1489 blockedEvent = new CustomEvent('fdvideo:player_blocked', { 1490 detail: { domId: domId }, 1491 bubbles: true 1492 }); 1493 document.dispatchEvent(blockedEvent); 1494 } catch (error) { 1495 debugError('[fd-video] Failed to create blocked-player event.', error); 1496 } 1497 } 1498 1499 window.FDVideoPlayerDirector = { 1500 registerAdapter: registerAdapter, 1501 scan: scan, 1502 initContainer: initContainer, 1503 onDmpReady: onDmpReady, 1504 notifyPlay: notifyPlay, 1505 removePlayerElement: removePlayerElement, 1506 getMobileBreakpointPx: function() { 1507 // Find the first container element with data-fd-config and extract mobileBreakpointPx. 1508 var container = document.querySelector('[data-fd-config]'); 1509 if (container) { 1510 var config = safeParseJson(container.getAttribute('data-fd-config')) || {}; 1511 if (config.mobileBreakpointPx && typeof config.mobileBreakpointPx === 'number') { 1512 return config.mobileBreakpointPx; 1513 } 1514 } 1515 // Fallback to default (Bootstrap md breakpoint). 1516 return 767; 1517 } 1518 }; 1519 1520 /** 1521 * Move each deferred container from the hidden wp_footer pool to its 1522 * matching slot placeholder. Called synchronously on DOMContentLoaded, 1523 * before adapter initialisation, so no layout shift occurs. 1524 */ 1525 function relocateDeferredContainers() { 1526 var pool = document.getElementById('fd-video-deferred-pool'); 1527 if (!pool) { 1528 return; 1529 } 1530 1531 var deferreds = pool.querySelectorAll('[data-fd-for-slot]'); 1532 for (var i = 0; i < deferreds.length; i++) { 1533 var deferred = deferreds[i]; 1534 var domId = deferred.getAttribute('data-fd-for-slot'); 1535 var slot = document.getElementById('fd-video-slot-' + domId); 1536 if (slot && slot.parentNode) { 1537 slot.parentNode.replaceChild(deferred, slot); 1538 } 1539 } 1540 1541 if (pool.parentNode) { 1542 pool.parentNode.removeChild(pool); 1543 } 1544 } 1545 1546 // Show the visible video meta when the player signals it is ready. 1547 document.addEventListener('fdvideo:player_ready', function (e) { 1548 var element = getContainerFromEvent(e); 1549 if (!element) { 1550 return; 1551 } 1552 clearPendingPlayerState(element); 1553 var wrapper = element.parentNode; 1554 if (!wrapper) { 1555 return; 1556 } 1557 var meta = wrapper.querySelector('.fd-video-meta'); 1558 if (meta) { 1559 meta.removeAttribute('hidden'); 1560 } 1561 var link = wrapper.querySelector('.fd-video-link'); 1562 if (link) { 1563 link.removeAttribute('hidden'); 1564 } 1565 }); 1566 1567 document.addEventListener('fdvideo:error', function (e) { 1568 var element = getContainerFromEvent(e); 1569 var detail = e.detail && typeof e.detail === 'object' ? e.detail : {}; 1570 var isInitFailure = detail.phase === 'init' || detail.status === 'init_failed'; 1571 if (!element) { 1572 return; 1573 } 1574 1575 if (!isInitFailure) { 1576 return; 1577 } 1578 1579 failDeferredContainer(element, 'player_error'); 1580 }); 1581 1582 document.addEventListener('DOMContentLoaded', function () { 1583 relocateDeferredContainers(); 1584 scan(document); 1585 }); 1586})(); 1587; 1588/*! This file is auto-generated */ 1589"use strict";var wp;(wp||={}).domReady=(()=>{var o=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var i=Object.prototype.hasOwnProperty;var f=(e,t)=>{for(var n in t)o(e,n,{get:t[n],enumerable:!0})},m=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let d of a(t))!i.call(e,d)&&d!==n&&o(e,d,{get:()=>
1589t[d],enumerable:!(r=u(t,d))||r.enumerable});return e};var c=e=>m(o({},"__esModule",{value:!0}),e);var p={};f(p,{default:()=>y});function y(e){if(!(typeof document>"u")){if(document.readyState==="complete"||document.readyState==="interactive")return void e();document.addEventListener("DOMContentLoaded",e)}}return c(p);})(); 1590if (typeof wp.domReady === 'object' && wp.domReady.default) { wp.domReady = wp.domReady.default; } 1591;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.