sanity-plugin-mux-input 2.12.1 → 2.14.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/LICENSE +1 -1
- package/README.md +133 -0
- package/dist/index.d.mts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +1564 -153
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1565 -154
- 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/components/AddCaptionDialog.tsx +421 -0
- package/src/components/CaptionsDialog.tsx +23 -0
- package/src/components/EditCaptionDialog.tsx +508 -0
- package/src/components/FileInputButton.tsx +2 -2
- package/src/components/Onboard.tsx +2 -2
- package/src/components/PageSelector.tsx +57 -0
- package/src/components/Player.tsx +4 -3
- package/src/components/PlayerActionsMenu.tsx +17 -8
- package/src/components/TextTracksManager.tsx +781 -0
- package/src/components/UploadConfiguration.tsx +181 -4
- package/src/components/UploadPlaceholder.tsx +14 -6
- package/src/components/Uploader.styled.tsx +8 -15
- package/src/components/Uploader.tsx +61 -6
- package/src/components/VideoDetails/VideoDetails.tsx +16 -0
- package/src/components/VideoInBrowser.tsx +2 -7
- package/src/components/VideoPlayer.tsx +35 -6
- package/src/components/VideosBrowser.tsx +9 -1
- package/src/components/icons/Audio.tsx +13 -0
- package/src/hooks/useAccessControl.ts +1 -0
- package/src/hooks/useDialogState.ts +1 -1
- package/src/util/getVideoMetadata.ts +3 -1
- package/src/util/textTracks.ts +219 -0
- package/src/util/types.ts +56 -3
- package/src/components/FileInputArea.tsx +0 -93
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 { useTheme_v2, Stack, Flex, Box, Text, Button, Dialog, Card, TextInput, Checkbox, Code, Inline, Spinner, Heading, MenuButton, Menu, MenuItem,
|
|
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 { useTheme_v2, Stack, Flex, Box, Text, Button, Dialog, Card, 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
5
|
import React, { useState, useMemo, useCallback, useReducer, useId, memo, useRef, useEffect, createContext, useContext, isValidElement, PureComponent, createElement, forwardRef, Suspense } from "react";
|
|
6
6
|
import compact from "lodash/compact.js";
|
|
7
7
|
import toLower from "lodash/toLower.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
|
{
|
|
@@ -567,6 +567,51 @@ function listAssets(client, options) {
|
|
|
567
567
|
query
|
|
568
568
|
});
|
|
569
569
|
}
|
|
570
|
+
function addTextTrackFromUrl(client, assetId, vttUrl, options) {
|
|
571
|
+
const { dataset } = client.config();
|
|
572
|
+
return client.request({
|
|
573
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks`,
|
|
574
|
+
withCredentials: !0,
|
|
575
|
+
method: "POST",
|
|
576
|
+
body: {
|
|
577
|
+
url: vttUrl,
|
|
578
|
+
type: "text",
|
|
579
|
+
language_code: options.language_code,
|
|
580
|
+
name: options.name,
|
|
581
|
+
text_type: options.text_type || "subtitles"
|
|
582
|
+
},
|
|
583
|
+
headers: {
|
|
584
|
+
"Content-Type": "application/json"
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function generateSubtitles(client, assetId, audioTrackId, options) {
|
|
589
|
+
const { dataset } = client.config();
|
|
590
|
+
return client.request({
|
|
591
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${audioTrackId}/generate-subtitles`,
|
|
592
|
+
withCredentials: !0,
|
|
593
|
+
method: "POST",
|
|
594
|
+
body: {
|
|
595
|
+
generated_subtitles: [
|
|
596
|
+
{
|
|
597
|
+
language_code: options.language_code,
|
|
598
|
+
name: options.name
|
|
599
|
+
}
|
|
600
|
+
]
|
|
601
|
+
},
|
|
602
|
+
headers: {
|
|
603
|
+
"Content-Type": "application/json"
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function deleteTextTrack(client, assetId, trackId) {
|
|
608
|
+
const { dataset } = client.config();
|
|
609
|
+
return client.request({
|
|
610
|
+
url: `/addons/mux/assets/${dataset}/${assetId}/tracks/${trackId}`,
|
|
611
|
+
withCredentials: !0,
|
|
612
|
+
method: "DELETE"
|
|
613
|
+
});
|
|
614
|
+
}
|
|
570
615
|
const ASSETS_PER_PAGE = 100;
|
|
571
616
|
async function fetchMuxAssetsPage(client, cursor) {
|
|
572
617
|
try {
|
|
@@ -1104,6 +1149,46 @@ function ImportVideosFromMux() {
|
|
|
1104
1149
|
if (importAssets.hasSecrets)
|
|
1105
1150
|
return importAssets.dialogOpen ? /* @__PURE__ */ jsx(ImportVideosDialog, { ...importAssets }) : /* @__PURE__ */ jsx(Button, { mode: "bleed", text: "Import from Mux", onClick: importAssets.openDialog });
|
|
1106
1151
|
}
|
|
1152
|
+
const PageSelector = (props) => {
|
|
1153
|
+
const page = props.page, setPage = props.setPage;
|
|
1154
|
+
return useEffect(() => {
|
|
1155
|
+
const clamped = Math.min(props.total - 1, Math.max(0, page));
|
|
1156
|
+
page !== clamped && setPage(clamped);
|
|
1157
|
+
}, [page, props.total, setPage]), /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1158
|
+
/* @__PURE__ */ jsx(
|
|
1159
|
+
Button,
|
|
1160
|
+
{
|
|
1161
|
+
icon: ChevronLeftIcon,
|
|
1162
|
+
mode: "bleed",
|
|
1163
|
+
padding: 3,
|
|
1164
|
+
style: { cursor: "pointer" },
|
|
1165
|
+
disabled: page <= 0,
|
|
1166
|
+
onClick: () => {
|
|
1167
|
+
setPage((page2) => Math.min(props.total - 1, Math.max(0, page2 - 1)));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
),
|
|
1171
|
+
/* @__PURE__ */ jsxs(Label$1, { muted: !0, children: [
|
|
1172
|
+
"Page ",
|
|
1173
|
+
page + 1,
|
|
1174
|
+
"/",
|
|
1175
|
+
props.total
|
|
1176
|
+
] }),
|
|
1177
|
+
/* @__PURE__ */ jsx(
|
|
1178
|
+
Button,
|
|
1179
|
+
{
|
|
1180
|
+
icon: ChevronRightIcon,
|
|
1181
|
+
mode: "bleed",
|
|
1182
|
+
padding: 3,
|
|
1183
|
+
style: { cursor: "pointer" },
|
|
1184
|
+
disabled: page >= props.total - 1,
|
|
1185
|
+
onClick: () => {
|
|
1186
|
+
setPage((page2) => Math.min(props.total - 1, Math.max(0, page2 + 1)));
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
)
|
|
1190
|
+
] });
|
|
1191
|
+
};
|
|
1107
1192
|
function useResyncMuxMetadata() {
|
|
1108
1193
|
const documentStore = useDocumentStore(), client = useClient$1({
|
|
1109
1194
|
apiVersion: SANITY_API_VERSION
|
|
@@ -1346,38 +1431,1249 @@ const SpinnerBox = () => /* @__PURE__ */ jsx(
|
|
|
1346
1431
|
/* @__PURE__ */ jsx(Text, { size: (props.size || 1) + 1, muted: !0, children: /* @__PURE__ */ jsx(Icon, {}) }),
|
|
1347
1432
|
/* @__PURE__ */ jsx(Text, { size: props.size || 1, muted: props.muted, children: props.text })
|
|
1348
1433
|
] });
|
|
1349
|
-
};
|
|
1350
|
-
function ResolutionIcon(props) {
|
|
1351
|
-
return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props, children: /* @__PURE__ */ jsx(
|
|
1352
|
-
"path",
|
|
1434
|
+
};
|
|
1435
|
+
function ResolutionIcon(props) {
|
|
1436
|
+
return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props, children: /* @__PURE__ */ jsx(
|
|
1437
|
+
"path",
|
|
1438
|
+
{
|
|
1439
|
+
fill: "currentColor",
|
|
1440
|
+
d: "M20 9V6h-3V4h5v5h-2ZM2 9V4h5v2H4v3H2Zm15 11v-2h3v-3h2v5h-5ZM2 20v-5h2v3h3v2H2Zm4-4V8h12v8H6Zm2-2h8v-4H8v4Zm0 0v-4v4Z"
|
|
1441
|
+
}
|
|
1442
|
+
) });
|
|
1443
|
+
}
|
|
1444
|
+
function StopWatchIcon(props) {
|
|
1445
|
+
return /* @__PURE__ */ jsxs(
|
|
1446
|
+
"svg",
|
|
1447
|
+
{
|
|
1448
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1449
|
+
width: "1em",
|
|
1450
|
+
height: "1em",
|
|
1451
|
+
viewBox: "0 0 512 512",
|
|
1452
|
+
...props,
|
|
1453
|
+
children: [
|
|
1454
|
+
/* @__PURE__ */ jsx("path", { d: "M232 306.667h48V176h-48v130.667z", fill: "currentColor" }),
|
|
1455
|
+
/* @__PURE__ */ jsx(
|
|
1456
|
+
"path",
|
|
1457
|
+
{
|
|
1458
|
+
d: "M407.67 170.271l30.786-30.786-33.942-33.941-30.785 30.786C341.217 111.057 300.369 96 256 96 149.961 96 64 181.961 64 288s85.961 192 192 192 192-85.961 192-192c0-44.369-15.057-85.217-40.33-117.729zm-45.604 223.795C333.734 422.398 296.066 438 256 438s-77.735-15.602-106.066-43.934C121.602 365.735 106 328.066 106 288s15.602-77.735 43.934-106.066C178.265 153.602 215.934 138 256 138s77.734 15.602 106.066 43.934C390.398 210.265 406 247.934 406 288s-15.602 77.735-43.934 106.066z",
|
|
1459
|
+
fill: "currentColor"
|
|
1460
|
+
}
|
|
1461
|
+
),
|
|
1462
|
+
/* @__PURE__ */ jsx("path", { d: "M192 32h128v48H192z", fill: "currentColor" })
|
|
1463
|
+
]
|
|
1464
|
+
}
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
function extractErrorMessage(error, defaultMessage = "Failed to process request") {
|
|
1468
|
+
let message = "";
|
|
1469
|
+
if (error && typeof error == "object") {
|
|
1470
|
+
const err = error;
|
|
1471
|
+
message = err.response?.body?.message || err.message || "";
|
|
1472
|
+
} else typeof error == "string" && (message = error);
|
|
1473
|
+
if (!message)
|
|
1474
|
+
return defaultMessage;
|
|
1475
|
+
const match = message.match(/\(([^)]+)\)/);
|
|
1476
|
+
if (match && match[1])
|
|
1477
|
+
return match[1];
|
|
1478
|
+
if (message.includes("responded with")) {
|
|
1479
|
+
const parts = message.split("(");
|
|
1480
|
+
if (parts.length > 1)
|
|
1481
|
+
return parts[parts.length - 1].replace(")", "").trim();
|
|
1482
|
+
}
|
|
1483
|
+
return message;
|
|
1484
|
+
}
|
|
1485
|
+
async function pollTrackStatus(options) {
|
|
1486
|
+
const {
|
|
1487
|
+
client,
|
|
1488
|
+
assetId,
|
|
1489
|
+
trackName,
|
|
1490
|
+
trackLanguageCode,
|
|
1491
|
+
maxAttempts = 10,
|
|
1492
|
+
onTrackFound,
|
|
1493
|
+
onTrackErrored,
|
|
1494
|
+
onTrackReady
|
|
1495
|
+
} = options, trimmedName = trackName.trim(), trimmedLanguageCode = trackLanguageCode.trim();
|
|
1496
|
+
let newTrack, attempts = 0, trackFound = !1;
|
|
1497
|
+
const findTrack = (textTracks) => {
|
|
1498
|
+
let foundTrack = textTracks.find(
|
|
1499
|
+
(track) => track.name === trimmedName && track.language_code === trimmedLanguageCode
|
|
1500
|
+
);
|
|
1501
|
+
return foundTrack || (foundTrack = textTracks.find((track) => track.language_code === trimmedLanguageCode)), !foundTrack && textTracks.length > 0 && (foundTrack = textTracks[textTracks.length - 1]), foundTrack;
|
|
1502
|
+
};
|
|
1503
|
+
for (; attempts < maxAttempts; ) {
|
|
1504
|
+
try {
|
|
1505
|
+
attempts > 0 && await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1506
|
+
const textTracks = (await getAsset(client, assetId)).data.tracks?.filter((track) => track.type === "text") || [], foundTrack = findTrack(textTracks);
|
|
1507
|
+
if (!foundTrack) {
|
|
1508
|
+
attempts++;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (trackFound = !0, newTrack = foundTrack, onTrackFound && onTrackFound(foundTrack), foundTrack.status === "ready") {
|
|
1512
|
+
onTrackReady && onTrackReady(foundTrack);
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1515
|
+
if (foundTrack.status === "errored")
|
|
1516
|
+
return onTrackErrored && onTrackErrored(foundTrack), {
|
|
1517
|
+
track: foundTrack,
|
|
1518
|
+
found: !0,
|
|
1519
|
+
status: "errored"
|
|
1520
|
+
};
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
console.error("Failed to fetch updated asset:", error);
|
|
1523
|
+
}
|
|
1524
|
+
attempts++;
|
|
1525
|
+
}
|
|
1526
|
+
return !newTrack || !trackFound ? {
|
|
1527
|
+
track: void 0,
|
|
1528
|
+
found: !1,
|
|
1529
|
+
status: "not-found"
|
|
1530
|
+
} : newTrack.status === "preparing" ? {
|
|
1531
|
+
track: newTrack,
|
|
1532
|
+
found: !0,
|
|
1533
|
+
status: "preparing"
|
|
1534
|
+
} : {
|
|
1535
|
+
track: newTrack,
|
|
1536
|
+
found: !0,
|
|
1537
|
+
status: "ready"
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
async function downloadVttFile(client, asset, track) {
|
|
1541
|
+
if (!track.id)
|
|
1542
|
+
throw new Error("Track ID is missing");
|
|
1543
|
+
if (track.status !== "ready")
|
|
1544
|
+
throw new Error(`Track is not ready yet. Status: ${track.status}`);
|
|
1545
|
+
if (!asset.assetId)
|
|
1546
|
+
throw new Error("Asset ID is required");
|
|
1547
|
+
const playbackId = getPlaybackId(asset);
|
|
1548
|
+
if (!playbackId)
|
|
1549
|
+
throw new Error("Playback ID is required");
|
|
1550
|
+
const playbackPolicy = getPlaybackPolicy(asset);
|
|
1551
|
+
let downloadUrl = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
1552
|
+
if (playbackPolicy === "signed") {
|
|
1553
|
+
const token = generateJwt(client, playbackId, "v");
|
|
1554
|
+
downloadUrl += `?token=${token}`;
|
|
1555
|
+
}
|
|
1556
|
+
const response = await fetch(downloadUrl);
|
|
1557
|
+
if (!response.ok)
|
|
1558
|
+
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
1559
|
+
const blob = await response.blob(), blobUrl = URL.createObjectURL(blob), link = document.createElement("a");
|
|
1560
|
+
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);
|
|
1561
|
+
}
|
|
1562
|
+
const SUPPORTED_MUX_LANGUAGES = [
|
|
1563
|
+
{ label: "English", code: "en", state: "Stable" },
|
|
1564
|
+
{ label: "Spanish", code: "es", state: "Stable" },
|
|
1565
|
+
{ label: "Italian", code: "it", state: "Stable" },
|
|
1566
|
+
{ label: "Portuguese", code: "pt", state: "Stable" },
|
|
1567
|
+
{ label: "German", code: "de", state: "Stable" },
|
|
1568
|
+
{ label: "French", code: "fr", state: "Stable" },
|
|
1569
|
+
{ label: "Polish", code: "pl", state: "Beta" },
|
|
1570
|
+
{ label: "Russian", code: "ru", state: "Beta" },
|
|
1571
|
+
{ label: "Dutch", code: "nl", state: "Beta" },
|
|
1572
|
+
{ label: "Catalan", code: "ca", state: "Beta" },
|
|
1573
|
+
{ label: "Turkish", code: "tr", state: "Beta" },
|
|
1574
|
+
{ label: "Swedish", code: "sv", state: "Beta" },
|
|
1575
|
+
{ label: "Ukrainian", code: "uk", state: "Beta" },
|
|
1576
|
+
{ label: "Norwegian", code: "no", state: "Beta" },
|
|
1577
|
+
{ label: "Finnish", code: "fi", state: "Beta" },
|
|
1578
|
+
{ label: "Slovak", code: "sk", state: "Beta" },
|
|
1579
|
+
{ label: "Greek", code: "el", state: "Beta" },
|
|
1580
|
+
{ label: "Czech", code: "cs", state: "Beta" },
|
|
1581
|
+
{ label: "Croatian", code: "hr", state: "Beta" },
|
|
1582
|
+
{ label: "Danish", code: "da", state: "Beta" },
|
|
1583
|
+
{ label: "Romanian", code: "ro", state: "Beta" },
|
|
1584
|
+
{ label: "Bulgarian", code: "bg", state: "Beta" }
|
|
1585
|
+
];
|
|
1586
|
+
function isCustomTextTrack(track) {
|
|
1587
|
+
return track.type !== "autogenerated";
|
|
1588
|
+
}
|
|
1589
|
+
function isAutogeneratedTrack(track) {
|
|
1590
|
+
return track.type === "autogenerated";
|
|
1591
|
+
}
|
|
1592
|
+
const LANGUAGE_OPTIONS$1 = LanguagesList.getAllCodes().map((code) => ({
|
|
1593
|
+
value: code,
|
|
1594
|
+
label: LanguagesList.getNativeName(code)
|
|
1595
|
+
})), MUX_LANGUAGE_OPTIONS = SUPPORTED_MUX_LANGUAGES.map((lang) => ({
|
|
1596
|
+
value: lang.code,
|
|
1597
|
+
label: lang.label
|
|
1598
|
+
}));
|
|
1599
|
+
function AddCaptionDialog({ asset, onAdd, onClose }) {
|
|
1600
|
+
const client = useClient(), toast = useToast(), dialogId = `AddCaptionDialog${useId()}`, [isAutogenerated, setIsAutogenerated] = useState(!1), [vttUrl, setVttUrl] = useState(""), [languageCode, setLanguageCode] = useState(""), [selectedLanguage, setSelectedLanguage] = useState(
|
|
1601
|
+
null
|
|
1602
|
+
), [name2, setName] = useState(""), [isSubmitting, setIsSubmitting] = useState(!1), [selectedFile, setSelectedFile] = useState(null), fileInputRef = useRef(null), uploadVttFile = async (file) => (await client.assets.upload("file", file, {
|
|
1603
|
+
filename: file.name
|
|
1604
|
+
})).url, handleAddTrackFromUrl = async () => {
|
|
1605
|
+
if (!asset.assetId)
|
|
1606
|
+
throw new Error("Asset ID is required");
|
|
1607
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim();
|
|
1608
|
+
let vttUrlToUse = vttUrl.trim();
|
|
1609
|
+
if (selectedFile)
|
|
1610
|
+
try {
|
|
1611
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
1612
|
+
} catch (uploadError) {
|
|
1613
|
+
throw toast.push({
|
|
1614
|
+
title: "Failed to upload VTT file",
|
|
1615
|
+
status: "error",
|
|
1616
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
1617
|
+
}), setIsSubmitting(!1), uploadError;
|
|
1618
|
+
}
|
|
1619
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
1620
|
+
language_code: trimmedLanguageCode,
|
|
1621
|
+
name: trimmedName,
|
|
1622
|
+
text_type: "subtitles"
|
|
1623
|
+
});
|
|
1624
|
+
const result = await pollTrackStatus({
|
|
1625
|
+
client,
|
|
1626
|
+
assetId: asset.assetId,
|
|
1627
|
+
trackName: trimmedName,
|
|
1628
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
1629
|
+
onTrackErrored: (track) => {
|
|
1630
|
+
const errorMessage = track.error?.messages?.[0] || track.error?.type || "The track failed to download from the provided URL";
|
|
1631
|
+
toast.push({
|
|
1632
|
+
title: "Caption track failed",
|
|
1633
|
+
status: "error",
|
|
1634
|
+
description: errorMessage
|
|
1635
|
+
}), onAdd(track), onClose();
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
if (!result.found || !result.track) {
|
|
1639
|
+
toast.push({
|
|
1640
|
+
title: "Caption track may have been added",
|
|
1641
|
+
status: "warning",
|
|
1642
|
+
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."
|
|
1643
|
+
}), onClose();
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (result.status !== "errored") {
|
|
1647
|
+
if (result.status === "preparing") {
|
|
1648
|
+
toast.push({
|
|
1649
|
+
title: "Caption track is processing",
|
|
1650
|
+
status: "info",
|
|
1651
|
+
description: "The track was created and is being processed. It will appear in the list shortly."
|
|
1652
|
+
}), onAdd(result.track), onClose();
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
toast.push({
|
|
1656
|
+
title: "Caption track added",
|
|
1657
|
+
status: "success",
|
|
1658
|
+
description: "Caption track added successfully"
|
|
1659
|
+
}), onAdd(result.track), onClose();
|
|
1660
|
+
}
|
|
1661
|
+
}, handleGenerateSubtitles = async () => {
|
|
1662
|
+
if (!asset.assetId)
|
|
1663
|
+
throw new Error("Asset ID is required");
|
|
1664
|
+
const audioTrack = (await getAsset(client, asset.assetId)).data.tracks?.find((track) => track.type === "audio");
|
|
1665
|
+
if (!audioTrack || !audioTrack.id)
|
|
1666
|
+
throw toast.push({
|
|
1667
|
+
title: "No audio track found",
|
|
1668
|
+
status: "error",
|
|
1669
|
+
description: "The asset does not have an audio track. Auto-generated subtitles require an audio track."
|
|
1670
|
+
}), new Error("No audio track found");
|
|
1671
|
+
await generateSubtitles(client, asset.assetId, audioTrack.id, {
|
|
1672
|
+
language_code: languageCode.trim(),
|
|
1673
|
+
name: name2.trim()
|
|
1674
|
+
});
|
|
1675
|
+
const mockTrack = {
|
|
1676
|
+
type: "text",
|
|
1677
|
+
id: `generating-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
1678
|
+
text_type: "subtitles",
|
|
1679
|
+
text_source: "generated_live",
|
|
1680
|
+
language_code: languageCode.trim(),
|
|
1681
|
+
name: name2.trim(),
|
|
1682
|
+
status: "preparing"
|
|
1683
|
+
};
|
|
1684
|
+
toast.push({
|
|
1685
|
+
title: "Generating subtitles",
|
|
1686
|
+
status: "success",
|
|
1687
|
+
description: "This may take a few minutes"
|
|
1688
|
+
}), onAdd(mockTrack), onClose();
|
|
1689
|
+
}, handleSubmit = async () => {
|
|
1690
|
+
if (!isAutogenerated) {
|
|
1691
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
1692
|
+
toast.push({
|
|
1693
|
+
title: "VTT file or URL required",
|
|
1694
|
+
status: "error",
|
|
1695
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
1696
|
+
});
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (vttUrl.trim() && !selectedFile)
|
|
1700
|
+
try {
|
|
1701
|
+
new URL(vttUrl.trim());
|
|
1702
|
+
} catch {
|
|
1703
|
+
toast.push({
|
|
1704
|
+
title: "Invalid URL",
|
|
1705
|
+
status: "error",
|
|
1706
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
1707
|
+
});
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
if (!name2.trim()) {
|
|
1712
|
+
toast.push({
|
|
1713
|
+
title: "Audio name required",
|
|
1714
|
+
status: "error",
|
|
1715
|
+
description: "Please enter an audio name for this caption track"
|
|
1716
|
+
});
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (!languageCode.trim()) {
|
|
1720
|
+
toast.push({
|
|
1721
|
+
title: "Language code required",
|
|
1722
|
+
status: "error",
|
|
1723
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
1724
|
+
});
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
setIsSubmitting(!0);
|
|
1728
|
+
try {
|
|
1729
|
+
isAutogenerated ? await handleGenerateSubtitles() : await handleAddTrackFromUrl();
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
toast.push({
|
|
1732
|
+
title: "Failed to add caption track",
|
|
1733
|
+
status: "error",
|
|
1734
|
+
description: extractErrorMessage(error, "Failed to add caption track")
|
|
1735
|
+
});
|
|
1736
|
+
} finally {
|
|
1737
|
+
setIsSubmitting(!1);
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
return /* @__PURE__ */ jsx(
|
|
1741
|
+
Dialog,
|
|
1742
|
+
{
|
|
1743
|
+
id: dialogId,
|
|
1744
|
+
header: "Add Caption Track",
|
|
1745
|
+
onClose,
|
|
1746
|
+
width: 1,
|
|
1747
|
+
onClickOutside: onClose,
|
|
1748
|
+
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, children: [
|
|
1749
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1750
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", marginBottom: 3, children: [
|
|
1751
|
+
/* @__PURE__ */ jsx(
|
|
1752
|
+
Checkbox,
|
|
1753
|
+
{
|
|
1754
|
+
id: "autogenerated-checkbox",
|
|
1755
|
+
style: { display: "block" },
|
|
1756
|
+
checked: isAutogenerated,
|
|
1757
|
+
onChange: (e) => {
|
|
1758
|
+
setIsAutogenerated(e.currentTarget.checked), e.currentTarget.checked && setVttUrl("");
|
|
1759
|
+
},
|
|
1760
|
+
disabled: isSubmitting
|
|
1761
|
+
}
|
|
1762
|
+
),
|
|
1763
|
+
/* @__PURE__ */ jsx(Flex, { flex: 1, paddingLeft: 2, children: /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx("label", { htmlFor: "autogenerated-checkbox", children: "Generate captions" }) }) })
|
|
1764
|
+
] }),
|
|
1765
|
+
!isAutogenerated && /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1766
|
+
/* @__PURE__ */ jsxs(Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
1767
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", children: [
|
|
1768
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: selectedFile ? `Selected: ${selectedFile.name}` : "No file selected" }),
|
|
1769
|
+
/* @__PURE__ */ jsx(
|
|
1770
|
+
Button,
|
|
1771
|
+
{
|
|
1772
|
+
icon: UploadIcon,
|
|
1773
|
+
text: "Select File",
|
|
1774
|
+
mode: "ghost",
|
|
1775
|
+
tone: "primary",
|
|
1776
|
+
fontSize: 1,
|
|
1777
|
+
padding: 2,
|
|
1778
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1779
|
+
disabled: isSubmitting
|
|
1780
|
+
}
|
|
1781
|
+
)
|
|
1782
|
+
] }),
|
|
1783
|
+
/* @__PURE__ */ jsx(
|
|
1784
|
+
"input",
|
|
1785
|
+
{
|
|
1786
|
+
ref: fileInputRef,
|
|
1787
|
+
type: "file",
|
|
1788
|
+
accept: ".vtt,text/vtt",
|
|
1789
|
+
style: { display: "none" },
|
|
1790
|
+
onChange: (e) => {
|
|
1791
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
)
|
|
1795
|
+
] }),
|
|
1796
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, style: { textAlign: "center" }, children: "Or enter the VTT file URL" }),
|
|
1797
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1798
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
1799
|
+
/* @__PURE__ */ jsx(
|
|
1800
|
+
TextInput,
|
|
1801
|
+
{
|
|
1802
|
+
id: "vtt-url",
|
|
1803
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
1804
|
+
value: vttUrl,
|
|
1805
|
+
onChange: (e) => {
|
|
1806
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
1807
|
+
},
|
|
1808
|
+
disabled: isSubmitting
|
|
1809
|
+
}
|
|
1810
|
+
)
|
|
1811
|
+
] })
|
|
1812
|
+
] })
|
|
1813
|
+
] }),
|
|
1814
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1815
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-name", children: "Audio name" }),
|
|
1816
|
+
/* @__PURE__ */ jsx(
|
|
1817
|
+
Autocomplete,
|
|
1818
|
+
{
|
|
1819
|
+
id: "caption-name",
|
|
1820
|
+
value: selectedLanguage?.value || "",
|
|
1821
|
+
onChange: (newValue) => {
|
|
1822
|
+
const selected = (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find((opt) => opt.value === newValue);
|
|
1823
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
1824
|
+
},
|
|
1825
|
+
options: isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1,
|
|
1826
|
+
icon: TranslateIcon,
|
|
1827
|
+
placeholder: "Select language",
|
|
1828
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
1829
|
+
openButton: !0,
|
|
1830
|
+
renderValue: (value) => (isAutogenerated ? MUX_LANGUAGE_OPTIONS : LANGUAGE_OPTIONS$1).find(
|
|
1831
|
+
(l) => l.value === value
|
|
1832
|
+
)?.label || value,
|
|
1833
|
+
renderOption: (option) => /* @__PURE__ */ jsx(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxs(Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
1834
|
+
option.label,
|
|
1835
|
+
" (",
|
|
1836
|
+
option.value,
|
|
1837
|
+
")"
|
|
1838
|
+
] }) }),
|
|
1839
|
+
disabled: isSubmitting
|
|
1840
|
+
}
|
|
1841
|
+
)
|
|
1842
|
+
] }),
|
|
1843
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1844
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-language", children: "Language Code" }),
|
|
1845
|
+
/* @__PURE__ */ jsx(
|
|
1846
|
+
TextInput,
|
|
1847
|
+
{
|
|
1848
|
+
id: "caption-language",
|
|
1849
|
+
placeholder: "en-US",
|
|
1850
|
+
value: languageCode,
|
|
1851
|
+
onChange: (e) => {
|
|
1852
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
1853
|
+
},
|
|
1854
|
+
disabled: isSubmitting
|
|
1855
|
+
}
|
|
1856
|
+
)
|
|
1857
|
+
] }),
|
|
1858
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
1859
|
+
/* @__PURE__ */ jsx(Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
1860
|
+
/* @__PURE__ */ jsx(
|
|
1861
|
+
Button,
|
|
1862
|
+
{
|
|
1863
|
+
text: "Add Caption Track",
|
|
1864
|
+
tone: "primary",
|
|
1865
|
+
icon: isSubmitting ? /* @__PURE__ */ jsx(
|
|
1866
|
+
Spinner,
|
|
1867
|
+
{
|
|
1868
|
+
style: {
|
|
1869
|
+
verticalAlign: "middle",
|
|
1870
|
+
display: "inline-block",
|
|
1871
|
+
marginBottom: "-3px",
|
|
1872
|
+
width: "1em",
|
|
1873
|
+
height: "1em",
|
|
1874
|
+
marginRight: "-6px"
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
) : /* @__PURE__ */ jsx(UploadIcon, {}),
|
|
1878
|
+
onClick: handleSubmit,
|
|
1879
|
+
disabled: isSubmitting
|
|
1880
|
+
}
|
|
1881
|
+
)
|
|
1882
|
+
] })
|
|
1883
|
+
] })
|
|
1884
|
+
}
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
const LANGUAGE_OPTIONS = LanguagesList.getAllCodes().map((code) => ({
|
|
1888
|
+
value: code,
|
|
1889
|
+
label: LanguagesList.getNativeName(code)
|
|
1890
|
+
}));
|
|
1891
|
+
function EditCaptionDialog({ asset, track, onUpdate, onClose }) {
|
|
1892
|
+
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(
|
|
1893
|
+
() => {
|
|
1894
|
+
const baseCode = track.language_code?.split("-")[0], found = LANGUAGE_OPTIONS.find(
|
|
1895
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
1896
|
+
);
|
|
1897
|
+
if (found) return found;
|
|
1898
|
+
if (track.name) {
|
|
1899
|
+
const foundByName = LANGUAGE_OPTIONS.find((opt) => opt.label === track.name);
|
|
1900
|
+
if (foundByName) return foundByName;
|
|
1901
|
+
}
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
), [name2, setName] = useState(track.name || ""), [isSubmitting, setIsSubmitting] = useState(!1), [downloading, setDownloading] = useState(!1), [selectedFile, setSelectedFile] = useState(null), fileInputRef = useRef(null);
|
|
1905
|
+
useEffect(() => {
|
|
1906
|
+
setLanguageCode(track.language_code || ""), setName(track.name || ""), setVttUrl("");
|
|
1907
|
+
const baseCode = track.language_code?.split("-")[0], foundByCode = LANGUAGE_OPTIONS.find(
|
|
1908
|
+
(opt) => opt.value === track.language_code || opt.value === baseCode
|
|
1909
|
+
), foundByName = track.name ? LANGUAGE_OPTIONS.find((opt) => opt.label === track.name) : null;
|
|
1910
|
+
setSelectedLanguage(foundByCode || foundByName || null);
|
|
1911
|
+
}, [track, asset, client]);
|
|
1912
|
+
const handleDownloadCurrentFile = async () => {
|
|
1913
|
+
setDownloading(!0);
|
|
1914
|
+
try {
|
|
1915
|
+
await downloadVttFile(client, asset, track);
|
|
1916
|
+
} catch (error) {
|
|
1917
|
+
let errorMessage = "Please try again", title = "Failed to download VTT file";
|
|
1918
|
+
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({
|
|
1919
|
+
title,
|
|
1920
|
+
status: "error",
|
|
1921
|
+
description: errorMessage
|
|
1922
|
+
});
|
|
1923
|
+
} finally {
|
|
1924
|
+
setDownloading(!1);
|
|
1925
|
+
}
|
|
1926
|
+
}, 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, {
|
|
1927
|
+
filename: file.name
|
|
1928
|
+
})).url, refreshAssetData = async () => {
|
|
1929
|
+
if (!(!asset._id || !asset.assetId))
|
|
1930
|
+
try {
|
|
1931
|
+
const latestAssetData = await getAsset(client, asset.assetId);
|
|
1932
|
+
await client.patch(asset._id).set({ data: latestAssetData.data, status: latestAssetData.data.status }).commit();
|
|
1933
|
+
} catch (refreshError) {
|
|
1934
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
1935
|
+
}
|
|
1936
|
+
}, handleUpdateTrackWithNewUrl = async () => {
|
|
1937
|
+
if (!asset.assetId)
|
|
1938
|
+
throw new Error("Asset ID is required");
|
|
1939
|
+
const trimmedName = name2.trim(), trimmedLanguageCode = languageCode.trim(), oldTrackId = track.id;
|
|
1940
|
+
try {
|
|
1941
|
+
await deleteTextTrack(client, asset.assetId, oldTrackId);
|
|
1942
|
+
} catch (deleteError) {
|
|
1943
|
+
throw toast.push({
|
|
1944
|
+
title: "Failed to delete old track",
|
|
1945
|
+
status: "error",
|
|
1946
|
+
description: "Could not delete the old track. Please try again or delete it manually."
|
|
1947
|
+
}), setIsSubmitting(!1), deleteError;
|
|
1948
|
+
}
|
|
1949
|
+
let vttUrlToUse = vttUrl.trim();
|
|
1950
|
+
if (selectedFile)
|
|
1951
|
+
try {
|
|
1952
|
+
vttUrlToUse = await uploadVttFile(selectedFile);
|
|
1953
|
+
} catch (uploadError) {
|
|
1954
|
+
throw toast.push({
|
|
1955
|
+
title: "Failed to upload VTT file",
|
|
1956
|
+
status: "error",
|
|
1957
|
+
description: "Could not upload the VTT file to Sanity. Please try again."
|
|
1958
|
+
}), setIsSubmitting(!1), uploadError;
|
|
1959
|
+
}
|
|
1960
|
+
try {
|
|
1961
|
+
await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
|
|
1962
|
+
language_code: trimmedLanguageCode,
|
|
1963
|
+
name: trimmedName,
|
|
1964
|
+
text_type: "subtitles"
|
|
1965
|
+
});
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
throw toast.push({
|
|
1968
|
+
title: "Failed to update caption track",
|
|
1969
|
+
status: "error",
|
|
1970
|
+
description: extractErrorMessage(error, "Failed to update caption track")
|
|
1971
|
+
}), setIsSubmitting(!1), error;
|
|
1972
|
+
}
|
|
1973
|
+
const result = await pollTrackStatus({
|
|
1974
|
+
client,
|
|
1975
|
+
assetId: asset.assetId,
|
|
1976
|
+
trackName: trimmedName,
|
|
1977
|
+
trackLanguageCode: trimmedLanguageCode,
|
|
1978
|
+
onTrackErrored: async (erroredTrack) => {
|
|
1979
|
+
const errorMessage = erroredTrack.error?.messages?.[0] || erroredTrack.error?.type || "The track failed to download from the provided URL";
|
|
1980
|
+
toast.push({
|
|
1981
|
+
title: "Caption track failed",
|
|
1982
|
+
status: "error",
|
|
1983
|
+
description: errorMessage
|
|
1984
|
+
}), await refreshAssetData(), onUpdate(erroredTrack, oldTrackId), setIsSubmitting(!1);
|
|
1985
|
+
}
|
|
1986
|
+
});
|
|
1987
|
+
if (!result.found || !result.track) {
|
|
1988
|
+
toast.push({
|
|
1989
|
+
title: "Caption track may have been updated",
|
|
1990
|
+
status: "warning",
|
|
1991
|
+
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."
|
|
1992
|
+
}), setIsSubmitting(!1);
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
result.status !== "errored" && (await refreshAssetData(), result.status === "preparing" ? toast.push({
|
|
1996
|
+
title: "Caption track is processing",
|
|
1997
|
+
status: "info",
|
|
1998
|
+
description: "The track was updated and is being processed. It will appear in the list shortly."
|
|
1999
|
+
}) : toast.push({
|
|
2000
|
+
title: "Caption track updated",
|
|
2001
|
+
status: "success",
|
|
2002
|
+
description: "Caption track updated successfully"
|
|
2003
|
+
}), onUpdate(result.track, oldTrackId), setIsSubmitting(!1));
|
|
2004
|
+
}, handleSubmit = async () => {
|
|
2005
|
+
if (!name2.trim()) {
|
|
2006
|
+
toast.push({
|
|
2007
|
+
title: "Audio name required",
|
|
2008
|
+
status: "error",
|
|
2009
|
+
description: "Please enter an audio name for this caption track"
|
|
2010
|
+
});
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
if (!languageCode.trim()) {
|
|
2014
|
+
toast.push({
|
|
2015
|
+
title: "Language code required",
|
|
2016
|
+
status: "error",
|
|
2017
|
+
description: "Please enter a language code (e.g., en, es, fr)"
|
|
2018
|
+
});
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
setIsSubmitting(!0);
|
|
2022
|
+
try {
|
|
2023
|
+
if (!asset.assetId)
|
|
2024
|
+
throw new Error("Asset ID is required");
|
|
2025
|
+
const originalVttUrl = (() => {
|
|
2026
|
+
if (isAutogenerated || !track.id) return "";
|
|
2027
|
+
const playbackId = getPlaybackId(asset);
|
|
2028
|
+
if (!playbackId) return "";
|
|
2029
|
+
let url = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`;
|
|
2030
|
+
if (getPlaybackPolicy(asset) === "signed") {
|
|
2031
|
+
const token = generateJwt(client, playbackId, "v");
|
|
2032
|
+
url += `?token=${token}`;
|
|
2033
|
+
}
|
|
2034
|
+
return url;
|
|
2035
|
+
})(), urlChanged = selectedFile !== null || vttUrl.trim() && vttUrl.trim() !== originalVttUrl;
|
|
2036
|
+
if (!urlChanged) {
|
|
2037
|
+
toast.push({
|
|
2038
|
+
title: "No changes",
|
|
2039
|
+
status: "info",
|
|
2040
|
+
description: 'Please provide a new VTT file or URL using the "Replace" button or URL field to update the track.'
|
|
2041
|
+
}), setIsSubmitting(!1);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (urlChanged) {
|
|
2045
|
+
if (!selectedFile && vttUrl.trim())
|
|
2046
|
+
try {
|
|
2047
|
+
new URL(vttUrl.trim());
|
|
2048
|
+
} catch {
|
|
2049
|
+
toast.push({
|
|
2050
|
+
title: "Invalid URL",
|
|
2051
|
+
status: "error",
|
|
2052
|
+
description: "Please enter a valid URL (e.g., https://example.com/subtitles.vtt)"
|
|
2053
|
+
}), setIsSubmitting(!1);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (!selectedFile && !vttUrl.trim()) {
|
|
2057
|
+
toast.push({
|
|
2058
|
+
title: "VTT file or URL required",
|
|
2059
|
+
status: "error",
|
|
2060
|
+
description: "Please select a VTT file or enter a VTT file URL"
|
|
2061
|
+
}), setIsSubmitting(!1);
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
await handleUpdateTrackWithNewUrl();
|
|
2065
|
+
}
|
|
2066
|
+
onClose();
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
toast.push({
|
|
2069
|
+
title: "Failed to update caption track",
|
|
2070
|
+
status: "error",
|
|
2071
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2072
|
+
});
|
|
2073
|
+
} finally {
|
|
2074
|
+
setIsSubmitting(!1);
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
return /* @__PURE__ */ jsx(
|
|
2078
|
+
Dialog,
|
|
2079
|
+
{
|
|
2080
|
+
id: dialogId,
|
|
2081
|
+
header: "Edit Caption Track",
|
|
2082
|
+
onClose,
|
|
2083
|
+
width: 1,
|
|
2084
|
+
onClickOutside: onClose,
|
|
2085
|
+
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, children: [
|
|
2086
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2087
|
+
/* @__PURE__ */ jsxs(Card, { padding: 3, marginBottom: 2, tone: "transparent", border: !0, radius: 2, children: [
|
|
2088
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", children: [
|
|
2089
|
+
/* @__PURE__ */ jsx(Text, { children: getCurrentFileName() }),
|
|
2090
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
2091
|
+
track.status !== "errored" && /* @__PURE__ */ jsx(
|
|
2092
|
+
Button,
|
|
2093
|
+
{
|
|
2094
|
+
icon: downloading ? /* @__PURE__ */ jsx(
|
|
2095
|
+
Spinner,
|
|
2096
|
+
{
|
|
2097
|
+
style: {
|
|
2098
|
+
verticalAlign: "middle",
|
|
2099
|
+
display: "inline-block",
|
|
2100
|
+
marginTop: "-2px",
|
|
2101
|
+
width: "0.5em",
|
|
2102
|
+
height: "0.5em"
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
) : /* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
2106
|
+
text: "Download",
|
|
2107
|
+
mode: "ghost",
|
|
2108
|
+
tone: "primary",
|
|
2109
|
+
fontSize: 1,
|
|
2110
|
+
padding: 2,
|
|
2111
|
+
onClick: handleDownloadCurrentFile,
|
|
2112
|
+
disabled: downloading || isSubmitting
|
|
2113
|
+
}
|
|
2114
|
+
),
|
|
2115
|
+
/* @__PURE__ */ jsx(
|
|
2116
|
+
Button,
|
|
2117
|
+
{
|
|
2118
|
+
icon: UploadIcon,
|
|
2119
|
+
text: "Replace",
|
|
2120
|
+
mode: "ghost",
|
|
2121
|
+
tone: "primary",
|
|
2122
|
+
fontSize: 1,
|
|
2123
|
+
padding: 2,
|
|
2124
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2125
|
+
disabled: isSubmitting
|
|
2126
|
+
}
|
|
2127
|
+
)
|
|
2128
|
+
] })
|
|
2129
|
+
] }),
|
|
2130
|
+
/* @__PURE__ */ jsx(
|
|
2131
|
+
"input",
|
|
2132
|
+
{
|
|
2133
|
+
ref: fileInputRef,
|
|
2134
|
+
type: "file",
|
|
2135
|
+
accept: ".vtt,text/vtt",
|
|
2136
|
+
style: { display: "none" },
|
|
2137
|
+
onChange: (e) => {
|
|
2138
|
+
e.target.files && e.target.files.length > 0 && !isSubmitting && (setSelectedFile(e.target.files[0]), setVttUrl(""));
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
),
|
|
2142
|
+
selectedFile && /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, style: { marginTop: 8 }, children: [
|
|
2143
|
+
"Selected: ",
|
|
2144
|
+
selectedFile.name
|
|
2145
|
+
] })
|
|
2146
|
+
] }),
|
|
2147
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2148
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "vtt-url", children: "VTT File URL" }),
|
|
2149
|
+
/* @__PURE__ */ jsx(
|
|
2150
|
+
TextInput,
|
|
2151
|
+
{
|
|
2152
|
+
id: "vtt-url",
|
|
2153
|
+
placeholder: "https://example.com/subtitles.vtt",
|
|
2154
|
+
value: vttUrl,
|
|
2155
|
+
onChange: (e) => {
|
|
2156
|
+
setVttUrl(e.currentTarget.value), setSelectedFile(null);
|
|
2157
|
+
},
|
|
2158
|
+
disabled: isSubmitting
|
|
2159
|
+
}
|
|
2160
|
+
),
|
|
2161
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Add a URL to replace the existing VTT file with a new one" })
|
|
2162
|
+
] })
|
|
2163
|
+
] }),
|
|
2164
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2165
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-name", children: "Audio name" }),
|
|
2166
|
+
/* @__PURE__ */ jsx(
|
|
2167
|
+
Autocomplete,
|
|
2168
|
+
{
|
|
2169
|
+
id: "caption-name",
|
|
2170
|
+
value: selectedLanguage?.value || "",
|
|
2171
|
+
onChange: (newValue) => {
|
|
2172
|
+
const selected = LANGUAGE_OPTIONS.find((opt) => opt.value === newValue);
|
|
2173
|
+
selected && (setSelectedLanguage(selected), setLanguageCode(selected.value), setName(selected.label));
|
|
2174
|
+
},
|
|
2175
|
+
options: LANGUAGE_OPTIONS,
|
|
2176
|
+
icon: TranslateIcon,
|
|
2177
|
+
placeholder: "Select language",
|
|
2178
|
+
filterOption: (query, option) => option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 || option.value.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
|
2179
|
+
openButton: !0,
|
|
2180
|
+
renderValue: (value) => LANGUAGE_OPTIONS.find((l) => l.value === value)?.label || value,
|
|
2181
|
+
renderOption: (option) => /* @__PURE__ */ jsx(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: /* @__PURE__ */ jsxs(Text, { size: 2, textOverflow: "ellipsis", children: [
|
|
2182
|
+
option.label,
|
|
2183
|
+
" (",
|
|
2184
|
+
option.value,
|
|
2185
|
+
")"
|
|
2186
|
+
] }) }),
|
|
2187
|
+
disabled: isSubmitting
|
|
2188
|
+
}
|
|
2189
|
+
)
|
|
2190
|
+
] }),
|
|
2191
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
2192
|
+
/* @__PURE__ */ jsx(Label$1, { htmlFor: "caption-language", children: "Language Code" }),
|
|
2193
|
+
/* @__PURE__ */ jsx(
|
|
2194
|
+
TextInput,
|
|
2195
|
+
{
|
|
2196
|
+
id: "caption-language",
|
|
2197
|
+
placeholder: "en-US",
|
|
2198
|
+
value: languageCode,
|
|
2199
|
+
onChange: (e) => {
|
|
2200
|
+
setLanguageCode(e.currentTarget.value), selectedLanguage && selectedLanguage.value !== e.currentTarget.value && (setSelectedLanguage(null), (!name2 || name2 === selectedLanguage.label) && setName(""));
|
|
2201
|
+
},
|
|
2202
|
+
disabled: isSubmitting
|
|
2203
|
+
}
|
|
2204
|
+
)
|
|
2205
|
+
] }),
|
|
2206
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, justify: "flex-end", marginTop: 2, children: [
|
|
2207
|
+
/* @__PURE__ */ jsx(Button, { text: "Cancel", mode: "ghost", onClick: onClose, disabled: isSubmitting }),
|
|
2208
|
+
/* @__PURE__ */ jsx(
|
|
2209
|
+
Button,
|
|
2210
|
+
{
|
|
2211
|
+
text: "Update Caption Track",
|
|
2212
|
+
tone: "primary",
|
|
2213
|
+
icon: isSubmitting ? /* @__PURE__ */ jsx(
|
|
2214
|
+
Spinner,
|
|
2215
|
+
{
|
|
2216
|
+
style: {
|
|
2217
|
+
verticalAlign: "middle",
|
|
2218
|
+
display: "inline-block",
|
|
2219
|
+
marginBottom: "-3px",
|
|
2220
|
+
width: "1em",
|
|
2221
|
+
height: "1em",
|
|
2222
|
+
marginRight: "-6px"
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
) : UploadIcon,
|
|
2226
|
+
onClick: handleSubmit,
|
|
2227
|
+
disabled: isSubmitting
|
|
2228
|
+
}
|
|
2229
|
+
)
|
|
2230
|
+
] })
|
|
2231
|
+
] })
|
|
2232
|
+
}
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
function TrackCard({
|
|
2236
|
+
track,
|
|
2237
|
+
iconOnly,
|
|
2238
|
+
downloadingTrackId,
|
|
2239
|
+
deletingTrackId,
|
|
2240
|
+
trackToEdit,
|
|
2241
|
+
getTrackSourceLabel,
|
|
2242
|
+
handleDownload,
|
|
2243
|
+
setTrackToEdit,
|
|
2244
|
+
setTrackToDelete
|
|
2245
|
+
}) {
|
|
2246
|
+
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: [
|
|
2247
|
+
/* @__PURE__ */ jsx(
|
|
2248
|
+
Spinner,
|
|
2249
|
+
{
|
|
2250
|
+
muted: !0,
|
|
2251
|
+
style: {
|
|
2252
|
+
width: "0.75em",
|
|
2253
|
+
height: "0.75em",
|
|
2254
|
+
verticalAlign: "middle",
|
|
2255
|
+
display: "inline-block",
|
|
2256
|
+
marginBottom: "-2px"
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
),
|
|
2260
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Processing..." })
|
|
2261
|
+
] }) : /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
2262
|
+
track.status !== "errored" && /* @__PURE__ */ jsx(
|
|
2263
|
+
Button,
|
|
2264
|
+
{
|
|
2265
|
+
icon: downloadingTrackId === track.id ? /* @__PURE__ */ jsx(
|
|
2266
|
+
Spinner,
|
|
2267
|
+
{
|
|
2268
|
+
style: {
|
|
2269
|
+
verticalAlign: "middle",
|
|
2270
|
+
display: "inline-block",
|
|
2271
|
+
marginTop: "-2px",
|
|
2272
|
+
width: "0.5em",
|
|
2273
|
+
height: "0.5em"
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
) : /* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
2277
|
+
text: iconOnly ? void 0 : "Download",
|
|
2278
|
+
mode: "ghost",
|
|
2279
|
+
tone: "primary",
|
|
2280
|
+
fontSize: 1,
|
|
2281
|
+
padding: 2,
|
|
2282
|
+
onClick: () => handleDownload(track),
|
|
2283
|
+
disabled: isDisabled("download"),
|
|
2284
|
+
title: "Download"
|
|
2285
|
+
}
|
|
2286
|
+
),
|
|
2287
|
+
/* @__PURE__ */ jsx(
|
|
2288
|
+
Button,
|
|
2289
|
+
{
|
|
2290
|
+
icon: /* @__PURE__ */ jsx(EditIcon, {}),
|
|
2291
|
+
text: iconOnly ? void 0 : "Edit",
|
|
2292
|
+
mode: "ghost",
|
|
2293
|
+
tone: "primary",
|
|
2294
|
+
fontSize: 1,
|
|
2295
|
+
padding: 2,
|
|
2296
|
+
disabled: isDisabled("edit"),
|
|
2297
|
+
onClick: () => setTrackToEdit(track),
|
|
2298
|
+
title: "Edit"
|
|
2299
|
+
}
|
|
2300
|
+
),
|
|
2301
|
+
/* @__PURE__ */ jsx(
|
|
2302
|
+
Button,
|
|
2303
|
+
{
|
|
2304
|
+
icon: deletingTrackId === track.id ? /* @__PURE__ */ jsx(
|
|
2305
|
+
Spinner,
|
|
2306
|
+
{
|
|
2307
|
+
style: {
|
|
2308
|
+
verticalAlign: "middle",
|
|
2309
|
+
display: "inline-block",
|
|
2310
|
+
marginTop: "-2px",
|
|
2311
|
+
width: "0.5em",
|
|
2312
|
+
height: "0.5em"
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
) : /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
2316
|
+
text: iconOnly ? void 0 : "Delete",
|
|
2317
|
+
mode: "ghost",
|
|
2318
|
+
tone: "critical",
|
|
2319
|
+
fontSize: 1,
|
|
2320
|
+
padding: 2,
|
|
2321
|
+
disabled: isDisabled("delete"),
|
|
2322
|
+
onClick: () => setTrackToDelete(track),
|
|
2323
|
+
title: "Delete"
|
|
2324
|
+
}
|
|
2325
|
+
)
|
|
2326
|
+
] });
|
|
2327
|
+
return /* @__PURE__ */ jsx(
|
|
2328
|
+
Card,
|
|
1353
2329
|
{
|
|
1354
|
-
|
|
1355
|
-
|
|
2330
|
+
padding: 3,
|
|
2331
|
+
radius: 2,
|
|
2332
|
+
tone: track.status === "errored" ? "caution" : "transparent",
|
|
2333
|
+
border: !0,
|
|
2334
|
+
children: /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
|
|
2335
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, flex: 1, children: [
|
|
2336
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
2337
|
+
/* @__PURE__ */ jsx(Text, { weight: "semibold", children: track.name || "Untitled" }),
|
|
2338
|
+
/* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
2339
|
+
"(",
|
|
2340
|
+
getTrackSourceLabel(track),
|
|
2341
|
+
")"
|
|
2342
|
+
] }),
|
|
2343
|
+
track.status === "errored" && /* @__PURE__ */ jsx(
|
|
2344
|
+
ErrorOutlineIcon,
|
|
2345
|
+
{
|
|
2346
|
+
style: { color: "var(--card-critical-color)" },
|
|
2347
|
+
"aria-label": "Error",
|
|
2348
|
+
fontSize: 20
|
|
2349
|
+
}
|
|
2350
|
+
)
|
|
2351
|
+
] }),
|
|
2352
|
+
track.language_code && /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
2353
|
+
"Language: ",
|
|
2354
|
+
track.language_code
|
|
2355
|
+
] }),
|
|
2356
|
+
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" })
|
|
2357
|
+
] }),
|
|
2358
|
+
renderActionButtons()
|
|
2359
|
+
] })
|
|
1356
2360
|
}
|
|
1357
|
-
)
|
|
2361
|
+
);
|
|
1358
2362
|
}
|
|
1359
|
-
function
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2363
|
+
function TextTracksManager({
|
|
2364
|
+
asset,
|
|
2365
|
+
iconOnly = !1,
|
|
2366
|
+
tracks: propTracks,
|
|
2367
|
+
collapseTracks = !1
|
|
2368
|
+
}) {
|
|
2369
|
+
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;
|
|
2370
|
+
useEffect(() => {
|
|
2371
|
+
if (!asset.assetId || !asset._id) return;
|
|
2372
|
+
const assetId = asset.assetId, documentId = asset._id;
|
|
2373
|
+
(async () => {
|
|
2374
|
+
try {
|
|
2375
|
+
const response = await getAsset(client, assetId);
|
|
2376
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
console.error("Failed to refresh asset data:", error);
|
|
2379
|
+
}
|
|
2380
|
+
})();
|
|
2381
|
+
}, [asset.assetId, asset._id, client]);
|
|
2382
|
+
const activeTracks = (propTracks || asset.data?.tracks?.filter((track) => track.type === "text") || []).filter(
|
|
2383
|
+
(track) => track.id && (track.status === "ready" || track.status === "preparing" || track.status === "errored")
|
|
2384
|
+
), allTracks = useMemo(() => {
|
|
2385
|
+
const tracksWithUpdates = activeTracks.map((track) => updatedTracks.get(track.id) || track), isMockTrackReplaced = (mockTrack, realTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : realTracksList.some((realTrack) => {
|
|
2386
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2387
|
+
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";
|
|
2388
|
+
}), 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));
|
|
2389
|
+
return [...tracksWithUpdates, ...tracksToKeep];
|
|
2390
|
+
}, [activeTracks, addedTracks, updatedTracks]);
|
|
2391
|
+
useEffect(() => {
|
|
2392
|
+
const newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2393
|
+
activeTracks.forEach((track) => {
|
|
2394
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2395
|
+
}), addedTracks.forEach((mockTrack) => {
|
|
2396
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2397
|
+
const realTrack = activeTracks.find((rt) => {
|
|
2398
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2399
|
+
return nameMatches && languageMatches;
|
|
2400
|
+
});
|
|
2401
|
+
realTrack?.id && newAutogeneratedIds.add(realTrack.id);
|
|
2402
|
+
}
|
|
2403
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2404
|
+
let hasNew = !1;
|
|
2405
|
+
const updated = new Set(prev);
|
|
2406
|
+
return newAutogeneratedIds.forEach((id) => {
|
|
2407
|
+
prev.has(id) || (updated.add(id), hasNew = !0);
|
|
2408
|
+
}), hasNew ? updated : prev;
|
|
2409
|
+
});
|
|
2410
|
+
}, [activeTracks, addedTracks]), useEffect(() => {
|
|
2411
|
+
if (allTracks.filter((track) => track.status === "preparing").length === 0 || !asset.assetId || !asset._id)
|
|
2412
|
+
return;
|
|
2413
|
+
const assetId = asset.assetId, documentId = asset._id, interval = setInterval(async () => {
|
|
2414
|
+
try {
|
|
2415
|
+
const response = await getAsset(client, assetId);
|
|
2416
|
+
await client.patch(documentId).set({ data: response.data, status: response.data.status }).commit();
|
|
2417
|
+
const fetchedTracks = response.data.tracks?.filter((track) => track.type === "text") || [], isMockTrackReplaced = (mockTrack, fetchedTracksList) => !mockTrack.id || !mockTrack.id.startsWith("generating-") ? !1 : fetchedTracksList.some((realTrack) => {
|
|
2418
|
+
const nameMatches = realTrack.name === mockTrack.name, languageMatches = realTrack.language_code === mockTrack.language_code;
|
|
2419
|
+
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";
|
|
2420
|
+
}), newAutogeneratedIds = /* @__PURE__ */ new Set();
|
|
2421
|
+
fetchedTracks.forEach((track) => {
|
|
2422
|
+
track.id && (track.text_source === "generated_live" || track.text_source === "generated_live_final" || track.text_source === "generated_vod") && newAutogeneratedIds.add(track.id);
|
|
2423
|
+
});
|
|
2424
|
+
const findMatchingRealTrack = (mockTrack, tracksList) => tracksList.find((rt) => {
|
|
2425
|
+
const nameMatches = rt.name === mockTrack.name, languageMatches = rt.language_code === mockTrack.language_code;
|
|
2426
|
+
return nameMatches && languageMatches;
|
|
2427
|
+
});
|
|
2428
|
+
setAddedTracks((prev) => prev.filter((mockTrack) => {
|
|
2429
|
+
if (mockTrack.id && mockTrack.id.startsWith("generating-")) {
|
|
2430
|
+
const replaced = isMockTrackReplaced(mockTrack, fetchedTracks);
|
|
2431
|
+
if (replaced) {
|
|
2432
|
+
const realTrack = findMatchingRealTrack(mockTrack, fetchedTracks);
|
|
2433
|
+
realTrack?.id && (newAutogeneratedIds.add(realTrack.id), setTrackActivityOrder((prevOrder) => {
|
|
2434
|
+
const mockOrder = prevOrder.get(mockTrack.id);
|
|
2435
|
+
if (mockOrder) {
|
|
2436
|
+
const newMap = new Map(prevOrder);
|
|
2437
|
+
return newMap.set(realTrack.id, mockOrder), newMap;
|
|
2438
|
+
}
|
|
2439
|
+
return prevOrder;
|
|
2440
|
+
}));
|
|
2441
|
+
}
|
|
2442
|
+
return !replaced;
|
|
1375
2443
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
2444
|
+
return !0;
|
|
2445
|
+
})), newAutogeneratedIds.size > 0 && setAutogeneratedTrackIds((prevIds) => {
|
|
2446
|
+
const updated = new Set(prevIds);
|
|
2447
|
+
return newAutogeneratedIds.forEach((id) => updated.add(id)), updated;
|
|
2448
|
+
});
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
console.error("Failed to refresh asset data:", error);
|
|
2451
|
+
}
|
|
2452
|
+
}, 3e3);
|
|
2453
|
+
return () => clearInterval(interval);
|
|
2454
|
+
}, [allTracks, asset.assetId, asset._id, client]);
|
|
2455
|
+
const visibleTracks = allTracks.filter(
|
|
2456
|
+
(track) => track.status === "ready" || track.status === "preparing" || track.status === "errored"
|
|
2457
|
+
).sort((a2, b) => {
|
|
2458
|
+
const orderA = trackActivityOrder.get(a2.id) || 0, orderB = trackActivityOrder.get(b.id) || 0;
|
|
2459
|
+
if (orderA > 0 && orderB > 0)
|
|
2460
|
+
return orderB - orderA;
|
|
2461
|
+
if (orderA > 0) return -1;
|
|
2462
|
+
if (orderB > 0) return 1;
|
|
2463
|
+
const aIsPreparing = a2.status === "preparing", bIsPreparing = b.status === "preparing";
|
|
2464
|
+
if (aIsPreparing && !bIsPreparing) return -1;
|
|
2465
|
+
if (!aIsPreparing && bIsPreparing) return 1;
|
|
2466
|
+
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);
|
|
2467
|
+
return aIsAutogenerated && !bIsAutogenerated ? -1 : !aIsAutogenerated && bIsAutogenerated ? 1 : 0;
|
|
2468
|
+
}), handleDownload = async (track) => {
|
|
2469
|
+
if (track.id) {
|
|
2470
|
+
setDownloadingTrackId(track.id);
|
|
2471
|
+
try {
|
|
2472
|
+
await downloadVttFile(client, asset, track);
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
toast.push({
|
|
2475
|
+
title: "Failed to download VTT file",
|
|
2476
|
+
status: "error",
|
|
2477
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2478
|
+
});
|
|
2479
|
+
} finally {
|
|
2480
|
+
setDownloadingTrackId(null);
|
|
2481
|
+
}
|
|
1379
2482
|
}
|
|
1380
|
-
)
|
|
2483
|
+
}, confirmDelete = async () => {
|
|
2484
|
+
if (!trackToDelete || !trackToDelete.id) return;
|
|
2485
|
+
const track = trackToDelete;
|
|
2486
|
+
setTrackToDelete(null), setDeletingTrackId(track.id);
|
|
2487
|
+
try {
|
|
2488
|
+
if (!asset.assetId)
|
|
2489
|
+
throw new Error("Asset ID is required");
|
|
2490
|
+
if (await deleteTextTrack(client, asset.assetId, track.id), asset._id)
|
|
2491
|
+
try {
|
|
2492
|
+
const response = await getAsset(client, asset.assetId);
|
|
2493
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2494
|
+
} catch (refreshError) {
|
|
2495
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2496
|
+
}
|
|
2497
|
+
toast.push({
|
|
2498
|
+
title: "Successfully deleted caption track",
|
|
2499
|
+
status: "success"
|
|
2500
|
+
}), setAddedTracks((prev) => prev.filter((t) => t.id !== track.id)), setUpdatedTracks((prev) => {
|
|
2501
|
+
const newMap = new Map(prev);
|
|
2502
|
+
return newMap.delete(track.id), newMap;
|
|
2503
|
+
}), setTrackActivityOrder((prev) => {
|
|
2504
|
+
const newMap = new Map(prev);
|
|
2505
|
+
return newMap.delete(track.id), newMap;
|
|
2506
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2507
|
+
const updated = new Set(prev);
|
|
2508
|
+
return updated.delete(track.id), updated;
|
|
2509
|
+
});
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
toast.push({
|
|
2512
|
+
title: "Failed to delete caption track",
|
|
2513
|
+
status: "error",
|
|
2514
|
+
description: error instanceof Error ? error.message : "Please try again"
|
|
2515
|
+
});
|
|
2516
|
+
} finally {
|
|
2517
|
+
setDeletingTrackId(null);
|
|
2518
|
+
}
|
|
2519
|
+
}, handleAddTrack = (track) => {
|
|
2520
|
+
setAddedTracks((prev) => [...prev, track]), setTrackActivityOrder((prev) => {
|
|
2521
|
+
const newMap = new Map(prev);
|
|
2522
|
+
return newMap.set(track.id, prev.size + 1), newMap;
|
|
2523
|
+
}), setShowAddDialog(!1);
|
|
2524
|
+
}, handleUpdateTrack = async (updatedTrack, oldTrackId) => {
|
|
2525
|
+
if (oldTrackId && (setAddedTracks((prev) => prev.filter((t) => t.id !== oldTrackId)), setUpdatedTracks((prev) => {
|
|
2526
|
+
const newMap = new Map(prev);
|
|
2527
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2528
|
+
}), setTrackActivityOrder((prev) => {
|
|
2529
|
+
const newMap = new Map(prev);
|
|
2530
|
+
return newMap.delete(oldTrackId), newMap;
|
|
2531
|
+
}), setAutogeneratedTrackIds((prev) => {
|
|
2532
|
+
const updated = new Set(prev);
|
|
2533
|
+
return updated.delete(oldTrackId), updated;
|
|
2534
|
+
})), addedTracks.some((t) => t.id === updatedTrack.id) ? setAddedTracks((prev) => prev.map((t) => t.id === updatedTrack.id ? updatedTrack : t)) : setUpdatedTracks((prev) => {
|
|
2535
|
+
const newMap = new Map(prev);
|
|
2536
|
+
return newMap.set(updatedTrack.id, updatedTrack), newMap;
|
|
2537
|
+
}), setTrackActivityOrder((prev) => {
|
|
2538
|
+
const newMap = new Map(prev);
|
|
2539
|
+
return newMap.set(updatedTrack.id, prev.size + 1), newMap;
|
|
2540
|
+
}), setTrackToEdit(null), asset._id && asset.assetId)
|
|
2541
|
+
try {
|
|
2542
|
+
const response = await getAsset(client, asset.assetId);
|
|
2543
|
+
await client.patch(asset._id).set({ data: response.data, status: response.data.status }).commit();
|
|
2544
|
+
} catch (refreshError) {
|
|
2545
|
+
console.error("Failed to refresh asset data:", refreshError);
|
|
2546
|
+
}
|
|
2547
|
+
}, 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";
|
|
2548
|
+
if (visibleTracks.length === 0 && !showAddDialog)
|
|
2549
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2550
|
+
/* @__PURE__ */ jsx(Flex, { justify: "flex-end", children: /* @__PURE__ */ jsx(
|
|
2551
|
+
Button,
|
|
2552
|
+
{
|
|
2553
|
+
icon: AddIcon,
|
|
2554
|
+
text: "Add Caption",
|
|
2555
|
+
tone: "primary",
|
|
2556
|
+
onClick: () => setShowAddDialog(!0)
|
|
2557
|
+
}
|
|
2558
|
+
) }),
|
|
2559
|
+
/* @__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." }) }),
|
|
2560
|
+
showAddDialog && /* @__PURE__ */ jsx(
|
|
2561
|
+
AddCaptionDialog,
|
|
2562
|
+
{
|
|
2563
|
+
asset,
|
|
2564
|
+
onAdd: handleAddTrack,
|
|
2565
|
+
onClose: () => setShowAddDialog(!1)
|
|
2566
|
+
}
|
|
2567
|
+
)
|
|
2568
|
+
] });
|
|
2569
|
+
const displayedTracks = collapseTracks && !isExpanded ? visibleTracks.slice(0, MAX_VISIBLE_TRACKS) : visibleTracks, hasMoreTracks = collapseTracks && visibleTracks.length > MAX_VISIBLE_TRACKS;
|
|
2570
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2571
|
+
/* @__PURE__ */ jsx(Flex, { justify: "flex-end", children: /* @__PURE__ */ jsx(
|
|
2572
|
+
Button,
|
|
2573
|
+
{
|
|
2574
|
+
icon: AddIcon,
|
|
2575
|
+
text: "Add Caption",
|
|
2576
|
+
tone: "primary",
|
|
2577
|
+
onClick: () => setShowAddDialog(!0)
|
|
2578
|
+
}
|
|
2579
|
+
) }),
|
|
2580
|
+
displayedTracks.map((track) => /* @__PURE__ */ jsx(
|
|
2581
|
+
TrackCard,
|
|
2582
|
+
{
|
|
2583
|
+
track,
|
|
2584
|
+
iconOnly,
|
|
2585
|
+
downloadingTrackId,
|
|
2586
|
+
deletingTrackId,
|
|
2587
|
+
trackToEdit,
|
|
2588
|
+
getTrackSourceLabel,
|
|
2589
|
+
handleDownload,
|
|
2590
|
+
setTrackToEdit,
|
|
2591
|
+
setTrackToDelete
|
|
2592
|
+
},
|
|
2593
|
+
track.id
|
|
2594
|
+
)),
|
|
2595
|
+
hasMoreTracks && /* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsx(
|
|
2596
|
+
Button,
|
|
2597
|
+
{
|
|
2598
|
+
icon: isExpanded ? ChevronUpIcon : ChevronDownIcon,
|
|
2599
|
+
text: isExpanded ? "Show less" : `Show ${visibleTracks.length - MAX_VISIBLE_TRACKS} more`,
|
|
2600
|
+
mode: "ghost",
|
|
2601
|
+
tone: "primary",
|
|
2602
|
+
onClick: () => setIsExpanded(!isExpanded)
|
|
2603
|
+
}
|
|
2604
|
+
) }),
|
|
2605
|
+
trackToDelete && /* @__PURE__ */ jsx(
|
|
2606
|
+
Dialog,
|
|
2607
|
+
{
|
|
2608
|
+
animate: !0,
|
|
2609
|
+
id: dialogId,
|
|
2610
|
+
header: "Delete track",
|
|
2611
|
+
onClose: () => setTrackToDelete(null),
|
|
2612
|
+
onClickOutside: () => setTrackToDelete(null),
|
|
2613
|
+
width: 1,
|
|
2614
|
+
children: /* @__PURE__ */ jsx(
|
|
2615
|
+
Card,
|
|
2616
|
+
{
|
|
2617
|
+
padding: 3,
|
|
2618
|
+
style: {
|
|
2619
|
+
minHeight: "150px",
|
|
2620
|
+
display: "flex",
|
|
2621
|
+
alignItems: "center",
|
|
2622
|
+
justifyContent: "center"
|
|
2623
|
+
},
|
|
2624
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
2625
|
+
/* @__PURE__ */ jsxs(Heading, { size: 2, children: [
|
|
2626
|
+
'Are you sure you want to delete "',
|
|
2627
|
+
trackToDelete.name || trackToDelete.language_code || "Untitled",
|
|
2628
|
+
'"?'
|
|
2629
|
+
] }),
|
|
2630
|
+
/* @__PURE__ */ jsx(Text, { size: 2, children: "This action is irreversible" }),
|
|
2631
|
+
/* @__PURE__ */ jsx(Stack, { space: 4, marginY: 4, children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
|
|
2632
|
+
Button,
|
|
2633
|
+
{
|
|
2634
|
+
icon: deletingTrackId === trackToDelete.id ? /* @__PURE__ */ jsx(
|
|
2635
|
+
Spinner,
|
|
2636
|
+
{
|
|
2637
|
+
style: {
|
|
2638
|
+
verticalAlign: "middle",
|
|
2639
|
+
display: "inline-block",
|
|
2640
|
+
marginTop: "-2px",
|
|
2641
|
+
width: "0.5em",
|
|
2642
|
+
height: "0.5em"
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
) : /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
2646
|
+
fontSize: 2,
|
|
2647
|
+
padding: 3,
|
|
2648
|
+
text: "Delete track",
|
|
2649
|
+
tone: "critical",
|
|
2650
|
+
onClick: confirmDelete,
|
|
2651
|
+
disabled: deletingTrackId !== null
|
|
2652
|
+
}
|
|
2653
|
+
) }) })
|
|
2654
|
+
] })
|
|
2655
|
+
}
|
|
2656
|
+
)
|
|
2657
|
+
}
|
|
2658
|
+
),
|
|
2659
|
+
showAddDialog && /* @__PURE__ */ jsx(
|
|
2660
|
+
AddCaptionDialog,
|
|
2661
|
+
{
|
|
2662
|
+
asset,
|
|
2663
|
+
onAdd: handleAddTrack,
|
|
2664
|
+
onClose: () => setShowAddDialog(!1)
|
|
2665
|
+
}
|
|
2666
|
+
),
|
|
2667
|
+
trackToEdit && /* @__PURE__ */ jsx(
|
|
2668
|
+
EditCaptionDialog,
|
|
2669
|
+
{
|
|
2670
|
+
asset,
|
|
2671
|
+
track: trackToEdit,
|
|
2672
|
+
onUpdate: handleUpdateTrack,
|
|
2673
|
+
onClose: () => setTrackToEdit(null)
|
|
2674
|
+
}
|
|
2675
|
+
)
|
|
2676
|
+
] });
|
|
1381
2677
|
}
|
|
1382
2678
|
const DialogStateContext = createContext({
|
|
1383
2679
|
dialogState: !1,
|
|
@@ -1395,6 +2691,10 @@ function getVideoSrc({ asset, client }) {
|
|
|
1395
2691
|
}
|
|
1396
2692
|
return `https://stream.mux.com/${playbackId}.m3u8?${searchParams}`;
|
|
1397
2693
|
}
|
|
2694
|
+
function CaptionsDialog({ asset }) {
|
|
2695
|
+
const { setDialogState } = useDialogStateContext(), dialogId = `CaptionsDialog${useId()}`;
|
|
2696
|
+
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 }) }) });
|
|
2697
|
+
}
|
|
1398
2698
|
function getDevicePixelRatio(options) {
|
|
1399
2699
|
const {
|
|
1400
2700
|
defaultDpr = 1,
|
|
@@ -1482,10 +2782,21 @@ function EditThumbnailDialog({ asset, currentTime = 0 }) {
|
|
|
1482
2782
|
}
|
|
1483
2783
|
);
|
|
1484
2784
|
}
|
|
2785
|
+
function AudioIcon(props) {
|
|
2786
|
+
return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props, children: /* @__PURE__ */ jsx(
|
|
2787
|
+
"path",
|
|
2788
|
+
{
|
|
2789
|
+
fill: "currentColor",
|
|
2790
|
+
style: { opacity: "0.65" },
|
|
2791
|
+
d: "M10.75 19q.95 0 1.6-.65t.65-1.6V13h3v-2h-4v3.875q-.275-.2-.587-.288t-.663-.087q-.95 0-1.6.65t-.65 1.6t.65 1.6t1.6.65M6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22zm7-13V4H6v16h12V9zM6 4v5zv16z"
|
|
2792
|
+
}
|
|
2793
|
+
) });
|
|
2794
|
+
}
|
|
1485
2795
|
function VideoPlayer({
|
|
1486
2796
|
asset,
|
|
1487
2797
|
thumbnailWidth = 250,
|
|
1488
2798
|
children,
|
|
2799
|
+
hlsConfig,
|
|
1489
2800
|
...props
|
|
1490
2801
|
}) {
|
|
1491
2802
|
const client = useClient(), { dialogState } = useDialogStateContext(), isAudio = assetIsAudio(asset), muxPlayer = useRef(null), {
|
|
@@ -1511,53 +2822,81 @@ function VideoPlayer({
|
|
|
1511
2822
|
// Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
|
|
1512
2823
|
props.forceAspectRatio * 1.2
|
|
1513
2824
|
) : AUDIO_ASPECT_RATIO), /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1514
|
-
/* @__PURE__ */ jsxs(
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
2825
|
+
/* @__PURE__ */ jsxs(
|
|
2826
|
+
Card,
|
|
2827
|
+
{
|
|
2828
|
+
tone: "transparent",
|
|
2829
|
+
style: {
|
|
2830
|
+
aspectRatio,
|
|
2831
|
+
position: "relative",
|
|
2832
|
+
...isAudio && { display: "flex", alignItems: "flex-end" }
|
|
2833
|
+
},
|
|
2834
|
+
children: [
|
|
2835
|
+
videoSrc && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2836
|
+
isAudio && /* @__PURE__ */ jsx(
|
|
2837
|
+
AudioIcon,
|
|
2838
|
+
{
|
|
2839
|
+
style: {
|
|
2840
|
+
padding: "0.5em",
|
|
2841
|
+
width: "2.2em",
|
|
2842
|
+
height: "2.2em",
|
|
2843
|
+
position: "absolute",
|
|
2844
|
+
top: 0,
|
|
2845
|
+
left: 0,
|
|
2846
|
+
zIndex: 1
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
),
|
|
2850
|
+
/* @__PURE__ */ jsx(
|
|
2851
|
+
MuxPlayer,
|
|
2852
|
+
{
|
|
2853
|
+
poster: isAudio ? void 0 : thumbnailSrc,
|
|
2854
|
+
ref: muxPlayer,
|
|
2855
|
+
...props,
|
|
2856
|
+
playsInline: !0,
|
|
2857
|
+
playbackId: asset.playbackId,
|
|
2858
|
+
tokens: signedToken ? { playback: signedToken, thumbnail: signedToken, storyboard: signedToken } : void 0,
|
|
2859
|
+
preload: "metadata",
|
|
2860
|
+
crossOrigin: "anonymous",
|
|
2861
|
+
metadata: {
|
|
2862
|
+
player_name: "Sanity Admin Dashboard",
|
|
2863
|
+
player_version: "2.14.0",
|
|
2864
|
+
page_type: "Preview Player"
|
|
2865
|
+
},
|
|
2866
|
+
audio: isAudio,
|
|
2867
|
+
_hlsConfig: hlsConfig,
|
|
2868
|
+
style: {
|
|
2869
|
+
...!isAudio && { height: "100%" },
|
|
2870
|
+
width: "100%",
|
|
2871
|
+
display: "block",
|
|
2872
|
+
objectFit: "contain",
|
|
2873
|
+
...isAudio && { alignSelf: "end" }
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
),
|
|
2877
|
+
children
|
|
2878
|
+
] }),
|
|
2879
|
+
error ? /* @__PURE__ */ jsx(
|
|
2880
|
+
"div",
|
|
2881
|
+
{
|
|
2882
|
+
style: {
|
|
2883
|
+
position: "absolute",
|
|
2884
|
+
top: "50%",
|
|
2885
|
+
left: "50%",
|
|
2886
|
+
transform: "translate(-50%, -50%)"
|
|
2887
|
+
},
|
|
2888
|
+
children: /* @__PURE__ */ jsxs(Text, { muted: !0, children: [
|
|
2889
|
+
/* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { marginRight: "0.15em" } }),
|
|
2890
|
+
typeof error == "object" && "message" in error && typeof error.message == "string" ? error.message : "Error loading video"
|
|
2891
|
+
] })
|
|
1538
2892
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
style: {
|
|
1547
|
-
position: "absolute",
|
|
1548
|
-
top: "50%",
|
|
1549
|
-
left: "50%",
|
|
1550
|
-
transform: "translate(-50%, -50%)"
|
|
1551
|
-
},
|
|
1552
|
-
children: /* @__PURE__ */ jsxs(Text, { muted: !0, children: [
|
|
1553
|
-
/* @__PURE__ */ jsx(ErrorOutlineIcon, { style: { marginRight: "0.15em" } }),
|
|
1554
|
-
typeof error == "object" && "message" in error && typeof error.message == "string" ? error.message : "Error loading video"
|
|
1555
|
-
] })
|
|
1556
|
-
}
|
|
1557
|
-
) : null,
|
|
1558
|
-
children
|
|
1559
|
-
] }),
|
|
1560
|
-
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime })
|
|
2893
|
+
) : null,
|
|
2894
|
+
children
|
|
2895
|
+
]
|
|
2896
|
+
}
|
|
2897
|
+
),
|
|
2898
|
+
dialogState === "edit-thumbnail" && /* @__PURE__ */ jsx(EditThumbnailDialog, { asset, currentTime: muxPlayer?.current?.currentTime }),
|
|
2899
|
+
dialogState === "edit-captions" && /* @__PURE__ */ jsx(CaptionsDialog, { asset })
|
|
1561
2900
|
] });
|
|
1562
2901
|
}
|
|
1563
2902
|
function assetIsAudio(asset) {
|
|
@@ -1829,7 +3168,8 @@ function getVideoMetadata(doc) {
|
|
|
1829
3168
|
duration: doc.data?.duration ? formatSeconds(doc.data?.duration) : void 0,
|
|
1830
3169
|
aspect_ratio: doc.data?.aspect_ratio,
|
|
1831
3170
|
max_stored_resolution: doc.data?.max_stored_resolution,
|
|
1832
|
-
max_stored_frame_rate: doc.data?.max_stored_frame_rate
|
|
3171
|
+
max_stored_frame_rate: doc.data?.max_stored_frame_rate,
|
|
3172
|
+
text_tracks: doc.data?.tracks?.filter((track) => track.type === "text") || []
|
|
1833
3173
|
};
|
|
1834
3174
|
}
|
|
1835
3175
|
function useVideoDetails(props) {
|
|
@@ -2021,7 +3361,20 @@ const AssetInput = (props) => /* @__PURE__ */ jsx(FormField$1, { title: props.la
|
|
|
2021
3361
|
minHeight: containerHeight
|
|
2022
3362
|
} : void 0,
|
|
2023
3363
|
children: [
|
|
2024
|
-
/* @__PURE__ */
|
|
3364
|
+
/* @__PURE__ */ jsxs(Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
3365
|
+
/* @__PURE__ */ jsx(VideoPlayer, { asset: props.asset, autoPlay: props.asset.autoPlay || !1 }),
|
|
3366
|
+
tab === "details" && /* @__PURE__ */ jsx(
|
|
3367
|
+
TextTracksManager,
|
|
3368
|
+
{
|
|
3369
|
+
asset: props.asset,
|
|
3370
|
+
iconOnly: !0,
|
|
3371
|
+
collapseTracks: !0,
|
|
3372
|
+
tracks: displayInfo?.text_tracks || props.asset.data?.tracks?.filter(
|
|
3373
|
+
(track) => track.type === "text"
|
|
3374
|
+
) || []
|
|
3375
|
+
}
|
|
3376
|
+
)
|
|
3377
|
+
] }),
|
|
2025
3378
|
/* @__PURE__ */ jsxs(Stack, { space: 4, flex: 1, sizing: "border", children: [
|
|
2026
3379
|
/* @__PURE__ */ jsxs(TabList, { space: 2, children: [
|
|
2027
3380
|
/* @__PURE__ */ jsx(
|
|
@@ -2299,14 +3652,7 @@ function VideoInBrowser({
|
|
|
2299
3652
|
alignItems: "center",
|
|
2300
3653
|
justifyContent: "center"
|
|
2301
3654
|
},
|
|
2302
|
-
children: /* @__PURE__ */ jsx(
|
|
2303
|
-
"path",
|
|
2304
|
-
{
|
|
2305
|
-
fill: "currentColor",
|
|
2306
|
-
style: { opacity: "0.65" },
|
|
2307
|
-
d: "M10.75 19q.95 0 1.6-.65t.65-1.6V13h3v-2h-4v3.875q-.275-.2-.587-.288t-.663-.087q-.95 0-1.6.65t-.65 1.6t.65 1.6t1.6.65M6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h8l6 6v12q0 .825-.587 1.413T18 22zm7-13V4H6v16h12V9zM6 4v5zv16z"
|
|
2308
|
-
}
|
|
2309
|
-
) })
|
|
3655
|
+
children: /* @__PURE__ */ jsx(AudioIcon, { width: "3em", height: "3em" })
|
|
2310
3656
|
}
|
|
2311
3657
|
) : /* @__PURE__ */ jsx(VideoThumbnail, { asset })
|
|
2312
3658
|
] }),
|
|
@@ -2358,10 +3704,10 @@ function VideoInBrowser({
|
|
|
2358
3704
|
);
|
|
2359
3705
|
}
|
|
2360
3706
|
function VideosBrowser({ onSelect }) {
|
|
2361
|
-
const { assets, isLoading, searchQuery, setSearchQuery, setSort, sort } = useAssets(), [editedAsset, setEditedAsset] = useState(null), freshEditedAsset = useMemo(
|
|
3707
|
+
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(
|
|
2362
3708
|
() => assets.find((a2) => a2._id === editedAsset?._id) || editedAsset,
|
|
2363
3709
|
[editedAsset, assets]
|
|
2364
|
-
);
|
|
3710
|
+
), pageStart = page * pageLimit, pageEnd = pageStart + pageLimit;
|
|
2365
3711
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2366
3712
|
/* @__PURE__ */ jsxs(Stack, { padding: 4, space: 4, style: { minHeight: "50vh" }, children: [
|
|
2367
3713
|
/* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
|
|
@@ -2375,7 +3721,8 @@ function VideosBrowser({ onSelect }) {
|
|
|
2375
3721
|
placeholder: "Search videos"
|
|
2376
3722
|
}
|
|
2377
3723
|
),
|
|
2378
|
-
/* @__PURE__ */ jsx(SelectSortOptions, { setSort, sort })
|
|
3724
|
+
/* @__PURE__ */ jsx(SelectSortOptions, { setSort, sort }),
|
|
3725
|
+
/* @__PURE__ */ jsx(PageSelector, { page, setPage, total: pageTotal, limit: pageLimit })
|
|
2379
3726
|
] }),
|
|
2380
3727
|
(onSelect ? "input" : "tool") == "tool" && /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
|
|
2381
3728
|
/* @__PURE__ */ jsx(ImportVideosFromMux, {}),
|
|
@@ -2398,7 +3745,7 @@ function VideosBrowser({ onSelect }) {
|
|
|
2398
3745
|
style: {
|
|
2399
3746
|
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))"
|
|
2400
3747
|
},
|
|
2401
|
-
children: assets.map((asset) => /* @__PURE__ */ jsx(
|
|
3748
|
+
children: assets.slice(pageStart, pageEnd).map((asset) => /* @__PURE__ */ jsx(
|
|
2402
3749
|
VideoInBrowser,
|
|
2403
3750
|
{
|
|
2404
3751
|
asset,
|
|
@@ -2931,7 +4278,7 @@ const TopControls = styled.div`
|
|
|
2931
4278
|
}
|
|
2932
4279
|
) : null
|
|
2933
4280
|
] }) });
|
|
2934
|
-
}, Player = ({ asset, buttons, readOnly, onChange }) => {
|
|
4281
|
+
}, Player = ({ asset, buttons, readOnly, onChange, config }) => {
|
|
2935
4282
|
const isLoading = useMemo(() => asset?.status === "preparing" ? "Preparing the video" : asset?.status === "waiting_for_upload" ? "Waiting for upload to start" : asset?.status === "waiting" ? "Processing upload" : !(asset?.status === "ready" || typeof asset?.status > "u"), [asset]), isPreparingStaticRenditions = useMemo(() => {
|
|
2936
4283
|
if (asset?.data?.static_renditions?.status && asset?.data?.static_renditions?.status !== "disabled")
|
|
2937
4284
|
return !1;
|
|
@@ -2952,7 +4299,7 @@ const TopControls = styled.div`
|
|
|
2952
4299
|
text: isLoading !== !0 && isLoading || "Waiting for Mux to complete the upload",
|
|
2953
4300
|
onCancel: readOnly ? void 0 : () => handleCancelUpload()
|
|
2954
4301
|
}
|
|
2955
|
-
) : /* @__PURE__ */ jsxs(VideoPlayer, { asset, children: [
|
|
4302
|
+
) : /* @__PURE__ */ jsxs(VideoPlayer, { asset, hlsConfig: config?.hlsConfig, children: [
|
|
2956
4303
|
buttons && /* @__PURE__ */ jsx(TopControls, { slot: "top-chrome", children: buttons }),
|
|
2957
4304
|
isPreparingStaticRenditions && /* @__PURE__ */ jsx(
|
|
2958
4305
|
Card,
|
|
@@ -3064,7 +4411,7 @@ const FileButton = styled(MenuItem)(({ theme }) => {
|
|
|
3064
4411
|
color: white;
|
|
3065
4412
|
`, isVideoAsset = (asset) => asset._type === "mux.videoAsset";
|
|
3066
4413
|
function PlayerActionsMenu(props) {
|
|
3067
|
-
const { asset, readOnly, dialogState, setDialogState, onChange, onSelect } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset) === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange]);
|
|
4414
|
+
const { asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept } = props, [open, setOpen] = useState(!1), [menuElement, setMenuRef] = useState(null), isSigned = useMemo(() => getPlaybackPolicy(asset) === "signed", [asset]), { hasConfigAccess } = useAccessControl(props.config), onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange]);
|
|
3068
4415
|
return useEffect(() => {
|
|
3069
4416
|
open && dialogState && setOpen(!1);
|
|
3070
4417
|
}, [dialogState, open]), useClickOutsideEvent(
|
|
@@ -3090,7 +4437,7 @@ function PlayerActionsMenu(props) {
|
|
|
3090
4437
|
/* @__PURE__ */ jsx(
|
|
3091
4438
|
FileInputMenuItem,
|
|
3092
4439
|
{
|
|
3093
|
-
accept
|
|
4440
|
+
accept,
|
|
3094
4441
|
icon: UploadIcon,
|
|
3095
4442
|
onSelect,
|
|
3096
4443
|
text: "Upload",
|
|
@@ -3106,14 +4453,24 @@ function PlayerActionsMenu(props) {
|
|
|
3106
4453
|
onClick: () => setDialogState("select-video")
|
|
3107
4454
|
}
|
|
3108
4455
|
),
|
|
3109
|
-
isVideoAsset(asset) && /* @__PURE__ */
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
4456
|
+
isVideoAsset(asset) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4457
|
+
/* @__PURE__ */ jsx(
|
|
4458
|
+
MenuItem,
|
|
4459
|
+
{
|
|
4460
|
+
icon: ImageIcon,
|
|
4461
|
+
text: "Thumbnail",
|
|
4462
|
+
onClick: () => setDialogState("edit-thumbnail")
|
|
4463
|
+
}
|
|
4464
|
+
),
|
|
4465
|
+
/* @__PURE__ */ jsx(
|
|
4466
|
+
MenuItem,
|
|
4467
|
+
{
|
|
4468
|
+
icon: TranslateIcon,
|
|
4469
|
+
text: "Captions",
|
|
4470
|
+
onClick: () => setDialogState("edit-captions")
|
|
4471
|
+
}
|
|
4472
|
+
)
|
|
4473
|
+
] }),
|
|
3117
4474
|
/* @__PURE__ */ jsx(MenuDivider, {}),
|
|
3118
4475
|
hasConfigAccess && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3119
4476
|
/* @__PURE__ */ jsx(
|
|
@@ -3167,36 +4524,6 @@ function formatBytes(bytes, si = !1, dp = 1) {
|
|
|
3167
4524
|
while (Math.round(Math.abs(bytes) * r) / r >= thresh && u2 < units.length - 1);
|
|
3168
4525
|
return bytes.toFixed(dp) + " " + units[u2];
|
|
3169
4526
|
}
|
|
3170
|
-
const SUPPORTED_MUX_LANGUAGES = [
|
|
3171
|
-
{ label: "English", code: "en", state: "Stable" },
|
|
3172
|
-
{ label: "Spanish", code: "es", state: "Stable" },
|
|
3173
|
-
{ label: "Italian", code: "it", state: "Stable" },
|
|
3174
|
-
{ label: "Portuguese", code: "pt", state: "Stable" },
|
|
3175
|
-
{ label: "German", code: "de", state: "Stable" },
|
|
3176
|
-
{ label: "French", code: "fr", state: "Stable" },
|
|
3177
|
-
{ label: "Polish", code: "pl", state: "Beta" },
|
|
3178
|
-
{ label: "Russian", code: "ru", state: "Beta" },
|
|
3179
|
-
{ label: "Dutch", code: "nl", state: "Beta" },
|
|
3180
|
-
{ label: "Catalan", code: "ca", state: "Beta" },
|
|
3181
|
-
{ label: "Turkish", code: "tr", state: "Beta" },
|
|
3182
|
-
{ label: "Swedish", code: "sv", state: "Beta" },
|
|
3183
|
-
{ label: "Ukrainian", code: "uk", state: "Beta" },
|
|
3184
|
-
{ label: "Norwegian", code: "no", state: "Beta" },
|
|
3185
|
-
{ label: "Finnish", code: "fi", state: "Beta" },
|
|
3186
|
-
{ label: "Slovak", code: "sk", state: "Beta" },
|
|
3187
|
-
{ label: "Greek", code: "el", state: "Beta" },
|
|
3188
|
-
{ label: "Czech", code: "cs", state: "Beta" },
|
|
3189
|
-
{ label: "Croatian", code: "hr", state: "Beta" },
|
|
3190
|
-
{ label: "Danish", code: "da", state: "Beta" },
|
|
3191
|
-
{ label: "Romanian", code: "ro", state: "Beta" },
|
|
3192
|
-
{ label: "Bulgarian", code: "bg", state: "Beta" }
|
|
3193
|
-
];
|
|
3194
|
-
function isCustomTextTrack(track) {
|
|
3195
|
-
return track.type !== "autogenerated";
|
|
3196
|
-
}
|
|
3197
|
-
function isAutogeneratedTrack(track) {
|
|
3198
|
-
return track.type === "autogenerated";
|
|
3199
|
-
}
|
|
3200
4527
|
const ALL_LANGUAGE_CODES = LanguagesList.getAllCodes().map((code) => ({
|
|
3201
4528
|
value: code,
|
|
3202
4529
|
label: LanguagesList.getNativeName(code)
|
|
@@ -3454,7 +4781,47 @@ function UploadConfiguration({
|
|
|
3454
4781
|
(r) => r !== "highest" && r !== "audio-only"
|
|
3455
4782
|
).length > 0, [config.static_renditions]), [renditionMode, setRenditionMode] = useState(
|
|
3456
4783
|
isAdvancedMode ? "advanced" : "standard"
|
|
3457
|
-
),
|
|
4784
|
+
), [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;
|
|
4785
|
+
useEffect(() => {
|
|
4786
|
+
setVideoDuration(null), setUrlFileSize(null), setIsLoadingDuration(!1), setIsLoadingFileSize(!1), setValidationError(null), setCanSkipFileSizeValidation(!1);
|
|
4787
|
+
let videoElement = null, currentVideoSrc = null;
|
|
4788
|
+
const cleanupVideo = (shouldRevokeUrl) => {
|
|
4789
|
+
videoElement && (videoElement.onloadedmetadata = null, videoElement.onerror = null, videoElement.src = "", videoElement.load(), videoElement = null), shouldRevokeUrl && currentVideoSrc?.startsWith("blob:") && URL.revokeObjectURL(currentVideoSrc), currentVideoSrc = null;
|
|
4790
|
+
}, validateDuration = (videoSrc, shouldRevokeUrl = !1) => {
|
|
4791
|
+
!MAX_DURATION_SECONDS || MAX_DURATION_SECONDS <= 0 || (setIsLoadingDuration(!0), videoElement = document.createElement("video"), videoElement.preload = "metadata", currentVideoSrc = videoSrc, videoElement.onloadedmetadata = () => {
|
|
4792
|
+
const duration = videoElement.duration;
|
|
4793
|
+
setVideoDuration(duration), setIsLoadingDuration(!1), duration > MAX_DURATION_SECONDS && setValidationError(
|
|
4794
|
+
`Video duration (${formatSeconds(duration)}) exceeds maximum allowed duration of ${formatSeconds(MAX_DURATION_SECONDS)}`
|
|
4795
|
+
), cleanupVideo(shouldRevokeUrl);
|
|
4796
|
+
}, videoElement.onerror = () => {
|
|
4797
|
+
setIsLoadingDuration(!1), console.warn("Could not read video metadata for validation"), cleanupVideo(shouldRevokeUrl);
|
|
4798
|
+
}, videoElement.src = videoSrc);
|
|
4799
|
+
}, validateFileSize = (size) => MAX_FILE_SIZE === void 0 || size <= MAX_FILE_SIZE ? !0 : (setValidationError(
|
|
4800
|
+
`File size (${formatBytes(size)}) exceeds maximum allowed size of ${formatBytes(MAX_FILE_SIZE)}`
|
|
4801
|
+
), !1);
|
|
4802
|
+
if (stagedUpload.type === "file") {
|
|
4803
|
+
const file = stagedUpload.files[0];
|
|
4804
|
+
validateFileSize(file.size) && validateDuration(URL.createObjectURL(file), !0);
|
|
4805
|
+
}
|
|
4806
|
+
if (stagedUpload.type === "url") {
|
|
4807
|
+
const url = stagedUpload.url;
|
|
4808
|
+
(async () => {
|
|
4809
|
+
setIsLoadingFileSize(!0);
|
|
4810
|
+
try {
|
|
4811
|
+
const contentLength = (await fetch(url, { method: "HEAD" })).headers.get("content-length"), fileSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
4812
|
+
setIsLoadingFileSize(!1), fileSize && setUrlFileSize(fileSize);
|
|
4813
|
+
const shouldValidateDuration = MAX_FILE_SIZE === void 0 || fileSize === null || validateFileSize(fileSize);
|
|
4814
|
+
fileSize === null && MAX_FILE_SIZE !== void 0 && setCanSkipFileSizeValidation(!0), shouldValidateDuration && validateDuration(url);
|
|
4815
|
+
} catch {
|
|
4816
|
+
setIsLoadingFileSize(!1), console.warn("Could not validate file size from URL"), setCanSkipFileSizeValidation(!0), validateDuration(url);
|
|
4817
|
+
}
|
|
4818
|
+
})();
|
|
4819
|
+
}
|
|
4820
|
+
return () => {
|
|
4821
|
+
cleanupVideo(!0);
|
|
4822
|
+
};
|
|
4823
|
+
}, [stagedUpload, MAX_FILE_SIZE, MAX_DURATION_SECONDS]);
|
|
4824
|
+
const toggleRendition = (rendition) => {
|
|
3458
4825
|
const current = config.static_renditions, hasRendition = current.includes(rendition);
|
|
3459
4826
|
dispatch(hasRendition ? {
|
|
3460
4827
|
action: "static_renditions",
|
|
@@ -3489,6 +4856,13 @@ function UploadConfiguration({
|
|
|
3489
4856
|
header: "Configure Mux Upload",
|
|
3490
4857
|
onClose,
|
|
3491
4858
|
children: /* @__PURE__ */ jsxs(Stack, { padding: 4, space: 2, children: [
|
|
4859
|
+
validationError && /* @__PURE__ */ jsx(Card, { padding: 3, tone: "critical", radius: 2, marginBottom: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "flex-start", children: [
|
|
4860
|
+
/* @__PURE__ */ jsx(ErrorOutlineIcon, { width: 20, height: 20 }),
|
|
4861
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
4862
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Validation Error" }),
|
|
4863
|
+
/* @__PURE__ */ jsx(Text, { size: 1, children: validationError })
|
|
4864
|
+
] })
|
|
4865
|
+
] }) }),
|
|
3492
4866
|
/* @__PURE__ */ jsx(Label$1, { size: 3, children: "FILE TO UPLOAD" }),
|
|
3493
4867
|
/* @__PURE__ */ jsx(
|
|
3494
4868
|
Card,
|
|
@@ -3502,7 +4876,14 @@ function UploadConfiguration({
|
|
|
3502
4876
|
/* @__PURE__ */ jsx(DocumentVideoIcon, { fontSize: "2em" }),
|
|
3503
4877
|
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
3504
4878
|
/* @__PURE__ */ jsx(Text, { textOverflow: "ellipsis", as: "h2", size: 3, children: stagedUpload.type === "file" ? stagedUpload.files[0].name : stagedUpload.url }),
|
|
3505
|
-
/* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: stagedUpload.type === "file" ? `Direct File Upload (${formatBytes(stagedUpload.files[0].size)})` : "File From URL (Unknown size)" })
|
|
4879
|
+
/* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: stagedUpload.type === "file" ? `Direct File Upload (${formatBytes(stagedUpload.files[0].size)})` : urlFileSize ? `File From URL (${formatBytes(urlFileSize)})` : isLoadingFileSize ? "File From URL (Loading size...)" : "File From URL (Unknown size)" }),
|
|
4880
|
+
stagedUpload.type === "file" && /* @__PURE__ */ jsxs(Stack, { space: 1, children: [
|
|
4881
|
+
isLoadingDuration && /* @__PURE__ */ jsx(Text, { as: "p", size: 1, muted: !0, children: "Reading video metadata..." }),
|
|
4882
|
+
videoDuration !== null && !validationError && /* @__PURE__ */ jsxs(Text, { as: "p", size: 1, muted: !0, children: [
|
|
4883
|
+
"Duration: ",
|
|
4884
|
+
formatSeconds(videoDuration)
|
|
4885
|
+
] })
|
|
4886
|
+
] })
|
|
3506
4887
|
] })
|
|
3507
4888
|
] })
|
|
3508
4889
|
}
|
|
@@ -3694,11 +5075,13 @@ function UploadConfiguration({
|
|
|
3694
5075
|
/* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsx(
|
|
3695
5076
|
Button,
|
|
3696
5077
|
{
|
|
3697
|
-
disabled: !basicConfig && !config.public_policy && !config.signed_policy,
|
|
5078
|
+
disabled: !basicConfig && !config.public_policy && !config.signed_policy || validationError !== null || isLoadingDuration || isLoadingFileSize && !canSkipFileSizeValidation,
|
|
3698
5079
|
icon: UploadIcon,
|
|
3699
5080
|
text: "Upload",
|
|
3700
5081
|
tone: "positive",
|
|
3701
|
-
onClick: () =>
|
|
5082
|
+
onClick: () => {
|
|
5083
|
+
validationError || startUpload(formatUploadConfig(config));
|
|
5084
|
+
}
|
|
3702
5085
|
}
|
|
3703
5086
|
) })
|
|
3704
5087
|
] })
|
|
@@ -3762,12 +5145,10 @@ function withFocusRing(component) {
|
|
|
3762
5145
|
`;
|
|
3763
5146
|
});
|
|
3764
5147
|
}
|
|
3765
|
-
const
|
|
5148
|
+
const UploadCardWithFocusRing = withFocusRing(Card), UploadCard = forwardRef(
|
|
3766
5149
|
({ children, tone, onPaste, onDrop, onDragEnter, onDragLeave, onDragOver }, forwardedRef) => {
|
|
3767
|
-
const
|
|
3768
|
-
|
|
3769
|
-
}, []), handleKeyUp = useCallback((event) => {
|
|
3770
|
-
(event.keyCode == ctrlKey || event.keyCode == cmdKey) && (ctrlDown.current = !1);
|
|
5150
|
+
const inputRef = useRef(null), handleKeyDown = useCallback((event) => {
|
|
5151
|
+
event.target.closest("#vtt-url") || (event.ctrlKey || event.metaKey) && event.key === "v" && inputRef.current.focus();
|
|
3771
5152
|
}, []);
|
|
3772
5153
|
return /* @__PURE__ */ jsxs(
|
|
3773
5154
|
UploadCardWithFocusRing,
|
|
@@ -3779,14 +5160,13 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(Card),
|
|
|
3779
5160
|
shadow: 0,
|
|
3780
5161
|
tabIndex: 0,
|
|
3781
5162
|
onKeyDown: handleKeyDown,
|
|
3782
|
-
onKeyUp: handleKeyUp,
|
|
3783
5163
|
onPaste,
|
|
3784
5164
|
onDrop,
|
|
3785
5165
|
onDragEnter,
|
|
3786
5166
|
onDragLeave,
|
|
3787
5167
|
onDragOver,
|
|
3788
5168
|
children: [
|
|
3789
|
-
/* @__PURE__ */ jsx(HiddenInput$1, { ref: inputRef
|
|
5169
|
+
/* @__PURE__ */ jsx(HiddenInput$1, { ref: inputRef }),
|
|
3790
5170
|
children
|
|
3791
5171
|
]
|
|
3792
5172
|
}
|
|
@@ -3821,7 +5201,7 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(Card),
|
|
|
3821
5201
|
/* @__PURE__ */ jsx(
|
|
3822
5202
|
HiddenInput,
|
|
3823
5203
|
{
|
|
3824
|
-
accept
|
|
5204
|
+
accept,
|
|
3825
5205
|
ref: inputRef,
|
|
3826
5206
|
tabIndex: 0,
|
|
3827
5207
|
type: "file",
|
|
@@ -3842,8 +5222,11 @@ const ctrlKey = 17, cmdKey = 91, UploadCardWithFocusRing = withFocusRing(Card),
|
|
|
3842
5222
|
)
|
|
3843
5223
|
] });
|
|
3844
5224
|
};
|
|
5225
|
+
function formatAcceptString(accept) {
|
|
5226
|
+
return accept.split(",").map((type) => type.trim().replace("/*", "")).join(" or ");
|
|
5227
|
+
}
|
|
3845
5228
|
function UploadPlaceholder(props) {
|
|
3846
|
-
const { setDialogState, readOnly, onSelect, hovering, needsSetup } = props, handleBrowse = useCallback(() => setDialogState("select-video"), [setDialogState]), handleConfigureApi = useCallback(() => setDialogState("secrets"), [setDialogState]), { hasConfigAccess } = useAccessControl(props.config);
|
|
5229
|
+
const { setDialogState, readOnly, onSelect, hovering, needsSetup, accept } = props, handleBrowse = useCallback(() => setDialogState("select-video"), [setDialogState]), handleConfigureApi = useCallback(() => setDialogState("secrets"), [setDialogState]), { hasConfigAccess } = useAccessControl(props.config);
|
|
3847
5230
|
return /* @__PURE__ */ jsx(
|
|
3848
5231
|
Card,
|
|
3849
5232
|
{
|
|
@@ -3866,12 +5249,17 @@ function UploadPlaceholder(props) {
|
|
|
3866
5249
|
children: [
|
|
3867
5250
|
/* @__PURE__ */ jsxs(Flex, { align: "center", justify: "flex-start", gap: 2, flex: 1, children: [
|
|
3868
5251
|
/* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsx(Text, { muted: !0, children: /* @__PURE__ */ jsx(DocumentVideoIcon, {}) }) }),
|
|
3869
|
-
/* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */
|
|
5252
|
+
/* @__PURE__ */ jsx(Flex, { justify: "center", children: /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
5253
|
+
"Drag ",
|
|
5254
|
+
formatAcceptString(accept),
|
|
5255
|
+
" file or paste URL here"
|
|
5256
|
+
] }) })
|
|
3870
5257
|
] }),
|
|
3871
5258
|
/* @__PURE__ */ jsxs(Inline, { space: 2, children: [
|
|
3872
5259
|
/* @__PURE__ */ jsx(
|
|
3873
5260
|
FileInputButton,
|
|
3874
5261
|
{
|
|
5262
|
+
accept,
|
|
3875
5263
|
mode: "bleed",
|
|
3876
5264
|
tone: "default",
|
|
3877
5265
|
icon: UploadIcon,
|
|
@@ -4022,21 +5410,35 @@ function Uploader(props) {
|
|
|
4022
5410
|
complete: () => dispatch({ action: "complete" }),
|
|
4023
5411
|
error: (error) => dispatch({ action: "error", error })
|
|
4024
5412
|
});
|
|
4025
|
-
},
|
|
4026
|
-
|
|
5413
|
+
}, invalidFileToast = useCallback(() => {
|
|
5414
|
+
toast.push({
|
|
5415
|
+
status: "error",
|
|
5416
|
+
title: `Invalid file type. Accepted types: ${props.config.acceptedMimeTypes?.join(", ")}`
|
|
5417
|
+
});
|
|
5418
|
+
}, [props.config.acceptedMimeTypes, toast]), isInvalidFile = (files) => Array.from(files).some((file) => !props.config.acceptedMimeTypes?.some((acceptedType) => {
|
|
5419
|
+
const pattern = `^${acceptedType.replace("*", ".*")}$`;
|
|
5420
|
+
return new RegExp(pattern).test(file.type);
|
|
5421
|
+
})), handleUpload = (files) => {
|
|
5422
|
+
isInvalidFile(files) || dispatch({
|
|
4027
5423
|
action: "stageUpload",
|
|
4028
5424
|
input: { type: "file", files }
|
|
4029
5425
|
});
|
|
4030
5426
|
}, handlePaste = (event) => {
|
|
5427
|
+
if (event.target.closest("#vtt-url"))
|
|
5428
|
+
return;
|
|
4031
5429
|
event.preventDefault(), event.stopPropagation();
|
|
4032
|
-
const url = (event.clipboardData || window.clipboardData)
|
|
5430
|
+
const url = (event.clipboardData || window.clipboardData)?.getData("text")?.trim();
|
|
4033
5431
|
if (!isValidUrl(url)) {
|
|
4034
5432
|
toast.push({ status: "error", title: "Invalid URL for Mux video input." });
|
|
4035
5433
|
return;
|
|
4036
5434
|
}
|
|
4037
5435
|
dispatch({ action: "stageUpload", input: { type: "url", url } });
|
|
4038
5436
|
}, handleDrop = (event) => {
|
|
4039
|
-
|
|
5437
|
+
if (event.preventDefault(), event.stopPropagation(), dragState === "invalid") {
|
|
5438
|
+
invalidFileToast(), setDragState(null);
|
|
5439
|
+
return;
|
|
5440
|
+
}
|
|
5441
|
+
setDragState(null), extractDroppedFiles(event.nativeEvent.dataTransfer).then((files) => {
|
|
4040
5442
|
dispatch({
|
|
4041
5443
|
action: "stageUpload",
|
|
4042
5444
|
input: { type: "file", files }
|
|
@@ -4046,8 +5448,11 @@ function Uploader(props) {
|
|
|
4046
5448
|
event.preventDefault(), event.stopPropagation();
|
|
4047
5449
|
}, handleDragEnter = (event) => {
|
|
4048
5450
|
event.stopPropagation(), dragEnteredEls.current.push(event.target);
|
|
4049
|
-
const type = event.dataTransfer.items?.[0]?.type
|
|
4050
|
-
|
|
5451
|
+
const type = event.dataTransfer.items?.[0]?.type, isValidType = props.config.acceptedMimeTypes?.some((acceptedType) => {
|
|
5452
|
+
const pattern = `^${acceptedType.replace("*", ".*")}$`;
|
|
5453
|
+
return new RegExp(pattern).test(type);
|
|
5454
|
+
});
|
|
5455
|
+
setDragState(isValidType ? "valid" : "invalid");
|
|
4051
5456
|
}, handleDragLeave = (event) => {
|
|
4052
5457
|
event.stopPropagation();
|
|
4053
5458
|
const idx = dragEnteredEls.current.indexOf(event.target);
|
|
@@ -4085,7 +5490,9 @@ function Uploader(props) {
|
|
|
4085
5490
|
}
|
|
4086
5491
|
);
|
|
4087
5492
|
let tone;
|
|
4088
|
-
|
|
5493
|
+
dragState && (tone = dragState === "valid" ? "positive" : "critical");
|
|
5494
|
+
const acceptMimeString = props.config?.acceptedMimeTypes?.length ? props.config.acceptedMimeTypes.join(",") : "video/*, audio/*";
|
|
5495
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4089
5496
|
/* @__PURE__ */ jsx(
|
|
4090
5497
|
UploadCard,
|
|
4091
5498
|
{
|
|
@@ -4107,9 +5514,11 @@ function Uploader(props) {
|
|
|
4107
5514
|
readOnly: props.readOnly,
|
|
4108
5515
|
asset: props.asset,
|
|
4109
5516
|
onChange: props.onChange,
|
|
5517
|
+
config: props.config,
|
|
4110
5518
|
buttons: /* @__PURE__ */ jsx(
|
|
4111
5519
|
PlayerActionsMenu$1,
|
|
4112
5520
|
{
|
|
5521
|
+
accept: acceptMimeString,
|
|
4113
5522
|
asset: props.asset,
|
|
4114
5523
|
dialogState: props.dialogState,
|
|
4115
5524
|
setDialogState: props.setDialogState,
|
|
@@ -4125,6 +5534,7 @@ function Uploader(props) {
|
|
|
4125
5534
|
) : /* @__PURE__ */ jsx(
|
|
4126
5535
|
UploadPlaceholder,
|
|
4127
5536
|
{
|
|
5537
|
+
accept: acceptMimeString,
|
|
4128
5538
|
hovering: dragState !== null,
|
|
4129
5539
|
onSelect: handleUpload,
|
|
4130
5540
|
readOnly: !!props.readOnly,
|
|
@@ -4385,7 +5795,8 @@ const muxVideoSchema = {
|
|
|
4385
5795
|
defaultPublic: !0,
|
|
4386
5796
|
defaultSigned: !1,
|
|
4387
5797
|
tool: DEFAULT_TOOL_CONFIG,
|
|
4388
|
-
allowedRolesForConfiguration: []
|
|
5798
|
+
allowedRolesForConfiguration: [],
|
|
5799
|
+
acceptedMimeTypes: ["video/*", "audio/*"]
|
|
4389
5800
|
};
|
|
4390
5801
|
function convertLegacyConfig(config) {
|
|
4391
5802
|
return config.static_renditions && config.static_renditions.length > 0 ? { static_renditions: config.static_renditions } : config.mp4_support === "standard" ? { static_renditions: ["highest"] } : { static_renditions: [] };
|