Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Website: Hero video optimization (subtitles, flickering) #944

Merged
merged 10 commits into from
Dec 3, 2024
Merged
1 change: 1 addition & 0 deletions shared/locales/de/website-home.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
}
]
},
"video-subtitle": "/assets/video/subtitle-video-testimonials.de_DE.vtt",
"section-2": {
"title-1": "Die Menschen in den ärmsten Regionen Sierra Leones kennen den Weg aus der Armut. ",
"title-2": [
Expand Down
1 change: 1 addition & 0 deletions shared/locales/en/website-home.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
}
]
},
"video-subtitle": "/assets/video/subtitle-video-testimonials.en_US.vtt",
"section-2": {
"title-1": "Those that live in Sierra Leone’s poorest communities know what it takes to rise out of poverty.",
"title-2": [
Expand Down
1 change: 1 addition & 0 deletions shared/locales/fr/website-home.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
}
]
},
"video-subtitle": "/assets/video/subtitle-video-testimonials.fr_FR.vtt",
"section-2": {
"title-1": "Dans les communautés les plus pauvres de Sierra Leone, les gens savent très bien ce qu’il faut faire pour sortir de la pauvreté.",
"title-2": [
Expand Down
1 change: 1 addition & 0 deletions shared/locales/it/website-home.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
}
]
},
"video-subtitle": "/assets/video/subtitle-video-testimonials.it_IT.vtt",
"section-2": {
"title-1": "Le persone che vivono nelle comunità più povere della Sierra Leone sanno di cosa hanno bisogno per uscire dalla povertà.",
"title-2": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
'use client';

import { DefaultParams } from '@/app/[lang]/[region]';
import { useGlobalStateProvider } from '@/components/providers/global-state-provider';
import { PauseIcon, PlayIcon, SpeakerWaveIcon, SpeakerXMarkIcon } from '@heroicons/react/24/solid';
import MuxVideo from '@mux/mux-video-react';
import { Button } from '@socialincome/ui';
import classNames from 'classnames';
import Image from 'next/image';
import { useEffect, useRef, useState } from 'react';
import { useEventListener, useIntersectionObserver } from 'usehooks-ts';

export const OVERLAY_FADE_OUT_DELAY = 4000;
type HeroVideoSubtitles = {
translations: {
subtitles: string;
};
} & DefaultParams;

const MuxVideoComponent = ({ lang, translations }: HeroVideoSubtitles) => {
mkue marked this conversation as resolved.
Show resolved Hide resolved
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(true);
const handleError = (e: Event) => {
setError(new Error('Failed to load video'));
setIsLoading(false);
};

const MuxVideoComponent = () => {
useEffect(() => {
const video = videoElementRef.current;
if (video) {
video.addEventListener('error', handleError);
return () => video.removeEventListener('error', handleError);
}
}, []);
const videoElementRef = useRef<HTMLVideoElement>(null);
const posterRef = useRef<HTMLDivElement>(null);
const [muted, setMuted] = useState(true);
const [playing, setPlaying] = useState(false);
const [showCaptions, setShowCaptions] = useState(true);
const [showControls, setShowControls] = useState(true);
const { entry, isIntersecting, ref } = useIntersectionObserver({ initialIsIntersecting: true, threshold: 0.5 });
const { setBackgroundColor } = useGlobalStateProvider();

useEffect(() => {
if (!entry) return;
if (!isIntersecting && entry.boundingClientRect.top < 0) {
Expand All @@ -34,8 +55,18 @@ const MuxVideoComponent = () => {
}, [entry, isIntersecting]);

useEffect(() => {
if (playing) {
videoElementRef.current?.play();
const video = videoElementRef.current;
if (playing && video) {
// Hide poster when video is ready
const handleCanPlay = () => {
if (posterRef.current) {
posterRef.current.style.opacity = '0';
posterRef.current.style.transition = 'opacity 0.5s ease';
}
};
video.addEventListener('canplay', handleCanPlay);
video.play();
return () => video.removeEventListener('canplay', handleCanPlay);
Comment on lines +58 to +69
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential race condition in video playback

There's a potential race condition between video.play() and the canplay event listener. The video might start playing before the poster fade-out is complete.

Apply this fix:

 if (playing && video) {
   const handleCanPlay = () => {
     if (posterRef.current) {
       posterRef.current.style.opacity = '0';
       posterRef.current.style.transition = 'opacity 0.5s ease';
     }
+    video.play();
   };
   video.addEventListener('canplay', handleCanPlay);
-  video.play();
   return () => video.removeEventListener('canplay', handleCanPlay);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const video = videoElementRef.current;
if (playing && video) {
// Hide poster when video is ready
const handleCanPlay = () => {
if (posterRef.current) {
posterRef.current.style.opacity = '0';
posterRef.current.style.transition = 'opacity 0.5s ease';
}
};
video.addEventListener('canplay', handleCanPlay);
video.play();
return () => video.removeEventListener('canplay', handleCanPlay);
const video = videoElementRef.current;
if (playing && video) {
// Hide poster when video is ready
const handleCanPlay = () => {
if (posterRef.current) {
posterRef.current.style.opacity = '0';
posterRef.current.style.transition = 'opacity 0.5s ease';
}
video.play();
};
video.addEventListener('canplay', handleCanPlay);
return () => video.removeEventListener('canplay', handleCanPlay);

} else {
videoElementRef.current?.pause();
}
Expand Down Expand Up @@ -67,32 +98,34 @@ const MuxVideoComponent = () => {

return (
<>
<div ref={posterRef} className="absolute inset-0 z-0">
<Image
alt="Video Poster"
className="h-full w-full object-cover"
src="https://image.mux.com/IPdwilTUVkKs2nK8zKZi5eKwbKhpCWxgsYNVxcANeFE/thumbnail.jpg?time=2"
/>
</div>
<MuxVideo
ref={videoElementRef}
className="h-full w-full object-cover"
className="z-10 h-full w-full object-cover"
playbackId="IPdwilTUVkKs2nK8zKZi5eKwbKhpCWxgsYNVxcANeFE"
poster="https://image.mux.com/IPdwilTUVkKs2nK8zKZi5eKwbKhpCWxgsYNVxcANeFE/thumbnail.jpg?time=2"
startTime={2}
loop
muted={muted}
autoPlay={playing}
playsInline
onCanPlay={() => setPlaying(true)} // Ensure smooth start
>
<track
kind="captions"
src="https://stream.mux.com/IPdwilTUVkKs2nK8zKZi5eKwbKhpCWxgsYNVxcANeFE/text/YZZCqh56kzyMBlwsaPsdlxaFKmlKzNNDKV7oyQb8ECZ4zpXnm500ieA.txt"
srcLang="en"
label="English"
default
/>
<track kind="captions" src={translations.subtitles} srcLang={lang} label={lang.toUpperCase()} default />
mkue marked this conversation as resolved.
Show resolved Hide resolved
<style>{`
video::cue {
background-color: rgba(0, 0, 0, 0.8);
color: white;
font-family: Arial, sans-serif;
font-size: 24px;
opacity: ${showCaptions ? 1 : 0};
`}</style>
video::cue {
background-color: rgba(0, 0, 0, 0.8);
color: white;
font-family: Arial, sans-serif;
font-size: 24px;
opacity: ${showCaptions ? 1 : 0};
}
`}</style>
Comment on lines +119 to +128
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Missing subtitle files need to be added

The translation files reference subtitle VTT files at /assets/video/, but these files are not present in the repository:

  • /assets/video/subtitle-video-testimonials.de_DE.vtt
  • /assets/video/subtitle-video-testimonials.en_US.vtt
  • /assets/video/subtitle-video-testimonials.fr_FR.vtt
  • /assets/video/subtitle-video-testimonials.it_IT.vtt
🔗 Analysis chain

Verify subtitle file availability

The subtitle implementation looks correct, but we need to verify the subtitle files exist at the paths specified in translations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if subtitle files exist in public assets
fd -e vtt . public/assets/video

# Check subtitle paths in translation files
rg "video-subtitle.*\.vtt" shared/locales

Length of output: 624


Script:

#!/bin/bash
# Check if subtitle files exist in public directory
fd -e vtt subtitle-video-testimonials public

Length of output: 140

</MuxVideo>
{/* Transparent element used to track whether navbar should be transparent or not */}
<div ref={ref} className="absolute inset-x-0 top-24 z-10 h-2 opacity-100"></div>
Expand Down Expand Up @@ -131,5 +164,4 @@ const MuxVideoComponent = () => {
</>
);
};

export default MuxVideoComponent;
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ export async function HeroVideo({ lang, region }: DefaultParams) {

return (
<div className="relative h-[calc(100svh)] w-full">
<MuxVideoComponent />
<MuxVideoComponent
translations={{
subtitles: translator.t<string>('video-subtitle'),
}}
lang={lang}
region={region}
/>
<HeroVideoOverlay
translations={{
buttonText: translator.t('section-1.take-action'),
Expand Down
Loading