Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 157 additions & 92 deletions next-cloudinary/src/components/CldVideoPlayer/CldVideoPlayer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useRef, MutableRefObject, useEffect} from 'react';
import Script from 'next/script';
import React, { useRef, MutableRefObject, useEffect} from 'react';
import Script, { ScriptProps } from 'next/script';
import Head from 'next/head';
import { parseUrl } from '@cloudinary-util/util';
import { CloudinaryVideoPlayer, CloudinaryVideoPlayerOptionsLogo, CloudinaryVideoPlayerOptions, } from '@cloudinary-util/types';
Expand All @@ -15,7 +15,6 @@ let playerInstances: string[] = [];
const PLAYER_VERSION = '1.10.6';

const CldVideoPlayer = (props: CldVideoPlayerProps) => {

const {
autoplay,
className,
Expand All @@ -26,6 +25,7 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => {
id,
language,
languages,
loading = 'lazy',
logo = true,
loop = false,
muted = false,
Expand All @@ -44,10 +44,10 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => {
...otherCldVidPlayerOptions
} = props as CldVideoPlayerProps;

const shouldInitializeRef = useRef(false);
const playerTransformations = Array.isArray(transformation) ? transformation : [transformation];
let publicId: string = src || "";


// If the publicId/src is a URL, attempt to parse it as a Cloudinary URL
// to get the public ID alone

Expand Down Expand Up @@ -83,15 +83,19 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => {
}

// Check if the same id is being used for multiple video instances.

const checkForMultipleInstance = playerInstances.filter((id) => id === playerId).length > 1
if (checkForMultipleInstance) {
console.warn(`Multiple instances of the same video detected on the
page which may cause some features to not work.
Try adding a unique id to each player.`)
page which may cause some features to not work.
Try adding a unique id to each player.`)
} else {
playerInstances.push(playerId)
}


// Map callback handlers based on names for the player options

const events: Record<string, Function|undefined> = {
error: onError,
loadeddata: onDataLoad,
Expand All @@ -101,6 +105,87 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => {
ended: onEnded
};

// Collect player configuration

let logoOptions: CloudinaryVideoPlayerOptionsLogo = {};

if ( typeof logo === 'boolean' ) {
logoOptions.showLogo = logo;
} else if ( typeof logo === 'object' ) {
logoOptions = {
...logoOptions,
showLogo: true,
logoImageUrl: logo.imageUrl,
logoOnclickUrl: logo.onClickUrl
}
}

// Parse the value passed to 'autoplay';
// if its a boolean or a boolean passed as string ("true") set it directly to browser standard prop autoplay else fallback to default;
// if its a string and not a boolean passed as string ("true") set it to cloudinary video player autoplayMode prop else fallback to undefined;

let autoPlayValue: boolean | 'true' | 'false' = false;
let autoplayModeValue: string | undefined = undefined;

if (typeof autoplay === 'boolean' || autoplay === 'true' || autoplay === 'false') {
autoPlayValue = autoplay
}

if (typeof autoplay === 'string' && autoplay !== 'true' && autoplay !== 'false') {
autoplayModeValue = autoplay;
}

let playerOptions: CloudinaryVideoPlayerOptions = {
autoplayMode: autoplayModeValue,
autoplay: autoPlayValue,
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
controls,
fontFace: fontFace || '',
language,
languages,
loop,
muted,
publicId,
transformation: playerTransformations,
...logoOptions,
...otherCldVidPlayerOptions
};

if ( Array.isArray(sourceTypes) ) {
playerOptions.sourceTypes = sourceTypes;
}

if ( typeof colors === 'object' ) {
playerOptions.colors = colors;
}

if ( typeof poster === 'string' ) {
// If poster is a string, assume it's either a public ID
// or a remote URL, in either case pass to `publicId`
playerOptions.posterOptions = {
publicId: poster
};
} else if ( typeof poster === 'object' ) {
// If poster is an object, we can either customize the
// automatically generated image from the video or generate
// a completely new image from a separate public ID, so look
// to see if the src is explicitly set to determine whether
// or not to use the video's ID or just pass things along
if ( typeof poster.src !== 'string' ) {
playerOptions.posterOptions = {
publicId: getCldVideoUrl({
...poster,
src: publicId,
format: 'auto:image',
})
};
} else {
playerOptions.posterOptions = {
publicId: getCldImageUrl(poster)
};
}
}

/**
* handleEvent
* @description Event handler for all player events
Expand All @@ -117,106 +202,86 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => {
//Check if Cloud Name exists
checkForCloudName(process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME);

/**
* handleOnLoad
* @description Stores the Cloudinary window instance to a ref when the widget script loads
*/
// Determine what value to use when loading the player script via Next Script
// Really the only time we'll be setting a value here is if we're using idle
// in which we'll use the Script tag's method instead of trying to run our own

function handleOnLoad() {
if ( 'cloudinary' in window ) {
cloudinaryRef.current = window.cloudinary;
let logoOptions: CloudinaryVideoPlayerOptionsLogo = {};

if ( typeof logo === 'boolean' ) {
logoOptions.showLogo = logo;
} else if ( typeof logo === 'object' ) {
logoOptions = {
...logoOptions,
showLogo: true,
logoImageUrl: logo.imageUrl,
logoOnclickUrl: logo.onClickUrl
}
}
let scriptStrategy: ScriptProps['strategy'] | undefined = undefined;

// Parse the value passed to 'autoplay';
// if its a boolean or a boolean passed as string ("true") set it directly to browser standard prop autoplay else fallback to default;
// if its a string and not a boolean passed as string ("true") set it to cloudinary video player autoplayMode prop else fallback to undefined;
// We're choosing a different definition of lazyloading here, where lazy loading
// will work more similarly to how images and iframes work, where we'll initialize
// the player closer to when it comes into view, so if we're defining loading
// as idle, we'll use lazyOnload which is on idle

let autoPlayValue: boolean | 'true' | 'false' = false;
let autoplayModeValue: string | undefined = undefined;
if ( ['idle', 'lazy'].includes(loading) ) {
scriptStrategy = 'lazyOnload';
}

if (typeof autoplay === 'boolean' || autoplay === 'true' || autoplay === 'false') {
autoPlayValue = autoplay
}
useEffect(() => {
if ( !videoRef.current || loading !== 'lazy' ) return;

if (typeof autoplay === 'string' && autoplay !== 'true' && autoplay !== 'false') {
autoplayModeValue = autoplay;
}
const video = videoRef.current as Element;

const observer = new IntersectionObserver((entries, observer) => {
if ( entries[0].isIntersecting ) {
shouldInitializeRef.current = true;

let playerOptions: CloudinaryVideoPlayerOptions = {
autoplayMode: autoplayModeValue,
autoplay: autoPlayValue,
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
controls,
fontFace: fontFace || '',
language,
languages,
loop,
muted,
publicId,
transformation: playerTransformations,
...logoOptions,
...otherCldVidPlayerOptions
};
if ( cloudinaryRef.current ) {
initializePlayer();
}

if ( Array.isArray(sourceTypes) ) {
playerOptions.sourceTypes = sourceTypes;
observer.unobserve(video);
}
});

observer.observe(video);

if ( typeof colors === 'object' ) {
playerOptions.colors = colors;
}
return () => {
observer.unobserve(video);
}
}, [videoRef.current, loading]);

/**
* handleOnLoad
* @description Stores the Cloudinary window instance to a ref when the widget script loads
*/

if ( typeof poster === 'string' ) {
// If poster is a string, assume it's either a public ID
// or a remote URL, in either case pass to `publicId`
playerOptions.posterOptions = {
publicId: poster
};
} else if ( typeof poster === 'object' ) {
// If poster is an object, we can either customize the
// automatically generated image from the video or generate
// a completely new image from a separate public ID, so look
// to see if the src is explicitly set to determine whether
// or not to use the video's ID or just pass things along
if ( typeof poster.src !== 'string' ) {
playerOptions.posterOptions = {
publicId: getCldVideoUrl({
...poster,
src: publicId,
format: 'auto:image',
})
};
function handleOnLoad() {
if ( 'cloudinary' in window ) {
cloudinaryRef.current = window.cloudinary;

if ( loading === 'eager' || ( loading === 'lazy' && shouldInitializeRef.current) ) {
initializePlayer();
} else if ( loading === 'idle' ) {
if ( 'requestIdleCallback' in window ) {
requestIdleCallback(() => initializePlayer());
} else {
playerOptions.posterOptions = {
publicId: getCldImageUrl(poster)
};
setTimeout(() => initializePlayer(), 1);
}
}

playerRef.current = cloudinaryRef.current.videoPlayer(videoRef.current, playerOptions);

Object.keys(events).forEach((key) => {
if ( typeof events[key] === 'function' ) {
playerRef.current?.on(key, handleEvent);
}
});
shouldInitializeRef.current = true;
}
}

useEffect(() => {
/**
* initializePlayer
* @description Creates new player instance and sets player events
*/

function initializePlayer() {
if ( !cloudinaryRef.current ) return;

playerRef.current = cloudinaryRef.current.videoPlayer(videoRef.current, playerOptions);

Object.keys(events).forEach((key) => {
if ( typeof events[key] === 'function' ) {
playerRef.current?.on(key, handleEvent);
}
});
}

useEffect(() => {
return () => {
//@ts-ignore
playerRef.current?.videojs.cloudinary.dispose();
Expand All @@ -234,7 +299,6 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => {
video: videoRef.current
}
}

return (
<>
<Head>
Expand All @@ -249,10 +313,11 @@ const CldVideoPlayer = (props: CldVideoPlayerProps) => {
height={height}
/>
<Script
id={`cloudinary-videoplayer-${playerId}-${Math.floor(Math.random() * 100)}`}
src={`https://unpkg.com/cloudinary-video-player@${PLAYER_VERSION}/dist/cld-video-player.min.js`}
onLoad={handleOnLoad}
onError={(e) => console.error(`Failed to load Cloudinary Video Player: ${e.message}`)}
strategy={scriptStrategy}
id={`cloudinary-videoplayer-${playerId}-${Math.floor(Math.random() * 100)}`}
src={`https://unpkg.com/cloudinary-video-player@${PLAYER_VERSION}/dist/cld-video-player.min.js`}
onLoad={handleOnLoad}
onError={(e) => console.error(`Failed to load Cloudinary Video Player: ${e.message}`)}
/>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type CldVideoPlayerProps = Omit<CloudinaryVideoPlayerOptions, "cloud_name
className?: string;
height: string | number;
id?: string;
loading?: 'eager' | 'idle' | 'lazy' | 'click';
logo?: boolean | CldVideoPlayerPropsLogo;
onDataLoad?: Function;
onError?: Function;
Expand Down