{"version":3,"sources":["lite-youtube.ts"],"names":["LiteYTEmbed","HTMLElement","constructor","super","this","isIframeLoaded","setupDom","observedAttributes","connectedCallback","addEventListener","warmConnections","once","addIframe","videoId","encodeURIComponent","getAttribute","id","setAttribute","playlistId","videoTitle","title","videoPlay","name","videoStartAt","autoLoad","hasAttribute","autoPause","noCookie","posterQuality","posterLoading","params","opts","shadowDom","attachShadow","mode","let","nonce","window","liteYouTubeNonce","innerHTML","domRefFrame","querySelector","domRefImg","fallback","webp","jpeg","domRefPlayButton","setupComponent","initImagePlaceholder","isYouTubeShort","initIntersectionObserver","attributeChangedCallback","oldVal","newVal","classList","contains","remove","shadowRoot","isIntersectionObserver","autoplay","wantsNoCookie","embedTarget","iframeHTML","insertAdjacentHTML","add","attemptShortAutoPlay","dispatchEvent","CustomEvent","detail","bubbles","cancelable","posterUrlWebp","posterUrlJpeg","loading","srcset","src","IntersectionObserver","entries","observer","forEach","entry","isIntersecting","unobserve","root","rootMargin","threshold","observe","e","o","intersectionRatio","contentWindow","postMessage","setTimeout","matchMedia","matches","addPrefetch","kind","url","linkElem","document","createElement","rel","href","crossOrigin","head","append","context","isPreconnected","liteYouTubeIsPreconnected","customElements","define"],"mappings":"MAgBaA,oBAAoBC,YAY/BC,cACEC,MAAK,EAHCC,KAAAC,eAAiB,CAAA,EAIvBD,KAAKE,SAAQ,CACf,CAEAC,gCACE,MAAO,CAAC,UAAW,aAAc,YAAa,aAChD,CAEAC,oBACEJ,KAAKK,iBACH,cACA,IAAMT,YAAYU,gBAAgBN,IAAI,EACtC,CACEO,KAAM,CAAA,C,CACP,EAGHP,KAAKK,iBAAiB,QAAS,IAAML,KAAKQ,UAAS,CAAE,CACvD,CAEAC,cACE,OAAOC,mBAAmBV,KAAKW,aAAa,SAAS,GAAK,EAAE,CAC9D,CAEAF,YAAYG,GACVZ,KAAKa,aAAa,UAAWD,CAAE,CACjC,CAEAE,iBACE,OAAOJ,mBAAmBV,KAAKW,aAAa,YAAY,GAAK,EAAE,CACjE,CAEAG,eAAeF,GACbZ,KAAKa,aAAa,aAAcD,CAAE,CACpC,CAEAG,iBACE,OAAOf,KAAKW,aAAa,YAAY,GAAK,OAC5C,CAEAI,eAAeC,GACbhB,KAAKa,aAAa,aAAcG,CAAK,CACvC,CAEAC,gBACE,OAAOjB,KAAKW,aAAa,WAAW,GAAK,MAC3C,CAEAM,cAAcC,GACZlB,KAAKa,aAAa,YAAaK,CAAI,CACrC,CAEAC,mBACE,OAAOnB,KAAKW,aAAa,cAAc,GAAK,GAC9C,CAEAS,eACE,OAAOpB,KAAKqB,aAAa,UAAU,CACrC,CAEAC,gBACE,OAAOtB,KAAKqB,aAAa,WAAW,CACtC,CAEAE,eACE,OAAOvB,KAAKqB,aAAa,UAAU,CACrC,CAEAG,oBACE,OAAOxB,KAAKW,aAAa,eAAe,GAAK,WAC/C,CAEAc,oBACE,OACGzB,KAAKW,aAAa,eAAe,GAClC,MAEJ,CAEAe,aACE,eAAgB1B,KAAKmB,gBAAgBnB,KAAKW,aAAa,QAAQ,CACjE,CAEAe,WAAWC,GACT3B,KAAKa,aAAa,SAAUc,CAAI,CAClC,CAKQzB,WACN,IAAM0B,EAAY5B,KAAK6B,aAAa,CAAEC,KAAM,MAAM,CAAE,EACpDC,IAAIC,EAAQ,GACRC,OAAOC,mBACTF,YAAkBC,OAAOC,qBAE3BN,EAAUO;eACCH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA8FXhC,KAAKoC,YAAcR,EAAUS,cAA8B,QAAQ,EACnErC,KAAKsC,UAAY,CACfC,SAAUX,EAAUS,cAAc,sBAAsB,EACxDG,KAAMZ,EAAUS,cAAc,kBAAkB,EAChDI,KAAMb,EAAUS,cAAc,kBAAkB,C,EAElDrC,KAAK0C,iBAAmBd,EAAUS,cAAc,aAAa,CAC/D,CAKQM,iBACN3C,KAAK4C,qBAAoB,EAEzB5C,KAAK0C,iBAAiB7B,aACpB,aACGb,KAAKiB,UAAR,KAAsBjB,KAAKe,UAAY,EAEzCf,KAAKa,aAAa,QAAYb,KAAKiB,UAAR,KAAsBjB,KAAKe,UAAY,GAE9Df,KAAKoB,UAAYpB,KAAK6C,eAAc,GAAM7C,KAAKsB,YACjDtB,KAAK8C,yBAAwB,CAEjC,CAQAC,yBACE7B,EACA8B,EACAC,GAEID,IAAWC,IACbjD,KAAK2C,eAAc,EAGf3C,KAAKoC,YAAYc,UAAUC,SAAS,WAAW,KACjDnD,KAAKoC,YAAYc,UAAUE,OAAO,WAAW,EAC7CpD,KAAKqD,WAAWhB,cAAc,QAAQ,EAAGe,OAAM,EAC/CpD,KAAKC,eAAiB,CAAA,EAG5B,CAMQO,UAAU8C,EAAyB,CAAA,GACzC,GAAI,CAACtD,KAAKC,eAAgB,CAExB8B,IAAIwB,EAAWD,EAAyB,EAAI,EACtCE,EAAgBxD,KAAKuB,SAAW,YAAc,GACpDQ,IAAI0B,EAEFA,EADEzD,KAAKc,sCACkCd,KAAKc,cAE7Bd,KAAKS,QAAR,IAKZT,KAAKsB,YACPtB,KAAK0B,OAAS,iBAIZ1B,KAAK6C,eAAc,IACrB7C,KAAK0B,OAAS,6EAA6E1B,KAAKS,QAChG8C,EAAW,GAGPG;gDACoC1D,KAAKe;;4BAEzByC,eAA2BC,aAAuBF,KAAYvD,KAAK0B;YAEzF1B,KAAKoC,YAAYuB,mBAAmB,YAAaD,CAAU,EAC3D1D,KAAKoC,YAAYc,UAAUU,IAAI,WAAW,EAC1C5D,KAAKC,eAAiB,CAAA,EACtBD,KAAK6D,qBAAoB,EACzB7D,KAAK8D,cACH,IAAIC,YAAY,0BAA2B,CACzCC,OAAQ,CACNvD,QAAST,KAAKS,O,EAEhBwD,QAAS,CAAA,EACTC,WAAY,CAAA,C,CACb,CAAC,CAEN,CACF,CAKQtB,uBACN,IAAMuB,iCAA+CnE,KAAKS,WAAWT,KAAKwB,qBACpE4C,4BAA0CpE,KAAKS,WAAWT,KAAKwB,oBACrExB,KAAKsC,UAAUC,SAAS8B,QAAUrE,KAAKyB,cACvCzB,KAAKsC,UAAUE,KAAK8B,OAASH,EAC7BnE,KAAKsC,UAAUG,KAAK6B,OAASF,EAC7BpE,KAAKsC,UAAUC,SAASgC,IAAMH,EAC9BpE,KAAKsC,UAAUC,SAAS1B,aACtB,aACGb,KAAKiB,UAAR,KAAsBjB,KAAKe,UAAY,EAEzCf,KAAKsC,WAAWC,UAAU1B,aACxB,MACGb,KAAKiB,UAAR,KAAsBjB,KAAKe,UAAY,CAE3C,CAKQ+B,2BAOW,IAAI0B,qBAAqB,CAACC,EAASC,KAClDD,EAAQE,QAAQC,IACVA,EAAMC,gBAAkB,CAAC7E,KAAKC,iBAChCL,YAAYU,gBAAgBN,IAAI,EAChCA,KAAKQ,UAAU,CAAA,CAAI,EACnBkE,EAASI,UAAU9E,IAAI,EAE3B,CAAC,CACH,EAdgB,CACd+E,KAAM,KACNC,WAAY,MACZC,UAAW,C,CAWH,EAEDC,QAAQlF,IAAI,EAIjBA,KAAKsB,WACa,IAAIkD,qBACtB,CAACW,EAAGC,KACFD,EAAER,QAAQC,IACwB,IAA5BA,EAAMS,mBACRrF,KAAKqD,WACFhB,cAAc,QAAQ,GACrBiD,eAAeC,YACf,oDACA,GAAG,CAGX,CAAC,CACH,EACA,CAAEN,UAAW,CAAC,CAAE,EAENC,QAAQlF,IAAI,CAE5B,CAaQ6D,uBACF7D,KAAK6C,eAAc,GACrB2C,WAAW,KACTxF,KAAKqD,WACFhB,cAAc,QAAQ,GACrBiD,eAAeC,YACf,mDACA,GAAG,CAGT,EAAG,GAAI,CAEX,CAOQ1C,iBACN,MACiC,KAA/B7C,KAAKW,aAAa,OAAO,GACzBsB,OAAOwD,WAAW,mBAAmB,EAAEC,OAE3C,CAQQC,mBAAmBC,EAAcC,GACvC,IAAMC,EAAWC,SAASC,cAAc,MAAM,EAC9CF,EAASG,IAAML,EACfE,EAASI,KAAOL,EAChBC,EAASK,YAAc,OACvBJ,SAASK,KAAKC,OAAOP,CAAQ,CAC/B,CAYQxF,uBAAuBgG,GACzB1G,YAAY2G,gBAAkBtE,OAAOuE,4BAGzC5G,YAAY+F,YAAY,aAAc,sBAAsB,EAG5D/F,YAAY+F,YAAY,aAAc,qBAAqB,EAEtDW,EAAQ/E,SAgBX3B,YAAY+F,YAAY,aAAc,kCAAkC,GAbxE/F,YAAY+F,YAAY,aAAc,yBAAyB,EAG/D/F,YAAY+F,YAAY,aAAc,wBAAwB,EAI9D/F,YAAY+F,YACV,aACA,qCAAqC,EAEvC/F,YAAY+F,YAAY,aAAc,gCAAgC,GAKxE/F,YAAY2G,eAAiB,CAAA,EAG7BtE,OAAOuE,0BAA4B,CAAA,EACrC,C,CAlce5G,YAAA2G,eAAiB,CAAA,EAqclCE,eAAeC,OAAO,eAAgB9G,WAAW,SA9cpCA,WA8cqC","file":"lite-youtube.min.js","sourcesContent":["/**\n *\n * The shadowDom / Intersection Observer version of Paul's concept:\n * https://github.com/paulirish/lite-youtube-embed\n *\n * A lightweight YouTube embed. Still should feel the same to the user, just\n * MUCH faster to initialize and paint.\n *\n * Thx to these as the inspiration\n * https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html\n * https://autoplay-youtube-player.glitch.me/\n *\n * Once built it, I also found these (👍👍):\n * https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube\n * https://github.com/Daugilas/lazyYT https://github.com/vb/lazyframe\n */\nexport class LiteYTEmbed extends HTMLElement {\n shadowRoot!: ShadowRoot;\n private domRefFrame!: HTMLDivElement;\n private domRefImg!: {\n fallback: HTMLImageElement;\n webp: HTMLSourceElement;\n jpeg: HTMLSourceElement;\n };\n private domRefPlayButton!: HTMLButtonElement;\n private static isPreconnected = false;\n private isIframeLoaded = false;\n\n constructor() {\n super();\n this.setupDom();\n }\n\n static get observedAttributes(): string[] {\n return ['videoid', 'playlistid', 'videoplay', 'videotitle'];\n }\n\n connectedCallback(): void {\n this.addEventListener(\n 'pointerover',\n () => LiteYTEmbed.warmConnections(this),\n {\n once: true,\n },\n );\n\n this.addEventListener('click', () => this.addIframe());\n }\n\n get videoId(): string {\n return encodeURIComponent(this.getAttribute('videoid') || '');\n }\n\n set videoId(id: string) {\n this.setAttribute('videoid', id);\n }\n\n get playlistId(): string {\n return encodeURIComponent(this.getAttribute('playlistid') || '');\n }\n\n set playlistId(id: string) {\n this.setAttribute('playlistid', id);\n }\n\n get videoTitle(): string {\n return this.getAttribute('videotitle') || 'Video';\n }\n\n set videoTitle(title: string) {\n this.setAttribute('videotitle', title);\n }\n\n get videoPlay(): string {\n return this.getAttribute('videoplay') || 'Play';\n }\n\n set videoPlay(name: string) {\n this.setAttribute('videoplay', name);\n }\n\n get videoStartAt(): string {\n return this.getAttribute('videoStartAt') || '0';\n }\n\n get autoLoad(): boolean {\n return this.hasAttribute('autoload');\n }\n\n get autoPause(): boolean {\n return this.hasAttribute('autopause');\n }\n\n get noCookie(): boolean {\n return this.hasAttribute('nocookie');\n }\n\n get posterQuality(): string {\n return this.getAttribute('posterquality') || 'hqdefault';\n }\n\n get posterLoading(): HTMLImageElement['loading'] {\n return (\n (this.getAttribute('posterloading') as HTMLImageElement['loading']) ||\n 'lazy'\n );\n }\n\n get params(): string {\n return `start=${this.videoStartAt}&${this.getAttribute('params')}`;\n }\n\n set params(opts: string) {\n this.setAttribute('params', opts);\n }\n\n /**\n * Define our shadowDOM for the component\n */\n private setupDom(): void {\n const shadowDom = this.attachShadow({ mode: 'open' });\n let nonce = '';\n if (window.liteYouTubeNonce) {\n nonce = `nonce=\"${window.liteYouTubeNonce}\"`;\n }\n shadowDom.innerHTML = `\n \n
\n \n \n \n \n \n \n \n \n
\n `;\n this.domRefFrame = shadowDom.querySelector('#frame')!;\n this.domRefImg = {\n fallback: shadowDom.querySelector('#fallbackPlaceholder')!,\n webp: shadowDom.querySelector('#webpPlaceholder')!,\n jpeg: shadowDom.querySelector('#jpegPlaceholder')!,\n };\n this.domRefPlayButton = shadowDom.querySelector('#playButton')!;\n }\n\n /**\n * Parse our attributes and fire up some placeholders\n */\n private setupComponent(): void {\n this.initImagePlaceholder();\n\n this.domRefPlayButton.setAttribute(\n 'aria-label',\n `${this.videoPlay}: ${this.videoTitle}`,\n );\n this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`);\n\n if (this.autoLoad || this.isYouTubeShort() || this.autoPause) {\n this.initIntersectionObserver();\n }\n }\n\n /**\n * Lifecycle method that we use to listen for attribute changes to period\n * @param {*} name\n * @param {*} oldVal\n * @param {*} newVal\n */\n attributeChangedCallback(\n name: string,\n oldVal: unknown,\n newVal: unknown,\n ): void {\n if (oldVal !== newVal) {\n this.setupComponent();\n\n // if we have a previous iframe, remove it and the activated class\n if (this.domRefFrame.classList.contains('activated')) {\n this.domRefFrame.classList.remove('activated');\n this.shadowRoot.querySelector('iframe')!.remove();\n this.isIframeLoaded = false;\n }\n }\n }\n\n /**\n * Inject the iframe into the component body\n * @param {boolean} isIntersectionObserver\n */\n private addIframe(isIntersectionObserver = false): void {\n if (!this.isIframeLoaded) {\n // Don't autoplay the intersection observer injection, it's weird\n let autoplay = isIntersectionObserver ? 0 : 1;\n const wantsNoCookie = this.noCookie ? '-nocookie' : '';\n let embedTarget;\n if (this.playlistId) {\n embedTarget = `?listType=playlist&list=${this.playlistId}&`;\n } else {\n embedTarget = `${this.videoId}?`;\n }\n\n // autopause needs the postMessage() in the iframe, so you have to enable\n // the jsapi\n if (this.autoPause) {\n this.params = `enablejsapi=1`;\n }\n\n // Oh wait, you're a YouTube short, so let's try to make you more workable\n if (this.isYouTubeShort()) {\n this.params = `loop=1&mute=1&modestbranding=1&playsinline=1&rel=0&enablejsapi=1&playlist=${this.videoId}`;\n autoplay = 1;\n }\n\n const iframeHTML = `\n`;\n this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML);\n this.domRefFrame.classList.add('activated');\n this.isIframeLoaded = true;\n this.attemptShortAutoPlay();\n this.dispatchEvent(\n new CustomEvent('liteYoutubeIframeLoaded', {\n detail: {\n videoId: this.videoId,\n },\n bubbles: true,\n cancelable: true,\n }),\n );\n }\n }\n\n /**\n * Setup the placeholder image for the component\n */\n private initImagePlaceholder(): void {\n const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/${this.posterQuality}.webp`;\n const posterUrlJpeg = `https://i.ytimg.com/vi/${this.videoId}/${this.posterQuality}.jpg`;\n this.domRefImg.fallback.loading = this.posterLoading;\n this.domRefImg.webp.srcset = posterUrlWebp;\n this.domRefImg.jpeg.srcset = posterUrlJpeg;\n this.domRefImg.fallback.src = posterUrlJpeg;\n this.domRefImg.fallback.setAttribute(\n 'aria-label',\n `${this.videoPlay}: ${this.videoTitle}`,\n );\n this.domRefImg?.fallback?.setAttribute(\n 'alt',\n `${this.videoPlay}: ${this.videoTitle}`,\n );\n }\n\n /**\n * Setup the Intersection Observer to load the iframe when scrolled into view\n */\n private initIntersectionObserver(): void {\n const options = {\n root: null,\n rootMargin: '0px',\n threshold: 0,\n };\n\n const observer = new IntersectionObserver((entries, observer) => {\n entries.forEach(entry => {\n if (entry.isIntersecting && !this.isIframeLoaded) {\n LiteYTEmbed.warmConnections(this);\n this.addIframe(true);\n observer.unobserve(this);\n }\n });\n }, options);\n\n observer.observe(this);\n\n // this needs the iframe loaded, so it has to run post the IO load at the\n // least otherwise things will break\n if (this.autoPause) {\n const windowPause = new IntersectionObserver(\n (e, o) => {\n e.forEach(entry => {\n if (entry.intersectionRatio !== 1) {\n this.shadowRoot\n .querySelector('iframe')\n ?.contentWindow?.postMessage(\n '{\"event\":\"command\",\"func\":\"pauseVideo\",\"args\":\"\"}',\n '*',\n );\n }\n });\n },\n { threshold: 1 },\n );\n windowPause.observe(this);\n }\n }\n\n /**\n * This is a terrible hack to attempt to get YouTube Short-like autoplay on\n * mobile viewports. It's this way because:\n * 1. YouTube's Iframe embed does not offer determinism when loading\n * 2. Attempting to use onYouTubeIframeAPIReady() does not work in 99% of\n * cases\n * 3. You can _technically_ load the Frame API library and do more advanced\n * things, but I don't want to burn the thread of the wire with its\n * shenanigans since this an edge case.\n * @private\n */\n private attemptShortAutoPlay() {\n if (this.isYouTubeShort()) {\n setTimeout(() => {\n this.shadowRoot\n .querySelector('iframe')\n ?.contentWindow?.postMessage(\n '{\"event\":\"command\",\"func\":\"' + 'playVideo' + '\",\"args\":\"\"}',\n '*',\n );\n // for youtube video recording demo\n }, 2000);\n }\n }\n\n /**\n * A hacky attr check and viewport peek to see if we're going to try to enable\n * a more friendly YouTube Short style loading\n * @returns boolean\n */\n private isYouTubeShort(): boolean {\n return (\n this.getAttribute('short') === '' &&\n window.matchMedia('(max-width: 40em)').matches\n );\n }\n\n /**\n * Add a to the head\n * @param {string} kind\n * @param {string} url\n * @param {string} as\n */\n private static addPrefetch(kind: string, url: string): void {\n const linkElem = document.createElement('link');\n linkElem.rel = kind;\n linkElem.href = url;\n linkElem.crossOrigin = 'true';\n document.head.append(linkElem);\n }\n\n /**\n * Begin preconnecting to warm up the iframe load Since the embed's network\n * requests load within its iframe, preload/prefetch'ing them outside the\n * iframe will only cause double-downloads. So, the best we can do is warm up\n * a few connections to origins that are in the critical path.\n *\n * Maybe `` would work, but it's unsupported:\n * http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site\n * Isolation and split caches adding serious complexity.\n */\n private static warmConnections(context: LiteYTEmbed): void {\n if (LiteYTEmbed.isPreconnected || window.liteYouTubeIsPreconnected) return;\n\n // we don't know which image type to preload, so warm the connection\n LiteYTEmbed.addPrefetch('preconnect', 'https://i.ytimg.com/');\n\n // Host that YT uses to serve JS needed by player, per amp-youtube\n LiteYTEmbed.addPrefetch('preconnect', 'https://s.ytimg.com');\n\n if (!context.noCookie) {\n // The iframe document and most of its subresources come right off\n // youtube.com\n LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com');\n\n // The botguard script is fetched off from google.com\n LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com');\n\n // TODO: Not certain if these ad related domains are in the critical path.\n // Could verify with domain-specific throttling.\n LiteYTEmbed.addPrefetch(\n 'preconnect',\n 'https://googleads.g.doubleclick.net',\n );\n LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net');\n } else {\n LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com');\n }\n\n LiteYTEmbed.isPreconnected = true;\n\n // multiple embeds in the same page don't check for each other\n window.liteYouTubeIsPreconnected = true;\n }\n}\n// Register custom element\ncustomElements.define('lite-youtube', LiteYTEmbed);\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lite-youtube': LiteYTEmbed;\n }\n interface Window {\n liteYouTubeNonce: string;\n liteYouTubeIsPreconnected: boolean;\n }\n}\n"]}