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.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useClient as useClient$1, createHookFromObservableFactory, useDocumentStore, collate, useDocumentValues, truncateString, useFormattedDuration, SanityDefaultPreview, useTimeAgo, TextWithTone, isRecord, getPreviewStateObservable, getPreviewValueWithFallback, DocumentPreviewPresence, useDocumentPreviewStore, useSchema, useDocumentPresence, PreviewCard, useCurrentUser, isReference, useProjectId, useDataset, PatchEvent, unset, setIfMissing, set, LinearProgress, FormField as FormField$2, definePlugin } from "sanity";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { ErrorOutlineIcon, InfoOutlineIcon, RetryIcon, CheckmarkCircleIcon, RetrieveIcon, SyncIcon, SortIcon,
|
|
4
|
-
import {
|
|
5
|
-
import React, { useState, useMemo, useCallback, useReducer, useId, memo, useRef, useEffect,
|
|
3
|
+
import { ErrorOutlineIcon, InfoOutlineIcon, RetryIcon, CheckmarkCircleIcon, RetrieveIcon, ChevronLeftIcon, ChevronRightIcon, SyncIcon, SortIcon, UploadIcon, TranslateIcon, DownloadIcon, AddIcon, ChevronUpIcon, ChevronDownIcon, TrashIcon, EditIcon, WarningOutlineIcon, PublishIcon, DocumentIcon, RevertIcon, SearchIcon, ClockIcon, CropIcon, CalendarIcon, TagIcon, CheckmarkIcon, LockIcon, PlayIcon, PlugIcon, EllipsisHorizontalIcon, ImageIcon, ResetIcon, WarningFilledIcon, DocumentVideoIcon } from "@sanity/icons";
|
|
4
|
+
import { Dialog, Stack, Card, Text, Button, useTheme_v2, Flex, Box, TextInput, Checkbox, Code, Inline, Spinner, Heading, Label as Label$1, MenuButton, Menu, MenuItem, useToast, Autocomplete, Tooltip, TabList, Tab, TabPanel, Grid, useClickOutsideEvent, Popover, MenuDivider, Radio, rem } from "@sanity/ui";
|
|
5
|
+
import React, { createContext, useContext, useState, useMemo, useCallback, useReducer, useId, memo, useRef, useEffect, Suspense, isValidElement, PureComponent, createElement, forwardRef } from "react";
|
|
6
6
|
import compact from "lodash/compact.js";
|
|
7
7
|
import toLower from "lodash/toLower.js";
|
|
8
8
|
import trim from "lodash/trim.js";
|
|
@@ -13,6 +13,7 @@ import { defer, timer, of, Observable, concat, throwError, from, Subject } from
|
|
|
13
13
|
import { styled, css } from "styled-components";
|
|
14
14
|
import { uuid } from "@sanity/uuid";
|
|
15
15
|
import { expand, concatMap, tap, switchMap, mergeMap, catchError, mergeMapTo, takeUntil } from "rxjs/operators";
|
|
16
|
+
import LanguagesList from "iso-639-1";
|
|
16
17
|
import MuxPlayer from "@mux/mux-player-react/lazy";
|
|
17
18
|
import { IntentLink } from "sanity/router";
|
|
18
19
|
import isNumber from "lodash/isNumber.js";
|
|
@@ -22,7 +23,6 @@ import useSWR from "swr";
|
|
|
22
23
|
import scrollIntoView from "scroll-into-view-if-needed";
|
|
23
24
|
import { UpChunk } from "@mux/upchunk";
|
|
24
25
|
import { isValidElementType } from "react-is";
|
|
25
|
-
import LanguagesList from "iso-639-1";
|
|
26
26
|
const ToolIcon = () => /* @__PURE__ */ jsx(
|
|
27
27
|
"svg",
|
|
28
28
|
{
|
|
@@ -35,7 +35,45 @@ const ToolIcon = () => /* @__PURE__ */ jsx(
|
|
|
35
35
|
xmlns: "http://www.w3.org/2000/svg",
|
|
36
36
|
children: /* @__PURE__ */ 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" })
|
|
37
37
|
}
|
|
38
|
-
),
|
|
38
|
+
), LOCAL_STORAGE_HAS_SHOWN_WARNING_KEY = "mux-plugin-has-shown-drm-playback-warning", DrmPlaybackWarningContext = createContext({
|
|
39
|
+
hasShownWarning: !1,
|
|
40
|
+
setHasWarnedAboutDrmPlayback: () => null
|
|
41
|
+
}), DrmPlaybackWarningContextProvider = ({
|
|
42
|
+
config,
|
|
43
|
+
children
|
|
44
|
+
}) => {
|
|
45
|
+
const hasWarned = (config?.disableDrmPlaybackWarning ?? !1) || window.localStorage.getItem(LOCAL_STORAGE_HAS_SHOWN_WARNING_KEY) === "true", [hasWarnedAboutDrmPlayback, setHasWarnedAboutDrmPlayback] = useState(hasWarned), setHasShownWarning = (b) => {
|
|
46
|
+
window.localStorage.setItem(LOCAL_STORAGE_HAS_SHOWN_WARNING_KEY, b.toString()), setHasWarnedAboutDrmPlayback(b);
|
|
47
|
+
};
|
|
48
|
+
return /* @__PURE__ */ jsx(
|
|
49
|
+
DrmPlaybackWarningContext.Provider,
|
|
50
|
+
{
|
|
51
|
+
value: {
|
|
52
|
+
hasShownWarning: hasWarnedAboutDrmPlayback,
|
|
53
|
+
setHasWarnedAboutDrmPlayback: setHasShownWarning
|
|
54
|
+
},
|
|
55
|
+
children
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}, useDrmPlaybackWarningContext = () => useContext(DrmPlaybackWarningContext), DRMWarningDialog = ({ onClose }) => {
|
|
59
|
+
const { setHasWarnedAboutDrmPlayback } = useDrmPlaybackWarningContext(), _onClose = () => {
|
|
60
|
+
setHasWarnedAboutDrmPlayback(!0), onClose();
|
|
61
|
+
};
|
|
62
|
+
return /* @__PURE__ */ jsx(
|
|
63
|
+
Dialog,
|
|
64
|
+
{
|
|
65
|
+
open: !0,
|
|
66
|
+
id: "drm-playback-warn",
|
|
67
|
+
onClose: _onClose,
|
|
68
|
+
header: "DRM Playback Warning",
|
|
69
|
+
footer: /* @__PURE__ */ jsx(Stack, { padding: 3, children: /* @__PURE__ */ jsx(Button, { mode: "ghost", tone: "primary", onClick: _onClose, text: "Ok" }) }),
|
|
70
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, padding: 3, children: [
|
|
71
|
+
/* @__PURE__ */ jsx(Card, { padding: [3, 3, 3], radius: 2, children: /* @__PURE__ */ jsx(Stack, { space: 3, children: /* @__PURE__ */ jsx(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." }) }) }),
|
|
72
|
+
/* @__PURE__ */ jsx(Card, { padding: [3, 3, 3], radius: 2, tone: "suggest", children: /* @__PURE__ */ jsx(Stack, { space: 3, children: /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "This is a one time warning. If it persists, you can disable it from your plugin configuration." }) }) })
|
|
73
|
+
] })
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}, SANITY_API_VERSION = "2024-03-05";
|
|
39
77
|
function useClient() {
|
|
40
78
|
return useClient$1({ apiVersion: SANITY_API_VERSION });
|
|
41
79
|
}
|
|
@@ -108,7 +146,7 @@ function useAssets() {
|
|
|
108
146
|
function useDialogState() {
|
|
109
147
|
return useState(!1);
|
|
110
148
|
}
|
|
111
|
-
function saveSecrets(client, token, secretKey, enableSignedUrls, signingKeyId, signingKeyPrivate) {
|
|
149
|
+
function saveSecrets(client, token, secretKey, enableSignedUrls, signingKeyId, signingKeyPrivate, drmConfigId) {
|
|
112
150
|
const doc = {
|
|
113
151
|
_id: "secrets.mux",
|
|
114
152
|
_type: "mux.apiKey",
|
|
@@ -116,9 +154,10 @@ function saveSecrets(client, token, secretKey, enableSignedUrls, signingKeyId, s
|
|
|
116
154
|
secretKey,
|
|
117
155
|
enableSignedUrls,
|
|
118
156
|
signingKeyId,
|
|
119
|
-
signingKeyPrivate
|
|
157
|
+
signingKeyPrivate,
|
|
158
|
+
drmConfigId
|
|
120
159
|
};
|
|
121
|
-
return client.createOrReplace(doc);
|
|
160
|
+
return doc.signingKeyId = enableSignedUrls ? signingKeyId : "", doc.signingKeyPrivate = enableSignedUrls ? signingKeyPrivate : "", client.createOrReplace(doc);
|
|
122
161
|
}
|
|
123
162
|
async function createSigningKeys(client) {
|
|
124
163
|
try {
|
|
@@ -171,7 +210,8 @@ const useSaveSecrets = (client, secrets) => useCallback(
|
|
|
171
210
|
async ({
|
|
172
211
|
token,
|
|
173
212
|
secretKey,
|
|
174
|
-
enableSignedUrls
|
|
213
|
+
enableSignedUrls,
|
|
214
|
+
drmConfigId
|
|
175
215
|
}) => {
|
|
176
216
|
let { signingKeyId, signingKeyPrivate } = secrets;
|
|
177
217
|
try {
|
|
@@ -181,7 +221,8 @@ const useSaveSecrets = (client, secrets) => useCallback(
|
|
|
181
221
|
secretKey,
|
|
182
222
|
enableSignedUrls,
|
|
183
223
|
signingKeyId,
|
|
184
|
-
signingKeyPrivate
|
|
224
|
+
signingKeyPrivate,
|
|
225
|
+
drmConfigId
|
|
185
226
|
), !(await testSecrets(client))?.status && token && secretKey)
|
|
186
227
|
throw new Error("Invalid secrets");
|
|
187
228
|
} catch (err) {
|
|
@@ -200,7 +241,8 @@ const useSaveSecrets = (client, secrets) => useCallback(
|
|
|
200
241
|
secretKey,
|
|
201
242
|
enableSignedUrls,
|
|
202
243
|
signingKeyId,
|
|
203
|
-
signingKeyPrivate
|
|
244
|
+
signingKeyPrivate,
|
|
245
|
+
drmConfigId ?? ""
|
|
204
246
|
);
|
|
205
247
|
} catch (err) {
|
|
206
248
|
throw console.log("Error while creating and saving signing key:", err?.message), err;
|
|
@@ -210,11 +252,19 @@ const useSaveSecrets = (client, secrets) => useCallback(
|
|
|
210
252
|
secretKey,
|
|
211
253
|
enableSignedUrls,
|
|
212
254
|
signingKeyId,
|
|
213
|
-
signingKeyPrivate
|
|
255
|
+
signingKeyPrivate,
|
|
256
|
+
drmConfigId
|
|
214
257
|
};
|
|
215
258
|
},
|
|
216
259
|
[client, secrets]
|
|
217
|
-
), 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 = [
|
|
260
|
+
), 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
|
+
"token",
|
|
262
|
+
"secretKey",
|
|
263
|
+
"enableSignedUrls",
|
|
264
|
+
"signingKeyId",
|
|
265
|
+
"signingKeyPrivate",
|
|
266
|
+
"drmConfigId"
|
|
267
|
+
], useSecretsDocumentValues = () => {
|
|
218
268
|
const { error, isLoading, value } = useDocumentValues(
|
|
219
269
|
muxSecretsDocumentId,
|
|
220
270
|
path$1
|
|
@@ -224,7 +274,8 @@ const useSaveSecrets = (client, secrets) => useCallback(
|
|
|
224
274
|
secretKey: value?.secretKey || null,
|
|
225
275
|
enableSignedUrls: value?.enableSignedUrls || !1,
|
|
226
276
|
signingKeyId: value?.signingKeyId || null,
|
|
227
|
-
signingKeyPrivate: value?.signingKeyPrivate || null
|
|
277
|
+
signingKeyPrivate: value?.signingKeyPrivate || null,
|
|
278
|
+
drmConfigId: value?.drmConfigId || null
|
|
228
279
|
};
|
|
229
280
|
return {
|
|
230
281
|
isInitialSetup: !exists,
|
|
@@ -234,7 +285,7 @@ const useSaveSecrets = (client, secrets) => useCallback(
|
|
|
234
285
|
}, [value]);
|
|
235
286
|
return { error, isLoading, value: cache };
|
|
236
287
|
};
|
|
237
|
-
function init({ token, secretKey, enableSignedUrls }) {
|
|
288
|
+
function init({ token, secretKey, enableSignedUrls, drmConfigId }) {
|
|
238
289
|
return {
|
|
239
290
|
submitting: !1,
|
|
240
291
|
error: null,
|
|
@@ -242,7 +293,8 @@ function init({ token, secretKey, enableSignedUrls }) {
|
|
|
242
293
|
// This ensures the `dirty` check works correctly
|
|
243
294
|
token: token ?? "",
|
|
244
295
|
secretKey: secretKey ?? "",
|
|
245
|
-
enableSignedUrls: enableSignedUrls ?? !1
|
|
296
|
+
enableSignedUrls: enableSignedUrls ?? !1,
|
|
297
|
+
drmConfigId: drmConfigId ?? ""
|
|
246
298
|
};
|
|
247
299
|
}
|
|
248
300
|
function reducer(state, action) {
|
|
@@ -270,7 +322,8 @@ function readSecrets(client) {
|
|
|
270
322
|
secretKey,
|
|
271
323
|
enableSignedUrls,
|
|
272
324
|
signingKeyId,
|
|
273
|
-
signingKeyPrivate
|
|
325
|
+
signingKeyPrivate,
|
|
326
|
+
drmConfigId
|
|
274
327
|
}`,
|
|
275
328
|
{ _id }
|
|
276
329
|
);
|
|
@@ -279,7 +332,8 @@ function readSecrets(client) {
|
|
|
279
332
|
secretKey: data?.secretKey || null,
|
|
280
333
|
enableSignedUrls: !!data?.enableSignedUrls || !1,
|
|
281
334
|
signingKeyId: data?.signingKeyId || null,
|
|
282
|
-
signingKeyPrivate: data?.signingKeyPrivate || null
|
|
335
|
+
signingKeyPrivate: data?.signingKeyPrivate || null,
|
|
336
|
+
drmConfigId: data?.drmConfigId || null
|
|
283
337
|
};
|
|
284
338
|
}, [cacheNs, _id, projectId, dataset]);
|
|
285
339
|
}
|
|
@@ -342,20 +396,20 @@ function FormField(props) {
|
|
|
342
396
|
] });
|
|
343
397
|
}
|
|
344
398
|
var FormField$1 = memo(FormField);
|
|
345
|
-
const fieldNames = ["token", "secretKey", "enableSignedUrls"];
|
|
399
|
+
const fieldNames = ["token", "secretKey", "enableSignedUrls", "drmConfigId"];
|
|
346
400
|
function ConfigureApiDialog({ secrets, setDialogState }) {
|
|
347
401
|
const client = useClient(), [state, dispatch] = useSecretsFormState(secrets), hasSecretsInitially = useMemo(() => secrets.token && secrets.secretKey, [secrets]), handleClose = useCallback(() => setDialogState(!1), [setDialogState]), dirty = useMemo(
|
|
348
|
-
() => secrets.token !== state.token || secrets.secretKey !== state.secretKey || secrets.enableSignedUrls !== state.enableSignedUrls,
|
|
402
|
+
() => secrets.token !== state.token || secrets.secretKey !== state.secretKey || secrets.enableSignedUrls !== state.enableSignedUrls || secrets.drmConfigId !== state.drmConfigId,
|
|
349
403
|
[secrets, state]
|
|
350
|
-
), id = `ConfigureApi${useId()}`, [tokenId, secretKeyId, enableSignedUrlsId] = useMemo(
|
|
404
|
+
), id = `ConfigureApi${useId()}`, [tokenId, secretKeyId, enableSignedUrlsId, drmConfigIdId] = useMemo(
|
|
351
405
|
() => fieldNames.map((field) => `${id}-${field}`),
|
|
352
406
|
[id]
|
|
353
407
|
), firstField = useRef(null), handleSaveSecrets = useSaveSecrets(client, secrets), saving = useRef(!1), handleSubmit = useCallback(
|
|
354
408
|
(event) => {
|
|
355
409
|
if (event.preventDefault(), !saving.current && event.currentTarget.reportValidity()) {
|
|
356
410
|
saving.current = !0, dispatch({ type: "submit" });
|
|
357
|
-
const { token, secretKey, enableSignedUrls } = state;
|
|
358
|
-
handleSaveSecrets({ token, secretKey, enableSignedUrls }).then((savedSecrets) => {
|
|
411
|
+
const { token, secretKey, enableSignedUrls, drmConfigId } = state;
|
|
412
|
+
handleSaveSecrets({ token, secretKey, enableSignedUrls, drmConfigId }).then((savedSecrets) => {
|
|
359
413
|
const { projectId, dataset } = client.config();
|
|
360
414
|
clear([cacheNs, _id, projectId, dataset]), preload(() => Promise.resolve(savedSecrets), [cacheNs, _id, projectId, dataset]), setDialogState(!1);
|
|
361
415
|
}).catch((err) => dispatch({ type: "error", payload: err.message })).finally(() => {
|
|
@@ -388,6 +442,14 @@ function ConfigureApiDialog({ secrets, setDialogState }) {
|
|
|
388
442
|
});
|
|
389
443
|
},
|
|
390
444
|
[dispatch]
|
|
445
|
+
), handleChangeDrmConfigId = useCallback(
|
|
446
|
+
(event) => {
|
|
447
|
+
dispatch({
|
|
448
|
+
type: "change",
|
|
449
|
+
payload: { name: "drmConfigId", value: event.currentTarget.value }
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
[dispatch]
|
|
391
453
|
);
|
|
392
454
|
return useEffect(() => {
|
|
393
455
|
firstField.current && firstField.current.focus();
|
|
@@ -474,6 +536,45 @@ function ConfigureApiDialog({ secrets, setDialogState }) {
|
|
|
474
536
|
] })
|
|
475
537
|
] }) }) : null
|
|
476
538
|
] }),
|
|
539
|
+
/* @__PURE__ */ jsx(FormField$1, { title: "DRM Configuration ID", inputId: drmConfigIdId, children: /* @__PURE__ */ jsx(
|
|
540
|
+
TextInput,
|
|
541
|
+
{
|
|
542
|
+
id: drmConfigIdId,
|
|
543
|
+
onChange: handleChangeDrmConfigId,
|
|
544
|
+
type: "text",
|
|
545
|
+
value: state.drmConfigId ?? "",
|
|
546
|
+
required: !1
|
|
547
|
+
}
|
|
548
|
+
) }),
|
|
549
|
+
/* @__PURE__ */ jsx(Card, { padding: [3, 3, 3], radius: 2, shadow: 1, tone: "neutral", children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
550
|
+
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
551
|
+
"DRM (Digital Rights Management) provides an extra layer of content security for video content streamed from Mux. For additional information check out our",
|
|
552
|
+
" ",
|
|
553
|
+
/* @__PURE__ */ jsx(
|
|
554
|
+
"a",
|
|
555
|
+
{
|
|
556
|
+
href: "https://www.mux.com/docs/guides/protect-videos-with-drm#play-drm-protected-videos",
|
|
557
|
+
target: "_blank",
|
|
558
|
+
rel: "noopener noreferrer",
|
|
559
|
+
children: "DRM Guide"
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
"."
|
|
563
|
+
] }),
|
|
564
|
+
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
565
|
+
/* @__PURE__ */ jsx(
|
|
566
|
+
"a",
|
|
567
|
+
{
|
|
568
|
+
href: "https://www.mux.com/support/human",
|
|
569
|
+
target: "_blank",
|
|
570
|
+
rel: "noopener noreferrer",
|
|
571
|
+
children: "Contact us"
|
|
572
|
+
}
|
|
573
|
+
),
|
|
574
|
+
" ",
|
|
575
|
+
"to get started using DRM."
|
|
576
|
+
] })
|
|
577
|
+
] }) }),
|
|
477
578
|
/* @__PURE__ */ jsxs(Inline, { space: 2, children: [
|
|
478
579
|
/* @__PURE__ */ jsx(
|
|
479
580
|
Button,
|
|
@@ -567,6 +668,51 @@ function listAssets(client, options) {
|
|
|
567
668
|
query
|
|
568
669
|
});
|
|
569
670
|
}
|
|
671
|
+
function addTextTrackFromUrl(client, assetId, vttUrl, options) {
|
|
672
|
+
const { dataset } = client.config();
|
|
673
|
+
return client.request({
|
|
674
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks`,
|
|
675
|
+
withCredentials: !0,
|
|
676
|
+
method: "POST",
|
|
677
|
+
body: {
|
|
678
|
+
url: vttUrl,
|
|
679
|
+
type: "text",
|
|
680
|
+
language_code: options.language_code,
|
|
681
|
+
name: options.name,
|
|
682
|
+
text_type: options.text_type || "subtitles"
|
|
683
|
+
},
|
|
684
|
+
headers: {
|
|
685
|
+
"Content-Type": "application/json"
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function generateSubtitles(client, assetId, audioTrackId, options) {
|
|
690
|
+
const { dataset } = client.config();
|
|
691
|
+
return client.request({
|
|
692
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${audioTrackId}/generate-subtitles`,
|
|
693
|
+
withCredentials: !0,
|
|
694
|
+
method: "POST",
|
|
695
|
+
body: {
|
|
696
|
+
generated_subtitles: [
|
|
697
|
+
{
|
|
698
|
+
language_code: options.language_code,
|
|
699
|
+
name: options.name
|
|
700
|
+
}
|
|
701
|
+
]
|
|
702
|
+
},
|
|
703
|
+
headers: {
|
|
704
|
+
"Content-Type": "application/json"
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
function deleteTextTrack(client, assetId, trackId) {
|
|
709
|
+
const { dataset } = client.config();
|
|
710
|
+
return client.request({
|
|
711
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${trackId}`,
|
|
712
|
+
withCredentials: !0,
|
|
713
|
+
method: "DELETE"
|
|
714
|
+
});
|
|
715
|
+
}
|
|
570
716
|
const ASSETS_PER_PAGE = 100;
|
|
571
717
|
async function fetchMuxAssetsPage(client, cursor) {
|
|
572
718
|
try {
|
|
@@ -732,6 +878,35 @@ function useInView(ref, options = {}) {
|
|
|
732
878
|
};
|
|
733
879
|
}, [options, ref]), inView;
|
|
734
880
|
}
|
|
881
|
+
function getPlaybackId(asset, priority = ["drm", "signed", "public"]) {
|
|
882
|
+
try {
|
|
883
|
+
if (!asset)
|
|
884
|
+
throw new TypeError("Tried to get playback Id with no asset");
|
|
885
|
+
const playbackIds = asset.data?.playback_ids;
|
|
886
|
+
if (playbackIds && playbackIds.length > 0) {
|
|
887
|
+
for (const policy of priority) {
|
|
888
|
+
const match = playbackIds.find((entry) => entry.policy === policy);
|
|
889
|
+
if (match)
|
|
890
|
+
return match.id;
|
|
891
|
+
}
|
|
892
|
+
return playbackIds[0].id;
|
|
893
|
+
}
|
|
894
|
+
throw new TypeError("Missing playbackId");
|
|
895
|
+
} catch (e) {
|
|
896
|
+
throw console.error("Asset is missing a playbackId", { asset }, e), e;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function getPlaybackPolicy(asset) {
|
|
900
|
+
return asset.data?.playback_ids?.find(
|
|
901
|
+
(playbackId) => getPlaybackId(asset, ["drm", "signed", "public"]) === playbackId.id
|
|
902
|
+
) ?? { id: "", policy: "public" };
|
|
903
|
+
}
|
|
904
|
+
function getPlaybackPolicyById(asset, playbackId) {
|
|
905
|
+
return asset.data?.playback_ids?.find((entry) => playbackId === entry.id);
|
|
906
|
+
}
|
|
907
|
+
function hasPlaybackPolicy(data, policy) {
|
|
908
|
+
return data.advanced_playback_policies && data.advanced_playback_policies.find((p) => p.policy === policy) || data.playback_policy?.find((p) => p === policy);
|
|
909
|
+
}
|
|
735
910
|
function generateJwt(client, playbackId, aud, payload) {
|
|
736
911
|
const { signingKeyId, signingKeyPrivate } = readSecrets(client);
|
|
737
912
|
if (!signingKeyId)
|
|
@@ -752,20 +927,13 @@ function generateJwt(client, playbackId, aud, payload) {
|
|
|
752
927
|
}
|
|
753
928
|
);
|
|
754
929
|
}
|
|
755
|
-
function getPlaybackId(asset) {
|
|
756
|
-
if (!asset?.playbackId)
|
|
757
|
-
throw console.error("Asset is missing a playbackId", { asset }), new TypeError("Missing playbackId");
|
|
758
|
-
return asset.playbackId;
|
|
759
|
-
}
|
|
760
|
-
function getPlaybackPolicy(asset) {
|
|
761
|
-
return asset.data?.playback_ids?.find((playbackId) => asset.playbackId === playbackId.id)?.policy ?? "public";
|
|
762
|
-
}
|
|
763
930
|
function createUrlParamsObject(client, asset, params, audience) {
|
|
764
931
|
const playbackId = getPlaybackId(asset);
|
|
765
932
|
let searchParams = new URLSearchParams(
|
|
766
933
|
JSON.parse(JSON.stringify(params, (_, v) => v ?? void 0))
|
|
767
934
|
);
|
|
768
|
-
|
|
935
|
+
const playbackPolicy = getPlaybackPolicyById(asset, playbackId)?.policy;
|
|
936
|
+
if (playbackPolicy === "signed" || playbackPolicy === "drm") {
|
|
769
937
|
const token = generateJwt(client, playbackId, audience, params);
|
|
770
938
|
searchParams = new URLSearchParams({ token });
|
|
771
939
|
}
|
|
@@ -796,6 +964,15 @@ function getPosterSrc({
|
|
|
796
964
|
const { playbackId, searchParams } = createUrlParamsObject(client, asset, params, "t");
|
|
797
965
|
return `https://image.mux.com/${playbackId}/thumbnail.png?${searchParams}`;
|
|
798
966
|
}
|
|
967
|
+
function tryWithSuspend(block, onError) {
|
|
968
|
+
try {
|
|
969
|
+
return block();
|
|
970
|
+
} catch (errorOrPromise) {
|
|
971
|
+
if (errorOrPromise instanceof Promise)
|
|
972
|
+
throw errorOrPromise;
|
|
973
|
+
return onError ? onError(errorOrPromise) : void 0;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
799
976
|
const Image = styled.img`
|
|
800
977
|
transition: opacity 0.175s ease-out 0s;
|
|
801
978
|
display: block;
|
|
@@ -813,22 +990,22 @@ function VideoThumbnail({
|
|
|
813
990
|
width,
|
|
814
991
|
staticImage = !1
|
|
815
992
|
}) {
|
|
816
|
-
const ref = useRef(null), inView = useInView(ref),
|
|
817
|
-
|
|
993
|
+
const posterWidth = width || 250, client = useClient(), ref = useRef(null), inView = useInView(ref), [status, setStatus] = useState("loading"), [error, setError] = useState(null), thumbnailSrc = useMemo(() => tryWithSuspend(
|
|
994
|
+
() => {
|
|
818
995
|
let thumbnail;
|
|
819
996
|
return staticImage ? thumbnail = getPosterSrc({ asset, client, width: posterWidth }) : thumbnail = getAnimatedPosterSrc({ asset, client, width: posterWidth }), thumbnail;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
|
|
997
|
+
},
|
|
998
|
+
(err) => {
|
|
999
|
+
handleError(err.message);
|
|
823
1000
|
}
|
|
824
|
-
|
|
1001
|
+
), [asset, client, posterWidth, staticImage]);
|
|
825
1002
|
function handleLoad() {
|
|
826
1003
|
setStatus("loaded");
|
|
827
1004
|
}
|
|
828
|
-
function handleError() {
|
|
829
|
-
setStatus("error");
|
|
1005
|
+
function handleError(err) {
|
|
1006
|
+
setStatus("error"), setError(err || "Failed loading thumbnail");
|
|
830
1007
|
}
|
|
831
|
-
return /* @__PURE__ */ jsx(
|
|
1008
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("span", { children: "Preparing thumbnail" }), children: /* @__PURE__ */ jsx(
|
|
832
1009
|
Card,
|
|
833
1010
|
{
|
|
834
1011
|
style: {
|
|
@@ -869,23 +1046,23 @@ function VideoThumbnail({
|
|
|
869
1046
|
},
|
|
870
1047
|
children: [
|
|
871
1048
|
/* @__PURE__ */ jsx(Text, { size: 4, muted: !0, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { fontSize: "1.75em" } }) }),
|
|
872
|
-
/* @__PURE__ */ jsx(Text, { muted: !0, align: "center", children:
|
|
1049
|
+
/* @__PURE__ */ jsx(Text, { muted: !0, align: "center", children: error })
|
|
873
1050
|
]
|
|
874
1051
|
}
|
|
875
1052
|
),
|
|
876
1053
|
/* @__PURE__ */ jsx(
|
|
877
1054
|
Image,
|
|
878
1055
|
{
|
|
879
|
-
src,
|
|
1056
|
+
src: thumbnailSrc ?? void 0,
|
|
880
1057
|
alt: `Preview for ${staticImage ? "image" : "video"} ${asset.filename || asset.assetId}`,
|
|
881
1058
|
onLoad: handleLoad,
|
|
882
|
-
onError: handleError,
|
|
1059
|
+
onError: () => handleError(),
|
|
883
1060
|
style: { opacity: status === "loaded" ? 1 : 0 }
|
|
884
1061
|
}
|
|
885
1062
|
)
|
|
886
1063
|
] }) : null
|
|
887
1064
|
}
|
|
888
|
-
);
|
|
1065
|
+
) });
|
|
889
1066
|
}
|
|
890
1067
|
const MissingAssetCheckbox = styled(Checkbox)`
|
|
891
1068
|
position: static !important;
|
|
@@ -1104,6 +1281,46 @@ function ImportVideosFromMux() {
|
|
|
1104
1281
|
if (importAssets.hasSecrets)
|
|
1105
1282
|
return importAssets.dialogOpen ? /* @__PURE__ */ jsx(ImportVideosDialog, { ...importAssets }) : /* @__PURE__ */ jsx(Button, { mode: "bleed", text: "Import from Mux", onClick: importAssets.openDialog });
|
|
1106
1283
|
}
|
|
1284
|
+
const PageSelector = (props) => {
|
|
1285
|
+
const page = props.page, setPage = props.setPage;
|
|
1286
|
+
return useEffect(() => {
|
|
1287
|
+
const clamped = Math.min(props.total - 1, Math.max(0, page));
|
|
1288
|
+
page !== clamped && setPage(clamped);
|
|
1289
|
+
}, [page, props.total, setPage]), /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1290
|
+
/* @__PURE__ */ jsx(
|
|
1291
|
+
Button,
|
|
1292
|
+
{
|
|
1293
|
+
icon: ChevronLeftIcon,
|
|
1294
|
+
mode: "bleed",
|
|
1295
|
+
padding: 3,
|
|
1296
|
+
style: { cursor: "pointer" },
|
|
1297
|
+
disabled: page <= 0,
|
|
1298
|
+
onClick: () => {
|
|
1299
|
+
setPage((p) => Math.min(props.total - 1, Math.max(0, p - 1)));
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
),
|
|
1303
|
+
/* @__PURE__ */ jsxs(Label$1, { muted: !0, children: [
|
|
1304
|
+
"Page ",
|
|
1305
|
+
page + 1,
|
|
1306
|
+
"/",
|
|
1307
|
+
props.total
|
|
1308
|
+
] }),
|
|
1309
|
+
/* @__PURE__ */ jsx(
|
|
1310
|
+
Button,
|
|
1311
|
+
{
|
|
1312
|
+
icon: ChevronRightIcon,
|
|
1313
|
+
mode: "bleed",
|
|
1314
|
+
padding: 3,
|
|
1315
|
+
style: { cursor: "pointer" },
|
|
1316
|
+
disabled: page >= props.total - 1,
|
|
1317
|
+
onClick: () => {
|
|
1318
|
+
setPage((p) => Math.min(props.total - 1, Math.max(0, p + 1)));
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
)
|
|
1322
|
+
] });
|
|
1323
|
+
};
|
|
1107
1324
|
function useResyncMuxMetadata() {
|
|
1108
1325
|
const documentStore = useDocumentStore(), client = useClient$1({
|
|
1109
1326
|
apiVersion: SANITY_API_VERSION
|
|
@@ -1379,75 +1596,1290 @@ function StopWatchIcon(props) {
|
|
|
1379
1596
|
}
|
|
1380
1597
|
);
|
|
1381
1598
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1599
|
+
function extractErrorMessage(error, defaultMessage = "Failed to process request") {
|
|
1600
|
+
let message = "";
|
|
1601
|
+
if (error && typeof error == "object") {
|
|
1602
|
+
const err = error;
|
|
1603
|
+
message = err.response?.body?.message || err.message || "";
|
|
1604
|
+
} else typeof error == "string" && (message = error);
|
|
1605
|
+
if (!message)
|
|
1606
|
+
return defaultMessage;
|
|
1607
|
+
const match = message.match(/\(([^)]+)\)/);
|
|
1608
|
+
if (match && match[1])
|
|
1609
|
+
return match[1];
|
|
1610
|
+
if (message.includes("responded with")) {
|
|
1611
|
+
const parts = message.split("(");
|
|
1612
|
+
if (parts.length > 1)
|
|
1613
|
+
return parts[parts.length - 1].replace(")", "").trim();
|
|
1395
1614
|
}
|
|
1396
|
-
return
|
|
1615
|
+
return message;
|
|
1397
1616
|
}
|
|
1398
|
-
function
|
|
1617
|
+
async function pollTrackStatus(options) {
|
|
1399
1618
|
const {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1619
|
+
client,
|
|
1620
|
+
assetId,
|
|
1621
|
+
trackName,
|
|
1622
|
+
trackLanguageCode,
|
|
1623
|
+
maxAttempts = 10,
|
|
1624
|
+
onTrackFound,
|
|
1625
|
+
onTrackErrored,
|
|
1626
|
+
onTrackReady
|
|
1627
|
+
} = options, trimmedName = trackName.trim(), trimmedLanguageCode = trackLanguageCode.trim();
|
|
1628
|
+
let newTrack, attempts = 0, trackFound = !1;
|
|
1629
|
+
const findTrack = (textTracks) => {
|
|
1630
|
+
let foundTrack = textTracks.find(
|
|
1631
|
+
(track) => track.name === trimmedName && track.language_code === trimmedLanguageCode
|
|
1632
|
+
);
|
|
1633
|
+
return foundTrack || (foundTrack = textTracks.find((track) => track.language_code === trimmedLanguageCode)), !foundTrack && textTracks.length > 0 && (foundTrack = textTracks[textTracks.length - 1]), foundTrack;
|
|
1634
|
+
};
|
|
1635
|
+
for (; attempts < maxAttempts; ) {
|
|
1636
|
+
try {
|
|
1637
|
+
attempts > 0 && await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1638
|
+
const textTracks = (await getAsset(client, assetId)).data.tracks?.filter((track) => track.type === "text") || [], foundTrack = findTrack(textTracks);
|
|
1639
|
+
if (!foundTrack) {
|
|
1640
|
+
attempts++;
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
if (trackFound = !0, newTrack = foundTrack, onTrackFound && onTrackFound(foundTrack), foundTrack.status === "ready") {
|
|
1644
|
+
onTrackReady && onTrackReady(foundTrack);
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
if (foundTrack.status === "errored")
|
|
1648
|
+
return onTrackErrored && onTrackErrored(foundTrack), {
|
|
1649
|
+
track: foundTrack,
|
|
1650
|
+
found: !0,
|
|
1651
|
+
status: "errored"
|
|
1652
|
+
};
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
console.error("Failed to fetch updated asset:", error);
|
|
1655
|
+
}
|
|
1656
|
+
attempts++;
|
|
1657
|
+
}
|
|
1658
|
+
return !newTrack || !trackFound ? {
|
|
1659
|
+
track: void 0,
|
|
1660
|
+
found: !1,
|
|
1661
|
+
status: "not-found"
|
|
1662
|
+
} : newTrack.status === "preparing" ? {
|
|
1663
|
+
track: newTrack,
|
|
1664
|
+
found: !0,
|
|
1665
|
+
status: "preparing"
|
|
1666
|
+
} : {
|
|
1667
|
+
track: newTrack,
|
|
1668
|
+
found: !0,
|
|
1669
|
+
status: "ready"
|
|
1670
|
+
};
|
|
1412
1671
|
}
|
|
1413
|
-
function
|
|
1414
|
-
|
|
1415
|
-
|
|
1672
|
+
async function downloadVttFile(client, asset, track) {
|
|
1673
|
+
if (!track.id)
|
|
1674
|
+
throw new Error("Track ID is missing");
|
|
1675
|
+
if (track.status !== "ready")
|
|
1676
|
+
throw new Error(`Track is not ready yet. Status: ${track.status}`);
|
|
1677
|
+
if (!asset.assetId)
|
|
1678
|
+
throw new Error("Asset ID is required");
|
|
1679
|
+
const playbackId = getPlaybackId(asset);
|
|
1680
|
+
if (!playbackId)
|
|
1681
|
+
throw new Error("Playback ID is required");
|
|
1682
|
+
const playbackPolicy = getPlaybackPolicy(asset)?.policy;
|
|
1683
|
+
let downloadUrl = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
1684
|
+
if (playbackPolicy === "signed" || playbackPolicy === "drm") {
|
|
1685
|
+
const token = generateJwt(client, playbackId, "v");
|
|
1686
|
+
downloadUrl += `?token=${token}`;
|
|
1687
|
+
}
|
|
1688
|
+
const response = await fetch(downloadUrl);
|
|
1689
|
+
if (!response.ok)
|
|
1690
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
1691
|
+
const blob = await response.blob(), blobUrl = URL.createObjectURL(blob), link = document.createElement("a");
|
|
1692
|
+
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);
|
|
1416
1693
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1694
|
+
const SUPPORTED_MUX_LANGUAGES = [
|
|
1695
|
+
{ label: "English", code: "en", state: "Stable" },
|
|
1696
|
+
{ label: "Spanish", code: "es", state: "Stable" },
|
|
1697
|
+
{ label: "Italian", code: "it", state: "Stable" },
|
|
1698
|
+
{ label: "Portuguese", code: "pt", state: "Stable" },
|
|
1699
|
+
{ label: "German", code: "de", state: "Stable" },
|
|
1700
|
+
{ label: "French", code: "fr", state: "Stable" },
|
|
1701
|
+
{ label: "Polish", code: "pl", state: "Beta" },
|
|
1702
|
+
{ label: "Russian", code: "ru", state: "Beta" },
|
|
1703
|
+
{ label: "Dutch", code: "nl", state: "Beta" },
|
|
1704
|
+
{ label: "Catalan", code: "ca", state: "Beta" },
|
|
1705
|
+
{ label: "Turkish", code: "tr", state: "Beta" },
|
|
1706
|
+
{ label: "Swedish", code: "sv", state: "Beta" },
|
|
1707
|
+
{ label: "Ukrainian", code: "uk", state: "Beta" },
|
|
1708
|
+
{ label: "Norwegian", code: "no", state: "Beta" },
|
|
1709
|
+
{ label: "Finnish", code: "fi", state: "Beta" },
|
|
1710
|
+
{ label: "Slovak", code: "sk", state: "Beta" },
|
|
1711
|
+
{ label: "Greek", code: "el", state: "Beta" },
|
|
1712
|
+
{ label: "Czech", code: "cs", state: "Beta" },
|
|
1713
|
+
{ label: "Croatian", code: "hr", state: "Beta" },
|
|
1714
|
+
{ label: "Danish", code: "da", state: "Beta" },
|
|
1715
|
+
{ label: "Romanian", code: "ro", state: "Beta" },
|
|
1716
|
+
{ label: "Bulgarian", code: "bg", state: "Beta" }
|
|
1717
|
+
];
|
|
1718
|
+
function isCustomTextTrack(track) {
|
|
1719
|
+
return track.type !== "autogenerated";
|
|
1419
1720
|
}
|
|
1420
|
-
function
|
|
1421
|
-
|
|
1422
|
-
return hh * 3600 + mm * 60 + ss;
|
|
1721
|
+
function isAutogeneratedTrack(track) {
|
|
1722
|
+
return track.type === "autogenerated";
|
|
1423
1723
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1724
|
+
const LANGUAGE_OPTIONS$1 = LanguagesList.getAllCodes().map((code) => ({
|
|
1725
|
+
value: code,
|
|
1726
|
+
label: LanguagesList.getNativeName(code)
|
|
1727
|
+
})), MUX_LANGUAGE_OPTIONS = SUPPORTED_MUX_LANGUAGES.map((lang) => ({
|
|
1728
|
+
value: lang.code,
|
|
1729
|
+
label: lang.label
|
|
1730
|
+
}));
|
|
1731
|
+
function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
1732
|
+
const client = useClient(), toast = useToast(), dialogId = `AddCaptionDialog${useId()}`, [isAutogenerated, setIsAutogenerated] = useState(!1), [vttUrl, setVttUrl] = useState(""), [languageCode, setLanguageCode] = useState(""), [selectedLanguage, setSelectedLanguage] = useState(
|
|
1733
|
+
null
|
|
1734
|
+
), [name2, setName] = useState(""), [isSubmitting, setIsSubmitting] = useState(!1), [selectedFile, setSelectedFile] = useState(null), fileInputRef = useRef(null), uploadVttFile = async (file) => (await client.assets.upload("file", file, {
|
|
1735
|
+
filename: file.name
|
|
1736
|
+
})).url, handleAddTrackFromUrl = async () => {
|
|
1737
|
+
if (!asset.assetId)
|
|
1738
|
+
throw new Error("Asset ID is required");
|
|
1739
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim();
|
|
1740
|
+
let vttUrlToUse = vttUrl.trim();
|
|
1741
|
+
if (selectedFile)
|
|
1742
|
+
try {
|
|
1743
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
1744
|
+
} catch (uploadError) {
|
|
1745
|
+
throw toast.push({
|
|
1746
|
+
title: "Failed to upload VTT file",
|
|
1747
|
+
status: "error",
|
|
1748
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
1749
|
+
}), setIsSubmitting(!1), uploadError;
|
|
1750
|
+
}
|
|
1751
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
1752
|
+
language_code: trimmedLanguageCode,
|
|
1753
|
+
name: trimmedName,
|
|
1754
|
+
text_type: "subtitles"
|
|
1755
|
+
});
|
|
1756
|
+
const result = await pollTrackStatus({
|
|
1757
|
+
client,
|
|
1758
|
+
assetId: asset.assetId,
|
|
1759
|
+
trackName: trimmedName,
|
|
1760
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
1761
|
+
onTrackErrored: (track) => {
|
|
1762
|
+
const errorMessage = track.error?.messages?.[0] || track.error?.type || "The track failed to download from the provided URL";
|
|
1763
|
+
toast.push({
|
|
1764
|
+
title: "Caption track failed",
|
|
1765
|
+
status: "error",
|
|
1766
|
+
description: errorMessage
|
|
1767
|
+
}), onAdd(track), onClose();
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
if (!result.found || !result.track) {
|
|
1771
|
+
toast.push({
|
|
1772
|
+
title: "Caption track may have been added",
|
|
1773
|
+
status: "warning",
|
|
1774
|
+
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."
|
|
1775
|
+
}), onClose();
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
if (result.status !== "errored") {
|
|
1779
|
+
if (result.status === "preparing") {
|
|
1780
|
+
toast.push({
|
|
1781
|
+
title: "Caption track is processing",
|
|
1782
|
+
status: "info",
|
|
1783
|
+
description: "The track was created and is being processed. It will appear in the list shortly."
|
|
1784
|
+
}), onAdd(result.track), onClose();
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
toast.push({
|
|
1788
|
+
title: "Caption track added",
|
|
1789
|
+
status: "success",
|
|
1790
|
+
description: "Caption track added successfully"
|
|
1791
|
+
}), onAdd(result.track), onClose();
|
|
1792
|
+
}
|
|
1793
|
+
}, handleGenerateSubtitles = async () => {
|
|
1794
|
+
if (!asset.assetId)
|
|
1795
|
+
throw new Error("Asset ID is required");
|
|
1796
|
+
const audioTrack = (await getAsset(client, asset.assetId)).data.tracks?.find((track) => track.type === "audio");
|
|
1797
|
+
if (!audioTrack || !audioTrack.id)
|
|
1798
|
+
throw toast.push({
|
|
1799
|
+
title: "No audio track found",
|
|
1800
|
+
status: "error",
|
|
1801
|
+
description: "The asset does not have an audio track. Auto-generated subtitles require an audio track."
|
|
1802
|
+
}), new Error("No audio track found");
|
|
1803
|
+
await generateSubtitles(client, asset.assetId, audioTrack.id, {
|
|
1804
|
+
language_code: languageCode.trim(),
|
|
1805
|
+
name: name2.trim()
|
|
1806
|
+
});
|
|
1807
|
+
const mockTrack = {
|
|
1808
|
+
type: "text",
|
|
1809
|
+
id: `generating-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
1810
|
+
text_type: "subtitles",
|
|
1811
|
+
text_source: "generated_live",
|
|
1812
|
+
language_code: languageCode.trim(),
|
|
1813
|
+
name: name2.trim(),
|
|
1814
|
+
status: "preparing"
|
|
1815
|
+
};
|
|
1816
|
+
toast.push({
|
|
1817
|
+
title: "Generating subtitles",
|
|
1818
|
+
status: "success",
|
|
1819
|
+
description: "This may take a few minutes"
|
|
1820
|
+
}), onAdd(mockTrack), onClose();
|
|
1821
|
+
}, handleSubmit = async () => {
|
|
1822
|
+
if (!isAutogenerated) {
|
|
1823
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
1824
|
+
toast.push({
|
|
1825
|
+
title: "VTT file or URL required",
|
|
1826
|
+
status: "error",
|
|
1827
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
1828
|
+
});
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
if (vttUrl.trim() && !selectedFile)
|
|
1832
|
+
try {
|
|
1833
|
+
new URL(vttUrl.trim());
|
|
1834
|
+
} catch {
|
|
1835
|
+
toast.push({
|
|
1836
|
+
title: "Invalid URL",
|
|
1837
|
+
status: "error",
|
|
1838
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
1839
|
+
});
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (!name2.trim()) {
|
|
1844
|
+
toast.push({
|
|
1845
|
+
title: "Audio name required",
|
|
1846
|
+
status: "error",
|
|
1847
|
+
description: "Please enter an audio name for this caption track"
|
|
1848
|
+
});
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
if (!languageCode.trim()) {
|
|
1852
|
+
toast.push({
|
|
1853
|
+
title: "Language code required",
|
|
1854
|
+
status: "error",
|
|
1855
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
1856
|
+
});
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
setIsSubmitting(!0);
|
|
1860
|
+
try {
|
|
1861
|
+
isAutogenerated ? await handleGenerateSubtitles() : await handleAddTrackFromUrl();
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
toast.push({
|
|
1864
|
+
title: "Failed to add caption track",
|
|
1865
|
+
status: "error",
|
|
1866
|
+
description: extractErrorMessage(error, "Failed to add caption track")
|
|
1867
|
+
});
|
|
1868
|
+
} finally {
|
|
1869
|
+
setIsSubmitting(!1);
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1432
1872
|
return /* @__PURE__ */ jsx(
|
|
1433
1873
|
Dialog,
|
|
1434
1874
|
{
|
|
1435
1875
|
id: dialogId,
|
|
1436
|
-
header: "
|
|
1437
|
-
onClose
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1876
|
+
header: "Add Caption Track",
|
|
1877
|
+
onClose,
|
|
1878
|
+
width: 1,
|
|
1879
|
+
onClickOutside: onClose,
|
|
1880
|
+
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, children: [
|
|
1881
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1882
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", marginBottom: 3, children: [
|
|
1883
|
+
/* @__PURE__ */ jsx(
|
|
1884
|
+
Checkbox,
|
|
1885
|
+
{
|
|
1886
|
+
id: "autogenerated-checkbox",
|
|
1887
|
+
style: { display: "block" },
|
|
1888
|
+
checked: isAutogenerated,
|
|
1889
|
+
onChange: (e) => {
|
|
1890
|
+
setIsAutogenerated(e.currentTarget.checked), e.currentTarget.checked && setVttUrl("");
|
|
1891
|
+
},
|
|
1892
|
+
disabled: isSubmitting
|
|
1893
|
+
}
|
|
1894
|
+
),
|
|
1895
|
+
/* @__PURE__ */ jsx(Flex, { flex: 1, paddingLeft: 2, children: /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx("label", { htmlFor: "autogenerated-checkbox", children: "Generate captions" }) }) })
|
|
1896
|
+
] }),
|
|
1897
|
+
!isAutogenerated && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1898
|
+
/* @__PURE__ */ jsxs(Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
1899
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", children: [
|
|
1900
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: selectedFile ? `Selected: ${selectedFile.name}` : "No file selected" }),
|
|
1901
|
+
/* @__PURE__ */ jsx(
|
|
1902
|
+
Button,
|
|
1903
|
+
{
|
|
1904
|
+
icon: UploadIcon,
|
|
1905
|
+
text: "Select File",
|
|
1906
|
+
mode: "ghost",
|
|
1907
|
+
tone: "primary",
|
|
1908
|
+
fontSize: 1,
|
|
1909
|
+
padding: 2,
|
|
1910
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1911
|
+
disabled: isSubmitting
|
|
1912
|
+
}
|
|
1913
|
+
)
|
|
1914
|
+
] }),
|
|
1915
|
+
/* @__PURE__ */ jsx(
|
|
1916
|
+
"input",
|
|
1917
|
+
{
|
|
1918
|
+
ref: fileInputRef,
|
|
1919
|
+
type: "file",
|
|
1920
|
+
accept: ".vtt,text/vtt",
|
|
1921
|
+
style: { display: "none" },
|
|
1922
|
+
onChange: (e) => {
|
|
1923
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
)
|
|
1927
|
+
] }),
|
|
1928
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, style: { textAlign: "center" }, children: "Or enter the VTT file URL" }),
|
|
1929
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1930
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
1931
|
+
/* @__PURE__ */ jsx(
|
|
1932
|
+
TextInput,
|
|
1933
|
+
{
|
|
1934
|
+
id: "vtt-url",
|
|
1935
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
1936
|
+
value: vttUrl,
|
|
1937
|
+
onChange: (e) => {
|
|
1938
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
1939
|
+
},
|
|
1940
|
+
disabled: isSubmitting
|
|
1941
|
+
}
|
|
1942
|
+
)
|
|
1943
|
+
] })
|
|
1944
|
+
] })
|
|
1945
|
+
] }),
|
|
1946
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1947
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-name", children: "Audio name" }),
|
|
1948
|
+
/* @__PURE__ */ jsx(
|
|
1949
|
+
Autocomplete,
|
|
1950
|
+
{
|
|
1951
|
+
id: "caption-name",
|
|
1952
|
+
value: selectedLanguage?.value || "",
|
|
1953
|
+
onChange: (newValue) => {
|
|
1954
|
+
const selected = (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find((opt) => opt.value === newValue);
|
|
1955
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
1956
|
+
},
|
|
1957
|
+
options: isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1,
|
|
1958
|
+
icon: TranslateIcon,
|
|
1959
|
+
placeholder: "Select language",
|
|
1960
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
1961
|
+
openButton: !0,
|
|
1962
|
+
renderValue: (value) => (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find(
|
|
1963
|
+
(l) => l.value === value
|
|
1964
|
+
)?.label || value,
|
|
1965
|
+
renderOption: (option) => /* @__PURE__ */ jsx(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxs(Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
1966
|
+
option.label,
|
|
1967
|
+
" (",
|
|
1968
|
+
option.value,
|
|
1969
|
+
")"
|
|
1970
|
+
] }) }),
|
|
1971
|
+
disabled: isSubmitting
|
|
1972
|
+
}
|
|
1973
|
+
)
|
|
1974
|
+
] }),
|
|
1975
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1976
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-language", children: "Language Code" }),
|
|
1977
|
+
/* @__PURE__ */ jsx(
|
|
1978
|
+
TextInput,
|
|
1979
|
+
{
|
|
1980
|
+
id: "caption-language",
|
|
1981
|
+
placeholder: "en-US",
|
|
1982
|
+
value: languageCode,
|
|
1983
|
+
onChange: (e) => {
|
|
1984
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
1985
|
+
},
|
|
1986
|
+
disabled: isSubmitting
|
|
1987
|
+
}
|
|
1988
|
+
)
|
|
1989
|
+
] }),
|
|
1990
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
1991
|
+
/* @__PURE__ */ jsx(Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
1992
|
+
/* @__PURE__ */ jsx(
|
|
1993
|
+
Button,
|
|
1994
|
+
{
|
|
1995
|
+
text: "Add Caption Track",
|
|
1996
|
+
tone: "primary",
|
|
1997
|
+
icon: isSubmitting ? /* @__PURE__ */ jsx(
|
|
1998
|
+
Spinner,
|
|
1999
|
+
{
|
|
2000
|
+
style: {
|
|
2001
|
+
verticalAlign: "middle",
|
|
2002
|
+
display: "inline-block",
|
|
2003
|
+
marginBottom: "-3px",
|
|
2004
|
+
width: "1em",
|
|
2005
|
+
height: "1em",
|
|
2006
|
+
marginRight: "-6px"
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
) : /* @__PURE__ */ jsx(UploadIcon, {}),
|
|
2010
|
+
onClick: handleSubmit,
|
|
2011
|
+
disabled: isSubmitting
|
|
2012
|
+
}
|
|
2013
|
+
)
|
|
2014
|
+
] })
|
|
2015
|
+
] })
|
|
2016
|
+
}
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
const LANGUAGE_OPTIONS = LanguagesList.getAllCodes().map((code) => ({
|
|
2020
|
+
value: code,
|
|
2021
|
+
label: LanguagesList.getNativeName(code)
|
|
2022
|
+
}));
|
|
2023
|
+
function EditCaptionDialog({ asset, track, onUpdate, onClose }) {
|
|
2024
|
+
const client = useClient(), toast = useToast(), dialogId = `EditCaptionDialog${useId()}`, isAutogenerated = track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod", [vttUrl, setVttUrl] = useState(""), [languageCode, setLanguageCode] = useState(track.language_code || ""), [selectedLanguage, setSelectedLanguage] = useState(
|
|
2025
|
+
() => {
|
|
2026
|
+
const baseCode = track.language_code?.split("-")[0], found = LANGUAGE_OPTIONS.find(
|
|
2027
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
2028
|
+
);
|
|
2029
|
+
if (found) return found;
|
|
2030
|
+
if (track.name) {
|
|
2031
|
+
const foundByName = LANGUAGE_OPTIONS.find((opt) => opt.label === track.name);
|
|
2032
|
+
if (foundByName) return foundByName;
|
|
2033
|
+
}
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2036
|
+
), [name2, setName] = useState(track.name || ""), [isSubmitting, setIsSubmitting] = useState(!1), [downloading, setDownloading] = useState(!1), [selectedFile, setSelectedFile] = useState(null), fileInputRef = useRef(null);
|
|
2037
|
+
useEffect(() => {
|
|
2038
|
+
setLanguageCode(track.language_code || ""), setName(track.name || ""), setVttUrl("");
|
|
2039
|
+
const baseCode = track.language_code?.split("-")[0], foundByCode = LANGUAGE_OPTIONS.find(
|
|
2040
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
2041
|
+
), foundByName = track.name ? LANGUAGE_OPTIONS.find((opt) => opt.label === track.name) : null;
|
|
2042
|
+
setSelectedLanguage(foundByCode || foundByName || null);
|
|
2043
|
+
}, [track, asset, client]);
|
|
2044
|
+
const handleDownloadCurrentFile = async () => {
|
|
2045
|
+
setDownloading(!0);
|
|
2046
|
+
try {
|
|
2047
|
+
await downloadVttFile(client, asset, track);
|
|
2048
|
+
} catch (error) {
|
|
2049
|
+
let errorMessage = "Please try again", title = "Failed to download VTT file";
|
|
2050
|
+
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({
|
|
2051
|
+
title,
|
|
2052
|
+
status: "error",
|
|
2053
|
+
description: errorMessage
|
|
2054
|
+
});
|
|
2055
|
+
} finally {
|
|
2056
|
+
setDownloading(!1);
|
|
2057
|
+
}
|
|
2058
|
+
}, 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, {
|
|
2059
|
+
filename: file.name
|
|
2060
|
+
})).url, refreshAssetData = async () => {
|
|
2061
|
+
if (!(!asset._id || !asset.assetId))
|
|
2062
|
+
try {
|
|
2063
|
+
const latestAssetData = await getAsset(client, asset.assetId);
|
|
2064
|
+
await client.patch(asset._id).set({ data: latestAssetData.data, status: latestAssetData.data.status }).commit();
|
|
2065
|
+
} catch (refreshError) {
|
|
2066
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2067
|
+
}
|
|
2068
|
+
}, handleUpdateTrackWithNewUrl = async () => {
|
|
2069
|
+
if (!asset.assetId)
|
|
2070
|
+
throw new Error("Asset ID is required");
|
|
2071
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim(), oldTrackId = track.id;
|
|
2072
|
+
try {
|
|
2073
|
+
await deleteTextTrack(client, asset.assetId, oldTrackId);
|
|
2074
|
+
} catch (deleteError) {
|
|
2075
|
+
throw toast.push({
|
|
2076
|
+
title: "Failed to delete old track",
|
|
2077
|
+
status: "error",
|
|
2078
|
+
description: "Could not delete the old track. Please try again or delete it manually."
|
|
2079
|
+
}), setIsSubmitting(!1), deleteError;
|
|
2080
|
+
}
|
|
2081
|
+
let vttUrlToUse = vttUrl.trim();
|
|
2082
|
+
if (selectedFile)
|
|
2083
|
+
try {
|
|
2084
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
2085
|
+
} catch (uploadError) {
|
|
2086
|
+
throw toast.push({
|
|
2087
|
+
title: "Failed to upload VTT file",
|
|
2088
|
+
status: "error",
|
|
2089
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
2090
|
+
}), setIsSubmitting(!1), uploadError;
|
|
2091
|
+
}
|
|
2092
|
+
try {
|
|
2093
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
2094
|
+
language_code: trimmedLanguageCode,
|
|
2095
|
+
name: trimmedName,
|
|
2096
|
+
text_type: "subtitles"
|
|
2097
|
+
});
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
throw toast.push({
|
|
2100
|
+
title: "Failed to update caption track",
|
|
2101
|
+
status: "error",
|
|
2102
|
+
description: extractErrorMessage(error, "Failed to update caption track")
|
|
2103
|
+
}), setIsSubmitting(!1), error;
|
|
2104
|
+
}
|
|
2105
|
+
const result = await pollTrackStatus({
|
|
2106
|
+
client,
|
|
2107
|
+
assetId: asset.assetId,
|
|
2108
|
+
trackName: trimmedName,
|
|
2109
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
2110
|
+
onTrackErrored: async (erroredTrack) => {
|
|
2111
|
+
const errorMessage = erroredTrack.error?.messages?.[0] || erroredTrack.error?.type || "The track failed to download from the provided URL";
|
|
2112
|
+
toast.push({
|
|
2113
|
+
title: "Caption track failed",
|
|
2114
|
+
status: "error",
|
|
2115
|
+
description: errorMessage
|
|
2116
|
+
}), await refreshAssetData(), onUpdate(erroredTrack, oldTrackId), setIsSubmitting(!1);
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
if (!result.found || !result.track) {
|
|
2120
|
+
toast.push({
|
|
2121
|
+
title: "Caption track may have been updated",
|
|
2122
|
+
status: "warning",
|
|
2123
|
+
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."
|
|
2124
|
+
}), setIsSubmitting(!1);
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
result.status !== "errored" && (await refreshAssetData(), result.status === "preparing" ? toast.push({
|
|
2128
|
+
title: "Caption track is processing",
|
|
2129
|
+
status: "info",
|
|
2130
|
+
description: "The track was updated and is being processed. It will appear in the list shortly."
|
|
2131
|
+
}) : toast.push({
|
|
2132
|
+
title: "Caption track updated",
|
|
2133
|
+
status: "success",
|
|
2134
|
+
description: "Caption track updated successfully"
|
|
2135
|
+
}), onUpdate(result.track, oldTrackId), setIsSubmitting(!1));
|
|
2136
|
+
}, handleSubmit = async () => {
|
|
2137
|
+
if (!name2.trim()) {
|
|
2138
|
+
toast.push({
|
|
2139
|
+
title: "Audio name required",
|
|
2140
|
+
status: "error",
|
|
2141
|
+
description: "Please enter an audio name for this caption track"
|
|
2142
|
+
});
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
if (!languageCode.trim()) {
|
|
2146
|
+
toast.push({
|
|
2147
|
+
title: "Language code required",
|
|
2148
|
+
status: "error",
|
|
2149
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
2150
|
+
});
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
setIsSubmitting(!0);
|
|
2154
|
+
try {
|
|
2155
|
+
if (!asset.assetId)
|
|
2156
|
+
throw new Error("Asset ID is required");
|
|
2157
|
+
const originalVttUrl = (() => {
|
|
2158
|
+
if (isAutogenerated || !track.id) return "";
|
|
2159
|
+
const playbackId = getPlaybackId(asset);
|
|
2160
|
+
if (!playbackId) return "";
|
|
2161
|
+
let url = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
2162
|
+
if (getPlaybackPolicy(asset)?.policy === "signed") {
|
|
2163
|
+
const token = generateJwt(client, playbackId, "v");
|
|
2164
|
+
url += `?token=${token}`;
|
|
2165
|
+
}
|
|
2166
|
+
return url;
|
|
2167
|
+
})(), urlChanged = selectedFile !== null || vttUrl.trim() && vttUrl.trim() !== originalVttUrl;
|
|
2168
|
+
if (!urlChanged) {
|
|
2169
|
+
toast.push({
|
|
2170
|
+
title: "No changes",
|
|
2171
|
+
status: "info",
|
|
2172
|
+
description: 'Please provide a new VTT file or URL using the "Replace" button or URL field to update the track.'
|
|
2173
|
+
}), setIsSubmitting(!1);
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
if (urlChanged) {
|
|
2177
|
+
if (!selectedFile && vttUrl.trim())
|
|
2178
|
+
try {
|
|
2179
|
+
new URL(vttUrl.trim());
|
|
2180
|
+
} catch {
|
|
2181
|
+
toast.push({
|
|
2182
|
+
title: "Invalid URL",
|
|
2183
|
+
status: "error",
|
|
2184
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
2185
|
+
}), setIsSubmitting(!1);
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
2189
|
+
toast.push({
|
|
2190
|
+
title: "VTT file or URL required",
|
|
2191
|
+
status: "error",
|
|
2192
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
2193
|
+
}), setIsSubmitting(!1);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
await handleUpdateTrackWithNewUrl();
|
|
2197
|
+
}
|
|
2198
|
+
onClose();
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
toast.push({
|
|
2201
|
+
title: "Failed to update caption track",
|
|
2202
|
+
status: "error",
|
|
2203
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2204
|
+
});
|
|
2205
|
+
} finally {
|
|
2206
|
+
setIsSubmitting(!1);
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
return /* @__PURE__ */ jsx(
|
|
2210
|
+
Dialog,
|
|
2211
|
+
{
|
|
2212
|
+
id: dialogId,
|
|
2213
|
+
header: "Edit Caption Track",
|
|
2214
|
+
onClose,
|
|
2215
|
+
width: 1,
|
|
2216
|
+
onClickOutside: onClose,
|
|
2217
|
+
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, children: [
|
|
2218
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2219
|
+
/* @__PURE__ */ jsxs(Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
2220
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", children: [
|
|
2221
|
+
/* @__PURE__ */ jsx(Text, { children: getCurrentFileName() }),
|
|
2222
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
2223
|
+
track.status !== "errored" && /* @__PURE__ */ jsx(
|
|
2224
|
+
Button,
|
|
2225
|
+
{
|
|
2226
|
+
icon: downloading ? /* @__PURE__ */ jsx(
|
|
2227
|
+
Spinner,
|
|
2228
|
+
{
|
|
2229
|
+
style: {
|
|
2230
|
+
verticalAlign: "middle",
|
|
2231
|
+
display: "inline-block",
|
|
2232
|
+
marginTop: "-2px",
|
|
2233
|
+
width: "0.5em",
|
|
2234
|
+
height: "0.5em"
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
) : /* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
2238
|
+
text: "Download",
|
|
2239
|
+
mode: "ghost",
|
|
2240
|
+
tone: "primary",
|
|
2241
|
+
fontSize: 1,
|
|
2242
|
+
padding: 2,
|
|
2243
|
+
onClick: handleDownloadCurrentFile,
|
|
2244
|
+
disabled: downloading || isSubmitting
|
|
2245
|
+
}
|
|
2246
|
+
),
|
|
2247
|
+
/* @__PURE__ */ jsx(
|
|
2248
|
+
Button,
|
|
2249
|
+
{
|
|
2250
|
+
icon: UploadIcon,
|
|
2251
|
+
text: "Replace",
|
|
2252
|
+
mode: "ghost",
|
|
2253
|
+
tone: "primary",
|
|
2254
|
+
fontSize: 1,
|
|
2255
|
+
padding: 2,
|
|
2256
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2257
|
+
disabled: isSubmitting
|
|
2258
|
+
}
|
|
2259
|
+
)
|
|
2260
|
+
] })
|
|
2261
|
+
] }),
|
|
2262
|
+
/* @__PURE__ */ jsx(
|
|
2263
|
+
"input",
|
|
2264
|
+
{
|
|
2265
|
+
ref: fileInputRef,
|
|
2266
|
+
type: "file",
|
|
2267
|
+
accept: ".vtt,text/vtt",
|
|
2268
|
+
style: { display: "none" },
|
|
2269
|
+
onChange: (e) => {
|
|
2270
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
),
|
|
2274
|
+
selectedFile && /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, style: { marginTop: 8 }, children: [
|
|
2275
|
+
"Selected: ",
|
|
2276
|
+
selectedFile.name
|
|
2277
|
+
] })
|
|
2278
|
+
] }),
|
|
2279
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2280
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
2281
|
+
/* @__PURE__ */ jsx(
|
|
2282
|
+
TextInput,
|
|
2283
|
+
{
|
|
2284
|
+
id: "vtt-url",
|
|
2285
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
2286
|
+
value: vttUrl,
|
|
2287
|
+
onChange: (e) => {
|
|
2288
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
2289
|
+
},
|
|
2290
|
+
disabled: isSubmitting
|
|
2291
|
+
}
|
|
2292
|
+
),
|
|
2293
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Add a URL to replace the existing VTT file with a new one" })
|
|
2294
|
+
] })
|
|
2295
|
+
] }),
|
|
2296
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2297
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-name", children: "Audio name" }),
|
|
2298
|
+
/* @__PURE__ */ jsx(
|
|
2299
|
+
Autocomplete,
|
|
2300
|
+
{
|
|
2301
|
+
id: "caption-name",
|
|
2302
|
+
value: selectedLanguage?.value || "",
|
|
2303
|
+
onChange: (newValue) => {
|
|
2304
|
+
const selected = LANGUAGE_OPTIONS.find((opt) => opt.value === newValue);
|
|
2305
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
2306
|
+
},
|
|
2307
|
+
options: LANGUAGE_OPTIONS,
|
|
2308
|
+
icon: TranslateIcon,
|
|
2309
|
+
placeholder: "Select language",
|
|
2310
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
2311
|
+
openButton: !0,
|
|
2312
|
+
renderValue: (value) => LANGUAGE_OPTIONS.find((l) => l.value === value)?.label || value,
|
|
2313
|
+
renderOption: (option) => /* @__PURE__ */ jsx(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxs(Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
2314
|
+
option.label,
|
|
2315
|
+
" (",
|
|
2316
|
+
option.value,
|
|
2317
|
+
")"
|
|
2318
|
+
] }) }),
|
|
2319
|
+
disabled: isSubmitting
|
|
2320
|
+
}
|
|
2321
|
+
)
|
|
2322
|
+
] }),
|
|
2323
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2324
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-language", children: "Language Code" }),
|
|
2325
|
+
/* @__PURE__ */ jsx(
|
|
2326
|
+
TextInput,
|
|
2327
|
+
{
|
|
2328
|
+
id: "caption-language",
|
|
2329
|
+
placeholder: "en-US",
|
|
2330
|
+
value: languageCode,
|
|
2331
|
+
onChange: (e) => {
|
|
2332
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
2333
|
+
},
|
|
2334
|
+
disabled: isSubmitting
|
|
2335
|
+
}
|
|
2336
|
+
)
|
|
2337
|
+
] }),
|
|
2338
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
2339
|
+
/* @__PURE__ */ jsx(Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
2340
|
+
/* @__PURE__ */ jsx(
|
|
2341
|
+
Button,
|
|
2342
|
+
{
|
|
2343
|
+
text: "Update Caption Track",
|
|
2344
|
+
tone: "primary",
|
|
2345
|
+
icon: isSubmitting ? /* @__PURE__ */ jsx(
|
|
2346
|
+
Spinner,
|
|
2347
|
+
{
|
|
2348
|
+
style: {
|
|
2349
|
+
verticalAlign: "middle",
|
|
2350
|
+
display: "inline-block",
|
|
2351
|
+
marginBottom: "-3px",
|
|
2352
|
+
width: "1em",
|
|
2353
|
+
height: "1em",
|
|
2354
|
+
marginRight: "-6px"
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
) : UploadIcon,
|
|
2358
|
+
onClick: handleSubmit,
|
|
2359
|
+
disabled: isSubmitting
|
|
2360
|
+
}
|
|
2361
|
+
)
|
|
2362
|
+
] })
|
|
2363
|
+
] })
|
|
2364
|
+
}
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
function TrackCard({
|
|
2368
|
+
track,
|
|
2369
|
+
iconOnly,
|
|
2370
|
+
downloadingTrackId,
|
|
2371
|
+
deletingTrackId,
|
|
2372
|
+
trackToEdit,
|
|
2373
|
+
getTrackSourceLabel,
|
|
2374
|
+
handleDownload,
|
|
2375
|
+
setTrackToEdit,
|
|
2376
|
+
setTrackToDelete
|
|
2377
|
+
}) {
|
|
2378
|
+
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__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
2379
|
+
/* @__PURE__ */ jsx(
|
|
2380
|
+
Spinner,
|
|
2381
|
+
{
|
|
2382
|
+
muted: !0,
|
|
2383
|
+
style: {
|
|
2384
|
+
width: "0.75em",
|
|
2385
|
+
height: "0.75em",
|
|
2386
|
+
verticalAlign: "middle",
|
|
2387
|
+
display: "inline-block",
|
|
2388
|
+
marginBottom: "-2px"
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
),
|
|
2392
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Processing..." })
|
|
2393
|
+
] }) : /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
2394
|
+
track.status !== "errored" && /* @__PURE__ */ jsx(
|
|
2395
|
+
Button,
|
|
2396
|
+
{
|
|
2397
|
+
icon: downloadingTrackId === track.id ? /* @__PURE__ */ jsx(
|
|
2398
|
+
Spinner,
|
|
2399
|
+
{
|
|
2400
|
+
style: {
|
|
2401
|
+
verticalAlign: "middle",
|
|
2402
|
+
display: "inline-block",
|
|
2403
|
+
marginTop: "-2px",
|
|
2404
|
+
width: "0.5em",
|
|
2405
|
+
height: "0.5em"
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
) : /* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
2409
|
+
text: iconOnly ? void 0 : "Download",
|
|
2410
|
+
mode: "ghost",
|
|
2411
|
+
tone: "primary",
|
|
2412
|
+
fontSize: 1,
|
|
2413
|
+
padding: 2,
|
|
2414
|
+
onClick: () => handleDownload(track),
|
|
2415
|
+
disabled: isDisabled("download"),
|
|
2416
|
+
title: "Download"
|
|
2417
|
+
}
|
|
2418
|
+
),
|
|
2419
|
+
/* @__PURE__ */ jsx(
|
|
2420
|
+
Button,
|
|
2421
|
+
{
|
|
2422
|
+
icon: /* @__PURE__ */ jsx(EditIcon, {}),
|
|
2423
|
+
text: iconOnly ? void 0 : "Edit",
|
|
2424
|
+
mode: "ghost",
|
|
2425
|
+
tone: "primary",
|
|
2426
|
+
fontSize: 1,
|
|
2427
|
+
padding: 2,
|
|
2428
|
+
disabled: isDisabled("edit"),
|
|
2429
|
+
onClick: () => setTrackToEdit(track),
|
|
2430
|
+
title: "Edit"
|
|
2431
|
+
}
|
|
2432
|
+
),
|
|
2433
|
+
/* @__PURE__ */ jsx(
|
|
2434
|
+
Button,
|
|
2435
|
+
{
|
|
2436
|
+
icon: deletingTrackId === track.id ? /* @__PURE__ */ jsx(
|
|
2437
|
+
Spinner,
|
|
2438
|
+
{
|
|
2439
|
+
style: {
|
|
2440
|
+
verticalAlign: "middle",
|
|
2441
|
+
display: "inline-block",
|
|
2442
|
+
marginTop: "-2px",
|
|
2443
|
+
width: "0.5em",
|
|
2444
|
+
height: "0.5em"
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
) : /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
2448
|
+
text: iconOnly ? void 0 : "Delete",
|
|
2449
|
+
mode: "ghost",
|
|
2450
|
+
tone: "critical",
|
|
2451
|
+
fontSize: 1,
|
|
2452
|
+
padding: 2,
|
|
2453
|
+
disabled: isDisabled("delete"),
|
|
2454
|
+
onClick: () => setTrackToDelete(track),
|
|
2455
|
+
title: "Delete"
|
|
2456
|
+
}
|
|
2457
|
+
)
|
|
2458
|
+
] });
|
|
2459
|
+
return /* @__PURE__ */ jsx(
|
|
2460
|
+
Card,
|
|
2461
|
+
{
|
|
2462
|
+
padding: 3,
|
|
2463
|
+
radius: 2,
|
|
2464
|
+
tone: track.status === "errored" ? "caution" : "transparent",
|
|
2465
|
+
border: !0,
|
|
2466
|
+
children: /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
2467
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, flex: 1, children: [
|
|
2468
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
2469
|
+
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: track.name || "Untitled" }),
|
|
2470
|
+
/* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
2471
|
+
"(",
|
|
2472
|
+
getTrackSourceLabel(track),
|
|
2473
|
+
")"
|
|
2474
|
+
] }),
|
|
2475
|
+
track.status === "errored" && /* @__PURE__ */ jsx(
|
|
2476
|
+
ErrorOutlineIcon,
|
|
2477
|
+
{
|
|
2478
|
+
style: { color: "var(--card-critical-color)" },
|
|
2479
|
+
"aria-label": "Error",
|
|
2480
|
+
fontSize: 20
|
|
2481
|
+
}
|
|
2482
|
+
)
|
|
2483
|
+
] }),
|
|
2484
|
+
track.language_code && /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
2485
|
+
"Language: ",
|
|
2486
|
+
track.language_code
|
|
2487
|
+
] }),
|
|
2488
|
+
track.status === "errored" && track.error && /* @__PURE__ */ jsx(Text, { size: 1, style: { color: "var(--card-critical-color)" }, children: track.error.messages?.[0] || track.error.type || "Failed to process track" })
|
|
2489
|
+
] }),
|
|
2490
|
+
renderActionButtons()
|
|
2491
|
+
] })
|
|
2492
|
+
}
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
function TextTracksManager({
|
|
2496
|
+
asset,
|
|
2497
|
+
iconOnly = !1,
|
|
2498
|
+
tracks: propTracks,
|
|
2499
|
+
collapseTracks = !1
|
|
2500
|
+
}) {
|
|
2501
|
+
const client = useClient(), toast = useToast(), dialogId = `DeleteCaptionDialog${useId()}`, [downloadingTrackId, setDownloadingTrackId] = useState(null), [deletingTrackId, setDeletingTrackId] = useState(null), [addedTracks, setAddedTracks] = useState([]), [updatedTracks, setUpdatedTracks] = useState(/* @__PURE__ */ new Map()), [trackActivityOrder, setTrackActivityOrder] = useState(/* @__PURE__ */ new Map()), [autogeneratedTrackIds, setAutogeneratedTrackIds] = useState(/* @__PURE__ */ new Set()), [trackToDelete, setTrackToDelete] = useState(null), [trackToEdit, setTrackToEdit] = useState(null), [showAddDialog, setShowAddDialog] = useState(!1), [isExpanded, setIsExpanded] = useState(!1), MAX_VISIBLE_TRACKS = 4;
|
|
2502
|
+
useEffect(() => {
|
|
2503
|
+
if (!asset.assetId || !asset._id) return;
|
|
2504
|
+
const assetId = asset.assetId, documentId = asset._id;
|
|
2505
|
+
(async () => {
|
|
2506
|
+
try {
|
|
2507
|
+
const response = await getAsset(client, assetId);
|
|
2508
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2509
|
+
} catch (error) {
|
|
2510
|
+
console.error("Failed to refresh asset data:", error);
|
|
2511
|
+
}
|
|
2512
|
+
})();
|
|
2513
|
+
}, [asset.assetId, asset._id, client]);
|
|
2514
|
+
const activeTracks = (propTracks || asset.data?.tracks?.filter((track) => track.type === "text") || []).filter(
|
|
2515
|
+
(track) => track.id && (track.status === "ready" || track.status === "preparing" || track.status === "errored")
|
|
2516
|
+
), allTracks = useMemo(() => {
|
|
2517
|
+
const tracksWithUpdates = activeTracks.map((track) => updatedTracks.get(track.id) || track), isMockTrackReplaced = (mockTrack, realTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : realTracksList.some((realTrack) => {
|
|
2518
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2519
|
+
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";
|
|
2520
|
+
}), 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));
|
|
2521
|
+
return [...tracksWithUpdates, ...tracksToKeep];
|
|
2522
|
+
}, [activeTracks, addedTracks, updatedTracks]);
|
|
2523
|
+
useEffect(() => {
|
|
2524
|
+
const newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2525
|
+
activeTracks.forEach((track) => {
|
|
2526
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2527
|
+
}), addedTracks.forEach((mockTrack) => {
|
|
2528
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2529
|
+
const realTrack = activeTracks.find((rt) => {
|
|
2530
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2531
|
+
return nameMatches && languageMatches;
|
|
2532
|
+
});
|
|
2533
|
+
realTrack?.id && newAutogeneratedIds.add(realTrack.id);
|
|
2534
|
+
}
|
|
2535
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2536
|
+
let hasNew = !1;
|
|
2537
|
+
const updated = new Set(prev);
|
|
2538
|
+
return newAutogeneratedIds.forEach((id) => {
|
|
2539
|
+
prev.has(id) || (updated.add(id), hasNew = !0);
|
|
2540
|
+
}), hasNew ? updated : prev;
|
|
2541
|
+
});
|
|
2542
|
+
}, [activeTracks, addedTracks]), useEffect(() => {
|
|
2543
|
+
if (allTracks.filter((track) => track.status === "preparing").length === 0 || !asset.assetId || !asset._id)
|
|
2544
|
+
return;
|
|
2545
|
+
const assetId = asset.assetId, documentId = asset._id, interval = setInterval(async () => {
|
|
2546
|
+
try {
|
|
2547
|
+
const response = await getAsset(client, assetId);
|
|
2548
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2549
|
+
const fetchedTracks = response.data.tracks?.filter((track) => track.type === "text") || [], isMockTrackReplaced = (mockTrack, fetchedTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : fetchedTracksList.some((realTrack) => {
|
|
2550
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2551
|
+
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";
|
|
2552
|
+
}), newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2553
|
+
fetchedTracks.forEach((track) => {
|
|
2554
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2555
|
+
});
|
|
2556
|
+
const findMatchingRealTrack = (mockTrack, tracksList) => tracksList.find((rt) => {
|
|
2557
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2558
|
+
return nameMatches && languageMatches;
|
|
2559
|
+
});
|
|
2560
|
+
setAddedTracks((prev) => prev.filter((mockTrack) => {
|
|
2561
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2562
|
+
const replaced = isMockTrackReplaced(mockTrack, fetchedTracks);
|
|
2563
|
+
if (replaced) {
|
|
2564
|
+
const realTrack = findMatchingRealTrack(mockTrack, fetchedTracks);
|
|
2565
|
+
realTrack?.id && (newAutogeneratedIds.add(realTrack.id), setTrackActivityOrder((prevOrder) => {
|
|
2566
|
+
const mockOrder = prevOrder.get(mockTrack.id);
|
|
2567
|
+
if (mockOrder) {
|
|
2568
|
+
const newMap = new Map(prevOrder);
|
|
2569
|
+
return newMap.set(realTrack.id, mockOrder), newMap;
|
|
2570
|
+
}
|
|
2571
|
+
return prevOrder;
|
|
2572
|
+
}));
|
|
2573
|
+
}
|
|
2574
|
+
return !replaced;
|
|
2575
|
+
}
|
|
2576
|
+
return !0;
|
|
2577
|
+
})), newAutogeneratedIds.size > 0 && setAutogeneratedTrackIds((prevIds) => {
|
|
2578
|
+
const updated = new Set(prevIds);
|
|
2579
|
+
return newAutogeneratedIds.forEach((id) => updated.add(id)), updated;
|
|
2580
|
+
});
|
|
2581
|
+
} catch (error) {
|
|
2582
|
+
console.error("Failed to refresh asset data:", error);
|
|
2583
|
+
}
|
|
2584
|
+
}, 3e3);
|
|
2585
|
+
return () => clearInterval(interval);
|
|
2586
|
+
}, [allTracks, asset.assetId, asset._id, client]);
|
|
2587
|
+
const visibleTracks = allTracks.filter(
|
|
2588
|
+
(track) => track.status === "ready" || track.status === "preparing" || track.status === "errored"
|
|
2589
|
+
).sort((a2, b) => {
|
|
2590
|
+
const orderA = trackActivityOrder.get(a2.id) || 0, orderB = trackActivityOrder.get(b.id) || 0;
|
|
2591
|
+
if (orderA > 0 && orderB > 0)
|
|
2592
|
+
return orderB - orderA;
|
|
2593
|
+
if (orderA > 0) return -1;
|
|
2594
|
+
if (orderB > 0) return 1;
|
|
2595
|
+
const aIsPreparing = a2.status === "preparing", bIsPreparing = b.status === "preparing";
|
|
2596
|
+
if (aIsPreparing && !bIsPreparing) return -1;
|
|
2597
|
+
if (!aIsPreparing && bIsPreparing) return 1;
|
|
2598
|
+
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);
|
|
2599
|
+
return aIsAutogenerated && !bIsAutogenerated ? -1 : !aIsAutogenerated && bIsAutogenerated ? 1 : 0;
|
|
2600
|
+
}), handleDownload = async (track) => {
|
|
2601
|
+
if (track.id) {
|
|
2602
|
+
setDownloadingTrackId(track.id);
|
|
2603
|
+
try {
|
|
2604
|
+
await downloadVttFile(client, asset, track);
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
toast.push({
|
|
2607
|
+
title: "Failed to download VTT file",
|
|
2608
|
+
status: "error",
|
|
2609
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2610
|
+
});
|
|
2611
|
+
} finally {
|
|
2612
|
+
setDownloadingTrackId(null);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
}, confirmDelete = async () => {
|
|
2616
|
+
if (!trackToDelete || !trackToDelete.id) return;
|
|
2617
|
+
const track = trackToDelete;
|
|
2618
|
+
setTrackToDelete(null), setDeletingTrackId(track.id);
|
|
2619
|
+
try {
|
|
2620
|
+
if (!asset.assetId)
|
|
2621
|
+
throw new Error("Asset ID is required");
|
|
2622
|
+
if (await deleteTextTrack(client, asset.assetId, track.id), asset._id)
|
|
2623
|
+
try {
|
|
2624
|
+
const response = await getAsset(client, asset.assetId);
|
|
2625
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2626
|
+
} catch (refreshError) {
|
|
2627
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2628
|
+
}
|
|
2629
|
+
toast.push({
|
|
2630
|
+
title: "Successfully deleted caption track",
|
|
2631
|
+
status: "success"
|
|
2632
|
+
}), setAddedTracks((prev) => prev.filter((t) => t.id !== track.id)), setUpdatedTracks((prev) => {
|
|
2633
|
+
const newMap = new Map(prev);
|
|
2634
|
+
return newMap.delete(track.id), newMap;
|
|
2635
|
+
}), setTrackActivityOrder((prev) => {
|
|
2636
|
+
const newMap = new Map(prev);
|
|
2637
|
+
return newMap.delete(track.id), newMap;
|
|
2638
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2639
|
+
const updated = new Set(prev);
|
|
2640
|
+
return updated.delete(track.id), updated;
|
|
2641
|
+
});
|
|
2642
|
+
} catch (error) {
|
|
2643
|
+
toast.push({
|
|
2644
|
+
title: "Failed to delete caption track",
|
|
2645
|
+
status: "error",
|
|
2646
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2647
|
+
});
|
|
2648
|
+
} finally {
|
|
2649
|
+
setDeletingTrackId(null);
|
|
2650
|
+
}
|
|
2651
|
+
}, handleAddTrack = (track) => {
|
|
2652
|
+
setAddedTracks((prev) => [...prev, track]), setTrackActivityOrder((prev) => {
|
|
2653
|
+
const newMap = new Map(prev);
|
|
2654
|
+
return newMap.set(track.id, prev.size + 1), newMap;
|
|
2655
|
+
}), setShowAddDialog(!1);
|
|
2656
|
+
}, handleUpdateTrack = async (updatedTrack, oldTrackId) => {
|
|
2657
|
+
if (oldTrackId && (setAddedTracks((prev) => prev.filter((t) => t.id !== oldTrackId)), setUpdatedTracks((prev) => {
|
|
2658
|
+
const newMap = new Map(prev);
|
|
2659
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2660
|
+
}), setTrackActivityOrder((prev) => {
|
|
2661
|
+
const newMap = new Map(prev);
|
|
2662
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2663
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2664
|
+
const updated = new Set(prev);
|
|
2665
|
+
return updated.delete(oldTrackId), updated;
|
|
2666
|
+
})), addedTracks.some((t) => t.id === updatedTrack.id) ? setAddedTracks((prev) => prev.map((t) => t.id === updatedTrack.id ? updatedTrack : t)) : setUpdatedTracks((prev) => {
|
|
2667
|
+
const newMap = new Map(prev);
|
|
2668
|
+
return newMap.set(updatedTrack.id, updatedTrack), newMap;
|
|
2669
|
+
}), setTrackActivityOrder((prev) => {
|
|
2670
|
+
const newMap = new Map(prev);
|
|
2671
|
+
return newMap.set(updatedTrack.id, prev.size + 1), newMap;
|
|
2672
|
+
}), setTrackToEdit(null), asset._id && asset.assetId)
|
|
2673
|
+
try {
|
|
2674
|
+
const response = await getAsset(client, asset.assetId);
|
|
2675
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2676
|
+
} catch (refreshError) {
|
|
2677
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2678
|
+
}
|
|
2679
|
+
}, 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";
|
|
2680
|
+
if (visibleTracks.length === 0 && !showAddDialog)
|
|
2681
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2682
|
+
/* @__PURE__ */ jsx(Flex, { justify: "flex-end", children: /* @__PURE__ */ jsx(
|
|
2683
|
+
Button,
|
|
2684
|
+
{
|
|
2685
|
+
icon: AddIcon,
|
|
2686
|
+
text: "Add Caption",
|
|
2687
|
+
tone: "primary",
|
|
2688
|
+
onClick: () => setShowAddDialog(!0)
|
|
2689
|
+
}
|
|
2690
|
+
) }),
|
|
2691
|
+
/* @__PURE__ */ jsx(Card, { padding: 4, radius: 2, tone: "transparent", border: !0, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No captions available. Add captions when uploading a video or add them manually." }) }),
|
|
2692
|
+
showAddDialog && /* @__PURE__ */ jsx(
|
|
2693
|
+
AddCaptionDialog,
|
|
2694
|
+
{
|
|
2695
|
+
asset,
|
|
2696
|
+
onAdd: handleAddTrack,
|
|
2697
|
+
onClose: () => setShowAddDialog(!1)
|
|
2698
|
+
}
|
|
2699
|
+
)
|
|
2700
|
+
] });
|
|
2701
|
+
const displayedTracks = collapseTracks && !isExpanded ? visibleTracks.slice(0, MAX_VISIBLE_TRACKS) : visibleTracks, hasMoreTracks = collapseTracks && visibleTracks.length > MAX_VISIBLE_TRACKS;
|
|
2702
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2703
|
+
/* @__PURE__ */ jsx(Flex, { justify: "flex-end", children: /* @__PURE__ */ jsx(
|
|
2704
|
+
Button,
|
|
2705
|
+
{
|
|
2706
|
+
icon: AddIcon,
|
|
2707
|
+
text: "Add Caption",
|
|
2708
|
+
tone: "primary",
|
|
2709
|
+
onClick: () => setShowAddDialog(!0)
|
|
2710
|
+
}
|
|
2711
|
+
) }),
|
|
2712
|
+
displayedTracks.map((track) => /* @__PURE__ */ jsx(
|
|
2713
|
+
TrackCard,
|
|
2714
|
+
{
|
|
2715
|
+
track,
|
|
2716
|
+
iconOnly,
|
|
2717
|
+
downloadingTrackId,
|
|
2718
|
+
deletingTrackId,
|
|
2719
|
+
trackToEdit,
|
|
2720
|
+
getTrackSourceLabel,
|
|
2721
|
+
handleDownload,
|
|
2722
|
+
setTrackToEdit,
|
|
2723
|
+
setTrackToDelete
|
|
2724
|
+
},
|
|
2725
|
+
track.id
|
|
2726
|
+
)),
|
|
2727
|
+
hasMoreTracks && /* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsx(
|
|
2728
|
+
Button,
|
|
2729
|
+
{
|
|
2730
|
+
icon: isExpanded ? ChevronUpIcon : ChevronDownIcon,
|
|
2731
|
+
text: isExpanded ? "Show less" : `Show ${visibleTracks.length - MAX_VISIBLE_TRACKS} more`,
|
|
2732
|
+
mode: "ghost",
|
|
2733
|
+
tone: "primary",
|
|
2734
|
+
onClick: () => setIsExpanded(!isExpanded)
|
|
2735
|
+
}
|
|
2736
|
+
) }),
|
|
2737
|
+
trackToDelete && /* @__PURE__ */ jsx(
|
|
2738
|
+
Dialog,
|
|
2739
|
+
{
|
|
2740
|
+
animate: !0,
|
|
2741
|
+
id: dialogId,
|
|
2742
|
+
header: "Delete track",
|
|
2743
|
+
onClose: () => setTrackToDelete(null),
|
|
2744
|
+
onClickOutside: () => setTrackToDelete(null),
|
|
2745
|
+
width: 1,
|
|
2746
|
+
children: /* @__PURE__ */ jsx(
|
|
2747
|
+
Card,
|
|
2748
|
+
{
|
|
2749
|
+
padding: 3,
|
|
2750
|
+
style: {
|
|
2751
|
+
minHeight: "150px",
|
|
2752
|
+
display: "flex",
|
|
2753
|
+
alignItems: "center",
|
|
2754
|
+
justifyContent: "center"
|
|
2755
|
+
},
|
|
2756
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2757
|
+
/* @__PURE__ */ jsxs(Heading, { size: 2, children: [
|
|
2758
|
+
'Are you sure you want to delete "',
|
|
2759
|
+
trackToDelete.name || trackToDelete.language_code || "Untitled",
|
|
2760
|
+
'"?'
|
|
2761
|
+
] }),
|
|
2762
|
+
/* @__PURE__ */ jsx(Text, { size: 2, children: "This action is irreversible" }),
|
|
2763
|
+
/* @__PURE__ */ jsx(Stack, { space: 4, marginY: 4, children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
|
|
2764
|
+
Button,
|
|
2765
|
+
{
|
|
2766
|
+
icon: deletingTrackId === trackToDelete.id ? /* @__PURE__ */ jsx(
|
|
2767
|
+
Spinner,
|
|
2768
|
+
{
|
|
2769
|
+
style: {
|
|
2770
|
+
verticalAlign: "middle",
|
|
2771
|
+
display: "inline-block",
|
|
2772
|
+
marginTop: "-2px",
|
|
2773
|
+
width: "0.5em",
|
|
2774
|
+
height: "0.5em"
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
) : /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
2778
|
+
fontSize: 2,
|
|
2779
|
+
padding: 3,
|
|
2780
|
+
text: "Delete track",
|
|
2781
|
+
tone: "critical",
|
|
2782
|
+
onClick: confirmDelete,
|
|
2783
|
+
disabled: deletingTrackId !== null
|
|
2784
|
+
}
|
|
2785
|
+
) }) })
|
|
2786
|
+
] })
|
|
2787
|
+
}
|
|
2788
|
+
)
|
|
2789
|
+
}
|
|
2790
|
+
),
|
|
2791
|
+
showAddDialog && /* @__PURE__ */ jsx(
|
|
2792
|
+
AddCaptionDialog,
|
|
2793
|
+
{
|
|
2794
|
+
asset,
|
|
2795
|
+
onAdd: handleAddTrack,
|
|
2796
|
+
onClose: () => setShowAddDialog(!1)
|
|
2797
|
+
}
|
|
2798
|
+
),
|
|
2799
|
+
trackToEdit && /* @__PURE__ */ jsx(
|
|
2800
|
+
EditCaptionDialog,
|
|
2801
|
+
{
|
|
2802
|
+
asset,
|
|
2803
|
+
track: trackToEdit,
|
|
2804
|
+
onUpdate: handleUpdateTrack,
|
|
2805
|
+
onClose: () => setTrackToEdit(null)
|
|
2806
|
+
}
|
|
2807
|
+
)
|
|
2808
|
+
] });
|
|
2809
|
+
}
|
|
2810
|
+
const DialogStateContext = createContext({
|
|
2811
|
+
dialogState: !1,
|
|
2812
|
+
setDialogState: () => null
|
|
2813
|
+
}), DialogStateProvider = ({
|
|
2814
|
+
dialogState,
|
|
2815
|
+
setDialogState,
|
|
2816
|
+
children
|
|
2817
|
+
}) => /* @__PURE__ */ jsx(DialogStateContext.Provider, { value: { dialogState, setDialogState }, children }), useDialogStateContext = () => useContext(DialogStateContext);
|
|
2818
|
+
function getVideoSrc({ client, muxPlaybackId: muxPlaybackId2 }) {
|
|
2819
|
+
const searchParams = new URLSearchParams();
|
|
2820
|
+
if (muxPlaybackId2.policy === "signed" || muxPlaybackId2.policy === "drm") {
|
|
2821
|
+
const token = generateJwt(client, muxPlaybackId2.id, "v");
|
|
2822
|
+
searchParams.set("token", token);
|
|
2823
|
+
}
|
|
2824
|
+
return `https://stream.mux.com/${muxPlaybackId2.id}.m3u8?${searchParams}`;
|
|
2825
|
+
}
|
|
2826
|
+
function CaptionsDialog({ asset }) {
|
|
2827
|
+
const { setDialogState } = useDialogStateContext(), dialogId = `CaptionsDialog${useId()}`;
|
|
2828
|
+
return /* @__PURE__ */ jsx(Dialog, { id: dialogId, header: "Edit Captions", onClose: () => setDialogState(!1), width: 1, children: /* @__PURE__ */ jsx(Stack, { padding: 4, children: /* @__PURE__ */ jsx(TextTracksManager, { asset }) }) });
|
|
2829
|
+
}
|
|
2830
|
+
function getDevicePixelRatio(options) {
|
|
2831
|
+
const {
|
|
2832
|
+
defaultDpr = 1,
|
|
2833
|
+
maxDpr = 3,
|
|
2834
|
+
round = !0
|
|
2835
|
+
} = options || {}, dpr = typeof window < "u" && typeof window.devicePixelRatio == "number" ? window.devicePixelRatio : defaultDpr;
|
|
2836
|
+
return Math.min(Math.max(1, round ? Math.floor(dpr) : dpr), maxDpr);
|
|
2837
|
+
}
|
|
2838
|
+
function formatSeconds(seconds) {
|
|
2839
|
+
if (typeof seconds != "number" || Number.isNaN(seconds))
|
|
2840
|
+
return "";
|
|
2841
|
+
const hrs = ~~(seconds / 3600), mins = ~~(seconds % 3600 / 60), secs = ~~seconds % 60;
|
|
2842
|
+
let ret = "";
|
|
2843
|
+
return hrs > 0 && (ret += "" + hrs + ":" + (mins < 10 ? "0" : "")), ret += "" + mins + ":" + (secs < 10 ? "0" : ""), ret += "" + secs, ret;
|
|
2844
|
+
}
|
|
2845
|
+
function formatSecondsToHHMMSS(seconds) {
|
|
2846
|
+
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");
|
|
2847
|
+
return `${hrs}:${mins}:${secs}`;
|
|
2848
|
+
}
|
|
2849
|
+
function isValidTimeFormat(time) {
|
|
2850
|
+
return /^([0-1]?[0-9]|2[0-3]):([0-5]?[0-9]):([0-5]?[0-9])$/.test(time) || time === "";
|
|
2851
|
+
}
|
|
2852
|
+
function getSecondsFromTimeFormat(time) {
|
|
2853
|
+
const [hh = 0, mm = 0, ss = 0] = time.split(":").map(Number);
|
|
2854
|
+
return hh * 3600 + mm * 60 + ss;
|
|
2855
|
+
}
|
|
2856
|
+
function EditThumbnailDialog({ asset, currentTime = 0 }) {
|
|
2857
|
+
const client = useClient(), { setDialogState } = useDialogStateContext(), dialogId = `EditThumbnailDialog${useId()}`, [timeFormatted, setTimeFormatted] = useState(
|
|
2858
|
+
() => formatSecondsToHHMMSS(currentTime)
|
|
2859
|
+
), [nextTime, setNextTime] = useState(currentTime), [inputError, setInputError] = useState(""), assetWithNewThumbnail = useMemo(() => ({ ...asset, thumbTime: nextTime }), [asset, nextTime]), [saving, setSaving] = useState(!1), [saveThumbnailError, setSaveThumbnailError] = useState(null), handleSave = () => {
|
|
2860
|
+
setSaving(!0), client.patch(asset._id).set({ thumbTime: nextTime }).commit({ returnDocuments: !1 }).then(() => void setDialogState(!1)).catch(setSaveThumbnailError).finally(() => void setSaving(!1));
|
|
2861
|
+
}, width = 300 * getDevicePixelRatio({ maxDpr: 2 });
|
|
2862
|
+
if (saveThumbnailError)
|
|
2863
|
+
throw saveThumbnailError;
|
|
2864
|
+
return /* @__PURE__ */ jsx(
|
|
2865
|
+
Dialog,
|
|
2866
|
+
{
|
|
2867
|
+
id: dialogId,
|
|
2868
|
+
header: "Edit thumbnail",
|
|
2869
|
+
onClose: () => setDialogState(!1),
|
|
2870
|
+
footer: /* @__PURE__ */ jsx(Stack, { padding: 3, children: /* @__PURE__ */ jsx(
|
|
2871
|
+
Button,
|
|
2872
|
+
{
|
|
2873
|
+
disabled: inputError !== "",
|
|
2874
|
+
mode: "ghost",
|
|
2875
|
+
tone: "primary",
|
|
2876
|
+
loading: saving,
|
|
2877
|
+
onClick: handleSave,
|
|
2878
|
+
text: "Set new thumbnail"
|
|
2879
|
+
},
|
|
2880
|
+
"thumbnail"
|
|
2881
|
+
) }),
|
|
2882
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, padding: 3, children: [
|
|
1451
2883
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1452
2884
|
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Current:" }),
|
|
1453
2885
|
/* @__PURE__ */ jsx(VideoThumbnail, { asset, width, staticImage: !0 })
|
|
@@ -1499,24 +2931,56 @@ function VideoPlayer({
|
|
|
1499
2931
|
hlsConfig,
|
|
1500
2932
|
...props
|
|
1501
2933
|
}) {
|
|
1502
|
-
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = useRef(null), {
|
|
1503
|
-
src: videoSrc,
|
|
1504
|
-
thumbnail: thumbnailSrc,
|
|
1505
|
-
error
|
|
1506
|
-
} = useMemo(() => {
|
|
2934
|
+
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = useRef(null), [error, setError] = useState(), playbackId = useMemo(() => {
|
|
1507
2935
|
try {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
return
|
|
2936
|
+
return getPlaybackId(asset, ["public", "signed", "drm"]);
|
|
2937
|
+
} catch {
|
|
2938
|
+
setError(new TypeError("Asset has no playback ID"));
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
}, [asset]), muxPlaybackId2 = useMemo(() => {
|
|
2942
|
+
if (playbackId)
|
|
2943
|
+
return getPlaybackPolicyById(asset, playbackId);
|
|
2944
|
+
}, [asset, playbackId]), src = useMemo(() => {
|
|
2945
|
+
if (playbackId && muxPlaybackId2)
|
|
2946
|
+
return tryWithSuspend(
|
|
2947
|
+
() => getVideoSrc({ muxPlaybackId: muxPlaybackId2, client }),
|
|
2948
|
+
(e) => {
|
|
2949
|
+
setError(e);
|
|
2950
|
+
}
|
|
2951
|
+
);
|
|
2952
|
+
}, [muxPlaybackId2, playbackId, client]), poster = useMemo(() => tryWithSuspend(
|
|
2953
|
+
() => getPosterSrc({ asset, client, width: thumbnailWidth }),
|
|
2954
|
+
(e) => {
|
|
2955
|
+
setError(e);
|
|
1512
2956
|
}
|
|
1513
|
-
|
|
2957
|
+
), [asset, client, thumbnailWidth]), signedToken = useMemo(() => {
|
|
1514
2958
|
try {
|
|
1515
|
-
return new URL(
|
|
2959
|
+
return new URL(src).searchParams.get("token");
|
|
1516
2960
|
} catch {
|
|
1517
|
-
return
|
|
2961
|
+
return;
|
|
1518
2962
|
}
|
|
1519
|
-
}, [
|
|
2963
|
+
}, [src]), drmToken = useMemo(() => {
|
|
2964
|
+
if (playbackId && muxPlaybackId2?.policy === "drm")
|
|
2965
|
+
return tryWithSuspend(
|
|
2966
|
+
() => generateJwt(client, playbackId, "d"),
|
|
2967
|
+
(e) => {
|
|
2968
|
+
setError(e);
|
|
2969
|
+
}
|
|
2970
|
+
);
|
|
2971
|
+
}, [client, muxPlaybackId2?.policy, playbackId]), tokens = useMemo(() => {
|
|
2972
|
+
try {
|
|
2973
|
+
const partialTokens = {
|
|
2974
|
+
playback: void 0,
|
|
2975
|
+
thumbnail: void 0,
|
|
2976
|
+
storyboard: void 0,
|
|
2977
|
+
drm: void 0
|
|
2978
|
+
};
|
|
2979
|
+
return signedToken && (partialTokens.playback = signedToken, partialTokens.thumbnail = signedToken, partialTokens.storyboard = signedToken), drmToken && (partialTokens.drm = drmToken), { ...partialTokens };
|
|
2980
|
+
} catch {
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2983
|
+
}, [signedToken, drmToken]), [width, height] = (asset?.data?.aspect_ratio ?? "16:9").split(":").map(Number), targetAspectRatio = props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width / height);
|
|
1520
2984
|
let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio);
|
|
1521
2985
|
return isAudio && (aspectRatio = props.forceAspectRatio ? (
|
|
1522
2986
|
// Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
|
|
@@ -1532,7 +2996,7 @@ function VideoPlayer({
|
|
|
1532
2996
|
...isAudio && { display: "flex", alignItems: "flex-end" }
|
|
1533
2997
|
},
|
|
1534
2998
|
children: [
|
|
1535
|
-
|
|
2999
|
+
src && poster && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1536
3000
|
isAudio && /* @__PURE__ */ jsx(
|
|
1537
3001
|
AudioIcon,
|
|
1538
3002
|
{
|
|
@@ -1547,34 +3011,36 @@ function VideoPlayer({
|
|
|
1547
3011
|
}
|
|
1548
3012
|
}
|
|
1549
3013
|
),
|
|
1550
|
-
/* @__PURE__ */
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
3014
|
+
/* @__PURE__ */ jsxs(Suspense, { fallback: null, children: [
|
|
3015
|
+
/* @__PURE__ */ jsx(
|
|
3016
|
+
MuxPlayer,
|
|
3017
|
+
{
|
|
3018
|
+
poster: isAudio ? void 0 : poster,
|
|
3019
|
+
ref: muxPlayer,
|
|
3020
|
+
...props,
|
|
3021
|
+
playsInline: !0,
|
|
3022
|
+
playbackId,
|
|
3023
|
+
tokens,
|
|
3024
|
+
preload: "metadata",
|
|
3025
|
+
crossOrigin: "anonymous",
|
|
3026
|
+
metadata: {
|
|
3027
|
+
player_name: "Sanity Admin Dashboard",
|
|
3028
|
+
player_version: "2.15.0",
|
|
3029
|
+
page_type: "Preview Player"
|
|
3030
|
+
},
|
|
3031
|
+
audio: isAudio,
|
|
3032
|
+
_hlsConfig: hlsConfig,
|
|
3033
|
+
style: {
|
|
3034
|
+
...!isAudio && { height: "100%" },
|
|
3035
|
+
width: "100%",
|
|
3036
|
+
display: "block",
|
|
3037
|
+
objectFit: "contain",
|
|
3038
|
+
...isAudio && { alignSelf: "end" }
|
|
3039
|
+
}
|
|
1574
3040
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
3041
|
+
),
|
|
3042
|
+
children
|
|
3043
|
+
] })
|
|
1578
3044
|
] }),
|
|
1579
3045
|
error ? /* @__PURE__ */ jsx(
|
|
1580
3046
|
"div",
|
|
@@ -1595,7 +3061,8 @@ function VideoPlayer({
|
|
|
1595
3061
|
]
|
|
1596
3062
|
}
|
|
1597
3063
|
),
|
|
1598
|
-
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime })
|
|
3064
|
+
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime }),
|
|
3065
|
+
dialogState === "edit-captions" && /* @__PURE__ */ jsx(CaptionsDialog, { asset })
|
|
1599
3066
|
] });
|
|
1600
3067
|
}
|
|
1601
3068
|
function assetIsAudio(asset) {
|
|
@@ -1865,9 +3332,11 @@ function getVideoMetadata(doc) {
|
|
|
1865
3332
|
playbackId: doc.playbackId,
|
|
1866
3333
|
createdAt: date,
|
|
1867
3334
|
duration: doc.data?.duration ? formatSeconds(doc.data?.duration) : void 0,
|
|
3335
|
+
playback_ids: doc.data?.playback_ids,
|
|
1868
3336
|
aspect_ratio: doc.data?.aspect_ratio,
|
|
1869
3337
|
max_stored_resolution: doc.data?.max_stored_resolution,
|
|
1870
|
-
max_stored_frame_rate: doc.data?.max_stored_frame_rate
|
|
3338
|
+
max_stored_frame_rate: doc.data?.max_stored_frame_rate,
|
|
3339
|
+
text_tracks: doc.data?.tracks?.filter((track) => track.type === "text") || []
|
|
1871
3340
|
};
|
|
1872
3341
|
}
|
|
1873
3342
|
function useVideoDetails(props) {
|
|
@@ -2059,7 +3528,20 @@ const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.la
|
|
|
2059
3528
|
minHeight: containerHeight
|
|
2060
3529
|
} : void 0,
|
|
2061
3530
|
children: [
|
|
2062
|
-
/* @__PURE__ */
|
|
3531
|
+
/* @__PURE__ */ jsxs(Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
3532
|
+
/* @__PURE__ */ jsx(VideoPlayer, { asset: props.asset, autoPlay: props.asset.autoPlay || !1 }),
|
|
3533
|
+
tab === "details" && /* @__PURE__ */ jsx(
|
|
3534
|
+
TextTracksManager,
|
|
3535
|
+
{
|
|
3536
|
+
asset: props.asset,
|
|
3537
|
+
iconOnly: !0,
|
|
3538
|
+
collapseTracks: !0,
|
|
3539
|
+
tracks: displayInfo?.text_tracks || props.asset.data?.tracks?.filter(
|
|
3540
|
+
(track) => track.type === "text"
|
|
3541
|
+
) || []
|
|
3542
|
+
}
|
|
3543
|
+
)
|
|
3544
|
+
] }),
|
|
2063
3545
|
/* @__PURE__ */ jsxs(Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
2064
3546
|
/* @__PURE__ */ jsxs(TabList, { space: 2, children: [
|
|
2065
3547
|
/* @__PURE__ */ jsx(
|
|
@@ -2153,14 +3635,7 @@ const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.la
|
|
|
2153
3635
|
),
|
|
2154
3636
|
/* @__PURE__ */ jsx(IconInfo, { text: `Mux ID:
|
|
2155
3637
|
${displayInfo.id}`, icon: TagIcon, size: 2 }),
|
|
2156
|
-
|
|
2157
|
-
IconInfo,
|
|
2158
|
-
{
|
|
2159
|
-
text: `Playback ID: ${displayInfo.playbackId}`,
|
|
2160
|
-
icon: TagIcon,
|
|
2161
|
-
size: 2
|
|
2162
|
-
}
|
|
2163
|
-
)
|
|
3638
|
+
/* @__PURE__ */ jsx(PlaybackIds, { playback_ids: displayInfo.playback_ids })
|
|
2164
3639
|
] })
|
|
2165
3640
|
] })
|
|
2166
3641
|
}
|
|
@@ -2183,6 +3658,25 @@ ${displayInfo.id}`, icon: TagIcon, size: 2 }),
|
|
|
2183
3658
|
]
|
|
2184
3659
|
}
|
|
2185
3660
|
);
|
|
3661
|
+
}, PlaybackIds = ({ playback_ids }) => playback_ids ? playback_ids.map((entry) => /* @__PURE__ */ jsx(
|
|
3662
|
+
IconInfo,
|
|
3663
|
+
{
|
|
3664
|
+
text: `Playback ID [${policyToText(entry.policy)}]: ${entry.id}`,
|
|
3665
|
+
icon: TagIcon,
|
|
3666
|
+
size: 2
|
|
3667
|
+
},
|
|
3668
|
+
entry.id
|
|
3669
|
+
)) : /* @__PURE__ */ jsx(IconInfo, { text: "No Playback ID", icon: TagIcon, size: 2 }), policyToText = (policy) => {
|
|
3670
|
+
switch (policy) {
|
|
3671
|
+
case "drm":
|
|
3672
|
+
return "DRM";
|
|
3673
|
+
case "signed":
|
|
3674
|
+
return "Signed";
|
|
3675
|
+
case "public":
|
|
3676
|
+
return "Public";
|
|
3677
|
+
default:
|
|
3678
|
+
return policy;
|
|
3679
|
+
}
|
|
2186
3680
|
}, VideoMetadata = (props) => {
|
|
2187
3681
|
if (!props.asset)
|
|
2188
3682
|
return null;
|
|
@@ -2276,10 +3770,12 @@ function VideoInBrowser({
|
|
|
2276
3770
|
onEdit,
|
|
2277
3771
|
asset
|
|
2278
3772
|
}) {
|
|
2279
|
-
const [renderVideo, setRenderVideo] = useState(!1), select = React.useCallback(() => onSelect?.(asset), [onSelect, asset]), edit = React.useCallback(() => onEdit?.(asset), [onEdit, asset]);
|
|
3773
|
+
const [renderVideo, setRenderVideo] = useState(!1), select = React.useCallback(() => onSelect?.(asset), [onSelect, asset]), edit = React.useCallback(() => onEdit?.(asset), [onEdit, asset]), { hasShownWarning } = useDrmPlaybackWarningContext();
|
|
2280
3774
|
if (!asset)
|
|
2281
3775
|
return null;
|
|
2282
|
-
const playbackPolicy = getPlaybackPolicy(asset)
|
|
3776
|
+
const playbackPolicy = getPlaybackPolicy(asset), onClickPlay = () => {
|
|
3777
|
+
playbackPolicy?.policy === "drm" && !hasShownWarning ? setRenderVideo("pre-render-warn") : setRenderVideo("render-video");
|
|
3778
|
+
};
|
|
2283
3779
|
return /* @__PURE__ */ jsxs(
|
|
2284
3780
|
Card,
|
|
2285
3781
|
{
|
|
@@ -2291,7 +3787,7 @@ function VideoInBrowser({
|
|
|
2291
3787
|
position: "relative"
|
|
2292
3788
|
},
|
|
2293
3789
|
children: [
|
|
2294
|
-
playbackPolicy === "signed" && /* @__PURE__ */ jsx(
|
|
3790
|
+
playbackPolicy?.policy === "signed" && /* @__PURE__ */ jsx(
|
|
2295
3791
|
Tooltip,
|
|
2296
3792
|
{
|
|
2297
3793
|
animate: !0,
|
|
@@ -2308,7 +3804,7 @@ function VideoInBrowser({
|
|
|
2308
3804
|
position: "absolute",
|
|
2309
3805
|
left: "1em",
|
|
2310
3806
|
top: "1em",
|
|
2311
|
-
zIndex:
|
|
3807
|
+
zIndex: 11
|
|
2312
3808
|
},
|
|
2313
3809
|
padding: 2,
|
|
2314
3810
|
border: !0,
|
|
@@ -2317,6 +3813,32 @@ function VideoInBrowser({
|
|
|
2317
3813
|
)
|
|
2318
3814
|
}
|
|
2319
3815
|
),
|
|
3816
|
+
playbackPolicy?.policy === "drm" && /* @__PURE__ */ jsx(
|
|
3817
|
+
Tooltip,
|
|
3818
|
+
{
|
|
3819
|
+
animate: !0,
|
|
3820
|
+
content: /* @__PURE__ */ jsx(Card, { padding: 2, radius: 2, children: /* @__PURE__ */ jsx(IconInfo, { icon: LockIcon, text: "DRM playback policy", size: 2 }) }),
|
|
3821
|
+
placement: "right",
|
|
3822
|
+
fallbackPlacements: ["top", "bottom"],
|
|
3823
|
+
portal: !0,
|
|
3824
|
+
children: /* @__PURE__ */ jsx(
|
|
3825
|
+
Card,
|
|
3826
|
+
{
|
|
3827
|
+
tone: "caution",
|
|
3828
|
+
style: {
|
|
3829
|
+
borderRadius: "0.25rem",
|
|
3830
|
+
position: "absolute",
|
|
3831
|
+
left: "1em",
|
|
3832
|
+
top: "1em",
|
|
3833
|
+
zIndex: 11
|
|
3834
|
+
},
|
|
3835
|
+
padding: 2,
|
|
3836
|
+
border: !0,
|
|
3837
|
+
children: /* @__PURE__ */ jsx(Text, { muted: !0, size: 1, weight: "semibold", style: { color: "var(--card-icon-color)" }, children: "DRM" })
|
|
3838
|
+
}
|
|
3839
|
+
)
|
|
3840
|
+
}
|
|
3841
|
+
),
|
|
2320
3842
|
/* @__PURE__ */ jsxs(
|
|
2321
3843
|
Stack,
|
|
2322
3844
|
{
|
|
@@ -2326,7 +3848,15 @@ function VideoInBrowser({
|
|
|
2326
3848
|
gridTemplateRows: "min-content min-content 1fr"
|
|
2327
3849
|
},
|
|
2328
3850
|
children: [
|
|
2329
|
-
renderVideo
|
|
3851
|
+
renderVideo === "pre-render-warn" && /* @__PURE__ */ jsx(
|
|
3852
|
+
DRMWarningDialog,
|
|
3853
|
+
{
|
|
3854
|
+
onClose: () => {
|
|
3855
|
+
setRenderVideo("render-video");
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
),
|
|
3859
|
+
renderVideo === "render-video" ? /* @__PURE__ */ jsx(VideoPlayer, { asset, autoPlay: !0, forceAspectRatio: THUMBNAIL_ASPECT_RATIO }) : /* @__PURE__ */ jsxs(PlayButton, { onClick: onClickPlay, children: [
|
|
2330
3860
|
/* @__PURE__ */ jsx("div", { "data-play": !0, children: /* @__PURE__ */ jsx(PlayIcon, {}) }),
|
|
2331
3861
|
assetIsAudio(asset) ? /* @__PURE__ */ jsx(
|
|
2332
3862
|
"div",
|
|
@@ -2388,12 +3918,12 @@ function VideoInBrowser({
|
|
|
2388
3918
|
}
|
|
2389
3919
|
);
|
|
2390
3920
|
}
|
|
2391
|
-
function VideosBrowser({ onSelect }) {
|
|
2392
|
-
const { assets, isLoading, searchQuery, setSearchQuery, setSort, sort } = useAssets(), [editedAsset, setEditedAsset] = useState(null), freshEditedAsset = useMemo(
|
|
3921
|
+
function VideosBrowser({ onSelect, config }) {
|
|
3922
|
+
const { assets, isLoading, searchQuery, setSearchQuery, setSort, sort } = useAssets(), [page, setPage] = useState(0), pageLimit = 20, pageTotal = Math.floor(assets.length / pageLimit) + 1, [editedAsset, setEditedAsset] = useState(null), freshEditedAsset = useMemo(
|
|
2393
3923
|
() => assets.find((a2) => a2._id === editedAsset?._id) || editedAsset,
|
|
2394
3924
|
[editedAsset, assets]
|
|
2395
|
-
);
|
|
2396
|
-
return /* @__PURE__ */ jsxs(
|
|
3925
|
+
), pageStart = page * pageLimit, pageEnd = pageStart + pageLimit;
|
|
3926
|
+
return /* @__PURE__ */ jsxs(DrmPlaybackWarningContextProvider, { config, children: [
|
|
2397
3927
|
/* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, style: { minHeight: "50vh" }, children: [
|
|
2398
3928
|
/* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
|
|
2399
3929
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
@@ -2406,7 +3936,8 @@ function VideosBrowser({ onSelect }) {
|
|
|
2406
3936
|
placeholder: "Search videos"
|
|
2407
3937
|
}
|
|
2408
3938
|
),
|
|
2409
|
-
/* @__PURE__ */ jsx(SelectSortOptions, { setSort, sort })
|
|
3939
|
+
/* @__PURE__ */ jsx(SelectSortOptions, { setSort, sort }),
|
|
3940
|
+
/* @__PURE__ */ jsx(PageSelector, { page, setPage, total: pageTotal })
|
|
2410
3941
|
] }),
|
|
2411
3942
|
(onSelect ? "input" : "tool") == "tool" && /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
|
|
2412
3943
|
/* @__PURE__ */ jsx(ImportVideosFromMux, {}),
|
|
@@ -2429,7 +3960,7 @@ function VideosBrowser({ onSelect }) {
|
|
|
2429
3960
|
style: {
|
|
2430
3961
|
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))"
|
|
2431
3962
|
},
|
|
2432
|
-
children: assets.map((asset) => /* @__PURE__ */ jsx(
|
|
3963
|
+
children: assets.slice(pageStart, pageEnd).map((asset) => /* @__PURE__ */ jsx(
|
|
2433
3964
|
VideoInBrowser,
|
|
2434
3965
|
{
|
|
2435
3966
|
asset,
|
|
@@ -2447,7 +3978,7 @@ function VideosBrowser({ onSelect }) {
|
|
|
2447
3978
|
freshEditedAsset && /* @__PURE__ */ jsx(VideoDetails, { closeDialog: () => setEditedAsset(null), asset: freshEditedAsset })
|
|
2448
3979
|
] });
|
|
2449
3980
|
}
|
|
2450
|
-
const StudioTool = () => /* @__PURE__ */ jsx(VideosBrowser, {}), DEFAULT_TOOL_CONFIG = {
|
|
3981
|
+
const StudioTool = (config) => /* @__PURE__ */ jsx(VideosBrowser, { config }), DEFAULT_TOOL_CONFIG = {
|
|
2451
3982
|
icon: ToolIcon,
|
|
2452
3983
|
title: "Videos"
|
|
2453
3984
|
};
|
|
@@ -2805,6 +4336,9 @@ function isValidUrl(url) {
|
|
|
2805
4336
|
return !1;
|
|
2806
4337
|
}
|
|
2807
4338
|
}
|
|
4339
|
+
function isServerError(error) {
|
|
4340
|
+
return "statusCode" in error && typeof error.statusCode == "number" && 500 <= error.statusCode && error.statusCode <= 600;
|
|
4341
|
+
}
|
|
2808
4342
|
function extractDroppedFiles(dataTransfer) {
|
|
2809
4343
|
const files = Array.from(dataTransfer.files || []), items = Array.from(dataTransfer.items || []);
|
|
2810
4344
|
return files && files.length > 0 ? Promise.resolve(files) : normalizeItems(items).then((arr) => arr.flat());
|
|
@@ -2846,7 +4380,12 @@ function walk(entry) {
|
|
|
2846
4380
|
}
|
|
2847
4381
|
return Promise.resolve([]);
|
|
2848
4382
|
}
|
|
2849
|
-
function SelectAssets({
|
|
4383
|
+
function SelectAssets({
|
|
4384
|
+
asset: selectedAsset,
|
|
4385
|
+
onChange,
|
|
4386
|
+
setDialogState,
|
|
4387
|
+
config
|
|
4388
|
+
}) {
|
|
2850
4389
|
const handleSelect = useCallback(
|
|
2851
4390
|
(chosenAsset) => {
|
|
2852
4391
|
chosenAsset?._id || onChange(PatchEvent.from([unset(["asset"])])), chosenAsset._id !== selectedAsset?._id && onChange(
|
|
@@ -2858,7 +4397,7 @@ function SelectAssets({ asset: selectedAsset, onChange, setDialogState }) {
|
|
|
2858
4397
|
},
|
|
2859
4398
|
[onChange, setDialogState, selectedAsset]
|
|
2860
4399
|
);
|
|
2861
|
-
return /* @__PURE__ */ jsx(VideosBrowser, { onSelect: handleSelect });
|
|
4400
|
+
return /* @__PURE__ */ jsx(VideosBrowser, { onSelect: handleSelect, config });
|
|
2862
4401
|
}
|
|
2863
4402
|
const StyledDialog = styled(Dialog)`
|
|
2864
4403
|
> div[data-ui='DialogCard'] > div[data-ui='Card'] {
|
|
@@ -2868,7 +4407,8 @@ const StyledDialog = styled(Dialog)`
|
|
|
2868
4407
|
function InputBrowser({
|
|
2869
4408
|
setDialogState,
|
|
2870
4409
|
asset,
|
|
2871
|
-
onChange
|
|
4410
|
+
onChange,
|
|
4411
|
+
config
|
|
2872
4412
|
}) {
|
|
2873
4413
|
const id = `InputBrowser${useId()}`, handleClose = useCallback(() => setDialogState(!1), [setDialogState]);
|
|
2874
4414
|
return /* @__PURE__ */ jsx(
|
|
@@ -2879,7 +4419,15 @@ function InputBrowser({
|
|
|
2879
4419
|
id,
|
|
2880
4420
|
onClose: handleClose,
|
|
2881
4421
|
width: 2,
|
|
2882
|
-
children: /* @__PURE__ */ jsx(
|
|
4422
|
+
children: /* @__PURE__ */ jsx(
|
|
4423
|
+
SelectAssets,
|
|
4424
|
+
{
|
|
4425
|
+
config,
|
|
4426
|
+
asset,
|
|
4427
|
+
onChange,
|
|
4428
|
+
setDialogState
|
|
4429
|
+
}
|
|
4430
|
+
)
|
|
2883
4431
|
}
|
|
2884
4432
|
);
|
|
2885
4433
|
}
|
|
@@ -3095,7 +4643,7 @@ const FileButton = styled(MenuItem)(({ theme }) => {
|
|
|
3095
4643
|
color: white;
|
|
3096
4644
|
`, isVideoAsset = (asset) => asset._type === "mux.videoAsset";
|
|
3097
4645
|
function PlayerActionsMenu(props) {
|
|
3098
|
-
const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset) === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange]);
|
|
4646
|
+
const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset)?.policy === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange]);
|
|
3099
4647
|
return useEffect(() => {
|
|
3100
4648
|
open && dialogState && setOpen(!1);
|
|
3101
4649
|
}, [dialogState, open]), useClickOutsideEvent(
|
|
@@ -3137,14 +4685,24 @@ function PlayerActionsMenu(props) {
|
|
|
3137
4685
|
onClick: () => setDialogState("select-video")
|
|
3138
4686
|
}
|
|
3139
4687
|
),
|
|
3140
|
-
isVideoAsset(asset) && /* @__PURE__ */
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
4688
|
+
isVideoAsset(asset) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4689
|
+
/* @__PURE__ */ jsx(
|
|
4690
|
+
MenuItem,
|
|
4691
|
+
{
|
|
4692
|
+
icon: ImageIcon,
|
|
4693
|
+
text: "Thumbnail",
|
|
4694
|
+
onClick: () => setDialogState("edit-thumbnail")
|
|
4695
|
+
}
|
|
4696
|
+
),
|
|
4697
|
+
/* @__PURE__ */ jsx(
|
|
4698
|
+
MenuItem,
|
|
4699
|
+
{
|
|
4700
|
+
icon: TranslateIcon,
|
|
4701
|
+
text: "Captions",
|
|
4702
|
+
onClick: () => setDialogState("edit-captions")
|
|
4703
|
+
}
|
|
4704
|
+
)
|
|
4705
|
+
] }),
|
|
3148
4706
|
/* @__PURE__ */ jsx(MenuDivider, {}),
|
|
3149
4707
|
hasConfigAccess && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3150
4708
|
/* @__PURE__ */ jsx(
|
|
@@ -3186,47 +4744,90 @@ function PlayerActionsMenu(props) {
|
|
|
3186
4744
|
] });
|
|
3187
4745
|
}
|
|
3188
4746
|
var PlayerActionsMenu$1 = memo(PlayerActionsMenu);
|
|
3189
|
-
function
|
|
3190
|
-
const
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
4747
|
+
function useFetchFileSize(stagedUpload, maxFileSize) {
|
|
4748
|
+
const [fileSize, setFileSize] = useState(null), [isLoadingFileSize, setIsLoadingFileSize] = useState(!1), [canSkipFileSizeValidation, setCanSkipFileSizeValidation] = useState(!1);
|
|
4749
|
+
return useEffect(() => {
|
|
4750
|
+
if (stagedUpload.type === "url") {
|
|
4751
|
+
setIsLoadingFileSize(!1), setCanSkipFileSizeValidation(!1), setFileSize(null);
|
|
4752
|
+
const url = stagedUpload.url;
|
|
4753
|
+
(async () => {
|
|
4754
|
+
setIsLoadingFileSize(!0);
|
|
4755
|
+
try {
|
|
4756
|
+
const contentLength = (await fetch(url, { method: "HEAD" })).headers.get("content-length"), newFileSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
4757
|
+
setIsLoadingFileSize(!1), newFileSize && setFileSize(newFileSize), newFileSize === null && maxFileSize !== void 0 && setCanSkipFileSizeValidation(!0);
|
|
4758
|
+
} catch {
|
|
4759
|
+
console.warn("Could not validate file size from URL"), setCanSkipFileSizeValidation(!0), setIsLoadingFileSize(!1);
|
|
4760
|
+
}
|
|
4761
|
+
})();
|
|
4762
|
+
}
|
|
4763
|
+
stagedUpload.type === "file" && setFileSize(stagedUpload.files[0].size);
|
|
4764
|
+
}, [maxFileSize, stagedUpload, stagedUpload.type]), {
|
|
4765
|
+
fileSize,
|
|
4766
|
+
isLoadingFileSize,
|
|
4767
|
+
canSkipFileSizeValidation
|
|
4768
|
+
};
|
|
3200
4769
|
}
|
|
3201
|
-
|
|
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
|
-
|
|
4770
|
+
function useMediaMetadata(stagedUpload) {
|
|
4771
|
+
const [videoAssetMetadata, setVideoAssetMetadata] = useState(null), [isLoadingMetadata, setIsLoadingMetadata] = useState(!1);
|
|
4772
|
+
return useEffect(() => {
|
|
4773
|
+
let videoSrc = null;
|
|
4774
|
+
if (stagedUpload.type === "file") {
|
|
4775
|
+
const file = stagedUpload.files[0];
|
|
4776
|
+
videoSrc = URL.createObjectURL(file);
|
|
4777
|
+
}
|
|
4778
|
+
if (stagedUpload.type === "url" && (videoSrc = stagedUpload.url), setVideoAssetMetadata((old) => ({
|
|
4779
|
+
...old,
|
|
4780
|
+
duration: void 0,
|
|
4781
|
+
width: void 0,
|
|
4782
|
+
height: void 0
|
|
4783
|
+
})), !videoSrc) return () => null;
|
|
4784
|
+
setIsLoadingMetadata(!0);
|
|
4785
|
+
const videoElement = document.createElement("video");
|
|
4786
|
+
videoElement.preload = "metadata";
|
|
4787
|
+
const metadataListeners = [
|
|
4788
|
+
() => {
|
|
4789
|
+
setIsLoadingMetadata(!1);
|
|
4790
|
+
},
|
|
4791
|
+
() => {
|
|
4792
|
+
const duration = videoElement.duration, width = videoElement.videoWidth, height = videoElement.videoHeight, isAudioOnly = width <= 0 && height <= 0;
|
|
4793
|
+
setVideoAssetMetadata((old) => ({
|
|
4794
|
+
...old,
|
|
4795
|
+
duration,
|
|
4796
|
+
width,
|
|
4797
|
+
height,
|
|
4798
|
+
isAudioOnly
|
|
4799
|
+
}));
|
|
4800
|
+
}
|
|
4801
|
+
], cleanupVideo = (videoEl) => {
|
|
4802
|
+
const currentVideoSrc = videoEl?.src;
|
|
4803
|
+
videoEl && (metadataListeners.forEach(
|
|
4804
|
+
(listener) => videoEl.removeEventListener("loadedmetadata", listener)
|
|
4805
|
+
), videoEl.onerror = null, videoEl.src = "", videoEl.load()), currentVideoSrc?.startsWith("blob:") && URL.revokeObjectURL(currentVideoSrc);
|
|
4806
|
+
};
|
|
4807
|
+
return metadataListeners.push(() => setTimeout(() => cleanupVideo(videoElement), 0)), videoElement.onerror = () => {
|
|
4808
|
+
setIsLoadingMetadata(!1), console.warn("Could not read video metadata for validation"), cleanupVideo(videoElement);
|
|
4809
|
+
}, metadataListeners.forEach(
|
|
4810
|
+
(listener) => videoElement.addEventListener("loadedmetadata", listener)
|
|
4811
|
+
), videoElement.src = videoSrc, () => {
|
|
4812
|
+
cleanupVideo(videoElement);
|
|
4813
|
+
};
|
|
4814
|
+
}, [stagedUpload.type, stagedUpload]), {
|
|
4815
|
+
videoAssetMetadata,
|
|
4816
|
+
setVideoAssetMetadata,
|
|
4817
|
+
isLoadingMetadata
|
|
4818
|
+
};
|
|
3227
4819
|
}
|
|
3228
|
-
function
|
|
3229
|
-
|
|
4820
|
+
function formatBytes(bytes, si = !1, dp = 1) {
|
|
4821
|
+
const thresh = si ? 1e3 : 1024;
|
|
4822
|
+
if (Math.abs(bytes) < thresh)
|
|
4823
|
+
return bytes + " B";
|
|
4824
|
+
const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
|
4825
|
+
let u2 = -1;
|
|
4826
|
+
const r = 10 ** dp;
|
|
4827
|
+
do
|
|
4828
|
+
bytes /= thresh, ++u2;
|
|
4829
|
+
while (Math.round(Math.abs(bytes) * r) / r >= thresh && u2 < units.length - 1);
|
|
4830
|
+
return bytes.toFixed(dp) + " " + units[u2];
|
|
3230
4831
|
}
|
|
3231
4832
|
const ALL_LANGUAGE_CODES = LanguagesList.getAllCodes().map((code) => ({
|
|
3232
4833
|
value: code,
|
|
@@ -3305,13 +4906,14 @@ function PlaybackPolicyOption({
|
|
|
3305
4906
|
optionName,
|
|
3306
4907
|
description,
|
|
3307
4908
|
dispatch,
|
|
3308
|
-
action
|
|
4909
|
+
action,
|
|
4910
|
+
disabled
|
|
3309
4911
|
}) {
|
|
3310
4912
|
const [scale, setScale] = useState(1), boxStyle = {
|
|
3311
4913
|
outline: "0.01rem solid grey",
|
|
3312
4914
|
transform: `scale(${scale})`,
|
|
3313
4915
|
transition: "transform 0.1s ease-in-out",
|
|
3314
|
-
cursor: "pointer",
|
|
4916
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
3315
4917
|
borderRadius: "0.25rem"
|
|
3316
4918
|
}, triggerAnimation = () => {
|
|
3317
4919
|
setScale(0.98), setTimeout(() => {
|
|
@@ -3319,15 +4921,24 @@ function PlaybackPolicyOption({
|
|
|
3319
4921
|
}, 100);
|
|
3320
4922
|
};
|
|
3321
4923
|
return /* @__PURE__ */ jsx("label", { children: /* @__PURE__ */ jsxs(Flex, { gap: 3, padding: 3, style: boxStyle, children: [
|
|
3322
|
-
/* @__PURE__ */ jsx(
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
4924
|
+
/* @__PURE__ */ jsx(
|
|
4925
|
+
Checkbox,
|
|
4926
|
+
{
|
|
4927
|
+
id,
|
|
4928
|
+
required: !0,
|
|
4929
|
+
checked,
|
|
4930
|
+
onChange: () => {
|
|
4931
|
+
action && (triggerAnimation(), dispatch({
|
|
4932
|
+
action,
|
|
4933
|
+
value: !checked
|
|
4934
|
+
}));
|
|
4935
|
+
},
|
|
4936
|
+
disabled
|
|
4937
|
+
}
|
|
4938
|
+
),
|
|
3328
4939
|
/* @__PURE__ */ jsxs(Grid, { gap: 3, children: [
|
|
3329
4940
|
/* @__PURE__ */ jsx(Text, { size: 3, weight: "bold", children: optionName }),
|
|
3330
|
-
/* @__PURE__ */ jsx(Text, { size: 2, muted: !0, children: description })
|
|
4941
|
+
typeof description == "string" ? /* @__PURE__ */ jsx(Text, { size: 2, muted: !0, children: description }) : description
|
|
3331
4942
|
] })
|
|
3332
4943
|
] }) });
|
|
3333
4944
|
}
|
|
@@ -3352,7 +4963,7 @@ function PlaybackPolicy({
|
|
|
3352
4963
|
secrets,
|
|
3353
4964
|
dispatch
|
|
3354
4965
|
}) {
|
|
3355
|
-
const noPolicySelected = !(config.public_policy || config.signed_policy);
|
|
4966
|
+
const noPolicySelected = !(config.public_policy || config.signed_policy || config.drm_policy), drmPolicyDisabled = !secrets.drmConfigId;
|
|
3356
4967
|
return /* @__PURE__ */ jsxs(Grid, { gap: 3, children: [
|
|
3357
4968
|
/* @__PURE__ */ jsx(Text, { weight: "bold", children: "Advanced Playback Policies" }),
|
|
3358
4969
|
/* @__PURE__ */ jsx(
|
|
@@ -3361,7 +4972,10 @@ function PlaybackPolicy({
|
|
|
3361
4972
|
id: `${id}--public`,
|
|
3362
4973
|
checked: config.public_policy,
|
|
3363
4974
|
optionName: "Public",
|
|
3364
|
-
description:
|
|
4975
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4976
|
+
/* @__PURE__ */ jsx(Text, { size: 2, muted: !0, children: "Playback IDs are accessible by constructing an HLS URL like" }),
|
|
4977
|
+
/* @__PURE__ */ jsx(Code, { children: "https://stream.mux.com/{PLAYBACK_ID}" })
|
|
4978
|
+
] }),
|
|
3365
4979
|
dispatch,
|
|
3366
4980
|
action: "public_policy"
|
|
3367
4981
|
}
|
|
@@ -3372,24 +4986,146 @@ function PlaybackPolicy({
|
|
|
3372
4986
|
id: `${id}--signed`,
|
|
3373
4987
|
checked: config.signed_policy,
|
|
3374
4988
|
optionName: "Signed",
|
|
3375
|
-
description:
|
|
3376
|
-
|
|
4989
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4990
|
+
/* @__PURE__ */ jsx(Text, { size: 2, muted: !0, children: "Playback IDs should be used with tokens" }),
|
|
4991
|
+
/* @__PURE__ */ jsx(Code, { children: "https://stream.mux.com/{PLAYBACK_ID}?token={TOKEN}" }),
|
|
4992
|
+
/* @__PURE__ */ jsxs(Text, { size: 2, muted: !0, children: [
|
|
4993
|
+
"See",
|
|
4994
|
+
" ",
|
|
4995
|
+
/* @__PURE__ */ jsx(
|
|
4996
|
+
"a",
|
|
4997
|
+
{
|
|
4998
|
+
href: "https://www.mux.com/docs/guides/secure-video-playback",
|
|
4999
|
+
target: "_blank",
|
|
5000
|
+
rel: "noopener noreferrer",
|
|
5001
|
+
children: "Secure video playback"
|
|
5002
|
+
}
|
|
5003
|
+
),
|
|
5004
|
+
" ",
|
|
5005
|
+
"for details about creating tokens."
|
|
5006
|
+
] })
|
|
5007
|
+
] }),
|
|
3377
5008
|
dispatch,
|
|
3378
5009
|
action: "signed_policy"
|
|
3379
5010
|
}
|
|
3380
5011
|
),
|
|
5012
|
+
drmPolicyDisabled ? /* @__PURE__ */ jsx(
|
|
5013
|
+
PlaybackPolicyOption,
|
|
5014
|
+
{
|
|
5015
|
+
id: `${id}--drm`,
|
|
5016
|
+
checked: !1,
|
|
5017
|
+
optionName: "DRM - Disabled",
|
|
5018
|
+
description: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Text, { size: 2, muted: !0, children: [
|
|
5019
|
+
"To enable DRM add your DRM Configuration Id to your plugin configuration in the API Credentials view.",
|
|
5020
|
+
" ",
|
|
5021
|
+
/* @__PURE__ */ jsx(
|
|
5022
|
+
"a",
|
|
5023
|
+
{
|
|
5024
|
+
href: "https://www.mux.com/support/human",
|
|
5025
|
+
target: "_blank",
|
|
5026
|
+
rel: "noopener noreferrer",
|
|
5027
|
+
children: "Contact us"
|
|
5028
|
+
}
|
|
5029
|
+
),
|
|
5030
|
+
" ",
|
|
5031
|
+
"to get started using DRM."
|
|
5032
|
+
] }) }),
|
|
5033
|
+
dispatch,
|
|
5034
|
+
disabled: !0
|
|
5035
|
+
}
|
|
5036
|
+
) : /* @__PURE__ */ jsx(
|
|
5037
|
+
PlaybackPolicyOption,
|
|
5038
|
+
{
|
|
5039
|
+
id: `${id}--drm`,
|
|
5040
|
+
checked: config.drm_policy,
|
|
5041
|
+
optionName: "DRM",
|
|
5042
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5043
|
+
/* @__PURE__ */ jsx(Text, { size: 2, muted: !0, children: "Playback IDs should be used with tokens as with Signed playback, but require extra configuration." }),
|
|
5044
|
+
/* @__PURE__ */ jsx(Code, { children: "https://stream.mux.com/{PLAYBACK_ID}?token={TOKEN}" }),
|
|
5045
|
+
/* @__PURE__ */ jsxs(Text, { size: 2, muted: !0, children: [
|
|
5046
|
+
"See",
|
|
5047
|
+
" ",
|
|
5048
|
+
/* @__PURE__ */ jsx(
|
|
5049
|
+
"a",
|
|
5050
|
+
{
|
|
5051
|
+
href: "https://www.mux.com/docs/guides/protect-videos-with-drm#play-drm-protected-videos",
|
|
5052
|
+
target: "_blank",
|
|
5053
|
+
rel: "noopener noreferrer",
|
|
5054
|
+
children: "Protect videos with DRM"
|
|
5055
|
+
}
|
|
5056
|
+
),
|
|
5057
|
+
" ",
|
|
5058
|
+
"for details about configuring your player for DRM playback and",
|
|
5059
|
+
" ",
|
|
5060
|
+
/* @__PURE__ */ jsx(
|
|
5061
|
+
"a",
|
|
5062
|
+
{
|
|
5063
|
+
href: "https://www.mux.com/docs/guides/secure-video-playback",
|
|
5064
|
+
target: "_blank",
|
|
5065
|
+
rel: "noopener noreferrer",
|
|
5066
|
+
children: "Secure video playback"
|
|
5067
|
+
}
|
|
5068
|
+
),
|
|
5069
|
+
" ",
|
|
5070
|
+
"for details about creating tokens."
|
|
5071
|
+
] })
|
|
5072
|
+
] }),
|
|
5073
|
+
dispatch,
|
|
5074
|
+
action: "drm_policy"
|
|
5075
|
+
}
|
|
5076
|
+
),
|
|
3381
5077
|
noPolicySelected && /* @__PURE__ */ jsx(PlaybackPolicyWarning, {})
|
|
3382
5078
|
] });
|
|
3383
5079
|
}
|
|
3384
|
-
const
|
|
3385
|
-
{ value: "basic", label: "Basic" },
|
|
3386
|
-
{ value: "plus", label: "Plus" },
|
|
3387
|
-
{ value: "premium", label: "Premium" }
|
|
3388
|
-
], RESOLUTION_TIERS = [
|
|
5080
|
+
const RESOLUTION_TIERS = [
|
|
3389
5081
|
{ value: "1080p", label: "1080p" },
|
|
3390
5082
|
{ value: "1440p", label: "1440p (2k)" },
|
|
3391
5083
|
{ value: "2160p", label: "2160p (4k)" }
|
|
3392
|
-
],
|
|
5084
|
+
], ResolutionTierSelector = ({
|
|
5085
|
+
id,
|
|
5086
|
+
config,
|
|
5087
|
+
dispatch,
|
|
5088
|
+
maxSupportedResolution
|
|
5089
|
+
}) => /* @__PURE__ */ jsx(
|
|
5090
|
+
FormField$2,
|
|
5091
|
+
{
|
|
5092
|
+
title: "Resolution Tier",
|
|
5093
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5094
|
+
"The maximum",
|
|
5095
|
+
" ",
|
|
5096
|
+
/* @__PURE__ */ jsx(
|
|
5097
|
+
"a",
|
|
5098
|
+
{
|
|
5099
|
+
href: "https://docs.mux.com/api-reference#video/operation/create-direct-upload",
|
|
5100
|
+
target: "_blank",
|
|
5101
|
+
rel: "noopener noreferrer",
|
|
5102
|
+
children: "resolution_tier"
|
|
5103
|
+
}
|
|
5104
|
+
),
|
|
5105
|
+
" ",
|
|
5106
|
+
"your asset is encoded, stored, and streamed at."
|
|
5107
|
+
] }),
|
|
5108
|
+
children: /* @__PURE__ */ jsx(Flex, { gap: 3, wrap: "wrap", children: RESOLUTION_TIERS.map(({ value, label }, index) => {
|
|
5109
|
+
const inputId = `${id}--type-${value}`;
|
|
5110
|
+
return index > maxSupportedResolution ? null : /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
5111
|
+
/* @__PURE__ */ jsx(
|
|
5112
|
+
Radio,
|
|
5113
|
+
{
|
|
5114
|
+
checked: config.max_resolution_tier === value,
|
|
5115
|
+
name: "asset-resolutiontier",
|
|
5116
|
+
onChange: (e) => dispatch({
|
|
5117
|
+
action: "max_resolution_tier",
|
|
5118
|
+
value: e.currentTarget.value
|
|
5119
|
+
}),
|
|
5120
|
+
value,
|
|
5121
|
+
id: inputId
|
|
5122
|
+
}
|
|
5123
|
+
),
|
|
5124
|
+
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: inputId, children: label })
|
|
5125
|
+
] }, value);
|
|
5126
|
+
}) })
|
|
5127
|
+
}
|
|
5128
|
+
), ADVANCED_RESOLUTIONS = [
|
|
3393
5129
|
{ value: "270p", label: "270p" },
|
|
3394
5130
|
{ value: "360p", label: "360p" },
|
|
3395
5131
|
{ value: "480p", label: "480p" },
|
|
@@ -3398,6 +5134,130 @@ const VIDEO_QUALITY_LEVELS = [
|
|
|
3398
5134
|
{ value: "1080p", label: "1080p" },
|
|
3399
5135
|
{ value: "1440p", label: "1440p" },
|
|
3400
5136
|
{ value: "2160p", label: "2160p" }
|
|
5137
|
+
], StaticRenditionSelector = ({
|
|
5138
|
+
id,
|
|
5139
|
+
config,
|
|
5140
|
+
dispatch
|
|
5141
|
+
}) => {
|
|
5142
|
+
const isAdvancedMode = useMemo(() => config.static_renditions.filter(
|
|
5143
|
+
(r) => r !== "highest" && r !== "audio-only"
|
|
5144
|
+
).length > 0, [config.static_renditions]), [renditionMode, setRenditionMode] = useState(
|
|
5145
|
+
isAdvancedMode ? "advanced" : "standard"
|
|
5146
|
+
), toggleRendition = (rendition) => {
|
|
5147
|
+
const current = config.static_renditions, hasRendition = current.includes(rendition);
|
|
5148
|
+
dispatch(hasRendition ? {
|
|
5149
|
+
action: "static_renditions",
|
|
5150
|
+
value: current.filter((r) => r !== rendition)
|
|
5151
|
+
} : {
|
|
5152
|
+
action: "static_renditions",
|
|
5153
|
+
value: [...current, rendition]
|
|
5154
|
+
});
|
|
5155
|
+
}, handleModeChange = (mode) => {
|
|
5156
|
+
setRenditionMode(mode), dispatch(mode === "standard" ? {
|
|
5157
|
+
action: "static_renditions",
|
|
5158
|
+
value: config.static_renditions.filter((r) => r === "highest" || r === "audio-only")
|
|
5159
|
+
} : {
|
|
5160
|
+
action: "static_renditions",
|
|
5161
|
+
value: config.static_renditions.filter((r) => r !== "highest")
|
|
5162
|
+
});
|
|
5163
|
+
};
|
|
5164
|
+
return /* @__PURE__ */ jsx(Stack, { space: 3, children: /* @__PURE__ */ jsx(
|
|
5165
|
+
FormField$2,
|
|
5166
|
+
{
|
|
5167
|
+
title: "Static Renditions",
|
|
5168
|
+
description: "Generate downloadable MP4 or M4A files. Note: Mux will not upscale to produce MP4 renditions - renditions that would cause upscaling are skipped.",
|
|
5169
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
5170
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 3, children: [
|
|
5171
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
5172
|
+
/* @__PURE__ */ jsx(
|
|
5173
|
+
Radio,
|
|
5174
|
+
{
|
|
5175
|
+
checked: renditionMode === "standard",
|
|
5176
|
+
name: "rendition-mode",
|
|
5177
|
+
onChange: () => handleModeChange("standard"),
|
|
5178
|
+
value: "standard",
|
|
5179
|
+
id: `${id}--mode-standard`
|
|
5180
|
+
}
|
|
5181
|
+
),
|
|
5182
|
+
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--mode-standard`, children: "Standard" })
|
|
5183
|
+
] }),
|
|
5184
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
5185
|
+
/* @__PURE__ */ jsx(
|
|
5186
|
+
Radio,
|
|
5187
|
+
{
|
|
5188
|
+
checked: renditionMode === "advanced",
|
|
5189
|
+
name: "rendition-mode",
|
|
5190
|
+
onChange: () => handleModeChange("advanced"),
|
|
5191
|
+
value: "advanced",
|
|
5192
|
+
id: `${id}--mode-advanced`
|
|
5193
|
+
}
|
|
5194
|
+
),
|
|
5195
|
+
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--mode-advanced`, children: "Advanced" })
|
|
5196
|
+
] })
|
|
5197
|
+
] }),
|
|
5198
|
+
renditionMode === "standard" && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
5199
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: [0, 2], children: [
|
|
5200
|
+
/* @__PURE__ */ jsx(
|
|
5201
|
+
Checkbox,
|
|
5202
|
+
{
|
|
5203
|
+
id: `${id}--highest`,
|
|
5204
|
+
style: { display: "block" },
|
|
5205
|
+
checked: config.static_renditions.includes("highest"),
|
|
5206
|
+
onChange: () => toggleRendition("highest")
|
|
5207
|
+
}
|
|
5208
|
+
),
|
|
5209
|
+
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--highest`, children: "Highest Resolution (up to 4K)" })
|
|
5210
|
+
] }),
|
|
5211
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: [0, 2], children: [
|
|
5212
|
+
/* @__PURE__ */ jsx(
|
|
5213
|
+
Checkbox,
|
|
5214
|
+
{
|
|
5215
|
+
id: `${id}--audio-only-standard`,
|
|
5216
|
+
style: { display: "block" },
|
|
5217
|
+
checked: config.static_renditions.includes("audio-only"),
|
|
5218
|
+
onChange: () => toggleRendition("audio-only")
|
|
5219
|
+
}
|
|
5220
|
+
),
|
|
5221
|
+
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--audio-only-standard`, children: "Audio Only (M4A)" })
|
|
5222
|
+
] })
|
|
5223
|
+
] }),
|
|
5224
|
+
renditionMode === "advanced" && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
5225
|
+
/* @__PURE__ */ jsx(Label$1, { size: 1, muted: !0, children: "Select specific resolutions:" }),
|
|
5226
|
+
/* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", children: ADVANCED_RESOLUTIONS.map(({ value, label }) => {
|
|
5227
|
+
const inputId = `${id}--resolution-${value}`;
|
|
5228
|
+
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
5229
|
+
/* @__PURE__ */ jsx(
|
|
5230
|
+
Checkbox,
|
|
5231
|
+
{
|
|
5232
|
+
id: inputId,
|
|
5233
|
+
style: { display: "block" },
|
|
5234
|
+
checked: config.static_renditions.includes(value),
|
|
5235
|
+
onChange: () => toggleRendition(value)
|
|
5236
|
+
}
|
|
5237
|
+
),
|
|
5238
|
+
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: inputId, size: 1, children: label })
|
|
5239
|
+
] }, value);
|
|
5240
|
+
}) }),
|
|
5241
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: [2, 2, 0, 2], children: [
|
|
5242
|
+
/* @__PURE__ */ jsx(
|
|
5243
|
+
Checkbox,
|
|
5244
|
+
{
|
|
5245
|
+
id: `${id}--audio-only-advanced`,
|
|
5246
|
+
style: { display: "block" },
|
|
5247
|
+
checked: config.static_renditions.includes("audio-only"),
|
|
5248
|
+
onChange: () => toggleRendition("audio-only")
|
|
5249
|
+
}
|
|
5250
|
+
),
|
|
5251
|
+
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--audio-only-advanced`, children: "Audio Only (M4A)" })
|
|
5252
|
+
] })
|
|
5253
|
+
] })
|
|
5254
|
+
] })
|
|
5255
|
+
}
|
|
5256
|
+
) });
|
|
5257
|
+
}, VIDEO_QUALITY_LEVELS = [
|
|
5258
|
+
{ value: "basic", label: "Basic" },
|
|
5259
|
+
{ value: "plus", label: "Plus" },
|
|
5260
|
+
{ value: "premium", label: "Premium" }
|
|
3401
5261
|
];
|
|
3402
5262
|
function sanitizeStaticRenditions(renditions) {
|
|
3403
5263
|
const hasHighest = renditions.includes("highest"), hasSpecificResolutions = renditions.some((r) => r !== "highest" && r !== "audio-only");
|
|
@@ -3429,7 +5289,8 @@ function UploadConfiguration({
|
|
|
3429
5289
|
max_resolution_tier: "1080p",
|
|
3430
5290
|
text_tracks: prev.text_tracks?.filter(({ type }) => type !== "autogenerated"),
|
|
3431
5291
|
public_policy: !0,
|
|
3432
|
-
signed_policy: !1
|
|
5292
|
+
signed_policy: !1,
|
|
5293
|
+
drm_policy: !1
|
|
3433
5294
|
}) : Object.assign({}, prev, {
|
|
3434
5295
|
video_quality: action.value,
|
|
3435
5296
|
static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
|
|
@@ -3443,6 +5304,8 @@ function UploadConfiguration({
|
|
|
3443
5304
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
3444
5305
|
case "public_policy":
|
|
3445
5306
|
return Object.assign({}, prev, { [action.action]: action.value });
|
|
5307
|
+
case "drm_policy":
|
|
5308
|
+
return Object.assign({}, prev, { [action.action]: action.value });
|
|
3446
5309
|
// Updating individual tracks
|
|
3447
5310
|
case "track": {
|
|
3448
5311
|
const text_tracks = [...prev.text_tracks], target_track_i = text_tracks.findIndex(({ _id: _id2 }) => _id2 === action.id);
|
|
@@ -3478,75 +5341,41 @@ function UploadConfiguration({
|
|
|
3478
5341
|
static_renditions: sanitizeStaticRenditions(pluginConfig.static_renditions || []),
|
|
3479
5342
|
signed_policy: secrets.enableSignedUrls && pluginConfig.defaultSigned,
|
|
3480
5343
|
public_policy: pluginConfig.defaultPublic,
|
|
5344
|
+
drm_policy: pluginConfig.defaultDrm && !!secrets.drmConfigId,
|
|
3481
5345
|
normalize_audio: pluginConfig.normalize_audio,
|
|
3482
5346
|
text_tracks: autoTextTracks
|
|
3483
5347
|
}
|
|
3484
|
-
),
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
), [videoDuration, setVideoDuration] = useState(null), [urlFileSize, setUrlFileSize] = useState(null), [isLoadingDuration, setIsLoadingDuration] = useState(!1), [isLoadingFileSize, setIsLoadingFileSize] = useState(!1), [validationError, setValidationError] = useState(null), [canSkipFileSizeValidation, setCanSkipFileSizeValidation] = useState(!1), MAX_FILE_SIZE = pluginConfig.maxAssetFileSize, MAX_DURATION_SECONDS = pluginConfig.maxAssetDuration;
|
|
5348
|
+
), [validationError, setValidationError] = useState(null), MAX_FILE_SIZE = pluginConfig.maxAssetFileSize, MAX_DURATION_SECONDS = pluginConfig.maxAssetDuration, { fileSize, isLoadingFileSize, canSkipFileSizeValidation } = useFetchFileSize(
|
|
5349
|
+
stagedUpload,
|
|
5350
|
+
MAX_FILE_SIZE
|
|
5351
|
+
), { videoAssetMetadata, setVideoAssetMetadata, isLoadingMetadata } = useMediaMetadata(stagedUpload);
|
|
3489
5352
|
useEffect(() => {
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
const
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
!MAX_DURATION_SECONDS || MAX_DURATION_SECONDS <= 0 || (setIsLoadingDuration(!0), videoElement = document.createElement("video"), videoElement.preload = "metadata", currentVideoSrc = videoSrc, videoElement.onloadedmetadata = () => {
|
|
3496
|
-
const duration = videoElement.duration;
|
|
3497
|
-
setVideoDuration(duration), setIsLoadingDuration(!1), duration > MAX_DURATION_SECONDS && setValidationError(
|
|
3498
|
-
`Video duration (${formatSeconds(duration)}) exceeds maximum allowed duration of ${formatSeconds(MAX_DURATION_SECONDS)}`
|
|
3499
|
-
), cleanupVideo(shouldRevokeUrl);
|
|
3500
|
-
}, videoElement.onerror = () => {
|
|
3501
|
-
setIsLoadingDuration(!1), console.warn("Could not read video metadata for validation"), cleanupVideo(shouldRevokeUrl);
|
|
3502
|
-
}, videoElement.src = videoSrc);
|
|
3503
|
-
}, validateFileSize = (size) => MAX_FILE_SIZE === void 0 || size <= MAX_FILE_SIZE ? !0 : (setValidationError(
|
|
5353
|
+
fileSize && setVideoAssetMetadata((old) => ({ ...old, size: fileSize }));
|
|
5354
|
+
}, [fileSize, setVideoAssetMetadata]), useEffect(() => {
|
|
5355
|
+
const validateDuration = (duration) => MAX_DURATION_SECONDS && duration > MAX_DURATION_SECONDS ? (setValidationError(
|
|
5356
|
+
`Video duration (${formatSeconds(duration)}) exceeds maximum allowed duration of ${formatSeconds(MAX_DURATION_SECONDS)}`
|
|
5357
|
+
), !1) : !0, validateFileSize = (size) => MAX_FILE_SIZE === void 0 || size <= MAX_FILE_SIZE ? !0 : (setValidationError(
|
|
3504
5358
|
`File size (${formatBytes(size)}) exceeds maximum allowed size of ${formatBytes(MAX_FILE_SIZE)}`
|
|
3505
|
-
), !1);
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
}
|
|
3522
|
-
})();
|
|
3523
|
-
}
|
|
3524
|
-
return () => {
|
|
3525
|
-
cleanupVideo(!0);
|
|
3526
|
-
};
|
|
3527
|
-
}, [stagedUpload, MAX_FILE_SIZE, MAX_DURATION_SECONDS]);
|
|
3528
|
-
const toggleRendition = (rendition) => {
|
|
3529
|
-
const current = config.static_renditions, hasRendition = current.includes(rendition);
|
|
3530
|
-
dispatch(hasRendition ? {
|
|
3531
|
-
action: "static_renditions",
|
|
3532
|
-
value: current.filter((r) => r !== rendition)
|
|
3533
|
-
} : {
|
|
3534
|
-
action: "static_renditions",
|
|
3535
|
-
value: [...current, rendition]
|
|
3536
|
-
});
|
|
3537
|
-
}, handleModeChange = (mode) => {
|
|
3538
|
-
setRenditionMode(mode), dispatch(mode === "standard" ? {
|
|
3539
|
-
action: "static_renditions",
|
|
3540
|
-
value: config.static_renditions.filter((r) => r === "highest" || r === "audio-only")
|
|
3541
|
-
} : {
|
|
3542
|
-
action: "static_renditions",
|
|
3543
|
-
value: config.static_renditions.filter((r) => r !== "highest")
|
|
3544
|
-
});
|
|
3545
|
-
}, { disableTextTrackConfig, disableUploadConfig } = pluginConfig, skipConfig = disableTextTrackConfig && disableUploadConfig;
|
|
5359
|
+
), !1), validateDrmAvailability = (isAudioOnly) => config.drm_policy && isAudioOnly ? (setValidationError("Audio-only asset cannot be DRM protected"), !1) : !0;
|
|
5360
|
+
let valid = !0;
|
|
5361
|
+
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);
|
|
5362
|
+
}, [
|
|
5363
|
+
MAX_FILE_SIZE,
|
|
5364
|
+
MAX_DURATION_SECONDS,
|
|
5365
|
+
canSkipFileSizeValidation,
|
|
5366
|
+
videoAssetMetadata?.duration,
|
|
5367
|
+
videoAssetMetadata?.size,
|
|
5368
|
+
videoAssetMetadata?.height,
|
|
5369
|
+
videoAssetMetadata?.width,
|
|
5370
|
+
videoAssetMetadata,
|
|
5371
|
+
config.drm_policy,
|
|
5372
|
+
validationError
|
|
5373
|
+
]);
|
|
5374
|
+
const { disableTextTrackConfig, disableUploadConfig } = pluginConfig, skipConfig = disableTextTrackConfig && disableUploadConfig;
|
|
3546
5375
|
if (useEffect(() => {
|
|
3547
|
-
skipConfig && startUpload(formatUploadConfig(config));
|
|
5376
|
+
skipConfig && startUpload(formatUploadConfig(config, secrets));
|
|
3548
5377
|
}, []), skipConfig) return null;
|
|
3549
|
-
const basicConfig = config.video_quality !== "plus" && config.video_quality !== "premium", maxSupportedResolution = RESOLUTION_TIERS.findIndex(
|
|
5378
|
+
const basicConfig = config.video_quality !== "plus" && config.video_quality !== "premium", playbackPolicySelected = config.public_policy || config.signed_policy || config.drm_policy, maxSupportedResolution = RESOLUTION_TIERS.findIndex(
|
|
3550
5379
|
(rt) => rt.value === pluginConfig.max_resolution_tier
|
|
3551
5380
|
);
|
|
3552
5381
|
return /* @__PURE__ */ jsx(
|
|
@@ -3580,12 +5409,12 @@ function UploadConfiguration({
|
|
|
3580
5409
|
/* @__PURE__ */ jsx(DocumentVideoIcon, { fontSize: "2em" }),
|
|
3581
5410
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
3582
5411
|
/* @__PURE__ */ jsx(Text, { textOverflow: "ellipsis", as: "h2", size: 3, children: stagedUpload.type === "file" ? stagedUpload.files[0].name : stagedUpload.url }),
|
|
3583
|
-
/* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: stagedUpload.type === "file" ? `Direct File Upload (${formatBytes(stagedUpload.files[0].size)})` :
|
|
5412
|
+
/* @__PURE__ */ jsx(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)" }),
|
|
3584
5413
|
stagedUpload.type === "file" && /* @__PURE__ */ jsxs(Stack, { space: 1, children: [
|
|
3585
|
-
|
|
3586
|
-
|
|
5414
|
+
isLoadingMetadata && /* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: "Reading video metadata..." }),
|
|
5415
|
+
videoAssetMetadata?.duration && !validationError && /* @__PURE__ */ jsxs(Text, { as: "p", size: 1, muted: !0, children: [
|
|
3587
5416
|
"Duration: ",
|
|
3588
|
-
formatSeconds(
|
|
5417
|
+
formatSeconds(videoAssetMetadata.duration)
|
|
3589
5418
|
] })
|
|
3590
5419
|
] })
|
|
3591
5420
|
] })
|
|
@@ -3631,160 +5460,37 @@ function UploadConfiguration({
|
|
|
3631
5460
|
}) })
|
|
3632
5461
|
}
|
|
3633
5462
|
),
|
|
3634
|
-
!basicConfig && maxSupportedResolution > 0 && /* @__PURE__ */ jsx(
|
|
3635
|
-
FormField$2,
|
|
3636
|
-
{
|
|
3637
|
-
title: "Resolution Tier",
|
|
3638
|
-
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3639
|
-
"The maximum",
|
|
3640
|
-
" ",
|
|
3641
|
-
/* @__PURE__ */ jsx(
|
|
3642
|
-
"a",
|
|
3643
|
-
{
|
|
3644
|
-
href: "https://docs.mux.com/api-reference#video/operation/create-direct-upload",
|
|
3645
|
-
target: "_blank",
|
|
3646
|
-
rel: "noopener noreferrer",
|
|
3647
|
-
children: "resolution_tier"
|
|
3648
|
-
}
|
|
3649
|
-
),
|
|
3650
|
-
" ",
|
|
3651
|
-
"your asset is encoded, stored, and streamed at."
|
|
3652
|
-
] }),
|
|
3653
|
-
children: /* @__PURE__ */ jsx(Flex, { gap: 3, wrap: "wrap", children: RESOLUTION_TIERS.map(({ value, label }, index) => {
|
|
3654
|
-
const inputId = `${id}--type-${value}`;
|
|
3655
|
-
return index > maxSupportedResolution ? null : /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3656
|
-
/* @__PURE__ */ jsx(
|
|
3657
|
-
Radio,
|
|
3658
|
-
{
|
|
3659
|
-
checked: config.max_resolution_tier === value,
|
|
3660
|
-
name: "asset-resolutiontier",
|
|
3661
|
-
onChange: (e) => dispatch({
|
|
3662
|
-
action: "max_resolution_tier",
|
|
3663
|
-
value: e.currentTarget.value
|
|
3664
|
-
}),
|
|
3665
|
-
value,
|
|
3666
|
-
id: inputId
|
|
3667
|
-
}
|
|
3668
|
-
),
|
|
3669
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: inputId, children: label })
|
|
3670
|
-
] }, value);
|
|
3671
|
-
}) })
|
|
3672
|
-
}
|
|
3673
|
-
),
|
|
3674
5463
|
!basicConfig && /* @__PURE__ */ jsx(FormField$2, { title: "Additional Configuration", children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
3675
5464
|
/* @__PURE__ */ jsx(PlaybackPolicy, { id, config, secrets, dispatch }),
|
|
3676
|
-
|
|
3677
|
-
|
|
5465
|
+
maxSupportedResolution > 0 && /* @__PURE__ */ jsx(
|
|
5466
|
+
ResolutionTierSelector,
|
|
3678
5467
|
{
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3684
|
-
/* @__PURE__ */ jsx(
|
|
3685
|
-
Radio,
|
|
3686
|
-
{
|
|
3687
|
-
checked: renditionMode === "standard",
|
|
3688
|
-
name: "rendition-mode",
|
|
3689
|
-
onChange: () => handleModeChange("standard"),
|
|
3690
|
-
value: "standard",
|
|
3691
|
-
id: `${id}--mode-standard`
|
|
3692
|
-
}
|
|
3693
|
-
),
|
|
3694
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--mode-standard`, children: "Standard" })
|
|
3695
|
-
] }),
|
|
3696
|
-
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3697
|
-
/* @__PURE__ */ jsx(
|
|
3698
|
-
Radio,
|
|
3699
|
-
{
|
|
3700
|
-
checked: renditionMode === "advanced",
|
|
3701
|
-
name: "rendition-mode",
|
|
3702
|
-
onChange: () => handleModeChange("advanced"),
|
|
3703
|
-
value: "advanced",
|
|
3704
|
-
id: `${id}--mode-advanced`
|
|
3705
|
-
}
|
|
3706
|
-
),
|
|
3707
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--mode-advanced`, children: "Advanced" })
|
|
3708
|
-
] })
|
|
3709
|
-
] }),
|
|
3710
|
-
renditionMode === "standard" && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
3711
|
-
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: [0, 2], children: [
|
|
3712
|
-
/* @__PURE__ */ jsx(
|
|
3713
|
-
Checkbox,
|
|
3714
|
-
{
|
|
3715
|
-
id: `${id}--highest`,
|
|
3716
|
-
style: { display: "block" },
|
|
3717
|
-
checked: config.static_renditions.includes("highest"),
|
|
3718
|
-
onChange: () => toggleRendition("highest")
|
|
3719
|
-
}
|
|
3720
|
-
),
|
|
3721
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--highest`, children: "Highest Resolution (up to 4K)" })
|
|
3722
|
-
] }),
|
|
3723
|
-
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: [0, 2], children: [
|
|
3724
|
-
/* @__PURE__ */ jsx(
|
|
3725
|
-
Checkbox,
|
|
3726
|
-
{
|
|
3727
|
-
id: `${id}--audio-only-standard`,
|
|
3728
|
-
style: { display: "block" },
|
|
3729
|
-
checked: config.static_renditions.includes("audio-only"),
|
|
3730
|
-
onChange: () => toggleRendition("audio-only")
|
|
3731
|
-
}
|
|
3732
|
-
),
|
|
3733
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--audio-only-standard`, children: "Audio Only (M4A)" })
|
|
3734
|
-
] })
|
|
3735
|
-
] }),
|
|
3736
|
-
renditionMode === "advanced" && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
3737
|
-
/* @__PURE__ */ jsx(Label$1, { size: 1, muted: !0, children: "Select specific resolutions:" }),
|
|
3738
|
-
/* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", children: ADVANCED_RESOLUTIONS.map(({ value, label }) => {
|
|
3739
|
-
const inputId = `${id}--resolution-${value}`;
|
|
3740
|
-
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
3741
|
-
/* @__PURE__ */ jsx(
|
|
3742
|
-
Checkbox,
|
|
3743
|
-
{
|
|
3744
|
-
id: inputId,
|
|
3745
|
-
style: { display: "block" },
|
|
3746
|
-
checked: config.static_renditions.includes(value),
|
|
3747
|
-
onChange: () => toggleRendition(value)
|
|
3748
|
-
}
|
|
3749
|
-
),
|
|
3750
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: inputId, size: 1, children: label })
|
|
3751
|
-
] }, value);
|
|
3752
|
-
}) }),
|
|
3753
|
-
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: [2, 2, 0, 2], children: [
|
|
3754
|
-
/* @__PURE__ */ jsx(
|
|
3755
|
-
Checkbox,
|
|
3756
|
-
{
|
|
3757
|
-
id: `${id}--audio-only-advanced`,
|
|
3758
|
-
style: { display: "block" },
|
|
3759
|
-
checked: config.static_renditions.includes("audio-only"),
|
|
3760
|
-
onChange: () => toggleRendition("audio-only")
|
|
3761
|
-
}
|
|
3762
|
-
),
|
|
3763
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: `${id}--audio-only-advanced`, children: "Audio Only (M4A)" })
|
|
3764
|
-
] })
|
|
3765
|
-
] })
|
|
3766
|
-
] })
|
|
5468
|
+
id,
|
|
5469
|
+
config,
|
|
5470
|
+
dispatch,
|
|
5471
|
+
maxSupportedResolution
|
|
3767
5472
|
}
|
|
3768
|
-
)
|
|
5473
|
+
),
|
|
5474
|
+
/* @__PURE__ */ jsx(StaticRenditionSelector, { id, config, dispatch }),
|
|
5475
|
+
!disableTextTrackConfig && /* @__PURE__ */ jsx(
|
|
5476
|
+
TextTracksEditor,
|
|
5477
|
+
{
|
|
5478
|
+
tracks: config.text_tracks,
|
|
5479
|
+
dispatch,
|
|
5480
|
+
defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
|
|
5481
|
+
}
|
|
5482
|
+
)
|
|
3769
5483
|
] }) })
|
|
3770
5484
|
] }),
|
|
3771
|
-
!disableTextTrackConfig && !basicConfig && /* @__PURE__ */ jsx(
|
|
3772
|
-
TextTracksEditor,
|
|
3773
|
-
{
|
|
3774
|
-
tracks: config.text_tracks,
|
|
3775
|
-
dispatch,
|
|
3776
|
-
defaultLang: pluginConfig.defaultAutogeneratedSubtitleLang
|
|
3777
|
-
}
|
|
3778
|
-
),
|
|
3779
5485
|
/* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsx(
|
|
3780
5486
|
Button,
|
|
3781
5487
|
{
|
|
3782
|
-
disabled: !basicConfig && !
|
|
5488
|
+
disabled: !basicConfig && !playbackPolicySelected || validationError !== null || isLoadingMetadata || isLoadingFileSize && !canSkipFileSizeValidation,
|
|
3783
5489
|
icon: UploadIcon,
|
|
3784
5490
|
text: "Upload",
|
|
3785
5491
|
tone: "positive",
|
|
3786
5492
|
onClick: () => {
|
|
3787
|
-
validationError || startUpload(formatUploadConfig(config));
|
|
5493
|
+
validationError || startUpload(formatUploadConfig(config, secrets));
|
|
3788
5494
|
}
|
|
3789
5495
|
}
|
|
3790
5496
|
) })
|
|
@@ -3792,11 +5498,14 @@ function UploadConfiguration({
|
|
|
3792
5498
|
}
|
|
3793
5499
|
);
|
|
3794
5500
|
}
|
|
3795
|
-
function
|
|
3796
|
-
const
|
|
3797
|
-
return config.public_policy &&
|
|
5501
|
+
function setAdvancedPlaybackPolicy(config, secrets) {
|
|
5502
|
+
const advanced_playback_policies = [];
|
|
5503
|
+
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({
|
|
5504
|
+
policy: "drm",
|
|
5505
|
+
drm_configuration_id: secrets.drmConfigId ?? void 0
|
|
5506
|
+
}) : console.error("Selected DRM Policy but missing DRM Configuration Id")), advanced_playback_policies;
|
|
3798
5507
|
}
|
|
3799
|
-
function formatUploadConfig(config) {
|
|
5508
|
+
function formatUploadConfig(config, secrets) {
|
|
3800
5509
|
const generated_subtitles = config.text_tracks.filter(isAutogeneratedTrack).map((track) => ({
|
|
3801
5510
|
name: track.name,
|
|
3802
5511
|
language_code: track.language_code
|
|
@@ -3820,7 +5529,7 @@ function formatUploadConfig(config) {
|
|
|
3820
5529
|
)
|
|
3821
5530
|
],
|
|
3822
5531
|
static_renditions: config.static_renditions.length > 0 ? config.static_renditions.map((resolution) => ({ resolution })) : void 0,
|
|
3823
|
-
|
|
5532
|
+
advanced_playback_policies: setAdvancedPlaybackPolicy(config, secrets),
|
|
3824
5533
|
max_resolution_tier: config.max_resolution_tier,
|
|
3825
5534
|
video_quality: config.video_quality,
|
|
3826
5535
|
normalize_audio: config.normalize_audio
|
|
@@ -3849,12 +5558,10 @@ function withFocusRing(component) {
|
|
|
3849
5558
|
`;
|
|
3850
5559
|
});
|
|
3851
5560
|
}
|
|
3852
|
-
const
|
|
5561
|
+
const UploadCardWithFocusRing = withFocusRing(Card), UploadCard = forwardRef(
|
|
3853
5562
|
({ children, tone, onPaste, onDrop, onDragEnter, onDragLeave, onDragOver }, forwardedRef) => {
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3856
|
-
}, []), handleKeyUp = useCallback((event) => {
|
|
3857
|
-
(event.keyCode == ctrlKey || event.keyCode == cmdKey) && (ctrlDown.current = !1);
|
|
5563
|
+
const inputRef = useRef(null), handleKeyDown = useCallback((event) => {
|
|
5564
|
+
event.target.closest("#vtt-url") || (event.ctrlKey || event.metaKey) && event.key === "v" && inputRef.current.focus();
|
|
3858
5565
|
}, []);
|
|
3859
5566
|
return /* @__PURE__ */ jsxs(
|
|
3860
5567
|
UploadCardWithFocusRing,
|
|
@@ -3866,14 +5573,13 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(Card),
|
|
|
3866
5573
|
shadow: 0,
|
|
3867
5574
|
tabIndex: 0,
|
|
3868
5575
|
onKeyDown: handleKeyDown,
|
|
3869
|
-
onKeyUp: handleKeyUp,
|
|
3870
5576
|
onPaste,
|
|
3871
5577
|
onDrop,
|
|
3872
5578
|
onDragEnter,
|
|
3873
5579
|
onDragLeave,
|
|
3874
5580
|
onDragOver,
|
|
3875
5581
|
children: [
|
|
3876
|
-
/* @__PURE__ */ jsx(HiddenInput$1, { ref: inputRef
|
|
5582
|
+
/* @__PURE__ */ jsx(HiddenInput$1, { ref: inputRef }),
|
|
3877
5583
|
children
|
|
3878
5584
|
]
|
|
3879
5585
|
}
|
|
@@ -4035,8 +5741,13 @@ function Uploader(props) {
|
|
|
4035
5741
|
case "reset":
|
|
4036
5742
|
case "complete":
|
|
4037
5743
|
return uploadRef.current?.unsubscribe(), uploadRef.current = null, uploadingDocumentId.current = null, INITIAL_STATE;
|
|
4038
|
-
case "error":
|
|
4039
|
-
|
|
5744
|
+
case "error": {
|
|
5745
|
+
uploadRef.current?.unsubscribe(), uploadRef.current = null, uploadingDocumentId.current = null;
|
|
5746
|
+
let error = action.error;
|
|
5747
|
+
return isServerError(action.error) && hasPlaybackPolicy(action.settings, "drm") && (error = new Error(
|
|
5748
|
+
"Unknown Error while uploading DRM protected content. Make sure your DRM configuration ID is valid and set correctly"
|
|
5749
|
+
)), Object.assign({}, INITIAL_STATE, { error });
|
|
5750
|
+
}
|
|
4040
5751
|
default:
|
|
4041
5752
|
return prev;
|
|
4042
5753
|
}
|
|
@@ -4115,7 +5826,7 @@ function Uploader(props) {
|
|
|
4115
5826
|
}
|
|
4116
5827
|
},
|
|
4117
5828
|
complete: () => dispatch({ action: "complete" }),
|
|
4118
|
-
error: (error) => dispatch({ action: "error", error })
|
|
5829
|
+
error: (error) => dispatch({ action: "error", error, settings })
|
|
4119
5830
|
});
|
|
4120
5831
|
}, invalidFileToast = useCallback(() => {
|
|
4121
5832
|
toast.push({
|
|
@@ -4131,6 +5842,8 @@ function Uploader(props) {
|
|
|
4131
5842
|
input: { type: "file", files }
|
|
4132
5843
|
});
|
|
4133
5844
|
}, handlePaste = (event) => {
|
|
5845
|
+
if (event.target.closest("#vtt-url"))
|
|
5846
|
+
return;
|
|
4134
5847
|
event.preventDefault(), event.stopPropagation();
|
|
4135
5848
|
const url = (event.clipboardData || window.clipboardData)?.getData("text")?.trim();
|
|
4136
5849
|
if (!isValidUrl(url)) {
|
|
@@ -4164,11 +5877,11 @@ function Uploader(props) {
|
|
|
4164
5877
|
idx > -1 && dragEnteredEls.current.splice(idx, 1), dragEnteredEls.current.length === 0 && setDragState(null);
|
|
4165
5878
|
};
|
|
4166
5879
|
if (state.error !== null) {
|
|
4167
|
-
const error =
|
|
5880
|
+
const error = state.error;
|
|
4168
5881
|
return /* @__PURE__ */ jsxs(Flex, { gap: 3, direction: "column", justify: "center", align: "center", children: [
|
|
4169
5882
|
/* @__PURE__ */ jsx(Text, { size: 5, muted: !0, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, {}) }),
|
|
4170
5883
|
/* @__PURE__ */ jsx(Text, { children: "Something went wrong" }),
|
|
4171
|
-
error instanceof Error && error.message && /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: error.message }),
|
|
5884
|
+
error instanceof Error && error.message && /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, weight: "semibold", style: { textAlign: "center" }, children: error.message }),
|
|
4172
5885
|
/* @__PURE__ */ jsx(Button, { text: "Upload another file", onClick: () => dispatch({ action: "reset" }) })
|
|
4173
5886
|
] });
|
|
4174
5887
|
}
|
|
@@ -4253,6 +5966,7 @@ function Uploader(props) {
|
|
|
4253
5966
|
props.dialogState === "select-video" && /* @__PURE__ */ jsx(
|
|
4254
5967
|
InputBrowser,
|
|
4255
5968
|
{
|
|
5969
|
+
config: props.config,
|
|
4256
5970
|
asset: props.asset,
|
|
4257
5971
|
onChange: props.onChange,
|
|
4258
5972
|
setDialogState: props.setDialogState
|
|
@@ -4499,6 +6213,7 @@ const muxVideoSchema = {
|
|
|
4499
6213
|
normalize_audio: !1,
|
|
4500
6214
|
defaultPublic: !0,
|
|
4501
6215
|
defaultSigned: !1,
|
|
6216
|
+
defaultDrm: !1,
|
|
4502
6217
|
tool: DEFAULT_TOOL_CONFIG,
|
|
4503
6218
|
allowedRolesForConfiguration: [],
|
|
4504
6219
|
acceptedMimeTypes: ["video/*", "audio/*"]
|