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