Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ More information on signed URLs is available on Mux's [docs](https://docs.mux.co

### MP4 support (downloadable videos or offline viewing)

To enable [static MP4 renditions](https://docs.mux.com/guides/video/enable-static-mp4-renditions), add `mp4_support: 'standard'` to the `options` of your `mux.video` schema type.
To enable [static MP4 renditions](https://docs.mux.com/guides/video/enable-static-mp4-renditions), add `mp4_support: 'standard'` to the `options` of your `mux.video` schema type. Set default with `mp4_support: 'standard'` in the plugin configuration.

```js
import {muxInput} from 'sanity-plugin-mux-input'
Expand All @@ -214,7 +214,7 @@ MP4 allows users to download videos for later or offline viewing. More informati

### Video resolution (max_resolution_tier)

To edit [max_resolution_tier](https://docs.mux.com/api-reference#video/operation/create-direct-upload) to support other resolutions other than 1080p, add `max_resolution_tier: '1080p' | '1440p' | '2160p'` to the `options` of your `mux.video` schema type. Defaults to `1080p`.
To edit [max_resolution_tier](https://docs.mux.com/api-reference#video/operation/create-direct-upload) to support other resolutions other than 1080p, add `max_resolution_tier: '1080p' | '1440p' | '2160p'` to the `options` of your `mux.video` schema type. Defaults to `1080p`. Set default with `max_resolution_tier: '1080p' | '1440p' | '2160p'` in the plugin configuration.

```js
import {muxInput} from 'sanity-plugin-mux-input'
Expand All @@ -224,8 +224,42 @@ export default defineConfig({
})
```

```js
export default {
title: 'Main Video',
name: 'mainVideo',
type: 'mux.video',
options: {
max_resolution_tier: '2160p',
},
}
```

When uploading new assets, editors can still choose a lower resolution for each video than configured globally. This option controls the maximum resolution encoded or processed for the uploaded video. The option is particularly important to manage costs when uploaded videos are higher than `1080p` resolution. More information on the feature is available on Mux's [docs](https://docs.mux.com/guides/stream-videos-in-4k). Also, read more on this feature announcement on Mux's [blog](https://www.mux.com/blog/more-pixels-fewer-problems-introducing-4k-support-for-mux-video).

### Control Accepted File Types

To control the file types accepted by the input, set the `acceptedMimeTypes` in the `options` of your `mux.video` schema type. Set default with `acceptedMimeTypes: ['video/*', 'audio/*']` in the plugin configuration. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) for more information on the `accept` attribute.

```js
import {muxInput} from 'sanity-plugin-mux-input'

export default defineConfig({
plugins: [muxInput({acceptedMimeTypes: ['audio']})],
})
```

```js
export default {
title: 'Main Video',
name: 'mainVideo',
type: 'mux.video',
options: {
acceptedMimeTypes: ['video'],
},
}
```

### Encoding tier (smart or baseline)

The [encoding tier](https://docs.mux.com/guides/use-encoding-tiers) informs the cost, quality, and available platform features for the asset. You can choose between `smart` and `baseline` at the plugin configuration. Defaults to `smart`.
Expand All @@ -238,6 +272,17 @@ export default defineConfig({
})
```

```js
export default {
title: 'Main Video',
name: 'mainVideo',
type: 'mux.video',
options: {
encoding_tier: 'baseline',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encoding_tier has been renamed to video_quality (and smart -> plus and baseline -> basic)

I don't think the Sanity plugin supports video_quality yet, so this isn't an actionable comment. Just making a note that, at some point, we'll need to update the Sanity plugin to reflect this.

},
}
```

If `encoding_tier: 'smart'`, editors can still choose to use the `baseline` encoding tier on a per-video basis when uploading new assets.

More information on the feature is available on Mux's [documentation](https://docs.mux.com/guides/use-encoding-tiers). Also, read more on the feature announcement on Mux's [blog](https://www.mux.com/blog/our-next-pricing-lever-baseline-on-demand-assets-with-free-video-encoding)
Expand Down
1 change: 1 addition & 0 deletions src/_exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const defaultConfig: PluginConfig = {
normalize_audio: false,
defaultSigned: false,
tool: DEFAULT_TOOL_CONFIG,
acceptedMimeTypes: ['video/*', 'audio/*'],
}

export const muxInput = definePlugin<Partial<PluginConfig> | void>((userConfig) => {
Expand Down
93 changes: 0 additions & 93 deletions src/components/FileInputArea.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions src/components/FileInputButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Label = styled.label`

export interface FileInputButtonProps extends ButtonProps {
onSelect: (files: FileList) => void
accept?: string
accept: string
}
export const FileInputButton = ({onSelect, accept, ...props}: FileInputButtonProps) => {
const inputId = `FileSelect${useId()}`
Expand All @@ -34,7 +34,7 @@ export const FileInputButton = ({onSelect, accept, ...props}: FileInputButtonPro
return (
<Label htmlFor={inputId}>
<HiddenInput
accept={accept || 'video/*'}
accept={accept}
ref={inputRef}
tabIndex={0}
type="file"
Expand Down
5 changes: 3 additions & 2 deletions src/components/PlayerActionsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ function PlayerActionsMenu(
onSelect: (files: File[]) => void
dialogState: DialogState
setDialogState: SetDialogState
accept: string
}
) {
const {asset, readOnly, dialogState, setDialogState, onChange, onSelect} = props
const {asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept} = props
const [open, setOpen] = useState(false)
const [menuElement, setMenuRef] = useState<HTMLDivElement | null>(null)
const isSigned = useMemo(() => getPlaybackPolicy(asset) === 'signed', [asset])
Expand Down Expand Up @@ -99,7 +100,7 @@ function PlayerActionsMenu(
</Label>
</Box>
<FileInputMenuItem
accept="video/*"
accept={accept}
icon={UploadIcon}
onSelect={onSelect}
text="Upload"
Expand Down
4 changes: 3 additions & 1 deletion src/components/UploadPlaceholder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ interface UploadPlaceholderProps {
hovering: boolean
needsSetup: boolean
onSelect: FileInputButtonProps['onSelect']
accept: string
}
export default function UploadPlaceholder(props: UploadPlaceholderProps) {
const {setDialogState, readOnly, onSelect, hovering, needsSetup} = props
const {setDialogState, readOnly, onSelect, hovering, needsSetup, accept} = props
const handleBrowse = useCallback(() => setDialogState('select-video'), [setDialogState])
const handleConfigureApi = useCallback(() => setDialogState('secrets'), [setDialogState])

Expand Down Expand Up @@ -50,6 +51,7 @@ export default function UploadPlaceholder(props: UploadPlaceholderProps) {
</Flex>
<Inline space={2}>
<FileInputButton
accept={accept}
mode="bleed"
tone="default"
icon={UploadIcon}
Expand Down
55 changes: 52 additions & 3 deletions src/components/Uploader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ErrorOutlineIcon} from '@sanity/icons'
import {Button, CardTone, Flex, Text, useToast} from '@sanity/ui'
import React, {useEffect, useReducer, useRef, useState} from 'react'
import React, {useCallback, useEffect, useReducer, useRef, useState} from 'react'
import {type Observable, Subject, Subscription} from 'rxjs'
import {takeUntil, tap} from 'rxjs/operators'
import type {SanityClient} from 'sanity'
Expand Down Expand Up @@ -222,12 +222,40 @@ export default function Uploader(props: Props) {
})
}

const invalidFileToast = useCallback(() => {
toast.push({
status: 'error',
title: `Invalid file type. Accepted types: ${props.config.acceptedMimeTypes?.join(', ')}`,
})
}, [props.config.acceptedMimeTypes, toast])

/**
* Validates if any file in the provided FileList or File array has an unsupported MIME type
* @param files - FileList or File array to validate
* @returns true if any file has an invalid MIME type, false if all files are valid
*/

const isInvalidFile = (files: FileList | File[]) => {
const isInvalid = Array.from(files).some((file) => {
return !props.config.acceptedMimeTypes?.some((acceptedType) =>
file.type.match(new RegExp(acceptedType))
)
})

if (isInvalid) {
invalidFileToast()
return true
}
return false
}

/* -------------------------- Upload Initialization ------------------------- */
// The below populate the uploadInput state field, which then triggers the
// upload configuration, or the startUpload function if no config is required.

// Stages an upload from the file selector
const handleUpload = (files: FileList | File[]) => {
if (isInvalidFile(files)) return
dispatch({
action: 'stageUpload',
input: {type: 'file', files},
Expand All @@ -249,9 +277,14 @@ export default function Uploader(props: Props) {

// Stages and validates an upload from dragging+dropping files or folders
const handleDrop: React.DragEventHandler<HTMLDivElement> = (event) => {
setDragState(null)
event.preventDefault()
event.stopPropagation()
if (dragState === 'invalid') {
invalidFileToast()
setDragState(null)
return
}
setDragState(null)
extractDroppedFiles(event.nativeEvent.dataTransfer!).then((files) => {
dispatch({
action: 'stageUpload',
Expand All @@ -271,7 +304,16 @@ export default function Uploader(props: Props) {
event.stopPropagation()
dragEnteredEls.current.push(event.target)
const type = event.dataTransfer.items?.[0]?.type
setDragState(type?.startsWith('video/') ? 'valid' : 'invalid')
const mimeTypes = props.config.acceptedMimeTypes

// Check if the dragged file type matches any of the accepted mime types
const isValidType = mimeTypes?.some((acceptedType) => {
// Convert mime type pattern to regex (e.g., 'video/*' -> /^video\/.*$/)
const pattern = `^${acceptedType.replace('*', '.*')}$`
return new RegExp(pattern).test(type)
})

setDragState(isValidType ? 'valid' : 'invalid')
}

const handleDragLeave: React.DragEventHandler<HTMLDivElement> = (event) => {
Expand Down Expand Up @@ -335,6 +377,10 @@ export default function Uploader(props: Props) {
let tone: CardTone | undefined
if (dragState) tone = dragState === 'valid' ? 'positive' : 'critical'

const acceptMimeString = props.config?.acceptedMimeTypes?.length
? props.config.acceptedMimeTypes.join(',')
: 'video/*, audio/*'

return (
<>
<UploadCard
Expand All @@ -353,6 +399,7 @@ export default function Uploader(props: Props) {
onChange={props.onChange}
buttons={
<PlayerActionsMenu
accept={acceptMimeString}
asset={props.asset}
dialogState={props.dialogState}
setDialogState={props.setDialogState}
Expand All @@ -364,6 +411,8 @@ export default function Uploader(props: Props) {
/>
) : (
<UploadPlaceholder
accept={acceptMimeString}
config={props.config}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
config={props.config}

hovering={dragState !== null}
onSelect={handleUpload}
readOnly={!!props.readOnly}
Expand Down
9 changes: 9 additions & 0 deletions src/util/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ export interface MuxInputConfig {
* @defaultValue false
*/
disableTextTrackConfig?: boolean

/**
* The mime types that are accepted by the input.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept}
* @defaultValue ['video/*','audio/*']

*/
acceptedMimeTypes?: ('audio' | 'video')[]
}

export interface PluginConfig extends MuxInputConfig {
Expand Down