Skip to content

Commit

Permalink
MWPW-147468 - Delayed Activation of Blade Animations on Ps Product Pa…
Browse files Browse the repository at this point in the history
…ge (#2487)

* feat(video): implemented video play within the viewport

* chore(video): opacity removed

* fix: window extension

* fix(DOM-exception): fixed dom exception & fixes test cases

* fix(unit-test): removed dublicates urls in mocks

* fix: test case added for play/pause & end event

* chore: removed dublicate checks
  • Loading branch information
sivasadobe authored Jul 8, 2024
1 parent 265838f commit daef70b
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 10 deletions.
3 changes: 2 additions & 1 deletion libs/blocks/video/video.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createIntersectionObserver, getConfig } from '../../utils/utils.js';
import { applyHoverPlay, getVideoAttrs } from '../../utils/decorate.js';
import { applyHoverPlay, getVideoAttrs, applyInViewPortPlay } from '../../utils/decorate.js';

const ROOT_MARGIN = 1000;

Expand All @@ -23,6 +23,7 @@ const loadVideo = (a) => {
a.insertAdjacentHTML('afterend', video);
const videoElem = document.body.querySelector(`source[src="${videoPath}"]`)?.parentElement;
applyHoverPlay(videoElem);
applyInViewPortPlay(videoElem);
a.remove();
};

Expand Down
38 changes: 36 additions & 2 deletions libs/utils/decorate.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,14 @@ export function getVideoAttrs(hash, dataset) {
const isAutoplay = hash?.includes('autoplay');
const isAutoplayOnce = hash?.includes('autoplay1');
const playOnHover = hash?.includes('hoverplay');
const playInViewport = hash?.includes('viewportplay');
const poster = dataset?.videoPoster ? `poster='${dataset.videoPoster}'` : '';
const globalAttrs = `playsinline ${poster}`;
const autoPlayAttrs = 'autoplay muted';
const playInViewportAttrs = playInViewport ? 'data-play-viewport' : '';

if (isAutoplay && !isAutoplayOnce) {
return `${globalAttrs} ${autoPlayAttrs} loop`;
return `${globalAttrs} ${autoPlayAttrs} loop ${playInViewportAttrs}`;
}
if (playOnHover && isAutoplayOnce) {
return `${globalAttrs} ${autoPlayAttrs} data-hoverplay`;
Expand All @@ -188,7 +190,7 @@ export function getVideoAttrs(hash, dataset) {
return `${globalAttrs} muted data-hoverplay`;
}
if (isAutoplayOnce) {
return `${globalAttrs} ${autoPlayAttrs}`;
return `${globalAttrs} ${autoPlayAttrs} ${playInViewportAttrs}`;
}
return `${globalAttrs} controls`;
}
Expand Down Expand Up @@ -225,3 +227,35 @@ export function handleObjectFit(bgRow) {
setObjectFitAndPos(text, pic, r, ['fill', 'contain', 'cover', 'none', 'scale-down']);
});
}

export function getVideoIntersectionObserver() {
if (!window?.videoIntersectionObs) {
window.videoIntersectionObs = new window.IntersectionObserver((entries) => {
entries.forEach((entry) => {
const { intersectionRatio, target: video } = entry;
const isHaveLoopAttr = video.getAttributeNames().includes('loop');
const { playedOnce = false } = video.dataset;
const isPlaying = video.currentTime > 0 && !video.paused && !video.ended
&& video.readyState > video.HAVE_CURRENT_DATA;

if (intersectionRatio <= 0.8) {
video.pause();
} else if ((isHaveLoopAttr || !playedOnce) && !isPlaying) {
video.play();
}
});
}, { threshold: [0.8] });
}
return window.videoIntersectionObs;
}

export function applyInViewPortPlay(video) {
if (!video) return;
if (video.hasAttribute('data-play-viewport')) {
const observer = getVideoIntersectionObserver();
video.addEventListener('ended', () => {
video.dataset.playedOnce = true;
});
observer.observe(video);
}
}
49 changes: 49 additions & 0 deletions test/blocks/video/mocks/body.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<style>
video {
width: 100%;
}
</style>
<main>
<div class="video no-lazy">
<a href="https://main--blog--adobecom.hlx.page/media_17927691d22fe4e1bd058e94762a224fdc57ebb7b.mp4">
Expand Down Expand Up @@ -37,4 +42,48 @@
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb.mp4#_hoverplay
</a>
</div>

<div class="video autoplay viewportplay ">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb1.mp4#autoplay#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb1.mp4#autoplay#viewportplay
</a>
</div>

