sanity-plugin-mux-input 2.14.0 → 2.15.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.
Files changed (48) hide show
  1. package/README.md +25 -24
  2. package/dist/index.d.mts +13 -1
  3. package/dist/index.d.ts +13 -1
  4. package/dist/index.js +771 -351
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +773 -353
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +1 -1
  9. package/src/_exports/index.ts +1 -0
  10. package/src/actions/secrets.ts +6 -1
  11. package/src/actions/upload.ts +1 -1
  12. package/src/components/ConfigureApi.tsx +51 -5
  13. package/src/components/EditCaptionDialog.tsx +2 -2
  14. package/src/components/InputBrowser.tsx +8 -2
  15. package/src/components/PageSelector.tsx +4 -7
  16. package/src/components/Player.styled.tsx +7 -2
  17. package/src/components/PlayerActionsMenu.tsx +1 -1
  18. package/src/components/SelectAsset.tsx +9 -3
  19. package/src/components/StudioTool.tsx +2 -2
  20. package/src/components/UploadConfiguration.tsx +104 -343
  21. package/src/components/Uploader.tsx +18 -7
  22. package/src/components/VideoDetails/VideoDetails.tsx +28 -8
  23. package/src/components/VideoInBrowser.tsx +53 -6
  24. package/src/components/VideoPlayer.tsx +120 -47
  25. package/src/components/VideoThumbnail.tsx +84 -72
  26. package/src/components/VideosBrowser.tsx +7 -5
  27. package/src/components/uploadConfiguration/PlaybackPolicy.tsx +95 -6
  28. package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +26 -10
  29. package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +71 -0
  30. package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +179 -0
  31. package/src/context/DrmPlaybackWarningContext.tsx +93 -0
  32. package/src/hooks/useFetchFileSize.ts +54 -0
  33. package/src/hooks/useMediaMetadata.ts +100 -0
  34. package/src/hooks/useSaveSecrets.ts +10 -3
  35. package/src/hooks/useSecretsDocumentValues.ts +9 -1
  36. package/src/hooks/useSecretsFormState.ts +6 -3
  37. package/src/util/asserters.ts +14 -0
  38. package/src/util/createUrlParamsObject.ts +7 -3
  39. package/src/util/generateJwt.ts +11 -2
  40. package/src/util/getPlaybackPolicy.ts +63 -4
  41. package/src/util/getStoryboardSrc.ts +7 -3
  42. package/src/util/getVideoMetadata.ts +1 -0
  43. package/src/util/getVideoSrc.ts +9 -9
  44. package/src/util/readSecrets.ts +3 -1
  45. package/src/util/textTracks.ts +6 -3
  46. package/src/util/tryWithSuspend.ts +22 -0
  47. package/src/util/types.ts +27 -2
  48. package/src/util/getPlaybackId.ts +0 -9
package/dist/index.js CHANGED
@@ -36,7 +36,45 @@ const ToolIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
36
36
  xmlns: "http://www.w3.org/2000/svg",
37
37
  children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-6l-7 4V7z" })
38
38
  }
39
- ), SANITY_API_VERSION = "2024-03-05";
39
+ ), LOCAL_STORAGE_HAS_SHOWN_WARNING_KEY = "mux-plugin-has-shown-drm-playback-warning", DrmPlaybackWarningContext = React.createContext({
40
+ hasShownWarning: !1,
41
+ setHasWarnedAboutDrmPlayback: () => null
42
+ }), DrmPlaybackWarningContextProvider = ({
43
+ config,
44
+ children
45
+ }) => {
46
+ const hasWarned = (config?.disableDrmPlaybackWarning ?? !1) || window.localStorage.getItem(LOCAL_STORAGE_HAS_SHOWN_WARNING_KEY) === "true", [hasWarnedAboutDrmPlayback, setHasWarnedAboutDrmPlayback] = React.useState(hasWarned), setHasShownWarning = (b) => {
47
+ window.localStorage.setItem(LOCAL_STORAGE_HAS_SHOWN_WARNING_KEY, b.toString()), setHasWarnedAboutDrmPlayback(b);
48
+ };
49
+ return /* @__PURE__ */ jsxRuntime.jsx(
50
+ DrmPlaybackWarningContext.Provider,
51
+ {
52
+ value: {
53
+ hasShownWarning: hasWarnedAboutDrmPlayback,
54
+ setHasWarnedAboutDrmPlayback: setHasShownWarning
55
+ },
56
+ children
57
+ }
58
+ );
59
+ }, useDrmPlaybackWarningContext = () => React.useContext(DrmPlaybackWarningContext), DRMWarningDialog = ({ onClose }) => {
60
+ const { setHasWarnedAboutDrmPlayback } = useDrmPlaybackWarningContext(), _onClose = () => {
61
+ setHasWarnedAboutDrmPlayback(!0), onClose();
62
+ };
63
+ return /* @__PURE__ */ jsxRuntime.jsx(
64
+ ui.Dialog,
65
+ {
66
+ open: !0,
67
+ id: "drm-playback-warn",
68
+ onClose: _onClose,
69
+ header: "DRM Playback Warning",
70
+ footer: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { mode: "ghost", tone: "primary", onClick: _onClose, text: "Ok" }) }),
71
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, padding: 3, children: [
72
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: [3, 3, 3], radius: 2, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 3, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "DRM-protected playback will generate a license with a small associated cost. The plugin will attempt to play signed or public playback IDs instead whenever possible." }) }) }),
73
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: [3, 3, 3], radius: 2, tone: "suggest", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 3, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "This is a one time warning. If it persists, you can disable it from your plugin configuration." }) }) })
74
+ ] })
75
+ }
76
+ );
77
+ }, SANITY_API_VERSION = "2024-03-05";
40
78
  function useClient() {
41
79
  return sanity.useClient({ apiVersion: SANITY_API_VERSION });
42
80
  }
