sanity-plugin-media-video
v1.0.4
Published
A Sanity plugin for adding a media object (Image/Video) to your sanity studio schemas and displaying the media with built-in functionalities such as auto-play, custom PiP on scroll, etc.
Downloads
335
Maintainers
Readme
Sanity Plugin Media Video
A Sanity plugin for adding a media object (Image/Video) to your sanity studio schemas and displaying the media with built-in functionalities such as auto-play, custom PiP on scroll, etc.
This is a Sanity Studio v3 plugin.
- Sanity Plugin Media Video
🔌 Installation
Install peer dependencies such as zod
and sanity-plugin-mux-input
as well since this uses Mux as part of the video encoding service.
npm install sanity-plugin-media-video sanity-plugin-mux-input zod
🧑💻 Usage
Add it as a plugin in sanity.config.ts (or .js):
Basic configuration
// sanity.config.ts
import { defineConfig } from 'sanity';
import { mediaVideoPlugin } from 'sanity-plugin-media-video';
export default defineConfig({
//...
plugins: [
// Add the muxInput from `sanity-plugin-mux-input` to make the mux 3rd party integration work
muxInput({
// your optional mux config here. Refer to this link for more info: https://github.com/sanity-io/sanity-plugin-mux-input?tab=readme-ov-file#configuring-mux-video-uploads
}),
// Add the mediaVideo plugin
mediaVideoPlugin({
// your optional configuration here
}),
],
});
The plugin adds a new object type called media
. Simply add it as a type
to one of your defined fields like so
export default defineType({
name: 'my-section',
title: 'My Example Section',
type: 'object',
fields: [
// ...your-other-fields
defineField({
name: 'my-custom-media-field',
title: 'My Custom Media Field',
type: 'media',
}),
// ...your-other-fields
],
});
Structure would look something like this:
⚙️ Plugin Configuration
This is the main configuration of the plugin. The available options are:
{
// Optional boolean to enable/disable required validation on the image field
isImageRequired?: boolean
}
🌐 Localization
This plugin uses the Studio UI Localization resource bundle, it is now possible to localize the fields to fit your needs.
Here is the default English bundle:
{
'image.title': 'Image',
'image.description': 'Serves as the image preview of the video',
'image.required.title': 'Image is required',
'image.altText.title': 'Alt Text',
'image.altText.description':
'Set an alternative text for accessibility purposes',
'enableVideo.title': 'Enable Video',
'enableVideo.description': 'Toggle to enable video',
'videoType.title': 'Video Type',
'videoType.link.title': 'Link',
'videoType.mux.title': 'Mux',
'videoType.required.title': 'Video Type is required',
'isAutoPlay.title': 'Auto Play',
'isAutoPlay.description': 'Automatically play the video when loaded',
'isPipAutomatic.title': 'Enable Automatic PiP for Autoplay',
'isPipAutomatic.description':
'This automatically creates a small floating video player when you scroll past the main video',
'videoUrl.title': 'Video Link',
'videoUrl.required.title': 'Video Link is required',
'muxVideo.required.title': 'Mux Video is required',
}
Available locales to be overriden
en-US
de-DE
If you want to override or add a new language, you will need to create a custom bundle with your desired translations. In order to override/add you must use mediaVideo
as the namespace and add it to the i18n
object in your sanity plugin configuration. Here is an example:
const myEnglishOverride = defineLocaleResourceBundle({
// make sure the `locale` language code corresponds to the one you want to override
locale: 'en-US',
namespace: 'mediaVideo',
resources: {
'image.title': 'This is my override title',
},
});
// sanity.config.ts
export default defineConfig({
// ...
i18n: {
bundles: [myEnglishOverride],
},
});
🎬 How to render the media video on your website
This plugin gives out a ready out of the box made component for rendering the media video on your website with built-in functionalities such as auto-play, custom PiP on scroll, etc. One way to render it is to use the provided renderer component or you can implement your own renderer component with the data that you get from the media
object.
NOTE
- This plugin uses the react-player library for rendering the media video.
- The renderer also needs to be wrapped within the provided custom Provider component in order to function properly.
1. Import the Provider component and wrap the renderer component with it. Suggested to be placed in the root of your application.
import { MediaVideoProvider } from 'sanity-plugin-media-video/contexts';
// other code
<body>
<MediaVideoProvider>
{/* Your renderer component here or other components */}
</MediaVideoProvider>
</body>;
2. Use the renderer component like so. Example usage below
import { MediaVideo } from 'sanity-plugin-media-video/renderer';
const MyComponent = (props) => {
return (
<MediaVideo
className='my-custom-class-name'
classNames={{
containerCn: 'my-custom-container-class-name',
imageContainerCn: 'my-custom-image-container-class-name',
imageCn: 'my-custom-image-class-name',
videoBackgroundCn: 'my-custom-video-background-class-name',
inlineVideoBackgroundCn: 'my-custom-inline-video-background-class-name',
videoCn: 'my-custom-video-class-name',
dialogTriggerCn: 'my-custom-dialog-trigger-class-name',
dialogContentCn: 'my-custom-dialog-content-class-name',
dialogOverlayCn: 'my-custom-dialog-overlay-class-name',
dialogCloseCn: 'my-custom-dialog-close-class-name',
playBtnContainerCn: 'my-custom-play-button-container-class-name',
playBtnCn: 'my-custom-play-button-class-name',
}}
videoUrl={videoUrl}
imagePreview={imagePreview}
isAutoPlay={isAutoPlay}
isPipAutomatic={isPipAutomatic}
videoType={videoType ?? 'link'}
muxData={muxData}
/>
);
};
Props
| Prop | Description | Default | Required |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- |
| videoUrl
| The URL of the video to be displayed. | undefined
| Yes |
| muxData
| Data object associated with Mux video assets for advanced integrations. | undefined
| No |
| videoType
| Type of video (default is 'link'), could be values like 'link', 'mux', etc. | 'link'
| No |
| imagePreview
| The image displayed for the video thumbnail. | null
| Yes |
| isAutoPlay
| Determines if the video should play automatically. | false
| No |
| isPipAutomatic
| Determines if picture-in-picture mode should be enabled automatically. | false
| No |
| customPipId
| Custom ID for the PIP mode, used for managing multiple instances or special configurations. | undefined
| No |
| playInPopout
| Whether to play the video in a popout dialog by default. | undefined
| No |
| playButton
| Custom play button component or element to be displayed as the play trigger. Can pass a React component or a JSX element. | undefined
| No |
| autoPlayVideoPlayerProps
| Props to be passed to the Auto Play Video Background ReactPlayer instance for advanced video player customization. | undefined
| No |
| videoPlayerProps
| Props to be passed to the ReactPlayer instance for advanced video player customization. | undefined
| No |
| isDesktopScreen
| Whether the current screen is in a desktop size. Useful for determining if to play in the popout mode or not. | undefined
| No |
| classNames
| Custom class names for various UI elements to facilitate styling and theming. | undefined
| No |
| ref
| Forwarded ref to the root div element of the component. | undefined
| No |
Custom class names
| Prop | Description |
| ------------------------------------ | -------------------------------------------------------- |
| classNames.containerCn
| Class name for the main container. |
| classNames.imageContainerCn
| Class name for the image container. |
| classNames.imageCn
| Class name for the preview image. |
| classNames.videoBackgroundCn
| Class name for the background of the video. |
| classNames.inlineVideoBackgroundCn
| Class name specifically for the inline video background. |
| classNames.videoCn
| Class name for the video element. |
| classNames.dialogTriggerCn
| Class name for the dialog trigger that opens the popout. |
| classNames.dialogContentCn
| Class name for the content of the dialog. |
| classNames.dialogOverlayCn
| Class name for the overlay of the dialog. |
| classNames.dialogCloseCn
| Class name for the close button of the dialog. |
| classNames.playBtnContainerCn
| Class name for the container of the play button. |
| classNames.playBtnCn
| Class name for the play button itself. |
Use your own implementation
You can use your own implementation if you want since the the Media Video renderer is only an optional added feature in this plugin. Suggested way to do this is by leveraging react-player library to make it easier to render the video.
Optionally the plugin also provides some block components to help you build your own implementation if you want to. You can access them through the MediaVideoComponents
object like so:
import { MediaVideoComponents } from 'sanity-plugin-media-video/renderer';
const MyCustomComponent = (props) => {
return (
<MediaVideoComponents.MediaVideoRoot
ref={ref}
className={className}
{...props}
>
<MediaVideoComponents.MediaVideoContainer>
{/* Your own implementation */}
</MediaVideoComponents.MediaVideoContainer>
</MediaVideoComponents.MediaVideoRoot>
);
};
Using the provided Hooks
You can also use the hooks provided by the plugin to help you build your own implementation:
import { MediaVideoComponents } from 'sanity-plugin-media-video/renderer';
const MyCustomComponent = (props) => {
const isDesktop = useMediaQuery('(min-width: 1024px)'); // Used to know if the screen is desktop or not
const pipUniqueId = uuidv4();
// Setup Intersection Observer to determine when the component is in view
const intersectionObserver = useInView({
rootMargin: '0px',
threshold: [0, 0.5, 1],
});
const { ref: topLevelRef } = intersectionObserver;
// Custom hook to manage video playback state and related events
const {
desktopPopoutPlay,
inlinePlay,
inlinePause,
showImage,
playedByAutoPlay,
isPopoutOpen,
isFloatingPip,
setInlinePause,
handleOnVideoProgress,
handleClickPlay,
handleVideoOnReady,
handleInlineOnPlay,
handleOnPipForceClose,
setActivePip,
} = useMediaVideoPlayback({
/**
* The following props are required for the hook to work
* can refer to the implementation in the MediaVideo component
* for more details
*/
isAutoPlay,
isPipAutomatic,
intersectionObserver,
pipUniqueId,
isDesktop,
playInPopout,
});
return (
{/* Your own implementation */}
)
};
🗃️ Data model
{
_type: 'media';
enableVideo: boolean;
isAutoPlay: boolean;
videoType: 'link' | 'mux';
isPipAutomatic: boolean;
videoUrl: string;
muxVideo: {
_type: 'mux.video';
asset: {
_ref: string;
_type: 'reference';
_weak: boolean;
}
}
image: {
_type: 'image';
asset: {
_type: 'reference';
_ref: string;
}
altText: string;
crop: {
_type: 'sanity.imageCrop';
bottom: number;
left: number;
right: number;
top: number;
}
hotspot: {
_type: 'sanity.imageHotspot';
height: number;
width: number;
x: number;
y: number;
}
}
}
🛢 GROQ Query
You can query the data model with this sample groq query
// Example for fetching data above
*[ _type == "exampleSchemaWithMedia" ] {
myMediaField {
image {
asset->{
_id,
url,
metadata {
lqip
}
},
crop,
hotspot,
altText
},
enableVideo,
videoType,
isAutoPlay,
isPipAutomatic,
videoUrl,
muxVideo {
_type,
asset->{
playbackId,
assetId,
filename,
...
},
},
}
}
❓ FAQs
How to add a new language?
- You can refer to the 🌐 Localization section on how to override or add a new language
Why the need to add an image field to the
media
object?- The image field is used to display a custom preview image of the video. This is useful for SSR performance and SEO purposes as it does not load the video itself directly when the page is rendered.
- This also makes it possible to just be a Media Picture instead if you do not want to use the video feature. With that you can then render it however you want with the image data
📝 License
MIT © Evelan
🧪 Develop & test
This plugin uses @sanity/plugin-kit with default configuration for build & watch scripts.
See Testing a plugin in Sanity Studio on how to run this plugin with hotreload in the studio.
Release new version
Run "CI & Release" workflow. Make sure to select the main branch and check "Release new version".
Semantic release will only release on configured branches, so it is safe to run release on any branch.