diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 50736a3..eabe967 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,17 +1,22 @@ module.exports = { + root: true, env: { - browser: false, - es2021: true + browser: true, + es2020: true }, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], extends: [ - 'standard-with-typescript', + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', 'prettier' ], overrides: [ ], parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', + sourceType: 'script', project: ['tsconfig.json'] }, rules: { diff --git a/dist/lite-yt-embed.d.ts b/dist/lite-yt-embed.d.ts index fdaa57d..831e2e0 100644 --- a/dist/lite-yt-embed.d.ts +++ b/dist/lite-yt-embed.d.ts @@ -35,13 +35,32 @@ declare class LiteYTEmbed extends HTMLElement { private static usesApi?; videoId: string; playlistId: string; + /** + * YouTube poster size + */ size: string; + /** + * Custom JPG poster + */ jpg: string; + /** + * WebP poster toggle or custom WebP poster + */ webp: string; + /** + * API Player instance + */ api?: YT.Player; private isInitialized?; private playLabelText; + /** + * Poster img element + */ private posterEl?; + /** + * Returns an array of attribute names that should be observed for change + */ + static get observedAttributes(): string[]; private static checkWebpSupport; /** * Begin pre-connecting to warm up the iframe load @@ -62,10 +81,17 @@ declare class LiteYTEmbed extends HTMLElement { * See: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements */ connectedCallback(): void; + /** + * Run whenever one of the element's attributes is changed in some way + */ + attributeChangedCallback(name: string, oldValue: string, newValue: string): void; /** * Tries to add iframe via DOM manipulations or YouTube API */ - addIframe(): void; + addIframe(force?: boolean): void; + /** + * Adds JPG (+ WebP) poster image + */ private addPoster; private setPosterDimensions; private tryDownscalingSize; diff --git a/dist/lite-yt-embed.js b/dist/lite-yt-embed.js index 4a00461..3bb68d9 100644 --- a/dist/lite-yt-embed.js +++ b/dist/lite-yt-embed.js @@ -16,14 +16,26 @@ class LiteYTEmbed extends HTMLElement { super(...arguments); this.videoId = ''; this.playlistId = ''; - // YouTube poster size + /** + * YouTube poster size + */ this.size = ''; - // Custom JPG poster + /** + * Custom JPG poster + */ this.jpg = ''; - // WebP poster toggle or custom WebP poster + /** + * WebP poster toggle or custom WebP poster + */ this.webp = ''; this.playLabelText = ''; } + /** + * Returns an array of attribute names that should be observed for change + */ + static get observedAttributes() { + return ['videoid', 'playlistid', 'playlabel', 'showtitle', 'params' /* , 'size', 'jpg', 'webp' */]; + } static checkWebpSupport() { const elem = document.createElement('canvas'); if (elem.getContext?.('2d') != null) { @@ -76,48 +88,34 @@ class LiteYTEmbed extends HTMLElement { window.LiteYTEmbedConfig = window.LiteYTEmbedConfig ?? {}; this.videoId = this.getAttribute('videoid') ?? ''; this.playlistId = this.getAttribute('playlistid') ?? ''; - let playBtnEl = this.querySelector('.lyt-playbtn'); - // A label for the button takes priority over a [playlabel] attribute on the custom-element - this.playLabelText = - playBtnEl?.textContent?.trim() ?? this.getAttribute('playlabel') ?? window.LiteYTEmbedConfig.playLabel ?? 'Play'; - // title in the top left corner - const showTitle = this.getAttribute('showtitle') ?? window.LiteYTEmbedConfig.showTitle ?? 'no'; - if (showTitle === 'yes') { - let titleEl = this.querySelector('.lyt-title'); - if (titleEl == null) { - titleEl = document.createElement('div'); - titleEl.className = 'lyt-title'; - this.append(titleEl); - } - if ((titleEl.textContent ?? '') === '') { - const titleTextEl = document.createElement('span'); - titleTextEl.textContent = this.playLabelText; - titleEl.append(titleTextEl); - } - } - this.addPoster(); // Set up play button, and its visually hidden label + let playBtnEl = this.querySelector('.lyt-playbtn'); if (playBtnEl == null) { playBtnEl = document.createElement('button'); playBtnEl.type = 'button'; playBtnEl.className = 'lyt-playbtn'; this.append(playBtnEl); } - if ((playBtnEl.textContent ?? '') === '') { - const playBtnLabelEl = document.createElement('span'); + let playBtnLabelEl = playBtnEl.querySelector('span'); + if (playBtnLabelEl == null) { + playBtnLabelEl = document.createElement('span'); playBtnLabelEl.className = 'lyt-visually-hidden'; - playBtnLabelEl.textContent = this.playLabelText; playBtnEl.append(playBtnLabelEl); } + playBtnLabelEl.textContent = window.LiteYTEmbedConfig?.playLabel ?? 'Play'; // progressive enhancement - remove `a` link attributes playBtnEl.removeAttribute('href'); playBtnEl.removeAttribute('target'); + this.addPoster(); // On hover (or tap), warm up the TCP connections we're (likely) about to use. this.addEventListener('pointerover', LiteYTEmbed.warmConnections, { once: true }); // Once the user clicks, add the real iframe and drop our play button // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time // We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 - this.addEventListener('click', this.addIframe); + this.addIframe = this.addIframe.bind(this); + this.addEventListener('click', () => { + this.addIframe(); + }); // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, // so they don't autoplay automatically. Instead we must load an additional 2 sequential JS files (1KB + 165KB) (un-br) for the YT Player API @@ -130,11 +128,136 @@ class LiteYTEmbed extends HTMLElement { } this.isInitialized = true; } + /** + * Run whenever one of the element's attributes is changed in some way + */ + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue === newValue) + return; + /** + * When an attribute is updated: + * * `showtitle` - Create or remove title element + * * `playlabel` - Update play button text, title text, poster title/alt attributes, iframe title attribute + * * If iframe isn't loaded yet: + * * * `size` - Update poster, restart fallback? + * * * `jpg` - Update poster, restart fallback? + * * * `webp` - Update poster, restart fallback? + * * * `videoid` - Update poster, restart fallback? + * * * `playlistid` - Do nothing + * * * `params` - Do nothing + * * If iframe is already loaded: + * * * `size` - Do nothing + * * * `jpg` - Do nothing + * * * `webp` - Do nothing + * * * `params` - Re-create iframe + * * * If using DOM: + * * * * `videoid` - If playlistid is empty then update iframe href, otherwise do nothing + * * * * `playlistid` - Update iframe href + * * * If using Player API: + * * * * `videoid` - If playlistid is empty then api.loadVideoById, otherwise do nothing + * * * * `playlistid` - If playlist is empty then api.loadVideoById, otherwise api.loadPlaylist + */ + // Typically contains the name of a video + if (name === 'playlabel') { + const defaultLabelText = window.LiteYTEmbedConfig?.playLabel ?? 'Play'; + this.playLabelText = newValue ?? defaultLabelText; + // update play button hidden text + const playBtnLabelEl = this.querySelector('.lyt-playbtn span'); + if (playBtnLabelEl != null) { + playBtnLabelEl.textContent = this.playLabelText; + } + // update top left title + const titleEl = this.querySelector('.lyt-title span'); + if (titleEl != null) { + // don't show default 'Play' as title + titleEl.textContent = this.playLabelText !== defaultLabelText ? newValue : ''; + } + // update poster alt and title + const posterEl = this.querySelector('.lyt-poster'); + if (posterEl != null) { + posterEl.setAttribute('alt', this.playLabelText); + posterEl.setAttribute('title', this.playLabelText); + } + // update iframe title + const iframe = this.querySelector('iframe'); + if (iframe != null) { + iframe.setAttribute('title', this.playLabelText); + } + return; + } + // 'yes' | 'no' - Shows or hides video title in the top left corner + if (name === 'showtitle') { + const showTitle = newValue ?? window.LiteYTEmbedConfig?.showTitle ?? 'no'; + let titleEl = this.querySelector('.lyt-title'); + if (showTitle === 'yes') { + // create if doesn't exist + if (titleEl == null) { + titleEl = document.createElement('div'); + titleEl.className = 'lyt-title'; + this.append(titleEl); + } + let titleTextEl = titleEl.querySelector('span'); + if (titleTextEl == null) { + titleTextEl = document.createElement('span'); + titleEl.append(titleTextEl); + } + const defaultLabelText = window.LiteYTEmbedConfig?.playLabel ?? 'Play'; + // don't show default 'Play' as title + titleTextEl.textContent = this.playLabelText !== defaultLabelText ? this.playLabelText : ''; + return; + } + // 'no' - remove if exists + if (titleEl != null) { + titleEl.remove(); + } + return; + } + // YouTube video + if (name === 'videoid') { + this.videoId = newValue ?? ''; + if (!this.classList.contains('lyt-activated')) { + // TODO: no iframe - update poster, restart fallback + return; + } + // playlist takes priority over video + if (this.playlistId !== '' || this.videoId === '') + return; + // load new video + this.addIframe(true); + return; + } + // YouTube playlist + if (name === 'playlistid') { + this.playlistId = newValue ?? ''; + if (!this.classList.contains('lyt-activated')) { + // no iframe - do nothing + return; + } + // no playlist and no video = do nothing + if (this.playlistId === '' && this.videoId === '') + return; + // load new playlist or video + this.addIframe(true); + return; + } + // Player parameters / playerVars + if (name === 'params') { + if (!this.classList.contains('lyt-activated')) { + // no iframe - do nothing + return; + } + // recreate iframe + this.api = undefined; + this.querySelector('iframe')?.remove(); + this.addIframe(true); + return; + } + } /** * Tries to add iframe via DOM manipulations or YouTube API */ - addIframe() { - if (this.classList.contains('lyt-activated')) + addIframe(force = false) { + if (!force && this.classList.contains('lyt-activated')) return; this.classList.add('lyt-activated'); const params = new URLSearchParams(this.getAttribute('params') ?? window.LiteYTEmbedConfig?.params ?? ''); @@ -150,25 +273,34 @@ class LiteYTEmbed extends HTMLElement { return; } // via DOM - const iframeEl = document.createElement('iframe'); - iframeEl.width = '560'; - iframeEl.height = '315'; - iframeEl.title = this.playLabelText; - iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; - iframeEl.allowFullscreen = true; - iframeEl.fetchPriority = 'high'; + let iframeEl = this.querySelector('iframe'); + let isNewIframe = false; + if (iframeEl == null) { + isNewIframe = true; + iframeEl = document.createElement('iframe'); + iframeEl.width = '560'; + iframeEl.height = '315'; + iframeEl.title = this.playLabelText; + iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; + iframeEl.allowFullscreen = true; + iframeEl.fetchPriority = 'high'; + this.append(iframeEl); + } if (this.playlistId !== '') { iframeEl.src = `https://www.youtube-nocookie.com/embed/videoseries?list=${this.playlistId}&${params.toString()}`; } else { iframeEl.src = `https://www.youtube-nocookie.com/embed/${this.videoId}?${params.toString()}`; } - this.append(iframeEl); // Set focus for a11y iframeEl.focus(); - this.dispatchEvent(new CustomEvent('ready')); + if (isNewIframe) { + this.dispatchEvent(new CustomEvent('ready')); + } } - // Adds JPG (+ WebP) poster image + /** + * Adds JPG (+ WebP) poster image + */ addPoster() { // TODO: Add fallback for progressively enhanced videos as well if (this.querySelector('.lyt-poster-container') != null) { @@ -187,6 +319,9 @@ class LiteYTEmbed extends HTMLElement { * Anything else is treated like a custom image */ this.webp = this.getAttribute('webp') ?? window.LiteYTEmbedConfig?.webp ?? 'yes'; + // don't create poster if none is specified + if (this.videoId === '' && this.jpg === '') + return; // Check if browser supports WebP if (LiteYTEmbed.supportsWebp === undefined) { LiteYTEmbed.supportsWebp = LiteYTEmbed.checkWebpSupport(); @@ -311,6 +446,19 @@ class LiteYTEmbed extends HTMLElement { } async addYTPlayerIframe(params) { await this.fetchYTPlayerApi(); + if (this.api) { + // Player was already initialized + if (this.playlistId === '') { + this.api.loadVideoById(this.videoId); + } + else { + this.api.loadPlaylist({ + list: this.playlistId, + listType: 'playlist', + }); + } + return; + } const videoPlaceholderEl = document.createElement('div'); this.append(videoPlaceholderEl); const options = { @@ -324,12 +472,12 @@ class LiteYTEmbed extends HTMLElement { }, }, }; - if (this.playlistId !== '') { - params.append('listType', 'playlist'); - params.append('list', this.playlistId); + if (this.playlistId === '') { + options.videoId = this.videoId; } else { - options.videoId = this.videoId; + params.append('listType', 'playlist'); + params.append('list', this.playlistId); } options.playerVars = Object.fromEntries(params.entries()); // eslint-disable-next-line no-new diff --git a/dist/lite-yt-embed.min.js b/dist/lite-yt-embed.min.js index ae287c7..a613b4d 100644 --- a/dist/lite-yt-embed.min.js +++ b/dist/lite-yt-embed.min.js @@ -1,2 +1,2 @@ -"use strict";class LiteYTEmbed extends HTMLElement{constructor(){super(...arguments),this.videoId="",this.playlistId="",this.size="",this.jpg="",this.webp="",this.t=""}static i(){var t=document.createElement("canvas");return null!=t.getContext?.("2d")&&0===t.toDataURL("image/webp").indexOf("data:image/webp")}static h(){LiteYTEmbed.o||(LiteYTEmbed.l("preconnect","https://www.youtube-nocookie.com"),LiteYTEmbed.l("preconnect","https://www.google.com"),LiteYTEmbed.l("preconnect","https://googleads.g.doubleclick.net"),LiteYTEmbed.l("preconnect","https://static.doubleclick.net"),LiteYTEmbed.o=!0)}static l(t,i){var s=document.createElement("link");s.rel=t,s.href=i,document.head.append(s)}connectedCallback(){if(this.isConnected&&!0!==this.m){window.LiteYTEmbedConfig=window.LiteYTEmbedConfig??{},this.videoId=this.getAttribute("videoid")??"",this.playlistId=this.getAttribute("playlistid")??"";let t=this.querySelector(".lyt-playbtn");var i;if(this.t=t?.textContent?.trim()??this.getAttribute("playlabel")??window.LiteYTEmbedConfig.playLabel??"Play","yes"===(this.getAttribute("showtitle")??window.LiteYTEmbedConfig.showTitle??"no")){let t=this.querySelector(".lyt-title");null==t&&((t=document.createElement("div")).className="lyt-title",this.append(t)),""===(t.textContent??"")&&((i=document.createElement("span")).textContent=this.t,t.append(i))}this.p(),null==t&&((t=document.createElement("button")).type="button",t.className="lyt-playbtn",this.append(t)),""===(t.textContent??"")&&((i=document.createElement("span")).className="lyt-visually-hidden",i.textContent=this.t,t.append(i)),t.removeAttribute("href"),t.removeAttribute("target"),this.addEventListener("pointerover",LiteYTEmbed.h,{once:!0}),this.addEventListener("click",this.addIframe),void 0===LiteYTEmbed.u&&(LiteYTEmbed.u=window.LiteYTEmbedConfig.forceApi??(navigator.vendor.includes("Apple")||navigator.userAgent.includes("Mobi"))),this.m=!0}}addIframe(){var t,i;this.classList.contains("lyt-activated")||(this.classList.add("lyt-activated"),(t=new URLSearchParams(this.getAttribute("params")??window.LiteYTEmbedConfig?.params??"")).append("autoplay","1"),t.append("playsinline","1"),""!==window.location.host&&t.append("origin",window.location.origin),!0===LiteYTEmbed.u?this.v(t):((i=document.createElement("iframe")).width="560",i.height="315",i.title=this.t,i.allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",i.allowFullscreen=!0,i.fetchPriority="high",""!==this.playlistId?i.src=`https://www.youtube-nocookie.com/embed/videoseries?list=${this.playlistId}&`+t.toString():i.src=`https://www.youtube-nocookie.com/embed/${this.videoId}?`+t.toString(),this.append(i),i.focus(),this.dispatchEvent(new CustomEvent("ready"))))}p(){var t,i;null==this.querySelector(".lyt-poster-container")&&(this.size=this.getAttribute("size")??window.LiteYTEmbedConfig?.size??"hq",["mq","hq","sd","maxres"].includes(this.size))&&(this.jpg=this.getAttribute("jpg")??"",this.webp=this.getAttribute("webp")??window.LiteYTEmbedConfig?.webp??"yes",void 0===LiteYTEmbed.g&&(LiteYTEmbed.g=LiteYTEmbed.i()),LiteYTEmbed.g||(this.webp="no"),(t=document.createElement("picture")).className="lyt-poster-container","no"!==this.webp&&((i=document.createElement("source")).setAttribute("type","image/webp"),i.setAttribute("srcset","yes"===this.webp?`https://i.ytimg.com/vi_webp/${this.videoId}/${this.size}default.webp`:this.webp),t.appendChild(i)),this.T=document.createElement("img"),this.T.setAttribute("decoding","async"),this.T.setAttribute("loading","lazy"),this.L(),this.T.setAttribute("src",""!==this.jpg?this.jpg:`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`),this.T.setAttribute("alt",this.t),this.T.setAttribute("title",this.t),this.T.className="lyt-poster",!0===window.LiteYTEmbedConfig?.useFallback&&(this.classList.add("lyt-poster-hidden"),this.Y=this.Y.bind(this),this.$=this.$.bind(this),this.T.complete&&this.Y(),this.T.addEventListener("load",this.Y),this.T.addEventListener("error",this.$)),t.appendChild(this.T),this.insertBefore(t,this.firstChild))}L(){let t,i;switch(this.size){case"mq":t=320,i=180;break;case"hq":t=480,i=360;break;case"sd":t=640,i=480;break;default:t=1280,i=720}this.T?.setAttribute("width",t.toString()),this.T?.setAttribute("height",i.toString())}k(){switch(this.size){case"maxres":return this.size="sd",!0;case"sd":return this.size="hq",!0}return!1}Y(){(this.T?.naturalWidth??0)<=120?this.$():this.classList.remove("lyt-poster-hidden")}$(){var t=this.querySelector("source");if(null!=t)return"yes"!==this.webp?(this.webp="yes",void t.setAttribute("srcset",`https://i.ytimg.com/vi_webp/${this.videoId}/${this.size}default.webp`)):this.k()?(this.L(),void t.setAttribute("srcset",`https://i.ytimg.com/vi_webp/${this.videoId}/${this.size}default.webp`)):(t.remove(),this.webp="no",void this.T?.setAttribute("src",`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`));""!==this.jpg?(this.jpg="",this.T?.setAttribute("src",`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`)):this.k()&&(this.L(),this.T?.setAttribute("src",`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`))}async v(t){await this.P();var i=document.createElement("div"),s=(this.append(i),{width:"100%",host:"https://www.youtube-nocookie.com",events:{onReady:t=>{this.api=t.target,this.api.playVideo(),this.dispatchEvent(new CustomEvent("ready"))}}});""!==this.playlistId?(t.append("listType","playlist"),t.append("list",this.playlistId)):s.videoId=this.videoId,s.playerVars=Object.fromEntries(t.entries()),new YT.Player(i,s)}P(){if(void 0===window.YT)return new Promise((t,i)=>{var s=document.createElement("script");s.src="https://www.youtube.com/iframe_api",s.async=!0,s.onload=()=>{window.YT?.ready(t)},s.onerror=i,this.append(s)})}}LiteYTEmbed.o=!1,customElements.define("lite-youtube",LiteYTEmbed); +"use strict";class LiteYTEmbed extends HTMLElement{constructor(){super(...arguments),this.videoId="",this.playlistId="",this.size="",this.jpg="",this.webp="",this.t=""}static get observedAttributes(){return["videoid","playlistid","playlabel","showtitle","params"]}static i(){var t=document.createElement("canvas");return null!=t.getContext?.("2d")&&0===t.toDataURL("image/webp").indexOf("data:image/webp")}static h(){LiteYTEmbed.o||(LiteYTEmbed.l("preconnect","https://www.youtube-nocookie.com"),LiteYTEmbed.l("preconnect","https://www.google.com"),LiteYTEmbed.l("preconnect","https://googleads.g.doubleclick.net"),LiteYTEmbed.l("preconnect","https://static.doubleclick.net"),LiteYTEmbed.o=!0)}static l(t,i){var s=document.createElement("link");s.rel=t,s.href=i,document.head.append(s)}connectedCallback(){if(this.isConnected&&!0!==this.m){window.LiteYTEmbedConfig=window.LiteYTEmbedConfig??{},this.videoId=this.getAttribute("videoid")??"",this.playlistId=this.getAttribute("playlistid")??"";let t=this.querySelector(".lyt-playbtn"),i=(null==t&&((t=document.createElement("button")).type="button",t.className="lyt-playbtn",this.append(t)),t.querySelector("span"));null==i&&((i=document.createElement("span")).className="lyt-visually-hidden",t.append(i)),i.textContent=window.LiteYTEmbedConfig?.playLabel??"Play",t.removeAttribute("href"),t.removeAttribute("target"),this.p(),this.addEventListener("pointerover",LiteYTEmbed.h,{once:!0}),this.addIframe=this.addIframe.bind(this),this.addEventListener("click",()=>{this.addIframe()}),void 0===LiteYTEmbed.u&&(LiteYTEmbed.u=window.LiteYTEmbedConfig.forceApi??(navigator.vendor.includes("Apple")||navigator.userAgent.includes("Mobi"))),this.m=!0}}attributeChangedCallback(t,s,e){if(s!==e)if("playlabel"===t)s=window.LiteYTEmbedConfig?.playLabel??"Play",this.t=e??s,null!=(h=this.querySelector(".lyt-playbtn span"))&&(h.textContent=this.t),null!=(h=this.querySelector(".lyt-title span"))&&(h.textContent=this.t!==s?e:""),null!=(h=this.querySelector(".lyt-poster"))&&(h.setAttribute("alt",this.t),h.setAttribute("title",this.t)),null!=(s=this.querySelector("iframe"))&&s.setAttribute("title",this.t);else{if("showtitle"!==t)return"videoid"===t?(this.videoId=e??"",this.classList.contains("lyt-activated")&&""===this.playlistId&&""!==this.videoId?void this.addIframe(!0):void 0):"playlistid"===t?(this.playlistId=e??"",!this.classList.contains("lyt-activated")||""===this.playlistId&&""===this.videoId?void 0:void this.addIframe(!0)):void("params"===t&&this.classList.contains("lyt-activated")&&(this.api=void 0,this.querySelector("iframe")?.remove(),this.addIframe(!0)));{var h=e??window.LiteYTEmbedConfig?.showTitle??"no";let i=this.querySelector(".lyt-title");if("yes"!==h)null!=i&&i.remove();else{null==i&&((i=document.createElement("div")).className="lyt-title",this.append(i));let t=i.querySelector("span");null==t&&(t=document.createElement("span"),i.append(t));s=window.LiteYTEmbedConfig?.playLabel??"Play";t.textContent=this.t!==s?this.t:""}}}}addIframe(s=!1){if(s||!this.classList.contains("lyt-activated")){this.classList.add("lyt-activated");s=new URLSearchParams(this.getAttribute("params")??window.LiteYTEmbedConfig?.params??"");if(s.append("autoplay","1"),s.append("playsinline","1"),""!==window.location.host&&s.append("origin",window.location.origin),!0===LiteYTEmbed.u)this.v(s);else{let t=this.querySelector("iframe"),i=!1;null==t&&(i=!0,(t=document.createElement("iframe")).width="560",t.height="315",t.title=this.t,t.allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",t.allowFullscreen=!0,t.fetchPriority="high",this.append(t)),""!==this.playlistId?t.src=`https://www.youtube-nocookie.com/embed/videoseries?list=${this.playlistId}&`+s.toString():t.src=`https://www.youtube-nocookie.com/embed/${this.videoId}?`+s.toString(),t.focus(),i&&this.dispatchEvent(new CustomEvent("ready"))}}}p(){var t,i;null==this.querySelector(".lyt-poster-container")&&(this.size=this.getAttribute("size")??window.LiteYTEmbedConfig?.size??"hq",["mq","hq","sd","maxres"].includes(this.size))&&(this.jpg=this.getAttribute("jpg")??"",this.webp=this.getAttribute("webp")??window.LiteYTEmbedConfig?.webp??"yes",""===this.videoId&&""===this.jpg||(void 0===LiteYTEmbed.g&&(LiteYTEmbed.g=LiteYTEmbed.i()),LiteYTEmbed.g||(this.webp="no"),(t=document.createElement("picture")).className="lyt-poster-container","no"!==this.webp&&((i=document.createElement("source")).setAttribute("type","image/webp"),i.setAttribute("srcset","yes"===this.webp?`https://i.ytimg.com/vi_webp/${this.videoId}/${this.size}default.webp`:this.webp),t.appendChild(i)),this.T=document.createElement("img"),this.T.setAttribute("decoding","async"),this.T.setAttribute("loading","lazy"),this.L(),this.T.setAttribute("src",""!==this.jpg?this.jpg:`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`),this.T.setAttribute("alt",this.t),this.T.setAttribute("title",this.t),this.T.className="lyt-poster",!0===window.LiteYTEmbedConfig?.useFallback&&(this.classList.add("lyt-poster-hidden"),this.Y=this.Y.bind(this),this.$=this.$.bind(this),this.T.complete&&this.Y(),this.T.addEventListener("load",this.Y),this.T.addEventListener("error",this.$)),t.appendChild(this.T),this.insertBefore(t,this.firstChild)))}L(){let t,i;switch(this.size){case"mq":t=320,i=180;break;case"hq":t=480,i=360;break;case"sd":t=640,i=480;break;default:t=1280,i=720}this.T?.setAttribute("width",t.toString()),this.T?.setAttribute("height",i.toString())}k(){switch(this.size){case"maxres":return this.size="sd",!0;case"sd":return this.size="hq",!0}return!1}Y(){(this.T?.naturalWidth??0)<=120?this.$():this.classList.remove("lyt-poster-hidden")}$(){var t=this.querySelector("source");if(null!=t)return"yes"!==this.webp?(this.webp="yes",void t.setAttribute("srcset",`https://i.ytimg.com/vi_webp/${this.videoId}/${this.size}default.webp`)):this.k()?(this.L(),void t.setAttribute("srcset",`https://i.ytimg.com/vi_webp/${this.videoId}/${this.size}default.webp`)):(t.remove(),this.webp="no",void this.T?.setAttribute("src",`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`));""!==this.jpg?(this.jpg="",this.T?.setAttribute("src",`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`)):this.k()&&(this.L(),this.T?.setAttribute("src",`https://i.ytimg.com/vi/${this.videoId}/${this.size}default.jpg`))}async v(t){var i,s;await this.P(),this.api?""===this.playlistId?this.api.loadVideoById(this.videoId):this.api.loadPlaylist({list:this.playlistId,listType:"playlist"}):(i=document.createElement("div"),this.append(i),s={width:"100%",host:"https://www.youtube-nocookie.com",events:{onReady:t=>{this.api=t.target,this.api.playVideo(),this.dispatchEvent(new CustomEvent("ready"))}}},""===this.playlistId?s.videoId=this.videoId:(t.append("listType","playlist"),t.append("list",this.playlistId)),s.playerVars=Object.fromEntries(t.entries()),new YT.Player(i,s))}P(){if(void 0===window.YT)return new Promise((t,i)=>{var s=document.createElement("script");s.src="https://www.youtube.com/iframe_api",s.async=!0,s.onload=()=>{window.YT?.ready(t)},s.onerror=i,this.append(s)})}}LiteYTEmbed.o=!1,customElements.define("lite-youtube",LiteYTEmbed); //# sourceMappingURL=lite-yt-embed.min.js.map \ No newline at end of file diff --git a/dist/lite-yt-embed.min.js.map b/dist/lite-yt-embed.min.js.map index 5281ba5..c51d0dd 100644 --- a/dist/lite-yt-embed.min.js.map +++ b/dist/lite-yt-embed.min.js.map @@ -1 +1 @@ -{"version":3,"file":"lite-yt-embed.js","sources":["0"],"names":["LiteYTEmbed","HTMLElement","constructor","super","arguments","this","videoId","playlistId","size","jpg","webp","playLabelText","i","elem","document","createElement","getContext","toDataURL","indexOf","h","preconnected","addPrefetch","l","kind","url","linkEl","rel","href","head","append","connectedCallback","isConnected","isInitialized","window","LiteYTEmbedConfig","getAttribute","let","playBtnEl","querySelector","playBtnLabelEl","textContent","trim","playLabel","showTitle","titleEl","className","titleTextEl","addPoster","type","removeAttribute","addEventListener","warmConnections","once","addIframe","undefined","usesApi","forceApi","navigator","vendor","includes","userAgent","params","iframeEl","classList","contains","add","URLSearchParams","location","host","origin","addYTPlayerIframe","width","height","title","allow","allowFullscreen","fetchPriority","src","toString","focus","dispatchEvent","CustomEvent","p","posterContainer","source","supportsWebp","checkWebpSupport","setAttribute","appendChild","posterEl","setPosterDimensions","useFallback","onPosterLoad","bind","onPosterError","complete","insertBefore","firstChild","L","k","Y","naturalWidth","remove","$","tryDownscalingSize","v","await","fetchYTPlayerApi","videoPlaceholderEl","options","events","onReady","api","event","target","playVideo","playerVars","Object","fromEntries","entries","YT","Player","P","Promise","resolve","reject","el","async","onload","ready","onerror","customElements","define"],"mappings":"AAAA,mBAaMA,oBAAoBC,YACtBC,cACIC,MAAM,GAAGC,SAAS,EAClBC,KAAKC,QAAU,GACfD,KAAKE,WAAa,GAElBF,KAAKG,KAAO,GAEZH,KAAKI,IAAM,GAEXJ,KAAKK,KAAO,GACZL,KAAKM,EAAgB,EACzB,CACAC,WACI,IAAMC,EAAOC,SAASC,cAAc,QAAQ,EAC5C,OAA+B,MAA3BF,EAAKG,aAAa,IAAI,GAC6C,IAA5DH,EAAKI,UAAU,YAAY,EAAEC,QAAQ,iBAAiB,CAGrE,CAUAC,WACQnB,YAAYoB,IAGhBpB,YAAYqB,EAAY,aAAc,kCAAkC,EAExErB,YAAYqB,EAAY,aAAc,wBAAwB,EAE9DrB,YAAYqB,EAAY,aAAc,qCAAqC,EAC3ErB,YAAYqB,EAAY,aAAc,gCAAgC,EACtErB,YAAYoB,EAAe,CAAA,EAC/B,CAIAE,SAAmBC,EAAMC,GACrB,IAAMC,EAASX,SAASC,cAAc,MAAM,EAC5CU,EAAOC,IAAMH,EACbE,EAAOE,KAAOH,EACdV,SAASc,KAAKC,OAAOJ,CAAM,CAC/B,CAKAK,oBAEI,GAAKzB,KAAK0B,aAGiB,CAAA,IAAvB1B,KAAK2B,EAAT,CAGAC,OAAOC,kBAAoBD,OAAOC,mBAAqB,GACvD7B,KAAKC,QAAUD,KAAK8B,aAAa,SAAS,GAAK,GAC/C9B,KAAKE,WAAaF,KAAK8B,aAAa,YAAY,GAAK,GACrDC,IAAIC,EAAYhC,KAAKiC,cAAc,cAAc,EAKjD,IAuBUC,EAtBV,GAJAlC,KAAKM,EACD0B,GAAWG,aAAaC,KAAK,GAAKpC,KAAK8B,aAAa,WAAW,GAAKF,OAAOC,kBAAkBQ,WAAa,OAG5F,SADArC,KAAK8B,aAAa,WAAW,GAAKF,OAAOC,kBAAkBS,WAAa,MACjE,CACrBP,IAAIQ,EAAUvC,KAAKiC,cAAc,YAAY,EAC9B,MAAXM,KACAA,EAAU9B,SAASC,cAAc,KAAK,GAC9B8B,UAAY,YACpBxC,KAAKwB,OAAOe,CAAO,GAEa,MAA/BA,EAAQJ,aAAe,OAClBM,EAAchC,SAASC,cAAc,MAAM,GACrCyB,YAAcnC,KAAKM,EAC/BiC,EAAQf,OAAOiB,CAAW,EAElC,CACAzC,KAAK0C,EAAU,EAEE,MAAbV,KACAA,EAAYvB,SAASC,cAAc,QAAQ,GACjCiC,KAAO,SACjBX,EAAUQ,UAAY,cACtBxC,KAAKwB,OAAOQ,CAAS,GAEa,MAAjCA,EAAUG,aAAe,OACpBD,EAAiBzB,SAASC,cAAc,MAAM,GACrC8B,UAAY,sBAC3BN,EAAeC,YAAcnC,KAAKM,EAClC0B,EAAUR,OAAOU,CAAc,GAGnCF,EAAUY,gBAAgB,MAAM,EAChCZ,EAAUY,gBAAgB,QAAQ,EAElC5C,KAAK6C,iBAAiB,cAAelD,YAAYmD,EAAiB,CAAEC,KAAM,CAAA,CAAK,CAAC,EAIhF/C,KAAK6C,iBAAiB,QAAS7C,KAAKgD,SAAS,EAMjBC,KAAAA,IAAxBtD,YAAYuD,IACZvD,YAAYuD,EACRtB,OAAOC,kBAAkBsB,WACpBC,UAAUC,OAAOC,SAAS,OAAO,GAAKF,UAAUG,UAAUD,SAAS,MAAM,IAEtFtD,KAAK2B,EAAgB,CAAA,CAzDX,CA0Dd,CAIAqB,YACI,IAGMQ,EAaAC,EAhBFzD,KAAK0D,UAAUC,SAAS,eAAe,IAE3C3D,KAAK0D,UAAUE,IAAI,eAAe,GAC5BJ,EAAS,IAAIK,gBAAgB7D,KAAK8B,aAAa,QAAQ,GAAKF,OAAOC,mBAAmB2B,QAAU,EAAE,GACjGhC,OAAO,WAAY,GAAG,EAC7BgC,EAAOhC,OAAO,cAAe,GAAG,EAEH,KAAzBI,OAAOkC,SAASC,MAChBP,EAAOhC,OAAO,SAAUI,OAAOkC,SAASE,MAAM,EAEtB,CAAA,IAAxBrE,YAAYuD,EAEPlD,KAAKiE,EAAkBT,CAAM,IAIhCC,EAAWhD,SAASC,cAAc,QAAQ,GACvCwD,MAAQ,MACjBT,EAASU,OAAS,MAClBV,EAASW,MAAQpE,KAAKM,EACtBmD,EAASY,MAAQ,0EACjBZ,EAASa,gBAAkB,CAAA,EAC3Bb,EAASc,cAAgB,OACD,KAApBvE,KAAKE,WACLuD,EAASe,+DAAiExE,KAAKE,cAAcsD,EAAOiB,SAAS,EAG7GhB,EAASe,8CAAgDxE,KAAKC,WAAWuD,EAAOiB,SAAS,EAE7FzE,KAAKwB,OAAOiC,CAAQ,EAEpBA,EAASiB,MAAM,EACf1E,KAAK2E,cAAc,IAAIC,YAAY,OAAO,CAAC,GAC/C,CAEAC,IAEI,IAuBMC,EAIIC,EA3ByC,MAA/C/E,KAAKiC,cAAc,uBAAuB,IAI9CjC,KAAKG,KAAOH,KAAK8B,aAAa,MAAM,GAAKF,OAAOC,mBAAmB1B,MAAQ,KAEtE,CAAC,KAAM,KAAM,KAAM,UAAUmD,SAAStD,KAAKG,IAAI,KAGpDH,KAAKI,IAAMJ,KAAK8B,aAAa,KAAK,GAAK,GAMvC9B,KAAKK,KAAOL,KAAK8B,aAAa,MAAM,GAAKF,OAAOC,mBAAmBxB,MAAQ,MAE1C4C,KAAAA,IAA7BtD,YAAYqF,IACZrF,YAAYqF,EAAerF,YAAYsF,EAAiB,GAEvDtF,YAAYqF,IACbhF,KAAKK,KAAO,OAEVyE,EAAkBrE,SAASC,cAAc,SAAS,GACxC8B,UAAY,uBAEV,OAAdxC,KAAKK,QACC0E,EAAStE,SAASC,cAAc,QAAQ,GACvCwE,aAAa,OAAQ,YAAY,EACxCH,EAAOG,aAAa,SAAwB,QAAdlF,KAAKK,oCAAgDL,KAAKC,WAAWD,KAAKG,mBAAqBH,KAAKK,IAAI,EACtIyE,EAAgBK,YAAYJ,CAAM,GAEtC/E,KAAKoF,EAAW3E,SAASC,cAAc,KAAK,EAC5CV,KAAKoF,EAASF,aAAa,WAAY,OAAO,EAC9ClF,KAAKoF,EAASF,aAAa,UAAW,MAAM,EAC5ClF,KAAKqF,EAAoB,EACzBrF,KAAKoF,EAASF,aAAa,MAAoB,KAAblF,KAAKI,IAAaJ,KAAKI,8BAAgCJ,KAAKC,WAAWD,KAAKG,iBAAiB,EAC/HH,KAAKoF,EAASF,aAAa,MAAOlF,KAAKM,CAAa,EACpDN,KAAKoF,EAASF,aAAa,QAASlF,KAAKM,CAAa,EACtDN,KAAKoF,EAAS5C,UAAY,aACoB,CAAA,IAA1CZ,OAAOC,mBAAmByD,cAC1BtF,KAAK0D,UAAUE,IAAI,mBAAmB,EACtC5D,KAAKuF,EAAevF,KAAKuF,EAAaC,KAAKxF,IAAI,EAC/CA,KAAKyF,EAAgBzF,KAAKyF,EAAcD,KAAKxF,IAAI,EAC7CA,KAAKoF,EAASM,UACd1F,KAAKuF,EAAa,EAEtBvF,KAAKoF,EAASvC,iBAAiB,OAAQ7C,KAAKuF,CAAY,EACxDvF,KAAKoF,EAASvC,iBAAiB,QAAS7C,KAAKyF,CAAa,GAE9DX,EAAgBK,YAAYnF,KAAKoF,CAAQ,EACzCpF,KAAK2F,aAAab,EAAiB9E,KAAK4F,UAAU,EACtD,CAEAC,IACI9D,IAAImC,EAAOC,EACX,OAAQnE,KAAKG,MACT,IAAK,KACD+D,EAAQ,IACRC,EAAS,IACT,MACJ,IAAK,KACDD,EAAQ,IACRC,EAAS,IACT,MACJ,IAAK,KACDD,EAAQ,IACRC,EAAS,IACT,MAEJ,QACID,EAAQ,KACRC,EAAS,GAEjB,CACAnE,KAAKoF,GAAUF,aAAa,QAAShB,EAAMO,SAAS,CAAC,EACrDzE,KAAKoF,GAAUF,aAAa,SAAUf,EAAOM,SAAS,CAAC,CAC3D,CACAqB,IACI,OAAQ9F,KAAKG,MACT,IAAK,SAED,OADAH,KAAKG,KAAO,KACL,CAAA,EACX,IAAK,KAED,OADAH,KAAKG,KAAO,KACL,CAAA,CACf,CAKA,MAAO,CAAA,CACX,CACA4F,KAES/F,KAAKoF,GAAUY,cAAgB,IAAM,IACtChG,KAAKyF,EAAc,EAGvBzF,KAAK0D,UAAUuC,OAAO,mBAAmB,CAC7C,CACAC,IACI,IAAMnB,EAAS/E,KAAKiC,cAAc,QAAQ,EAC1C,GAAc,MAAV8C,EAEA,MAAkB,QAAd/E,KAAKK,MAELL,KAAKK,KAAO,MAAZL,KACA+E,EAAOG,aAAa,wCAAyClF,KAAKC,WAAWD,KAAKG,kBAAkB,GAIpGH,KAAKmG,EAAmB,GACxBnG,KAAKqF,EAAoB,EAAzBrF,KACA+E,EAAOG,aAAa,wCAAyClF,KAAKC,WAAWD,KAAKG,kBAAkB,IAIxG4E,EAAOkB,OAAO,EACdjG,KAAKK,KAAO,KADZ0E,KAEA/E,KAAKoF,GAAUF,aAAa,gCAAiClF,KAAKC,WAAWD,KAAKG,iBAAiB,GAItF,KAAbH,KAAKI,KAELJ,KAAKI,IAAM,GACXJ,KAAKoF,GAAUF,aAAa,gCAAiClF,KAAKC,WAAWD,KAAKG,iBAAiB,GAInGH,KAAKmG,EAAmB,IACxBnG,KAAKqF,EAAoB,EACzBrF,KAAKoF,GAAUF,aAAa,gCAAiClF,KAAKC,WAAWD,KAAKG,iBAAiB,EAI3G,CACAiG,QAAwB5C,GACpB6C,MAAMrG,KAAKsG,EAAiB,EAC5B,IAAMC,EAAqB9F,SAASC,cAAc,KAAK,EAEjD8F,GADNxG,KAAKwB,OAAO+E,CAAkB,EACd,CACZrC,MAAO,OACPH,KAAM,mCACN0C,OAAQ,CACJC,QAAS,IACL1G,KAAK2G,IAAMC,EAAMC,OACjB7G,KAAK2G,IAAIG,UAAU,EACnB9G,KAAK2E,cAAc,IAAIC,YAAY,OAAO,CAAC,CAC/C,CACJ,CACJ,GACwB,KAApB5E,KAAKE,YACLsD,EAAOhC,OAAO,WAAY,UAAU,EACpCgC,EAAOhC,OAAO,OAAQxB,KAAKE,UAAU,GAGrCsG,EAAQvG,QAAUD,KAAKC,QAE3BuG,EAAQO,WAAaC,OAAOC,YAAYzD,EAAO0D,QAAQ,CAAC,EAExD,IAAIC,GAAGC,OAAOb,EAAoBC,CAAO,CAC7C,CAIAa,IACI,GAAkBpE,KAAAA,IAAdrB,OAAOuF,GAEX,OAAO,IAAIG,QAAQ,CAACC,EAASC,KACzB,IAAMC,EAAKhH,SAASC,cAAc,QAAQ,EAC1C+G,EAAGjD,IAAM,qCACTiD,EAAGC,MAAQ,CAAA,EACXD,EAAGE,OAAS,KACR/F,OAAOuF,IAAIS,MAAML,CAAO,CAC5B,EACAE,EAAGI,QAAUL,EACbxH,KAAKwB,OAAOiG,CAAE,CAClB,CAAC,CACL,CACJ,CACA9H,YAAYoB,EAAe,CAAA,EAC3B+G,eAAeC,OAAO,eAAgBpI,WAAW"} \ No newline at end of file +{"version":3,"file":"lite-yt-embed.js","sources":["0"],"names":["LiteYTEmbed","HTMLElement","constructor","super","arguments","this","videoId","playlistId","size","jpg","webp","playLabelText","observedAttributes","i","elem","document","createElement","getContext","toDataURL","indexOf","h","preconnected","addPrefetch","l","kind","url","linkEl","rel","href","head","append","connectedCallback","isConnected","isInitialized","window","LiteYTEmbedConfig","getAttribute","let","playBtnEl","querySelector","playBtnLabelEl","type","className","textContent","playLabel","removeAttribute","addPoster","addEventListener","warmConnections","once","addIframe","bind","undefined","usesApi","forceApi","navigator","vendor","includes","userAgent","attributeChangedCallback","name","oldValue","newValue","defaultLabelText","titleEl","posterEl","setAttribute","iframe","classList","contains","api","remove","showTitle","titleTextEl","force","add","params","URLSearchParams","location","host","origin","addYTPlayerIframe","iframeEl","isNewIframe","width","height","title","allow","allowFullscreen","fetchPriority","src","toString","focus","dispatchEvent","CustomEvent","p","posterContainer","source","supportsWebp","checkWebpSupport","appendChild","setPosterDimensions","useFallback","onPosterLoad","onPosterError","complete","insertBefore","firstChild","L","k","Y","naturalWidth","$","tryDownscalingSize","v","videoPlaceholderEl","options","await","fetchYTPlayerApi","loadVideoById","loadPlaylist","list","listType","events","onReady","event","target","playVideo","playerVars","Object","fromEntries","entries","YT","Player","P","Promise","resolve","reject","el","async","onload","ready","onerror","customElements","define"],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index 426ab1f..2782e9d 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "cleanup": "node ./scripts/cleanup.mjs", "css": "node ./scripts/process-css.mjs", "js": "node ./scripts/process-js.mjs", - "build": "npm run cleanup && tsc && npm run css && npm run js" + "build": "npm run cleanup && tsc && npm run css && npm run js", + "lint": "eslint --ext .ts ./src" } } diff --git a/src/lite-yt-embed.ts b/src/lite-yt-embed.ts index b385558..47eb1eb 100644 --- a/src/lite-yt-embed.ts +++ b/src/lite-yt-embed.ts @@ -38,21 +38,38 @@ class LiteYTEmbed extends HTMLElement { private static preconnected = false; private static usesApi?: boolean; - public videoId: string = ''; - public playlistId: string = ''; - // YouTube poster size - public size: string = ''; - // Custom JPG poster - public jpg: string = ''; - // WebP poster toggle or custom WebP poster - public webp: string = ''; - // API Player for this video + public videoId = ''; + public playlistId = ''; + /** + * YouTube poster size + */ + public size = ''; + /** + * Custom JPG poster + */ + public jpg = ''; + /** + * WebP poster toggle or custom WebP poster + */ + public webp = ''; + /** + * API Player instance + */ public api?: YT.Player; private isInitialized?: boolean; - private playLabelText: string = ''; - // Poster img element + private playLabelText = ''; + /** + * Poster img element + */ private posterEl?: HTMLImageElement; + /** + * Returns an array of attribute names that should be observed for change + */ + public static get observedAttributes(): string[] { + return ['videoid', 'playlistid', 'playlabel', 'showtitle', 'params' /* , 'size', 'jpg', 'webp' */]; + } + private static checkWebpSupport(): boolean { const elem = document.createElement('canvas'); @@ -101,12 +118,12 @@ class LiteYTEmbed extends HTMLElement { * Invoked each time the custom element is appended into a document-connected element * See: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements */ - connectedCallback(): void { + public connectedCallback(): void { // connectedCallback may be called once the element is no longer connected, use Node.isConnected to make sure if (!this.isConnected) return; // make sure that the element is not being processed more than once - if(this.isInitialized === true) return; + if (this.isInitialized === true) return; // init global config object if it doesn't exist window.LiteYTEmbedConfig = window.LiteYTEmbedConfig ?? {}; @@ -114,53 +131,38 @@ class LiteYTEmbed extends HTMLElement { this.videoId = this.getAttribute('videoid') ?? ''; this.playlistId = this.getAttribute('playlistid') ?? ''; - let playBtnEl: HTMLButtonElement | null = this.querySelector('.lyt-playbtn'); - // A label for the button takes priority over a [playlabel] attribute on the custom-element - this.playLabelText = - playBtnEl?.textContent?.trim() ?? this.getAttribute('playlabel') ?? window.LiteYTEmbedConfig.playLabel ?? 'Play'; - - // title in the top left corner - const showTitle = this.getAttribute('showtitle') ?? window.LiteYTEmbedConfig.showTitle ?? 'no'; - if (showTitle === 'yes') { - let titleEl = this.querySelector('.lyt-title'); - if (titleEl == null) { - titleEl = document.createElement('div'); - titleEl.className = 'lyt-title'; - this.append(titleEl); - } - if ((titleEl.textContent ?? '') === '') { - const titleTextEl = document.createElement('span'); - titleTextEl.textContent = this.playLabelText; - titleEl.append(titleTextEl); - } - } - - this.addPoster(); - // Set up play button, and its visually hidden label + let playBtnEl: HTMLButtonElement | null = this.querySelector('.lyt-playbtn'); if (playBtnEl == null) { playBtnEl = document.createElement('button'); playBtnEl.type = 'button'; playBtnEl.className = 'lyt-playbtn'; this.append(playBtnEl); } - if ((playBtnEl.textContent ?? '') === '') { - const playBtnLabelEl = document.createElement('span'); + let playBtnLabelEl = playBtnEl.querySelector('span'); + if (playBtnLabelEl == null) { + playBtnLabelEl = document.createElement('span'); playBtnLabelEl.className = 'lyt-visually-hidden'; - playBtnLabelEl.textContent = this.playLabelText; playBtnEl.append(playBtnLabelEl); } + playBtnLabelEl.textContent = window.LiteYTEmbedConfig?.playLabel ?? 'Play'; + // progressive enhancement - remove `a` link attributes playBtnEl.removeAttribute('href'); playBtnEl.removeAttribute('target'); + this.addPoster(); + // On hover (or tap), warm up the TCP connections we're (likely) about to use. this.addEventListener('pointerover', LiteYTEmbed.warmConnections, { once: true }); // Once the user clicks, add the real iframe and drop our play button // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time // We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 - this.addEventListener('click', this.addIframe); + this.addIframe = this.addIframe.bind(this); + this.addEventListener('click', () => { + this.addIframe(); + }); // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, @@ -176,11 +178,160 @@ class LiteYTEmbed extends HTMLElement { this.isInitialized = true; } + /** + * Run whenever one of the element's attributes is changed in some way + */ + public attributeChangedCallback(name: string, oldValue: string, newValue: string): void { + if (oldValue === newValue) return; + + /** + * When an attribute is updated: + * * `showtitle` - Create or remove title element + * * `playlabel` - Update play button text, title text, poster title/alt attributes, iframe title attribute + * * If iframe isn't loaded yet: + * * * `size` - Update poster, restart fallback? + * * * `jpg` - Update poster, restart fallback? + * * * `webp` - Update poster, restart fallback? + * * * `videoid` - Update poster, restart fallback? + * * * `playlistid` - Do nothing + * * * `params` - Do nothing + * * If iframe is already loaded: + * * * `size` - Do nothing + * * * `jpg` - Do nothing + * * * `webp` - Do nothing + * * * `params` - Re-create iframe + * * * If using DOM: + * * * * `videoid` - If playlistid is empty then update iframe href, otherwise do nothing + * * * * `playlistid` - Update iframe href + * * * If using Player API: + * * * * `videoid` - If playlistid is empty then api.loadVideoById, otherwise do nothing + * * * * `playlistid` - If playlist is empty then api.loadVideoById, otherwise api.loadPlaylist + */ + + // Typically contains the name of a video + if (name === 'playlabel') { + const defaultLabelText = window.LiteYTEmbedConfig?.playLabel ?? 'Play'; + this.playLabelText = newValue ?? defaultLabelText; + + // update play button hidden text + const playBtnLabelEl = this.querySelector('.lyt-playbtn span'); + if (playBtnLabelEl != null) { + playBtnLabelEl.textContent = this.playLabelText; + } + + // update top left title + const titleEl = this.querySelector('.lyt-title span'); + if (titleEl != null) { + // don't show default 'Play' as title + titleEl.textContent = this.playLabelText !== defaultLabelText ? newValue : ''; + } + + // update poster alt and title + const posterEl = this.querySelector('.lyt-poster'); + if (posterEl != null) { + posterEl.setAttribute('alt', this.playLabelText); + posterEl.setAttribute('title', this.playLabelText); + } + + // update iframe title + const iframe = this.querySelector('iframe'); + if (iframe != null) { + iframe.setAttribute('title', this.playLabelText); + } + + return; + } + + // 'yes' | 'no' - Shows or hides video title in the top left corner + if (name === 'showtitle') { + const showTitle = newValue ?? window.LiteYTEmbedConfig?.showTitle ?? 'no'; + let titleEl = this.querySelector('.lyt-title'); + + if (showTitle === 'yes') { + // create if doesn't exist + if (titleEl == null) { + titleEl = document.createElement('div'); + titleEl.className = 'lyt-title'; + this.append(titleEl); + } + let titleTextEl = titleEl.querySelector('span'); + if (titleTextEl == null) { + titleTextEl = document.createElement('span'); + titleEl.append(titleTextEl); + } + const defaultLabelText = window.LiteYTEmbedConfig?.playLabel ?? 'Play'; + // don't show default 'Play' as title + titleTextEl.textContent = this.playLabelText !== defaultLabelText ? this.playLabelText : ''; + + return; + } + + // 'no' - remove if exists + if (titleEl != null) { + titleEl.remove(); + } + + return; + } + + // YouTube video + if (name === 'videoid') { + this.videoId = newValue ?? ''; + + if (!this.classList.contains('lyt-activated')) { + // TODO: no iframe - update poster, restart fallback + + return; + } + + // playlist takes priority over video + if (this.playlistId !== '' || this.videoId === '') return; + + // load new video + this.addIframe(true); + + return; + } + + // YouTube playlist + if (name === 'playlistid') { + this.playlistId = newValue ?? ''; + + if (!this.classList.contains('lyt-activated')) { + // no iframe - do nothing + return; + } + + // no playlist and no video = do nothing + if (this.playlistId === '' && this.videoId === '') return; + + // load new playlist or video + this.addIframe(true); + + return; + } + + // Player parameters / playerVars + if (name === 'params') { + if (!this.classList.contains('lyt-activated')) { + // no iframe - do nothing + return; + } + + // recreate iframe + this.api = undefined; + this.querySelector('iframe')?.remove(); + this.addIframe(true); + + return; + } + } + /** * Tries to add iframe via DOM manipulations or YouTube API */ - public addIframe(): void { - if (this.classList.contains('lyt-activated')) return; + public addIframe(force = false): void { + if (!force && this.classList.contains('lyt-activated')) return; this.classList.add('lyt-activated'); const params = new URLSearchParams(this.getAttribute('params') ?? window.LiteYTEmbedConfig?.params ?? ''); @@ -199,27 +350,36 @@ class LiteYTEmbed extends HTMLElement { } // via DOM - const iframeEl = document.createElement('iframe'); - iframeEl.width = '560'; - iframeEl.height = '315'; - iframeEl.title = this.playLabelText; - iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; - iframeEl.allowFullscreen = true; - iframeEl.fetchPriority = 'high'; + let iframeEl = this.querySelector('iframe'); + let isNewIframe = false; + if (iframeEl == null) { + isNewIframe = true; + iframeEl = document.createElement('iframe'); + iframeEl.width = '560'; + iframeEl.height = '315'; + iframeEl.title = this.playLabelText; + iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; + iframeEl.allowFullscreen = true; + iframeEl.fetchPriority = 'high'; + this.append(iframeEl); + } if (this.playlistId !== '') { iframeEl.src = `https://www.youtube-nocookie.com/embed/videoseries?list=${this.playlistId}&${params.toString()}`; } else { iframeEl.src = `https://www.youtube-nocookie.com/embed/${this.videoId}?${params.toString()}`; } - this.append(iframeEl); // Set focus for a11y iframeEl.focus(); - this.dispatchEvent(new CustomEvent('ready')); + if (isNewIframe) { + this.dispatchEvent(new CustomEvent('ready')); + } } - // Adds JPG (+ WebP) poster image + /** + * Adds JPG (+ WebP) poster image + */ private addPoster(): void { // TODO: Add fallback for progressively enhanced videos as well @@ -242,6 +402,9 @@ class LiteYTEmbed extends HTMLElement { */ this.webp = this.getAttribute('webp') ?? window.LiteYTEmbedConfig?.webp ?? 'yes'; + // don't create poster if none is specified + if (this.videoId === '' && this.jpg === '') return; + // Check if browser supports WebP if (LiteYTEmbed.supportsWebp === undefined) { LiteYTEmbed.supportsWebp = LiteYTEmbed.checkWebpSupport(); @@ -396,6 +559,20 @@ class LiteYTEmbed extends HTMLElement { private async addYTPlayerIframe(params: URLSearchParams): Promise { await this.fetchYTPlayerApi(); + if (this.api) { + // Player was already initialized + if (this.playlistId === '') { + this.api.loadVideoById(this.videoId); + } else { + this.api.loadPlaylist({ + list: this.playlistId, + listType: 'playlist', + }); + } + + return; + } + const videoPlaceholderEl = document.createElement('div'); this.append(videoPlaceholderEl); @@ -412,11 +589,11 @@ class LiteYTEmbed extends HTMLElement { }, }; - if (this.playlistId !== '') { + if (this.playlistId === '') { + options.videoId = this.videoId; + } else { params.append('listType', 'playlist'); params.append('list', this.playlistId); - } else { - options.videoId = this.videoId; } options.playerVars = Object.fromEntries(params.entries()); diff --git a/tests/no-empty-poster.html b/tests/no-empty-poster.html new file mode 100644 index 0000000..036fe42 --- /dev/null +++ b/tests/no-empty-poster.html @@ -0,0 +1,17 @@ + + + + lite-youtube-embed - No empty poster + + + + + +

Don't create poster when it's not specified

+ + + + + + + diff --git a/tsconfig.json b/tsconfig.json index fdfcfe8..97ad586 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "lib": ["es2020", "dom", "dom.iterable"], "module": "es2020", "target": "es2020", + "noImplicitAny": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/variants/fallback-poster.html b/variants/fallback-poster.html index 6bdc4ef..9ff6409 100644 --- a/variants/fallback-poster.html +++ b/variants/fallback-poster.html @@ -8,7 +8,14 @@

Fallback poster

-

This video uses fallback for poster but it may take a while.

+

This video uses fallback for poster. It can take a while for YouTube to respond when a requested poster is missing. This is what causes the delay.

+

What you're seeing here is the basically the worst case scenario because we request `maxres` WebP poster for an old video that doesn't have WebP at all and only has `hqdefault.jpg`.

+
    +
  1. `maxresdefault.webp` is missing -> swtich to `sddefault.webp`
  2. +
  3. `sddefault.webp` is missing -> switch to `hqdefault.webp`
  4. +
  5. `hqdefault.webp` is missing -> switch to `hqdefault.jpg`
  6. +
  7. `hqdefault.jpg` exists
  8. +
 var LiteYTEmbedConfig = {
diff --git a/variants/show-title.html b/variants/show-title.html
index 90d08d1..7b2cf04 100644
--- a/variants/show-title.html
+++ b/variants/show-title.html
@@ -10,9 +10,28 @@
 

Show title

Show video title like YouTube does.

- +

+ +

+ + +

+ +