@@ -109,7 +147,7 @@ function useAssets() {
109
147
  function useDialogState() {
110
148
  return React.useState(!1);
111
149
  }
112
- function saveSecrets(client, token, secretKey, enableSignedUrls, signingKeyId, signingKeyPrivate) {
150
+ function saveSecrets(client, token, secretKey, enableSignedUrls, signingKeyId, signingKeyPrivate, drmConfigId) {
113
151
  const doc = {
114
152
  _id: "secrets.mux",
115
153
  _type: "mux.apiKey",
@@ -117,9 +155,10 @@ function saveSecrets(client, token, secretKey, enableSignedUrls, signingKeyId, s
117
155
  secretKey,
118
156
  enableSignedUrls,
119
157
  signingKeyId,
120
- signingKeyPrivate
158
+ signingKeyPrivate,
159
+ drmConfigId
121
160
  };
122
- return client.createOrReplace(doc);
161
+ return doc.signingKeyId = enableSignedUrls ? signingKeyId : "", doc.signingKeyPrivate = enableSignedUrls ? signingKeyPrivate : "", client.createOrReplace(doc);
123
162
  }
124
163
  async function createSigningKeys(client) {
125
164
  try {
@@ -172,7 +211,8 @@ const useSaveSecrets = (client, secrets) => React.useCallback(
172
211
  async ({
173
212
  token,
174
213
  secretKey,
175
- enableSignedUrls
214
+ enableSignedUrls,
215
+ drmConfigId
176
216
  }) => {
177
217
  let { signingKeyId, signingKeyPrivate } = secrets;
178
218
  try {
@@ -182,7 +222,8 @@ const useSaveSecrets = (client, secrets) => React.useCallback(
182
222
  secretKey,
183
223
  enableSignedUrls,
184
224
  signingKeyId,
185
- signingKeyPrivate
225
+ signingKeyPrivate,
226
+ drmConfigId
186
227
  ), !(await testSecrets(client))?.status && token && secretKey)
187
228
  throw new Error("Invalid secrets");
188
229
  } catch (err) {
@@ -201,7 +242,8 @@ const useSaveSecrets = (client, secrets) => React.useCallback(
201
242
  secretKey,
202
243
  enableSignedUrls,
203
244
  signingKeyId,
204
- signingKeyPrivate
245
+ signingKeyPrivate,
246
+ drmConfigId ?? ""
205
247
  );
206
248
  } catch (err) {
207
249
  throw console.log("Error while creating and saving signing key:", err?.message), err;
@@ -211,11 +253,19 @@ const useSaveSecrets = (client, secrets) => React.useCallback(
211
253
  secretKey,
212
254
  enableSignedUrls,
213
255
  signingKeyId,
214
- signingKeyPrivate
256
+ signingKeyPrivate,
257
+ drmConfigId
215
258
  };
216
259
  },
217
260
  [client, secrets]
218
- ), name = "mux-input", cacheNs = "sanity-plugin-mux-input", muxSecretsDocumentId = "secrets.mux", DIALOGS_Z_INDEX = 6e4, THUMBNAIL_ASPECT_RATIO = 1.7777777777777777, MIN_ASPECT_RATIO = 5 / 4, AUDIO_ASPECT_RATIO = 5 / 1, path$1 = ["token", "secretKey", "enableSignedUrls", "signingKeyId", "signingKeyPrivate"], useSecretsDocumentValues = () => {
261
+ ), name = "mux-input", cacheNs = "sanity-plugin-mux-input", muxSecretsDocumentId = "secrets.mux", DIALOGS_Z_INDEX = 6e4, THUMBNAIL_ASPECT_RATIO = 1.7777777777777777, MIN_ASPECT_RATIO = 5 / 4, AUDIO_ASPECT_RATIO = 5 / 1, path$1 = [
262
+ "token",
263
+ "secretKey",
264
+ "enableSignedUrls",
265
+ "signingKeyId",
266
+ "signingKeyPrivate",
267
+ "drmConfigId"
268
+ ], useSecretsDocumentValues = () => {
219
269
  const { error, isLoading, value } = sanity.useDocumentValues(
220
270
  muxSecretsDocumentId,
221
271
  path$1
@@ -225,7 +275,8 @@ const useSaveSecrets = (client, secrets) => React.useCallback(
225
275
  secretKey: value?.secretKey || null,
226
276
  enableSignedUrls: value?.enableSignedUrls || !1,
227
277
  signingKeyId: value?.signingKeyId || null,
228
- signingKeyPrivate: value?.signingKeyPrivate || null
278
+ signingKeyPrivate: value?.signingKeyPrivate || null,
279
+ drmConfigId: value?.drmConfigId || null
229
280
  };
230
281
  return {
231
282
  isInitialSetup: !exists,
@@ -235,7 +286,7 @@ const useSaveSecrets = (client, secrets) => React.useCallback(
235
286
  }, [value]);
236
287
  return { error, isLoading, value: cache };
237
288
  };
238
- function init({ token, secretKey, enableSignedUrls }) {
289
+ function init({ token, secretKey, enableSignedUrls, drmConfigId }) {
239
290
  return {
240
291
  submitting: !1,
241
292
  error: null,
@@ -243,7 +294,8 @@ function init({ token, secretKey, enableSignedUrls }) {
243
294
  // This ensures the `dirty` check works correctly
244
295
  token: token ?? "",
245
296
  secretKey: secretKey ?? "",
246
- enableSignedUrls: enableSignedUrls ?? !1
297
+ enableSignedUrls: enableSignedUrls ?? !1,
298
+ drmConfigId: drmConfigId ?? ""
247
299
  };
248
300
  }
249
301
  function reducer(state, action) {
@@ -271,7 +323,8 @@ function readSecrets(client) {
271
323
  secretKey,
272
324
  enableSignedUrls,
273
325
  signingKeyId,
274
- signingKeyPrivate
326
+ signingKeyPrivate,
327
+ drmConfigId
275
328
  }`,
276
329
  { _id }
277
330
  );
@@ -280,7 +333,8 @@ function readSecrets(client) {
280
333
  secretKey: data?.secretKey || null,
281
334
  enableSignedUrls: !!data?.enableSignedUrls || !1,
282
335
  signingKeyId: data?.signingKeyId || null,
283
- signingKeyPrivate: data?.signingKeyPrivate || null
336
+ signingKeyPrivate: data?.signingKeyPrivate || null,
337
+ drmConfigId: data?.drmConfigId || null
284
338
  };
285
339
  }, [cacheNs, _id, projectId, dataset]);
286
340
  }
@@ -343,20 +397,20 @@ function FormField(props) {
343
397
  ] });
344
398
  }
345
399
  var FormField$1 = React.memo(FormField);
346
- const fieldNames = ["token", "secretKey", "enableSignedUrls"];
400
+ const fieldNames = ["token", "secretKey", "enableSignedUrls", "drmConfigId"];
347
401
  function ConfigureApiDialog({ secrets, setDialogState }) {
348
402
  const client = useClient(), [state, dispatch] = useSecretsFormState(secrets), hasSecretsInitially = React.useMemo(() => secrets.token && secrets.secretKey, [secrets]), handleClose = React.useCallback(() => setDialogState(!1), [setDialogState]), dirty = React.useMemo(
349
- () => secrets.token !== state.token || secrets.secretKey !== state.secretKey || secrets.enableSignedUrls !== state.enableSignedUrls,
403
+ () => secrets.token !== state.token || secrets.secretKey !== state.secretKey || secrets.enableSignedUrls !== state.enableSignedUrls || secrets.drmConfigId !== state.drmConfigId,
350
404
  [secrets, state]
351
- ), id = `ConfigureApi${React.useId()}`, [tokenId, secretKeyId, enableSignedUrlsId] = React.useMemo(
405
+ ), id = `ConfigureApi${React.useId()}`, [tokenId, secretKeyId, enableSignedUrlsId, drmConfigIdId] = React.useMemo(
352
406
  () => fieldNames.map((field) => `${id}-${field}`),
353
407
  [id]
354
408
  ), firstField = React.useRef(null), handleSaveSecrets = useSaveSecrets(client, secrets), saving = React.useRef(!1), handleSubmit = React.useCallback(
355
409
  (event) => {
356
410
  if (event.preventDefault(), !saving.current && event.currentTarget.reportValidity()) {
357
411
  saving.current = !0, dispatch({ type: "submit" });
358
- const { token, secretKey, enableSignedUrls } = state;
359
- handleSaveSecrets({ token, secretKey, enableSignedUrls }).then((savedSecrets) => {
412
+ const { token, secretKey, enableSignedUrls, drmConfigId } = state;
413
+ handleSaveSecrets({ token, secretKey, enableSignedUrls, drmConfigId }).then((savedSecrets) => {
360
414
  const { projectId, dataset } = client.config();
361
415
  suspendReact.clear([cacheNs, _id, projectId, dataset]), suspendReact.preload(() => Promise.resolve(savedSecrets), [cacheNs, _id, projectId, dataset]), setDialogState(!1);
362
416
  }).catch((err) => dispatch({ type: "error", payload: err.message })).finally(() => {
@@ -389,6 +443,14 @@ function ConfigureApiDialog({ secrets, setDialogState }) {
389
443
  });
390
444
  },
391
445
  [dispatch]
446
+ ), handleChangeDrmConfigId = React.useCallback(
447
+ (event) => {
448
+ dispatch({
449
+ type: "change",
450
+ payload: { name: "drmConfigId", value: event.currentTarget.value }
451
+ });
452
+ },
453
+ [dispatch]
392
454
  );
393
455
  return React.useEffect(() => {
394
456
  firstField.current && firstField.current.focus();
@@ -475,6 +537,45 @@ function ConfigureApiDialog({ secrets, setDialogState }) {
475
537
  ] })
476
538
  ] }) }) : null
477
539
  ] }),
540
+ /* @__PURE__ */ jsxRuntime.jsx(FormField$1, { title: "DRM Configuration ID", inputId: drmConfigIdId, children: /* @__PURE__ */ jsxRuntime.jsx(
541
+ ui.TextInput,
542
+ {
543
+ id: drmConfigIdId,
544
+ onChange: handleChangeDrmConfigId,
545
+ type: "text",
546
+ value: state.drmConfigId ?? "",
547
+ required: !1
548
+ }
549
+ ) }),
550
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: [3, 3, 3], radius: 2, shadow: 1, tone: "neutral", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
551
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 1, children: [
552
+ "DRM (Digital Rights Management) provides an extra layer of content security for video content streamed from Mux. For additional information check out our",
553
+ " ",
554
+ /* @__PURE__ */ jsxRuntime.jsx(
555
+ "a",
556
+ {
557
+ href: "https://www.mux.com/docs/guides/protect-videos-with-drm#play-drm-protected-videos",
558
+ target: "_blank",
559
+ rel: "noopener noreferrer",
560
+ children: "DRM Guide"
561
+ }
562
+ ),
563
+ "."
564
+ ] }),
565
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 1, children: [
566
+ /* @__PURE__ */ jsxRuntime.jsx(
567
+ "a",
568
+ {
569
+ href: "https://www.mux.com/support/human",
570
+ target: "_blank",
571
+ rel: "noopener noreferrer",
572
+ children: "Contact us"
573
+ }
574
+ ),
575
+ " ",
576
+ "to get started using DRM."
577
+ ] })
578
+ ] }) }),
478
579
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Inline, { space: 2, children: [
479
580
  /* @__PURE__ */ jsxRuntime.jsx(
480
581
  ui.Button,
@@ -778,6 +879,35 @@ function useInView(ref, options = {}) {
778
879
  };
779
880
  }, [options, ref]), inView;
780
881
  }
882
+ function getPlaybackId(asset, priority = ["drm", "signed", "public"]) {
883
+ try {
884
+ if (!asset)
885
+ throw new TypeError("Tried to get playback Id with no asset");
886
+ const playbackIds = asset.data?.playback_ids;
887
+ if (playbackIds && playbackIds.length > 0) {
888
+ for (const policy of priority) {
889
+ const match = playbackIds.find((entry) => entry.policy === policy);
890
+ if (match)
891
+ return match.id;
892
+ }
893
+ return playbackIds[0].id;
894
+ }
895
+ throw new TypeError("Missing playbackId");
896
+ } catch (e) {
897
+ throw console.error("Asset is missing a playbackId", { asset }, e), e;
898
+ }
899
+ }
900
+ function getPlaybackPolicy(asset) {
901
+ return asset.data?.playback_ids?.find(
902
+ (playbackId) => getPlaybackId(asset, ["drm", "signed", "public"]) === playbackId.id
903
+ ) ?? { id: "", policy: "public" };
904
+ }
905
+ function getPlaybackPolicyById(asset, playbackId) {
906
+ return asset.data?.playback_ids?.find((entry) => playbackId === entry.id);
907
+ }
908
+ function hasPlaybackPolicy(data, policy) {
909
+ return data.advanced_playback_policies && data.advanced_playback_policies.find((p) => p.policy === policy) || data.playback_policy?.find((p) => p === policy);
910
+ }
781
911
  function generateJwt(client, playbackId, aud, payload) {
782
912
  const { signingKeyId, signingKeyPrivate } = readSecrets(client);
783
913
  if (!signingKeyId)
@@ -798,20 +928,13 @@ function generateJwt(client, playbackId, aud, payload) {
798
928
  }
799
929
  );
800
930
  }
801
- function getPlaybackId(asset) {
802
- if (!asset?.playbackId)
803
- throw console.error("Asset is missing a playbackId", { asset }), new TypeError("Missing playbackId");
804
- return asset.playbackId;
805
- }
806
- function getPlaybackPolicy(asset) {
807
- return asset.data?.playback_ids?.find((playbackId) => asset.playbackId === playbackId.id)?.policy ?? "public";
808
- }
809
931
  function createUrlParamsObject(client, asset, params, audience) {
810
932
  const playbackId = getPlaybackId(asset);
811
933
  let searchParams = new URLSearchParams(
812
934
  JSON.parse(JSON.stringify(params, (_, v) => v ?? void 0))
813
935
  );
814
- if (getPlaybackPolicy(asset) === "signed") {
936
+ const playbackPolicy = getPlaybackPolicyById(asset, playbackId)?.policy;
937
+ if (playbackPolicy === "signed" || playbackPolicy === "drm") {
815
938
  const token = generateJwt(client, playbackId, audience, params);
816
939
  searchParams = new URLSearchParams({ token });
817
940
  }
@@ -842,6 +965,15 @@ function getPosterSrc({
842
965
  const { playbackId, searchParams } = createUrlParamsObject(client, asset, params, "t");
843
966
  return `https://image.mux.com/${playbackId}/thumbnail.png?${searchParams}`;
844
967
  }
968
+ function tryWithSuspend(block, onError) {
969
+ try {
970
+ return block();
971
+ } catch (errorOrPromise) {
972
+ if (errorOrPromise instanceof Promise)
973
+ throw errorOrPromise;
974
+ return onError ? onError(errorOrPromise) : void 0;
975
+ }
976
+ }
845
977
  const Image = styledComponents.styled.img`
846
978
  transition: opacity 0.175s ease-out 0s;
847
979
  display: block;
@@ -859,22 +991,22 @@ function VideoThumbnail({
859
991
  width,
860
992
  staticImage = !1
861
993
  }) {
862
- const ref = React.useRef(null), inView = useInView(ref), posterWidth = width || 250, [status, setStatus] = React.useState("loading"), client = useClient(), src = React.useMemo(() => {
863
- try {
994
+ const posterWidth = width || 250, client = useClient(), ref = React.useRef(null), inView = useInView(ref), [status, setStatus] = React.useState("loading"), [error, setError] = React.useState(null), thumbnailSrc = React.useMemo(() => tryWithSuspend(
995
+ () => {
864
996
  let thumbnail;
865
997
  return staticImage ? thumbnail = getPosterSrc({ asset, client, width: posterWidth }) : thumbnail = getAnimatedPosterSrc({ asset, client, width: posterWidth }), thumbnail;
866
- } catch {
867
- status !== "error" && setStatus("error");
868
- return;
998
+ },
999
+ (err) => {
1000
+ handleError(err.message);
869
1001
  }
870
- }, [asset, client, posterWidth, status, staticImage]);
1002
+ ), [asset, client, posterWidth, staticImage]);
871
1003
  function handleLoad() {
872
1004
  setStatus("loaded");
873
1005
  }
874
- function handleError() {
875
- setStatus("error");
1006
+ function handleError(err) {
1007
+ setStatus("error"), setError(err || "Failed loading thumbnail");
876
1008
  }
877
- return /* @__PURE__ */ jsxRuntime.jsx(
1009
+ return /* @__PURE__ */ jsxRuntime.jsx(React.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Preparing thumbnail" }), children: /* @__PURE__ */ jsxRuntime.jsx(
878
1010
  ui.Card,
879
1011
  {
880
1012
  style: {
@@ -915,23 +1047,23 @@ function VideoThumbnail({
915
1047
  },
916
1048
  children: [
917
1049
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 4, muted: !0, children: /* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, { style: { fontSize: "1.75em" } }) }),
918
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, align: "center", children: "Failed loading thumbnail" })
1050
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, align: "center", children: error })
919
1051
  ]
920
1052
  }
921
1053
  ),
922
1054
  /* @__PURE__ */ jsxRuntime.jsx(
923
1055
  Image,
924
1056
  {
925
- src,
1057
+ src: thumbnailSrc ?? void 0,
926
1058
  alt: `Preview for ${staticImage ? "image" : "video"} ${asset.filename || asset.assetId}`,
927
1059
  onLoad: handleLoad,
928
- onError: handleError,
1060
+ onError: () => handleError(),
929
1061
  style: { opacity: status === "loaded" ? 1 : 0 }
930
1062
  }
931
1063
  )
932
1064
  ] }) : null
933
1065
  }
934
- );
1066
+ ) });
935
1067
  }
936
1068
  const MissingAssetCheckbox = styledComponents.styled(ui.Checkbox)`
937
1069
  position: static !important;
@@ -1165,7 +1297,7 @@ const PageSelector = (props) => {
1165
1297
  style: { cursor: "pointer" },
1166
1298
  disabled: page <= 0,
1167
1299
  onClick: () => {
1168
- setPage((page2) => Math.min(props.total - 1, Math.max(0, page2 - 1)));
1300
+ setPage((p) => Math.min(props.total - 1, Math.max(0, p - 1)));
1169
1301
  }
1170
1302
  }
1171
1303
  ),
@@ -1184,7 +1316,7 @@ const PageSelector = (props) => {
1184
1316
  style: { cursor: "pointer" },
1185
1317
  disabled: page >= props.total - 1,
1186
1318
  onClick: () => {
1187
- setPage((page2) => Math.min(props.total - 1, Math.max(0, page2 + 1)));
1319
+ setPage((p) => Math.min(props.total - 1, Math.max(0, p + 1)));
1188
1320
  }
1189
1321
  }
1190
1322
  )
@@ -1548,9 +1680,9 @@ async function downloadVttFile(client, asset, track) {
1548
1680
  const playbackId = getPlaybackId(asset);
1549
1681
  if (!playbackId)
1550
1682
  throw new Error("Playback ID is required");
1551
- const playbackPolicy = getPlaybackPolicy(asset);
1683
+ const playbackPolicy = getPlaybackPolicy(asset)?.policy;
1552
1684
  let downloadUrl = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
1553
- if (playbackPolicy === "signed") {
1685
+ if (playbackPolicy === "signed" || playbackPolicy === "drm") {
1554
1686
  const token = generateJwt(client, playbackId, "v");
1555
1687
  downloadUrl += `?token=${token}`;
1556
1688
  }
@@ -2028,7 +2160,7 @@ function EditCaptionDialog({ asset, track, onUpdate, onClose }) {
2028
2160
  const playbackId = getPlaybackId(asset);
2029
2161
  if (!playbackId) return "";
2030
2162
  let url = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
2031
- if (getPlaybackPolicy(asset) === "signed") {
2163
+ if (getPlaybackPolicy(asset)?.policy === "signed") {
2032
2164
  const token = generateJwt(client, playbackId, "v");
2033
2165
  url += `?token=${token}`;
2034
2166
  }
@@ -2684,13 +2816,13 @@ const DialogStateContext = React.createContext({
2684
2816
  setDialogState,
2685
2817
  children
2686
2818
  }) => /* @__PURE__ */ jsxRuntime.jsx(DialogStateContext.Provider, { value: { dialogState, setDialogState }, children }), useDialogStateContext = () => React.useContext(DialogStateContext);
2687
- function getVideoSrc({ asset, client }) {
2688
- const playbackId = getPlaybackId(asset), searchParams = new URLSearchParams();
2689
- if (getPlaybackPolicy(asset) === "signed") {
2690
- const token = generateJwt(client, playbackId, "v");
2819
+ function getVideoSrc({ client, muxPlaybackId: muxPlaybackId2 }) {
2820
+ const searchParams = new URLSearchParams();
2821
+ if (muxPlaybackId2.policy === "signed" || muxPlaybackId2.policy === "drm") {
2822
+ const token = generateJwt(client, muxPlaybackId2.id, "v");
2691
2823
  searchParams.set("token", token);
2692
2824
  }
2693
- return `https://stream.mux.com/${playbackId}.m3u8?${searchParams}`;
2825
+ return `https://stream.mux.com/${muxPlaybackId2.id}.m3u8?${searchParams}`;
2694
2826
  }
2695
2827
  function CaptionsDialog({ asset }) {
2696
2828
  const { setDialogState } = useDialogStateContext(), dialogId = `CaptionsDialog${React.useId()}`;
@@ -2800,24 +2932,56 @@ function VideoPlayer({
2800
2932
  hlsConfig,
2801
2933
  ...props
2802
2934
  }) {
2803
- const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = React.useRef(null), {
2804
- src: videoSrc,
2805
- thumbnail: thumbnailSrc,
2806
- error
2807
- } = React.useMemo(() => {
2935
+ const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = React.useRef(null), [error, setError] = React.useState(), playbackId = React.useMemo(() => {
2808
2936
  try {
2809
- const thumbnail = getPosterSrc({ asset, client, width: thumbnailWidth }), src = asset?.playbackId && getVideoSrc({ client, asset });
2810
- return src ? { src, thumbnail } : { error: new TypeError("Asset has no playback ID") };
2811
- } catch (error2) {
2812
- return { error: error2 };
2937
+ return getPlaybackId(asset, ["public", "signed", "drm"]);
2938
+ } catch {
2939
+ setError(new TypeError("Asset has no playback ID"));
2940
+ return;
2813
2941
  }
2814
- }, [asset, client, thumbnailWidth]), signedToken = React.useMemo(() => {
2942
+ }, [asset]), muxPlaybackId2 = React.useMemo(() => {
2943
+ if (playbackId)
2944
+ return getPlaybackPolicyById(asset, playbackId);
2945
+ }, [asset, playbackId]), src = React.useMemo(() => {
2946
+ if (playbackId && muxPlaybackId2)
2947
+ return tryWithSuspend(
2948
+ () => getVideoSrc({ muxPlaybackId: muxPlaybackId2, client }),
2949
+ (e) => {
2950
+ setError(e);
2951
+ }
2952
+ );
2953
+ }, [muxPlaybackId2, playbackId, client]), poster = React.useMemo(() => tryWithSuspend(
2954
+ () => getPosterSrc({ asset, client, width: thumbnailWidth }),
2955
+ (e) => {
2956
+ setError(e);
2957
+ }
2958
+ ), [asset, client, thumbnailWidth]), signedToken = React.useMemo(() => {
2815
2959
  try {
2816
- return new URL(videoSrc).searchParams.get("token");
2960
+ return new URL(src).searchParams.get("token");
2817
2961
  } catch {
2818
- return !1;
2962
+ return;
2819
2963
  }
2820
- }, [videoSrc]), [width, height] = (asset?.data?.aspect_ratio ?? "16:9").split(":").map(Number), targetAspectRatio = props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width / height);
2964
+ }, [src]), drmToken = React.useMemo(() => {
2965
+ if (playbackId && muxPlaybackId2?.policy === "drm")
2966
+ return tryWithSuspend(
2967
+ () => generateJwt(client, playbackId, "d"),
2968
+ (e) => {
2969
+ setError(e);
2970
+ }
2971
+ );
2972
+ }, [client, muxPlaybackId2?.policy, playbackId]), tokens = React.useMemo(() => {
2973
+ try {
2974
+ const partialTokens = {
2975
+ playback: void 0,
2976
+ thumbnail: void 0,
2977
+ storyboard: void 0,
2978
+ drm: void 0
2979
+ };
2980
+ return signedToken && (partialTokens.playback = signedToken, partialTokens.thumbnail = signedToken, partialTokens.storyboard = signedToken), drmToken && (partialTokens.drm = drmToken), { ...partialTokens };
2981
+ } catch {
2982
+ return;
2983
+ }
2984
+ }, [signedToken, drmToken]), [width, height] = (asset?.data?.aspect_ratio ?? "16:9").split(":").map(Number), targetAspectRatio = props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width / height);
2821
2985
  let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio);
2822
2986
  return isAudio && (aspectRatio = props.forceAspectRatio ? (
2823
2987
  // Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
@@ -2833,7 +2997,7 @@ function VideoPlayer({
2833
2997
  ...isAudio && { display: "flex", alignItems: "flex-end" }
2834
2998
  },
2835
2999
  children: [
2836
- videoSrc && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3000
+ src && poster && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2837
3001
  isAudio && /* @__PURE__ */ jsxRuntime.jsx(
2838
3002
  AudioIcon,
2839
3003
  {
@@ -2848,34 +3012,36 @@ function VideoPlayer({
2848
3012
  }
2849
3013
  }
2850
3014
  ),
2851
- /* @__PURE__ */ jsxRuntime.jsx(
2852
- MuxPlayer__default.default,
2853
- {
2854
- poster: isAudio ? void 0 : thumbnailSrc,
2855
- ref: muxPlayer,
2856
- ...props,
2857
- playsInline: !0,
2858
- playbackId: asset.playbackId,
2859
- tokens: signedToken ? { playback: signedToken, thumbnail: signedToken, storyboard: signedToken } : void 0,
2860
- preload: "metadata",
2861
- crossOrigin: "anonymous",
2862
- metadata: {
2863
- player_name: "Sanity Admin Dashboard",
2864
- player_version: "2.14.0",
2865
- page_type: "Preview Player"
2866
- },
2867
- audio: isAudio,
2868
- _hlsConfig: hlsConfig,
2869
- style: {
2870
- ...!isAudio && { height: "100%" },
2871
- width: "100%",
2872
- display: "block",
2873
- objectFit: "contain",
2874
- ...isAudio && { alignSelf: "end" }
3015
+ /* @__PURE__ */ jsxRuntime.jsxs(React.Suspense, { fallback: null, children: [
3016
+ /* @__PURE__ */ jsxRuntime.jsx(
3017
+ MuxPlayer__default.default,
3018
+ {
3019
+ poster: isAudio ? void 0 : poster,
3020
+ ref: muxPlayer,
3021
+ ...props,
3022
+ playsInline: !0,
3023
+ playbackId,
3024
+ tokens,
3025
+ preload: "metadata",
3026
+ crossOrigin: "anonymous",
3027
+ metadata: {
3028
+ player_name: "Sanity Admin Dashboard",
3029
+ player_version: "2.15.0",
3030
+ page_type: "Preview Player"
3031
+ },
3032
+ audio: isAudio,
3033
+ _hlsConfig: hlsConfig,
3034
+ style: {
3035
+ ...!isAudio && { height: "100%" },
3036
+ width: "100%",
3037
+ display: "block",
3038
+ objectFit: "contain",
3039
+ ...isAudio && { alignSelf: "end" }
3040
+ }
2875
3041
  }
2876
- }
2877
- ),
2878
- children
3042
+ ),
3043
+ children
3044
+ ] })
2879
3045
  ] }),
2880
3046
  error ? /* @__PURE__ */ jsxRuntime.jsx(
2881
3047
  "div",
@@ -3167,6 +3333,7 @@ function getVideoMetadata(doc) {
3167
3333
  playbackId: doc.playbackId,
3168
3334
  createdAt: date,
3169
3335
  duration: doc.data?.duration ? formatSeconds(doc.data?.duration) : void 0,
3336
+ playback_ids: doc.data?.playback_ids,
3170
3337
  aspect_ratio: doc.data?.aspect_ratio,
3171
3338
  max_stored_resolution: doc.data?.max_stored_resolution,
3172
3339
  max_stored_frame_rate: doc.data?.max_stored_frame_rate,
@@ -3469,14 +3636,7 @@ const AssetInput = (props) => /* @__PURE__ */ jsxRuntime.jsx(FormField$1, { titl
3469
3636
  ),
3470
3637
  /* @__PURE__ */ jsxRuntime.jsx(IconInfo, { text: `Mux ID:
3471
3638
  ${displayInfo.id}`, icon: icons.TagIcon, size: 2 }),
3472
- displayInfo?.playbackId && /* @__PURE__ */ jsxRuntime.jsx(
3473
- IconInfo,
3474
- {
3475
- text: `Playback ID: ${displayInfo.playbackId}`,
3476
- icon: icons.TagIcon,
3477
- size: 2
3478
- }
3479
- )
3639
+ /* @__PURE__ */ jsxRuntime.jsx(PlaybackIds, { playback_ids: displayInfo.playback_ids })
3480
3640
  ] })
3481
3641
  ] })
3482
3642
  }
@@ -3499,6 +3659,25 @@ ${displayInfo.id}`, icon: icons.TagIcon, size: 2 }),
3499
3659
  ]
3500
3660
  }
3501
3661
  );
3662
+ }, PlaybackIds = ({ playback_ids }) => playback_ids ? playback_ids.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
3663
+ IconInfo,
3664
+ {
3665
+ text: `Playback ID [${policyToText(entry.policy)}]: ${entry.id}`,
3666
+ icon: icons.TagIcon,
3667
+ size: 2
3668
+ },
3669
+ entry.id
3670
+ )) : /* @__PURE__ */ jsxRuntime.jsx(IconInfo, { text: "No Playback ID", icon: icons.TagIcon, size: 2 }), policyToText = (policy) => {
3671
+ switch (policy) {
3672
+ case "drm":
3673
+ return "DRM";
3674
+ case "signed":
3675
+ return "Signed";
3676
+ case "public":
3677
+ return "Public";
3678
+ default:
3679
+ return policy;
3680
+ }
3502
3681
  }, VideoMetadata = (props) => {
3503
3682
  if (!props.asset)
3504
3683
  return null;
@@ -3592,10 +3771,12 @@ function VideoInBrowser({
3592
3771
  onEdit,
3593
3772
  asset
3594
3773
  }) {
3595
- const [renderVideo, setRenderVideo] = React.useState(!1), select = React__default.default.useCallback(() => onSelect?.(asset), [onSelect, asset]), edit = React__default.default.useCallback(() => onEdit?.(asset), [onEdit, asset]);
3774
+ const [renderVideo, setRenderVideo] = React.useState(!1), select = React__default.default.useCallback(() => onSelect?.(asset), [onSelect, asset]), edit = React__default.default.useCallback(() => onEdit?.(asset), [onEdit, asset]), { hasShownWarning } = useDrmPlaybackWarningContext();
3596
3775
  if (!asset)
3597
3776
  return null;
3598
- const playbackPolicy = getPlaybackPolicy(asset);
3777
+ const playbackPolicy = getPlaybackPolicy(asset), onClickPlay = () => {
3778
+ playbackPolicy?.policy === "drm" && !hasShownWarning ? setRenderVideo("pre-render-warn") : setRenderVideo("render-video");
3779
+ };
3599
3780
  return /* @__PURE__ */ jsxRuntime.jsxs(
3600
3781
  ui.Card,
3601
3782
  {
@@ -3607,7 +3788,7 @@ function VideoInBrowser({
3607
3788
  position: "relative"
3608
3789
  },
3609
3790
  children: [
3610
- playbackPolicy === "signed" && /* @__PURE__ */ jsxRuntime.jsx(
3791
+ playbackPolicy?.policy === "signed" && /* @__PURE__ */ jsxRuntime.jsx(
3611
3792
  ui.Tooltip,
3612
3793
  {
3613
3794
  animate: !0,
@@ -3624,7 +3805,7 @@ function VideoInBrowser({
3624
3805
  position: "absolute",
3625
3806
  left: "1em",
3626
3807
  top: "1em",
3627
- zIndex: 10
3808
+ zIndex: 11
3628
3809
  },
3629
3810
  padding: 2,
3630
3811
  border: !0,
@@ -3633,6 +3814,32 @@ function VideoInBrowser({
3633
3814
  )
3634
3815
  }
3635
3816
  ),
3817
+ playbackPolicy?.policy === "drm" && /* @__PURE__ */ jsxRuntime.jsx(
3818
+ ui.Tooltip,
3819
+ {
3820
+ animate: !0,
3821
+ content: /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 2, radius: 2, children: /* @__PURE__ */ jsxRuntime.jsx(IconInfo, { icon: icons.LockIcon, text: "DRM playback policy", size: 2 }) }),
3822
+ placement: "right",
3823
+ fallbackPlacements: ["top", "bottom"],
3824
+ portal: !0,
3825
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3826
+ ui.Card,
3827
+ {
3828
+ tone: "caution",
3829
+ style: {
3830
+ borderRadius: "0.25rem",
3831
+ position: "absolute",
3832
+ left: "1em",
3833
+ top: "1em",
3834
+ zIndex: 11
3835
+ },
3836
+ padding: 2,
3837
+ border: !0,
3838
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, size: 1, weight: "semibold", style: { color: "var(--card-icon-color)" }, children: "DRM" })
3839
+ }
3840
+ )
3841
+ }
3842
+ ),
3636
3843
  /* @__PURE__ */ jsxRuntime.jsxs(
3637
3844
  ui.Stack,
3638
3845
  {
@@ -3642,7 +3849,15 @@ function VideoInBrowser({
3642
3849
  gridTemplateRows: "min-content min-content 1fr"
3643
3850
  },
3644
3851
  children: [
3645
- renderVideo ? /* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { asset, autoPlay: !0, forceAspectRatio: THUMBNAIL_ASPECT_RATIO }) : /* @__PURE__ */ jsxRuntime.jsxs(PlayButton, { onClick: () => setRenderVideo(!0), children: [
3852
+ renderVideo === "pre-render-warn" && /* @__PURE__ */ jsxRuntime.jsx(
3853
+ DRMWarningDialog,
3854
+ {
3855
+ onClose: () => {
3856
+ setRenderVideo("render-video");
3857
+ }
3858
+ }
3859
+ ),
3860
+ renderVideo === "render-video" ? /* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { asset, autoPlay: !0, forceAspectRatio: THUMBNAIL_ASPECT_RATIO }) : /* @__PURE__ */ jsxRuntime.jsxs(PlayButton, { onClick: onClickPlay, children: [
3646
3861
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-play": !0, children: /* @__PURE__ */ jsxRuntime.jsx(icons.PlayIcon, {}) }),
3647
3862
  assetIsAudio(asset) ? /* @__PURE__ */ jsxRuntime.jsx(
3648
3863
  "div",
@@ -3704,12 +3919,12 @@ function VideoInBrowser({
3704
3919
  }
3705
3920
  );
3706
3921
  }
3707
- function VideosBrowser({ onSelect }) {
3922
+ function VideosBrowser({ onSelect, config }) {
3708
3923
  const { assets, isLoading, searchQuery, setSearchQuery, setSort, sort } = useAssets(), [page, setPage] = React.useState(0), pageLimit = 20, pageTotal = Math.floor(assets.length / pageLimit) + 1, [editedAsset, setEditedAsset] = React.useState(null), freshEditedAsset = React.useMemo(
3709
3924
  () => assets.find((a2) => a2._id === editedAsset?._id) || editedAsset,
3710
3925
  [editedAsset, assets]
3711
3926
  ), pageStart = page * pageLimit, pageEnd = pageStart + pageLimit;
3712
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3927
+ return /* @__PURE__ */ jsxRuntime.jsxs(DrmPlaybackWarningContextProvider, { config, children: [
3713
3928
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { padding: 4, space: 4, style: { minHeight: "50vh" }, children: [
3714
3929
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { justify: "space-between", align: "center", children: [
3715
3930
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 3, children: [
@@ -3723,7 +3938,7 @@ function VideosBrowser({ onSelect }) {
3723
3938
  }
3724
3939
  ),
3725
3940
  /* @__PURE__ */ jsxRuntime.jsx(SelectSortOptions, { setSort, sort }),
3726
- /* @__PURE__ */ jsxRuntime.jsx(PageSelector, { page, setPage, total: pageTotal, limit: pageLimit })
3941
+ /* @__PURE__ */ jsxRuntime.jsx(PageSelector, { page, setPage, total: pageTotal })
3727
3942
  ] }),
3728
3943
  (onSelect ? "input" : "tool") == "tool" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Inline, { space: 2, children: [
3729
3944
  /* @__PURE__ */ jsxRuntime.jsx(ImportVideosFromMux, {}),
@@ -3764,7 +3979,7 @@ function VideosBrowser({ onSelect }) {
3764
3979
  freshEditedAsset && /* @__PURE__ */ jsxRuntime.jsx(VideoDetails, { closeDialog: () => setEditedAsset(null), asset: freshEditedAsset })
3765
3980
  ] });
3766
3981
  }
3767
- const StudioTool = () => /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, {}), DEFAULT_TOOL_CONFIG = {
3982
+ const StudioTool = (config) => /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, { config }), DEFAULT_TOOL_CONFIG = {
3768
3983
  icon: ToolIcon,
3769
3984
  title: "Videos"
3770
3985
  };
@@ -4122,6 +4337,9 @@ function isValidUrl(url) {
4122
4337
  return !1;
4123
4338
  }
4124
4339
  }
4340
+ function isServerError(error) {
4341
+ return "statusCode" in error && typeof error.statusCode == "number" && 500 <= error.statusCode && error.statusCode <= 600;
4342
+ }
4125
4343
  function extractDroppedFiles(dataTransfer) {
4126
4344
  const files = Array.from(dataTransfer.files || []), items = Array.from(dataTransfer.items || []);
4127
4345
  return files && files.length > 0 ? Promise.resolve(files) : normalizeItems(items).then((arr) => arr.flat());
@@ -4163,7 +4381,12 @@ function walk(entry) {
4163
4381
  }
4164
4382
  return Promise.resolve([]);
4165
4383
  }
4166
- function SelectAssets({ asset: selectedAsset, onChange, setDialogState }) {
4384
+ function SelectAssets({
4385
+ asset: selectedAsset,
4386
+ onChange,
4387
+ setDialogState,
4388
+ config
4389
+ }) {
4167
4390
  const handleSelect = React.useCallback(
4168
4391
  (chosenAsset) => {
4169
4392
  chosenAsset?._id || onChange(sanity.PatchEvent.from([sanity.unset(["asset"])])), chosenAsset._id !== selectedAsset?._id && onChange(
@@ -4175,7 +4398,7 @@ function SelectAssets({ asset: selectedAsset, onChange, setDialogState }) {
4175
4398
  },
4176
4399
  [onChange, setDialogState, selectedAsset]
4177
4400
  );
4178
- return /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, { onSelect: handleSelect });
4401
+ return /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, { onSelect: handleSelect, config });
4179
4402
  }
4180
4403
  const StyledDialog = styledComponents.styled(ui.Dialog)`
4181
4404
  > div[data-ui='DialogCard'] > div[data-ui='Card'] {
@@ -4185,7 +4408,8 @@ const StyledDialog = styledComponents.styled(ui.Dialog)`
4185
4408
  function InputBrowser({
4186
4409
  setDialogState,
4187
4410
  asset,
4188
- onChange
4411
+ onChange,
4412
+ config
4189
4413
  }) {
4190
4414
  const id = `InputBrowser${React.useId()}`, handleClose = React.useCallback(() => setDialogState(!1), [setDialogState]);
4191
4415
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -4196,7 +4420,15 @@ function InputBrowser({
4196
4420
  id,
4197
4421
  onClose: handleClose,
4198
4422
  width: 2,
4199
- children: /* @__PURE__ */ jsxRuntime.jsx(SelectAssets, { asset, onChange, setDialogState })
4423
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4424
+ SelectAssets,
4425
+ {
4426
+ config,
4427
+ asset,
4428
+ onChange,
4429
+ setDialogState
4430
+ }
4431
+ )
4200
4432
  }
4201
4433
  );
4202
4434
  }
@@ -4412,7 +4644,7 @@ const FileButton = styledComponents.styled(ui.MenuItem)(({ theme }) => {
4412
4644
  color: white;
4413
4645
  `, isVideoAsset = (asset) => asset._type === "mux.videoAsset";
4414
4646
  function PlayerActionsMenu(props) {
4415
- const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = React.useState(!1), [menuElement, setMenuRef] = React.useState(null), isSigned = React.useMemo(() => getPlaybackPolicy(asset) === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = React.useCallback(() => onChange(sanity.PatchEvent.from(sanity.unset([]))), [onChange]);
4647
+ const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = React.useState(!1), [menuElement, setMenuRef] = React.useState(null), isSigned = React.useMemo(() => getPlaybackPolicy(asset)?.policy === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = React.useCallback(() => onChange(sanity.PatchEvent.from(sanity.unset([]))), [onChange]);
4416
4648
  return React.useEffect(() => {
4417
4649
  open && dialogState && setOpen(!1);
4418
4650
  }, [dialogState, open]), ui.useClickOutsideEvent(
@@ -4513,6 +4745,79 @@ function PlayerActionsMenu(props) {
4513
4745
  ] });
4514
4746
  }
4515
4747
  var PlayerActionsMenu$1 = React.memo(PlayerActionsMenu);
4748
+ function useFetchFileSize(stagedUpload, maxFileSize) {
4749
+ const [fileSize, setFileSize] = React.useState(null), [isLoadingFileSize, setIsLoadingFileSize] = React.useState(!1), [canSkipFileSizeValidation, setCanSkipFileSizeValidation] = React.useState(!1);
4750
+ return React.useEffect(() => {
4751
+ if (stagedUpload.type === "url") {
4752
+ setIsLoadingFileSize(!1), setCanSkipFileSizeValidation(!1), setFileSize(null);
4753
+ const url = stagedUpload.url;
4754
+ (async () => {
4755
+ setIsLoadingFileSize(!0);
4756
+ try {
4757
+ const contentLength = (await fetch(url, { method: "HEAD" })).headers.get("content-length"), newFileSize = contentLength ? parseInt(contentLength, 10) : null;
4758
+ setIsLoadingFileSize(!1), newFileSize && setFileSize(newFileSize), newFileSize === null && maxFileSize !== void 0 && setCanSkipFileSizeValidation(!0);
4759
+ } catch {
4760
+ console.warn("Could not validate file size from URL"), setCanSkipFileSizeValidation(!0), setIsLoadingFileSize(!1);
4761
+ }
4762
+ })();
4763
+ }
4764
+ stagedUpload.type === "file" && setFileSize(stagedUpload.files[0].size);
4765
+ }, [maxFileSize, stagedUpload, stagedUpload.type]), {
4766
+ fileSize,
4767
+ isLoadingFileSize,
4768
+ canSkipFileSizeValidation
4769
+ };
4770
+ }
4771
+ function useMediaMetadata(stagedUpload) {
4772
+ const [videoAssetMetadata, setVideoAssetMetadata] = React.useState(null), [isLoadingMetadata, setIsLoadingMetadata] = React.useState(!1);
4773
+ return React.useEffect(() => {
4774
+ let videoSrc = null;
4775
+ if (stagedUpload.type === "file") {
4776
+ const file = stagedUpload.files[0];
4777
+ videoSrc = URL.createObjectURL(file);
4778
+ }
4779
+ if (stagedUpload.type === "url" && (videoSrc = stagedUpload.url), setVideoAssetMetadata((old) => ({
4780
+ ...old,
4781
+ duration: void 0,
4782
+ width: void 0,
4783
+ height: void 0
4784
+ })), !videoSrc) return () => null;
4785
+ setIsLoadingMetadata(!0);
4786
+ const videoElement = document.createElement("video");
4787
+ videoElement.preload = "metadata";
4788
+ const metadataListeners = [
4789
+ () => {
4790
+ setIsLoadingMetadata(!1);
4791
+ },
4792
+ () => {
4793
+ const duration = videoElement.duration, width = videoElement.videoWidth, height = videoElement.videoHeight, isAudioOnly = width <= 0 && height <= 0;
4794
+ setVideoAssetMetadata((old) => ({
4795
+ ...old,
4796
+ duration,
4797
+ width,
4798
+ height,
4799
+ isAudioOnly
4800
+ }));
4801
+ }
4802
+ ], cleanupVideo = (videoEl) => {
4803
+ const currentVideoSrc = videoEl?.src;
4804
+ videoEl && (metadataListeners.forEach(
4805
+ (listener) => videoEl.removeEventListener("loadedmetadata", listener)
4806
+ ), videoEl.onerror = null, videoEl.src = "", videoEl.load()), currentVideoSrc?.startsWith("blob:") && URL.revokeObjectURL(currentVideoSrc);
4807
+ };
4808
+ return metadataListeners.push(() => setTimeout(() => cleanupVideo(videoElement), 0)), videoElement.onerror = () => {
4809
+ setIsLoadingMetadata(!1), console.warn("Could not read video metadata for validation"), cleanupVideo(videoElement);
4810
+ }, metadataListeners.forEach(
4811
+ (listener) => videoElement.addEventListener("loadedmetadata", listener)
4812
+ ), videoElement.src = videoSrc, () => {
4813
+ cleanupVideo(videoElement);
4814
+ };
4815
+ }, [stagedUpload.type, stagedUpload]), {
4816
+ videoAssetMetadata,
4817
+ setVideoAssetMetadata,
4818
+ isLoadingMetadata
4819
+ };
4820
+ }
4516
4821
  function formatBytes(bytes, si = !1, dp = 1) {
4517
4822
  const thresh = si ? 1e3 : 1024;
4518
4823
  if (Math.abs(bytes) < thresh)
@@ -4602,13 +4907,14 @@ function PlaybackPolicyOption({
4602
4907
  optionName,
4603
4908
  description,
4604
4909
  dispatch,
4605
- action
4910
+ action,
4911
+ disabled
4606
4912
  }) {
4607
4913
  const [scale, setScale] = React.useState(1), boxStyle = {
4608
4914
  outline: "0.01rem solid grey",
4609
4915
  transform: `scale(${scale})`,
4610
4916
  transition: "transform 0.1s ease-in-out",
4611
- cursor: "pointer",
4917
+ cursor: disabled ? "not-allowed" : "pointer",
4612
4918
  borderRadius: "0.25rem"
4613
4919
  }, triggerAnimation = () => {
4614
4920
  setScale(0.98), setTimeout(() => {
@@ -4616,15 +4922,24 @@ function PlaybackPolicyOption({
4616
4922
  }, 100);
4617
4923
  };
4618
4924
  return /* @__PURE__ */ jsxRuntime.jsx("label", { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, padding: 3, style: boxStyle, children: [
4619
- /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { id, required: !0, checked, onChange: () => {
4620
- triggerAnimation(), dispatch({
4621
- action,
4622
- value: !checked
4623
- });
4624
- } }),
4925
+ /* @__PURE__ */ jsxRuntime.jsx(
4926
+ ui.Checkbox,
4927
+ {
4928
+ id,
4929
+ required: !0,
4930
+ checked,
4931
+ onChange: () => {
4932
+ action && (triggerAnimation(), dispatch({
4933
+ action,
4934
+ value: !checked
4935
+ }));
4936
+ },
4937
+ disabled
4938
+ }
4939
+ ),
4625
4940
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Grid, { gap: 3, children: [
4626
4941
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 3, weight: "bold", children: optionName }),
4627
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 2, muted: !0, children: description })
4942
+ typeof description == "string" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 2, muted: !0, children: description }) : description
4628
4943
  ] })
4629
4944
  ] }) });
4630
4945
  }
@@ -4649,7 +4964,7 @@ function PlaybackPolicy({
4649
4964
  secrets,
4650
4965
  dispatch
4651
4966
  }) {
4652
- const noPolicySelected = !(config.public_policy || config.signed_policy);
4967
+ const noPolicySelected = !(config.public_policy || config.signed_policy || config.drm_policy), drmPolicyDisabled = !secrets.drmConfigId;
4653
4968
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Grid, { gap: 3, children: [
4654
4969
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { weight: "bold", children: "Advanced Playback Policies" }),
4655
4970
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4658,7 +4973,10 @@ function PlaybackPolicy({
4658
4973
  id: `${id}--public`,
4659
4974
  checked: config.public_policy,
4660
4975
  optionName: "Public",
4661
- description: "Playback IDs are accessible by constructing an HLS URL like https://stream.mux.com/{PLAYBACK_ID}",
4976
+ description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4977
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 2, muted: !0, children: "Playback IDs are accessible by constructing an HLS URL like" }),
4978
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Code, { children: "https://stream.mux.com/{PLAYBACK_ID}" })
4979
+ ] }),
4662
4980
  dispatch,
4663
4981
  action: "public_policy"
4664
4982
  }
@@ -4669,24 +4987,146 @@ function PlaybackPolicy({
4669
4987
  id: `${id}--signed`,
4670
4988
  checked: config.signed_policy,
4671
4989
  optionName: "Signed",
4672
- description: `Playback IDs should be used with tokens https://stream.mux.com/{PLAYBACK_ID}?token={TOKEN}.
4673
- // See Secure video playback for details about creating tokens.`,
4990
+ description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4991
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 2, muted: !0, children: "Playback IDs should be used with tokens" }),
4992
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Code, { children: "https://stream.mux.com/{PLAYBACK_ID}?token={TOKEN}" }),
4993
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 2, muted: !0, children: [
4994
+ "See",
4995
+ " ",
4996
+ /* @__PURE__ */ jsxRuntime.jsx(
4997
+ "a",
4998
+ {
4999
+ href: "https://www.mux.com/docs/guides/secure-video-playback",
5000
+ target: "_blank",
5001
+ rel: "noopener noreferrer",
5002
+ children: "Secure video playback"
5003
+ }
5004
+ ),
5005
+ " ",
5006
+ "for details about creating tokens."
5007
+ ] })
5008
+ ] }),
4674
5009
  dispatch,
