1// VIDEO, SOUNDCLOUD - Universal Lazy Embed Script - Final Version - 25.10.2025 2document.addEventListener("DOMContentLoaded", function() { 3 const lazyPlaceholders = document.querySelectorAll(".lazy-embed-placeholder"); 4 5 if (lazyPlaceholders.length === 0) return; 6 7 const translations = { 8 de: { withSound: "Mit Ton", noSound: "Ohne Ton" }, 9 en: { withSound: "With Sound", noSound: "No Sound" } 10 }; 11 12 const getLanguage = () => { 13 const lang = document.documentElement.lang.split('-')[0]; 14 return translations[lang] ? lang : 'de'; 15 }; 16 17 const currentTranslations = translations[getLanguage()]; 18 19 const loadMedia = (placeholder, isMuted) => { 20 const provider = placeholder.dataset.provider; 21 const videoId = placeholder.dataset.videoId; 22 let iframeHtml = ''; 23 const mutedParam = isMuted ? 1 : 0; 24 25 if (provider === "youtube") { 26 iframeHtml = `<iframe src="https://www.youtube.com/embed/${videoId}?rel=0&autoplay=1&mute=${mutedParam}&playsinline=1" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`; 27 } else if (provider === "vimeo") { 28 iframeHtml = `<iframe src="https://player.vimeo.com/video/${videoId}?autoplay=1&muted=${mutedParam}&title=0&byline=0&portrait=0" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>`; 29 } else if (provider === "soundcloud") { 30 const trackUrl = placeholder.dataset.trackUrl; 31 // KORREKTUR: Der Iframe wird jetzt ebenfalls absolut positioniert, um den Container korrekt auszufüllen. 32 iframeHtml = `<iframe style="position:absolute;top:0;left:0;width:100%;height:100%;" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=${encodeURIComponent(trackUrl)}&color=%23ff5500&auto_play=true&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true"></iframe>`; 33 } 34 35 if (iframeHtml) { 36 placeholder.innerHTML = iframeHtml; 37 placeholder.classList.add('media-loaded'); 38 } 39 }; 40 41 const handleInteraction = (event) => { 42 const placeholder = event.currentTarget; 43 const provider = placeholder.dataset.provider; 44 const isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); 45 46 if (isMobile && provider !== 'soundcloud') { 47 loadMedia(placeholder, true); 48 } else { 49 if (provider === 'soundcloud') { 50 loadMedia(placeholder, false); 51 return; 52 } 53 const overlay = document.createElement('div'); 54 overlay.className = 'sound-choice-overlay'; 55 const soundOnIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></svg>'; 56 const soundOffIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"></path></svg>'; 57 58 const withSoundButton = document.createElement('button'); 59 withSoundButton.className = 'sound-choice-button'; 60 withSoundButton.innerHTML = `${soundOnIcon} <span>${currentTranslations.withSound}</span>`; 61 withSoundButton.onclick = (e) => { e.stopPropagation(); loadMedia(placeholder, false); }; 62 63 const withoutSoundButton = document.createElement('button'); 64 withoutSoundButton.className = 'sound-choice-button'; 65 withoutSoundButton.innerHTML = `${soundOffIcon} <span>${currentTranslations.noSound}</span>`;
66 withoutSoundButton.onclick = (e) => { e.stopPropagation(); loadMedia(placeholder, true); }; 67 68 const playButton = placeholder.querySelector('.play-button'); 69 if (playButton) { playButton.remove(); } 70 71 overlay.appendChild(withSoundButton); 72 overlay.appendChild(withoutSoundButton); 73 placeholder.appendChild(overlay); 74 } 75 }; 76 77 const initializePlaceholder = async (placeholder) => { 78 const provider = placeholder.dataset.provider; 79 const videoId = placeholder.dataset.videoId; 80 81 if (placeholder.querySelector('.lazy-embed-thumbnail')) { return; } 82 83 const thumbnailElement = document.createElement('img'); 84 thumbnailElement.className = 'lazy-embed-thumbnail'; 85 thumbnailElement.alt = 'Vorschaubild'; 86 Object.assign(thumbnailElement.style, { position: 'absolute', top: '0', left: '0', width: '100%', height: '100%', objectFit: 'cover', zIndex: '1' }); 87 88 const playButton = placeholder.querySelector('.play-button'); 89 if (playButton) { Object.assign(playButton.style, { zIndex: '2' }); } 90 91 placeholder.prepend(thumbnailElement); 92 93 thumbnailElement.onerror = function() { 94 thumbnailElement.style.display = 'none'; 95 if (placeholder.querySelector('.thumbnail-error-message')) return; 96 const errorMessage = document.createElement('div'); 97 errorMessage.className = 'thumbnail-error-message'; 98 errorMessage.innerHTML = 'Vorschaubild konnte nicht geladen werden.<br>Möglicherweise blockiert durch Browser- oder Virenschutz.'; 99 Object.assign(errorMessage.style, { position: 'absolute', top: '0', left: '0', width: '100%', height: '100%', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', backgroundColor: '#EAEAEA', color: '#333', textAlign: 'center', padding: '20px 10px', boxSizing: 'border-box', zIndex: '1' }); 100 placeholder.prepend(errorMessage); 101 }; 102 103 try { 104 if (provider === "youtube") { 105 const urlsToTry = [`https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`, `https://i.ytimg.com/vi/${videoId}/sddefault.jpg`, `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`]; 106 let thumbnailUrl = urlsToTry[urlsToTry.length - 1]; 107 for (const url of urlsToTry) { try { const response = await fetch(url, { method: 'HEAD' }); if (response.ok) { thumbnailUrl = url; break; } } catch (e) {} } 108 thumbnailElement.src = thumbnailUrl; 109 } else if (provider === "vimeo") { 110 const response = await fetch(`https://vimeo.com/api/oembed.json?url=https://vimeo.com/${videoId}`); 111 if (response.ok) { 112 const data = await response.json(); 113 if (data.thumbnail_url) { thumbnailElement.src = data.thumbnail_url.replace(/_\d+x?\d*/, ''); } 114 else { thumbnailElement.onerror(); } 115 } else { thumbnailElement.onerror(); } 116 } else if (provider === "soundcloud") { 117 const trackUrl = placeholder.dataset.trackUrl; 118 if (trackUrl) { 119 const oembedUrl = `https://soundcloud.com/oembed?url=${encodeURIComponent(trackUrl)}&format=json`; 120 const oembedResponse = await fetch(oembedUrl); 121 if (oembedResponse.ok) { 122 const oembedData = await oembedResponse.json(); 123 if (oembedData.thumbnail_url) { thumbnailElement.src = oembedData.thumbnail_url; } 124 else { thumbnailElement.onerror(); } 125 } 126 } 127 } 128 } catch (error) { thumbnailElement.onerror(); } 129 }; 130 131 if ("IntersectionObserver" in window) { 132 const observer = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (entry.isIntersecting) { const placeholder = entry.target; initializePlaceholder(placeholder); obs.unobserve(placeholder); } }); });
133 lazyPlaceholders.forEach(placeholder => { observer.observe(placeholder); placeholder.addEventListener('click', handleInteraction, { once: true }); }); 134 } else { 135 lazyPlaceholders.forEach(placeholder => { initializePlaceholder(placeholder); placeholder.addEventListener('click', handleInteraction, { once: true }); }); 136 } 137});
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.