vendor: 7,058 bytes, lines 1-215
1export class LiteYTEmbed extends HTMLElement { 2 constructor() { 3 super(); 4 this.isIframeLoaded = false; 5 this.isPlaylistThumbnailLoaded = false; 6 this.setupDom(); 7 } 8 static get observedAttributes() { 9 return ['videoid', 'playlistid', 'videoplay', 'videotitle']; 10 } 11 connectedCallback() { 12 this.addEventListener('pointerover', () => LiteYTEmbed.warmConnections(this), { 13 once: true, 14 }); 15 this.addEventListener('click', () => this.addIframe()); 16 } 17 get videoId() { 18 return encodeURIComponent(this.getAttribute('videoid') || ''); 19 } 20 set videoId(id) { 21 this.setAttribute('videoid', id); 22 } 23 get playlistId() { 24 return encodeURIComponent(this.getAttribute('playlistid') || ''); 25 } 26 set playlistId(id) { 27 this.setAttribute('playlistid', id); 28 } 29 get videoTitle() { 30 return this.getAttribute('videotitle') || 'Video'; 31 } 32 set videoTitle(title) { 33 this.setAttribute('videotitle', title); 34 } 35 get videoPlay() { 36 return this.getAttribute('videoplay') || 'Play'; 37 } 38 set videoPlay(name) { 39 this.setAttribute('videoplay', name); 40 } 41 get videoStartAt() { 42 return this.getAttribute('videoStartAt') || '0'; 43 } 44 get autoLoad() { 45 return this.hasAttribute('autoload'); 46 } 47 get autoPause() { 48 return this.hasAttribute('autopause'); 49 } 50 get noCookie() { 51 return this.hasAttribute('nocookie'); 52 } 53 get posterQuality() { 54 return this.getAttribute('posterquality') || 'hqdefault'; 55 } 56 get posterLoading() { 57 return (this.getAttribute('posterloading') || 58 'lazy'); 59 } 60 get params() { 61 return `start=${this.videoStartAt}&${this.getAttribute('params')}`; 62 } 63 set params(opts) { 64 this.setAttribute('params', opts); 65 } 66 set posterQuality(opts) { 67 this.setAttribute('posterquality', opts); 68 } 69 get disableNoscript() { 70 return this.hasAttribute('disablenoscript'); 71 } 72 setupDom() { 73 const shadowDom = this.attachShadow({ mode: 'open' }); 74 let nonce = ''; 75 if (window.liteYouTubeNonce) { 76 nonce = `nonce="${window.liteYouTubeNonce}"`; 77 } 78 shadowDom.innerHTML = ` 79 <style ${nonce}> 80 :host { 81 --aspect-ratio: var(--lite-youtube-aspect-ratio, 16 / 9); 82 --aspect-ratio-short: var(--lite-youtube-aspect-ratio-short, 9 / 16); 83 --frame-shadow-visible: var(--lite-youtube-frame-shadow-visible, yes); 84 contain: content; 85 display: block; 86 position: relative; 87 width: 100%; 88 aspect-ratio: var(--aspect-ratio); 89 } 90 91 @media (max-width: 40em) { 92 :host([short]) { 93 aspect-ratio: var(--aspect-ratio-short); 94 } 95 } 96 97 #frame, #fallbackPlaceholder, iframe { 98 position: absolute; 99 width: 100%; 100 height: 100%; 101 left: 0; 102 top: 0; 103 } 104 105 #frame { 106 cursor: pointer; 107 } 108 109 #fallbackPlaceholder, slot[name=image]::slotted(*) { 110 object-fit: cover; 111 width: 100%; 112 height: 100%; 113 } 114 115 @container style(--frame-shadow-visible: yes) { 116 #frame::before { 117 content: ''; 118 display: block; 119 position: absolute; 120 top: 0; 121 background-image: linear-gradient(180deg, #111 -20%, transparent 90%); 122 height: 60px; 123 width: 100%; 124 z-index: 1; 125 } 126 } 127 128 #playButton { 129 width: 68px; 130 height: 48px; 131 background-color: transparent; 132 background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 48"><path d="M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55c-2.93.78-4.63 3.26-5.42 6.19C.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z" fill="red"/><path d="M45 24 27 14v20" fill="white"/></svg>'); 133 z-index: 1; 134 border: 0; 135 border-radius: inherit; 136 } 137 138 #playButton:before { 139 content: ''; 140 border-style: solid; 141 border-width: 11px 0 11px 19px; 142 border-color: transparent transparent transparent #fff; 143 } 144 145 #playButton, 146 #playButton:before { 147 position: absolute; 148 top: 50%; 149 left: 50%; 150 transform: translate3d(-50%, -50%, 0); 151 cursor: inherit; 152 } 153 154 /* Post-click styles */ 155 .activated { 156 cursor: unset; 157 } 158 159 #frame.activated::before, 160 #frame.activated > #playButton { 161 display: none; 162 } 163 </style> 164 <div id="frame"> 165 <picture> 166 <slot name="image"> 167 <source id="webpPlaceholder" type="image/webp"> 168 <source id="jpegPlaceholder" type="image/jpeg"> 169 <img id="fallbackPlaceholder" referrerpolicy="origin" loading="lazy"> 170 </slot> 171 </picture> 172 <button id="playButton" part="playButton"></button> 173 </div> 174 `; 175 this.domRefFrame = shadowDom.querySelector('#frame'); 176 this.domRefImg = { 177 fallback: shadowDom.querySelector('#fallbackPlaceholder'), 178 webp: shadowDom.querySelector('#webpPlaceholder'), 179 jpeg: shadowDom.querySelector('#jpegPlaceholder'), 180 }; 181 this.domRefPlayButton = shadowDom.querySelector('#playButton'); 182 } 183 setupComponent() { 184 const hasImgSlot = this.shadowRoot.querySelector('slot[name=image]'); 185 if (hasImgSlot.assignedNodes().length === 0) { 186 this.initImagePlaceholder(); 187 } 188 this.domRefPlayButton.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); 189 this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`); 190 if (this.autoLoad || this.isYouTubeShort() || this.autoPause) { 191 this.initIntersectionObserver(); 192 } 193 if (!this.disableNoscript) { 194 this.injectSearchNoScript(); 195 } 196 } 197 attributeChangedCallback(name, oldVal, newVal) { 198 if (oldVal !== newVal) { 199 if (name === 'playlistid' && oldVal !== null && oldVal !== newVal) { 200 this.isPlaylistThumbnailLoaded = false; 201 } 202 this.setupComponent(); 203 if (this.domRefFrame.classList.contains('activated')) { 204 this.domRefFrame.classList.remove('activated'); 205 this.shadowRoot.querySelector('iframe').remove(); 206 this.isIframeLoaded = false; 207 } 208 } 209 } 210 injectSearchNoScript() { 211 const eleNoScript = document.createElement('noscript'); 212 this.prepend(eleNoScript); 213 eleNoScript.innerHTML = this.generateIframe(); 214 } 215 generateIframe
215Element(isIntersectionObserver = false) { 216 let autoplay = isIntersectionObserver ? 0 : 1; 217 let autoPause = this.autoPause ? '&enablejsapi=1' : ''; 218 const wantsNoCookie = this.noCookie ? '-nocookie' : ''; 219 let embedTarget; 220 if (this.playlistId) { 221 embedTarget = `?listType=playlist&list=${this.playlistId}&`; 222 } 223 else { 224 embedTarget = `${this.videoId}?`; 225 } 226 if (this.isYouTubeShort()) { 227 this.params = `loop=1&mute=1&modestbranding=1&playsinline=1&rel=0&enablejsapi=1&playlist=${this.videoId}`; 228 autoplay = 1; 229 } 230 const iframe = document.createElement('iframe'); 231 iframe.setAttribute('credentialless', ''); 232 iframe.setAttribute('frameborder', '0'); 233 iframe.title = this.videoTitle; 234 iframe.referrerPolicy = 'strict-origin-when-cross-origin'; 235 iframe.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 236 iframe.allowFullscreen = true; 237 iframe.src = `https://www.youtube${wantsNoCookie}.com/embed/${embedTarget}autoplay=${autoplay}${autoPause}&${this.params}`; 238 return iframe; 239 } 240 generateIframe(isIntersectionObserver = false) { 241 return this.generateIframeElement(isIntersectionObserver).outerHTML; 242 } 243 addIframe(isIntersectionObserver = false) { 244 if (!this.isIframeLoaded) { 245 const iframe = this.generateIframeElement(isIntersectionObserver); 246 this.domRefFrame.appendChild(iframe
vendor: 5,935 bytes, lines 246-392
246); 247 this.domRefFrame.classList.add('activated'); 248 this.isIframeLoaded = true; 249 this.attemptShortAutoPlay(); 250 this.dispatchEvent(new CustomEvent('liteYoutubeIframeLoaded', { 251 detail: { 252 videoId: this.videoId, 253 }, 254 bubbles: true, 255 cancelable: true, 256 })); 257 } 258 } 259 initImagePlaceholder() { 260 if (this.playlistId && !this.videoId) { 261 this.loadPlaylistThumbnail(); 262 } 263 else { 264 this.testPosterImage(); 265 } 266 this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`); 267 this.domRefImg?.fallback?.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`); 268 } 269 async loadPlaylistThumbnail() { 270 if (this.isPlaylistThumbnailLoaded) { 271 return; 272 } 273 this.isPlaylistThumbnailLoaded = true; 274 try { 275 const oEmbedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/playlist?list=${this.playlistId}&format=json`; 276 const response = await fetch(oEmbedUrl); 277 if (!response.ok) { 278 throw new Error(`Failed to fetch playlist thumbnail: ${response.status}`); 279 } 280 const data = await response.json(); 281 if (data.thumbnail_url) { 282 const thumbnailUrl = data.thumbnail_url; 283 const videoIdMatch = thumbnailUrl.match(/\/vi\/([^\/]+)\//); 284 if (videoIdMatch) { 285 const extractedVideoId = videoIdMatch[1]; 286 this.loadThumbnailImages(extractedVideoId); 287 } 288 else { 289 this.domRefImg.fallback.src = thumbnailUrl; 290 this.domRefImg.fallback.loading = this.posterLoading; 291 } 292 } 293 } 294 catch (error) { 295 console.warn('Failed to load playlist thumbnail:', error); 296 } 297 } 298 loadThumbnailImages(videoId) { 299 const posterUrlWebp = `https://i.ytimg.com/vi_webp/${videoId}/${this.posterQuality}.webp`; 300 this.domRefImg.webp.srcset = posterUrlWebp; 301 const posterUrlJpeg = `https://i.ytimg.com/vi/${videoId}/${this.posterQuality}.jpg`; 302 this.domRefImg.jpeg.srcset = posterUrlJpeg; 303 this.domRefImg.fallback.src = posterUrlJpeg; 304 this.domRefImg.fallback.loading = this.posterLoading; 305 } 306 async testPosterImage() { 307 setTimeout(() => { 308 const webpUrl = `https://i.ytimg.com/vi_webp/${this.videoId}/${this.posterQuality}.webp`; 309 const img = new Image(); 310 img.fetchPriority = 'low'; 311 img.referrerPolicy = 'origin'; 312 img.src = webpUrl; 313 img.onload = async (e) => { 314 const target = e.target; 315 const noPoster = target?.naturalHeight == 90 && target?.naturalWidth == 120; 316 if (noPoster) { 317 this.posterQuality = 'hqdefault'; 318 } 319 this.loadThumbnailImages(this.videoId); 320 }; 321 }, 100); 322 } 323 initIntersectionObserver() { 324 const options = { 325 root: null, 326 rootMargin: '0px', 327 threshold: 0, 328 }; 329 const observer = new IntersectionObserver((entries, observer) => { 330 entries.forEach(entry => { 331 if (entry.isIntersecting && !this.isIframeLoaded) { 332 LiteYTEmbed.warmConnections(this); 333 this.addIframe(true); 334 observer.unobserve(this); 335 } 336 }); 337 }, options); 338 observer.observe(this); 339 if (this.autoPause) { 340 const windowPause = new IntersectionObserver((e, o) => { 341 e.forEach(entry => { 342 if (entry.intersectionRatio !== 1) { 343 this.shadowRoot 344 .querySelector('iframe') 345 ?.contentWindow?.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*'); 346 } 347 }); 348 }, { threshold: 1 }); 349 windowPause.observe(this); 350 } 351 } 352 attemptShortAutoPlay() { 353 if (this.isYouTubeShort()) { 354 setTimeout(() => { 355 this.shadowRoot 356 .querySelector('iframe') 357 ?.contentWindow?.postMessage('{"event":"command","func":"' + 'playVideo' + '","args":""}', '*'); 358 }, 2000); 359 } 360 } 361 isYouTubeShort() { 362 return (this.getAttribute('short') === '' && 363 window.matchMedia('(max-width: 40em)').matches); 364 } 365 static addPrefetch(kind, url) { 366 const linkElem = document.createElement('link'); 367 linkElem.rel = kind; 368 linkElem.href = url; 369 linkElem.crossOrigin = 'true'; 370 document.head.append(linkElem); 371 } 372 static warmConnections(context) { 373 if (LiteYTEmbed.isPreconnected || window.liteYouTubeIsPreconnected) 374 return; 375 LiteYTEmbed.addPrefetch('preconnect', 'https://i.ytimg.com/'); 376 LiteYTEmbed.addPrefetch('preconnect', 'https://s.ytimg.com'); 377 if (!context.noCookie) { 378 LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com'); 379 LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 380 LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 381 LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 382 } 383 else { 384 LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 385 } 386 LiteYTEmbed.isPreconnected = true; 387 window.liteYouTubeIsPreconnected = true; 388 } 389} 390LiteYTEmbed.isPreconnected = false; 391customElements.define('lite-youtube', LiteYTEmbed); 392//# sourceMappingURL=lite-youtube.js.map
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.