4675
5010
  action: "signed_policy"
4676
5011
  }
4677
5012
  ),
5013
+ drmPolicyDisabled ? /* @__PURE__ */ jsxRuntime.jsx(
5014
+ PlaybackPolicyOption,
5015
+ {
5016
+ id: `${id}--drm`,
5017
+ checked: !1,
5018
+ optionName: "DRM - Disabled",
5019
+ description: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 2, muted: !0, children: [
5020
+ "To enable DRM add your DRM Configuration Id to your plugin configuration in the API Credentials view.",
5021
+ " ",
5022
+ /* @__PURE__ */ jsxRuntime.jsx(
5023
+ "a",
5024
+ {
5025
+ href: "https://www.mux.com/support/human",
5026
+ target: "_blank",
5027
+ rel: "noopener noreferrer",
5028
+ children: "Contact us"
5029
+ }
5030
+ ),
5031
+ " ",
5032
+ "to get started using DRM."
5033
+ ] }) }),
5034
+ dispatch,
5035
+ disabled: !0
5036
+ }
5037
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
5038
+ PlaybackPolicyOption,
5039
+ {
5040
+ id: `${id}--drm`,
5041
+ checked: config.drm_policy,
5042
+ optionName: "DRM",
5043
+ description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5044
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 2, muted: !0, children: "Playback IDs should be used with tokens as with Signed playback, but require extra configuration." }),
5045
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Code, { children: "https://stream.mux.com/{PLAYBACK_ID}?token={TOKEN}" }),
5046
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 2, muted: !0, children: [
5047
+ "See",
5048
+ " ",
5049
+ /* @__PURE__ */ jsxRuntime.jsx(
5050
+ "a",
5051
+ {
5052
+ href: "https://www.mux.com/docs/guides/protect-videos-with-drm#play-drm-protected-videos",
5053
+ target: "_blank",
5054
+ rel: "noopener noreferrer",
5055
+ children: "Protect videos with DRM"
5056
+ }
5057
+ ),
5058
+ " ",
5059
+ "for details about configuring your player for DRM playback and",
5060
+ " ",
5061
+ /* @__PURE__ */ jsxRuntime.jsx(
5062
+ "a",
5063
+ {
5064
+ href: "https://www.mux.com/docs/guides/secure-video-playback",
5065
+ target: "_blank",
5066
+ rel: "noopener noreferrer",
5067
+ children: "Secure video playback"
5068
+ }
5069
+ ),
5070
+ " ",
5071
+ "for details about creating tokens."
5072
+ ] })
5073
+ ] }),
5074
+ dispatch,
5075
+ action: "drm_policy"
5076
+ }
5077
+ ),
4678
5078
  noPolicySelected && /* @__PURE__ */ jsxRuntime.jsx(PlaybackPolicyWarning, {})
