From 316b2b9bbffa8eac45b7d7a191e914661dafa273 Mon Sep 17 00:00:00 2001 From: Justin Ribeiro Date: Thu, 14 Nov 2024 19:25:11 -0500 Subject: [PATCH] feat(auto-pause): adds autopause attribute for pausing videos off of screen (#112) --- demo/auto-pause.html | 46 +++++++++++++++++++++++++++++++++++++++ lite-youtube.ts | 43 ++++++++++++++++++++++++++++++++---- test/lite-youtube.test.ts | 9 ++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 demo/auto-pause.html diff --git a/demo/auto-pause.html b/demo/auto-pause.html new file mode 100644 index 0000000..8695055 --- /dev/null +++ b/demo/auto-pause.html @@ -0,0 +1,46 @@ + + + + + + lite-youtube demo + + + + + +
+    <style>
      lite-youtube {
        --lite-youtube-frame-shadow-visible: no;
      }
    </style>
+    <lite-youtube videoid="guJLfqTFfIw"</lite-youtube>
+    
+ +
+ + diff --git a/lite-youtube.ts b/lite-youtube.ts index 5aa900e..154ad52 100644 --- a/lite-youtube.ts +++ b/lite-youtube.ts @@ -36,9 +36,13 @@ export class LiteYTEmbed extends HTMLElement { } connectedCallback(): void { - this.addEventListener('pointerover', () => LiteYTEmbed.warmConnections(this), { - once: true, - }); + this.addEventListener( + 'pointerover', + () => LiteYTEmbed.warmConnections(this), + { + once: true, + }, + ); this.addEventListener('click', () => this.addIframe()); } @@ -83,6 +87,10 @@ export class LiteYTEmbed extends HTMLElement { return this.hasAttribute('autoload'); } + get autoPause(): boolean { + return this.hasAttribute('autopause'); + } + get noCookie(): boolean { return this.hasAttribute('nocookie'); } @@ -231,7 +239,7 @@ export class LiteYTEmbed extends HTMLElement { ); this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`); - if (this.autoLoad || this.isYouTubeShort()) { + if (this.autoLoad || this.isYouTubeShort() || this.autoPause) { this.initIntersectionObserver(); } } @@ -275,6 +283,12 @@ export class LiteYTEmbed extends HTMLElement { embedTarget = `${this.videoId}?`; } + // autopause needs the postMessage() in the iframe, so you have to enable + // the jsapi + if (this.autoPause) { + this.params = `enablejsapi=1`; + } + // Oh wait, you're a YouTube short, so let's try to make you more workable if (this.isYouTubeShort()) { this.params = `loop=1&mute=1&modestbranding=1&playsinline=1&rel=0&enablejsapi=1&playlist=${this.videoId}`; @@ -343,6 +357,27 @@ export class LiteYTEmbed extends HTMLElement { }, options); observer.observe(this); + + // this needs the iframe loaded, so it has to run post the IO load at the + // least otherwise things will break + if (this.autoPause) { + const windowPause = new IntersectionObserver( + (e, o) => { + e.forEach(entry => { + if (entry.intersectionRatio !== 1) { + this.shadowRoot + .querySelector('iframe') + ?.contentWindow?.postMessage( + '{"event":"command","func":"pauseVideo","args":""}', + '*', + ); + } + }); + }, + { threshold: 1 }, + ); + windowPause.observe(this); + } } /** diff --git a/test/lite-youtube.test.ts b/test/lite-youtube.test.ts index e6a5283..79cb963 100644 --- a/test/lite-youtube.test.ts +++ b/test/lite-youtube.test.ts @@ -119,6 +119,15 @@ describe('', () => { expect(document.head.querySelectorAll('link').length).to.be.equal(1); }); + it('autoPause should inject iframe and warm', async () => { + const el = await fixture( + html``, + ); + // this is a cheeky test by counting the test runner + the warm injector + // TODO write a better observer + expect(document.head.querySelectorAll('link').length).to.be.equal(7); + }); + it('nocookie attr should change iframe url target', async () => { const el = await fixture( html``,