Projects
Services
Migration
Blog
Alt textAlt text

A really nice Mux video component

Reading Time

3 min read

Published on

August 8, 2024

Author

Jono

Jono

Hrithik

Hrithik

Shreyas

Shreyas

You might have come across some of the videos on our website and thought: “Gee wowsers, they’re still hitting near perfect performance scores in Lighthouse”. Well, let me tell you: it was a struggle.

However, the good news is, today we want to share what took us far too long to perfect, and give you the opportunity to use Mux yourself, without tanking performance with the Mux Player.

It's also worth mentioning that the Mux player has more compatibility and better video experience (see Feedback from Dylan Jhaveri for more context) but frankly, I'm fairly certain folks aren't browsing our website on IE8, nor do we want to build for IE8, so this is the compromise we are willing to make (sorry dinosaurs 🦕).

The code

'use client';
// Swap these out for another icon set if you want
import { Loader2, PlayIcon } from 'lucide-react';
// To dynamically load the video player
import dynamic from 'next/dynamic';
import { FC, useState } from 'react';
import { preload } from 'react-dom';
// This is from Sanity type generation
import { MuxVideo, Video as VideoType } from '~/sanity.types';
const getVideoToPlayUrl = (muxId: string) => {
return `https://stream.mux.com/${muxId}.m3u8`;
};
const getPosterUrl = (muxId: string) => {
return `https://image.mux.com/${muxId}/thumbnail.webp?fit_mode=smartcrop&time=0`;
};
const getMuxVideoProps = (muxId: string) => {
return {
poster: getPosterUrl(muxId),
vidUrl: getVideoToPlayUrl(muxId),
};
};
type VideoPlay = {
poster: string;
vidUrl: string;
loop?: boolean;
muted?: boolean;
control?: boolean;
};
const VideoAutoPlay = ({ control, loop, muted, poster, vidUrl }: VideoPlay) => {
const HlsVideo = dynamic(
() => import('./hls-video').then((mod) => mod.HlsVideo),
{
loading: () => {
return (
<div className="relative size-full">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={poster}
// Pick your most commonly used video size to avoid CLS
width={1920}
height={1080}
alt="a"
className="mx-auto size-full "
/>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform ">
<Loader2 className="animate-spin" size={112} strokeWidth={0.5} />
</div>
</div>
);
},
},
);
return (
<HlsVideo
autoPlay
poster={poster}
vidUrl={vidUrl}
loop={control ? loop : undefined}
muted={control ? muted : undefined}
controls={control}
width={1920}
height={1080}
className="mx-auto size-full"
/>
);
};
const VideoWithOutAutoPlay = (
props: VideoPlay & { loading?: 'lazy' | 'eager' },
) => {
const { poster, loading } = props;
const [play, setPlay] = useState(false);
return (
<div className=" size-full">
{play ? (
<div className="relative size-full ">
<div className="my-4">
<VideoAutoPlay {...props} />
</div>
</div>
) : (
<div className="relative size-full">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={poster}
width={1920}
height={1080}
loading={loading === 'lazy' ? 'lazy' : 'eager'}
alt="a"
className="mx-auto size-full "
/>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform ">
<button
className="grid place-items-center rounded-full bg-black p-8 hover:bg-hotpink-400"
onClick={() => setPlay(true)}
>
<PlayIcon className="h-8 w-8 fill-white text-white" />
<span className="sr-only">Play</span>
</button>
</div>
</div>
)}
</div>
);
};
export type VideoProps = VideoType & {
videoEmbed: NonNullable<VideoType['videoEmbed']> & {
video: MuxVideo & {
asset: {
playbackId: string;
};
};
};
};
export const Video: FC<VideoProps & { loading?: 'lazy' | 'eager' }> = ({
videoEmbed,
loading = 'eager',
}) => {
const { video, autoPlay, control, loop, muted } = videoEmbed ?? {};
const { poster, vidUrl } = getMuxVideoProps(video?.asset?.playbackId);
preload(vidUrl, {
as: 'video',
fetchPriority: 'high',
});
preload(poster, {
as: 'image',
fetchPriority: 'high',
});
const videoProps = { poster, loading, vidUrl, loop, muted, control };
return (
<div className="mx-auto flex max-w-7xl">
{control && autoPlay ? (
<VideoAutoPlay {...videoProps} />
) : (
<VideoWithOutAutoPlay {...videoProps} loading={loading} />
)}
</div>
);
};

Performance improvements

Well remember how there was some bloke called Charles Goodhart said: "When a measure becomes a target, it ceases to be a good measure". Well we basically ignored everything that person said, and decided to optimise to see if it was possible to hit 100/100 on a website that still has a lot of images, fancy libraries and analytics.

If you're looking to try and get the perfect score on your website, get in touch with us, because we sure as hell know a lot about this stuff, after all the hassle we've been through to try and achieve it.

A quick explainer what this is good for

a

Feedback from Dylan Jhaveri

We posted this on Vercel Community, and got some great feedback and better context. So, before you go ahead and straight up copy the code without knowing the tradeoffs make sure you read this bit.

"Love the work you’ve done in your example to optimize for lighthouse score. There is a fundamental tradeoff here that I think folks should think about:

web vitals (lighthouse score) vs. video performance

The biggest change that I can see, if I’m following correctly is waiting to load <hls-video> until after the user clicks play.

That’s going to make your lighthouse score great, (which is great!) – but the tradeoff is that when the user clicks play it’s going to take more time for playback to start and the user to see the first frame of the video.

For your use case on roboto.studio I think that probably makes perfect sense. For other use cases where you want the video to load fast you may want to lazy load the player 2 (https://docs.mux.com/guides/player-lazy-loading), which tries to balance the tradeoffs:

  • Doesn’t block the initial page load (despite this, your lighthouse scores will be impacted negatively)
  • Lazy loads the player without requiring the user to click play so that when the user does click play the video starts up immediately"
Cheers Dylan, and thanks for the free content ✌️ go read his blog posts here
Get in touch

I mux ask you a question

Get in touch for any lighthouse or media needs, we're always interested in making the web lightning fast, so bringing your hardest questions, we'll get you sorted in no-time.

Services

Legal

Like what you see?

Sign up for our newsletter to stay up to date with our latest projects and insights.

© 2024 Roboto Studio Ltd - 11126043

Roboto Studio Ltd,

71-75 Shelton Street,

Covent Garden,

London, WC2H 9JQ

Registered in England and Wales | VAT Number 426637679