Working with video
Learn how to handle video content in Sanity's Media Library.
Media Library's video streaming capabilities let you present high-quality, adaptive video content directly from your centralized asset storage. Videos uploaded to your Media Library are automatically processed and optimized for streaming, providing reliable video delivery with adaptive quality and global CDN distribution. You can also download the original file you uploaded and static renditions of it from the Media Library interface, separate from the playback stream.
This guide explains how to effectively present videos from your Media Library in your front-end applications. For information about uploading videos or configuring Media Library, see the Media Library documentation.
Prerequisites
This is a paid feature
This feature is available on certain Enterprise plans. Talk to sales to learn more.
- A Sanity project with Media Library enabled and Studio v4.0.1 or later.
- Video addon enabled for your project. Video is an addon feature of Media Library and is not enabled by default. Contact sales to learn more.
- Video assets uploaded to your Media Library.
Understanding video representation in Media Library
Before implementing videos in your front-end, it's important to understand how Media Library handles videos:
- Video fields are created using the
defineVideoFieldhelper fromsanity/media-library - Videos are processed to generate playback IDs for streaming
- Each video asset contains metadata, including aspect ratio, duration, and framerate
Note: Both public and private videos are supported. Private videos play through short-lived signed URLs rather than a public URL.
Supported formats and limitations
Media Library accepts most common video formats for upload:
Supported formats:
- MP4 (recommended)
- MOV
- AVI
- MKV
- WebM
- And most other standard video formats
Considerations:
- Processing time varies based on video length and resolution.
- Higher resolution videos (4K+) may take longer to process.
For best performance and compatibility, we recommend uploading videos in MP4 format with H.264 encoding. To minimize processing time, follow the standard input specifications.
Video asset structure
Video assets in your Media Library have a nested structure:
{
"_id": "2yg9Un9RMsQjuf3WDqEo70ggi8D",
"_type": "sanity.asset",
"assetType": "sanity.videoAsset",
"title": "video-filename.mp4",
"versions": [
{
"instance": {
"_id": "2yg9UGEpG8H4xjgMj8YSstwpfVh",
"_type": "sanity.videoAsset",
"originalFilename": "video-filename.mp4",
"metadata": {
"_type": "sanity.videoMetadata",
"aspectRatio": 0.5625,
"duration": 8.86,
"playbacks": [ // one entry per playback policy
{
"_id": "V5uFaHghtnzgV6lYlBkrbehGkvd5KNGHYU7w2Eo7HoQ", // Playback ID
"_type": "sanity.videoMetadata.playback",
"policy": "public" // "public" or "secured"
}
]
},
"mimeType": "video/mp4",
"originalFilename": "video-filename.mp4"
}
}
]
}Setting up video fields in Studio
Use the defineVideoField helper to create video fields in your studio schemas:
import { defineVideoField } from 'sanity/media-library'
export default {
name: 'videoDocument',
title: 'Video Document',
type: 'document',
fields: [
defineVideoField({
title: 'Featured Video',
name: 'video',
}),
// ... other fields
]
}Get the video data
You can fetch video data in two ways: with the Sanity client's getPlaybackInfo() method, or with a GROQ query against the video asset's metadata.
getPlaybackInfo() is the safer default: it covers both public and secured videos, and returns a signed URL and token when a video needs one. A GROQ query is convenient when you already fetch the surrounding document. The playback ID it returns plays only for videos with a public playback policy.
Private videos and signed URLs
Private videos work differently from private images and files. For images and files, you sign URLs on your server with a Media Library signing key, as described in Asset visibility. Video doesn't use signing keys. The playback info endpoint signs the URLs for you.
When a video has a secured playback and no public playback, getPlaybackInfo() returns signed URLs for the stream, thumbnail, animated preview, storyboard, and subtitles. Each URL comes with a token, and subtitles also include expiresAt. When a video has a public playback, the response always uses its unsigned public URLs. Renditions of private videos work differently. See "Get renditions of private videos."
The endpoint only signs URLs for authenticated requests. Call getPlaybackInfo() from your server with a token, and pass the URLs to the browser. Without a token, a request for a video that only has a secured playback returns a not found error.
Signed URLs expire. By default, they stay valid for the length of the video plus a buffer. To set a different lifetime, pass the expiration option described in "Customize thumbnails and signed URL expiry." Fetch playback info again before the URLs expire.
A GROQ query can't give you a playable URL for a private video. metadata.playbacks holds only playback IDs, and a secured playback ID doesn't play without a signed token.
Fetch playback info with the Sanity client
The Sanity client provides a getPlaybackInfo() method that retrieves all necessary video information in a single call. This method requires API version v2025-03-25 or later.
First, configure your client to use Media Library:
import {createClient} from '@sanity/client'
const client = createClient({
apiVersion: '2025-03-25',
useCdn: false,
token: 'your-token',
'~experimental_resource': {
type: 'media-library',
id: 'your-media-library-id',
},
})Query for the document with your video field:
*[_type == 'videoDocument'] {
title,
video
}{
"title": "My Video Document",
"video": {
"_type": "sanity.video",
"asset": {
"_ref": "media-library:mlZxz9rvqf76:video-30rh9U3GDEK3ToiId1Zje4uvalC-mp4",
"_type": "reference"
}
}
}Fetch playback information using the video asset reference:
const document = await client.fetch(
`*[_type == 'videoDocument']{ title, video }`
)
const playbackInfo = await client.mediaLibrary.video.getPlaybackInfo(
document.video.asset
)The response contains URLs for the stream, images like the thumbnail, static MP4 renditions, and subtitle tracks, along with metadata like duration and aspect ratio.
{
id: "30rh9U3GDEK3ToiId1Zje4uvalC", // Playback ID
stream: { url: "https://stream.m.sanity-cdn.com/..." },
thumbnail: { url: "https://image.m.sanity-cdn.com/..." },
animated: { url: "https://image.m.sanity-cdn.com/..." },
storyboard: { url: "https://image.m.sanity-cdn.com/..." },
duration: 120.5,
aspectRatio: 1.77,
renditions: [
{ url: "https://apicdn.sanity.io/.../renditions/1080p.mp4", resolution: "1080p" },
{ url: "https://apicdn.sanity.io/.../renditions/720p.mp4", resolution: "720p" },
{ url: "https://apicdn.sanity.io/.../renditions/480p.mp4", resolution: "480p" },
{ url: "https://apicdn.sanity.io/.../renditions/270p.mp4", resolution: "270p" }
],
subtitles: [
{
trackId: "AbC123",
languageCode: "en",
url: "https://stream.m.sanity-cdn.com/.../text/AbC123.vtt",
closedCaptions: false
}
]
}Customize thumbnails and signed URL expiry
getPlaybackInfo() takes an optional second argument. Use transformations to set the size, format, and timestamp of the image URLs in the response, and expiration to set how long signed URLs stay valid. The options work for public and secured videos, so this is how you get a thumbnail from a specific time in a secured video.
const playbackInfo = await client.mediaLibrary.video.getPlaybackInfo(
document.video.asset,
{
transformations: {
thumbnail: {time: 5, width: 640, format: 'webp'},
animated: {start: 0, end: 3, width: 320, fps: 15, format: 'gif'},
storyboard: {format: 'jpg'},
},
expiration: '2h',
},
)
// playbackInfo.thumbnail.url is the frame at 5 seconds, 640 px wide, as WebPAvailable options:
thumbnail:widthandheightin pixels,timein seconds,fit(preserve,stretch,crop,smartcrop, orpad), andformat(jpg,png, orwebp).animated:widthandheightin pixels,startandendin seconds,fpsfrom 1 to 30, andformat(giforwebp).storyboard:format(jpg,png, orwebp).expiration: how long signed URLs stay valid, as a duration string such as30mor2h. Applies to secured videos. Defaults to the video's duration plus a buffer.
An invalid value, such as an fps outside 1 to 30 or an unsupported format, makes the request fail with an error.
When you call the HTTP API directly, pass the same options as query parameters on GET /media-libraries/{libraryId}/video/{instanceId}/playback-info: thumbnailWidth, thumbnailHeight, thumbnailTime, thumbnailFit, thumbnailFormat, animatedWidth, animatedHeight, animatedStart, animatedEnd, animatedFps, animatedFormat, storyboardFormat, and expiration.
Fetch the playback ID with GROQ
Public videos only
A private video has no public playback ID, so this query returns null for playbackId. To play a private video, use getPlaybackInfo(). See "Private videos and signed URLs."
Use the documents::get() function in a GROQ query to follow the Global Dataset Reference and access the video asset.
Each entry in metadata.playbacks pairs a playback ID (_id) with a playback policy of either public or secured. An asset can hold more than one entry, so filter by policy instead of taking the first one:
*[_type == 'videoDocument'] {
title,
"video": documents::get(video.asset){
_id,
"aspectRatio": metadata.aspectRatio,
"playbackId": metadata.playbacks[policy == "public"][0]._id // null for private videos
}
}{
"title": "My Video Document",
"video": {
"_id": "video-30rh9U3GDEK3ToiId1Zje4uvalC-mp4",
"aspectRatio": 0.5625,
"playbackId": "V5uFaHghtnzgV6lYlBkrbehGkvd5KNGHYU7w2Eo7HoQ"
}
}For more comprehensive video information, you can query additional metadata:
*[_type == 'videoDocument'] {
title,
"video": documents::get(video.asset){
_id,
"aspectRatio": metadata.aspectRatio,
"duration": metadata.duration,
"originalFilename": originalFilename,
"playbackId": metadata.playbacks[policy == "public"][0]._id // null for private videos
}
}Display videos with Mux Player
To present videos from Media Library in your frontend, we'll use the Mux video player.
Install the Mux React player component:
npm install @mux/mux-player-reactpnpm add @mux/mux-player-reactyarn add @mux/mux-player-reactbun add @mux/mux-player-reactBasic implementation
Here's the simplest way to display a video from your Media Library:
import MuxPlayer from '@mux/mux-player-react'
type VideoPlayerProps = {
playbackId: string
aspectRatio: number
}
export default function VideoPlayer({ playbackId, aspectRatio }: VideoPlayerProps) {
return (
<MuxPlayer
customDomain="m.sanity-cdn.com"
playbackId={playbackId}
style={{
width: '100%',
height: '100%',
aspectRatio: aspectRatio
}}
/>
)
}Using with queried data
Integrate with your GROQ query results:
import MuxPlayer from '@mux/mux-player-react'
type VideoData = {
playbackId: string
aspectRatio: number
originalFilename: string
}
type DocumentWithVideoProps = {
video: VideoData
}
export default function DocumentWithVideo({ video }: DocumentWithVideoProps) {
if (!video?.playbackId) {
return <div>No video available</div>
}
return (
<div className="video-container">
<MuxPlayer
customDomain="m.sanity-cdn.com"
playbackId={video.playbackId}
style={{
width: '100%',
height: '100%',
aspectRatio: video.aspectRatio
}}
/>
</div>
)
}Displaying thumbnails
Thumbnail images are automatically generated for your videos. You can use these as poster images:
import MuxPlayer from '@mux/mux-player-react'
export default function VideoWithPoster({ video }: { video: VideoData }) {
const posterUrl = `https://image.m.sanity-cdn.com/${video.playbackId}/thumbnail.jpg`
return (
<MuxPlayer
customDomain="m.sanity-cdn.com"
playbackId={video.playbackId}
poster={posterUrl}
style={{
width: '100%',
height: '100%',
aspectRatio: video.aspectRatio
}}
/>
)
}
You can also customize the poster image size and format:
// Custom poster with specific dimensions
const posterUrl = `https://image.m.sanity-cdn.com/${video.playbackId}/thumbnail.jpg?width=800&height=${Math.round(800 / video.aspectRatio)}&fit_mode=crop`
// Use WebP format for better compression
const optimizedPoster = `https://image.m.sanity-cdn.com/${video.playbackId}/thumbnail.webp?width=800&fit_mode=preserve`Subtitles and captions
Subtitles are separate text tracks on the video asset, not part of the video file itself. You add them in Media Library by generating them with AI or by uploading a VTT or SRT file. Because the tracks are separate, a static MP4 rendition plays without them — the player loads each track alongside the video.
getPlaybackInfo() returns every track that has finished processing in a subtitles array. Each entry has these fields:
trackId: Identifier for the text track.languageCode: ISO 639-1 code for the track's language, such asen.url: WebVTT file for the track, in the formhttps://stream.m.sanity-cdn.com/<playbackId>/text/<trackId>.vtt.closedCaptions:truewhen the track is a closed-captions track rather than a plain subtitle track.
A track that is still generating is left out of the array, so fetch playback info again after adding a subtitle.
Pass each track to a track element inside the player:
import MuxPlayer from '@mux/mux-player-react'
import {createClient} from '@sanity/client'
const client = createClient({
apiVersion: '2025-03-25',
useCdn: false,
token: process.env.SANITY_API_READ_TOKEN,
'~experimental_resource': {
type: 'media-library',
id: 'your-media-library-id',
},
})
export default async function VideoWithSubtitles({assetRef}: {assetRef: string}) {
const playbackInfo = await client.mediaLibrary.video.getPlaybackInfo(assetRef)
return (
<MuxPlayer
customDomain="m.sanity-cdn.com"
playbackId={playbackInfo.id}
style={{width: '100%', aspectRatio: playbackInfo.aspectRatio}}
>
{playbackInfo.subtitles?.map((subtitle) => (
<track
key={subtitle.trackId}
kind={subtitle.closedCaptions ? 'captions' : 'subtitles'}
label={subtitle.languageCode}
src={subtitle.url}
srcLang={subtitle.languageCode}
/>
))}
</MuxPlayer>
)
}For a video with a secured playback policy, the subtitle URL comes back signed and each entry also carries token and expiresAt. Fetch playback info again before the token expires.
Get static MP4 renditions
Besides the adaptive stream, Media Library creates static MP4 files of each video at fixed resolutions. A rendition URL doesn't change once the file exists, so renditions suit places where a streaming player doesn't fit, such as embedding a video on another site or offering a download. Renditions are MP4 only.
Media Library requests renditions at 1080p, 720p, 480p, and 270p. Renditions are never upscaled, so a resolution is only created when the source video is at least that size. For example, a 720p source gets 720p, 480p, and 270p renditions, but no 1080p rendition. Some videos uploaded before June 2026 also have no 720p rendition. Don't assume a specific resolution exists: choose from the renditions the video has.
Renditions finish processing after the stream. The playback info leaves out the renditions array until at least one rendition is ready, so treat it as optional.
Get rendition URLs
Use getPlaybackInfo() to get rendition URLs. Each entry in its renditions array has these fields:
url: The rendition's URL. It redirects to the MP4 file.resolution: The rendition's resolution, such as720p.tokenandexpiresAt: Present only for a private video. See "Get renditions of private videos."
The following example picks the largest rendition up to 720p:
const playbackInfo = await client.mediaLibrary.video.getPlaybackInfo(
document.video.asset
)
// Not every video has every resolution, so choose from what's available
const renditions = playbackInfo.renditions ?? []
const rendition = renditions
.filter((r) => parseInt(r.resolution) <= 720)
.sort((a, b) => parseInt(b.resolution) - parseInt(a.resolution))[0]
if (rendition) {
console.log(rendition.resolution, rendition.url)
}The URL points to the Media Library API's /media-libraries/{libraryId}/video/{videoId}/renditions/{name} endpoint, where name is a file name such as 720p.mp4. To download the file instead of playing it, add dl=true to the URL. To set your own file name for the download, pass it as the dl value. For details, see the Media Library API reference.
A GROQ query can tell you which renditions a video has, but not their URLs. Each entry in the asset's metadata.renditions has a name, such as 720p.mp4, and a resolution:
*[_type == 'videoDocument'] {
title,
"renditions": documents::get(video.asset).metadata.renditions[]{
name,
resolution
}
}Get renditions of private videos
For a video with a secured playback and no public playback, the rendition URL needs authentication. Without a token, it returns a not found error. Request the rendition URL from your server with the same Sanity token you used for getPlaybackInfo(), sent as a bearer token.
The response redirects to a signed MP4 URL that stays valid for one hour, and you can pass that URL to the browser. Get a new one before it expires.
Knowing when a video is ready
An upload returns as soon as the file is received, but the video goes through processing before it can be played. Until that finishes, the asset has no playback ID, so streaming and thumbnail URLs are not yet available.
Two fields on the asset tell you where it stands:
statemoves toreadywhen processing completes.metadata.playbackslists the playback IDs to use for streaming and thumbnail URLs.
You don't need to poll for either. A Media Library asset function runs when an asset changes, so you can react when a video becomes ready and generate thumbnails, patch documents, or trigger other work from there. For a walkthrough, see Create a Media Library Asset Function.
Subtitle tracks follow the same pattern. A track that is still generating is left out of the playback info, so an asset function is also the cleanest way to pick up captions once they finish.
Understanding streaming and thumbnail URLs
Media Library provides video streaming and thumbnail generation. Understanding the URL structure helps you optimize video delivery:
Streaming URLs
Videos are delivered via HLS (HTTP Live Streaming) using this URL pattern:
https://stream.m.sanity-cdn.com/{playbackId}.m3u8The Mux Player handles this automatically, but you can access the raw streaming URL if needed:
const hlsUrl = `https://stream.m.sanity-cdn.com/${video.playbackId}.m3u8`Thumbnail URLs
Thumbnails are automatically generated using this URL pattern:
https://image.m.sanity-cdn.com/{playbackId}/thumbnail.{format}You can customize thumbnails with query parameters, similar to Sanity's image pipeline:
// Basic thumbnail
const thumbnail = `https://image.m.sanity-cdn.com/${video.playbackId}/thumbnail.jpg`
// Specific size and format
const customThumb = `https://image.m.sanity-cdn.com/${video.playbackId}/thumbnail.webp?width=400&height=300`
// Crop and fit options
const croppedThumb = `https://image.m.sanity-cdn.com/${video.playbackId}/thumbnail.jpg?width=200&height=200&fit_mode=crop`
// Thumbnail from specific time (in seconds)
const timeThumb = `https://image.m.sanity-cdn.com/${video.playbackId}/thumbnail.jpg?time=30`Available thumbnail parameters:
widthandheight- Resize the thumbnailfit_mode- How to fit within dimensions (crop,preserve,stretch,pad)time- Extract thumbnail from specific video timestamp (in seconds)- Format options:
.jpg,.png,.webp
For the complete list of thumbnail transformation options, see the Mux thumbnail documentation.
Advanced player configuration
Configure additional player features:
import MuxPlayer from '@mux/mux-player-react'
type AdvancedVideoProps = {
video: {
playbackId: string
aspectRatio: number
originalFilename: string
}
autoPlay?: boolean
muted?: boolean
onPlay?: () => void
onEnded?: () => void
}
export default function AdvancedVideo({
video,
autoPlay = false,
muted = false,
onPlay,
onEnded
}: AdvancedVideoProps) {
return (
<MuxPlayer
customDomain="m.sanity-cdn.com"
playbackId={video.playbackId}
autoPlay={autoPlay}
muted={muted}
loop={false}
preload="metadata"
style={{
width: '100%',
height: '100%',
aspectRatio: video.aspectRatio
}}
onPlay={onPlay}
onEnded={onEnded}
/>
)
}Customizing player appearance
For styling and theming options, see the Mux Player customization guide. The player supports:
- Custom accent colors
- CSS custom properties for extensive styling
- Multiple built-in themes
- Custom CSS for complete control over appearance
Background video
For looping, muted, autoplay videos use the dedicated @mux/mux-background-video React component rather than Mux Player. It is lightweight, uses HLS adaptive streaming, and is optimized for this use case.
Background video vs. Mux Player
Choose the right component based on how the video is used:
- Background video: short, looping, muted clips that autoplay silently. No playback controls. Use
MuxBackgroundVideo. - Interactive video: product demos, explainers, or any video the user actively watches with play/pause/seek controls. Use
MuxPlayer.
Installation
npm install @mux/mux-background-videopnpm add @mux/mux-background-videoyarn add @mux/mux-background-videobun add @mux/mux-background-videoBasic implementation
Pass the playback ID from your Media Library asset to the component. Include an <img> element as a poster — it displays while the video loads and is important for LCP performance.
import MuxBackgroundVideo from '@mux/mux-background-video'
type HeroBackgroundProps = {
playbackId: string
}
export default function HeroBackground({ playbackId }: HeroBackgroundProps) {
return (
<MuxBackgroundVideo
customDomain="m.sanity-cdn.com"
playbackId={playbackId}
style={{ width: '100%', height: '100%' }}
>
<img
src={`https://image.m.sanity-cdn.com/${playbackId}/thumbnail.webp?width=1920&fit_mode=preserve`}
alt=""
aria-hidden="true"
/>
</MuxBackgroundVideo>
)
}Controlling resolution
Use the maxResolution prop to cap the quality served. This reduces bandwidth usage when the video is displayed at a smaller size — for example, a full-bleed hero rarely needs more than 1080p.
<MuxBackgroundVideo
customDomain="m.sanity-cdn.com"
playbackId={playbackId}
maxResolution="1080p"
style={{ width: '100%', height: '100%' }}
>
<img
src={`https://image.m.sanity-cdn.com/${playbackId}/thumbnail.webp?width=1920&fit_mode=preserve`}
alt=""
aria-hidden="true"
/>
</MuxBackgroundVideo>Available values: 270p, 360p, 480p, 540p, 720p, 1080p, 1440p, 2160p.
Pause when the tab is hidden
Background videos continue playing when users switch tabs, consuming CPU and battery. Pause playback when the page is hidden using the Page Visibility API:
import MuxBackgroundVideo from '@mux/mux-background-video'
import { useEffect, useRef } from 'react'
export default function HeroBackground({ playbackId }: { playbackId: string }) {
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
const handleVisibilityChange = () => {
if (!videoRef.current) return
if (document.hidden) {
videoRef.current.pause()
} else {
videoRef.current.play()
}
}
document.addEventListener('visibilitychange', handleVisibilityChange)
return () => document.removeEventListener('visibilitychange', handleVisibilityChange)
}, [])
return (
<MuxBackgroundVideo
ref={videoRef}
customDomain="m.sanity-cdn.com"
playbackId={playbackId}
maxResolution="1080p"
style={{ width: '100%', height: '100%' }}
>
<img
src={`https://image.m.sanity-cdn.com/${playbackId}/thumbnail.webp?width=1920&fit_mode=preserve`}
alt=""
aria-hidden="true"
/>
</MuxBackgroundVideo>
)
}Performance considerations
To maximize performance when displaying videos:
Preload settings
Use appropriate preload settings based on your use case:
preload="none"- Don't preload anything. Best for pages with many videos.preload="metadata"- Preload video metadata only. Default setting.preload="auto"- Preload the entire video. Use sparingly, and avoid when loading multiple videos.
Loading strategies
Lazy load videos below the fold as users scroll to reduce initial page load time.
Resolution control
Set maxResolution on the player to cap the maximum video quality and reduce bandwidth usage. Available options are "720p", "1080p", "1440p", and "2160p".
<MuxPlayer
playbackId={playbackId}
maxResolution="1080p"
style={{
width: '100%',
height: '100%',
aspectRatio: aspectRatio,
}}
/>Image optimization
Use appropriately sized thumbnails instead of full-resolution images by utilizing the transformation options.
User interaction
Consider implementing click-to-play for videos that aren't essential to the user experience to save bandwidth.
Poster images and LCP
For above-the-fold videos, always provide a poster image. The <img> element inside MuxBackgroundVideo (or the poster prop on MuxPlayer) is treated as a Largest Contentful Paint candidate. Use an appropriately sized .webp thumbnail from the media CDN (image.m.sanity-cdn.com) to keep LCP fast.
min-resolution
Use minResolution alongside maxResolution to define a quality range. This is useful when your video contains text or fine detail that must remain legible — for example, a screen recording or product demo where 480p would be unacceptable.
<MuxPlayer
customDomain="m.sanity-cdn.com"
playbackId={playbackId}
minResolution="720p"
maxResolution="1080p"
style={{ width: '100%', height: '100%', aspectRatio: aspectRatio }}
/>Common issues
Video not loading
- Check that the playback ID exists in your query:
video.asset->metadata.playbacks[policy == "public"][0]._id - For a private video,
metadata.playbacks[policy == "public"]is empty. UsegetPlaybackInfo()to get a signed stream URL. - Ensure your Media Library integration is properly configured
- Verify the GROQ query is returning the expected data structure
Player not displaying
- Confirm
@mux/mux-player-reactis properly installed - Check browser console for JavaScript errors
- Verify the container element has appropriate dimensions
Aspect ratio issues
- Ensure you're using the correct aspect ratio from
video.asset->metadata.aspectRatio - Note that aspect ratio is width/height (e.g., 0.5625 for 9:16 vertical video)
- Set proper container styles to maintain aspect ratio
Subtitles not appearing
- Subtitles are separate text tracks, so a static MP4 rendition URL never carries them. Read the
subtitlesarray fromgetPlaybackInfo()and attach each URL to atrackelement on the player. - A track that is still generating is left out of the
subtitlesarray. Wait for processing to finish, then fetch playback info again.
Videos appears as files
- If you uploaded through the Media Library HTTP API, check whether the request included
type=file. Thetypeparameter is an override: when supplied, Media Library trusts it and stores the upload as that type. - Omit
typeto let Media Library detect videos from the uploaded content, or use the correct override only when you intentionally want to force a classification. - If you uploaded through
@sanity/client, useclient.assets.upload('file', videoBody, ...); this does not sendtype=fileto the Media Library HTTP API, so the video can still be detected and processed.
Additional resources
- Mux Player documentation: Complete guide to Mux Player features and customization options.
- Media Library documentation: Learn more about configuring and using Media Library.
- Creating custom aspects: Add custom metadata fields to your video assets.
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?