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

Fix Hydration Issue on BackgroundVideo #340

Merged
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
200 changes: 97 additions & 103 deletions src/components/players/background-player.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,78 @@
'use client';

import { forwardRef, Children, isValidElement, useState } from 'react';
import { forwardRef, Children, isValidElement, useState, Suspense } from 'react';
import Media from './media/index.js';
import { getPlaybackId, getPosterURLFromPlaybackId } from '../../providers/mux/transformer.js';
import { svgBlurImage } from '../utils.js';

import type { PlayerProps } from '../types.js';
import type { MediaProps } from './media/index.js';

const BackgroundPlayer = forwardRef<HTMLVideoElement, Omit<MediaProps, 'ref'> & PlayerProps>((allProps, forwardedRef) => {
let {
style,
className,
children,
asset,
poster,
blurDataURL,
onPlaying,
onLoadStart,
...rest
} = allProps;

const slottedPoster = Children.toArray(children).find((child) => {
return typeof child === 'object' && 'type' in child && (child.props as any).slot === 'poster';
});

// If there's a slotted poster image (e.g. next/image) remove the default player poster and blurDataURL.
if (isValidElement(slottedPoster)) {
poster = '';
blurDataURL = undefined;
}
const BackgroundPlayer = forwardRef<HTMLVideoElement, Omit<MediaProps, 'ref'> & PlayerProps>(
(allProps, forwardedRef) => {
let { style, className, children, asset, poster, blurDataURL, onPlaying, onLoadStart, ...rest } = allProps;

const props = rest as MediaProps & { thumbnailTime?: number };
const imgStyleProps: React.CSSProperties = {};
const playbackId = asset ? getPlaybackId(asset) : undefined;

let isCustomPoster = true;
let srcSet: string | undefined;

if (playbackId && asset?.status === 'ready') {
props.src = undefined;
props.playbackId = playbackId;

if (poster) {
isCustomPoster = poster !== getPosterURLFromPlaybackId(playbackId, props);

if (!isCustomPoster) {
// If it's not a custom poster URL, optimize with a srcset.
srcSet =
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 480 })} 480w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 640 })} 640w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 960 })} 960w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 1280 })} 1280w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 1600 })} 1600w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props })} 1920w`;
}
const slottedPoster = Children.toArray(children).find((child) => {
return typeof child === 'object' && 'type' in child && (child.props as any).slot === 'poster';
});

// If there's a slotted poster image (e.g. next/image) remove the default player poster and blurDataURL.
if (isValidElement(slottedPoster)) {
poster = '';
blurDataURL = undefined;
}
}

if (blurDataURL) {
const showGeneratedBlur = !isCustomPoster && blurDataURL === asset?.blurDataURL;
const showCustomBlur = isCustomPoster && blurDataURL !== asset?.blurDataURL;

if (showGeneratedBlur || showCustomBlur) {
imgStyleProps.width = '100%';
imgStyleProps.height = '100%';
imgStyleProps.color = 'transparent';
imgStyleProps.backgroundSize = 'cover';
imgStyleProps.backgroundPosition = 'center';
imgStyleProps.backgroundRepeat = 'no-repeat';
imgStyleProps.backgroundImage = `url('data:image/svg+xml;charset=utf-8,${svgBlurImage(blurDataURL)}')`;
const props = rest as MediaProps & { thumbnailTime?: number };
const imgStyleProps: React.CSSProperties = {};
const playbackId = asset ? getPlaybackId(asset) : undefined;

let isCustomPoster = true;
let srcSet: string | undefined;

if (playbackId && asset?.status === 'ready') {
props.src = undefined;
props.playbackId = playbackId;

if (poster) {
isCustomPoster = poster !== getPosterURLFromPlaybackId(playbackId, props);

if (!isCustomPoster) {
// If it's not a custom poster URL, optimize with a srcset.
srcSet =
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 480 })} 480w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 640 })} 640w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 960 })} 960w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 1280 })} 1280w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props, width: 1600 })} 1600w,` +
`${getPosterURLFromPlaybackId(playbackId, { ...props })} 1920w`;
}
}
}
}

// Remove props that are not supported by MuxVideo.
delete props.thumbnailTime;
if (blurDataURL) {
const showGeneratedBlur = !isCustomPoster && blurDataURL === asset?.blurDataURL;
const showCustomBlur = isCustomPoster && blurDataURL !== asset?.blurDataURL;

if (showGeneratedBlur || showCustomBlur) {
imgStyleProps.width = '100%';
imgStyleProps.height = '100%';
imgStyleProps.color = 'transparent';
imgStyleProps.backgroundSize = 'cover';
imgStyleProps.backgroundPosition = 'center';
imgStyleProps.backgroundRepeat = 'no-repeat';
imgStyleProps.backgroundImage = `url('data:image/svg+xml;charset=utf-8,${svgBlurImage(blurDataURL)}')`;
}
}

const [posterHidden, setPosterHidden] = useState(false);
// Remove props that are not supported by MuxVideo.
delete props.thumbnailTime;
const [posterHidden, setPosterHidden] = useState(false);

return (
<>
<style>{
/* css */`
return (
<Suspense fallback={null}>
<>
<style>{
/* css */ `
.next-video-bg {
width: 100%;
height: 100%;
Expand Down Expand Up @@ -117,40 +108,43 @@ const BackgroundPlayer = forwardRef<HTMLVideoElement, Omit<MediaProps, 'ref'> &
padding: 5% 5% 10%;
}
`
}</style>
<div className={`${className ? `${className} ` : ''}next-video-bg`} style={{ ...style }}>
<Media
ref={forwardedRef}
className="next-video-bg-video"
onPlaying={(event) => {
onPlaying?.(event as any);
setPosterHidden(true);
}}
onLoadStart={(event) => {
onLoadStart?.(event as any);
setPosterHidden(false);
}}
muted={true}
autoPlay={true}
loop={true}
playsInline={true}
{...props}
/>
{poster && (
<img
className="next-video-bg-poster"
src={isCustomPoster ? poster : undefined}
srcSet={srcSet}
style={imgStyleProps}
hidden={posterHidden}
decoding="async"
aria-hidden="true"
/>
)}
<div className="next-video-bg-text">{children}</div>
</div>
</>
);
});
}</style>
<div className={`${className ? `${className} ` : ''}next-video-bg`} style={{ ...style }}>
<Media
suppressHydrationWarning
ref={forwardedRef}
className="next-video-bg-video"
onPlaying={(event) => {
onPlaying?.(event as any);
setPosterHidden(true);
}}
onLoadStart={(event) => {
onLoadStart?.(event as any);
setPosterHidden(false);
}}
muted={true}
autoPlay={true}
loop={true}
playsInline={true}
{...props}
/>
{poster && (
<img
className="next-video-bg-poster"
src={isCustomPoster ? poster : undefined}
srcSet={srcSet}
style={imgStyleProps}
hidden={posterHidden}
decoding="async"
aria-hidden="true"
/>
)}
<div className="next-video-bg-text">{children}</div>
</div>
</>
</Suspense>
);
}
);

export default BackgroundPlayer;