sanity-plugin-mux-input 2.13.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.
- package/README.md +25 -24
- package/dist/index.d.mts +35 -2
- package/dist/index.d.ts +35 -2
- package/dist/index.js +2176 -461
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2178 -463
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/_exports/index.ts +1 -0
- package/src/actions/assets.ts +75 -0
- package/src/actions/secrets.ts +6 -1
- package/src/actions/upload.ts +1 -1
- package/src/components/AddCaptionDialog.tsx +421 -0
- package/src/components/CaptionsDialog.tsx +23 -0
- package/src/components/ConfigureApi.tsx +51 -5
- package/src/components/EditCaptionDialog.tsx +508 -0
- package/src/components/InputBrowser.tsx +8 -2
- package/src/components/Onboard.tsx +2 -2
- package/src/components/PageSelector.tsx +54 -0
- package/src/components/Player.styled.tsx +7 -2
- package/src/components/PlayerActionsMenu.tsx +14 -6
- package/src/components/SelectAsset.tsx +9 -3
- package/src/components/StudioTool.tsx +2 -2
- package/src/components/TextTracksManager.tsx +781 -0
- package/src/components/UploadConfiguration.tsx +104 -343
- package/src/components/Uploader.styled.tsx +8 -15
- package/src/components/Uploader.tsx +25 -7
- package/src/components/VideoDetails/VideoDetails.tsx +43 -7
- package/src/components/VideoInBrowser.tsx +53 -6
- package/src/components/VideoPlayer.tsx +122 -47
- package/src/components/VideoThumbnail.tsx +84 -72
- package/src/components/VideosBrowser.tsx +15 -5
- package/src/components/uploadConfiguration/PlaybackPolicy.tsx +95 -6
- package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +26 -10
- package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +71 -0
- package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +179 -0
- package/src/context/DrmPlaybackWarningContext.tsx +93 -0
- package/src/hooks/useAccessControl.ts +1 -0
- package/src/hooks/useDialogState.ts +1 -1
- package/src/hooks/useFetchFileSize.ts +54 -0
- package/src/hooks/useMediaMetadata.ts +100 -0
- package/src/hooks/useSaveSecrets.ts +10 -3
- package/src/hooks/useSecretsDocumentValues.ts +9 -1
- package/src/hooks/useSecretsFormState.ts +6 -3
- package/src/util/asserters.ts +14 -0
- package/src/util/createUrlParamsObject.ts +7 -3
- package/src/util/generateJwt.ts +11 -2
- package/src/util/getPlaybackPolicy.ts +63 -4
- package/src/util/getStoryboardSrc.ts +7 -3
- package/src/util/getVideoMetadata.ts +4 -1
- package/src/util/getVideoSrc.ts +9 -9
- package/src/util/readSecrets.ts +3 -1
- package/src/util/textTracks.ts +222 -0
- package/src/util/tryWithSuspend.ts +22 -0
- package/src/util/types.ts +39 -6
- package/src/util/getPlaybackId.ts +0 -9
package/dist/index.js
CHANGED
|
@@ -19,11 +19,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
19
19
|
mod
|
|
20
20
|
));
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
22
|
-
var sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), icons = require("@sanity/icons"), ui = require("@sanity/ui"), React = require("react"), compact = require("lodash/compact.js"), toLower = require("lodash/toLower.js"), trim = require("lodash/trim.js"), uniq = require("lodash/uniq.js"), words = require("lodash/words.js"), suspendReact = require("suspend-react"), rxjs = require("rxjs"), styledComponents = require("styled-components"), uuid = require("@sanity/uuid"), operators = require("rxjs/operators"), MuxPlayer = require("@mux/mux-player-react/lazy"), router = require("sanity/router"), isNumber = require("lodash/isNumber.js"), isString = require("lodash/isString.js"), reactRx = require("react-rx"), useSWR = require("swr"), scrollIntoView = require("scroll-into-view-if-needed"), upchunk = require("@mux/upchunk"), reactIs = require("react-is")
|
|
22
|
+
var sanity = require("sanity"), jsxRuntime = require("react/jsx-runtime"), icons = require("@sanity/icons"), ui = require("@sanity/ui"), React = require("react"), compact = require("lodash/compact.js"), toLower = require("lodash/toLower.js"), trim = require("lodash/trim.js"), uniq = require("lodash/uniq.js"), words = require("lodash/words.js"), suspendReact = require("suspend-react"), rxjs = require("rxjs"), styledComponents = require("styled-components"), uuid = require("@sanity/uuid"), operators = require("rxjs/operators"), LanguagesList = require("iso-639-1"), MuxPlayer = require("@mux/mux-player-react/lazy"), router = require("sanity/router"), isNumber = require("lodash/isNumber.js"), isString = require("lodash/isString.js"), reactRx = require("react-rx"), useSWR = require("swr"), scrollIntoView = require("scroll-into-view-if-needed"), upchunk = require("@mux/upchunk"), reactIs = require("react-is");
|
|
23
23
|
function _interopDefaultCompat(e) {
|
|
24
24
|
return e && typeof e == "object" && "default" in e ? e : { default: e };
|
|
25
25
|
}
|
|
26
|
-
var React__default = /* @__PURE__ */ _interopDefaultCompat(React), compact__default = /* @__PURE__ */ _interopDefaultCompat(compact), toLower__default = /* @__PURE__ */ _interopDefaultCompat(toLower), trim__default = /* @__PURE__ */ _interopDefaultCompat(trim), uniq__default = /* @__PURE__ */ _interopDefaultCompat(uniq), words__default = /* @__PURE__ */ _interopDefaultCompat(words),
|
|
26
|
+
var React__default = /* @__PURE__ */ _interopDefaultCompat(React), compact__default = /* @__PURE__ */ _interopDefaultCompat(compact), toLower__default = /* @__PURE__ */ _interopDefaultCompat(toLower), trim__default = /* @__PURE__ */ _interopDefaultCompat(trim), uniq__default = /* @__PURE__ */ _interopDefaultCompat(uniq), words__default = /* @__PURE__ */ _interopDefaultCompat(words), LanguagesList__default = /* @__PURE__ */ _interopDefaultCompat(LanguagesList), MuxPlayer__default = /* @__PURE__ */ _interopDefaultCompat(MuxPlayer), isNumber__default = /* @__PURE__ */ _interopDefaultCompat(isNumber), isString__default = /* @__PURE__ */ _interopDefaultCompat(isString), useSWR__default = /* @__PURE__ */ _interopDefaultCompat(useSWR), scrollIntoView__default = /* @__PURE__ */ _interopDefaultCompat(scrollIntoView);
|
|
27
27
|
const ToolIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
|
|
28
28
|
"svg",
|
|
29
29
|
{
|
|
@@ -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
|
-
),
|
|
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 = [
|
|
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,
|
|
@@ -568,6 +669,51 @@ function listAssets(client, options) {
|
|
|
568
669
|
query
|
|
569
670
|
});
|
|
570
671
|
}
|
|
672
|
+
function addTextTrackFromUrl(client, assetId, vttUrl, options) {
|
|
673
|
+
const { dataset } = client.config();
|
|
674
|
+
return client.request({
|
|
675
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks`,
|
|
676
|
+
withCredentials: !0,
|
|
677
|
+
method: "POST",
|
|
678
|
+
body: {
|
|
679
|
+
url: vttUrl,
|
|
680
|
+
type: "text",
|
|
681
|
+
language_code: options.language_code,
|
|
682
|
+
name: options.name,
|
|
683
|
+
text_type: options.text_type || "subtitles"
|
|
684
|
+
},
|
|
685
|
+
headers: {
|
|
686
|
+
"Content-Type": "application/json"
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
function generateSubtitles(client, assetId, audioTrackId, options) {
|
|
691
|
+
const { dataset } = client.config();
|
|
692
|
+
return client.request({
|
|
693
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${audioTrackId}/generate-subtitles`,
|
|
694
|
+
withCredentials: !0,
|
|
695
|
+
method: "POST",
|
|
696
|
+
body: {
|
|
697
|
+
generated_subtitles: [
|
|
698
|
+
{
|
|
699
|
+
language_code: options.language_code,
|
|
700
|
+
name: options.name
|
|
701
|
+
}
|
|
702
|
+
]
|
|
703
|
+
},
|
|
704
|
+
headers: {
|
|
705
|
+
"Content-Type": "application/json"
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
function deleteTextTrack(client, assetId, trackId) {
|
|
710
|
+
const { dataset } = client.config();
|
|
711
|
+
return client.request({
|
|
712
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${trackId}`,
|
|
713
|
+
withCredentials: !0,
|
|
714
|
+
method: "DELETE"
|
|
715
|
+
});
|
|
716
|
+
}
|
|
571
717
|
const ASSETS_PER_PAGE = 100;
|
|
572
718
|
async function fetchMuxAssetsPage(client, cursor) {
|
|
573
719
|
try {
|
|
@@ -733,6 +879,35 @@ function useInView(ref, options = {}) {
|
|
|
733
879
|
};
|
|
734
880
|
}, [options, ref]), inView;
|
|
735
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
|
+
}
|
|
736
911
|
function generateJwt(client, playbackId, aud, payload) {
|
|
737
912
|
const { signingKeyId, signingKeyPrivate } = readSecrets(client);
|
|
738
913
|
if (!signingKeyId)
|
|
@@ -753,20 +928,13 @@ function generateJwt(client, playbackId, aud, payload) {
|
|
|
753
928
|
}
|
|
754
929
|
);
|
|
755
930
|
}
|
|
756
|
-
function getPlaybackId(asset) {
|
|
757
|
-
if (!asset?.playbackId)
|
|
758
|
-
throw console.error("Asset is missing a playbackId", { asset }), new TypeError("Missing playbackId");
|
|
759
|
-
return asset.playbackId;
|
|
760
|
-
}
|
|
761
|
-
function getPlaybackPolicy(asset) {
|
|
762
|
-
return asset.data?.playback_ids?.find((playbackId) => asset.playbackId === playbackId.id)?.policy ?? "public";
|
|
763
|
-
}
|
|
764
931
|
function createUrlParamsObject(client, asset, params, audience) {
|
|
765
932
|
const playbackId = getPlaybackId(asset);
|
|
766
933
|
let searchParams = new URLSearchParams(
|
|
767
934
|
JSON.parse(JSON.stringify(params, (_, v) => v ?? void 0))
|
|
768
935
|
);
|
|
769
|
-
|
|
936
|
+
const playbackPolicy = getPlaybackPolicyById(asset, playbackId)?.policy;
|
|
937
|
+
if (playbackPolicy === "signed" || playbackPolicy === "drm") {
|
|
770
938
|
const token = generateJwt(client, playbackId, audience, params);
|
|
771
939
|
searchParams = new URLSearchParams({ token });
|
|
772
940
|
}
|
|
@@ -797,6 +965,15 @@ function getPosterSrc({
|
|
|
797
965
|
const { playbackId, searchParams } = createUrlParamsObject(client, asset, params, "t");
|
|
798
966
|
return `https://image.mux.com/${playbackId}/thumbnail.png?${searchParams}`;
|
|
799
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
|
+
}
|
|
800
977
|
const Image = styledComponents.styled.img`
|
|
801
978
|
transition: opacity 0.175s ease-out 0s;
|
|
802
979
|
display: block;
|
|
@@ -814,22 +991,22 @@ function VideoThumbnail({
|
|
|
814
991
|
width,
|
|
815
992
|
staticImage = !1
|
|
816
993
|
}) {
|
|
817
|
-
const ref = React.useRef(null), inView = useInView(ref),
|
|
818
|
-
|
|
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
|
+
() => {
|
|
819
996
|
let thumbnail;
|
|
820
997
|
return staticImage ? thumbnail = getPosterSrc({ asset, client, width: posterWidth }) : thumbnail = getAnimatedPosterSrc({ asset, client, width: posterWidth }), thumbnail;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
998
|
+
},
|
|
999
|
+
(err) => {
|
|
1000
|
+
handleError(err.message);
|
|
824
1001
|
}
|
|
825
|
-
|
|
1002
|
+
), [asset, client, posterWidth, staticImage]);
|
|
826
1003
|
function handleLoad() {
|
|
827
1004
|
setStatus("loaded");
|
|
828
1005
|
}
|
|
829
|
-
function handleError() {
|
|
830
|
-
setStatus("error");
|
|
1006
|
+
function handleError(err) {
|
|
1007
|
+
setStatus("error"), setError(err || "Failed loading thumbnail");
|
|
831
1008
|
}
|
|
832
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1009
|
+
return /* @__PURE__ */ jsxRuntime.jsx(React.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Preparing thumbnail" }), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
833
1010
|
ui.Card,
|
|
834
1011
|
{
|
|
835
1012
|
style: {
|
|
@@ -870,23 +1047,23 @@ function VideoThumbnail({
|
|
|
870
1047
|
},
|
|
871
1048
|
children: [
|
|
872
1049
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 4, muted: !0, children: /* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, { style: { fontSize: "1.75em" } }) }),
|
|
873
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, align: "center", children:
|
|
1050
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, align: "center", children: error })
|
|
874
1051
|
]
|
|
875
1052
|
}
|
|
876
1053
|
),
|
|
877
1054
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
878
1055
|
Image,
|
|
879
1056
|
{
|
|
880
|
-
src,
|
|
1057
|
+
src: thumbnailSrc ?? void 0,
|
|
881
1058
|
alt: `Preview for ${staticImage ? "image" : "video"} ${asset.filename || asset.assetId}`,
|
|
882
1059
|
onLoad: handleLoad,
|
|
883
|
-
onError: handleError,
|
|
1060
|
+
onError: () => handleError(),
|
|
884
1061
|
style: { opacity: status === "loaded" ? 1 : 0 }
|
|
885
1062
|
}
|
|
886
1063
|
)
|
|
887
1064
|
] }) : null
|
|
888
1065
|
}
|
|
889
|
-
);
|
|
1066
|
+
) });
|
|
890
1067
|
}
|
|
891
1068
|
const MissingAssetCheckbox = styledComponents.styled(ui.Checkbox)`
|
|
892
1069
|
position: static !important;
|
|
@@ -1105,6 +1282,46 @@ function ImportVideosFromMux() {
|
|
|
1105
1282
|
if (importAssets.hasSecrets)
|
|
1106
1283
|
return importAssets.dialogOpen ? /* @__PURE__ */ jsxRuntime.jsx(ImportVideosDialog, { ...importAssets }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { mode: "bleed", text: "Import from Mux", onClick: importAssets.openDialog });
|
|
1107
1284
|
}
|
|
1285
|
+
const PageSelector = (props) => {
|
|
1286
|
+
const page = props.page, setPage = props.setPage;
|
|
1287
|
+
return React.useEffect(() => {
|
|
1288
|
+
const clamped = Math.min(props.total - 1, Math.max(0, page));
|
|
1289
|
+
page !== clamped && setPage(clamped);
|
|
1290
|
+
}, [page, props.total, setPage]), /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1291
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1292
|
+
ui.Button,
|
|
1293
|
+
{
|
|
1294
|
+
icon: icons.ChevronLeftIcon,
|
|
1295
|
+
mode: "bleed",
|
|
1296
|
+
padding: 3,
|
|
1297
|
+
style: { cursor: "pointer" },
|
|
1298
|
+
disabled: page <= 0,
|
|
1299
|
+
onClick: () => {
|
|
1300
|
+
setPage((p) => Math.min(props.total - 1, Math.max(0, p - 1)));
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
),
|
|
1304
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Label, { muted: !0, children: [
|
|
1305
|
+
"Page ",
|
|
1306
|
+
page + 1,
|
|
1307
|
+
"/",
|
|
1308
|
+
props.total
|
|
1309
|
+
] }),
|
|
1310
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1311
|
+
ui.Button,
|
|
1312
|
+
{
|
|
1313
|
+
icon: icons.ChevronRightIcon,
|
|
1314
|
+
mode: "bleed",
|
|
1315
|
+
padding: 3,
|
|
1316
|
+
style: { cursor: "pointer" },
|
|
1317
|
+
disabled: page >= props.total - 1,
|
|
1318
|
+
onClick: () => {
|
|
1319
|
+
setPage((p) => Math.min(props.total - 1, Math.max(0, p + 1)));
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
)
|
|
1323
|
+
] });
|
|
1324
|
+
};
|
|
1108
1325
|
function useResyncMuxMetadata() {
|
|
1109
1326
|
const documentStore = sanity.useDocumentStore(), client = sanity.useClient({
|
|
1110
1327
|
apiVersion: SANITY_API_VERSION
|
|
@@ -1380,75 +1597,1290 @@ function StopWatchIcon(props) {
|
|
|
1380
1597
|
}
|
|
1381
1598
|
);
|
|
1382
1599
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1600
|
+
function extractErrorMessage(error, defaultMessage = "Failed to process request") {
|
|
1601
|
+
let message = "";
|
|
1602
|
+
if (error && typeof error == "object") {
|
|
1603
|
+
const err = error;
|
|
1604
|
+
message = err.response?.body?.message || err.message || "";
|
|
1605
|
+
} else typeof error == "string" && (message = error);
|
|
1606
|
+
if (!message)
|
|
1607
|
+
return defaultMessage;
|
|
1608
|
+
const match = message.match(/\(([^)]+)\)/);
|
|
1609
|
+
if (match && match[1])
|
|
1610
|
+
return match[1];
|
|
1611
|
+
if (message.includes("responded with")) {
|
|
1612
|
+
const parts = message.split("(");
|
|
1613
|
+
if (parts.length > 1)
|
|
1614
|
+
return parts[parts.length - 1].replace(")", "").trim();
|
|
1396
1615
|
}
|
|
1397
|
-
return
|
|
1616
|
+
return message;
|
|
1398
1617
|
}
|
|
1399
|
-
function
|
|
1618
|
+
async function pollTrackStatus(options) {
|
|
1400
1619
|
const {
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1620
|
+
client,
|
|
1621
|
+
assetId,
|
|
1622
|
+
trackName,
|
|
1623
|
+
trackLanguageCode,
|
|
1624
|
+
maxAttempts = 10,
|
|
1625
|
+
onTrackFound,
|
|
1626
|
+
onTrackErrored,
|
|
1627
|
+
onTrackReady
|
|
1628
|
+
} = options, trimmedName = trackName.trim(), trimmedLanguageCode = trackLanguageCode.trim();
|
|
1629
|
+
let newTrack, attempts = 0, trackFound = !1;
|
|
1630
|
+
const findTrack = (textTracks) => {
|
|
1631
|
+
let foundTrack = textTracks.find(
|
|
1632
|
+
(track) => track.name === trimmedName && track.language_code === trimmedLanguageCode
|
|
1633
|
+
);
|
|
1634
|
+
return foundTrack || (foundTrack = textTracks.find((track) => track.language_code === trimmedLanguageCode)), !foundTrack && textTracks.length > 0 && (foundTrack = textTracks[textTracks.length - 1]), foundTrack;
|
|
1635
|
+
};
|
|
1636
|
+
for (; attempts < maxAttempts; ) {
|
|
1637
|
+
try {
|
|
1638
|
+
attempts > 0 && await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1639
|
+
const textTracks = (await getAsset(client, assetId)).data.tracks?.filter((track) => track.type === "text") || [], foundTrack = findTrack(textTracks);
|
|
1640
|
+
if (!foundTrack) {
|
|
1641
|
+
attempts++;
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (trackFound = !0, newTrack = foundTrack, onTrackFound && onTrackFound(foundTrack), foundTrack.status === "ready") {
|
|
1645
|
+
onTrackReady && onTrackReady(foundTrack);
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
if (foundTrack.status === "errored")
|
|
1649
|
+
return onTrackErrored && onTrackErrored(foundTrack), {
|
|
1650
|
+
track: foundTrack,
|
|
1651
|
+
found: !0,
|
|
1652
|
+
status: "errored"
|
|
1653
|
+
};
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
console.error("Failed to fetch updated asset:", error);
|
|
1656
|
+
}
|
|
1657
|
+
attempts++;
|
|
1658
|
+
}
|
|
1659
|
+
return !newTrack || !trackFound ? {
|
|
1660
|
+
track: void 0,
|
|
1661
|
+
found: !1,
|
|
1662
|
+
status: "not-found"
|
|
1663
|
+
} : newTrack.status === "preparing" ? {
|
|
1664
|
+
track: newTrack,
|
|
1665
|
+
found: !0,
|
|
1666
|
+
status: "preparing"
|
|
1667
|
+
} : {
|
|
1668
|
+
track: newTrack,
|
|
1669
|
+
found: !0,
|
|
1670
|
+
status: "ready"
|
|
1671
|
+
};
|
|
1413
1672
|
}
|
|
1414
|
-
function
|
|
1415
|
-
|
|
1416
|
-
|
|
1673
|
+
async function downloadVttFile(client, asset, track) {
|
|
1674
|
+
if (!track.id)
|
|
1675
|
+
throw new Error("Track ID is missing");
|
|
1676
|
+
if (track.status !== "ready")
|
|
1677
|
+
throw new Error(`Track is not ready yet. Status: ${track.status}`);
|
|
1678
|
+
if (!asset.assetId)
|
|
1679
|
+
throw new Error("Asset ID is required");
|
|
1680
|
+
const playbackId = getPlaybackId(asset);
|
|
1681
|
+
if (!playbackId)
|
|
1682
|
+
throw new Error("Playback ID is required");
|
|
1683
|
+
const playbackPolicy = getPlaybackPolicy(asset)?.policy;
|
|
1684
|
+
let downloadUrl = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
1685
|
+
if (playbackPolicy === "signed" || playbackPolicy === "drm") {
|
|
1686
|
+
const token = generateJwt(client, playbackId, "v");
|
|
1687
|
+
downloadUrl += `?token=${token}`;
|
|
1688
|
+
}
|
|
1689
|
+
const response = await fetch(downloadUrl);
|
|
1690
|
+
if (!response.ok)
|
|
1691
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
1692
|
+
const blob = await response.blob(), blobUrl = URL.createObjectURL(blob), link = document.createElement("a");
|
|
1693
|
+
link.href = blobUrl, link.download = `${asset.filename || "captions"}-${track.language_code || "en"}.vtt`, document.body.appendChild(link), link.click(), document.body.removeChild(link), URL.revokeObjectURL(blobUrl);
|
|
1417
1694
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1695
|
+
const SUPPORTED_MUX_LANGUAGES = [
|
|
1696
|
+
{ label: "English", code: "en", state: "Stable" },
|
|
1697
|
+
{ label: "Spanish", code: "es", state: "Stable" },
|
|
1698
|
+
{ label: "Italian", code: "it", state: "Stable" },
|
|
1699
|
+
{ label: "Portuguese", code: "pt", state: "Stable" },
|
|
1700
|
+
{ label: "German", code: "de", state: "Stable" },
|
|
1701
|
+
{ label: "French", code: "fr", state: "Stable" },
|
|
1702
|
+
{ label: "Polish", code: "pl", state: "Beta" },
|
|
1703
|
+
{ label: "Russian", code: "ru", state: "Beta" },
|
|
1704
|
+
{ label: "Dutch", code: "nl", state: "Beta" },
|
|
1705
|
+
{ label: "Catalan", code: "ca", state: "Beta" },
|
|
1706
|
+
{ label: "Turkish", code: "tr", state: "Beta" },
|
|
1707
|
+
{ label: "Swedish", code: "sv", state: "Beta" },
|
|
1708
|
+
{ label: "Ukrainian", code: "uk", state: "Beta" },
|
|
1709
|
+
{ label: "Norwegian", code: "no", state: "Beta" },
|
|
1710
|
+
{ label: "Finnish", code: "fi", state: "Beta" },
|
|
1711
|
+
{ label: "Slovak", code: "sk", state: "Beta" },
|
|
1712
|
+
{ label: "Greek", code: "el", state: "Beta" },
|
|
1713
|
+
{ label: "Czech", code: "cs", state: "Beta" },
|
|
1714
|
+
{ label: "Croatian", code: "hr", state: "Beta" },
|
|
1715
|
+
{ label: "Danish", code: "da", state: "Beta" },
|
|
1716
|
+
{ label: "Romanian", code: "ro", state: "Beta" },
|
|
1717
|
+
{ label: "Bulgarian", code: "bg", state: "Beta" }
|
|
1718
|
+
];
|
|
1719
|
+
function isCustomTextTrack(track) {
|
|
1720
|
+
return track.type !== "autogenerated";
|
|
1420
1721
|
}
|
|
1421
|
-
function
|
|
1422
|
-
|
|
1423
|
-
return hh * 3600 + mm * 60 + ss;
|
|
1722
|
+
function isAutogeneratedTrack(track) {
|
|
1723
|
+
return track.type === "autogenerated";
|
|
1424
1724
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1725
|
+
const LANGUAGE_OPTIONS$1 = LanguagesList__default.default.getAllCodes().map((code) => ({
|
|
1726
|
+
value: code,
|
|
1727
|
+
label: LanguagesList__default.default.getNativeName(code)
|
|
1728
|
+
})), MUX_LANGUAGE_OPTIONS = SUPPORTED_MUX_LANGUAGES.map((lang) => ({
|
|
1729
|
+
value: lang.code,
|
|
1730
|
+
label: lang.label
|
|
1731
|
+
}));
|
|
1732
|
+
function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
1733
|
+
const client = useClient(), toast = ui.useToast(), dialogId = `AddCaptionDialog${React.useId()}`, [isAutogenerated, setIsAutogenerated] = React.useState(!1), [vttUrl, setVttUrl] = React.useState(""), [languageCode, setLanguageCode] = React.useState(""), [selectedLanguage, setSelectedLanguage] = React.useState(
|
|
1734
|
+
null
|
|
1735
|
+
), [name2, setName] = React.useState(""), [isSubmitting, setIsSubmitting] = React.useState(!1), [selectedFile, setSelectedFile] = React.useState(null), fileInputRef = React.useRef(null), uploadVttFile = async (file) => (await client.assets.upload("file", file, {
|
|
1736
|
+
filename: file.name
|
|
1737
|
+
})).url, handleAddTrackFromUrl = async () => {
|
|
1738
|
+
if (!asset.assetId)
|
|
1739
|
+
throw new Error("Asset ID is required");
|
|
1740
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim();
|
|
1741
|
+
let vttUrlToUse = vttUrl.trim();
|
|
1742
|
+
if (selectedFile)
|
|
1743
|
+
try {
|
|
1744
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
1745
|
+
} catch (uploadError) {
|
|
1746
|
+
throw toast.push({
|
|
1747
|
+
title: "Failed to upload VTT file",
|
|
1748
|
+
status: "error",
|
|
1749
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
1750
|
+
}), setIsSubmitting(!1), uploadError;
|
|
1751
|
+
}
|
|
1752
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
1753
|
+
language_code: trimmedLanguageCode,
|
|
1754
|
+
name: trimmedName,
|
|
1755
|
+
text_type: "subtitles"
|
|
1756
|
+
});
|
|
1757
|
+
const result = await pollTrackStatus({
|
|
1758
|
+
client,
|
|
1759
|
+
assetId: asset.assetId,
|
|
1760
|
+
trackName: trimmedName,
|
|
1761
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
1762
|
+
onTrackErrored: (track) => {
|
|
1763
|
+
const errorMessage = track.error?.messages?.[0] || track.error?.type || "The track failed to download from the provided URL";
|
|
1764
|
+
toast.push({
|
|
1765
|
+
title: "Caption track failed",
|
|
1766
|
+
status: "error",
|
|
1767
|
+
description: errorMessage
|
|
1768
|
+
}), onAdd(track), onClose();
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
if (!result.found || !result.track) {
|
|
1772
|
+
toast.push({
|
|
1773
|
+
title: "Caption track may have been added",
|
|
1774
|
+
status: "warning",
|
|
1775
|
+
description: "The track was created but its status could not be determined. It may still be processing. Please refresh the page to see if it appears."
|
|
1776
|
+
}), onClose();
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
if (result.status !== "errored") {
|
|
1780
|
+
if (result.status === "preparing") {
|
|
1781
|
+
toast.push({
|
|
1782
|
+
title: "Caption track is processing",
|
|
1783
|
+
status: "info",
|
|
1784
|
+
description: "The track was created and is being processed. It will appear in the list shortly."
|
|
1785
|
+
}), onAdd(result.track), onClose();
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
toast.push({
|
|
1789
|
+
title: "Caption track added",
|
|
1790
|
+
status: "success",
|
|
1791
|
+
description: "Caption track added successfully"
|
|
1792
|
+
}), onAdd(result.track), onClose();
|
|
1793
|
+
}
|
|
1794
|
+
}, handleGenerateSubtitles = async () => {
|
|
1795
|
+
if (!asset.assetId)
|
|
1796
|
+
throw new Error("Asset ID is required");
|
|
1797
|
+
const audioTrack = (await getAsset(client, asset.assetId)).data.tracks?.find((track) => track.type === "audio");
|
|
1798
|
+
if (!audioTrack || !audioTrack.id)
|
|
1799
|
+
throw toast.push({
|
|
1800
|
+
title: "No audio track found",
|
|
1801
|
+
status: "error",
|
|
1802
|
+
description: "The asset does not have an audio track. Auto-generated subtitles require an audio track."
|
|
1803
|
+
}), new Error("No audio track found");
|
|
1804
|
+
await generateSubtitles(client, asset.assetId, audioTrack.id, {
|
|
1805
|
+
language_code: languageCode.trim(),
|
|
1806
|
+
name: name2.trim()
|
|
1807
|
+
});
|
|
1808
|
+
const mockTrack = {
|
|
1809
|
+
type: "text",
|
|
1810
|
+
id: `generating-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
1811
|
+
text_type: "subtitles",
|
|
1812
|
+
text_source: "generated_live",
|
|
1813
|
+
language_code: languageCode.trim(),
|
|
1814
|
+
name: name2.trim(),
|
|
1815
|
+
status: "preparing"
|
|
1816
|
+
};
|
|
1817
|
+
toast.push({
|
|
1818
|
+
title: "Generating subtitles",
|
|
1819
|
+
status: "success",
|
|
1820
|
+
description: "This may take a few minutes"
|
|
1821
|
+
}), onAdd(mockTrack), onClose();
|
|
1822
|
+
}, handleSubmit = async () => {
|
|
1823
|
+
if (!isAutogenerated) {
|
|
1824
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
1825
|
+
toast.push({
|
|
1826
|
+
title: "VTT file or URL required",
|
|
1827
|
+
status: "error",
|
|
1828
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
1829
|
+
});
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (vttUrl.trim() && !selectedFile)
|
|
1833
|
+
try {
|
|
1834
|
+
new URL(vttUrl.trim());
|
|
1835
|
+
} catch {
|
|
1836
|
+
toast.push({
|
|
1837
|
+
title: "Invalid URL",
|
|
1838
|
+
status: "error",
|
|
1839
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
1840
|
+
});
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (!name2.trim()) {
|
|
1845
|
+
toast.push({
|
|
1846
|
+
title: "Audio name required",
|
|
1847
|
+
status: "error",
|
|
1848
|
+
description: "Please enter an audio name for this caption track"
|
|
1849
|
+
});
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
if (!languageCode.trim()) {
|
|
1853
|
+
toast.push({
|
|
1854
|
+
title: "Language code required",
|
|
1855
|
+
status: "error",
|
|
1856
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
1857
|
+
});
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
setIsSubmitting(!0);
|
|
1861
|
+
try {
|
|
1862
|
+
isAutogenerated ? await handleGenerateSubtitles() : await handleAddTrackFromUrl();
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
toast.push({
|
|
1865
|
+
title: "Failed to add caption track",
|
|
1866
|
+
status: "error",
|
|
1867
|
+
description: extractErrorMessage(error, "Failed to add caption track")
|
|
1868
|
+
});
|
|
1869
|
+
} finally {
|
|
1870
|
+
setIsSubmitting(!1);
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1433
1873
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1434
1874
|
ui.Dialog,
|
|
1435
1875
|
{
|
|
1436
1876
|
id: dialogId,
|
|
1437
|
-
header: "
|
|
1438
|
-
onClose
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1877
|
+
header: "Add Caption Track",
|
|
1878
|
+
onClose,
|
|
1879
|
+
width: 1,
|
|
1880
|
+
onClickOutside: onClose,
|
|
1881
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { padding: 4, space: 4, children: [
|
|
1882
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
1883
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", marginBottom: 3, children: [
|
|
1884
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1885
|
+
ui.Checkbox,
|
|
1886
|
+
{
|
|
1887
|
+
id: "autogenerated-checkbox",
|
|
1888
|
+
style: { display: "block" },
|
|
1889
|
+
checked: isAutogenerated,
|
|
1890
|
+
onChange: (e) => {
|
|
1891
|
+
setIsAutogenerated(e.currentTarget.checked), e.currentTarget.checked && setVttUrl("");
|
|
1892
|
+
},
|
|
1893
|
+
disabled: isSubmitting
|
|
1894
|
+
}
|
|
1895
|
+
),
|
|
1896
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { flex: 1, paddingLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "autogenerated-checkbox", children: "Generate captions" }) }) })
|
|
1897
|
+
] }),
|
|
1898
|
+
!isAutogenerated && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
1899
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
1900
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", justify: "space-between", children: [
|
|
1901
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: selectedFile ? `Selected: ${selectedFile.name}` : "No file selected" }),
|
|
1902
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1903
|
+
ui.Button,
|
|
1904
|
+
{
|
|
1905
|
+
icon: icons.UploadIcon,
|
|
1906
|
+
text: "Select File",
|
|
1907
|
+
mode: "ghost",
|
|
1908
|
+
tone: "primary",
|
|
1909
|
+
fontSize: 1,
|
|
1910
|
+
padding: 2,
|
|
1911
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1912
|
+
disabled: isSubmitting
|
|
1913
|
+
}
|
|
1914
|
+
)
|
|
1915
|
+
] }),
|
|
1916
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1917
|
+
"input",
|
|
1918
|
+
{
|
|
1919
|
+
ref: fileInputRef,
|
|
1920
|
+
type: "file",
|
|
1921
|
+
accept: ".vtt,text/vtt",
|
|
1922
|
+
style: { display: "none" },
|
|
1923
|
+
onChange: (e) => {
|
|
1924
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
)
|
|
1928
|
+
] }),
|
|
1929
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, style: { textAlign: "center" }, children: "Or enter the VTT file URL" }),
|
|
1930
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
1931
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
1932
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1933
|
+
ui.TextInput,
|
|
1934
|
+
{
|
|
1935
|
+
id: "vtt-url",
|
|
1936
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
1937
|
+
value: vttUrl,
|
|
1938
|
+
onChange: (e) => {
|
|
1939
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
1940
|
+
},
|
|
1941
|
+
disabled: isSubmitting
|
|
1942
|
+
}
|
|
1943
|
+
)
|
|
1944
|
+
] })
|
|
1945
|
+
] })
|
|
1946
|
+
] }),
|
|
1947
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
1948
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "caption-name", children: "Audio name" }),
|
|
1949
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1950
|
+
ui.Autocomplete,
|
|
1951
|
+
{
|
|
1952
|
+
id: "caption-name",
|
|
1953
|
+
value: selectedLanguage?.value || "",
|
|
1954
|
+
onChange: (newValue) => {
|
|
1955
|
+
const selected = (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find((opt) => opt.value === newValue);
|
|
1956
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
1957
|
+
},
|
|
1958
|
+
options: isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1,
|
|
1959
|
+
icon: icons.TranslateIcon,
|
|
1960
|
+
placeholder: "Select language",
|
|
1961
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
1962
|
+
openButton: !0,
|
|
1963
|
+
renderValue: (value) => (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find(
|
|
1964
|
+
(l) => l.value === value
|
|
1965
|
+
)?.label || value,
|
|
1966
|
+
renderOption: (option) => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
1967
|
+
option.label,
|
|
1968
|
+
" (",
|
|
1969
|
+
option.value,
|
|
1970
|
+
")"
|
|
1971
|
+
] }) }),
|
|
1972
|
+
disabled: isSubmitting
|
|
1973
|
+
}
|
|
1974
|
+
)
|
|
1975
|
+
] }),
|
|
1976
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
1977
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "caption-language", children: "Language Code" }),
|
|
1978
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1979
|
+
ui.TextInput,
|
|
1980
|
+
{
|
|
1981
|
+
id: "caption-language",
|
|
1982
|
+
placeholder: "en-US",
|
|
1983
|
+
value: languageCode,
|
|
1984
|
+
onChange: (e) => {
|
|
1985
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
1986
|
+
},
|
|
1987
|
+
disabled: isSubmitting
|
|
1988
|
+
}
|
|
1989
|
+
)
|
|
1990
|
+
] }),
|
|
1991
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
1992
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
1993
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1994
|
+
ui.Button,
|
|
1995
|
+
{
|
|
1996
|
+
text: "Add Caption Track",
|
|
1997
|
+
tone: "primary",
|
|
1998
|
+
icon: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1999
|
+
ui.Spinner,
|
|
2000
|
+
{
|
|
2001
|
+
style: {
|
|
2002
|
+
verticalAlign: "middle",
|
|
2003
|
+
display: "inline-block",
|
|
2004
|
+
marginBottom: "-3px",
|
|
2005
|
+
width: "1em",
|
|
2006
|
+
height: "1em",
|
|
2007
|
+
marginRight: "-6px"
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(icons.UploadIcon, {}),
|
|
2011
|
+
onClick: handleSubmit,
|
|
2012
|
+
disabled: isSubmitting
|
|
2013
|
+
}
|
|
2014
|
+
)
|
|
2015
|
+
] })
|
|
2016
|
+
] })
|
|
2017
|
+
}
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
const LANGUAGE_OPTIONS = LanguagesList__default.default.getAllCodes().map((code) => ({
|
|
2021
|
+
value: code,
|
|
2022
|
+
label: LanguagesList__default.default.getNativeName(code)
|
|
2023
|
+
}));
|
|
2024
|
+
function EditCaptionDialog({ asset, track, onUpdate, onClose }) {
|
|
2025
|
+
const client = useClient(), toast = ui.useToast(), dialogId = `EditCaptionDialog${React.useId()}`, isAutogenerated = track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod", [vttUrl, setVttUrl] = React.useState(""), [languageCode, setLanguageCode] = React.useState(track.language_code || ""), [selectedLanguage, setSelectedLanguage] = React.useState(
|
|
2026
|
+
() => {
|
|
2027
|
+
const baseCode = track.language_code?.split("-")[0], found = LANGUAGE_OPTIONS.find(
|
|
2028
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
2029
|
+
);
|
|
2030
|
+
if (found) return found;
|
|
2031
|
+
if (track.name) {
|
|
2032
|
+
const foundByName = LANGUAGE_OPTIONS.find((opt) => opt.label === track.name);
|
|
2033
|
+
if (foundByName) return foundByName;
|
|
2034
|
+
}
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
), [name2, setName] = React.useState(track.name || ""), [isSubmitting, setIsSubmitting] = React.useState(!1), [downloading, setDownloading] = React.useState(!1), [selectedFile, setSelectedFile] = React.useState(null), fileInputRef = React.useRef(null);
|
|
2038
|
+
React.useEffect(() => {
|
|
2039
|
+
setLanguageCode(track.language_code || ""), setName(track.name || ""), setVttUrl("");
|
|
2040
|
+
const baseCode = track.language_code?.split("-")[0], foundByCode = LANGUAGE_OPTIONS.find(
|
|
2041
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
2042
|
+
), foundByName = track.name ? LANGUAGE_OPTIONS.find((opt) => opt.label === track.name) : null;
|
|
2043
|
+
setSelectedLanguage(foundByCode || foundByName || null);
|
|
2044
|
+
}, [track, asset, client]);
|
|
2045
|
+
const handleDownloadCurrentFile = async () => {
|
|
2046
|
+
setDownloading(!0);
|
|
2047
|
+
try {
|
|
2048
|
+
await downloadVttFile(client, asset, track);
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
let errorMessage = "Please try again", title = "Failed to download VTT file";
|
|
2051
|
+
error instanceof Error ? (errorMessage = error.message, error.message.includes("Track") && (title = "Cannot download")) : (error === "Track ID is missing" || error === "Track is not ready yet") && (errorMessage = String(error), title = "Cannot download"), toast.push({
|
|
2052
|
+
title,
|
|
2053
|
+
status: "error",
|
|
2054
|
+
description: errorMessage
|
|
2055
|
+
});
|
|
2056
|
+
} finally {
|
|
2057
|
+
setDownloading(!1);
|
|
2058
|
+
}
|
|
2059
|
+
}, getCurrentFileName = () => track.id && asset.filename ? `${asset.filename}-${track.language_code || "en"}.vtt` : `captions-${track.language_code || "en"}.vtt`, uploadVttFile = async (file) => (await client.assets.upload("file", file, {
|
|
2060
|
+
filename: file.name
|
|
2061
|
+
})).url, refreshAssetData = async () => {
|
|
2062
|
+
if (!(!asset._id || !asset.assetId))
|
|
2063
|
+
try {
|
|
2064
|
+
const latestAssetData = await getAsset(client, asset.assetId);
|
|
2065
|
+
await client.patch(asset._id).set({ data: latestAssetData.data, status: latestAssetData.data.status }).commit();
|
|
2066
|
+
} catch (refreshError) {
|
|
2067
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2068
|
+
}
|
|
2069
|
+
}, handleUpdateTrackWithNewUrl = async () => {
|
|
2070
|
+
if (!asset.assetId)
|
|
2071
|
+
throw new Error("Asset ID is required");
|
|
2072
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim(), oldTrackId = track.id;
|
|
2073
|
+
try {
|
|
2074
|
+
await deleteTextTrack(client, asset.assetId, oldTrackId);
|
|
2075
|
+
} catch (deleteError) {
|
|
2076
|
+
throw toast.push({
|
|
2077
|
+
title: "Failed to delete old track",
|
|
2078
|
+
status: "error",
|
|
2079
|
+
description: "Could not delete the old track. Please try again or delete it manually."
|
|
2080
|
+
}), setIsSubmitting(!1), deleteError;
|
|
2081
|
+
}
|
|
2082
|
+
let vttUrlToUse = vttUrl.trim();
|
|
2083
|
+
if (selectedFile)
|
|
2084
|
+
try {
|
|
2085
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
2086
|
+
} catch (uploadError) {
|
|
2087
|
+
throw toast.push({
|
|
2088
|
+
title: "Failed to upload VTT file",
|
|
2089
|
+
status: "error",
|
|
2090
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
2091
|
+
}), setIsSubmitting(!1), uploadError;
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
2095
|
+
language_code: trimmedLanguageCode,
|
|
2096
|
+
name: trimmedName,
|
|
2097
|
+
text_type: "subtitles"
|
|
2098
|
+
});
|
|
2099
|
+
} catch (error) {
|
|
2100
|
+
throw toast.push({
|
|
2101
|
+
title: "Failed to update caption track",
|
|
2102
|
+
status: "error",
|
|
2103
|
+
description: extractErrorMessage(error, "Failed to update caption track")
|
|
2104
|
+
}), setIsSubmitting(!1), error;
|
|
2105
|
+
}
|
|
2106
|
+
const result = await pollTrackStatus({
|
|
2107
|
+
client,
|
|
2108
|
+
assetId: asset.assetId,
|
|
2109
|
+
trackName: trimmedName,
|
|
2110
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
2111
|
+
onTrackErrored: async (erroredTrack) => {
|
|
2112
|
+
const errorMessage = erroredTrack.error?.messages?.[0] || erroredTrack.error?.type || "The track failed to download from the provided URL";
|
|
2113
|
+
toast.push({
|
|
2114
|
+
title: "Caption track failed",
|
|
2115
|
+
status: "error",
|
|
2116
|
+
description: errorMessage
|
|
2117
|
+
}), await refreshAssetData(), onUpdate(erroredTrack, oldTrackId), setIsSubmitting(!1);
|
|
2118
|
+
}
|
|
2119
|
+
});
|
|
2120
|
+
if (!result.found || !result.track) {
|
|
2121
|
+
toast.push({
|
|
2122
|
+
title: "Caption track may have been updated",
|
|
2123
|
+
status: "warning",
|
|
2124
|
+
description: "The track was updated but its status could not be determined. It may still be processing. Please refresh the page to see if it appears."
|
|
2125
|
+
}), setIsSubmitting(!1);
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
result.status !== "errored" && (await refreshAssetData(), result.status === "preparing" ? toast.push({
|
|
2129
|
+
title: "Caption track is processing",
|
|
2130
|
+
status: "info",
|
|
2131
|
+
description: "The track was updated and is being processed. It will appear in the list shortly."
|
|
2132
|
+
}) : toast.push({
|
|
2133
|
+
title: "Caption track updated",
|
|
2134
|
+
status: "success",
|
|
2135
|
+
description: "Caption track updated successfully"
|
|
2136
|
+
}), onUpdate(result.track, oldTrackId), setIsSubmitting(!1));
|
|
2137
|
+
}, handleSubmit = async () => {
|
|
2138
|
+
if (!name2.trim()) {
|
|
2139
|
+
toast.push({
|
|
2140
|
+
title: "Audio name required",
|
|
2141
|
+
status: "error",
|
|
2142
|
+
description: "Please enter an audio name for this caption track"
|
|
2143
|
+
});
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
if (!languageCode.trim()) {
|
|
2147
|
+
toast.push({
|
|
2148
|
+
title: "Language code required",
|
|
2149
|
+
status: "error",
|
|
2150
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
2151
|
+
});
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
setIsSubmitting(!0);
|
|
2155
|
+
try {
|
|
2156
|
+
if (!asset.assetId)
|
|
2157
|
+
throw new Error("Asset ID is required");
|
|
2158
|
+
const originalVttUrl = (() => {
|
|
2159
|
+
if (isAutogenerated || !track.id) return "";
|
|
2160
|
+
const playbackId = getPlaybackId(asset);
|
|
2161
|
+
if (!playbackId) return "";
|
|
2162
|
+
let url = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
2163
|
+
if (getPlaybackPolicy(asset)?.policy === "signed") {
|
|
2164
|
+
const token = generateJwt(client, playbackId, "v");
|
|
2165
|
+
url += `?token=${token}`;
|
|
2166
|
+
}
|
|
2167
|
+
return url;
|
|
2168
|
+
})(), urlChanged = selectedFile !== null || vttUrl.trim() && vttUrl.trim() !== originalVttUrl;
|
|
2169
|
+
if (!urlChanged) {
|
|
2170
|
+
toast.push({
|
|
2171
|
+
title: "No changes",
|
|
2172
|
+
status: "info",
|
|
2173
|
+
description: 'Please provide a new VTT file or URL using the "Replace" button or URL field to update the track.'
|
|
2174
|
+
}), setIsSubmitting(!1);
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
if (urlChanged) {
|
|
2178
|
+
if (!selectedFile && vttUrl.trim())
|
|
2179
|
+
try {
|
|
2180
|
+
new URL(vttUrl.trim());
|
|
2181
|
+
} catch {
|
|
2182
|
+
toast.push({
|
|
2183
|
+
title: "Invalid URL",
|
|
2184
|
+
status: "error",
|
|
2185
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
2186
|
+
}), setIsSubmitting(!1);
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
2190
|
+
toast.push({
|
|
2191
|
+
title: "VTT file or URL required",
|
|
2192
|
+
status: "error",
|
|
2193
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
2194
|
+
}), setIsSubmitting(!1);
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
await handleUpdateTrackWithNewUrl();
|
|
2198
|
+
}
|
|
2199
|
+
onClose();
|
|
2200
|
+
} catch (error) {
|
|
2201
|
+
toast.push({
|
|
2202
|
+
title: "Failed to update caption track",
|
|
2203
|
+
status: "error",
|
|
2204
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2205
|
+
});
|
|
2206
|
+
} finally {
|
|
2207
|
+
setIsSubmitting(!1);
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2211
|
+
ui.Dialog,
|
|
2212
|
+
{
|
|
2213
|
+
id: dialogId,
|
|
2214
|
+
header: "Edit Caption Track",
|
|
2215
|
+
onClose,
|
|
2216
|
+
width: 1,
|
|
2217
|
+
onClickOutside: onClose,
|
|
2218
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { padding: 4, space: 4, children: [
|
|
2219
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
2220
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
2221
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", justify: "space-between", children: [
|
|
2222
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: getCurrentFileName() }),
|
|
2223
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 2, children: [
|
|
2224
|
+
track.status !== "errored" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2225
|
+
ui.Button,
|
|
2226
|
+
{
|
|
2227
|
+
icon: downloading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2228
|
+
ui.Spinner,
|
|
2229
|
+
{
|
|
2230
|
+
style: {
|
|
2231
|
+
verticalAlign: "middle",
|
|
2232
|
+
display: "inline-block",
|
|
2233
|
+
marginTop: "-2px",
|
|
2234
|
+
width: "0.5em",
|
|
2235
|
+
height: "0.5em"
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(icons.DownloadIcon, {}),
|
|
2239
|
+
text: "Download",
|
|
2240
|
+
mode: "ghost",
|
|
2241
|
+
tone: "primary",
|
|
2242
|
+
fontSize: 1,
|
|
2243
|
+
padding: 2,
|
|
2244
|
+
onClick: handleDownloadCurrentFile,
|
|
2245
|
+
disabled: downloading || isSubmitting
|
|
2246
|
+
}
|
|
2247
|
+
),
|
|
2248
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2249
|
+
ui.Button,
|
|
2250
|
+
{
|
|
2251
|
+
icon: icons.UploadIcon,
|
|
2252
|
+
text: "Replace",
|
|
2253
|
+
mode: "ghost",
|
|
2254
|
+
tone: "primary",
|
|
2255
|
+
fontSize: 1,
|
|
2256
|
+
padding: 2,
|
|
2257
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2258
|
+
disabled: isSubmitting
|
|
2259
|
+
}
|
|
2260
|
+
)
|
|
2261
|
+
] })
|
|
2262
|
+
] }),
|
|
2263
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2264
|
+
"input",
|
|
2265
|
+
{
|
|
2266
|
+
ref: fileInputRef,
|
|
2267
|
+
type: "file",
|
|
2268
|
+
accept: ".vtt,text/vtt",
|
|
2269
|
+
style: { display: "none" },
|
|
2270
|
+
onChange: (e) => {
|
|
2271
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
),
|
|
2275
|
+
selectedFile && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 1, muted: !0, style: { marginTop: 8 }, children: [
|
|
2276
|
+
"Selected: ",
|
|
2277
|
+
selectedFile.name
|
|
2278
|
+
] })
|
|
2279
|
+
] }),
|
|
2280
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
2281
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
2282
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2283
|
+
ui.TextInput,
|
|
2284
|
+
{
|
|
2285
|
+
id: "vtt-url",
|
|
2286
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
2287
|
+
value: vttUrl,
|
|
2288
|
+
onChange: (e) => {
|
|
2289
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
2290
|
+
},
|
|
2291
|
+
disabled: isSubmitting
|
|
2292
|
+
}
|
|
2293
|
+
),
|
|
2294
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "Add a URL to replace the existing VTT file with a new one" })
|
|
2295
|
+
] })
|
|
2296
|
+
] }),
|
|
2297
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
2298
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "caption-name", children: "Audio name" }),
|
|
2299
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2300
|
+
ui.Autocomplete,
|
|
2301
|
+
{
|
|
2302
|
+
id: "caption-name",
|
|
2303
|
+
value: selectedLanguage?.value || "",
|
|
2304
|
+
onChange: (newValue) => {
|
|
2305
|
+
const selected = LANGUAGE_OPTIONS.find((opt) => opt.value === newValue);
|
|
2306
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
2307
|
+
},
|
|
2308
|
+
options: LANGUAGE_OPTIONS,
|
|
2309
|
+
icon: icons.TranslateIcon,
|
|
2310
|
+
placeholder: "Select language",
|
|
2311
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
2312
|
+
openButton: !0,
|
|
2313
|
+
renderValue: (value) => LANGUAGE_OPTIONS.find((l) => l.value === value)?.label || value,
|
|
2314
|
+
renderOption: (option) => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
2315
|
+
option.label,
|
|
2316
|
+
" (",
|
|
2317
|
+
option.value,
|
|
2318
|
+
")"
|
|
2319
|
+
] }) }),
|
|
2320
|
+
disabled: isSubmitting
|
|
2321
|
+
}
|
|
2322
|
+
)
|
|
2323
|
+
] }),
|
|
2324
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
2325
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "caption-language", children: "Language Code" }),
|
|
2326
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2327
|
+
ui.TextInput,
|
|
2328
|
+
{
|
|
2329
|
+
id: "caption-language",
|
|
2330
|
+
placeholder: "en-US",
|
|
2331
|
+
value: languageCode,
|
|
2332
|
+
onChange: (e) => {
|
|
2333
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
2334
|
+
},
|
|
2335
|
+
disabled: isSubmitting
|
|
2336
|
+
}
|
|
2337
|
+
)
|
|
2338
|
+
] }),
|
|
2339
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
2340
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
2341
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2342
|
+
ui.Button,
|
|
2343
|
+
{
|
|
2344
|
+
text: "Update Caption Track",
|
|
2345
|
+
tone: "primary",
|
|
2346
|
+
icon: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2347
|
+
ui.Spinner,
|
|
2348
|
+
{
|
|
2349
|
+
style: {
|
|
2350
|
+
verticalAlign: "middle",
|
|
2351
|
+
display: "inline-block",
|
|
2352
|
+
marginBottom: "-3px",
|
|
2353
|
+
width: "1em",
|
|
2354
|
+
height: "1em",
|
|
2355
|
+
marginRight: "-6px"
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
) : icons.UploadIcon,
|
|
2359
|
+
onClick: handleSubmit,
|
|
2360
|
+
disabled: isSubmitting
|
|
2361
|
+
}
|
|
2362
|
+
)
|
|
2363
|
+
] })
|
|
2364
|
+
] })
|
|
2365
|
+
}
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
function TrackCard({
|
|
2369
|
+
track,
|
|
2370
|
+
iconOnly,
|
|
2371
|
+
downloadingTrackId,
|
|
2372
|
+
deletingTrackId,
|
|
2373
|
+
trackToEdit,
|
|
2374
|
+
getTrackSourceLabel,
|
|
2375
|
+
handleDownload,
|
|
2376
|
+
setTrackToEdit,
|
|
2377
|
+
setTrackToDelete
|
|
2378
|
+
}) {
|
|
2379
|
+
const isDisabled = (action) => action === "download" ? downloadingTrackId !== null || deletingTrackId === track.id || trackToEdit?.id === track.id : action === "edit" ? downloadingTrackId === track.id || deletingTrackId === track.id || trackToEdit?.id === track.id : downloadingTrackId === track.id || deletingTrackId !== null || trackToEdit?.id === track.id, renderActionButtons = () => track.status === "preparing" ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
2380
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2381
|
+
ui.Spinner,
|
|
2382
|
+
{
|
|
2383
|
+
muted: !0,
|
|
2384
|
+
style: {
|
|
2385
|
+
width: "0.75em",
|
|
2386
|
+
height: "0.75em",
|
|
2387
|
+
verticalAlign: "middle",
|
|
2388
|
+
display: "inline-block",
|
|
2389
|
+
marginBottom: "-2px"
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
),
|
|
2393
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "Processing..." })
|
|
2394
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 2, children: [
|
|
2395
|
+
track.status !== "errored" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2396
|
+
ui.Button,
|
|
2397
|
+
{
|
|
2398
|
+
icon: downloadingTrackId === track.id ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2399
|
+
ui.Spinner,
|
|
2400
|
+
{
|
|
2401
|
+
style: {
|
|
2402
|
+
verticalAlign: "middle",
|
|
2403
|
+
display: "inline-block",
|
|
2404
|
+
marginTop: "-2px",
|
|
2405
|
+
width: "0.5em",
|
|
2406
|
+
height: "0.5em"
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(icons.DownloadIcon, {}),
|
|
2410
|
+
text: iconOnly ? void 0 : "Download",
|
|
2411
|
+
mode: "ghost",
|
|
2412
|
+
tone: "primary",
|
|
2413
|
+
fontSize: 1,
|
|
2414
|
+
padding: 2,
|
|
2415
|
+
onClick: () => handleDownload(track),
|
|
2416
|
+
disabled: isDisabled("download"),
|
|
2417
|
+
title: "Download"
|
|
2418
|
+
}
|
|
2419
|
+
),
|
|
2420
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2421
|
+
ui.Button,
|
|
2422
|
+
{
|
|
2423
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.EditIcon, {}),
|
|
2424
|
+
text: iconOnly ? void 0 : "Edit",
|
|
2425
|
+
mode: "ghost",
|
|
2426
|
+
tone: "primary",
|
|
2427
|
+
fontSize: 1,
|
|
2428
|
+
padding: 2,
|
|
2429
|
+
disabled: isDisabled("edit"),
|
|
2430
|
+
onClick: () => setTrackToEdit(track),
|
|
2431
|
+
title: "Edit"
|
|
2432
|
+
}
|
|
2433
|
+
),
|
|
2434
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2435
|
+
ui.Button,
|
|
2436
|
+
{
|
|
2437
|
+
icon: deletingTrackId === track.id ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2438
|
+
ui.Spinner,
|
|
2439
|
+
{
|
|
2440
|
+
style: {
|
|
2441
|
+
verticalAlign: "middle",
|
|
2442
|
+
display: "inline-block",
|
|
2443
|
+
marginTop: "-2px",
|
|
2444
|
+
width: "0.5em",
|
|
2445
|
+
height: "0.5em"
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(icons.TrashIcon, {}),
|
|
2449
|
+
text: iconOnly ? void 0 : "Delete",
|
|
2450
|
+
mode: "ghost",
|
|
2451
|
+
tone: "critical",
|
|
2452
|
+
fontSize: 1,
|
|
2453
|
+
padding: 2,
|
|
2454
|
+
disabled: isDisabled("delete"),
|
|
2455
|
+
onClick: () => setTrackToDelete(track),
|
|
2456
|
+
title: "Delete"
|
|
2457
|
+
}
|
|
2458
|
+
)
|
|
2459
|
+
] });
|
|
2460
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2461
|
+
ui.Card,
|
|
2462
|
+
{
|
|
2463
|
+
padding: 3,
|
|
2464
|
+
radius: 2,
|
|
2465
|
+
tone: track.status === "errored" ? "caution" : "transparent",
|
|
2466
|
+
border: !0,
|
|
2467
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
2468
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, flex: 1, children: [
|
|
2469
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
2470
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { weight: "semibold", children: track.name || "Untitled" }),
|
|
2471
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 1, muted: !0, children: [
|
|
2472
|
+
"(",
|
|
2473
|
+
getTrackSourceLabel(track),
|
|
2474
|
+
")"
|
|
2475
|
+
] }),
|
|
2476
|
+
track.status === "errored" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2477
|
+
icons.ErrorOutlineIcon,
|
|
2478
|
+
{
|
|
2479
|
+
style: { color: "var(--card-critical-color)" },
|
|
2480
|
+
"aria-label": "Error",
|
|
2481
|
+
fontSize: 20
|
|
2482
|
+
}
|
|
2483
|
+
)
|
|
2484
|
+
] }),
|
|
2485
|
+
track.language_code && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 1, muted: !0, children: [
|
|
2486
|
+
"Language: ",
|
|
2487
|
+
track.language_code
|
|
2488
|
+
] }),
|
|
2489
|
+
track.status === "errored" && track.error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, style: { color: "var(--card-critical-color)" }, children: track.error.messages?.[0] || track.error.type || "Failed to process track" })
|
|
2490
|
+
] }),
|
|
2491
|
+
renderActionButtons()
|
|
2492
|
+
] })
|
|
2493
|
+
}
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
function TextTracksManager({
|
|
2497
|
+
asset,
|
|
2498
|
+
iconOnly = !1,
|
|
2499
|
+
tracks: propTracks,
|
|
2500
|
+
collapseTracks = !1
|
|
2501
|
+
}) {
|
|
2502
|
+
const client = useClient(), toast = ui.useToast(), dialogId = `DeleteCaptionDialog${React.useId()}`, [downloadingTrackId, setDownloadingTrackId] = React.useState(null), [deletingTrackId, setDeletingTrackId] = React.useState(null), [addedTracks, setAddedTracks] = React.useState([]), [updatedTracks, setUpdatedTracks] = React.useState(/* @__PURE__ */ new Map()), [trackActivityOrder, setTrackActivityOrder] = React.useState(/* @__PURE__ */ new Map()), [autogeneratedTrackIds, setAutogeneratedTrackIds] = React.useState(/* @__PURE__ */ new Set()), [trackToDelete, setTrackToDelete] = React.useState(null), [trackToEdit, setTrackToEdit] = React.useState(null), [showAddDialog, setShowAddDialog] = React.useState(!1), [isExpanded, setIsExpanded] = React.useState(!1), MAX_VISIBLE_TRACKS = 4;
|
|
2503
|
+
React.useEffect(() => {
|
|
2504
|
+
if (!asset.assetId || !asset._id) return;
|
|
2505
|
+
const assetId = asset.assetId, documentId = asset._id;
|
|
2506
|
+
(async () => {
|
|
2507
|
+
try {
|
|
2508
|
+
const response = await getAsset(client, assetId);
|
|
2509
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
console.error("Failed to refresh asset data:", error);
|
|
2512
|
+
}
|
|
2513
|
+
})();
|
|
2514
|
+
}, [asset.assetId, asset._id, client]);
|
|
2515
|
+
const activeTracks = (propTracks || asset.data?.tracks?.filter((track) => track.type === "text") || []).filter(
|
|
2516
|
+
(track) => track.id && (track.status === "ready" || track.status === "preparing" || track.status === "errored")
|
|
2517
|
+
), allTracks = React.useMemo(() => {
|
|
2518
|
+
const tracksWithUpdates = activeTracks.map((track) => updatedTracks.get(track.id) || track), isMockTrackReplaced = (mockTrack, realTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : realTracksList.some((realTrack) => {
|
|
2519
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2520
|
+
return !nameMatches || !languageMatches ? !1 : realTrack.status === "ready" ? realTrack.text_source === "generated_live" || realTrack.text_source === "generated_live_final" || realTrack.text_source === "generated_vod" : realTrack.status === "preparing";
|
|
2521
|
+
}), isTrackAlreadyInRealTracks = (addedTrack, realTracksList) => addedTrack.id ? addedTrack.id.startsWith("generating-") ? isMockTrackReplaced(addedTrack, realTracksList) : realTracksList.some((realTrack) => realTrack.id === addedTrack.id) : !1, tracksToKeep = addedTracks.filter((addedTrack) => addedTrack.id && addedTrack.id.startsWith("generating-") ? !isMockTrackReplaced(addedTrack, tracksWithUpdates) : !isTrackAlreadyInRealTracks(addedTrack, tracksWithUpdates));
|
|
2522
|
+
return [...tracksWithUpdates, ...tracksToKeep];
|
|
2523
|
+
}, [activeTracks, addedTracks, updatedTracks]);
|
|
2524
|
+
React.useEffect(() => {
|
|
2525
|
+
const newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2526
|
+
activeTracks.forEach((track) => {
|
|
2527
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2528
|
+
}), addedTracks.forEach((mockTrack) => {
|
|
2529
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2530
|
+
const realTrack = activeTracks.find((rt) => {
|
|
2531
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2532
|
+
return nameMatches && languageMatches;
|
|
2533
|
+
});
|
|
2534
|
+
realTrack?.id && newAutogeneratedIds.add(realTrack.id);
|
|
2535
|
+
}
|
|
2536
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2537
|
+
let hasNew = !1;
|
|
2538
|
+
const updated = new Set(prev);
|
|
2539
|
+
return newAutogeneratedIds.forEach((id) => {
|
|
2540
|
+
prev.has(id) || (updated.add(id), hasNew = !0);
|
|
2541
|
+
}), hasNew ? updated : prev;
|
|
2542
|
+
});
|
|
2543
|
+
}, [activeTracks, addedTracks]), React.useEffect(() => {
|
|
2544
|
+
if (allTracks.filter((track) => track.status === "preparing").length === 0 || !asset.assetId || !asset._id)
|
|
2545
|
+
return;
|
|
2546
|
+
const assetId = asset.assetId, documentId = asset._id, interval = setInterval(async () => {
|
|
2547
|
+
try {
|
|
2548
|
+
const response = await getAsset(client, assetId);
|
|
2549
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2550
|
+
const fetchedTracks = response.data.tracks?.filter((track) => track.type === "text") || [], isMockTrackReplaced = (mockTrack, fetchedTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : fetchedTracksList.some((realTrack) => {
|
|
2551
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2552
|
+
return !nameMatches || !languageMatches ? !1 : realTrack.status === "ready" ? realTrack.text_source === "generated_live" || realTrack.text_source === "generated_live_final" || realTrack.text_source === "generated_vod" : realTrack.status === "preparing";
|
|
2553
|
+
}), newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2554
|
+
fetchedTracks.forEach((track) => {
|
|
2555
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2556
|
+
});
|
|
2557
|
+
const findMatchingRealTrack = (mockTrack, tracksList) => tracksList.find((rt) => {
|
|
2558
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2559
|
+
return nameMatches && languageMatches;
|
|
2560
|
+
});
|
|
2561
|
+
setAddedTracks((prev) => prev.filter((mockTrack) => {
|
|
2562
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2563
|
+
const replaced = isMockTrackReplaced(mockTrack, fetchedTracks);
|
|
2564
|
+
if (replaced) {
|
|
2565
|
+
const realTrack = findMatchingRealTrack(mockTrack, fetchedTracks);
|
|
2566
|
+
realTrack?.id && (newAutogeneratedIds.add(realTrack.id), setTrackActivityOrder((prevOrder) => {
|
|
2567
|
+
const mockOrder = prevOrder.get(mockTrack.id);
|
|
2568
|
+
if (mockOrder) {
|
|
2569
|
+
const newMap = new Map(prevOrder);
|
|
2570
|
+
return newMap.set(realTrack.id, mockOrder), newMap;
|
|
2571
|
+
}
|
|
2572
|
+
return prevOrder;
|
|
2573
|
+
}));
|
|
2574
|
+
}
|
|
2575
|
+
return !replaced;
|
|
2576
|
+
}
|
|
2577
|
+
return !0;
|
|
2578
|
+
})), newAutogeneratedIds.size > 0 && setAutogeneratedTrackIds((prevIds) => {
|
|
2579
|
+
const updated = new Set(prevIds);
|
|
2580
|
+
return newAutogeneratedIds.forEach((id) => updated.add(id)), updated;
|
|
2581
|
+
});
|
|
2582
|
+
} catch (error) {
|
|
2583
|
+
console.error("Failed to refresh asset data:", error);
|
|
2584
|
+
}
|
|
2585
|
+
}, 3e3);
|
|
2586
|
+
return () => clearInterval(interval);
|
|
2587
|
+
}, [allTracks, asset.assetId, asset._id, client]);
|
|
2588
|
+
const visibleTracks = allTracks.filter(
|
|
2589
|
+
(track) => track.status === "ready" || track.status === "preparing" || track.status === "errored"
|
|
2590
|
+
).sort((a2, b) => {
|
|
2591
|
+
const orderA = trackActivityOrder.get(a2.id) || 0, orderB = trackActivityOrder.get(b.id) || 0;
|
|
2592
|
+
if (orderA > 0 && orderB > 0)
|
|
2593
|
+
return orderB - orderA;
|
|
2594
|
+
if (orderA > 0) return -1;
|
|
2595
|
+
if (orderB > 0) return 1;
|
|
2596
|
+
const aIsPreparing = a2.status === "preparing", bIsPreparing = b.status === "preparing";
|
|
2597
|
+
if (aIsPreparing && !bIsPreparing) return -1;
|
|
2598
|
+
if (!aIsPreparing && bIsPreparing) return 1;
|
|
2599
|
+
const aIsAutogenerated = a2.id && a2.id.startsWith("generating-") || a2.id && autogeneratedTrackIds.has(a2.id), bIsAutogenerated = b.id && b.id.startsWith("generating-") || b.id && autogeneratedTrackIds.has(b.id);
|
|
2600
|
+
return aIsAutogenerated && !bIsAutogenerated ? -1 : !aIsAutogenerated && bIsAutogenerated ? 1 : 0;
|
|
2601
|
+
}), handleDownload = async (track) => {
|
|
2602
|
+
if (track.id) {
|
|
2603
|
+
setDownloadingTrackId(track.id);
|
|
2604
|
+
try {
|
|
2605
|
+
await downloadVttFile(client, asset, track);
|
|
2606
|
+
} catch (error) {
|
|
2607
|
+
toast.push({
|
|
2608
|
+
title: "Failed to download VTT file",
|
|
2609
|
+
status: "error",
|
|
2610
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2611
|
+
});
|
|
2612
|
+
} finally {
|
|
2613
|
+
setDownloadingTrackId(null);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
}, confirmDelete = async () => {
|
|
2617
|
+
if (!trackToDelete || !trackToDelete.id) return;
|
|
2618
|
+
const track = trackToDelete;
|
|
2619
|
+
setTrackToDelete(null), setDeletingTrackId(track.id);
|
|
2620
|
+
try {
|
|
2621
|
+
if (!asset.assetId)
|
|
2622
|
+
throw new Error("Asset ID is required");
|
|
2623
|
+
if (await deleteTextTrack(client, asset.assetId, track.id), asset._id)
|
|
2624
|
+
try {
|
|
2625
|
+
const response = await getAsset(client, asset.assetId);
|
|
2626
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2627
|
+
} catch (refreshError) {
|
|
2628
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2629
|
+
}
|
|
2630
|
+
toast.push({
|
|
2631
|
+
title: "Successfully deleted caption track",
|
|
2632
|
+
status: "success"
|
|
2633
|
+
}), setAddedTracks((prev) => prev.filter((t) => t.id !== track.id)), setUpdatedTracks((prev) => {
|
|
2634
|
+
const newMap = new Map(prev);
|
|
2635
|
+
return newMap.delete(track.id), newMap;
|
|
2636
|
+
}), setTrackActivityOrder((prev) => {
|
|
2637
|
+
const newMap = new Map(prev);
|
|
2638
|
+
return newMap.delete(track.id), newMap;
|
|
2639
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2640
|
+
const updated = new Set(prev);
|
|
2641
|
+
return updated.delete(track.id), updated;
|
|
2642
|
+
});
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
toast.push({
|
|
2645
|
+
title: "Failed to delete caption track",
|
|
2646
|
+
status: "error",
|
|
2647
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2648
|
+
});
|
|
2649
|
+
} finally {
|
|
2650
|
+
setDeletingTrackId(null);
|
|
2651
|
+
}
|
|
2652
|
+
}, handleAddTrack = (track) => {
|
|
2653
|
+
setAddedTracks((prev) => [...prev, track]), setTrackActivityOrder((prev) => {
|
|
2654
|
+
const newMap = new Map(prev);
|
|
2655
|
+
return newMap.set(track.id, prev.size + 1), newMap;
|
|
2656
|
+
}), setShowAddDialog(!1);
|
|
2657
|
+
}, handleUpdateTrack = async (updatedTrack, oldTrackId) => {
|
|
2658
|
+
if (oldTrackId && (setAddedTracks((prev) => prev.filter((t) => t.id !== oldTrackId)), setUpdatedTracks((prev) => {
|
|
2659
|
+
const newMap = new Map(prev);
|
|
2660
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2661
|
+
}), setTrackActivityOrder((prev) => {
|
|
2662
|
+
const newMap = new Map(prev);
|
|
2663
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2664
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2665
|
+
const updated = new Set(prev);
|
|
2666
|
+
return updated.delete(oldTrackId), updated;
|
|
2667
|
+
})), addedTracks.some((t) => t.id === updatedTrack.id) ? setAddedTracks((prev) => prev.map((t) => t.id === updatedTrack.id ? updatedTrack : t)) : setUpdatedTracks((prev) => {
|
|
2668
|
+
const newMap = new Map(prev);
|
|
2669
|
+
return newMap.set(updatedTrack.id, updatedTrack), newMap;
|
|
2670
|
+
}), setTrackActivityOrder((prev) => {
|
|
2671
|
+
const newMap = new Map(prev);
|
|
2672
|
+
return newMap.set(updatedTrack.id, prev.size + 1), newMap;
|
|
2673
|
+
}), setTrackToEdit(null), asset._id && asset.assetId)
|
|
2674
|
+
try {
|
|
2675
|
+
const response = await getAsset(client, asset.assetId);
|
|
2676
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2677
|
+
} catch (refreshError) {
|
|
2678
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2679
|
+
}
|
|
2680
|
+
}, getTrackSourceLabel = (track) => track.id && track.id.startsWith("generating-") || track.id && autogeneratedTrackIds.has(track.id) || track.text_source === "generated_live_final" || track.text_source === "generated_live" || track.text_source === "generated_vod" ? "Auto-generated" : track.text_source === "uploaded" ? "Uploaded" : "Custom";
|
|
2681
|
+
if (visibleTracks.length === 0 && !showAddDialog)
|
|
2682
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
2683
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { justify: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2684
|
+
ui.Button,
|
|
2685
|
+
{
|
|
2686
|
+
icon: icons.AddIcon,
|
|
2687
|
+
text: "Add Caption",
|
|
2688
|
+
tone: "primary",
|
|
2689
|
+
onClick: () => setShowAddDialog(!0)
|
|
2690
|
+
}
|
|
2691
|
+
) }),
|
|
2692
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 4, radius: 2, tone: "transparent", border: !0, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "No captions available. Add captions when uploading a video or add them manually." }) }),
|
|
2693
|
+
showAddDialog && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2694
|
+
AddCaptionDialog,
|
|
2695
|
+
{
|
|
2696
|
+
asset,
|
|
2697
|
+
onAdd: handleAddTrack,
|
|
2698
|
+
onClose: () => setShowAddDialog(!1)
|
|
2699
|
+
}
|
|
2700
|
+
)
|
|
2701
|
+
] });
|
|
2702
|
+
const displayedTracks = collapseTracks && !isExpanded ? visibleTracks.slice(0, MAX_VISIBLE_TRACKS) : visibleTracks, hasMoreTracks = collapseTracks && visibleTracks.length > MAX_VISIBLE_TRACKS;
|
|
2703
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
2704
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { justify: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2705
|
+
ui.Button,
|
|
2706
|
+
{
|
|
2707
|
+
icon: icons.AddIcon,
|
|
2708
|
+
text: "Add Caption",
|
|
2709
|
+
tone: "primary",
|
|
2710
|
+
onClick: () => setShowAddDialog(!0)
|
|
2711
|
+
}
|
|
2712
|
+
) }),
|
|
2713
|
+
displayedTracks.map((track) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2714
|
+
TrackCard,
|
|
2715
|
+
{
|
|
2716
|
+
track,
|
|
2717
|
+
iconOnly,
|
|
2718
|
+
downloadingTrackId,
|
|
2719
|
+
deletingTrackId,
|
|
2720
|
+
trackToEdit,
|
|
2721
|
+
getTrackSourceLabel,
|
|
2722
|
+
handleDownload,
|
|
2723
|
+
setTrackToEdit,
|
|
2724
|
+
setTrackToDelete
|
|
2725
|
+
},
|
|
2726
|
+
track.id
|
|
2727
|
+
)),
|
|
2728
|
+
hasMoreTracks && /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { justify: "center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2729
|
+
ui.Button,
|
|
2730
|
+
{
|
|
2731
|
+
icon: isExpanded ? icons.ChevronUpIcon : icons.ChevronDownIcon,
|
|
2732
|
+
text: isExpanded ? "Show less" : `Show ${visibleTracks.length - MAX_VISIBLE_TRACKS} more`,
|
|
2733
|
+
mode: "ghost",
|
|
2734
|
+
tone: "primary",
|
|
2735
|
+
onClick: () => setIsExpanded(!isExpanded)
|
|
2736
|
+
}
|
|
2737
|
+
) }),
|
|
2738
|
+
trackToDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2739
|
+
ui.Dialog,
|
|
2740
|
+
{
|
|
2741
|
+
animate: !0,
|
|
2742
|
+
id: dialogId,
|
|
2743
|
+
header: "Delete track",
|
|
2744
|
+
onClose: () => setTrackToDelete(null),
|
|
2745
|
+
onClickOutside: () => setTrackToDelete(null),
|
|
2746
|
+
width: 1,
|
|
2747
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2748
|
+
ui.Card,
|
|
2749
|
+
{
|
|
2750
|
+
padding: 3,
|
|
2751
|
+
style: {
|
|
2752
|
+
minHeight: "150px",
|
|
2753
|
+
display: "flex",
|
|
2754
|
+
alignItems: "center",
|
|
2755
|
+
justifyContent: "center"
|
|
2756
|
+
},
|
|
2757
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
2758
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Heading, { size: 2, children: [
|
|
2759
|
+
'Are you sure you want to delete "',
|
|
2760
|
+
trackToDelete.name || trackToDelete.language_code || "Untitled",
|
|
2761
|
+
'"?'
|
|
2762
|
+
] }),
|
|
2763
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 2, children: "This action is irreversible" }),
|
|
2764
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 4, marginY: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2765
|
+
ui.Button,
|
|
2766
|
+
{
|
|
2767
|
+
icon: deletingTrackId === trackToDelete.id ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2768
|
+
ui.Spinner,
|
|
2769
|
+
{
|
|
2770
|
+
style: {
|
|
2771
|
+
verticalAlign: "middle",
|
|
2772
|
+
display: "inline-block",
|
|
2773
|
+
marginTop: "-2px",
|
|
2774
|
+
width: "0.5em",
|
|
2775
|
+
height: "0.5em"
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(icons.TrashIcon, {}),
|
|
2779
|
+
fontSize: 2,
|
|
2780
|
+
padding: 3,
|
|
2781
|
+
text: "Delete track",
|
|
2782
|
+
tone: "critical",
|
|
2783
|
+
onClick: confirmDelete,
|
|
2784
|
+
disabled: deletingTrackId !== null
|
|
2785
|
+
}
|
|
2786
|
+
) }) })
|
|
2787
|
+
] })
|
|
2788
|
+
}
|
|
2789
|
+
)
|
|
2790
|
+
}
|
|
2791
|
+
),
|
|
2792
|
+
showAddDialog && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2793
|
+
AddCaptionDialog,
|
|
2794
|
+
{
|
|
2795
|
+
asset,
|
|
2796
|
+
onAdd: handleAddTrack,
|
|
2797
|
+
onClose: () => setShowAddDialog(!1)
|
|
2798
|
+
}
|
|
2799
|
+
),
|
|
2800
|
+
trackToEdit && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2801
|
+
EditCaptionDialog,
|
|
2802
|
+
{
|
|
2803
|
+
asset,
|
|
2804
|
+
track: trackToEdit,
|
|
2805
|
+
onUpdate: handleUpdateTrack,
|
|
2806
|
+
onClose: () => setTrackToEdit(null)
|
|
2807
|
+
}
|
|
2808
|
+
)
|
|
2809
|
+
] });
|
|
2810
|
+
}
|
|
2811
|
+
const DialogStateContext = React.createContext({
|
|
2812
|
+
dialogState: !1,
|
|
2813
|
+
setDialogState: () => null
|
|
2814
|
+
}), DialogStateProvider = ({
|
|
2815
|
+
dialogState,
|
|
2816
|
+
setDialogState,
|
|
2817
|
+
children
|
|
2818
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(DialogStateContext.Provider, { value: { dialogState, setDialogState }, children }), useDialogStateContext = () => React.useContext(DialogStateContext);
|
|
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");
|
|
2823
|
+
searchParams.set("token", token);
|
|
2824
|
+
}
|
|
2825
|
+
return `https://stream.mux.com/${muxPlaybackId2.id}.m3u8?${searchParams}`;
|
|
2826
|
+
}
|
|
2827
|
+
function CaptionsDialog({ asset }) {
|
|
2828
|
+
const { setDialogState } = useDialogStateContext(), dialogId = `CaptionsDialog${React.useId()}`;
|
|
2829
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, { id: dialogId, header: "Edit Captions", onClose: () => setDialogState(!1), width: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(TextTracksManager, { asset }) }) });
|
|
2830
|
+
}
|
|
2831
|
+
function getDevicePixelRatio(options) {
|
|
2832
|
+
const {
|
|
2833
|
+
defaultDpr = 1,
|
|
2834
|
+
maxDpr = 3,
|
|
2835
|
+
round = !0
|
|
2836
|
+
} = options || {}, dpr = typeof window < "u" && typeof window.devicePixelRatio == "number" ? window.devicePixelRatio : defaultDpr;
|
|
2837
|
+
return Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
|
|
2838
|
+
}
|
|
2839
|
+
function formatSeconds(seconds) {
|
|
2840
|
+
if (typeof seconds != "number" || Number.isNaN(seconds))
|
|
2841
|
+
return "";
|
|
2842
|
+
const hrs = ~~(seconds / 3600), mins = ~~(seconds % 3600 / 60), secs = ~~seconds % 60;
|
|
2843
|
+
let ret = "";
|
|
2844
|
+
return hrs > 0 && (ret += "" + hrs + ":" + (mins < 10 ? "0" : "")), ret += "" + mins + ":" + (secs < 10 ? "0" : ""), ret += "" + secs, ret;
|
|
2845
|
+
}
|
|
2846
|
+
function formatSecondsToHHMMSS(seconds) {
|
|
2847
|
+
const hrs = Math.floor(seconds / 3600).toString().padStart(2, "0"), mins = Math.floor(seconds % 3600 / 60).toString().padStart(2, "0"), secs = Math.floor(seconds % 60).toString().padStart(2, "0");
|
|
2848
|
+
return `${hrs}:${mins}:${secs}`;
|
|
2849
|
+
}
|
|
2850
|
+
function isValidTimeFormat(time) {
|
|
2851
|
+
return /^([0-1]?[0-9]|2[0-3]):([0-5]?[0-9]):([0-5]?[0-9])$/.test(time) || time === "";
|
|
2852
|
+
}
|
|
2853
|
+
function getSecondsFromTimeFormat(time) {
|
|
2854
|
+
const [hh = 0, mm = 0, ss = 0] = time.split(":").map(Number);
|
|
2855
|
+
return hh * 3600 + mm * 60 + ss;
|
|
2856
|
+
}
|
|
2857
|
+
function EditThumbnailDialog({ asset, currentTime = 0 }) {
|
|
2858
|
+
const client = useClient(), { setDialogState } = useDialogStateContext(), dialogId = `EditThumbnailDialog${React.useId()}`, [timeFormatted, setTimeFormatted] = React.useState(
|
|
2859
|
+
() => formatSecondsToHHMMSS(currentTime)
|
|
2860
|
+
), [nextTime, setNextTime] = React.useState(currentTime), [inputError, setInputError] = React.useState(""), assetWithNewThumbnail = React.useMemo(() => ({ ...asset, thumbTime: nextTime }), [asset, nextTime]), [saving, setSaving] = React.useState(!1), [saveThumbnailError, setSaveThumbnailError] = React.useState(null), handleSave = () => {
|
|
2861
|
+
setSaving(!0), client.patch(asset._id).set({ thumbTime: nextTime }).commit({ returnDocuments: !1 }).then(() => void setDialogState(!1)).catch(setSaveThumbnailError).finally(() => void setSaving(!1));
|
|
2862
|
+
}, width = 300 * getDevicePixelRatio({ maxDpr: 2 });
|
|
2863
|
+
if (saveThumbnailError)
|
|
2864
|
+
throw saveThumbnailError;
|
|
2865
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2866
|
+
ui.Dialog,
|
|
2867
|
+
{
|
|
2868
|
+
id: dialogId,
|
|
2869
|
+
header: "Edit thumbnail",
|
|
2870
|
+
onClose: () => setDialogState(!1),
|
|
2871
|
+
footer: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2872
|
+
ui.Button,
|
|
2873
|
+
{
|
|
2874
|
+
disabled: inputError !== "",
|
|
2875
|
+
mode: "ghost",
|
|
2876
|
+
tone: "primary",
|
|
2877
|
+
loading: saving,
|
|
2878
|
+
onClick: handleSave,
|
|
2879
|
+
text: "Set new thumbnail"
|
|
2880
|
+
},
|
|
2881
|
+
"thumbnail"
|
|
2882
|
+
) }),
|
|
2883
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, padding: 3, children: [
|
|
1452
2884
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
1453
2885
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: "Current:" }),
|
|
1454
2886
|
/* @__PURE__ */ jsxRuntime.jsx(VideoThumbnail, { asset, width, staticImage: !0 })
|
|
@@ -1500,24 +2932,56 @@ function VideoPlayer({
|
|
|
1500
2932
|
hlsConfig,
|
|
1501
2933
|
...props
|
|
1502
2934
|
}) {
|
|
1503
|
-
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = React.useRef(null), {
|
|
1504
|
-
src: videoSrc,
|
|
1505
|
-
thumbnail: thumbnailSrc,
|
|
1506
|
-
error
|
|
1507
|
-
} = React.useMemo(() => {
|
|
2935
|
+
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = React.useRef(null), [error, setError] = React.useState(), playbackId = React.useMemo(() => {
|
|
1508
2936
|
try {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
return
|
|
2937
|
+
return getPlaybackId(asset, ["public", "signed", "drm"]);
|
|
2938
|
+
} catch {
|
|
2939
|
+
setError(new TypeError("Asset has no playback ID"));
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
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);
|
|
1513
2957
|
}
|
|
1514
|
-
|
|
2958
|
+
), [asset, client, thumbnailWidth]), signedToken = React.useMemo(() => {
|
|
1515
2959
|
try {
|
|
1516
|
-
return new URL(
|
|
2960
|
+
return new URL(src).searchParams.get("token");
|
|
1517
2961
|
} catch {
|
|
1518
|
-
return
|
|
2962
|
+
return;
|
|
1519
2963
|
}
|
|
1520
|
-
}, [
|
|
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);
|
|
1521
2985
|
let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio);
|
|
1522
2986
|
return isAudio && (aspectRatio = props.forceAspectRatio ? (
|
|
1523
2987
|
// Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
|
|
@@ -1533,7 +2997,7 @@ function VideoPlayer({
|
|
|
1533
2997
|
...isAudio && { display: "flex", alignItems: "flex-end" }
|
|
1534
2998
|
},
|
|
1535
2999
|
children: [
|
|
1536
|
-
|
|
3000
|
+
src && poster && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1537
3001
|
isAudio && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1538
3002
|
AudioIcon,
|
|
1539
3003
|
{
|
|
@@ -1548,34 +3012,36 @@ function VideoPlayer({
|
|
|
1548
3012
|
}
|
|
1549
3013
|
}
|
|
1550
3014
|
),
|
|
1551
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
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
|
+
}
|
|
1575
3041
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
3042
|
+
),
|
|
3043
|
+
children
|
|
3044
|
+
] })
|
|
1579
3045
|
] }),
|
|
1580
3046
|
error ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1581
3047
|
"div",
|
|
@@ -1596,7 +3062,8 @@ function VideoPlayer({
|
|
|
1596
3062
|
]
|
|
1597
3063
|
}
|
|
1598
3064
|
),
|
|
1599
|
-
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsxRuntime.jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime })
|
|
3065
|
+
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsxRuntime.jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime }),
|
|
3066
|
+
dialogState === "edit-captions" && /* @__PURE__ */ jsxRuntime.jsx(CaptionsDialog, { asset })
|
|
1600
3067
|
] });
|
|
1601
3068
|
}
|
|
1602
3069
|
function assetIsAudio(asset) {
|
|
@@ -1866,9 +3333,11 @@ function getVideoMetadata(doc) {
|
|
|
1866
3333
|
playbackId: doc.playbackId,
|
|
1867
3334
|
createdAt: date,
|
|
1868
3335
|
duration: doc.data?.duration ? formatSeconds(doc.data?.duration) : void 0,
|
|
3336
|
+
playback_ids: doc.data?.playback_ids,
|
|
1869
3337
|
aspect_ratio: doc.data?.aspect_ratio,
|
|
1870
3338
|
max_stored_resolution: doc.data?.max_stored_resolution,
|
|
1871
|
-
max_stored_frame_rate: doc.data?.max_stored_frame_rate
|
|
3339
|
+
max_stored_frame_rate: doc.data?.max_stored_frame_rate,
|
|
3340
|
+
text_tracks: doc.data?.tracks?.filter((track) => track.type === "text") || []
|
|
1872
3341
|
};
|
|
1873
3342
|
}
|
|
1874
3343
|
function useVideoDetails(props) {
|
|
@@ -2060,7 +3529,20 @@ const AssetInput = (props) => /* @__PURE__ */ jsxRuntime.jsx(FormField$1, { titl
|
|
|
2060
3529
|
minHeight: containerHeight
|
|
2061
3530
|
} : void 0,
|
|
2062
3531
|
children: [
|
|
2063
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3532
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
3533
|
+
/* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { asset: props.asset, autoPlay: props.asset.autoPlay || !1 }),
|
|
3534
|
+
tab === "details" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3535
|
+
TextTracksManager,
|
|
3536
|
+
{
|
|
3537
|
+
asset: props.asset,
|
|
3538
|
+
iconOnly: !0,
|
|
3539
|
+
collapseTracks: !0,
|
|
3540
|
+
tracks: displayInfo?.text_tracks || props.asset.data?.tracks?.filter(
|
|
3541
|
+
(track) => track.type === "text"
|
|
3542
|
+
) || []
|
|
3543
|
+
}
|
|
3544
|
+
)
|
|
3545
|
+
] }),
|
|
2064
3546
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
2065
3547
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.TabList, { space: 2, children: [
|
|
2066
3548
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2154,14 +3636,7 @@ const AssetInput = (props) => /* @__PURE__ */ jsxRuntime.jsx(FormField$1, { titl
|
|
|
2154
3636
|
),
|
|
2155
3637
|
/* @__PURE__ */ jsxRuntime.jsx(IconInfo, { text: `Mux ID:
|
|
2156
3638
|
${displayInfo.id}`, icon: icons.TagIcon, size: 2 }),
|
|
2157
|
-
|
|
2158
|
-
IconInfo,
|
|
2159
|
-
{
|
|
2160
|
-
text: `Playback ID: ${displayInfo.playbackId}`,
|
|
2161
|
-
icon: icons.TagIcon,
|
|
2162
|
-
size: 2
|
|
2163
|
-
}
|
|
2164
|
-
)
|
|
3639
|
+
/* @__PURE__ */ jsxRuntime.jsx(PlaybackIds, { playback_ids: displayInfo.playback_ids })
|
|
2165
3640
|
] })
|
|
2166
3641
|
] })
|
|
2167
3642
|
}
|
|
@@ -2184,6 +3659,25 @@ ${displayInfo.id}`, icon: icons.TagIcon, size: 2 }),
|
|
|
2184
3659
|
]
|
|
2185
3660
|
}
|
|
2186
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
|
+
}
|
|
2187
3681
|
}, VideoMetadata = (props) => {
|
|
2188
3682
|
if (!props.asset)
|
|
2189
3683
|
return null;
|
|
@@ -2277,10 +3771,12 @@ function VideoInBrowser({
|
|
|
2277
3771
|
onEdit,
|
|
2278
3772
|
asset
|
|
2279
3773
|
}) {
|
|
2280
|
-
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();
|
|
2281
3775
|
if (!asset)
|
|
2282
3776
|
return null;
|
|
2283
|
-
const playbackPolicy = getPlaybackPolicy(asset)
|
|
3777
|
+
const playbackPolicy = getPlaybackPolicy(asset), onClickPlay = () => {
|
|
3778
|
+
playbackPolicy?.policy === "drm" && !hasShownWarning ? setRenderVideo("pre-render-warn") : setRenderVideo("render-video");
|
|
3779
|
+
};
|
|
2284
3780
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2285
3781
|
ui.Card,
|
|
2286
3782
|
{
|
|
@@ -2292,7 +3788,7 @@ function VideoInBrowser({
|
|
|
2292
3788
|
position: "relative"
|
|
2293
3789
|
},
|
|
2294
3790
|
children: [
|
|
2295
|
-
playbackPolicy === "signed" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3791
|
+
playbackPolicy?.policy === "signed" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2296
3792
|
ui.Tooltip,
|
|
2297
3793
|
{
|
|
2298
3794
|
animate: !0,
|
|
@@ -2309,7 +3805,7 @@ function VideoInBrowser({
|
|
|
2309
3805
|
position: "absolute",
|
|
2310
3806
|
left: "1em",
|
|
2311
3807
|
top: "1em",
|
|
2312
|
-
zIndex:
|
|
3808
|
+
zIndex: 11
|
|
2313
3809
|
},
|
|
2314
3810
|
padding: 2,
|
|
2315
3811
|
border: !0,
|
|
@@ -2318,6 +3814,32 @@ function VideoInBrowser({
|
|
|
2318
3814
|
)
|
|
2319
3815
|
}
|
|
2320
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
|
+
),
|
|
2321
3843
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2322
3844
|
ui.Stack,
|
|
2323
3845
|
{
|
|
@@ -2327,7 +3849,15 @@ function VideoInBrowser({
|
|
|
2327
3849
|
gridTemplateRows: "min-content min-content 1fr"
|
|
2328
3850
|
},
|
|
2329
3851
|
children: [
|
|
2330
|
-
renderVideo
|
|
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: [
|
|
2331
3861
|
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-play": !0, children: /* @__PURE__ */ jsxRuntime.jsx(icons.PlayIcon, {}) }),
|
|
2332
3862
|
assetIsAudio(asset) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2333
3863
|
"div",
|
|
@@ -2389,12 +3919,12 @@ function VideoInBrowser({
|
|
|
2389
3919
|
}
|
|
2390
3920
|
);
|
|
2391
3921
|
}
|
|
2392
|
-
function VideosBrowser({ onSelect }) {
|
|
2393
|
-
const { assets, isLoading, searchQuery, setSearchQuery, setSort, sort } = useAssets(), [editedAsset, setEditedAsset] = React.useState(null), freshEditedAsset = React.useMemo(
|
|
3922
|
+
function VideosBrowser({ onSelect, config }) {
|
|
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(
|
|
2394
3924
|
() => assets.find((a2) => a2._id === editedAsset?._id) || editedAsset,
|
|
2395
3925
|
[editedAsset, assets]
|
|
2396
|
-
);
|
|
2397
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3926
|
+
), pageStart = page * pageLimit, pageEnd = pageStart + pageLimit;
|
|
3927
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DrmPlaybackWarningContextProvider, { config, children: [
|
|
2398
3928
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { padding: 4, space: 4, style: { minHeight: "50vh" }, children: [
|
|
2399
3929
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { justify: "space-between", align: "center", children: [
|
|
2400
3930
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 3, children: [
|
|
@@ -2407,7 +3937,8 @@ function VideosBrowser({ onSelect }) {
|
|
|
2407
3937
|
placeholder: "Search videos"
|
|
2408
3938
|
}
|
|
2409
3939
|
),
|
|
2410
|
-
/* @__PURE__ */ jsxRuntime.jsx(SelectSortOptions, { setSort, sort })
|
|
3940
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectSortOptions, { setSort, sort }),
|
|
3941
|
+
/* @__PURE__ */ jsxRuntime.jsx(PageSelector, { page, setPage, total: pageTotal })
|
|
2411
3942
|
] }),
|
|
2412
3943
|
(onSelect ? "input" : "tool") == "tool" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Inline, { space: 2, children: [
|
|
2413
3944
|
/* @__PURE__ */ jsxRuntime.jsx(ImportVideosFromMux, {}),
|
|
@@ -2430,7 +3961,7 @@ function VideosBrowser({ onSelect }) {
|
|
|
2430
3961
|
style: {
|
|
2431
3962
|
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))"
|
|
2432
3963
|
},
|
|
2433
|
-
children: assets.map((asset) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3964
|
+
children: assets.slice(pageStart, pageEnd).map((asset) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2434
3965
|
VideoInBrowser,
|
|
2435
3966
|
{
|
|
2436
3967
|
asset,
|
|
@@ -2448,7 +3979,7 @@ function VideosBrowser({ onSelect }) {
|
|
|
2448
3979
|
freshEditedAsset && /* @__PURE__ */ jsxRuntime.jsx(VideoDetails, { closeDialog: () => setEditedAsset(null), asset: freshEditedAsset })
|
|
2449
3980
|
] });
|
|
2450
3981
|
}
|
|
2451
|
-
const StudioTool = () => /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, {}), DEFAULT_TOOL_CONFIG = {
|
|
3982
|
+
const StudioTool = (config) => /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, { config }), DEFAULT_TOOL_CONFIG = {
|
|
2452
3983
|
icon: ToolIcon,
|
|
2453
3984
|
title: "Videos"
|
|
2454
3985
|
};
|
|
@@ -2806,6 +4337,9 @@ function isValidUrl(url) {
|
|
|
2806
4337
|
return !1;
|
|
2807
4338
|
}
|
|
2808
4339
|
}
|
|
4340
|
+
function isServerError(error) {
|
|
4341
|
+
return "statusCode" in error && typeof error.statusCode == "number" && 500 <= error.statusCode && error.statusCode <= 600;
|
|
4342
|
+
}
|
|
2809
4343
|
function extractDroppedFiles(dataTransfer) {
|
|
2810
4344
|
const files = Array.from(dataTransfer.files || []), items = Array.from(dataTransfer.items || []);
|
|
2811
4345
|
return files && files.length > 0 ? Promise.resolve(files) : normalizeItems(items).then((arr) => arr.flat());
|
|
@@ -2847,7 +4381,12 @@ function walk(entry) {
|
|
|
2847
4381
|
}
|
|
2848
4382
|
return Promise.resolve([]);
|
|
2849
4383
|
}
|
|
2850
|
-
function SelectAssets({
|
|
4384
|
+
function SelectAssets({
|
|
4385
|
+
asset: selectedAsset,
|
|
4386
|
+
onChange,
|
|
4387
|
+
setDialogState,
|
|
4388
|
+
config
|
|
4389
|
+
}) {
|
|
2851
4390
|
const handleSelect = React.useCallback(
|
|
2852
4391
|
(chosenAsset) => {
|
|
2853
4392
|
chosenAsset?._id || onChange(sanity.PatchEvent.from([sanity.unset(["asset"])])), chosenAsset._id !== selectedAsset?._id && onChange(
|
|
@@ -2859,7 +4398,7 @@ function SelectAssets({ asset: selectedAsset, onChange, setDialogState }) {
|
|
|
2859
4398
|
},
|
|
2860
4399
|
[onChange, setDialogState, selectedAsset]
|
|
2861
4400
|
);
|
|
2862
|
-
return /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, { onSelect: handleSelect });
|
|
4401
|
+
return /* @__PURE__ */ jsxRuntime.jsx(VideosBrowser, { onSelect: handleSelect, config });
|
|
2863
4402
|
}
|
|
2864
4403
|
const StyledDialog = styledComponents.styled(ui.Dialog)`
|
|
2865
4404
|
> div[data-ui='DialogCard'] > div[data-ui='Card'] {
|
|
@@ -2869,7 +4408,8 @@ const StyledDialog = styledComponents.styled(ui.Dialog)`
|
|
|
2869
4408
|
function InputBrowser({
|
|
2870
4409
|
setDialogState,
|
|
2871
4410
|
asset,
|
|
2872
|
-
onChange
|
|
4411
|
+
onChange,
|
|
4412
|
+
config
|
|
2873
4413
|
}) {
|
|
2874
4414
|
const id = `InputBrowser${React.useId()}`, handleClose = React.useCallback(() => setDialogState(!1), [setDialogState]);
|
|
2875
4415
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2880,7 +4420,15 @@ function InputBrowser({
|
|
|
2880
4420
|
id,
|
|
2881
4421
|
onClose: handleClose,
|
|
2882
4422
|
width: 2,
|
|
2883
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4423
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4424
|
+
SelectAssets,
|
|
4425
|
+
{
|
|
4426
|
+
config,
|
|
4427
|
+
asset,
|
|
4428
|
+
onChange,
|
|
4429
|
+
setDialogState
|
|
4430
|
+
}
|
|
4431
|
+
)
|
|
2884
4432
|
}
|
|
2885
4433
|
);
|
|
2886
4434
|
}
|
|
@@ -3096,7 +4644,7 @@ const FileButton = styledComponents.styled(ui.MenuItem)(({ theme }) => {
|
|
|
3096
4644
|
color: white;
|
|
3097
4645
|
`, isVideoAsset = (asset) => asset._type === "mux.videoAsset";
|
|
3098
4646
|
function PlayerActionsMenu(props) {
|
|
3099
|
-
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]);
|
|
3100
4648
|
return React.useEffect(() => {
|
|
3101
4649
|
open && dialogState && setOpen(!1);
|
|
3102
4650
|
}, [dialogState, open]), ui.useClickOutsideEvent(
|
|
@@ -3138,14 +4686,24 @@ function PlayerActionsMenu(props) {
|
|
|
3138
4686
|
onClick: () => setDialogState("select-video")
|
|
3139
4687
|
}
|
|
3140
4688
|
),
|
|
3141
|
-
isVideoAsset(asset) && /* @__PURE__ */ jsxRuntime.
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
4689
|
+
isVideoAsset(asset) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4690
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4691
|
+
ui.MenuItem,
|
|
4692
|
+
{
|
|
4693
|
+
icon: icons.ImageIcon,
|
|
4694
|
+
text: "Thumbnail",
|
|
4695
|
+
onClick: () => setDialogState("edit-thumbnail")
|
|
4696
|
+
}
|
|
4697
|
+
),
|
|
4698
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4699
|
+
ui.MenuItem,
|
|
4700
|
+
{
|
|
4701
|
+
icon: icons.TranslateIcon,
|
|
4702
|
+
text: "Captions",
|
|
4703
|
+
onClick: () => setDialogState("edit-captions")
|
|
4704
|
+
}
|
|
4705
|
+
)
|
|
4706
|
+
] }),
|
|
3149
4707
|
/* @__PURE__ */ jsxRuntime.jsx(ui.MenuDivider, {}),
|
|
3150
4708
|
hasConfigAccess && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3151
4709
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3187,47 +4745,90 @@ function PlayerActionsMenu(props) {
|
|
|
3187
4745
|
] });
|
|
3188
4746
|
}
|
|
3189
4747
|
var PlayerActionsMenu$1 = React.memo(PlayerActionsMenu);
|
|
3190
|
-
function
|
|
3191
|
-
const
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
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
|
+
};
|
|
3201
4770
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
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
|
+
};
|
|
3228
4820
|
}
|
|
3229
|
-
function
|
|
3230
|
-
|
|
4821
|
+
function formatBytes(bytes, si = !1, dp = 1) {
|
|
4822
|
+
const thresh = si ? 1e3 : 1024;
|
|
4823
|
+
if (Math.abs(bytes) < thresh)
|
|
4824
|
+
return bytes + " B";
|
|
4825
|
+
const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
|
4826
|
+
let u2 = -1;
|
|
4827
|
+
const r = 10 ** dp;
|
|
4828
|
+
do
|
|
4829
|
+
bytes /= thresh, ++u2;
|
|
4830
|
+
while (Math.round(Math.abs(bytes) * r) / r >= thresh && u2 < units.length - 1);
|
|
4831
|
+
return bytes.toFixed(dp) + " " + units[u2];
|
|
3231
4832
|
}
|
|
3232
4833
|
const ALL_LANGUAGE_CODES = LanguagesList__default.default.getAllCodes().map((code) => ({
|
|
3233
4834
|
value: code,
|
|
@@ -3306,13 +4907,14 @@ function PlaybackPolicyOption({
|
|
|
3306
4907
|
optionName,
|
|
3307
4908
|
description,
|
|
3308
4909
|
dispatch,
|
|
3309
|
-
action
|
|
4910
|
+
action,
|
|
4911
|
+
disabled
|
|
3310
4912
|
}) {
|
|
3311
4913
|
const [scale, setScale] = React.useState(1), boxStyle = {
|
|
3312
4914
|
outline: "0.01rem solid grey",
|
|
3313
4915
|
transform: `scale(${scale})`,
|
|
3314
4916
|
transition: "transform 0.1s ease-in-out",
|
|
3315
|
-
cursor: "pointer",
|
|
4917
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
3316
4918
|
borderRadius: "0.25rem"
|
|
3317
4919
|
}, triggerAnimation = () => {
|
|
3318
4920
|
setScale(0.98), setTimeout(() => {
|
|
@@ -3320,15 +4922,24 @@ function PlaybackPolicyOption({
|
|
|
3320
4922
|
}, 100);
|
|
3321
4923
|
};
|
|
3322
4924
|
return /* @__PURE__ */ jsxRuntime.jsx("label", { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, padding: 3, style: boxStyle, children: [
|
|
3323
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
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
|
+
),
|
|
3329
4940
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Grid, { gap: 3, children: [
|
|
3330
4941
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 3, weight: "bold", children: optionName }),
|
|
3331
|
-
/* @__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
|
|
3332
4943
|
] })
|
|
3333
4944
|
] }) });
|
|
3334
4945
|
}
|
|
@@ -3353,7 +4964,7 @@ function PlaybackPolicy({
|
|
|
3353
4964
|
secrets,
|
|
3354
4965
|
dispatch
|
|
3355
4966
|
}) {
|
|
3356
|
-
const noPolicySelected = !(config.public_policy || config.signed_policy);
|
|
4967
|
+
const noPolicySelected = !(config.public_policy || config.signed_policy || config.drm_policy), drmPolicyDisabled = !secrets.drmConfigId;
|
|
3357
4968
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Grid, { gap: 3, children: [
|
|
3358
4969
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { weight: "bold", children: "Advanced Playback Policies" }),
|
|
3359
4970
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3362,7 +4973,10 @@ function PlaybackPolicy({
|
|
|
3362
4973
|
id: `${id}--public`,
|
|
3363
4974
|
checked: config.public_policy,
|
|
3364
4975
|
optionName: "Public",
|
|
3365
|
-
description:
|
|
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
|
+
] }),
|
|
3366
4980
|
dispatch,
|
|
3367
4981
|
action: "public_policy"
|
|
3368
4982
|
}
|
|
@@ -3373,24 +4987,146 @@ function PlaybackPolicy({
|
|
|
3373
4987
|
id: `${id}--signed`,
|
|
3374
4988
|
checked: config.signed_policy,
|
|
3375
4989
|
optionName: "Signed",
|
|
3376
|
-
description:
|
|
3377
|
-
|
|
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
|
+
] }),
|
|
3378
5009
|
dispatch,
|
|
3379
5010
|
action: "signed_policy"
|
|
3380
5011
|
}
|
|
3381
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
|
+
),
|
|
3382
5078
|
noPolicySelected && /* @__PURE__ */ jsxRuntime.jsx(PlaybackPolicyWarning, {})
|
|
3383
5079
|
] });
|
|
3384
5080
|
}
|
|
3385
|
-
const
|
|
3386
|
-
{ value: "basic", label: "Basic" },
|
|
3387
|
-
{ value: "plus", label: "Plus" },
|
|
3388
|
-
{ value: "premium", label: "Premium" }
|
|
3389
|
-
], RESOLUTION_TIERS = [
|
|
5081
|
+
const RESOLUTION_TIERS = [
|
|
3390
5082
|
{ value: "1080p", label: "1080p" },
|
|
3391
5083
|
{ value: "1440p", label: "1440p (2k)" },
|
|
3392
5084
|
{ value: "2160p", label: "2160p (4k)" }
|
|
3393
|
-
],
|
|
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 = [
|
|
3394
5130
|
{ value: "270p", label: "270p" },
|
|
3395
5131
|
{ value: "360p", label: "360p" },
|
|
3396
5132
|
{ value: "480p", label: "480p" },
|
|
@@ -3399,6 +5135,130 @@ const VIDEO_QUALITY_LEVELS = [
|
|
|
3399
5135
|
{ value: "1080p", label: "1080p" },
|
|
3400
5136
|
{ value: "1440p", label: "1440p" },
|
|
3401
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" }
|
|
3402
5262
|
];
|
|
3403
5263
|
function sanitizeStaticRenditions(renditions) {
|
|
3404
5264
|
const hasHighest = renditions.includes("highest"), hasSpecificResolutions = renditions.some((r) => r !== "highest" && r !== "audio-only");
|
|
@@ -3430,7 +5290,8 @@ function UploadConfiguration({
|
|
|
3430
5290
|
max_resolution_tier: "1080p",
|
|
3431
5291
|
text_tracks: prev.text_tracks?.filter(({ type }) => type !== "autogenerated"),
|
|
3432
5292
|
public_policy: !0,
|
|
3433
|
-
signed_policy: !1
|
|
5293
|
+
signed_policy: !1,
|
|
5294
|
+
drm_policy: !1
|
|
3434
5295
|
}) : Object.assign({}, prev, {
|
|
3435
5296
|
video_quality: action.value,
|
|
3436
5297
|
static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
|
|
@@ -3444,6 +5305,8 @@ function UploadConfiguration({
|
|
|
3444
5305
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
3445
5306
|
case "public_policy":
|
|
3446
5307
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
5308
|
+
case "drm_policy":
|
|
5309
|
+
return Object.assign({}, prev, { [action.action]: action.value });
|
|
3447
5310
|
// Updating individual tracks
|
|
3448
5311
|
case "track": {
|
|
3449
5312
|
const text_tracks = [...prev.text_tracks], target_track_i = text_tracks.findIndex(({ _id: _id2 }) => _id2 === action.id);
|
|
@@ -3479,75 +5342,41 @@ function UploadConfiguration({
|
|
|
3479
5342
|
static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
|
|
3480
5343
|
signed_policy: secrets.enableSignedUrls && pluginConfig.defaultSigned,
|
|
3481
5344
|
public_policy: pluginConfig.defaultPublic,
|
|
5345
|
+
drm_policy: pluginConfig.defaultDrm && !!secrets.drmConfigId,
|
|
3482
5346
|
normalize_audio: pluginConfig.normalize_audio,
|
|
3483
5347
|
text_tracks: autoTextTracks
|
|
3484
5348
|
}
|
|
3485
|
-
),
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
), [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);
|
|
3490
5353
|
React.useEffect(() => {
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
const
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
!MAX_DURATION_SECONDS || MAX_DURATION_SECONDS <= 0 || (setIsLoadingDuration(!0), videoElement = document.createElement("video"), videoElement.preload = "metadata", currentVideoSrc = videoSrc, videoElement.onloadedmetadata = () => {
|
|
3497
|
-
const duration = videoElement.duration;
|
|
3498
|
-
setVideoDuration(duration), setIsLoadingDuration(!1), duration > MAX_DURATION_SECONDS && setValidationError(
|
|
3499
|
-
`Video duration (${formatSeconds(duration)}) exceeds maximum allowed duration of ${formatSeconds(MAX_DURATION_SECONDS)}`
|
|
3500
|
-
), cleanupVideo(shouldRevokeUrl);
|
|
3501
|
-
}, videoElement.onerror = () => {
|
|
3502
|
-
setIsLoadingDuration(!1), console.warn("Could not read video metadata for validation"), cleanupVideo(shouldRevokeUrl);
|
|
3503
|
-
}, videoElement.src = videoSrc);
|
|
3504
|
-
}, 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(
|
|
3505
5359
|
`File size (${formatBytes(size)}) exceeds maximum allowed size of ${formatBytes(MAX_FILE_SIZE)}`
|
|
3506
|
-
), !1);
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
}
|
|
3523
|
-
})();
|
|
3524
|
-
}
|
|
3525
|
-
return () => {
|
|
3526
|
-
cleanupVideo(!0);
|
|
3527
|
-
};
|
|
3528
|
-
}, [stagedUpload, MAX_FILE_SIZE, MAX_DURATION_SECONDS]);
|
|
3529
|
-
const toggleRendition = (rendition) => {
|
|
3530
|
-
const current = config.static_renditions, hasRendition = current.includes(rendition);
|
|
3531
|
-
dispatch(hasRendition ? {
|
|
3532
|
-
action: "static_renditions",
|
|
3533
|
-
value: current.filter((r) => r !== rendition)
|
|
3534
|
-
} : {
|
|
3535
|
-
action: "static_renditions",
|
|
3536
|
-
value: [...current, rendition]
|
|
3537
|
-
});
|
|
3538
|
-
}, handleModeChange = (mode) => {
|
|
3539
|
-
setRenditionMode(mode), dispatch(mode === "standard" ? {
|
|
3540
|
-
action: "static_renditions",
|
|
3541
|
-
value: config.static_renditions.filter((r) => r === "highest" || r === "audio-only")
|
|
3542
|
-
} : {
|
|
3543
|
-
action: "static_renditions",
|
|
3544
|
-
value: config.static_renditions.filter((r) => r !== "highest")
|
|
3545
|
-
});
|
|
3546
|
-
}, { 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;
|
|
3547
5376
|
if (React.useEffect(() => {
|
|
3548
|
-
skipConfig && startUpload(formatUploadConfig(config));
|
|
5377
|
+
skipConfig && startUpload(formatUploadConfig(config, secrets));
|
|
3549
5378
|
}, []), skipConfig) return null;
|
|
3550
|
-
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(
|
|
3551
5380
|
(rt) => rt.value === pluginConfig.max_resolution_tier
|
|
3552
5381
|
);
|
|
3553
5382
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3581,12 +5410,12 @@ function UploadConfiguration({
|
|
|
3581
5410
|
/* @__PURE__ */ jsxRuntime.jsx(icons.DocumentVideoIcon, { fontSize: "2em" }),
|
|
3582
5411
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
3583
5412
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { textOverflow: "ellipsis", as: "h2", size: 3, children: stagedUpload.type === "file" ? stagedUpload.files[0].name : stagedUpload.url }),
|
|
3584
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "p", size: 1, muted: !0, children: stagedUpload.type === "file" ? `Direct File Upload (${formatBytes(stagedUpload.files[0].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)" }),
|
|
3585
5414
|
stagedUpload.type === "file" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 1, children: [
|
|
3586
|
-
|
|
3587
|
-
|
|
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: [
|
|
3588
5417
|
"Duration: ",
|
|
3589
|
-
formatSeconds(
|
|
5418
|
+
formatSeconds(videoAssetMetadata.duration)
|
|
3590
5419
|
] })
|
|
3591
5420
|
] })
|
|
3592
5421
|
] })
|
|
@@ -3632,160 +5461,37 @@ function UploadConfiguration({
|
|
|
3632
5461
|
}) })
|
|
3633
5462
|
}
|
|
3634
5463
|
),
|
|
3635
|
-
!basicConfig && maxSupportedResolution > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3636
|
-
sanity.FormField,
|
|
3637
|
-
{
|
|
3638
|
-
title: "Resolution Tier",
|
|
3639
|
-
description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3640
|
-
"The maximum",
|
|
3641
|
-
" ",
|
|
3642
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3643
|
-
"a",
|
|
3644
|
-
{
|
|
3645
|
-
href: "https://docs.mux.com/api-reference#video/operation/create-direct-upload",
|
|
3646
|
-
target: "_blank",
|
|
3647
|
-
rel: "noopener noreferrer",
|
|
3648
|
-
children: "resolution_tier"
|
|
3649
|
-
}
|
|
3650
|
-
),
|
|
3651
|
-
" ",
|
|
3652
|
-
"your asset is encoded, stored, and streamed at."
|
|
3653
|
-
] }),
|
|
3654
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 3, wrap: "wrap", children: RESOLUTION_TIERS.map(({ value, label }, index) => {
|
|
3655
|
-
const inputId = `${id}--type-${value}`;
|
|
3656
|
-
return index > maxSupportedResolution ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
3657
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3658
|
-
ui.Radio,
|
|
3659
|
-
{
|
|
3660
|
-
checked: config.max_resolution_tier === value,
|
|
3661
|
-
name: "asset-resolutiontier",
|
|
3662
|
-
onChange: (e) => dispatch({
|
|
3663
|
-
action: "max_resolution_tier",
|
|
3664
|
-
value: e.currentTarget.value
|
|
3665
|
-
}),
|
|
3666
|
-
value,
|
|
3667
|
-
id: inputId
|
|
3668
|
-
}
|
|
3669
|
-
),
|
|
3670
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: inputId, children: label })
|
|
3671
|
-
] }, value);
|
|
3672
|
-
}) })
|
|
3673
|
-
}
|
|
3674
|
-
),
|
|
3675
5464
|
!basicConfig && /* @__PURE__ */ jsxRuntime.jsx(sanity.FormField, { title: "Additional Configuration", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
3676
5465
|
/* @__PURE__ */ jsxRuntime.jsx(PlaybackPolicy, { id, config, secrets, dispatch }),
|
|
3677
|
-
|
|
3678
|
-
|
|
5466
|
+
maxSupportedResolution > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5467
|
+
ResolutionTierSelector,
|
|
3679
5468
|
{
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
3685
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3686
|
-
ui.Radio,
|
|
3687
|
-
{
|
|
3688
|
-
checked: renditionMode === "standard",
|
|
3689
|
-
name: "rendition-mode",
|
|
3690
|
-
onChange: () => handleModeChange("standard"),
|
|
3691
|
-
value: "standard",
|
|
3692
|
-
id: `${id}--mode-standard`
|
|
3693
|
-
}
|
|
3694
|
-
),
|
|
3695
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--mode-standard`, children: "Standard" })
|
|
3696
|
-
] }),
|
|
3697
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
3698
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3699
|
-
ui.Radio,
|
|
3700
|
-
{
|
|
3701
|
-
checked: renditionMode === "advanced",
|
|
3702
|
-
name: "rendition-mode",
|
|
3703
|
-
onChange: () => handleModeChange("advanced"),
|
|
3704
|
-
value: "advanced",
|
|
3705
|
-
id: `${id}--mode-advanced`
|
|
3706
|
-
}
|
|
3707
|
-
),
|
|
3708
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--mode-advanced`, children: "Advanced" })
|
|
3709
|
-
] })
|
|
3710
|
-
] }),
|
|
3711
|
-
renditionMode === "standard" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
3712
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [0, 2], children: [
|
|
3713
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3714
|
-
ui.Checkbox,
|
|
3715
|
-
{
|
|
3716
|
-
id: `${id}--highest`,
|
|
3717
|
-
style: { display: "block" },
|
|
3718
|
-
checked: config.static_renditions.includes("highest"),
|
|
3719
|
-
onChange: () => toggleRendition("highest")
|
|
3720
|
-
}
|
|
3721
|
-
),
|
|
3722
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--highest`, children: "Highest Resolution (up to 4K)" })
|
|
3723
|
-
] }),
|
|
3724
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [0, 2], children: [
|
|
3725
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3726
|
-
ui.Checkbox,
|
|
3727
|
-
{
|
|
3728
|
-
id: `${id}--audio-only-standard`,
|
|
3729
|
-
style: { display: "block" },
|
|
3730
|
-
checked: config.static_renditions.includes("audio-only"),
|
|
3731
|
-
onChange: () => toggleRendition("audio-only")
|
|
3732
|
-
}
|
|
3733
|
-
),
|
|
3734
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--audio-only-standard`, children: "Audio Only (M4A)" })
|
|
3735
|
-
] })
|
|
3736
|
-
] }),
|
|
3737
|
-
renditionMode === "advanced" && /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
3738
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: 1, muted: !0, children: "Select specific resolutions:" }),
|
|
3739
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, wrap: "wrap", children: ADVANCED_RESOLUTIONS.map(({ value, label }) => {
|
|
3740
|
-
const inputId = `${id}--resolution-${value}`;
|
|
3741
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
3742
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3743
|
-
ui.Checkbox,
|
|
3744
|
-
{
|
|
3745
|
-
id: inputId,
|
|
3746
|
-
style: { display: "block" },
|
|
3747
|
-
checked: config.static_renditions.includes(value),
|
|
3748
|
-
onChange: () => toggleRendition(value)
|
|
3749
|
-
}
|
|
3750
|
-
),
|
|
3751
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: inputId, size: 1, children: label })
|
|
3752
|
-
] }, value);
|
|
3753
|
-
}) }),
|
|
3754
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, padding: [2, 2, 0, 2], children: [
|
|
3755
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3756
|
-
ui.Checkbox,
|
|
3757
|
-
{
|
|
3758
|
-
id: `${id}--audio-only-advanced`,
|
|
3759
|
-
style: { display: "block" },
|
|
3760
|
-
checked: config.static_renditions.includes("audio-only"),
|
|
3761
|
-
onChange: () => toggleRendition("audio-only")
|
|
3762
|
-
}
|
|
3763
|
-
),
|
|
3764
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { as: "label", htmlFor: `${id}--audio-only-advanced`, children: "Audio Only (M4A)" })
|
|
3765
|
-
] })
|
|
3766
|
-
] })
|
|
3767
|
-
] })
|
|
5469
|
+
id,
|
|
5470
|
+
config,
|
|
5471
|
+
dispatch,
|
|
5472
|
+
maxSupportedResolution
|
|
3768
5473
|
}
|
|
3769
|
-
)
|
|
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
|
+
)
|
|
3770
5484
|
] }) })
|
|
3771
5485
|
] }),
|
|
3772
|
-
!disableTextTrackConfig && !basicConfig && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3773
|
-
TextTracksEditor,
|
|
3774
|
-
{
|
|
3775
|
-
tracks: config.text_tracks,
|
|
3776
|
-
dispatch,
|
|
3777
|
-
defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
|
|
3778
|
-
}
|
|
3779
|
-
),
|
|
3780
5486
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3781
5487
|
ui.Button,
|
|
3782
5488
|
{
|
|
3783
|
-
disabled: !basicConfig && !
|
|
5489
|
+
disabled: !basicConfig && !playbackPolicySelected || validationError !== null || isLoadingMetadata || isLoadingFileSize && !canSkipFileSizeValidation,
|
|
3784
5490
|
icon: icons.UploadIcon,
|
|
3785
5491
|
text: "Upload",
|
|
3786
5492
|
tone: "positive",
|
|
3787
5493
|
onClick: () => {
|
|
3788
|
-
validationError || startUpload(formatUploadConfig(config));
|
|
5494
|
+
validationError || startUpload(formatUploadConfig(config, secrets));
|
|
3789
5495
|
}
|
|
3790
5496
|
}
|
|
3791
5497
|
) })
|
|
@@ -3793,11 +5499,14 @@ function UploadConfiguration({
|
|
|
3793
5499
|
}
|
|
3794
5500
|
);
|
|
3795
5501
|
}
|
|
3796
|
-
function
|
|
3797
|
-
const
|
|
3798
|
-
return config.public_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;
|
|
3799
5508
|
}
|
|
3800
|
-
function formatUploadConfig(config) {
|
|
5509
|
+
function formatUploadConfig(config, secrets) {
|
|
3801
5510
|
const generated_subtitles = config.text_tracks.filter(isAutogeneratedTrack).map((track) => ({
|
|
3802
5511
|
name: track.name,
|
|
3803
5512
|
language_code: track.language_code
|
|
@@ -3821,7 +5530,7 @@ function formatUploadConfig(config) {
|
|
|
3821
5530
|
)
|
|
3822
5531
|
],
|
|
3823
5532
|
static_renditions: config.static_renditions.length > 0 ? config.static_renditions.map((resolution) => ({ resolution })) : void 0,
|
|
3824
|
-
|
|
5533
|
+
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
3825
5534
|
max_resolution_tier: config.max_resolution_tier,
|
|
3826
5535
|
video_quality: config.video_quality,
|
|
3827
5536
|
normalize_audio: config.normalize_audio
|
|
@@ -3850,12 +5559,10 @@ function withFocusRing(component) {
|
|
|
3850
5559
|
`;
|
|
3851
5560
|
});
|
|
3852
5561
|
}
|
|
3853
|
-
const
|
|
5562
|
+
const UploadCardWithFocusRing = withFocusRing(ui.Card), UploadCard = React.forwardRef(
|
|
3854
5563
|
({ children, tone, onPaste, onDrop, onDragEnter, onDragLeave, onDragOver }, forwardedRef) => {
|
|
3855
|
-
const
|
|
3856
|
-
|
|
3857
|
-
}, []), handleKeyUp = React.useCallback((event) => {
|
|
3858
|
-
(event.keyCode == ctrlKey || event.keyCode == cmdKey) && (ctrlDown.current = !1);
|
|
5564
|
+
const inputRef = React.useRef(null), handleKeyDown = React.useCallback((event) => {
|
|
5565
|
+
event.target.closest("#vtt-url") || (event.ctrlKey || event.metaKey) && event.key === "v" && inputRef.current.focus();
|
|
3859
5566
|
}, []);
|
|
3860
5567
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3861
5568
|
UploadCardWithFocusRing,
|
|
@@ -3867,14 +5574,13 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(ui.Card
|
|
|
3867
5574
|
shadow: 0,
|
|
3868
5575
|
tabIndex: 0,
|
|
3869
5576
|
onKeyDown: handleKeyDown,
|
|
3870
|
-
onKeyUp: handleKeyUp,
|
|
3871
5577
|
onPaste,
|
|
3872
5578
|
onDrop,
|
|
3873
5579
|
onDragEnter,
|
|
3874
5580
|
onDragLeave,
|
|
3875
5581
|
onDragOver,
|
|
3876
5582
|
children: [
|
|
3877
|
-
/* @__PURE__ */ jsxRuntime.jsx(HiddenInput$1, { ref: inputRef
|
|
5583
|
+
/* @__PURE__ */ jsxRuntime.jsx(HiddenInput$1, { ref: inputRef }),
|
|
3878
5584
|
children
|
|
3879
5585
|
]
|
|
3880
5586
|
}
|
|
@@ -4036,8 +5742,13 @@ function Uploader(props) {
|
|
|
4036
5742
|
case "reset":
|
|
4037
5743
|
case "complete":
|
|
4038
5744
|
return uploadRef.current?.unsubscribe(), uploadRef.current = null, uploadingDocumentId.current = null, INITIAL_STATE;
|
|
4039
|
-
case "error":
|
|
4040
|
-
|
|
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
|
+
}
|
|
4041
5752
|
default:
|
|
4042
5753
|
return prev;
|
|
4043
5754
|
}
|
|
@@ -4116,7 +5827,7 @@ function Uploader(props) {
|
|
|
4116
5827
|
}
|
|
4117
5828
|
},
|
|
4118
5829
|
complete: () => dispatch({ action: "complete" }),
|
|
4119
|
-
error: (error) => dispatch({ action: "error", error })
|
|
5830
|
+
error: (error) => dispatch({ action: "error", error, settings })
|
|
4120
5831
|
});
|
|
4121
5832
|
}, invalidFileToast = React.useCallback(() => {
|
|
4122
5833
|
toast.push({
|
|
@@ -4132,6 +5843,8 @@ function Uploader(props) {
|
|
|
4132
5843
|
input: { type: "file", files }
|
|
4133
5844
|
});
|
|
4134
5845
|
}, handlePaste = (event) => {
|
|
5846
|
+
if (event.target.closest("#vtt-url"))
|
|
5847
|
+
return;
|
|
4135
5848
|
event.preventDefault(), event.stopPropagation();
|
|
4136
5849
|
const url = (event.clipboardData || window.clipboardData)?.getData("text")?.trim();
|
|
4137
5850
|
if (!isValidUrl(url)) {
|
|
@@ -4165,11 +5878,11 @@ function Uploader(props) {
|
|
|
4165
5878
|
idx > -1 && dragEnteredEls.current.splice(idx, 1), dragEnteredEls.current.length === 0 && setDragState(null);
|
|
4166
5879
|
};
|
|
4167
5880
|
if (state.error !== null) {
|
|
4168
|
-
const error =
|
|
5881
|
+
const error = state.error;
|
|
4169
5882
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, direction: "column", justify: "center", align: "center", children: [
|
|
4170
5883
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 5, muted: !0, children: /* @__PURE__ */ jsxRuntime.jsx(icons.ErrorOutlineIcon, {}) }),
|
|
4171
5884
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Something went wrong" }),
|
|
4172
|
-
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 }),
|
|
4173
5886
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { text: "Upload another file", onClick: () => dispatch({ action: "reset" }) })
|
|
4174
5887
|
] });
|
|
4175
5888
|
}
|
|
@@ -4254,6 +5967,7 @@ function Uploader(props) {
|
|
|
4254
5967
|
props.dialogState === "select-video" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4255
5968
|
InputBrowser,
|
|
4256
5969
|
{
|
|
5970
|
+
config: props.config,
|
|
4257
5971
|
asset: props.asset,
|
|
4258
5972
|
onChange: props.onChange,
|
|
4259
5973
|
setDialogState: props.setDialogState
|
|
@@ -4500,6 +6214,7 @@ const muxVideoSchema = {
|
|
|
4500
6214
|
normalize_audio: !1,
|
|
4501
6215
|
defaultPublic: !0,
|
|
4502
6216
|
defaultSigned: !1,
|
|
6217
|
+
defaultDrm: !1,
|
|
4503
6218
|
tool: DEFAULT_TOOL_CONFIG,
|
|
4504
6219
|
allowedRolesForConfiguration: [],
|
|
4505
6220
|
acceptedMimeTypes: ["video/*", "audio/*"]
|