sanity-plugin-mux-input 2.12.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1482,10 +1482,21 @@ function EditThumbnailDialog({ asset, currentTime = 0 }) {
1482
1482
  }
1483
1483
  );
1484
1484
  }
1485
+ function AudioIcon(props) {
1486
+ return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props, children: /* @__PURE__ */ jsx(
1487
+ "path",
1488
+ {
1489
+ fill: "currentColor",
1490
+ style: { opacity: "0.65" },
1491
+ d: "M10.75 19q.95 0 1.6-.65t.65-1.6V13h3v-2h-4v3.875q-.275-.2-.587-.288t-.663-.087q-.95 0-1.6.65t-.65 1.6t.65 1.6t1.6.65M6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22zm7-13V4H6v16h12V9zM6 4v5zv16z"
1492
+ }
1493
+ ) });
1494
+ }
1485
1495
  function VideoPlayer({
1486
1496
  asset,
1487
1497
  thumbnailWidth = 250,
1488
1498
  children,
1499
+ hlsConfig,
1489
1500
  ...props
1490
1501
  }) {
1491
1502
  const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = useRef(null), {
@@ -1511,52 +1522,79 @@ function VideoPlayer({
1511
1522
  // Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
1512
1523
  props.forceAspectRatio * 1.2
1513
1524
  ) : AUDIO_ASPECT_RATIO), /* @__PURE__ */ jsxs(Fragment, { children: [
1514
- /* @__PURE__ */ jsxs(Card, { tone: "transparent", style: { aspectRatio, position: "relative" }, children: [
1515
- videoSrc && /* @__PURE__ */ jsxs(Fragment, { children: [
1516
- /* @__PURE__ */ jsx(
1517
- MuxPlayer,
1518
- {
1519
- poster: thumbnailSrc,
1520
- ref: muxPlayer,
1521
- ...props,
1522
- playsInline: !0,
1523
- playbackId: asset.playbackId,
1524
- tokens: signedToken ? { playback: signedToken, thumbnail: signedToken, storyboard: signedToken } : void 0,
1525
- preload: "metadata",
1526
- crossOrigin: "anonymous",
1527
- metadata: {
1528
- player_name: "Sanity Admin Dashboard",
1529
- player_version: "2.12.1",
1530
- page_type: "Preview Player"
1531
- },
1532
- audio: isAudio,
1533
- style: {
1534
- height: "100%",
1535
- width: "100%",
1536
- display: "block",
1537
- objectFit: "contain"
1525
+ /* @__PURE__ */ jsxs(
1526
+ Card,
1527
+ {
1528
+ tone: "transparent",
1529
+ style: {
1530
+ aspectRatio,
1531
+ position: "relative",
1532
+ ...isAudio && { display: "flex", alignItems: "flex-end" }
1533
+ },
1534
+ children: [
1535
+ videoSrc && /* @__PURE__ */ jsxs(Fragment, { children: [
1536
+ isAudio && /* @__PURE__ */ jsx(
1537
+ AudioIcon,
1538
+ {
1539
+ style: {
1540
+ padding: "0.5em",
1541
+ width: "2.2em",
1542
+ height: "2.2em",
1543
+ position: "absolute",
1544
+ top: 0,
1545
+ left: 0,
1546
+ zIndex: 1
1547
+ }
1548
+ }
1549
+ ),
1550
+ /* @__PURE__ */ jsx(
1551
+ MuxPlayer,
1552
+ {
1553
+ poster: isAudio ? void 0 : thumbnailSrc,
1554
+ ref: muxPlayer,
1555
+ ...props,
1556
+ playsInline: !0,
1557
+ playbackId: asset.playbackId,
1558
+ tokens: signedToken ? { playback: signedToken, thumbnail: signedToken, storyboard: signedToken } : void 0,
1559
+ preload: "metadata",
1560
+ crossOrigin: "anonymous",
1561
+ metadata: {
1562
+ player_name: "Sanity Admin Dashboard",
1563
+ player_version: "2.13.0",
1564
+ page_type: "Preview Player"
1565
+ },
1566
+ audio: isAudio,
1567
+ _hlsConfig: hlsConfig,
1568
+ style: {
1569
+ ...!isAudio && { height: "100%" },
1570
+ width: "100%",
1571
+ display: "block",
1572
+ objectFit: "contain",
1573
+ ...isAudio && { alignSelf: "end" }
1574
+ }
1575
+ }
1576
+ ),
1577
+ children
1578
+ ] }),
1579
+ error ? /* @__PURE__ */ jsx(
1580
+ "div",
1581
+ {
1582
+ style: {
1583
+ position: "absolute",
1584
+ top: "50%",
1585
+ left: "50%",
1586
+ transform: "translate(-50%, -50%)"
1587
+ },
1588
+ children: /* @__PURE__ */ jsxs(Text, { muted: !0, children: [
1589
+ /* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { marginRight: "0.15em" } }),
1590
+ typeof error == "object" && "message" in error && typeof error.message == "string" ? error.message : "Error loading video"
1591
+ ] })
1538
1592
  }
1539
- }
1540
- ),
1541
- children
1542
- ] }),
1543
- error ? /* @__PURE__ */ jsx(
1544
- "div",
1545
- {
1546
- style: {
1547
- position: "absolute",
1548
- top: "50%",
1549
- left: "50%",
1550
- transform: "translate(-50%, -50%)"
1551
- },
1552
- children: /* @__PURE__ */ jsxs(Text, { muted: !0, children: [
1553
- /* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { marginRight: "0.15em" } }),
1554
- typeof error == "object" && "message" in error && typeof error.message == "string" ? error.message : "Error loading video"
1555
- ] })
1556
- }
1557
- ) : null,
1558
- children
1559
- ] }),
1593
+ ) : null,
1594
+ children
1595
+ ]
1596
+ }
1597
+ ),
1560
1598
  dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime })
1561
1599
  ] });
1562
1600
  }
@@ -2299,14 +2337,7 @@ function VideoInBrowser({
2299
2337
  alignItems: "center",
2300
2338
  justifyContent: "center"
2301
2339
  },
2302
- children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "3em", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
2303
- "path",
2304
- {
2305
- fill: "currentColor",
2306
- style: { opacity: "0.65" },
2307
- d: "M10.75 19q.95 0 1.6-.65t.65-1.6V13h3v-2h-4v3.875q-.275-.2-.587-.288t-.663-.087q-.95 0-1.6.65t-.65 1.6t.65 1.6t1.6.65M6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22zm7-13V4H6v16h12V9zM6 4v5zv16z"
2308
- }
2309
- ) })
2340
+ children: /* @__PURE__ */ jsx(AudioIcon, { width: "3em", height: "3em" })
2310
2341
  }
2311
2342
  ) : /* @__PURE__ */ jsx(VideoThumbnail, { asset })
2312
2343
  ] }),
@@ -2931,7 +2962,7 @@ const TopControls = styled.div`
2931
2962
  }
2932
2963
  ) : null
2933
2964
  ] }) });
2934
- }, Player = ({ asset, buttons, readOnly, onChange }) => {
2965
+ }, Player = ({ asset, buttons, readOnly, onChange, config }) => {
2935
2966
  const isLoading = useMemo(() => asset?.status === "preparing" ? "Preparing the video" : asset?.status === "waiting_for_upload" ? "Waiting for upload to start" : asset?.status === "waiting" ? "Processing upload" : !(asset?.status === "ready" || typeof asset?.status > "u"), [asset]), isPreparingStaticRenditions = useMemo(() => {
2936
2967
  if (asset?.data?.static_renditions?.status && asset?.data?.static_renditions?.status !== "disabled")
2937
2968
  return !1;
@@ -2952,7 +2983,7 @@ const TopControls = styled.div`
2952
2983
  text: isLoading !== !0 && isLoading || "Waiting for Mux to complete the upload",
2953
2984
  onCancel: readOnly ? void 0 : () => handleCancelUpload()
2954
2985
  }
2955
- ) : /* @__PURE__ */ jsxs(VideoPlayer, { asset, children: [
2986
+ ) : /* @__PURE__ */ jsxs(VideoPlayer, { asset, hlsConfig: config?.hlsConfig, children: [
2956
2987
  buttons && /* @__PURE__ */ jsx(TopControls, { slot: "top-chrome", children: buttons }),
2957
2988
  isPreparingStaticRenditions && /* @__PURE__ */ jsx(
2958
2989
  Card,
@@ -3064,7 +3095,7 @@ const FileButton = styled(MenuItem)(({ theme }) => {
3064
3095
  color: white;
3065
3096
  `, isVideoAsset = (asset) => asset._type === "mux.videoAsset";
3066
3097
  function PlayerActionsMenu(props) {
3067
- const { asset, readOnly, dialogState, setDialogState, onChange, onSelect } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset) === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange]);
3098
+ const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset) === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange]);
3068
3099
  return useEffect(() => {
3069
3100
  open && dialogState && setOpen(!1);
3070
3101
  }, [dialogState, open]), useClickOutsideEvent(
@@ -3090,7 +3121,7 @@ function PlayerActionsMenu(props) {
3090
3121
  /* @__PURE__ */ jsx(
3091
3122
  FileInputMenuItem,
3092
3123
  {
3093
- accept: "video/*",
3124
+ accept,
3094
3125
  icon: UploadIcon,
3095
3126
  onSelect,
3096
3127
  text: "Upload",
@@ -3454,7 +3485,47 @@ function UploadConfiguration({
3454
3485
  (r) => r !== "highest" && r !== "audio-only"
3455
3486
  ).length > 0, [config.static_renditions]), [renditionMode, setRenditionMode] = useState(
3456
3487
  isAdvancedMode ? "advanced" : "standard"
3457
- ), toggleRendition = (rendition) => {
3488
+ ), [videoDuration, setVideoDuration] = useState(null), [urlFileSize, setUrlFileSize] = useState(null), [isLoadingDuration, setIsLoadingDuration] = useState(!1), [isLoadingFileSize, setIsLoadingFileSize] = useState(!1), [validationError, setValidationError] = useState(null), [canSkipFileSizeValidation, setCanSkipFileSizeValidation] = useState(!1), MAX_FILE_SIZE = pluginConfig.maxAssetFileSize, MAX_DURATION_SECONDS = pluginConfig.maxAssetDuration;
3489
+ useEffect(() => {
3490
+ setVideoDuration(null), setUrlFileSize(null), setIsLoadingDuration(!1), setIsLoadingFileSize(!1), setValidationError(null), setCanSkipFileSizeValidation(!1);
3491
+ let videoElement = null, currentVideoSrc = null;
3492
+ const cleanupVideo = (shouldRevokeUrl) => {
3493
+ videoElement && (videoElement.onloadedmetadata = null, videoElement.onerror = null, videoElement.src = "", videoElement.load(), videoElement = null), shouldRevokeUrl && currentVideoSrc?.startsWith("blob:") && URL.revokeObjectURL(currentVideoSrc), currentVideoSrc = null;
3494
+ }, validateDuration = (videoSrc, shouldRevokeUrl = !1) => {
3495
+ !MAX_DURATION_SECONDS || MAX_DURATION_SECONDS <= 0 || (setIsLoadingDuration(!0), videoElement = document.createElement("video"), videoElement.preload = "metadata", currentVideoSrc = videoSrc, videoElement.onloadedmetadata = () => {
3496
+ const duration = videoElement.duration;
3497
+ setVideoDuration(duration), setIsLoadingDuration(!1), duration > MAX_DURATION_SECONDS && setValidationError(
3498
+ `Video duration (${formatSeconds(duration)}) exceeds maximum allowed duration of ${formatSeconds(MAX_DURATION_SECONDS)}`
3499
+ ), cleanupVideo(shouldRevokeUrl);
3500
+ }, videoElement.onerror = () => {
3501
+ setIsLoadingDuration(!1), console.warn("Could not read video metadata for validation"), cleanupVideo(shouldRevokeUrl);
3502
+ }, videoElement.src = videoSrc);
3503
+ }, validateFileSize = (size) => MAX_FILE_SIZE === void 0 || size <= MAX_FILE_SIZE ? !0 : (setValidationError(
3504
+ `File size (${formatBytes(size)}) exceeds maximum allowed size of ${formatBytes(MAX_FILE_SIZE)}`
3505
+ ), !1);
3506
+ if (stagedUpload.type === "file") {
3507
+ const file = stagedUpload.files[0];
3508
+ validateFileSize(file.size) && validateDuration(URL.createObjectURL(file), !0);
3509
+ }
3510
+ if (stagedUpload.type === "url") {
3511
+ const url = stagedUpload.url;
3512
+ (async () => {
3513
+ setIsLoadingFileSize(!0);
3514
+ try {
3515
+ const contentLength = (await fetch(url, { method: "HEAD" })).headers.get("content-length"), fileSize = contentLength ? parseInt(contentLength, 10) : null;
3516
+ setIsLoadingFileSize(!1), fileSize && setUrlFileSize(fileSize);
3517
+ const shouldValidateDuration = MAX_FILE_SIZE === void 0 || fileSize === null || validateFileSize(fileSize);
3518
+ fileSize === null && MAX_FILE_SIZE !== void 0 && setCanSkipFileSizeValidation(!0), shouldValidateDuration && validateDuration(url);
3519
+ } catch {
3520
+ setIsLoadingFileSize(!1), console.warn("Could not validate file size from URL"), setCanSkipFileSizeValidation(!0), validateDuration(url);
3521
+ }
3522
+ })();
3523
+ }
3524
+ return () => {
3525
+ cleanupVideo(!0);
3526
+ };
3527
+ }, [stagedUpload, MAX_FILE_SIZE, MAX_DURATION_SECONDS]);
3528
+ const toggleRendition = (rendition) => {
3458
3529
  const current = config.static_renditions, hasRendition = current.includes(rendition);
3459
3530
  dispatch(hasRendition ? {
3460
3531
  action: "static_renditions",
@@ -3489,6 +3560,13 @@ function UploadConfiguration({
3489
3560
  header: "Configure Mux Upload",
3490
3561
  onClose,
3491
3562
  children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 2, children: [
3563
+ validationError && /* @__PURE__ */ jsx(Card, { padding: 3, tone: "critical", radius: 2, marginBottom: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "flex-start", children: [
3564
+ /* @__PURE__ */ jsx(ErrorOutlineIcon, { width: 20, height: 20 }),
3565
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3566
+ /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Validation Error" }),
3567
+ /* @__PURE__ */ jsx(Text, { size: 1, children: validationError })
3568
+ ] })
3569
+ ] }) }),
3492
3570
  /* @__PURE__ */ jsx(Label$1, { size: 3, children: "FILE TO UPLOAD" }),
3493
3571
  /* @__PURE__ */ jsx(
3494
3572
  Card,
@@ -3502,7 +3580,14 @@ function UploadConfiguration({
3502
3580
  /* @__PURE__ */ jsx(DocumentVideoIcon, { fontSize: "2em" }),
3503
3581
  /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3504
3582
  /* @__PURE__ */ jsx(Text, { textOverflow: "ellipsis", as: "h2", size: 3, children: stagedUpload.type === "file" ? stagedUpload.files[0].name : stagedUpload.url }),
3505
- /* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: stagedUpload.type === "file" ? `Direct File Upload (${formatBytes(stagedUpload.files[0].size)})` : "File From URL (Unknown size)" })
3583
+ /* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: stagedUpload.type === "file" ? `Direct File Upload (${formatBytes(stagedUpload.files[0].size)})` : urlFileSize ? `File From URL (${formatBytes(urlFileSize)})` : isLoadingFileSize ? "File From URL (Loading size...)" : "File From URL (Unknown size)" }),
3584
+ stagedUpload.type === "file" && /* @__PURE__ */ jsxs(Stack, { space: 1, children: [
3585
+ isLoadingDuration && /* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: "Reading video metadata..." }),
3586
+ videoDuration !== null && !validationError && /* @__PURE__ */ jsxs(Text, { as: "p", size: 1, muted: !0, children: [
3587
+ "Duration: ",
3588
+ formatSeconds(videoDuration)
3589
+ ] })
3590
+ ] })
3506
3591
  ] })
3507
3592
  ] })
3508
3593
  }
@@ -3694,11 +3779,13 @@ function UploadConfiguration({
3694
3779
  /* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsx(
3695
3780
  Button,
3696
3781
  {
3697
- disabled: !basicConfig && !config.public_policy && !config.signed_policy,
3782
+ disabled: !basicConfig && !config.public_policy && !config.signed_policy || validationError !== null || isLoadingDuration || isLoadingFileSize && !canSkipFileSizeValidation,
3698
3783
  icon: UploadIcon,
3699
3784
  text: "Upload",
3700
3785
  tone: "positive",
3701
- onClick: () => startUpload(formatUploadConfig(config))
3786
+ onClick: () => {
3787
+ validationError || startUpload(formatUploadConfig(config));
3788
+ }
3702
3789
  }
3703
3790
  ) })
3704
3791
  ] })
@@ -3821,7 +3908,7 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(Card),
3821
3908
  /* @__PURE__ */ jsx(
3822
3909
  HiddenInput,
3823
3910
  {
3824
- accept: accept || "video/*",
3911
+ accept,
3825
3912
  ref: inputRef,
3826
3913
  tabIndex: 0,
3827
3914
  type: "file",
@@ -3842,8 +3929,11 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(Card),
3842
3929
  )
3843
3930
  ] });
3844
3931
  };
3932
+ function formatAcceptString(accept) {
3933
+ return accept.split(",").map((type) => type.trim().replace("/*", "")).join(" or ");
3934
+ }
3845
3935
  function UploadPlaceholder(props) {
3846
- const { setDialogState, readOnly, onSelect, hovering, needsSetup } = props, handleBrowse = useCallback(() => setDialogState("select-video"), [setDialogState]), handleConfigureApi = useCallback(() => setDialogState("secrets"), [setDialogState]), { hasConfigAccess } = useAccessControl(props.config);
3936
+ const { setDialogState, readOnly, onSelect, hovering, needsSetup, accept } = props, handleBrowse = useCallback(() => setDialogState("select-video"), [setDialogState]), handleConfigureApi = useCallback(() => setDialogState("secrets"), [setDialogState]), { hasConfigAccess } = useAccessControl(props.config);
3847
3937
  return /* @__PURE__ */ jsx(
3848
3938
  Card,
3849
3939
  {
@@ -3866,12 +3956,17 @@ function UploadPlaceholder(props) {
3866
3956
  children: [
3867
3957
  /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "flex-start", gap: 2, flex: 1, children: [
3868
3958
  /* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsx(Text, { muted: !0, children: /* @__PURE__ */ jsx(DocumentVideoIcon, {}) }) }),
3869
- /* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Drag video or paste URL here" }) })
3959
+ /* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
3960
+ "Drag ",
3961
+ formatAcceptString(accept),
3962
+ " file or paste URL here"
3963
+ ] }) })
3870
3964
  ] }),
3871
3965
  /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3872
3966
  /* @__PURE__ */ jsx(
3873
3967
  FileInputButton,
3874
3968
  {
3969
+ accept,
3875
3970
  mode: "bleed",
3876
3971
  tone: "default",
3877
3972
  icon: UploadIcon,
@@ -4022,21 +4117,33 @@ function Uploader(props) {
4022
4117
  complete: () => dispatch({ action: "complete" }),
4023
4118
  error: (error) => dispatch({ action: "error", error })
4024
4119
  });
4025
- }, handleUpload = (files) => {
4026
- dispatch({
4120
+ }, invalidFileToast = useCallback(() => {
4121
+ toast.push({
4122
+ status: "error",
4123
+ title: `Invalid file type. Accepted types: ${props.config.acceptedMimeTypes?.join(", ")}`
4124
+ });
4125
+ }, [props.config.acceptedMimeTypes, toast]), isInvalidFile = (files) => Array.from(files).some((file) => !props.config.acceptedMimeTypes?.some((acceptedType) => {
4126
+ const pattern = `^${acceptedType.replace("*", ".*")}$`;
4127
+ return new RegExp(pattern).test(file.type);
4128
+ })), handleUpload = (files) => {
4129
+ isInvalidFile(files) || dispatch({
4027
4130
  action: "stageUpload",
4028
4131
  input: { type: "file", files }
4029
4132
  });
4030
4133
  }, handlePaste = (event) => {
4031
4134
  event.preventDefault(), event.stopPropagation();
4032
- const url = (event.clipboardData || window.clipboardData).getData("text")?.trim();
4135
+ const url = (event.clipboardData || window.clipboardData)?.getData("text")?.trim();
4033
4136
  if (!isValidUrl(url)) {
4034
4137
  toast.push({ status: "error", title: "Invalid URL for Mux video input." });
4035
4138
  return;
4036
4139
  }
4037
4140
  dispatch({ action: "stageUpload", input: { type: "url", url } });
4038
4141
  }, handleDrop = (event) => {
4039
- setDragState(null), event.preventDefault(), event.stopPropagation(), extractDroppedFiles(event.nativeEvent.dataTransfer).then((files) => {
4142
+ if (event.preventDefault(), event.stopPropagation(), dragState === "invalid") {
4143
+ invalidFileToast(), setDragState(null);
4144
+ return;
4145
+ }
4146
+ setDragState(null), extractDroppedFiles(event.nativeEvent.dataTransfer).then((files) => {
4040
4147
  dispatch({
4041
4148
  action: "stageUpload",
4042
4149
  input: { type: "file", files }
@@ -4046,8 +4153,11 @@ function Uploader(props) {
4046
4153
  event.preventDefault(), event.stopPropagation();
4047
4154
  }, handleDragEnter = (event) => {
4048
4155
  event.stopPropagation(), dragEnteredEls.current.push(event.target);
4049
- const type = event.dataTransfer.items?.[0]?.type;
4050
- setDragState(type?.startsWith("video/") ? "valid" : "invalid");
4156
+ const type = event.dataTransfer.items?.[0]?.type, isValidType = props.config.acceptedMimeTypes?.some((acceptedType) => {
4157
+ const pattern = `^${acceptedType.replace("*", ".*")}$`;
4158
+ return new RegExp(pattern).test(type);
4159
+ });
4160
+ setDragState(isValidType ? "valid" : "invalid");
4051
4161
  }, handleDragLeave = (event) => {
4052
4162
  event.stopPropagation();
4053
4163
  const idx = dragEnteredEls.current.indexOf(event.target);
@@ -4085,7 +4195,9 @@ function Uploader(props) {
4085
4195
  }
4086
4196
  );
4087
4197
  let tone;
4088
- return dragState && (tone = dragState === "valid" ? "positive" : "critical"), /* @__PURE__ */ jsxs(Fragment, { children: [
4198
+ dragState && (tone = dragState === "valid" ? "positive" : "critical");
4199
+ const acceptMimeString = props.config?.acceptedMimeTypes?.length ? props.config.acceptedMimeTypes.join(",") : "video/*, audio/*";
4200
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4089
4201
  /* @__PURE__ */ jsx(
4090
4202
  UploadCard,
4091
4203
  {
@@ -4107,9 +4219,11 @@ function Uploader(props) {
4107
4219
  readOnly: props.readOnly,
4108
4220
  asset: props.asset,
4109
4221
  onChange: props.onChange,
4222
+ config: props.config,
4110
4223
  buttons: /* @__PURE__ */ jsx(
4111
4224
  PlayerActionsMenu$1,
4112
4225
  {
4226
+ accept: acceptMimeString,
4113
4227
  asset: props.asset,
4114
4228
  dialogState: props.dialogState,
4115
4229
  setDialogState: props.setDialogState,
@@ -4125,6 +4239,7 @@ function Uploader(props) {
4125
4239
  ) : /* @__PURE__ */ jsx(
4126
4240
  UploadPlaceholder,
4127
4241
  {
4242
+ accept: acceptMimeString,
4128
4243
  hovering: dragState !== null,
4129
4244
  onSelect: handleUpload,
4130
4245
  readOnly: !!props.readOnly,
@@ -4385,7 +4500,8 @@ const muxVideoSchema = {
4385
4500
  defaultPublic: !0,
4386
4501
  defaultSigned: !1,
4387
4502
  tool: DEFAULT_TOOL_CONFIG,
4388
- allowedRolesForConfiguration: []
4503
+ allowedRolesForConfiguration: [],
4504
+ acceptedMimeTypes: ["video/*", "audio/*"]
4389
4505
  };
4390
4506
  function convertLegacyConfig(config) {
4391
4507
  return config.static_renditions && config.static_renditions.length > 0 ? { static_renditions: config.static_renditions } : config.mp4_support === "standard" ? { static_renditions: ["highest"] } : { static_renditions: [] };