4679
5079
  ] });
4680
5080
  }
4681
- const VIDEO_QUALITY_LEVELS = [
4682
- { value: "basic", label: "Basic" },
4683
- { value: "plus", label: "Plus" },
4684
- { value: "premium", label: "Premium" }
4685
- ], RESOLUTION_TIERS = [
5081
+ const RESOLUTION_TIERS = [
4686
5082
  { value: "1080p", label: "1080p" },
4687
5083
  { value: "1440p", label: "1440p (2k)" },
4688
5084
  { value: "2160p", label: "2160p (4k)" }
4689
- ], ADVANCED_RESOLUTIONS = [
5085
+ ], ResolutionTierSelector = ({
5086
+ id,
5087
+ config,
5088
+ dispatch,
5089
+ maxSupportedResolution
5090
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
5091
+ sanity.FormField,
5092
+ {
5093
+ title: "Resolution Tier",
5094
+ description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5095
+ "The maximum",
5096
+ " ",
5097
+ /* @__PURE__ */ jsxRuntime.jsx(
5098
+ "a",
5099
+ {
5100
+ href: "https://docs.mux.com/api-reference#video/operation/create-direct-upload",
5101
+ target: "_blank",
5102
+ rel: "noopener noreferrer",
5103
+ children: "resolution_tier"
5104
+ }
5105
+ ),
5106
+ " ",
5107
+ "your asset is encoded, stored, and streamed at."
5108
+ ] }),
5109
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 3, wrap: "wrap", children: RESOLUTION_TIERS.map(({ value, label }, index) => {
5110
+ const inputId = `${id}--type-${value}`;
5111
+ return index > maxSupportedResolution ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
5112
+ /* @__PURE__ */ jsxRuntime.jsx(
5113
+ ui.Radio,
5114
+ {
5115
+ checked: config.max_resolution_tier === value,
5116
+ name: "asset-resolutiontier",
5117
+ onChange: (e) => dispatch({
5118
+ action: "max_resolution_tier",
5119
+ value: e.currentTarget.value
5120
+ }),
5121
+ value,
5122
+ id: inputId
5123
+ }
5124
+ ),
5125
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: inputId, children: label })
5126
+ ] }, value);
5127
+ }) })
5128
+ }
5129
+ ), ADVANCED_RESOLUTIONS = [
4690
5130
  { value: "270p", label: "270p" },
4691
5131
  { value: "360p", label: "360p" },
4692
5132
  { value: "480p", label: "480p" },
@@ -4695,6 +5135,130 @@ const VIDEO_QUALITY_LEVELS = [
4695
5135
  { value: "1080p", label: "1080p" },
4696
5136
  { value: "1440p", label: "1440p" },
4697
5137
  { value: "2160p", label: "2160p" }
5138
+ ], StaticRenditionSelector = ({
5139
+ id,
5140
+ config,
5141
+ dispatch
5142
+ }) => {
5143
+ const isAdvancedMode = React.useMemo(() => config.static_renditions.filter(
5144
+ (r) => r !== "highest" && r !== "audio-only"
5145
+ ).length > 0, [config.static_renditions]), [renditionMode, setRenditionMode] = React.useState(
5146
+ isAdvancedMode ? "advanced" : "standard"
5147
+ ), toggleRendition = (rendition) => {
5148
+ const current = config.static_renditions, hasRendition = current.includes(rendition);
5149
+ dispatch(hasRendition ? {
5150
+ action: "static_renditions",
5151
+ value: current.filter((r) => r !== rendition)
5152
+ } : {
5153
+ action: "static_renditions",
5154
+ value: [...current, rendition]
5155
+ });
5156
+ }, handleModeChange = (mode) => {
5157
+ setRenditionMode(mode), dispatch(mode === "standard" ? {
5158
+ action: "static_renditions",
5159
+ value: config.static_renditions.filter((r) => r === "highest" || r === "audio-only")
5160
+ } : {
5161
+ action: "static_renditions",
5162
+ value: config.static_renditions.filter((r) => r !== "highest")
5163
+ });
5164
+ };
5165
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 3, children: /* @__PURE__ */ jsxRuntime.jsx(
5166
+ sanity.FormField,
5167
+ {
5168
+ title: "Static Renditions",
5169
+ description: "Generate downloadable MP4 or M4A files. Note: Mux will not upscale to produce MP4 renditions - renditions that would cause upscaling are skipped.",
5170
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
5171
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, children: [
5172
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
5173
+ /* @__PURE__ */ jsxRuntime.jsx(
5174
+ ui.Radio,
5175
+ {
5176
+ checked: renditionMode === "standard",
5177
+ name: "rendition-mode",
5178
+ onChange: () => handleModeChange("standard"),
5179
+ value: "standard",
5180
+ id: `${id}--mode-standard`
5181
+ }
5182
+ ),
5183
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--mode-standard`, children: "Standard" })
5184
+ ] }),
5185
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
5186
+ /* @__PURE__ */ jsxRuntime.jsx(
5187
+ ui.Radio,
5188
+ {
5189
+ checked: renditionMode === "advanced",
5190
+ name: "rendition-mode",
5191
+ onChange: () => handleModeChange("advanced"),
5192
+ value: "advanced",
5193
+ id: `${id}--mode-advanced`
5194
+ }
5195
+ ),
5196
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--mode-advanced`, children: "Advanced" })
5197
+ ] })
5198
+ ] }),
5199
+ renditionMode === "standard" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
5200
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [0, 2], children: [
5201
+ /* @__PURE__ */ jsxRuntime.jsx(
5202
+ ui.Checkbox,
5203
+ {
5204
+ id: `${id}--highest`,
5205
+ style: { display: "block" },
5206
+ checked: config.static_renditions.includes("highest"),
5207
+ onChange: () => toggleRendition("highest")
5208
+ }
5209
+ ),
5210
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--highest`, children: "Highest Resolution (up to 4K)" })
5211
+ ] }),
5212
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [0, 2], children: [
5213
+ /* @__PURE__ */ jsxRuntime.jsx(
5214
+ ui.Checkbox,
5215
+ {
5216
+ id: `${id}--audio-only-standard`,
5217
+ style: { display: "block" },
5218
+ checked: config.static_renditions.includes("audio-only"),
5219
+ onChange: () => toggleRendition("audio-only")
5220
+ }
5221
+ ),
5222
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--audio-only-standard`, children: "Audio Only (M4A)" })
5223
+ ] })
5224
+ ] }),
5225
+ renditionMode === "advanced" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
5226
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: 1, muted: !0, children: "Select specific resolutions:" }),
5227
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, wrap: "wrap", children: ADVANCED_RESOLUTIONS.map(({ value, label }) => {
5228
+ const inputId = `${id}--resolution-${value}`;
5229
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
5230
+ /* @__PURE__ */ jsxRuntime.jsx(
5231
+ ui.Checkbox,
5232
+ {
5233
+ id: inputId,
5234
+ style: { display: "block" },
5235
+ checked: config.static_renditions.includes(value),
5236
+ onChange: () => toggleRendition(value)
5237
+ }
5238
+ ),
5239
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: inputId, size: 1, children: label })
5240
+ ] }, value);
5241
+ }) }),
5242
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [2, 2, 0, 2], children: [
5243
+ /* @__PURE__ */ jsxRuntime.jsx(
5244
+ ui.Checkbox,
5245
+ {
5246
+ id: `${id}--audio-only-advanced`,
5247
+ style: { display: "block" },
5248
+ checked: config.static_renditions.includes("audio-only"),
5249
+ onChange: () => toggleRendition("audio-only")
5250
+ }
5251
+ ),
5252
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--audio-only-advanced`, children: "Audio Only (M4A)" })
5253
+ ] })
5254
+ ] })
5255
+ ] })
5256
+ }
5257
+ ) });
5258
+ }, VIDEO_QUALITY_LEVELS = [
5259
+ { value: "basic", label: "Basic" },
5260
+ { value: "plus", label: "Plus" },
5261
+ { value: "premium", label: "Premium" }
4698
5262
  ];
4699
5263
  function sanitizeStaticRenditions(renditions) {
4700
5264
  const hasHighest = renditions.includes("highest"), hasSpecificResolutions = renditions.some((r) => r !== "highest" && r !== "audio-only");
@@ -4726,7 +5290,8 @@ function UploadConfiguration({
4726
5290
  max_resolution_tier: "1080p",
4727
5291
  text_tracks: prev.text_tracks?.filter(({ type }) => type !== "autogenerated"),
4728
5292
  public_policy: !0,
4729
- signed_policy: !1
5293
+ signed_policy: !1,
5294
+ drm_policy: !1
4730
5295
  }) : Object.assign({}, prev, {
4731
5296
  video_quality: action.value,
4732
5297
  static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
@@ -4740,6 +5305,8 @@ function UploadConfiguration({
4740
5305
  return Object.assign({}, prev, { [action.action]: action.value });
4741
5306
  case "public_policy":
4742
5307
  return Object.assign({}, prev, { [action.action]: action.value });
5308
+ case "drm_policy":
5309
+ return Object.assign({}, prev, { [action.action]: action.value });
4743
5310
  // Updating individual tracks
4744
5311
  case "track": {
4745
5312
  const text_tracks = [...prev.text_tracks], target_track_i = text_tracks.findIndex(({ _id: _id2 }) => _id2 === action.id);
@@ -4775,75 +5342,41 @@ function UploadConfiguration({
4775
5342
  static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
4776
5343
  signed_policy: secrets.enableSignedUrls && pluginConfig.defaultSigned,
4777
5344
  public_policy: pluginConfig.defaultPublic,
5345
+ drm_policy: pluginConfig.defaultDrm && !!secrets.drmConfigId,
4778
5346
  normalize_audio: pluginConfig.normalize_audio,
4779
5347
  text_tracks: autoTextTracks
4780
5348
  }
4781
- ), isAdvancedMode = React.useMemo(() => config.static_renditions.filter(
4782
- (r) => r !== "highest" && r !== "audio-only"
4783
- ).length > 0, [config.static_renditions]), [renditionMode, setRenditionMode] = React.useState(
4784
- isAdvancedMode ? "advanced" : "standard"
4785
- ), [videoDuration, setVideoDuration] = React.useState(null), [urlFileSize, setUrlFileSize] = React.useState(null), [isLoadingDuration, setIsLoadingDuration] = React.useState(!1), [isLoadingFileSize, setIsLoadingFileSize] = React.useState(!1), [validationError, setValidationError] = React.useState(null), [canSkipFileSizeValidation, setCanSkipFileSizeValidation] = React.useState(!1), MAX_FILE_SIZE = pluginConfig.maxAssetFileSize, MAX_DURATION_SECONDS = pluginConfig.maxAssetDuration;
5349
+ ), [validationError, setValidationError] = React.useState(null), MAX_FILE_SIZE = pluginConfig.maxAssetFileSize, MAX_DURATION_SECONDS = pluginConfig.maxAssetDuration, { fileSize, isLoadingFileSize, canSkipFileSizeValidation } = useFetchFileSize(
5350
+ stagedUpload,
5351
+ MAX_FILE_SIZE
5352
+ ), { videoAssetMetadata, setVideoAssetMetadata, isLoadingMetadata } = useMediaMetadata(stagedUpload);
4786
5353
  React.useEffect(() => {
4787
- setVideoDuration(null), setUrlFileSize(null), setIsLoadingDuration(!1), setIsLoadingFileSize(!1), setValidationError(null), setCanSkipFileSizeValidation(!1);
4788
- let videoElement = null, currentVideoSrc = null;
4789
- const cleanupVideo = (shouldRevokeUrl) => {
4790
- videoElement && (videoElement.onloadedmetadata = null, videoElement.onerror = null, videoElement.src = "", videoElement.load(), videoElement = null), shouldRevokeUrl && currentVideoSrc?.startsWith("blob:") && URL.revokeObjectURL(currentVideoSrc), currentVideoSrc = null;
4791
- }, validateDuration = (videoSrc, shouldRevokeUrl = !1) => {
4792
- !MAX_DURATION_SECONDS || MAX_DURATION_SECONDS <= 0 || (setIsLoadingDuration(!0), videoElement = document.createElement("video"), videoElement.preload = "metadata", currentVideoSrc = videoSrc, videoElement.onloadedmetadata = () => {
4793
- const duration = videoElement.duration;
4794
- setVideoDuration(duration), setIsLoadingDuration(!1), duration > MAX_DURATION_SECONDS && setValidationError(
4795
- `Video duration (${formatSeconds(duration)}) exceeds maximum allowed duration of ${formatSeconds(MAX_DURATION_SECONDS)}`
4796
- ), cleanupVideo(shouldRevokeUrl);
4797
- }, videoElement.onerror = () => {
4798
- setIsLoadingDuration(!1), console.warn("Could not read video metadata for validation"), cleanupVideo(shouldRevokeUrl);
4799
- }, videoElement.src = videoSrc);
4800
- }, validateFileSize = (size) => MAX_FILE_SIZE === void 0 || size <= MAX_FILE_SIZE ? !0 : (setValidationError(
5354
+ fileSize && setVideoAssetMetadata((old) => ({ ...old, size: fileSize }));
5355
+ }, [fileSize, setVideoAssetMetadata]), React.useEffect(() => {
5356
+ const validateDuration = (duration) => MAX_DURATION_SECONDS && duration > MAX_DURATION_SECONDS ? (setValidationError(
5357
+ `Video duration (${formatSeconds(duration)}) exceeds maximum allowed duration of ${formatSeconds(MAX_DURATION_SECONDS)}`
5358
+ ), !1) : !0, validateFileSize = (size) => MAX_FILE_SIZE === void 0 || size <= MAX_FILE_SIZE ? !0 : (setValidationError(
4801
5359
  `File size (${formatBytes(size)}) exceeds maximum allowed size of ${formatBytes(MAX_FILE_SIZE)}`
4802
- ), !1);
4803
- if (stagedUpload.type === "file") {
4804
- const file = stagedUpload.files[0];
4805
- validateFileSize(file.size) && validateDuration(URL.createObjectURL(file), !0);
4806
- }
4807
- if (stagedUpload.type === "url") {
4808
- const url = stagedUpload.url;
4809
- (async () => {
4810
- setIsLoadingFileSize(!0);
4811
- try {
4812
- const contentLength = (await fetch(url, { method: "HEAD" })).headers.get("content-length"), fileSize = contentLength ? parseInt(contentLength, 10) : null;
4813
- setIsLoadingFileSize(!1), fileSize && setUrlFileSize(fileSize);
4814
- const shouldValidateDuration = MAX_FILE_SIZE === void 0 || fileSize === null || validateFileSize(fileSize);
4815
- fileSize === null && MAX_FILE_SIZE !== void 0 && setCanSkipFileSizeValidation(!0), shouldValidateDuration && validateDuration(url);
4816
- } catch {
4817
- setIsLoadingFileSize(!1), console.warn("Could not validate file size from URL"), setCanSkipFileSizeValidation(!0), validateDuration(url);
4818
- }
4819
- })();
4820
- }
4821
- return () => {
4822
- cleanupVideo(!0);
4823
- };
4824
- }, [stagedUpload, MAX_FILE_SIZE, MAX_DURATION_SECONDS]);
4825
- const toggleRendition = (rendition) => {
4826
- const current = config.static_renditions, hasRendition = current.includes(rendition);
4827
- dispatch(hasRendition ? {
4828
- action: "static_renditions",
4829
- value: current.filter((r) => r !== rendition)
4830
- } : {
4831
- action: "static_renditions",
4832
- value: [...current, rendition]
4833
- });
4834
- }, handleModeChange = (mode) => {
4835
- setRenditionMode(mode), dispatch(mode === "standard" ? {
4836
- action: "static_renditions",
4837
- value: config.static_renditions.filter((r) => r === "highest" || r === "audio-only")
4838
- } : {
4839
- action: "static_renditions",
4840
- value: config.static_renditions.filter((r) => r !== "highest")
4841
- });
4842
- }, { disableTextTrackConfig, disableUploadConfig } = pluginConfig, skipConfig = disableTextTrackConfig && disableUploadConfig;
5360
+ ), !1), validateDrmAvailability = (isAudioOnly) => config.drm_policy && isAudioOnly ? (setValidationError("Audio-only asset cannot be DRM protected"), !1) : !0;
5361
+ let valid = !0;
5362
+ videoAssetMetadata?.size && (valid = valid && (canSkipFileSizeValidation || validateFileSize(videoAssetMetadata.size))), videoAssetMetadata?.duration && (valid = valid && validateDuration(videoAssetMetadata.duration)), videoAssetMetadata?.isAudioOnly != null && (valid = valid && validateDrmAvailability(videoAssetMetadata.isAudioOnly)), valid && setValidationError(null);
5363
+ }, [
5364
+ MAX_FILE_SIZE,
5365
+ MAX_DURATION_SECONDS,
5366
+ canSkipFileSizeValidation,
5367
+ videoAssetMetadata?.duration,
5368
+ videoAssetMetadata?.size,
5369
+ videoAssetMetadata?.height,
5370
+ videoAssetMetadata?.width,
5371
+ videoAssetMetadata,
5372
+ config.drm_policy,
5373
+ validationError
5374
+ ]);
5375
+ const { disableTextTrackConfig, disableUploadConfig } = pluginConfig, skipConfig = disableTextTrackConfig && disableUploadConfig;
4843
5376
  if (React.useEffect(() => {
4844
- skipConfig && startUpload(formatUploadConfig(config));
5377
+ skipConfig && startUpload(formatUploadConfig(config, secrets));
4845
5378
  }, []), skipConfig) return null;
4846
- const basicConfig = config.video_quality !== "plus" && config.video_quality !== "premium", maxSupportedResolution = RESOLUTION_TIERS.findIndex(
5379
+ const basicConfig = config.video_quality !== "plus" && config.video_quality !== "premium", playbackPolicySelected = config.public_policy || config.signed_policy || config.drm_policy, maxSupportedResolution = RESOLUTION_TIERS.findIndex(
4847
5380
  (rt) => rt.value === pluginConfig.max_resolution_tier
4848
5381
  );
4849
5382
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -4877,12 +5410,12 @@ function UploadConfiguration({
4877
5410
  /* @__PURE__ */ jsxRuntime.jsx(icons.DocumentVideoIcon, { fontSize: "2em" }),
4878
5411
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
4879
5412
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { textOverflow: "ellipsis", as: "h2", size: 3, children: stagedUpload.type === "file" ? stagedUpload.files[0].name : stagedUpload.url }),
4880
- /* @__PURE__ */ jsxRuntime.jsx(ui.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)" }),
5413
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", size: 1, muted: !0, children: stagedUpload.type === "file" ? `Direct File Upload (${formatBytes(stagedUpload.files[0].size)})` : videoAssetMetadata?.size ? `File From URL (${formatBytes(videoAssetMetadata.size)})` : isLoadingFileSize ? "File From URL (Loading size...)" : "File From URL (Unknown size)" }),
4881
5414
  stagedUpload.type === "file" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 1, children: [
4882
- isLoadingDuration && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", size: 1, muted: !0, children: "Reading video metadata..." }),
4883
- videoDuration !== null && !validationError && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { as: "p", size: 1, muted: !0, children: [
5415
+ isLoadingMetadata && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", size: 1, muted: !0, children: "Reading video metadata..." }),
5416
+ videoAssetMetadata?.duration && !validationError && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { as: "p", size: 1, muted: !0, children: [
4884
5417
  "Duration: ",
4885
- formatSeconds(videoDuration)
5418
+ formatSeconds(videoAssetMetadata.duration)
4886
5419
  ] })
4887
5420
  ] })
4888
5421
  ] })
@@ -4928,160 +5461,37 @@ function UploadConfiguration({
4928
5461
  }) })
4929
5462
  }
4930
5463
  ),
4931
- !basicConfig && maxSupportedResolution > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4932
- sanity.FormField,
4933
- {
4934
- title: "Resolution Tier",
4935
- description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4936
- "The maximum",
4937
- " ",
4938
- /* @__PURE__ */ jsxRuntime.jsx(
4939
- "a",
4940
- {
4941
- href: "https://docs.mux.com/api-reference#video/operation/create-direct-upload",
4942
- target: "_blank",
4943
- rel: "noopener noreferrer",
4944
- children: "resolution_tier"
4945
- }
4946
- ),
4947
- " ",
4948
- "your asset is encoded, stored, and streamed at."
4949
- ] }),
4950
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 3, wrap: "wrap", children: RESOLUTION_TIERS.map(({ value, label }, index) => {
4951
- const inputId = `${id}--type-${value}`;
4952
- return index > maxSupportedResolution ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
4953
- /* @__PURE__ */ jsxRuntime.jsx(
4954
- ui.Radio,
4955
- {
4956
- checked: config.max_resolution_tier === value,
4957
- name: "asset-resolutiontier",
4958
- onChange: (e) => dispatch({
4959
- action: "max_resolution_tier",
4960
- value: e.currentTarget.value
4961
- }),
4962
- value,
4963
- id: inputId
4964
- }
4965
- ),
4966
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: inputId, children: label })
4967
- ] }, value);
4968
- }) })
4969
- }
4970
- ),
4971
5464
  !basicConfig && /* @__PURE__ */ jsxRuntime.jsx(sanity.FormField, { title: "Additional Configuration", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
4972
5465
  /* @__PURE__ */ jsxRuntime.jsx(PlaybackPolicy, { id, config, secrets, dispatch }),
4973
- /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 3, children: /* @__PURE__ */ jsxRuntime.jsx(
4974
- sanity.FormField,
5466
+ maxSupportedResolution > 0 && /* @__PURE__ */ jsxRuntime.jsx(
5467
+ ResolutionTierSelector,
4975
5468
  {
4976
- title: "Static Renditions",
4977
- description: "Generate downloadable MP4 or M4A files. Note: Mux will not upscale to produce MP4 renditions - renditions that would cause upscaling are skipped.",
4978
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
4979
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, children: [
4980
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
4981
- /* @__PURE__ */ jsxRuntime.jsx(
4982
- ui.Radio,
4983
- {
4984
- checked: renditionMode === "standard",
4985
- name: "rendition-mode",
4986
- onChange: () => handleModeChange("standard"),
4987
- value: "standard",
4988
- id: `${id}--mode-standard`
4989
- }
4990
- ),
4991
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--mode-standard`, children: "Standard" })
4992
- ] }),
4993
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
4994
- /* @__PURE__ */ jsxRuntime.jsx(
4995
- ui.Radio,
4996
- {
4997
- checked: renditionMode === "advanced",
4998
- name: "rendition-mode",
4999
- onChange: () => handleModeChange("advanced"),
5000
- value: "advanced",
5001
- id: `${id}--mode-advanced`
5002
- }
5003
- ),
5004
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--mode-advanced`, children: "Advanced" })
5005
- ] })
5006
- ] }),
5007
- renditionMode === "standard" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
5008
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [0, 2], children: [
5009
- /* @__PURE__ */ jsxRuntime.jsx(
5010
- ui.Checkbox,
5011
- {
5012
- id: `${id}--highest`,
5013
- style: { display: "block" },
5014
- checked: config.static_renditions.includes("highest"),
5015
- onChange: () => toggleRendition("highest")
5016
- }
5017
- ),
5018
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--highest`, children: "Highest Resolution (up to 4K)" })
5019
- ] }),
5020
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [0, 2], children: [
5021
- /* @__PURE__ */ jsxRuntime.jsx(
5022
- ui.Checkbox,
5023
- {
5024
- id: `${id}--audio-only-standard`,
5025
- style: { display: "block" },
5026
- checked: config.static_renditions.includes("audio-only"),
5027
- onChange: () => toggleRendition("audio-only")
5028
- }
5029
- ),
5030
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--audio-only-standard`, children: "Audio Only (M4A)" })
5031
- ] })
5032
- ] }),
5033
- renditionMode === "advanced" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
5034
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: 1, muted: !0, children: "Select specific resolutions:" }),
5035
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, wrap: "wrap", children: ADVANCED_RESOLUTIONS.map(({ value, label }) => {
5036
- const inputId = `${id}--resolution-${value}`;
5037
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
5038
- /* @__PURE__ */ jsxRuntime.jsx(
5039
- ui.Checkbox,
5040
- {
5041
- id: inputId,
5042
- style: { display: "block" },
5043
- checked: config.static_renditions.includes(value),
5044
- onChange: () => toggleRendition(value)
5045
- }
5046
- ),
5047
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: inputId, size: 1, children: label })
5048
- ] }, value);
5049
- }) }),
5050
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [2, 2, 0, 2], children: [
5051
- /* @__PURE__ */ jsxRuntime.jsx(
5052
- ui.Checkbox,
5053
- {
5054
- id: `${id}--audio-only-advanced`,
5055
- style: { display: "block" },
5056
- checked: config.static_renditions.includes("audio-only"),
5057
- onChange: () => toggleRendition("audio-only")
5058
- }
5059
- ),
5060
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--audio-only-advanced`, children: "Audio Only (M4A)" })
5061
- ] })
5062
- ] })
5063
- ] })
5469
+ id,
5470
+ config,
5471
+ dispatch,
5472
+ maxSupportedResolution
5064
5473
  }