<div class="video autoplay viewportplay scrolled-80">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay#viewportplay
</a>
</div>
<div class="video autoplay1 viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb2.mp4#autoplay1#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb2.mp4#autoplay1#viewportplay
</a>
</div>

<div class="video autoplay1 viewportplay ended">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay1#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay1#viewportplay
</a>
</div>

<div class="video no-autoplay no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb3.mp4#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb3.mp4#viewportplay
</a>
</div>
<div class="video no-autoplay1 no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb4.mp4#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb4.mp4#viewportplay
</a>
</div>
<div class="video hoverplay no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb5.mp4#viewportplay#_hoverplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb5.mp4#viewportplay#_hoverplay
</a>
</div>
<div class="video autoplay1 hoverplay no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb6.mp4#autoplay1#_hoverplay#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb6.mp4#autoplay1#_hoverplay#viewportplay
</a>
</div>
</main>
166 changes: 159 additions & 7 deletions test/blocks/video/video.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { readFile } from '@web/test-runner-commands';
import { expect } from '@esm-bundle/chai';
import { expect, assert } from '@esm-bundle/chai';

import sinon from 'sinon';

import { waitForElement } from '../../helpers/waitfor.js';
import { setConfig } from '../../../libs/utils/utils.js';

Expand Down Expand Up @@ -71,25 +74,174 @@ describe('video uploaded using franklin bot', () => {
it('no hoverplay attribute added when with autoplay on loop', async () => {
const block = document.querySelector('.video.autoplay.playonhover');
const a = block.querySelector('a');
const { href } = a;
a.textContent = href;
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = await waitForElement('.video.autoplay.playonhover video');
const video = block.querySelector('video');
expect(video.hasAttribute('loop')).to.be.true;
expect(video.hasAttribute('data-hoverplay')).to.be.false;
});

it('decorate video with hoverplay when only hoverplay is added to url', async () => {
const block = document.querySelector('.video.hoveronly');
const a = block.querySelector('a');
const { href } = a;
a.textContent = href;
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = await waitForElement('.video.hoveronly video');
const video = block.querySelector('video');
expect(video.hasAttribute('data-hoverplay')).to.be.true;
});

it('decorate video with viewportplay only with autoplay', async () => {
const block = document.querySelector('.video.autoplay.viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('play video when element reached 80% viewport', async () => {
const block = document.querySelector('.video.autoplay.viewportplay.scrolled-80');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);
const nextFrame = () => new Promise((resolve) => {
requestAnimationFrame(resolve);
});

init(a);
const video = block.querySelector('video');
const source = video.querySelector('source');
source.setAttribute('src', 'https://www.adobe.com/creativecloud/media_1167374e3354ef57f126fa78a55cbc1708ac4babd.mp4');
source.setAttribute('type', 'video/mp4');

const playSpy = sinon.spy(video, 'play');
const pauseSpy = sinon.spy(video, 'pause');

video.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(playSpy.calledOnce);

document.body.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(pauseSpy.calledOnce);

expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('Don\'t play the video once it end when autoplay1 enabled', async () => {
const block = document.querySelector('.video.autoplay1.viewportplay.ended');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);
const nextFrame = () => new Promise((resolve) => {
requestAnimationFrame(resolve);
});

init(a);
const video = block.querySelector('video');
const source = video.querySelector('source');
source.setAttribute('src', 'https://www.adobe.com/creativecloud/media_1167374e3354ef57f126fa78a55cbc1708ac4babd.mp4');
source.setAttribute('type', 'video/mp4');

const playSpy = sinon.spy(video, 'play');
const pauseSpy = sinon.spy(video, 'pause');
const endedSpy = sinon.spy();
video.addEventListener('ended', endedSpy);

video.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(playSpy.calledOnce);

document.body.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(pauseSpy.calledOnce);

video.dispatchEvent(new Event('ended'));
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});

video.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});

expect(playSpy.callCount).to.equal(1);
expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('decorate video with viewportplay only with autoplay1', async () => {
const block = document.querySelector('.video.autoplay1.viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('decorate video with no viewportplay with autoplay1 hoverplay', async () => {
const block = document.querySelector('.video.autoplay1.hoverplay.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});

it('decorate video with no viewportplay with hoverplay', async () => {
const block = document.querySelector('.video.hoverplay.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});

it('decorate video with no viewportplay no autoplay', async () => {
const block = document.querySelector('.video.no-autoplay.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});

it('decorate video with no viewportplay no autoplay1', async () => {
const block = document.querySelector('.video.no-autoplay1.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});
});

0 comments on commit daef70b

Please sign in to comment.