Skip to content

Commit

Permalink
Website: Hero video optimization (subtitles, flickering) (#944)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdumond authored Dec 3, 2024
1 parent 3880920 commit 892b5fc
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 22 deletions.
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) => {
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);
} 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 />
<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>
</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

0 comments on commit 892b5fc

Please sign in to comment.