5065
- ) })
5474
+ ),
5475
+ /* @__PURE__ */ jsxRuntime.jsx(StaticRenditionSelector, { id, config, dispatch }),
5476
+ !disableTextTrackConfig && /* @__PURE__ */ jsxRuntime.jsx(
5477
+ TextTracksEditor,
5478
+ {
5479
+ tracks: config.text_tracks,
5480
+ dispatch,
5481
+ defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
5482
+ }
5483
+ )
5066
5484
  ] }) })
5067
5485
  ] }),
5068
- !disableTextTrackConfig && !basicConfig && /* @__PURE__ */ jsxRuntime.jsx(
5069
- TextTracksEditor,
5070
- {
5071
- tracks: config.text_tracks,
5072
- dispatch,
5073
- defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
5074
- }
5075
- ),
5076
5486
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
5077
5487
  ui.Button,
5078
5488
  {
5079
- disabled: !basicConfig && !config.public_policy && !config.signed_policy || validationError !== null || isLoadingDuration || isLoadingFileSize && !canSkipFileSizeValidation,
5489
+ disabled: !basicConfig && !playbackPolicySelected || validationError !== null || isLoadingMetadata || isLoadingFileSize && !canSkipFileSizeValidation,
5080
5490
  icon: icons.UploadIcon,
5081
5491
  text: "Upload",
5082
5492
  tone: "positive",
5083
5493
  onClick: () => {
5084
- validationError || startUpload(formatUploadConfig(config));
5494
+ validationError || startUpload(formatUploadConfig(config, secrets));
5085
5495
  }
5086
5496
  }
