Skip to content

Commit

Permalink
fix: fix custom player types + add react-player example (#242)
Browse files Browse the repository at this point in the history
* example: add custom player. react-player

* docs: add custom player demo

* fix: player types & update readme
  • Loading branch information
luwes authored Apr 19, 2024
1 parent 3efcbbb commit 6895a1b
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 49 deletions.
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export default function Page() {
```


### Custom Player
### Custom Player ([Demo](https://next-video-demo.vercel.app/custom-player))

You can customize the player by passing a custom player component to the `as` prop.
The custom player component accepts the following props:
Expand All @@ -254,23 +254,32 @@ The custom player component accepts the following props:

```tsx
import Video from 'next-video';
import { ReactPlayerAsVideo } from './player';
import ReactPlayer from './player';
import awesomeVideo from '/videos/awesome-video.mp4';

export default function Page() {
return <Video as={ReactPlayerAsVideo} src={awesomeVideo} />;
return <Video as={ReactPlayer} src={awesomeVideo} />;
}
```

```tsx
// player.js
// player.tsx
'use client';

import type { PlayerProps } from 'next-video';
import ReactPlayer from 'react-player';

export function ReactPlayerAsVideo(props) {
let { asset, src, poster, blurDataURL, ...rest } = props;
export default function Player(props: PlayerProps) {
let { asset, src, poster, blurDataURL, thumbnailTime, ...rest } = props;
let config = { file: { attributes: { poster } } };

return <ReactPlayer url={src} config={config} {...rest} />;
return <ReactPlayer
url={src}
config={config}
width="100%"
height="100%"
{...rest}
/>;
}
```

Expand Down
18 changes: 18 additions & 0 deletions examples/default-provider/app/custom-player/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Metadata } from 'next';
import Video from 'next-video';
import ReactPlayer from './player'
import getStarted from '/videos/country-clouds.mp4';

export const metadata: Metadata = {
title: 'next-video - Custom Player',
};

export default function Page() {
return (
<>
<section>
<Video as={ReactPlayer} src={getStarted} />
</section>
</>
);
}
17 changes: 17 additions & 0 deletions examples/default-provider/app/custom-player/player.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client';

import type { PlayerProps } from 'next-video';
import ReactPlayer from 'react-player';

export default function Player(props: PlayerProps) {
let { asset, src, poster, blurDataURL, thumbnailTime, ...rest } = props;
let config = { file: { attributes: { poster } } };

return <ReactPlayer
url={src}
config={config}
width="100%"
height="100%"
{...rest}
/>;
}
7 changes: 5 additions & 2 deletions examples/default-provider/app/sidebar-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ export default function SidebarNav() {
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">Basic example</Link>
</li>
<li>
<Link className={`link ${pathname === '/background-video' ? 'active' : ''}`} href="/background-video">Background Video</Link>
<Link className={`link ${pathname === '/background-video' ? 'active' : ''}`} href="/background-video">Background video</Link>
</li>
<li>
<Link className={`link ${pathname === '/slotted-poster' ? 'active' : ''}`} href="/slotted-poster">Slotted Poster</Link>
<Link className={`link ${pathname === '/custom-player' ? 'active' : ''}`} href="/custom-player">Custom player</Link>
</li>
<li>
<Link className={`link ${pathname === '/slotted-poster' ? 'active' : ''}`} href="/slotted-poster">Slotted poster</Link>
</li>
<li>
<Link className={`link ${pathname === '/string-source' ? 'active' : ''}`} href="/string-source">String video source</Link>
Expand Down
48 changes: 42 additions & 6 deletions examples/default-provider/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/default-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"next-video": "file:../..",
"open-props": "^1.7.3",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-player": "^3.0.0-canary.0"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
15 changes: 3 additions & 12 deletions src/components/players/background-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,8 @@ import { svgBlurImage } from '../utils.js';
export type BackgroundPlayerProps = Omit<DefaultPlayerProps, 'src'>;

export const BackgroundPlayer = forwardRef((allProps: BackgroundPlayerProps, forwardedRef: any) => {
let {
style,
children,
asset,
controls,
poster,
blurDataURL,
onPlaying,
onLoadStart,
...rest
} = allProps;
let { style, children, asset, controls, poster, blurDataURL, onPlaying, onLoadStart, ...rest } =
allProps;

const slottedPoster = Children.toArray(children).find((child) => {
return typeof child === 'object' && 'type' in child && child.props.slot === 'poster';
Expand Down Expand Up @@ -75,7 +66,7 @@ export const BackgroundPlayer = forwardRef((allProps: BackgroundPlayerProps, for
}

// Remove props that are not supported by MuxVideo.
delete props.thumbnailTime
delete props.thumbnailTime;

const [posterHidden, setPosterHidden] = useState(false);

Expand Down
10 changes: 10 additions & 0 deletions src/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,19 @@ export interface PlayerProps {
*/
controls?: boolean;

/**
* The poster image for the video.
*/
poster?: StaticImageData | string;

/**
* Set a manual data URL to be used as a placeholder image before the poster image successfully loads.
* For imported videos this will be automatically generated.
*/
blurDataURL?: string;

/**
* The thumbnail time in seconds to use for the video poster image.
*/
thumbnailTime?: number;
}
38 changes: 17 additions & 21 deletions src/components/video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ import { forwardRef, useState } from 'react';
import { DefaultPlayer } from './players/default-player.js';
import { Alert } from './alert.js';
import { createVideoRequest, defaultLoader } from './video-loader.js';
import { config, camelCase, toSymlinkPath, usePolling, isReactComponent, getUrlExtension } from './utils.js';
import * as transformers from '../providers/transformers.js';
import {
config,
camelCase,
toSymlinkPath,
usePolling,
isReactComponent,
getUrlExtension,
} from './utils.js';

import type { DefaultPlayerProps } from './players/default-player.js';
import type { Asset } from '../assets.js';
import type { VideoLoaderProps, VideoProps, VideoPropsInternal } from './types.js';
import type { VideoLoaderProps, VideoProps, VideoPropsInternal, PlayerProps } from './types.js';

const NextVideo = forwardRef((props: VideoProps, forwardedRef) => {
// Keep in component so we can emulate the DEV_MODE.
Expand Down Expand Up @@ -52,22 +59,16 @@ const NextVideo = forwardRef((props: VideoProps, forwardedRef) => {

usePolling(request, needsPolling ? 1000 : null);

const videoProps = getVideoProps(
{ ...props, transform, src } as VideoPropsInternal,
{ asset }
);
const videoProps = getVideoProps({ ...props, transform, src } as VideoPropsInternal, { asset });

if (!isReactComponent(VideoPlayer)) {
console.warn('The `as` property is not a valid component:', VideoPlayer);
}

return (
<div
className={`${className ? `${className} ` : ''}next-video-container`}
style={style}
>
<div className={`${className ? `${className} ` : ''}next-video-container`} style={style}>
<style>{
/* css */`
/* css */ `
.next-video-container {
position: relative;
width: 100%;
Expand Down Expand Up @@ -102,10 +103,9 @@ const NextVideo = forwardRef((props: VideoProps, forwardedRef) => {
{...videoProps}
></VideoPlayer>

{DEV_MODE && <Alert
hidden={Boolean(playing || !status || status === 'ready')}
status={status}
/>}
{DEV_MODE && (
<Alert hidden={Boolean(playing || !status || status === 'ready')} status={status} />
)}
</div>
);
});
Expand All @@ -130,7 +130,7 @@ export function getVideoProps(allProps: VideoPropsInternal, state: { asset?: Ass
src: src as string | undefined,
controls,
blurDataURL,
...rest
...rest,
};

// Handle StaticImageData which are image imports that resolve to an object.
Expand Down Expand Up @@ -171,8 +171,4 @@ function defaultTransformer(asset: Asset, props: Record<string, any>) {

export default NextVideo;

export type {
VideoLoaderProps,
VideoProps,
DefaultPlayerProps,
};
export type { VideoLoaderProps, VideoProps, DefaultPlayerProps, PlayerProps };

0 comments on commit 6895a1b

Please sign in to comment.