From a652608f7fe8f9309dffed9e8c602594b8534933 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Fri, 17 Jul 2020 13:23:38 -0700 Subject: [PATCH 1/8] YFix double tap issue on Mobile Currently, you must to double tab a video and is very annoying for the users. Fetch YouTube API script on demand The client fetch the YouTube API Library only when the user click a placeholder video by the first time. It doesn't use iframes directly iframes are using internally through the YouTube API Library Keep the performance stunning It doesn't impact to the perfomance because the library is not being fetched at the same time that the page is being loaded --- src/lite-yt-embed.js | 45 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/lite-yt-embed.js b/src/lite-yt-embed.js index add8fe4..4601f8f 100644 --- a/src/lite-yt-embed.js +++ b/src/lite-yt-embed.js @@ -17,7 +17,6 @@ class LiteYTEmbed extends HTMLElement { // Gotta encode the untrusted value // https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-2---attribute-escape-before-inserting-untrusted-data-into-html-common-attributes this.videoId = encodeURIComponent(this.getAttribute('videoid')); - /** * Lo, the youtube placeholder image! (aka the thumbnail, poster image, etc) * There is much internet debate on the reliability of thumbnail URLs. Weak consensus is that you @@ -40,6 +39,19 @@ class LiteYTEmbed extends HTMLElement { // TODO: support dynamically setting the attribute via attributeChangedCallback } + fetchYoutubeAPI(cb) { + var tag = document.createElement('script'); + tag.src = 'https://www.youtube.com/iframe_api'; + tag.async = true; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + tag.onload = () => { + YT.ready(() => { + if(cb) cb(); + }) + }; + } + connectedCallback() { this.style.backgroundImage = `url("${this.posterUrl}")`; @@ -98,13 +110,30 @@ class LiteYTEmbed extends HTMLElement { LiteYTEmbed.preconnected = true; } - addIframe(){ - const iframeHTML = ` -`; - this.insertAdjacentHTML('beforeend', iframeHTML); + onYouTubeIframeAPIReady() { + const videoWrapper = document.createElement('div') + videoWrapper.id = this.videoId; + document.body.appendChild(videoWrapper); + this.insertAdjacentElement('beforeend', videoWrapper); + + new YT.Player(videoWrapper, { + width: '100%', + videoId: this.videoId, + playerVars: { 'autoplay': 1, 'playsinline': 1 }, + events: { + 'onReady': (event) => { + event.target.playVideo(); + } + } + }); + } + + addIframe(){ + if(typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') { + this.fetchYoutubeAPI(this.onYouTubeIframeAPIReady.bind(this)); + } + + // Keeping the default implementation for iframes this.classList.add('lyt-activated'); } } From 46679524d0810669c85a7ed0b6896c32eaf80a41 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Sun, 28 Nov 2021 15:09:09 -0800 Subject: [PATCH 2/8] refactor and introduce forceautoplay option --- index.html | 1 + src/lite-yt-embed.js | 60 +++++++++++++++++++++---------------- variants/forceautoplay.html | 16 ++++++++++ 3 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 variants/forceautoplay.html diff --git a/index.html b/index.html index b51fd6c..11c3861 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,7 @@

View isolated demos:

  • lite-youtube-embed - progressively enhanced
  • lite-youtube-embed - custom poster image
  • lite-youtube-embed - with parameters
  • +
  • lite-youtube-embed - with forceautoplay on
  • normal youtube embed diff --git a/src/lite-yt-embed.js b/src/lite-yt-embed.js index 40c7bb4..26a418f 100644 --- a/src/lite-yt-embed.js +++ b/src/lite-yt-embed.js @@ -52,6 +52,11 @@ class LiteYTEmbed extends HTMLElement { // 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); + + // prep the YT API + if (this.hasAttribute('forceautoplay')) { + this.fetchYoutubeAPI(); + } } // // TODO: Support the the user changing the [videoid] attribute @@ -95,49 +100,52 @@ class LiteYTEmbed extends HTMLElement { LiteYTEmbed.preconnected = true; } - fetchYoutubeAPI(cb) { - var tag = document.createElement('script'); - tag.src = 'https://www.youtube.com/iframe_api'; - tag.async = true; - var firstScriptTag = document.getElementsByTagName('script')[0]; - firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - tag.onload = () => { - YT.ready(() => { - if(cb) cb(); - }) - }; + fetchYoutubeAPI() { + if (window.YT || (window.YT && window.YT.Player)) return; + this.ytApiPromise = new Promise((res, rej) => { + var el = document.createElement('script'); + el.src = 'https://www.youtube.com/iframe_api'; + el.async = true; + document.head.append(el); + el.onload = _ => { + YT.ready(res) + }; + el.onerror = rej; + }); } - onYouTubeIframeAPIReady() { - const videoWrapper = document.createElement('div') - videoWrapper.id = this.videoId; - document.body.appendChild(videoWrapper); - this.insertAdjacentElement('beforeend', videoWrapper); + async addYTPlayerIframe(params) { + await this.ytApiPromise; + + const videoPlaceholderEl = document.createElement('div') + this.append(videoPlaceholderEl); + + const paramsObj = Object.fromEntries(params.entries()) - new YT.Player(videoWrapper, { + new YT.Player(videoPlaceholderEl, { width: '100%', videoId: this.videoId, - playerVars: { 'autoplay': 1, 'playsinline': 1 }, + playerVars: paramsObj, events: { - 'onReady': (event) => { + 'onReady': event => { event.target.playVideo(); } } }); } - addIframe(){ - if(typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') { - this.fetchYoutubeAPI(this.onYouTubeIframeAPIReady.bind(this)); - } - - // Keeping the default implementation for iframes + async addIframe(){ if (this.classList.contains('lyt-activated')) return; this.classList.add('lyt-activated'); const params = new URLSearchParams(this.getAttribute('params') || []); params.append('autoplay', '1'); - + params.append('playsinline', '1'); + + if (this.hasAttribute('forceautoplay')) { + return this.addYTPlayerIframe(params); + } + const iframeEl = document.createElement('iframe'); iframeEl.width = 560; iframeEl.height = 315; diff --git a/variants/forceautoplay.html b/variants/forceautoplay.html new file mode 100644 index 0000000..2656513 --- /dev/null +++ b/variants/forceautoplay.html @@ -0,0 +1,16 @@ + + + + lite-youtube-embed + + + + + +

    force autoplay

    + + + + + + From 0461e7483d9fc599361553cdb454cdcb10494b23 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Sun, 28 Nov 2021 15:31:31 -0800 Subject: [PATCH 3/8] flip on yt api path for firefox and safari only --- index.html | 1 - src/lite-yt-embed.js | 16 +++++++++++----- variants/forceautoplay.html | 16 ---------------- variants/yt.html | 6 +++--- 4 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 variants/forceautoplay.html diff --git a/index.html b/index.html index 11c3861..b51fd6c 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,6 @@

    View isolated demos:

  • lite-youtube-embed - progressively enhanced
  • lite-youtube-embed - custom poster image
  • lite-youtube-embed - with parameters
  • -
  • lite-youtube-embed - with forceautoplay on
  • normal youtube embed diff --git a/src/lite-yt-embed.js b/src/lite-yt-embed.js index 26a418f..236e11f 100644 --- a/src/lite-yt-embed.js +++ b/src/lite-yt-embed.js @@ -53,9 +53,14 @@ class LiteYTEmbed extends HTMLElement { // 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); - // prep the YT API - if (this.hasAttribute('forceautoplay')) { - this.fetchYoutubeAPI(); + + // Chrome & Edge have no problem with the basic YouTube Embed with ?autoplay=1 + // However Safari and Firefox 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 300KB (ungz) of JS for the YT Player API + this.needsYTApiForAutoplay = navigator.vendor.includes('Apple') || navigator.userAgent.includes('Firefox'); + + if (this.needsYTApiForAutoplay) { + this.fetchYTPlayerApi(); } } @@ -100,8 +105,9 @@ class LiteYTEmbed extends HTMLElement { LiteYTEmbed.preconnected = true; } - fetchYoutubeAPI() { + fetchYTPlayerApi() { if (window.YT || (window.YT && window.YT.Player)) return; + this.ytApiPromise = new Promise((res, rej) => { var el = document.createElement('script'); el.src = 'https://www.youtube.com/iframe_api'; @@ -142,7 +148,7 @@ class LiteYTEmbed extends HTMLElement { params.append('autoplay', '1'); params.append('playsinline', '1'); - if (this.hasAttribute('forceautoplay')) { + if (this.needsYTApiForAutoplay) { return this.addYTPlayerIframe(params); } diff --git a/variants/forceautoplay.html b/variants/forceautoplay.html deleted file mode 100644 index 2656513..0000000 --- a/variants/forceautoplay.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - lite-youtube-embed - - - - - -

    force autoplay

    - - - - - - diff --git a/variants/yt.html b/variants/yt.html index ca1aaf9..e9260d4 100644 --- a/variants/yt.html +++ b/variants/yt.html @@ -7,10 +7,10 @@ -

    typical iframe embed

    +

    typical iframe embed