5087
5497
  ) })
@@ -5089,11 +5499,14 @@ function UploadConfiguration({
5089
5499
  }
5090
5500
  );
5091
5501
  }
5092
- function setPlaybackPolicy(config) {
5093
- const playback_policy = [];
5094
- return config.public_policy && playback_policy.push("public"), config.signed_policy && playback_policy.push("signed"), playback_policy;
5502
+ function setAdvancedPlaybackPolicy(config, secrets) {
5503
+ const advanced_playback_policies = [];
5504
+ return config.public_policy && advanced_playback_policies.push({ policy: "public" }), config.signed_policy && advanced_playback_policies.push({ policy: "signed" }), config.drm_policy && (secrets.drmConfigId ? advanced_playback_policies.push({
5505
+ policy: "drm",
5506
+ drm_configuration_id: secrets.drmConfigId ?? void 0
5507
+ }) : console.error("Selected DRM Policy but missing DRM Configuration Id")), advanced_playback_policies;
5095
5508
  }
5096
- function formatUploadConfig(config) {
5509
+ function formatUploadConfig(config, secrets) {
5097
5510
  const generated_subtitles = config.text_tracks.filter(isAutogeneratedTrack).map((track) => ({
5098
5511
  name: track.name,
5099
5512
  language_code: track.language_code
@@ -5117,7 +5530,7 @@ function formatUploadConfig(config) {
5117
5530
  )
5118
5531
  ],
5119
5532
  static_renditions: config.static_renditions.length > 0 ? config.static_renditions.map((resolution) => ({ resolution })) : void 0,
5120
- playback_policy: setPlaybackPolicy(config),
5533
+ advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
5121
5534
  max_resolution_tier: config.max_resolution_tier,
5122
5535
  video_quality: config.video_quality,
5123
5536
  normalize_audio: config.normalize_audio
@@ -5329,8 +5742,13 @@ function Uploader(props) {
5329
5742
  case "reset":
5330
5743
  case "complete":
5331
5744
  return uploadRef.current?.unsubscribe(), uploadRef.current = null, uploadingDocumentId.current = null, INITIAL_STATE;
5332
- case "error":
5333
- return uploadRef.current?.unsubscribe(), uploadRef.current = null, uploadingDocumentId.current = null, Object.assign({}, INITIAL_STATE, { error: action.error });
5745
+ case "error": {
5746
+ uploadRef.current?.unsubscribe(), uploadRef.current = null, uploadingDocumentId.current = null;
5747
+ let error = action.error;
5748
+ return isServerError(action.error) && hasPlaybackPolicy(action.settings, "drm") && (error = new Error(
5749
+ "Unknown Error while uploading DRM protected content. Make sure your DRM configuration ID is valid and set correctly"
5750
+ )), Object.assign({}, INITIAL_STATE, { error });
5751
+ }
5334
5752
  default:
5335
5753
  return prev;
5336
5754
  }
@@ -5409,7 +5827,7 @@ function Uploader(props) {
5409
5827
  }
5410
5828
  },
5411
5829
  complete: () => dispatch({ action: "complete" }),
5412
- error: (error) => dispatch({ action: "error", error })
5830
+ error: (error) => dispatch({ action: "error", error, settings })
5413
5831
  });
5414
5832
  }, invalidFileToast = React.useCallback(() => {
5415
5833
  toast.push({
@@ -5460,11 +5878,11 @@ function Uploader(props) {
5460
5878
  idx > -1 && dragEnteredEls.current.splice(idx, 1), dragEnteredEls.current.length === 0 && setDragState(null);
5461
5879
  };
5462
5880
  if (state.error !== null) {
5463
- const error = {};
5881
+ const error = state.error;
5464
5882
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, direction: "column", justify: "center", align: "center", children: [
5465
5883
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 5, muted: !0, children: /* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, {}) }),
5466
5884
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Something went wrong" }),
5467
- error instanceof Error && error.message && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: error.message }),
5885
+ error instanceof Error && error.message && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, weight: "semibold", style: { textAlign: "center" }, children: error.message }),
5468
5886
  /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { text: "Upload another file", onClick: () => dispatch({ action: "reset" }) })
5469
5887
  ] });
5470
5888
  }
@@ -5549,6 +5967,7 @@ function Uploader(props) {
5549
5967
  props.dialogState === "select-video" && /* @__PURE__ */ jsxRuntime.jsx(
5550
5968
  InputBrowser,
5551
5969
  {
5970
+ config: props.config,
5552
5971
  asset: props.asset,
5553
5972
  onChange: props.onChange,
5554
5973
  setDialogState: props.setDialogState
@@ -5795,6 +6214,7 @@ const muxVideoSchema = {
5795
6214
  normalize_audio: !1,
5796
6215
  defaultPublic: !0,
5797
6216
  defaultSigned: !1,
6217
+ defaultDrm: !1,
5798
6218
  tool: DEFAULT_TOOL_CONFIG,
5799
6219
  allowedRolesForConfiguration: [],
5800
6220
  acceptedMimeTypes: ["video/*", "audio/*"]