From 13a48c71023e9669d28b87ecb83c7ef846f607a9 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Wed, 27 Nov 2024 13:21:16 +0100 Subject: [PATCH 1/3] start and pause videos on autoplay depending of visibility --- .../cms-site/src/blocks/DamVideoBlock.tsx | 36 ++++++++++------ .../cms-site/src/blocks/VimeoVideoBlock.tsx | 14 +++++-- .../cms-site/src/blocks/YouTubeVideoBlock.tsx | 17 +++++--- .../src/blocks/helpers/controlVideos.ts | 41 +++++++++++++++++++ .../blocks/helpers/useIsElementVisible.tsx | 20 +++++++++ 5 files changed, 107 insertions(+), 21 deletions(-) create mode 100644 packages/site/cms-site/src/blocks/helpers/controlVideos.ts create mode 100644 packages/site/cms-site/src/blocks/helpers/useIsElementVisible.tsx diff --git a/packages/site/cms-site/src/blocks/DamVideoBlock.tsx b/packages/site/cms-site/src/blocks/DamVideoBlock.tsx index 82f1ff320b..d8451398ea 100644 --- a/packages/site/cms-site/src/blocks/DamVideoBlock.tsx +++ b/packages/site/cms-site/src/blocks/DamVideoBlock.tsx @@ -1,11 +1,13 @@ "use client"; -import { ReactElement, ReactNode, useState } from "react"; +import { ReactElement, ReactNode, useRef, useState } from "react"; import styled, { css } from "styled-components"; import { DamVideoBlockData } from "../blocks.generated"; import { withPreview } from "../iframebridge/withPreview"; import { PreviewSkeleton } from "../previewskeleton/PreviewSkeleton"; +import { pauseDamVideo, playDamVideo } from "./helpers/controlVideos"; +import { useIsElementVisible } from "./helpers/useIsElementVisible"; import { VideoPreviewImage, VideoPreviewImageProps } from "./helpers/VideoPreviewImage"; import { PropsWithData } from "./PropsWithData"; @@ -33,6 +35,13 @@ export const DamVideoBlock = withPreview( const [showPreviewImage, setShowPreviewImage] = useState(true); const hasPreviewImage = Boolean(previewImage && previewImage.damFile); + const inViewRef = useRef(null); + const videoRef = useRef(null); + + const inView = useIsElementVisible(inViewRef); + + inView && autoplay ? playDamVideo(videoRef) : pauseDamVideo(videoRef); + return ( <> {hasPreviewImage && showPreviewImage ? ( @@ -56,17 +65,20 @@ export const DamVideoBlock = withPreview( /> ) ) : ( - +
+ +
)} ); diff --git a/packages/site/cms-site/src/blocks/VimeoVideoBlock.tsx b/packages/site/cms-site/src/blocks/VimeoVideoBlock.tsx index bf14f84147..9682fa3d2f 100644 --- a/packages/site/cms-site/src/blocks/VimeoVideoBlock.tsx +++ b/packages/site/cms-site/src/blocks/VimeoVideoBlock.tsx @@ -1,10 +1,12 @@ "use client"; -import { ReactNode, useState } from "react"; +import { ReactNode, useEffect, useRef, useState } from "react"; import styled, { css } from "styled-components"; import { VimeoVideoBlockData } from "../blocks.generated"; import { withPreview } from "../iframebridge/withPreview"; import { PreviewSkeleton } from "../previewskeleton/PreviewSkeleton"; +import { pauseVimeoVideo, playVimeoVideo } from "./helpers/controlVideos"; +import { useIsElementVisible } from "./helpers/useIsElementVisible"; import { VideoPreviewImage, VideoPreviewImageProps } from "./helpers/VideoPreviewImage"; import { PropsWithData } from "./PropsWithData"; @@ -43,6 +45,12 @@ export const VimeoVideoBlock = withPreview( }: VimeoVideoBlockProps) => { const [showPreviewImage, setShowPreviewImage] = useState(true); const hasPreviewImage = !!(previewImage && previewImage.damFile); + const inViewRef = useRef(null); + const inView = useIsElementVisible(inViewRef); + + useEffect(() => { + inView && autoplay ? playVimeoVideo() : pauseVimeoVideo(); + }, [autoplay, inView]); if (!vimeoIdentifier) return ; @@ -50,8 +58,6 @@ export const VimeoVideoBlock = withPreview( const searchParams = new URLSearchParams(); - if (autoplay !== undefined || (hasPreviewImage && !showPreviewImage)) - searchParams.append("autoplay", Number(autoplay || (hasPreviewImage && !showPreviewImage)).toString()); if (autoplay) searchParams.append("muted", "1"); if (loop !== undefined) searchParams.append("loop", Number(loop).toString()); @@ -87,7 +93,7 @@ export const VimeoVideoBlock = withPreview( /> ) ) : ( - + )} diff --git a/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx b/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx index a6e552cc5a..25ba2f0f8d 100644 --- a/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx +++ b/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx @@ -1,11 +1,13 @@ "use client"; -import { ReactElement, ReactNode, useState } from "react"; +import { ReactElement, ReactNode, useEffect, useRef, useState } from "react"; import styled, { css } from "styled-components"; import { YouTubeVideoBlockData } from "../blocks.generated"; import { withPreview } from "../iframebridge/withPreview"; import { PreviewSkeleton } from "../previewskeleton/PreviewSkeleton"; +import { pauseYoutubeVideo, playYoutubeVideo } from "./helpers/controlVideos"; +import { useIsElementVisible } from "./helpers/useIsElementVisible"; import { VideoPreviewImage, VideoPreviewImageProps } from "./helpers/VideoPreviewImage"; import { PropsWithData } from "./PropsWithData"; @@ -40,6 +42,12 @@ export const YouTubeVideoBlock = withPreview( }: YouTubeVideoBlockProps) => { const [showPreviewImage, setShowPreviewImage] = useState(true); const hasPreviewImage = !!(previewImage && previewImage.damFile); + const inViewRef = useRef(null); + const inView = useIsElementVisible(inViewRef); + + useEffect(() => { + inView && autoplay ? playYoutubeVideo() : pauseYoutubeVideo(); + }, [autoplay, inView]); if (!youtubeIdentifier) { return ; @@ -49,9 +57,8 @@ export const YouTubeVideoBlock = withPreview( const searchParams = new URLSearchParams(); searchParams.append("modestbranding", "1"); searchParams.append("rel", "0"); + searchParams.append("enablejsapi", "1"); - if (autoplay !== undefined || (hasPreviewImage && !showPreviewImage)) - searchParams.append("autoplay", Number(autoplay || (hasPreviewImage && !showPreviewImage)).toString()); if (autoplay) searchParams.append("mute", "1"); if (showControls !== undefined) searchParams.append("controls", Number(showControls).toString()); @@ -87,8 +94,8 @@ export const YouTubeVideoBlock = withPreview( /> ) ) : ( - - + + )} diff --git a/packages/site/cms-site/src/blocks/helpers/controlVideos.ts b/packages/site/cms-site/src/blocks/helpers/controlVideos.ts new file mode 100644 index 0000000000..7f054eae3c --- /dev/null +++ b/packages/site/cms-site/src/blocks/helpers/controlVideos.ts @@ -0,0 +1,41 @@ +import { RefObject } from "react"; + +export const playDamVideo = (videoRef: RefObject) => { + if (videoRef.current) { + videoRef.current.play(); + } +}; + +export const pauseDamVideo = (videoRef: RefObject) => { + if (videoRef.current) { + videoRef.current.pause(); + } +}; + +export const pauseYoutubeVideo = () => { + const iframe = document.getElementsByTagName("iframe")[0]; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage(`{"event":"command","func":"pauseVideo","args":""}`, "*"); + } +}; + +export const playYoutubeVideo = () => { + const iframe = document.getElementsByTagName("iframe")[0]; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage(`{"event":"command","func":"playVideo","args":""}`, "*"); + } +}; + +export const pauseVimeoVideo = () => { + const iframe = document.getElementsByTagName("iframe")[0]; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage(JSON.stringify({ method: "pause" }), "https://player.vimeo.com"); + } +}; + +export const playVimeoVideo = () => { + const iframe = document.getElementsByTagName("iframe")[0]; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage(JSON.stringify({ method: "play" }), "https://player.vimeo.com"); + } +}; diff --git a/packages/site/cms-site/src/blocks/helpers/useIsElementVisible.tsx b/packages/site/cms-site/src/blocks/helpers/useIsElementVisible.tsx new file mode 100644 index 0000000000..cc98f003fd --- /dev/null +++ b/packages/site/cms-site/src/blocks/helpers/useIsElementVisible.tsx @@ -0,0 +1,20 @@ +import { RefObject, useEffect, useState } from "react"; + +export const useIsElementVisible = (ref: RefObject) => { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const options = { root: null, rootMargin: "0px", threshold: 1.0 }; + const observer = new IntersectionObserver((entries) => { + setIsVisible(entries[0].isIntersecting); + }, options); + if (ref.current) observer.observe(ref.current); + const inViewRefValue = ref.current; + + return () => { + if (inViewRefValue) observer.unobserve(inViewRefValue); + }; + }, [ref]); + + return isVisible; +}; From dd73e2875f2933d41ab3e80302ab1eabcd42e791 Mon Sep 17 00:00:00 2001 From: Julia Wegmayr Date: Wed, 27 Nov 2024 13:48:45 +0100 Subject: [PATCH 2/3] add changeset --- .changeset/sharp-monkeys-cry.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/sharp-monkeys-cry.md diff --git a/.changeset/sharp-monkeys-cry.md b/.changeset/sharp-monkeys-cry.md new file mode 100644 index 0000000000..f6941baa3f --- /dev/null +++ b/.changeset/sharp-monkeys-cry.md @@ -0,0 +1,7 @@ +--- +"@comet/cms-site": minor +--- + +Play videos on auto play only when visible + +Start videos depending on their visibility in `DamVideoBlock`, `YoutubeVideoBlock` and `VimeoVideoBlock`. Videos that are not in the viewport of the user, pause. From 108a218280d220275cd39a380dbff31a0c1d2d4a Mon Sep 17 00:00:00 2001 From: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:33:59 +0100 Subject: [PATCH 3/3] Improve changeset --- .changeset/sharp-monkeys-cry.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/sharp-monkeys-cry.md b/.changeset/sharp-monkeys-cry.md index f6941baa3f..8d9d68040d 100644 --- a/.changeset/sharp-monkeys-cry.md +++ b/.changeset/sharp-monkeys-cry.md @@ -2,6 +2,7 @@ "@comet/cms-site": minor --- -Play videos on auto play only when visible +Play/pause auto-play videos depending on their visibility -Start videos depending on their visibility in `DamVideoBlock`, `YoutubeVideoBlock` and `VimeoVideoBlock`. Videos that are not in the viewport of the user, pause. +Start videos in `DamVideoBlock`, `YoutubeVideoBlock` and `VimeoVideoBlock` when the block is in or enters the viewport. +Pause them when the block is leaving the viewport.