Reputation: 12064
I have react component as follows:
import React, { Fragment, useEffect, useRef, useState } from 'react';
import { hasWindow } from '../utils.js';
import { Helmet } from 'react-helmet';
const PLATFORMS = {
YOUTUBE: 'platform',
BUNNY: 'bunny',
VIMEO: 'vimeo'
};
const VideoExternal = ({ video, isOnScreen, item, context }) => {
const [isPlaying, setIsPlaying] = useState(video.autoplay);
const [isPaused, setIsPaused] = useState(false);
const [isMuted, setIsMuted] = useState(video.autoplay);
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
const [isVideoHovered, setIsVideoHovered] = useState(false);
const [showLastImage, setShowLastImage] = useState(false);
const iframeId = `iframe-${video.url}-${video.externalUrl}`;
const iframeRef = useRef(null);
const playerRef = useRef(null);
const getPlatform = (url) => {
if (url.includes('youtube') || url.includes('youtu')) return PLATFORMS.YOUTUBE;
if (url.includes('b-cdn') || url.includes('mediadelivery')) return PLATFORMS.BUNNY;
if (url.includes('vimeo')) return PLATFORMS.VIMEO;
return null;
};
const getBaseUrl = (url) => {
const urlObj = new URL(url);
return `${urlObj.origin}${urlObj.pathname}`;
};
const addQueryParams = (url, platform, autoplay, loop) => {
const urlObj = new URL(url);
switch (platform) {
case PLATFORMS.YOUTUBE:
if (autoplay) urlObj.searchParams.set('autoplay', '1');
if (loop) urlObj.searchParams.set('loop', '1');
isMuted ? urlObj.searchParams.set('mute', '1') : urlObj.searchParams.set('mute', '0');
urlObj.searchParams.set('controls', '0');
urlObj.searchParams.set('enablejsapi', '1');
break;
case PLATFORMS.VIMEO:
if (autoplay) urlObj.searchParams.set('autoplay', '1');
if (loop) urlObj.searchParams.set('loop', '1');
isMuted ? urlObj.searchParams.set('muted', '1') : urlObj.searchParams.set('muted', '0');
urlObj.searchParams.set('controls', '0');
break;
case PLATFORMS.BUNNY:
if (autoplay) urlObj.searchParams.set('autoplay', 'true');
if (loop) urlObj.searchParams.set('loop', 'true');
urlObj.searchParams.set('mute', `${isMuted} ? 'true' : 'false'`);
urlObj.searchParams.set('controls', 'false');
break;
default:
break;
}
return urlObj.toString();
};
const platform = getPlatform(video.externalUrl);
const baseUrl = getBaseUrl(video.externalUrl, platform);
const iframeUrl = addQueryParams(baseUrl, platform, video.autoplay, video.loop);
const initializePlayer = (iframe) => {
if (!iframe) return null;
if (platform === PLATFORMS.YOUTUBE) {
playerRef.current = new YT.Player(iframeId, {
events: {
onReady: () => {
console.log('onReady');
},
onStateChange: event => {
if (!video.loop && event.data === YT.PlayerState.ENDED) {
setIsPlaying(false);
setIsMuted(true);
setShowLastImage(true);
console.log('Video ended.');
}
}
}
});
} else if (platform === PLATFORMS.VIMEO) {
playerRef.current = new Vimeo.Player(iframe);
}
};
useEffect(() => {
if (isOnScreen && isVideoLoaded) {
const iframe = iframeRef?.current;
// Create new player instance
initializePlayer(iframe);
}
}, [isVideoLoaded, isOnScreen]);
// Effect to reset playing and showing the last image on slide change
useEffect(() => {
setIsPlaying(video.autoplay);
setIsMuted(video.autoplay);
setShowLastImage(false);
}, [isOnScreen]);
// Effect to check when video is ended
useEffect(() => {
if (isOnScreen && isVideoLoaded && hasWindow()) {
const iframe = iframeRef?.current;
if (platform === PLATFORMS.YOUTUBE) {
} else if (platform === PLATFORMS.VIMEO) {
playerRef?.current?.on('finish', function () {
if (!video.loop) {
setIsPlaying(false);
setIsMuted(true);
setShowLastImage(true);
}
});
} else if (platform === PLATFORMS.BUNNY) {
}
}
}, [video.loop, isOnScreen, isVideoLoaded]);
const handlePlayClick = () => {
const iframe = iframeRef?.current;
const iframeWindow = iframe?.contentWindow;
if (iframe && isVideoLoaded) {
if (platform === PLATFORMS.YOUTUBE)
playerRef?.current?.playVideo();
} else if (platform === PLATFORMS.VIMEO) {
playerRef?.current?.play();
} else if (platform === PLATFORMS.BUNNY && iframeWindow) {
iframeWindow?.postMessage('{"event":"command","func":"play"}', '*');
}
setIsPaused(false);
setIsPlaying(true);
setIsMuted(false);
setShowLastImage(false);
}
};
const isFirstImageVisible = !isPlaying && !isPaused && !showLastImage && video.firstImage !== '';
const isLastImageVisible = !isPlaying && !isPaused && showLastImage && video.lastImage !== '';
const isPlayButtonVisible = !isPlaying && video.playButtonVisible;
const renderFirstImage = () => {
return <img src={video.firstImage} alt='Start of Video' className='rounded-16 h-full w-full absolute inset-0 object-cover' />;
};
const renderLastImage = () => {
return <img src={video.lastImage} alt='End of Video' className='rounded-16 h-full w-full absolute inset-0 object-cover' />;
};
const renderPlayButton = () => {
return <i className='fad fa-play-circle text-4xl md:text-6xl text-gray-200 hover:text-gray-100 cursor-pointer absolute z-1' onClick={handlePlayClick} />;
};
const handleIframeLoad = () => {
setIsVideoLoaded(true);
};
return (
<Fragment>
<Helmet>
<script src='https://player.vimeo.com/api/player.js'></script>
<script src='https://www.youtube.com/iframe_api'></script>
</Helmet>
<div className='rounded-16 h-full w-full relative flex items-center justify-center' onMouseEnter={() => setIsVideoHovered(true)} onMouseLeave={() => setIsVideoHovered(false)}>
{isFirstImageVisible && renderFirstImage()}
<iframe id={iframeId} ref={iframeRef} src={iframeUrl} allow='autoplay; fullscreen; picture-in-picture' title='Video' className='h-full w-full rounded-16' onLoad={handleIframeLoad}></iframe>
{isPlayButtonVisible && renderPlayButton()}
{isLastImageVisible && renderLastImage()}
</div>
</Fragment>
);
};
export default VideoExternal;
When the component loads the video starts playing (video.autoplay is true), and when video is ended I see console.log('Video ended.');
When I then click on play button and (executes handlePlayClick function) and thus playerRef?.current?.playVideo();
is executed, the video starts playing, but when it ends I do not see console.log('isEnded: ', true).
Any idea why? Do I need to reinitialize the iframe or is there something else that I'm missing?
Thanks.
Upvotes: 0
Views: 102
Reputation: 1
The issue seems to be related to the way the YouTube player instance is being initialized and handled. This is what you should try:
useEffect
hook: Instead of initializing the YouTube player instance inside the useEffect
hook, initialize it in the component itself. This ensures that the player instance is not recreated on every render, which might be causing the issue.const VideoExternal = ({ video, isOnScreen, item, context }) => {
// ...
const [player, setPlayer] = useState(null);
useEffect(() => {
if (isOnScreen && isVideoLoaded && platform === PLATFORMS.YOUTUBE) {
const iframe = iframeRef.current;
if (iframe) {
const newPlayer = new YT.Player(iframeId, {
events: {
onReady: () => {
console.log('onReady');
},
onStateChange: (event) => {
if (!video.loop && event.data === YT.PlayerState.ENDED) {
setIsPlaying(false);
setIsMuted(true);
setShowLastImage(true);
console.log('Video ended.');
}
},
},
});
setPlayer(newPlayer);
}
}
}, [isOnScreen, isVideoLoaded]);
// ...
};
player
state to control the video playback: Instead of using playerRef.current
, use the player
state to control the video playback.const handlePlayClick = () => {
const iframe = iframeRef?.current;
const iframeWindow = iframe?.contentWindow;
if (iframe && isVideoLoaded) {
if (platform === PLATFORMS.YOUTUBE) {
player?.playVideo();
} else if (platform === PLATFORMS.VIMEO) {
playerRef?.current?.play();
} else if (platform === PLATFORMS.BUNNY && iframeWindow) {
iframeWindow?.postMessage('{"event":"command","func":"play"}', '*');
}
setIsPaused(false);
setIsPlaying(true);
setIsMuted(false);
setShowLastImage(false);
}
};
useEffect
hook with an empty dependency array to clean up the player instance when the component unmounts.useEffect(() => {
return () => {
if (player) {
player.destroy();
}
};
}, []);
Upvotes: 0