diff --git a/README.md b/README.md
index 70a02ed..100ca84 100644
--- a/README.md
+++ b/README.md
@@ -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:
@@ -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 ;
+ return ;
}
```
```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 ;
+ return ;
}
```
diff --git a/examples/default-provider/app/custom-player/page.tsx b/examples/default-provider/app/custom-player/page.tsx
new file mode 100644
index 0000000..004c6de
--- /dev/null
+++ b/examples/default-provider/app/custom-player/page.tsx
@@ -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 (
+ <>
+
+ >
+ );
+}
diff --git a/examples/default-provider/app/custom-player/player.tsx b/examples/default-provider/app/custom-player/player.tsx
new file mode 100644
index 0000000..6694f81
--- /dev/null
+++ b/examples/default-provider/app/custom-player/player.tsx
@@ -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 ;
+}
diff --git a/examples/default-provider/app/sidebar-nav.tsx b/examples/default-provider/app/sidebar-nav.tsx
index a86a021..3b5fe07 100644
--- a/examples/default-provider/app/sidebar-nav.tsx
+++ b/examples/default-provider/app/sidebar-nav.tsx
@@ -12,10 +12,13 @@ export default function SidebarNav() {
Basic example
- Background Video
+ Background video
- Slotted Poster
+ Custom player
+
+
+ Slotted poster
String video source
diff --git a/examples/default-provider/package-lock.json b/examples/default-provider/package-lock.json
index b0effca..58fb0b4 100644
--- a/examples/default-provider/package-lock.json
+++ b/examples/default-provider/package-lock.json
@@ -12,7 +12,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",
@@ -24,7 +25,7 @@
}
},
"../..": {
- "version": "1.0.0",
+ "version": "1.0.1",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.540.0",
@@ -1079,6 +1080,14 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -2720,6 +2729,11 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/load-script": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
+ "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA=="
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -2761,6 +2775,11 @@
"node": "14 || >=16.14"
}
},
+ "node_modules/memoize-one": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -2899,7 +2918,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3211,7 +3229,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -3270,11 +3287,30 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-player": {
+ "version": "3.0.0-canary.0",
+ "resolved": "https://registry.npmjs.org/react-player/-/react-player-3.0.0-canary.0.tgz",
+ "integrity": "sha512-g8r010vlfeCtt4Wxd7UA1RdXfvSK2KYu5k/HC4JS6IngynnV/Q0ODsIzzv6JguMaLl0I7ccPFj3juyEYTigvOg==",
+ "dependencies": {
+ "deepmerge": "^4.0.0",
+ "load-script": "^1.0.0",
+ "memoize-one": "^5.1.1",
+ "prop-types": "^15.7.2",
+ "react-fast-compare": "^3.0.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0"
+ }
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.5",
diff --git a/examples/default-provider/package.json b/examples/default-provider/package.json
index 7ef030d..4247d0c 100644
--- a/examples/default-provider/package.json
+++ b/examples/default-provider/package.json
@@ -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",
diff --git a/src/components/players/background-player.tsx b/src/components/players/background-player.tsx
index 1427015..c2f3c0d 100644
--- a/src/components/players/background-player.tsx
+++ b/src/components/players/background-player.tsx
@@ -12,17 +12,8 @@ import { svgBlurImage } from '../utils.js';
export type BackgroundPlayerProps = Omit;
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';
@@ -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);
diff --git a/src/components/types.ts b/src/components/types.ts
index 989b433..1b9e7c3 100644
--- a/src/components/types.ts
+++ b/src/components/types.ts
@@ -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;
}
diff --git a/src/components/video.tsx b/src/components/video.tsx
index 1832ac6..8cb7680 100644
--- a/src/components/video.tsx
+++ b/src/components/video.tsx
@@ -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.
@@ -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 (
-