@layers-app/editor-video 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +17 -3
- package/dist/index.js +800 -545
- package/dist/index.js.map +1 -1
- package/dist/plugin/behaviour.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/ui/VideoBlock.d.ts.map +1 -1
- package/dist/ui/VideoUploadComponent.d.ts.map +1 -1
- package/dist/ui/components/VideoCustomControls.d.ts.map +1 -1
- package/dist/ui/components/VideoSettingsModal/ChaptersSection.d.ts +3 -1
- package/dist/ui/components/VideoSettingsModal/ChaptersSection.d.ts.map +1 -1
- package/dist/ui/components/VideoSettingsModal/CoverSection.d.ts.map +1 -1
- package/dist/ui/components/VideoSettingsModal/FooterActions.d.ts +2 -1
- package/dist/ui/components/VideoSettingsModal/FooterActions.d.ts.map +1 -1
- package/dist/ui/components/VideoSettingsModal/ManualSubtitlesPanel.d.ts +2 -1
- package/dist/ui/components/VideoSettingsModal/ManualSubtitlesPanel.d.ts.map +1 -1
- package/dist/ui/components/VideoSettingsModal/SubtitlesSection.d.ts.map +1 -1
- package/dist/ui/components/VideoSettingsModal/index.d.ts +2 -1
- package/dist/ui/components/VideoSettingsModal/index.d.ts.map +1 -1
- package/dist/ui/components/VideoSubtitlesMenu/index.d.ts.map +1 -1
- package/dist/ui/hooks/useVideoTranscoding.d.ts.map +1 -1
- package/dist/ui/hooks/useVideoUpload.d.ts.map +1 -1
- package/dist/ui/states/UploadState.d.ts.map +1 -1
- package/dist/ui/states/VideoPlayerState.d.ts +3 -1
- package/dist/ui/states/VideoPlayerState.d.ts.map +1 -1
- package/dist/ui/utils/api.d.ts.map +1 -1
- package/dist/ui/utils/videoSubtitlesApi.d.ts +5 -0
- package/dist/ui/utils/videoSubtitlesApi.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
|
5
5
|
import { createContext, useState, useRef, useCallback, useMemo, useContext, forwardRef, createElement, useEffect } from "react";
|
|
6
6
|
import { createCommand, $getNodeByKey, $setSelection, COMMAND_PRIORITY_LOW, DecoratorNode } from "lexical";
|
|
7
7
|
import { useTranslation } from "react-i18next";
|
|
8
|
-
import { Tooltip, ActionIcon, Text, Progress, Paper, Button, TextInput, Menu, Box, Slider, Flex, Select, Stack, Divider, Group, Textarea, Loader, Modal } from "@mantine/core";
|
|
8
|
+
import { Tooltip, ActionIcon, Text, Progress, Paper, Button, TextInput, Menu, Box, Slider, Flex, Select, Stack, Divider, Group, Textarea, Loader, Switch, Modal } from "@mantine/core";
|
|
9
9
|
import { BlockWithAlignableContents } from "@lexical/react/LexicalBlockWithAlignableContents";
|
|
10
10
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
11
11
|
import { Dropzone as Dropzone$1, IMAGE_MIME_TYPE } from "@mantine/dropzone";
|
|
@@ -328,25 +328,34 @@ function setVideoSize(editor, nodeKey, width, height) {
|
|
|
328
328
|
});
|
|
329
329
|
}
|
|
330
330
|
function setVideoPoster(editor, nodeKey, posterUrl) {
|
|
331
|
-
editor.update(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
editor.update(
|
|
332
|
+
() => {
|
|
333
|
+
const node = $getNodeByKey(nodeKey);
|
|
334
|
+
if (!node || !$isVideoNode(node)) return;
|
|
335
|
+
node.setPosterUrl(posterUrl || "");
|
|
336
|
+
},
|
|
337
|
+
{ tag: "skip-scroll-into-view" }
|
|
338
|
+
);
|
|
336
339
|
}
|
|
337
340
|
function setVideoPrimaryUrl(editor, nodeKey, primaryUrl) {
|
|
338
|
-
editor.update(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
editor.update(
|
|
342
|
+
() => {
|
|
343
|
+
const node = $getNodeByKey(nodeKey);
|
|
344
|
+
if (!node || !$isVideoNode(node)) return;
|
|
345
|
+
node.setUrl(primaryUrl);
|
|
346
|
+
},
|
|
347
|
+
{ tag: "skip-scroll-into-view" }
|
|
348
|
+
);
|
|
343
349
|
}
|
|
344
350
|
function setVideoChaptersDisabled(editor, nodeKey, disabled2) {
|
|
345
|
-
editor.update(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
351
|
+
editor.update(
|
|
352
|
+
() => {
|
|
353
|
+
const node = $getNodeByKey(nodeKey);
|
|
354
|
+
if (!node || !$isVideoNode(node)) return;
|
|
355
|
+
node.setChaptersDisabled(disabled2);
|
|
356
|
+
},
|
|
357
|
+
{ tag: "skip-scroll-into-view" }
|
|
358
|
+
);
|
|
350
359
|
}
|
|
351
360
|
function removeVideoNode(editor, nodeKey, deleteNode) {
|
|
352
361
|
deleteNode(editor, nodeKey);
|
|
@@ -854,11 +863,6 @@ function useVideoTranscoding({
|
|
|
854
863
|
const now = Date.now();
|
|
855
864
|
if (!procStallReportedRef.current && lastProcChangeAtRef.current > 0 && now - lastProcChangeAtRef.current > STALL_TIMEOUT_MS$1) {
|
|
856
865
|
procStallReportedRef.current = true;
|
|
857
|
-
console.warn("Processing stall detected", {
|
|
858
|
-
stage: proc.stage,
|
|
859
|
-
percent,
|
|
860
|
-
stalledMs: now - lastProcChangeAtRef.current
|
|
861
|
-
});
|
|
862
866
|
}
|
|
863
867
|
const monotonicPercent = Math.max(maxPercentRef.current, percent);
|
|
864
868
|
maxPercentRef.current = monotonicPercent;
|
|
@@ -955,7 +959,26 @@ function useCancelUpload() {
|
|
|
955
959
|
const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024;
|
|
956
960
|
const STALL_TIMEOUT_MS = 3e4;
|
|
957
961
|
const MAX_CONSECUTIVE_CONFLICTS = 3;
|
|
962
|
+
const MAX_RETRIES_PER_CHUNK = 3;
|
|
963
|
+
const MAX_TOTAL_RETRIES = 15;
|
|
964
|
+
const RETRY_BASE_DELAY_MS = 3e3;
|
|
965
|
+
function abortableSleep(ms, signal) {
|
|
966
|
+
return new Promise((resolve) => {
|
|
967
|
+
if (signal == null ? void 0 : signal.aborted) {
|
|
968
|
+
resolve();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const id = setTimeout(resolve, ms);
|
|
972
|
+
signal == null ? void 0 : signal.addEventListener("abort", () => {
|
|
973
|
+
clearTimeout(id);
|
|
974
|
+
resolve();
|
|
975
|
+
}, { once: true });
|
|
976
|
+
});
|
|
977
|
+
}
|
|
958
978
|
function getBaseUrl() {
|
|
979
|
+
if (typeof window !== "undefined" && window.location.hostname === "dev-app.layers.md") {
|
|
980
|
+
return "https://api.layers.md";
|
|
981
|
+
}
|
|
959
982
|
return "";
|
|
960
983
|
}
|
|
961
984
|
function getAuthHeaders$2() {
|
|
@@ -971,10 +994,13 @@ async function uploadChunkWithProgress(videoId, file, offset, end, total, baseUr
|
|
|
971
994
|
onXhrReady(xhr);
|
|
972
995
|
}
|
|
973
996
|
if (signal) {
|
|
997
|
+
if (signal.aborted) {
|
|
998
|
+
reject(new Error("Upload cancelled"));
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
974
1001
|
signal.addEventListener("abort", () => {
|
|
975
1002
|
xhr.abort();
|
|
976
|
-
|
|
977
|
-
});
|
|
1003
|
+
}, { once: true });
|
|
978
1004
|
}
|
|
979
1005
|
xhr.upload.addEventListener("progress", (e) => {
|
|
980
1006
|
if (e.lengthComputable && onProgress) {
|
|
@@ -987,18 +1013,30 @@ async function uploadChunkWithProgress(videoId, file, offset, end, total, baseUr
|
|
|
987
1013
|
const contentType = xhr.getResponseHeader("content-type") || "";
|
|
988
1014
|
const isJson = contentType.includes("application/json");
|
|
989
1015
|
const data = isJson ? JSON.parse(xhr.responseText) : xhr.responseText;
|
|
990
|
-
if (xhr.status === 409 && data && typeof data === "object" && "expectedOffset" in data) {
|
|
991
|
-
resolve({
|
|
992
|
-
expectedOffset: data.expectedOffset
|
|
993
|
-
});
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
1016
|
resolve(
|
|
997
1017
|
typeof data === "object" && data !== null ? data : {}
|
|
998
1018
|
);
|
|
999
1019
|
} catch (error) {
|
|
1000
1020
|
reject(error);
|
|
1001
1021
|
}
|
|
1022
|
+
} else if (xhr.status === 409) {
|
|
1023
|
+
try {
|
|
1024
|
+
const contentType = xhr.getResponseHeader("content-type") || "";
|
|
1025
|
+
const isJson = contentType.includes("application/json");
|
|
1026
|
+
const data = isJson ? JSON.parse(xhr.responseText) : {};
|
|
1027
|
+
if (data && typeof data === "object" && "expectedOffset" in data) {
|
|
1028
|
+
resolve({
|
|
1029
|
+
expectedOffset: data.expectedOffset
|
|
1030
|
+
});
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
reject({
|
|
1036
|
+
status: xhr.status,
|
|
1037
|
+
statusText: xhr.statusText,
|
|
1038
|
+
data: { message: "Upload conflict" }
|
|
1039
|
+
});
|
|
1002
1040
|
} else {
|
|
1003
1041
|
let errorData = {};
|
|
1004
1042
|
try {
|
|
@@ -1035,9 +1073,7 @@ async function uploadChunkWithProgress(videoId, file, offset, end, total, baseUr
|
|
|
1035
1073
|
Object.keys(headers).forEach((key) => {
|
|
1036
1074
|
xhr.setRequestHeader(key, headers[key]);
|
|
1037
1075
|
});
|
|
1038
|
-
|
|
1039
|
-
xhr.withCredentials = true;
|
|
1040
|
-
}
|
|
1076
|
+
xhr.withCredentials = true;
|
|
1041
1077
|
xhr.send(blob);
|
|
1042
1078
|
});
|
|
1043
1079
|
}
|
|
@@ -1073,13 +1109,16 @@ function useVideoUpload({
|
|
|
1073
1109
|
isPaused: false,
|
|
1074
1110
|
isPausing: false,
|
|
1075
1111
|
currentXhr: null,
|
|
1076
|
-
|
|
1112
|
+
chunkAbortController: null,
|
|
1113
|
+
uploadSession: 0,
|
|
1114
|
+
cancelled: false
|
|
1077
1115
|
});
|
|
1078
1116
|
const progressIntervalRef = useRef(
|
|
1079
1117
|
null
|
|
1080
1118
|
);
|
|
1081
1119
|
const speedHistoryRef = useRef([]);
|
|
1082
1120
|
const lastDisplayedRemainingRef = useRef(0);
|
|
1121
|
+
const maxDisplayedPercentRef = useRef(0);
|
|
1083
1122
|
const MAX_SPEED_HISTORY = 20;
|
|
1084
1123
|
const EMA_ALPHA = 0.15;
|
|
1085
1124
|
const emaSpeedRef = useRef(null);
|
|
@@ -1096,6 +1135,11 @@ function useVideoUpload({
|
|
|
1096
1135
|
}
|
|
1097
1136
|
const timeDiff = (now - state.lastTime) / 1e3;
|
|
1098
1137
|
const uploadedDiff = uploaded - state.lastBytes;
|
|
1138
|
+
if (uploadedDiff < 0) {
|
|
1139
|
+
state.lastBytes = uploaded;
|
|
1140
|
+
state.lastTime = now;
|
|
1141
|
+
return { speed: emaSpeedRef.current ?? 0, remaining: lastDisplayedRemainingRef.current };
|
|
1142
|
+
}
|
|
1099
1143
|
if (timeDiff > 0 && !state.isPaused) {
|
|
1100
1144
|
const instantSpeed = Math.max(0, uploadedDiff / timeDiff);
|
|
1101
1145
|
speedHistoryRef.current.push(instantSpeed);
|
|
@@ -1134,10 +1178,12 @@ function useVideoUpload({
|
|
|
1134
1178
|
(uploaded, total) => {
|
|
1135
1179
|
const now = Date.now();
|
|
1136
1180
|
const { speed, remaining } = calculateSpeed(uploaded, total, now);
|
|
1137
|
-
const
|
|
1181
|
+
const rawPercent = Math.floor(uploaded * 100 / total);
|
|
1182
|
+
const percent = Math.max(rawPercent, maxDisplayedPercentRef.current);
|
|
1183
|
+
maxDisplayedPercentRef.current = percent;
|
|
1138
1184
|
setUploadProgress({
|
|
1139
1185
|
percent,
|
|
1140
|
-
uploaded,
|
|
1186
|
+
uploaded: Math.max(uploaded, total * percent / 100),
|
|
1141
1187
|
total,
|
|
1142
1188
|
speed,
|
|
1143
1189
|
remaining
|
|
@@ -1147,7 +1193,7 @@ function useVideoUpload({
|
|
|
1147
1193
|
);
|
|
1148
1194
|
const uploadFileChunks = useCallback(
|
|
1149
1195
|
async (file, videoId, startOffset = 0) => {
|
|
1150
|
-
var _a, _b, _c;
|
|
1196
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1151
1197
|
const state = uploadStateRef.current;
|
|
1152
1198
|
const total = file.size;
|
|
1153
1199
|
let offset = Math.max(0, startOffset);
|
|
@@ -1170,6 +1216,8 @@ function useVideoUpload({
|
|
|
1170
1216
|
}
|
|
1171
1217
|
}, 1e3);
|
|
1172
1218
|
let consecutiveConflicts = 0;
|
|
1219
|
+
let chunkRetries = 0;
|
|
1220
|
+
let totalRetries = 0;
|
|
1173
1221
|
let hasSuccessfulChunk = false;
|
|
1174
1222
|
if (offset > 0) {
|
|
1175
1223
|
updateProgress(offset, total);
|
|
@@ -1177,7 +1225,7 @@ function useVideoUpload({
|
|
|
1177
1225
|
updateProgress(0, total);
|
|
1178
1226
|
}
|
|
1179
1227
|
while (offset < total) {
|
|
1180
|
-
if ((_a = state.signal) == null ? void 0 : _a.aborted) {
|
|
1228
|
+
if (state.cancelled || ((_a = state.signal) == null ? void 0 : _a.aborted)) {
|
|
1181
1229
|
throw new Error("Upload cancelled");
|
|
1182
1230
|
}
|
|
1183
1231
|
if (state.isPaused) {
|
|
@@ -1192,13 +1240,17 @@ function useVideoUpload({
|
|
|
1192
1240
|
const nowStall = Date.now();
|
|
1193
1241
|
if (!state.uploadStallReported && nowStall - state.lastUploadAckAt > STALL_TIMEOUT_MS) {
|
|
1194
1242
|
state.uploadStallReported = true;
|
|
1195
|
-
console.warn("Upload stall detected", {
|
|
1196
|
-
atOffset: offset,
|
|
1197
|
-
stalledMs: nowStall - state.lastUploadAckAt
|
|
1198
|
-
});
|
|
1199
1243
|
}
|
|
1200
1244
|
const end = Math.min(total - 1, offset + chunkSize - 1);
|
|
1201
1245
|
const chunkStartOffset = offset;
|
|
1246
|
+
const chunkAc = new AbortController();
|
|
1247
|
+
state.chunkAbortController = chunkAc;
|
|
1248
|
+
const forwardAbort = () => chunkAc.abort();
|
|
1249
|
+
if (((_b = state.signal) == null ? void 0 : _b.aborted) || state.cancelled) {
|
|
1250
|
+
chunkAc.abort();
|
|
1251
|
+
} else if (state.signal) {
|
|
1252
|
+
state.signal.addEventListener("abort", forwardAbort, { once: true });
|
|
1253
|
+
}
|
|
1202
1254
|
try {
|
|
1203
1255
|
const response = await uploadChunkWithProgress(
|
|
1204
1256
|
videoId,
|
|
@@ -1208,7 +1260,7 @@ function useVideoUpload({
|
|
|
1208
1260
|
total,
|
|
1209
1261
|
baseUrl,
|
|
1210
1262
|
authHeaders,
|
|
1211
|
-
|
|
1263
|
+
chunkAc.signal,
|
|
1212
1264
|
(loaded) => {
|
|
1213
1265
|
if (state.isPaused || state.isPausing) return;
|
|
1214
1266
|
const estimatedOffset = chunkStartOffset + loaded;
|
|
@@ -1220,13 +1272,11 @@ function useVideoUpload({
|
|
|
1220
1272
|
state.currentXhr = xhr;
|
|
1221
1273
|
}
|
|
1222
1274
|
);
|
|
1275
|
+
state.chunkAbortController = null;
|
|
1276
|
+
state.currentXhr = null;
|
|
1277
|
+
(_c = state.signal) == null ? void 0 : _c.removeEventListener("abort", forwardAbort);
|
|
1223
1278
|
if (response.expectedOffset != null) {
|
|
1224
1279
|
consecutiveConflicts++;
|
|
1225
|
-
console.warn("Offset mismatch detected", {
|
|
1226
|
-
expectedOffset: response.expectedOffset,
|
|
1227
|
-
hadOffset: offset,
|
|
1228
|
-
attempt: consecutiveConflicts
|
|
1229
|
-
});
|
|
1230
1280
|
if (consecutiveConflicts >= MAX_CONSECUTIVE_CONFLICTS) {
|
|
1231
1281
|
throw {
|
|
1232
1282
|
status: 409,
|
|
@@ -1238,10 +1288,14 @@ function useVideoUpload({
|
|
|
1238
1288
|
continue;
|
|
1239
1289
|
}
|
|
1240
1290
|
consecutiveConflicts = 0;
|
|
1291
|
+
chunkRetries = 0;
|
|
1241
1292
|
hasSuccessfulChunk = true;
|
|
1242
1293
|
offset = response.nextOffset != null ? response.nextOffset : end + 1;
|
|
1294
|
+
if (offset < total) {
|
|
1295
|
+
await abortableSleep(500, state.signal ?? void 0);
|
|
1296
|
+
}
|
|
1243
1297
|
state.currentOffset = offset;
|
|
1244
|
-
state.
|
|
1298
|
+
state.chunkAbortController = null;
|
|
1245
1299
|
state.lastUploadAckAt = Date.now();
|
|
1246
1300
|
state.uploadStallReported = false;
|
|
1247
1301
|
if (!state.isPaused && !state.isPausing) {
|
|
@@ -1255,26 +1309,54 @@ function useVideoUpload({
|
|
|
1255
1309
|
}
|
|
1256
1310
|
} catch (error) {
|
|
1257
1311
|
state.currentXhr = null;
|
|
1312
|
+
state.chunkAbortController = null;
|
|
1313
|
+
(_d = state.signal) == null ? void 0 : _d.removeEventListener("abort", forwardAbort);
|
|
1258
1314
|
if (state.isPaused || state.isPausing) {
|
|
1259
1315
|
state.isPausing = false;
|
|
1316
|
+
state.isPaused = true;
|
|
1260
1317
|
break;
|
|
1261
1318
|
}
|
|
1262
|
-
if ((error == null ? void 0 : error.name) === "AbortError" || ((
|
|
1319
|
+
if ((error == null ? void 0 : error.name) === "AbortError" || ((_e = state.signal) == null ? void 0 : _e.aborted) || state.cancelled) {
|
|
1263
1320
|
throw new Error("Upload cancelled");
|
|
1264
1321
|
}
|
|
1265
|
-
if ((error == null ? void 0 : error.status) === 409 && ((
|
|
1322
|
+
if ((error == null ? void 0 : error.status) === 409 && ((_f = error == null ? void 0 : error.data) == null ? void 0 : _f.expectedOffset) != null) {
|
|
1266
1323
|
consecutiveConflicts++;
|
|
1267
|
-
console.warn("Offset conflict in error handler", {
|
|
1268
|
-
expectedOffset: error.data.expectedOffset,
|
|
1269
|
-
hadOffset: offset,
|
|
1270
|
-
attempt: consecutiveConflicts
|
|
1271
|
-
});
|
|
1272
1324
|
if (consecutiveConflicts >= MAX_CONSECUTIVE_CONFLICTS) {
|
|
1273
1325
|
throw error;
|
|
1274
1326
|
}
|
|
1275
1327
|
offset = error.data.expectedOffset;
|
|
1276
1328
|
continue;
|
|
1277
1329
|
}
|
|
1330
|
+
const isNetworkError = !(error == null ? void 0 : error.status) || (error == null ? void 0 : error.status) === 0 || (error == null ? void 0 : error.status) === 409;
|
|
1331
|
+
if (isNetworkError && chunkRetries < MAX_RETRIES_PER_CHUNK && totalRetries < MAX_TOTAL_RETRIES) {
|
|
1332
|
+
chunkRetries++;
|
|
1333
|
+
totalRetries++;
|
|
1334
|
+
const delay = RETRY_BASE_DELAY_MS * chunkRetries;
|
|
1335
|
+
await abortableSleep(delay, state.signal ?? void 0);
|
|
1336
|
+
if (state.cancelled || ((_g = state.signal) == null ? void 0 : _g.aborted) || state.isPaused || state.isPausing) {
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
try {
|
|
1340
|
+
const currentStatus = await fetchStatus(videoId, baseUrl, authHeaders);
|
|
1341
|
+
if (((_h = currentStatus.upload) == null ? void 0 : _h.nextOffset) != null) {
|
|
1342
|
+
const serverOffset = currentStatus.upload.nextOffset;
|
|
1343
|
+
if (serverOffset === offset && chunkRetries >= 2) {
|
|
1344
|
+
await abortableSleep(5e3, state.signal ?? void 0);
|
|
1345
|
+
const recheck = await fetchStatus(videoId, baseUrl, authHeaders);
|
|
1346
|
+
if (((_i = recheck.upload) == null ? void 0 : _i.nextOffset) != null && recheck.upload.nextOffset > offset) {
|
|
1347
|
+
offset = recheck.upload.nextOffset;
|
|
1348
|
+
state.currentOffset = offset;
|
|
1349
|
+
chunkRetries = 0;
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
offset = serverOffset;
|
|
1354
|
+
state.currentOffset = serverOffset;
|
|
1355
|
+
}
|
|
1356
|
+
} catch {
|
|
1357
|
+
}
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1278
1360
|
throw error;
|
|
1279
1361
|
}
|
|
1280
1362
|
}
|
|
@@ -1303,7 +1385,15 @@ function useVideoUpload({
|
|
|
1303
1385
|
);
|
|
1304
1386
|
const handleUpload = useCallback(
|
|
1305
1387
|
async (file) => {
|
|
1306
|
-
var _a, _b;
|
|
1388
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1389
|
+
const state0 = uploadStateRef.current;
|
|
1390
|
+
if (state0.isPaused || state0.isPausing) {
|
|
1391
|
+
state0.isPaused = false;
|
|
1392
|
+
state0.isPausing = false;
|
|
1393
|
+
state0.cancelled = true;
|
|
1394
|
+
state0.videoId = null;
|
|
1395
|
+
state0.attachmentId = null;
|
|
1396
|
+
}
|
|
1307
1397
|
setIsUploading(true);
|
|
1308
1398
|
setIsPaused(false);
|
|
1309
1399
|
const state = uploadStateRef.current;
|
|
@@ -1311,11 +1401,13 @@ function useVideoUpload({
|
|
|
1311
1401
|
const mySession = state.uploadSession;
|
|
1312
1402
|
state.isPaused = false;
|
|
1313
1403
|
state.isPausing = false;
|
|
1404
|
+
state.cancelled = false;
|
|
1314
1405
|
state.file = file;
|
|
1315
1406
|
state.currentOffset = 0;
|
|
1316
1407
|
state.lastTime = 0;
|
|
1317
1408
|
state.lastBytes = 0;
|
|
1318
1409
|
state.uploadStallReported = false;
|
|
1410
|
+
maxDisplayedPercentRef.current = 0;
|
|
1319
1411
|
state.signal = start();
|
|
1320
1412
|
try {
|
|
1321
1413
|
if (!parentId) {
|
|
@@ -1338,19 +1430,19 @@ function useVideoUpload({
|
|
|
1338
1430
|
state.videoId = videoId;
|
|
1339
1431
|
state.attachmentId = attachmentId;
|
|
1340
1432
|
setUploadVideoId(videoId);
|
|
1341
|
-
|
|
1342
|
-
const
|
|
1343
|
-
if (((_a =
|
|
1344
|
-
startOffset =
|
|
1433
|
+
try {
|
|
1434
|
+
const verifiedStatus = await fetchStatus(videoId, baseUrl, authHeaders);
|
|
1435
|
+
if (((_a = verifiedStatus.upload) == null ? void 0 : _a.nextOffset) != null && verifiedStatus.upload.nextOffset > 0) {
|
|
1436
|
+
startOffset = verifiedStatus.upload.nextOffset;
|
|
1437
|
+
} else if (((_c = (_b = initResponse.status) == null ? void 0 : _b.upload) == null ? void 0 : _c.nextOffset) != null) {
|
|
1438
|
+
startOffset = initResponse.status.upload.nextOffset;
|
|
1439
|
+
}
|
|
1440
|
+
} catch {
|
|
1441
|
+
if (((_e = (_d = initResponse.status) == null ? void 0 : _d.upload) == null ? void 0 : _e.nextOffset) != null) {
|
|
1442
|
+
startOffset = initResponse.status.upload.nextOffset;
|
|
1345
1443
|
}
|
|
1346
1444
|
}
|
|
1347
1445
|
} catch (initError) {
|
|
1348
|
-
console.error("Init video error:", {
|
|
1349
|
-
status: initError == null ? void 0 : initError.status,
|
|
1350
|
-
statusText: initError == null ? void 0 : initError.statusText,
|
|
1351
|
-
data: initError == null ? void 0 : initError.data,
|
|
1352
|
-
error: initError
|
|
1353
|
-
});
|
|
1354
1446
|
throw initError;
|
|
1355
1447
|
}
|
|
1356
1448
|
} else {
|
|
@@ -1360,7 +1452,7 @@ function useVideoUpload({
|
|
|
1360
1452
|
baseUrl,
|
|
1361
1453
|
authHeaders
|
|
1362
1454
|
);
|
|
1363
|
-
if (((
|
|
1455
|
+
if (((_f = status.upload) == null ? void 0 : _f.nextOffset) != null) {
|
|
1364
1456
|
startOffset = status.upload.nextOffset;
|
|
1365
1457
|
} else {
|
|
1366
1458
|
videoId = null;
|
|
@@ -1415,20 +1507,14 @@ function useVideoUpload({
|
|
|
1415
1507
|
if (state.isPaused || state.isPausing) {
|
|
1416
1508
|
return;
|
|
1417
1509
|
}
|
|
1418
|
-
console.error("Upload error:", (error == null ? void 0 : error.message) || error);
|
|
1419
1510
|
if ((error == null ? void 0 : error.message) === "Upload cancelled") {
|
|
1420
1511
|
return;
|
|
1421
1512
|
}
|
|
1422
1513
|
if ((error == null ? void 0 : error.status) === 409) {
|
|
1423
1514
|
onError("offset-mismatch", uploadStateRef.current.videoId, error);
|
|
1424
1515
|
} else if (error == null ? void 0 : error.status) {
|
|
1425
|
-
console.error(
|
|
1426
|
-
`API error: ${error.status} ${error.statusText}`,
|
|
1427
|
-
error.data
|
|
1428
|
-
);
|
|
1429
1516
|
onError("interrupted", uploadStateRef.current.videoId, error);
|
|
1430
1517
|
} else {
|
|
1431
|
-
console.error("Unknown upload error:", error);
|
|
1432
1518
|
onError("interrupted", uploadStateRef.current.videoId, error);
|
|
1433
1519
|
}
|
|
1434
1520
|
setIsUploading(false);
|
|
@@ -1453,15 +1539,15 @@ function useVideoUpload({
|
|
|
1453
1539
|
return;
|
|
1454
1540
|
}
|
|
1455
1541
|
state.isPausing = true;
|
|
1456
|
-
state.isPaused = true;
|
|
1457
1542
|
setIsPaused(true);
|
|
1458
1543
|
if (progressIntervalRef.current) {
|
|
1459
1544
|
clearInterval(progressIntervalRef.current);
|
|
1460
1545
|
progressIntervalRef.current = null;
|
|
1461
1546
|
}
|
|
1462
|
-
if (state.
|
|
1463
|
-
state.
|
|
1547
|
+
if (state.chunkAbortController) {
|
|
1548
|
+
state.chunkAbortController.abort();
|
|
1464
1549
|
} else {
|
|
1550
|
+
state.isPaused = true;
|
|
1465
1551
|
state.isPausing = false;
|
|
1466
1552
|
}
|
|
1467
1553
|
}, []);
|
|
@@ -1499,6 +1585,7 @@ function useVideoUpload({
|
|
|
1499
1585
|
setIsPaused(false);
|
|
1500
1586
|
state.isPaused = false;
|
|
1501
1587
|
state.isPausing = false;
|
|
1588
|
+
state.cancelled = false;
|
|
1502
1589
|
await uploadFileChunks(state.file, state.videoId, startOffset);
|
|
1503
1590
|
if (state.uploadSession !== mySession) return;
|
|
1504
1591
|
if (!state.isPaused && !state.isPausing && state.videoId) {
|
|
@@ -1513,7 +1600,7 @@ function useVideoUpload({
|
|
|
1513
1600
|
if ((error == null ? void 0 : error.message) === "Upload cancelled") {
|
|
1514
1601
|
return;
|
|
1515
1602
|
}
|
|
1516
|
-
if (((error == null ? void 0 : error.status) === 0 || (error == null ? void 0 : error.status) === 409) && state.file) {
|
|
1603
|
+
if (((error == null ? void 0 : error.status) === 0 || (error == null ? void 0 : error.status) === 409) && state.file && !state.cancelled) {
|
|
1517
1604
|
state.videoId = null;
|
|
1518
1605
|
state.attachmentId = null;
|
|
1519
1606
|
handleUpload(state.file);
|
|
@@ -1532,10 +1619,14 @@ function useVideoUpload({
|
|
|
1532
1619
|
}, [baseUrl, authHeaders, uploadFileChunks, onError, start, handleUpload]);
|
|
1533
1620
|
const handleCancel = useCallback(() => {
|
|
1534
1621
|
const state = uploadStateRef.current;
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1622
|
+
if (state.chunkAbortController) {
|
|
1623
|
+
try {
|
|
1624
|
+
state.chunkAbortController.abort();
|
|
1625
|
+
} catch {
|
|
1626
|
+
}
|
|
1538
1627
|
}
|
|
1628
|
+
state.cancelled = true;
|
|
1629
|
+
cancelUpload();
|
|
1539
1630
|
if (progressIntervalRef.current) {
|
|
1540
1631
|
clearInterval(progressIntervalRef.current);
|
|
1541
1632
|
progressIntervalRef.current = null;
|
|
@@ -1559,7 +1650,9 @@ function useVideoUpload({
|
|
|
1559
1650
|
state.isPaused = false;
|
|
1560
1651
|
state.isPausing = false;
|
|
1561
1652
|
state.currentXhr = null;
|
|
1653
|
+
state.chunkAbortController = null;
|
|
1562
1654
|
speedHistoryRef.current = [];
|
|
1655
|
+
maxDisplayedPercentRef.current = 0;
|
|
1563
1656
|
setUploadVideoId(null);
|
|
1564
1657
|
}, [cancelUpload]);
|
|
1565
1658
|
return {
|
|
@@ -1669,7 +1762,7 @@ function CheckCircleIcon({
|
|
|
1669
1762
|
"path",
|
|
1670
1763
|
{
|
|
1671
1764
|
d: "M16 1.66699C23.916 1.66699 30.333 8.08391 30.333 16C30.333 23.916 23.916 30.333 16 30.333C8.08391 30.333 1.66699 23.916 1.66699 16C1.66699 8.08392 8.08392 1.66699 16 1.66699Z",
|
|
1672
|
-
fill: "var(--mantine-primary-color-filled, #
|
|
1765
|
+
fill: "var(--mantine-primary-color-filled, #4c6ef5)"
|
|
1673
1766
|
}
|
|
1674
1767
|
),
|
|
1675
1768
|
/* @__PURE__ */ jsx(
|
|
@@ -1816,7 +1909,7 @@ function ProgressBar({
|
|
|
1816
1909
|
"%"
|
|
1817
1910
|
] }),
|
|
1818
1911
|
isPaused ? /* @__PURE__ */ jsx(Text, { size: "sm", c: "var(--mantine-color-text)", children: t("editor.video.actions.pause") }) : showRemaining && progress.remaining > 0 && /* @__PURE__ */ jsxs(Text, { size: "sm", c: "var(--mantine-color-dimmed)", children: [
|
|
1819
|
-
"
|
|
1912
|
+
t("editor.video.upload.remaining"),
|
|
1820
1913
|
/* @__PURE__ */ jsxs(Text, { component: "span", c: "var(--mantine-color-text)", children: [
|
|
1821
1914
|
" ",
|
|
1822
1915
|
formatTime2(progress.remaining)
|
|
@@ -1826,7 +1919,7 @@ function ProgressBar({
|
|
|
1826
1919
|
/* @__PURE__ */ jsx(Progress, { value: clampedPercent, size: 6, radius: "md" }),
|
|
1827
1920
|
showInfo && /* @__PURE__ */ jsxs("div", { className: "video-upload-progress-info", children: [
|
|
1828
1921
|
/* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
|
|
1829
|
-
"
|
|
1922
|
+
t("editor.video.upload.progress"),
|
|
1830
1923
|
/* @__PURE__ */ jsxs(Text, { component: "span", size: "xs", c: "var(--mantine-color-text)", inherit: true, children: [
|
|
1831
1924
|
" ",
|
|
1832
1925
|
formatBytes(progress.uploaded),
|
|
@@ -2413,6 +2506,7 @@ function UploadState({
|
|
|
2413
2506
|
onCancel,
|
|
2414
2507
|
isPaused = false
|
|
2415
2508
|
}) {
|
|
2509
|
+
const { t } = useTranslation();
|
|
2416
2510
|
return /* @__PURE__ */ jsxs(
|
|
2417
2511
|
Paper,
|
|
2418
2512
|
{
|
|
@@ -2481,7 +2575,7 @@ function UploadState({
|
|
|
2481
2575
|
]
|
|
2482
2576
|
}
|
|
2483
2577
|
),
|
|
2484
|
-
/* @__PURE__ */ jsx(Text, { size: "md", fw: 500, c: "var(--mantine-color-bright)", children: "
|
|
2578
|
+
/* @__PURE__ */ jsx(Text, { size: "md", fw: 500, c: "var(--mantine-color-bright)", children: t("editor.video.upload.uploading") })
|
|
2485
2579
|
]
|
|
2486
2580
|
}
|
|
2487
2581
|
),
|
|
@@ -2953,6 +3047,138 @@ function VideoSpeedMenu({
|
|
|
2953
3047
|
}
|
|
2954
3048
|
);
|
|
2955
3049
|
}
|
|
3050
|
+
function buildApiUrl$1(path, baseUrl) {
|
|
3051
|
+
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
3052
|
+
return path;
|
|
3053
|
+
}
|
|
3054
|
+
const base = baseUrl || (typeof window !== "undefined" ? window.location.origin : "");
|
|
3055
|
+
return `${base}${path}`;
|
|
3056
|
+
}
|
|
3057
|
+
function getAuthHeaders$1(authToken) {
|
|
3058
|
+
if (!authToken) return {};
|
|
3059
|
+
return { Authorization: `Bearer ${authToken}` };
|
|
3060
|
+
}
|
|
3061
|
+
async function parseJson$1(response) {
|
|
3062
|
+
const text = await response.text();
|
|
3063
|
+
if (!text) return null;
|
|
3064
|
+
try {
|
|
3065
|
+
return JSON.parse(text);
|
|
3066
|
+
} catch {
|
|
3067
|
+
return text;
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
async function handleResponse$1(response) {
|
|
3071
|
+
if (!response.ok) {
|
|
3072
|
+
const data = await parseJson$1(response);
|
|
3073
|
+
const message = (data && typeof data === "object" && "message" in data ? data.message : null) || response.statusText || "Request failed";
|
|
3074
|
+
throw new Error(message);
|
|
3075
|
+
}
|
|
3076
|
+
return parseJson$1(response);
|
|
3077
|
+
}
|
|
3078
|
+
function normalizeSubtitleTrack(raw) {
|
|
3079
|
+
const id = (raw == null ? void 0 : raw.id) ?? (raw == null ? void 0 : raw.trackId) ?? (raw == null ? void 0 : raw.subtitleId) ?? "";
|
|
3080
|
+
const langRaw = (raw == null ? void 0 : raw.lang) ?? (raw == null ? void 0 : raw.language) ?? (raw == null ? void 0 : raw.srclang) ?? "";
|
|
3081
|
+
const lang = langRaw ? String(langRaw) : "und";
|
|
3082
|
+
const labelRaw = (raw == null ? void 0 : raw.label) ?? (raw == null ? void 0 : raw.name) ?? (raw == null ? void 0 : raw.title) ?? lang;
|
|
3083
|
+
const label = labelRaw ? String(labelRaw) : lang;
|
|
3084
|
+
const kind = (raw == null ? void 0 : raw.kind) ?? "subtitles";
|
|
3085
|
+
const isDefault = Boolean((raw == null ? void 0 : raw.isDefault) ?? (raw == null ? void 0 : raw.default) ?? false);
|
|
3086
|
+
const isDisabled = Boolean((raw == null ? void 0 : raw.isDisabled) ?? false);
|
|
3087
|
+
const url = typeof (raw == null ? void 0 : raw.url) === "string" ? raw.url : typeof (raw == null ? void 0 : raw.vttUrl) === "string" ? raw.vttUrl : typeof (raw == null ? void 0 : raw.src) === "string" ? raw.src : void 0;
|
|
3088
|
+
const createdAt = typeof (raw == null ? void 0 : raw.createdAt) === "string" ? raw.createdAt : void 0;
|
|
3089
|
+
const fileName = typeof (raw == null ? void 0 : raw.fileName) === "string" ? raw.fileName : void 0;
|
|
3090
|
+
const auto = Boolean((raw == null ? void 0 : raw.auto) ?? (raw == null ? void 0 : raw.isAuto) ?? (raw == null ? void 0 : raw.source) === "auto");
|
|
3091
|
+
return {
|
|
3092
|
+
id: String(id),
|
|
3093
|
+
lang,
|
|
3094
|
+
label,
|
|
3095
|
+
kind: String(kind),
|
|
3096
|
+
isDefault,
|
|
3097
|
+
isDisabled,
|
|
3098
|
+
url,
|
|
3099
|
+
createdAt,
|
|
3100
|
+
fileName,
|
|
3101
|
+
status: "ready",
|
|
3102
|
+
auto
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
function isAutoGeneratedTrack(track) {
|
|
3106
|
+
return track.label.length <= 3;
|
|
3107
|
+
}
|
|
3108
|
+
async function listTracks(videoId, options = {}) {
|
|
3109
|
+
const url = buildApiUrl$1(`/v1/videos/${videoId}/subtitles`, options.baseUrl);
|
|
3110
|
+
const response = await fetch(url, {
|
|
3111
|
+
method: "GET",
|
|
3112
|
+
credentials: "include",
|
|
3113
|
+
headers: {
|
|
3114
|
+
...getAuthHeaders$1(options.authToken)
|
|
3115
|
+
}
|
|
3116
|
+
});
|
|
3117
|
+
const data = await handleResponse$1(response);
|
|
3118
|
+
const rawTracks = Array.isArray(data) ? data : Array.isArray(data == null ? void 0 : data.tracks) ? data.tracks : [];
|
|
3119
|
+
return rawTracks.map(normalizeSubtitleTrack).filter((track) => track.id);
|
|
3120
|
+
}
|
|
3121
|
+
async function uploadTrack(videoId, file, uploadOptions, options = {}) {
|
|
3122
|
+
const params = new URLSearchParams();
|
|
3123
|
+
params.set("lang", uploadOptions.lang);
|
|
3124
|
+
if (uploadOptions.label) {
|
|
3125
|
+
params.set("label", uploadOptions.label);
|
|
3126
|
+
}
|
|
3127
|
+
if (uploadOptions.kind) {
|
|
3128
|
+
params.set("kind", uploadOptions.kind);
|
|
3129
|
+
}
|
|
3130
|
+
{
|
|
3131
|
+
params.set("default", "false");
|
|
3132
|
+
}
|
|
3133
|
+
const url = buildApiUrl$1(
|
|
3134
|
+
`/v1/videos/${videoId}/subtitles?${params.toString()}`,
|
|
3135
|
+
options.baseUrl
|
|
3136
|
+
);
|
|
3137
|
+
const formData = new FormData();
|
|
3138
|
+
formData.append("file", file);
|
|
3139
|
+
const response = await fetch(url, {
|
|
3140
|
+
method: "POST",
|
|
3141
|
+
credentials: "include",
|
|
3142
|
+
headers: {
|
|
3143
|
+
...getAuthHeaders$1(options.authToken)
|
|
3144
|
+
},
|
|
3145
|
+
body: formData,
|
|
3146
|
+
signal: uploadOptions.signal
|
|
3147
|
+
});
|
|
3148
|
+
const data = await handleResponse$1(response);
|
|
3149
|
+
if (!data) return null;
|
|
3150
|
+
return normalizeSubtitleTrack(data);
|
|
3151
|
+
}
|
|
3152
|
+
async function patchTrackDisabled(videoId, trackId, isDisabled, options = {}) {
|
|
3153
|
+
const url = buildApiUrl$1(
|
|
3154
|
+
`/v1/videos/${videoId}/subtitles/${trackId}/disabled`,
|
|
3155
|
+
options.baseUrl
|
|
3156
|
+
);
|
|
3157
|
+
const response = await fetch(url, {
|
|
3158
|
+
method: "PATCH",
|
|
3159
|
+
credentials: "include",
|
|
3160
|
+
headers: {
|
|
3161
|
+
"Content-Type": "application/json",
|
|
3162
|
+
...getAuthHeaders$1(options.authToken)
|
|
3163
|
+
},
|
|
3164
|
+
body: JSON.stringify({ isDisabled })
|
|
3165
|
+
});
|
|
3166
|
+
await handleResponse$1(response);
|
|
3167
|
+
}
|
|
3168
|
+
async function deleteTrack(videoId, trackId, options = {}) {
|
|
3169
|
+
const url = buildApiUrl$1(
|
|
3170
|
+
`/v1/videos/${videoId}/subtitles/${trackId}`,
|
|
3171
|
+
options.baseUrl
|
|
3172
|
+
);
|
|
3173
|
+
const response = await fetch(url, {
|
|
3174
|
+
method: "DELETE",
|
|
3175
|
+
credentials: "include",
|
|
3176
|
+
headers: {
|
|
3177
|
+
...getAuthHeaders$1(options.authToken)
|
|
3178
|
+
}
|
|
3179
|
+
});
|
|
3180
|
+
await handleResponse$1(response);
|
|
3181
|
+
}
|
|
2956
3182
|
const LANG_NAMES = {
|
|
2957
3183
|
en: "English",
|
|
2958
3184
|
es: "Español",
|
|
@@ -2988,7 +3214,7 @@ function VideoSubtitlesMenu({
|
|
|
2988
3214
|
const items = settings.subtitles.map((item) => {
|
|
2989
3215
|
const isActive = item.id === settings.currentSubtitleId;
|
|
2990
3216
|
let label = item.label;
|
|
2991
|
-
if (
|
|
3217
|
+
if (isAutoGeneratedTrack({ label })) {
|
|
2992
3218
|
const fullName = item.lang ? LANG_NAMES[item.lang.toLowerCase()] : null;
|
|
2993
3219
|
if (fullName) label = fullName;
|
|
2994
3220
|
label = `${label} (${autoLabel})`;
|
|
@@ -3176,8 +3402,8 @@ function VideoCustomControls({
|
|
|
3176
3402
|
const visibleBufferedRanges = isProcessing ? [] : bufferedRanges;
|
|
3177
3403
|
const sortedChapters = useMemo(() => {
|
|
3178
3404
|
if (!chapters || chapters.length === 0) return [];
|
|
3179
|
-
return [...chapters].sort((a, b) => a.startSec - b.startSec);
|
|
3180
|
-
}, [chapters]);
|
|
3405
|
+
return [...chapters].filter((ch) => duration > 0 && ch.startSec <= duration).sort((a, b) => a.startSec - b.startSec);
|
|
3406
|
+
}, [chapters, duration]);
|
|
3181
3407
|
const chapterSegments = useMemo(() => {
|
|
3182
3408
|
if (sortedChapters.length === 0 || duration <= 0) return [];
|
|
3183
3409
|
const segments = [];
|
|
@@ -3538,10 +3764,6 @@ function VideoCustomControls({
|
|
|
3538
3764
|
className: "video-player-button",
|
|
3539
3765
|
type: "button",
|
|
3540
3766
|
"aria-label": t("editor.video.player.settings"),
|
|
3541
|
-
onClick: () => {
|
|
3542
|
-
setIsSettingsOpen((prev) => !prev);
|
|
3543
|
-
setActiveMenu("main");
|
|
3544
|
-
},
|
|
3545
3767
|
children: /* @__PURE__ */ jsx(SettingsPlayerIcon, { size: 20 })
|
|
3546
3768
|
}
|
|
3547
3769
|
) }),
|
|
@@ -3579,6 +3801,7 @@ function VideoCustomControls({
|
|
|
3579
3801
|
onSelect: (speed) => {
|
|
3580
3802
|
setOptimisticSpeed(speed);
|
|
3581
3803
|
onSpeedClick(speed);
|
|
3804
|
+
setIsSettingsOpen(false);
|
|
3582
3805
|
}
|
|
3583
3806
|
}
|
|
3584
3807
|
),
|
|
@@ -3588,7 +3811,10 @@ function VideoCustomControls({
|
|
|
3588
3811
|
disabled: isSettingsDisabled,
|
|
3589
3812
|
settings,
|
|
3590
3813
|
onBack: () => setActiveMenu("main"),
|
|
3591
|
-
onSelect:
|
|
3814
|
+
onSelect: (subtitleId) => {
|
|
3815
|
+
onSubtitleSelect(subtitleId);
|
|
3816
|
+
setIsSettingsOpen(false);
|
|
3817
|
+
}
|
|
3592
3818
|
}
|
|
3593
3819
|
),
|
|
3594
3820
|
activeMenu === "quality" && /* @__PURE__ */ jsx(
|
|
@@ -3597,7 +3823,10 @@ function VideoCustomControls({
|
|
|
3597
3823
|
disabled: isSettingsDisabled,
|
|
3598
3824
|
settings,
|
|
3599
3825
|
onBack: () => setActiveMenu("main"),
|
|
3600
|
-
onSelect:
|
|
3826
|
+
onSelect: (qualityId) => {
|
|
3827
|
+
onQualityClick(qualityId);
|
|
3828
|
+
setIsSettingsOpen(false);
|
|
3829
|
+
}
|
|
3601
3830
|
}
|
|
3602
3831
|
)
|
|
3603
3832
|
]
|
|
@@ -3772,141 +4001,21 @@ const setSubtitle = (player, id, videoElement) => {
|
|
|
3772
4001
|
track.mode = index === id ? "showing" : "disabled";
|
|
3773
4002
|
});
|
|
3774
4003
|
};
|
|
3775
|
-
function
|
|
3776
|
-
if (
|
|
3777
|
-
|
|
4004
|
+
function normalizeUrl(url, baseUrl) {
|
|
4005
|
+
if (!url) return null;
|
|
4006
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
4007
|
+
return url;
|
|
3778
4008
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
}
|
|
3782
|
-
function getAuthHeaders$1(authToken) {
|
|
3783
|
-
if (!authToken) return {};
|
|
3784
|
-
return { Authorization: `Bearer ${authToken}` };
|
|
3785
|
-
}
|
|
3786
|
-
async function parseJson$1(response) {
|
|
3787
|
-
const text = await response.text();
|
|
3788
|
-
if (!text) return null;
|
|
3789
|
-
try {
|
|
3790
|
-
return JSON.parse(text);
|
|
3791
|
-
} catch {
|
|
3792
|
-
return text;
|
|
4009
|
+
if (url.startsWith("/")) {
|
|
4010
|
+
const base = baseUrl || window.location.origin;
|
|
4011
|
+
return `${base}${url}`;
|
|
3793
4012
|
}
|
|
4013
|
+
return url;
|
|
3794
4014
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
throw new Error(message);
|
|
3800
|
-
}
|
|
3801
|
-
return parseJson$1(response);
|
|
3802
|
-
}
|
|
3803
|
-
function normalizeSubtitleTrack(raw) {
|
|
3804
|
-
const id = (raw == null ? void 0 : raw.id) ?? (raw == null ? void 0 : raw.trackId) ?? (raw == null ? void 0 : raw.subtitleId) ?? "";
|
|
3805
|
-
const langRaw = (raw == null ? void 0 : raw.lang) ?? (raw == null ? void 0 : raw.language) ?? (raw == null ? void 0 : raw.srclang) ?? "";
|
|
3806
|
-
const lang = langRaw ? String(langRaw) : "und";
|
|
3807
|
-
const labelRaw = (raw == null ? void 0 : raw.label) ?? (raw == null ? void 0 : raw.name) ?? (raw == null ? void 0 : raw.title) ?? lang;
|
|
3808
|
-
const label = labelRaw ? String(labelRaw) : lang;
|
|
3809
|
-
const kind = (raw == null ? void 0 : raw.kind) ?? "subtitles";
|
|
3810
|
-
const isDefault = Boolean((raw == null ? void 0 : raw.isDefault) ?? (raw == null ? void 0 : raw.default) ?? false);
|
|
3811
|
-
const url = typeof (raw == null ? void 0 : raw.url) === "string" ? raw.url : typeof (raw == null ? void 0 : raw.vttUrl) === "string" ? raw.vttUrl : typeof (raw == null ? void 0 : raw.src) === "string" ? raw.src : void 0;
|
|
3812
|
-
const createdAt = typeof (raw == null ? void 0 : raw.createdAt) === "string" ? raw.createdAt : void 0;
|
|
3813
|
-
const fileName = typeof (raw == null ? void 0 : raw.fileName) === "string" ? raw.fileName : void 0;
|
|
3814
|
-
const auto = Boolean((raw == null ? void 0 : raw.auto) ?? (raw == null ? void 0 : raw.isAuto) ?? (raw == null ? void 0 : raw.source) === "auto");
|
|
3815
|
-
return {
|
|
3816
|
-
id: String(id),
|
|
3817
|
-
lang,
|
|
3818
|
-
label,
|
|
3819
|
-
kind: String(kind),
|
|
3820
|
-
isDefault,
|
|
3821
|
-
url,
|
|
3822
|
-
createdAt,
|
|
3823
|
-
fileName,
|
|
3824
|
-
status: "ready",
|
|
3825
|
-
auto
|
|
3826
|
-
};
|
|
3827
|
-
}
|
|
3828
|
-
async function uploadTrack(videoId, file, uploadOptions, options = {}) {
|
|
3829
|
-
const params = new URLSearchParams();
|
|
3830
|
-
params.set("lang", uploadOptions.lang);
|
|
3831
|
-
if (uploadOptions.label) {
|
|
3832
|
-
params.set("label", uploadOptions.label);
|
|
3833
|
-
}
|
|
3834
|
-
if (uploadOptions.kind) {
|
|
3835
|
-
params.set("kind", uploadOptions.kind);
|
|
3836
|
-
}
|
|
3837
|
-
{
|
|
3838
|
-
params.set("default", "false");
|
|
3839
|
-
}
|
|
3840
|
-
const url = buildApiUrl$1(
|
|
3841
|
-
`/v1/videos/${videoId}/subtitles?${params.toString()}`,
|
|
3842
|
-
options.baseUrl
|
|
3843
|
-
);
|
|
3844
|
-
const formData = new FormData();
|
|
3845
|
-
formData.append("file", file);
|
|
3846
|
-
const response = await fetch(url, {
|
|
3847
|
-
method: "POST",
|
|
3848
|
-
credentials: "include",
|
|
3849
|
-
headers: {
|
|
3850
|
-
...getAuthHeaders$1(options.authToken)
|
|
3851
|
-
},
|
|
3852
|
-
body: formData,
|
|
3853
|
-
signal: uploadOptions.signal
|
|
3854
|
-
});
|
|
3855
|
-
const data = await handleResponse$1(response);
|
|
3856
|
-
if (!data) return null;
|
|
3857
|
-
return normalizeSubtitleTrack(data);
|
|
3858
|
-
}
|
|
3859
|
-
function getVttUrl(videoId, trackId, options = {}) {
|
|
3860
|
-
return buildApiUrl$1(
|
|
3861
|
-
`/v1/videos/${videoId}/subtitles/${trackId}.vtt`,
|
|
3862
|
-
options.baseUrl
|
|
3863
|
-
);
|
|
3864
|
-
}
|
|
3865
|
-
function normalizeUrl$1(path, baseUrl) {
|
|
3866
|
-
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
3867
|
-
return path;
|
|
3868
|
-
}
|
|
3869
|
-
const base = baseUrl || (typeof window !== "undefined" ? window.location.origin : "");
|
|
3870
|
-
if (path.startsWith("/")) {
|
|
3871
|
-
return `${base}${path}`;
|
|
3872
|
-
}
|
|
3873
|
-
return `${base}/${path}`;
|
|
3874
|
-
}
|
|
3875
|
-
function resolveSubtitleUrl(videoId, track, options = {}) {
|
|
3876
|
-
if (track.url) {
|
|
3877
|
-
return normalizeUrl$1(track.url, options.baseUrl);
|
|
3878
|
-
}
|
|
3879
|
-
return getVttUrl(videoId, track.id, options);
|
|
3880
|
-
}
|
|
3881
|
-
async function deleteTrack(videoId, trackId, options = {}) {
|
|
3882
|
-
const url = buildApiUrl$1(
|
|
3883
|
-
`/v1/videos/${videoId}/subtitles/${trackId}`,
|
|
3884
|
-
options.baseUrl
|
|
3885
|
-
);
|
|
3886
|
-
const response = await fetch(url, {
|
|
3887
|
-
method: "DELETE",
|
|
3888
|
-
credentials: "include",
|
|
3889
|
-
headers: {
|
|
3890
|
-
...getAuthHeaders$1(options.authToken)
|
|
3891
|
-
}
|
|
3892
|
-
});
|
|
3893
|
-
await handleResponse$1(response);
|
|
3894
|
-
}
|
|
3895
|
-
function normalizeUrl(url, baseUrl) {
|
|
3896
|
-
if (!url) return null;
|
|
3897
|
-
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
3898
|
-
return url;
|
|
3899
|
-
}
|
|
3900
|
-
if (url.startsWith("/")) {
|
|
3901
|
-
const base = baseUrl || window.location.origin;
|
|
3902
|
-
return `${base}${url}`;
|
|
3903
|
-
}
|
|
3904
|
-
return url;
|
|
3905
|
-
}
|
|
3906
|
-
function getNativeSubtitleTracks(videoElement) {
|
|
3907
|
-
return Array.from(videoElement.textTracks || []).filter(
|
|
3908
|
-
(track) => track.kind === "subtitles" || track.kind === "captions"
|
|
3909
|
-
);
|
|
4015
|
+
function getNativeSubtitleTracks(videoElement) {
|
|
4016
|
+
return Array.from(videoElement.textTracks || []).filter(
|
|
4017
|
+
(track) => track.kind === "subtitles" || track.kind === "captions"
|
|
4018
|
+
);
|
|
3910
4019
|
}
|
|
3911
4020
|
function getSubtitleUrl(track, baseUrl) {
|
|
3912
4021
|
if (!track.url) return null;
|
|
@@ -3926,7 +4035,8 @@ function VideoPlayerState({
|
|
|
3926
4035
|
subtitles,
|
|
3927
4036
|
isProcessing = false,
|
|
3928
4037
|
posterUrl,
|
|
3929
|
-
onPlayerInfo
|
|
4038
|
+
onPlayerInfo,
|
|
4039
|
+
onFallback
|
|
3930
4040
|
}) {
|
|
3931
4041
|
const { t, i18n } = useTranslation();
|
|
3932
4042
|
const [currentVideoUrl, setCurrentVideoUrl] = useState(null);
|
|
@@ -3953,6 +4063,9 @@ function VideoPlayerState({
|
|
|
3953
4063
|
);
|
|
3954
4064
|
const shakaAddedIdsRef = useRef([]);
|
|
3955
4065
|
const shakaFilterRegisteredRef = useRef(false);
|
|
4066
|
+
const triedFallbackUrlsRef = useRef(/* @__PURE__ */ new Set());
|
|
4067
|
+
const hlsUrlCheckedRef = useRef(null);
|
|
4068
|
+
const savedStateLockRef = useRef(false);
|
|
3956
4069
|
useEffect(() => {
|
|
3957
4070
|
if (!language) {
|
|
3958
4071
|
return;
|
|
@@ -3966,16 +4079,17 @@ function VideoPlayerState({
|
|
|
3966
4079
|
return;
|
|
3967
4080
|
}
|
|
3968
4081
|
if (subtitles != null) {
|
|
3969
|
-
const normalized = (subtitles || []).map(normalizeSubtitleTrack).filter((track) => track.id);
|
|
4082
|
+
const normalized = (subtitles || []).map(normalizeSubtitleTrack).filter((track) => track.id && !track.isDisabled);
|
|
3970
4083
|
setSubtitleTracks(normalized);
|
|
3971
4084
|
}
|
|
3972
4085
|
}, [videoId, subtitles]);
|
|
3973
4086
|
useEffect(() => {
|
|
3974
4087
|
setIsAutoQualityEnabled(true);
|
|
4088
|
+
hlsUrlCheckedRef.current = null;
|
|
3975
4089
|
}, [videoId]);
|
|
3976
4090
|
const handleSubtitlesOpen = useCallback(() => {
|
|
3977
4091
|
if (!videoId || subtitles == null) return;
|
|
3978
|
-
const normalized = (subtitles || []).map(normalizeSubtitleTrack).filter((track) => track.id);
|
|
4092
|
+
const normalized = (subtitles || []).map(normalizeSubtitleTrack).filter((track) => track.id && !track.isDisabled);
|
|
3979
4093
|
setSubtitleTracks(normalized);
|
|
3980
4094
|
}, [videoId, subtitles]);
|
|
3981
4095
|
useEffect(() => {
|
|
@@ -4082,102 +4196,148 @@ function VideoPlayerState({
|
|
|
4082
4196
|
selectedSubtitleMeta
|
|
4083
4197
|
]);
|
|
4084
4198
|
useEffect(() => {
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4199
|
+
if (!currentVideoUrl) return;
|
|
4200
|
+
let cancelled = false;
|
|
4201
|
+
let player = null;
|
|
4202
|
+
const handleFatalError = (error) => {
|
|
4203
|
+
if (cancelled) return;
|
|
4204
|
+
const fallback = normalizeUrl(nativeVideoUrl, baseUrl) || normalizeUrl(rawUrl ?? null, baseUrl);
|
|
4205
|
+
if (fallback) {
|
|
4206
|
+
setHlsFailed(true);
|
|
4207
|
+
onFallback == null ? void 0 : onFallback(true);
|
|
4208
|
+
} else {
|
|
4209
|
+
setPlayerError(
|
|
4210
|
+
t("editor.video.player.playbackError", { code: error.code })
|
|
4211
|
+
);
|
|
4095
4212
|
}
|
|
4096
|
-
}
|
|
4213
|
+
};
|
|
4097
4214
|
const errorHandler = (event) => {
|
|
4098
4215
|
const error = event.detail;
|
|
4099
|
-
console.error("Shaka Player error:", {
|
|
4100
|
-
code: error.code,
|
|
4101
|
-
category: error.category,
|
|
4102
|
-
severity: error.severity,
|
|
4103
|
-
data: error.data
|
|
4104
|
-
});
|
|
4105
4216
|
if (error.severity === 2) {
|
|
4106
|
-
|
|
4107
|
-
if (fallback) {
|
|
4108
|
-
console.warn(
|
|
4109
|
-
`[VideoPlayer] HLS failed (code=${error.code}), falling back to native video:`,
|
|
4110
|
-
fallback
|
|
4111
|
-
);
|
|
4112
|
-
setHlsFailed(true);
|
|
4113
|
-
} else {
|
|
4114
|
-
setPlayerError(
|
|
4115
|
-
t("editor.video.player.playbackError", { code: error.code })
|
|
4116
|
-
);
|
|
4117
|
-
}
|
|
4217
|
+
handleFatalError(error);
|
|
4118
4218
|
} else {
|
|
4119
4219
|
try {
|
|
4120
|
-
player.recover();
|
|
4121
|
-
} catch
|
|
4122
|
-
console.error("Failed to recover from error:", recoverError);
|
|
4220
|
+
player == null ? void 0 : player.recover();
|
|
4221
|
+
} catch {
|
|
4123
4222
|
}
|
|
4124
4223
|
}
|
|
4125
4224
|
};
|
|
4126
|
-
|
|
4127
|
-
|
|
4225
|
+
const tryLoad = () => {
|
|
4226
|
+
var _a;
|
|
4227
|
+
player = (_a = playerRef.current) == null ? void 0 : _a.player;
|
|
4228
|
+
if (!player) {
|
|
4229
|
+
const raf2 = requestAnimationFrame(tryLoad);
|
|
4230
|
+
return () => cancelAnimationFrame(raf2);
|
|
4231
|
+
}
|
|
4232
|
+
if (errorHandlerRef.current) {
|
|
4233
|
+
try {
|
|
4234
|
+
player.removeEventListener("error", errorHandlerRef.current);
|
|
4235
|
+
} catch {
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
errorHandlerRef.current = errorHandler;
|
|
4239
|
+
player.addEventListener("error", errorHandler);
|
|
4240
|
+
player.load(currentVideoUrl).catch((loadError) => {
|
|
4241
|
+
if (cancelled) return;
|
|
4242
|
+
if (loadError.code === 7e3) return;
|
|
4243
|
+
handleFatalError(loadError);
|
|
4244
|
+
});
|
|
4245
|
+
};
|
|
4246
|
+
const raf = requestAnimationFrame(tryLoad);
|
|
4128
4247
|
return () => {
|
|
4248
|
+
cancelled = true;
|
|
4249
|
+
cancelAnimationFrame(raf);
|
|
4129
4250
|
if (player && errorHandlerRef.current) {
|
|
4130
4251
|
try {
|
|
4131
4252
|
player.removeEventListener("error", errorHandlerRef.current);
|
|
4132
|
-
} catch
|
|
4133
|
-
console.warn("Error removing event listener on cleanup:", e);
|
|
4253
|
+
} catch {
|
|
4134
4254
|
}
|
|
4135
4255
|
}
|
|
4136
4256
|
};
|
|
4137
|
-
}, [currentVideoUrl, nativeVideoUrl, rawUrl, baseUrl]);
|
|
4257
|
+
}, [currentVideoUrl, nativeVideoUrl, rawUrl, baseUrl, onFallback]);
|
|
4138
4258
|
useEffect(() => {
|
|
4139
|
-
|
|
4259
|
+
let cancelled = false;
|
|
4260
|
+
let retryTimer = null;
|
|
4140
4261
|
if (optimizedReady && primaryUrl) {
|
|
4141
4262
|
const normalizedUrl = normalizeUrl(primaryUrl, baseUrl);
|
|
4142
4263
|
if (normalizedUrl) {
|
|
4143
|
-
if (
|
|
4144
|
-
const
|
|
4145
|
-
if (
|
|
4146
|
-
|
|
4147
|
-
time: videoElement.currentTime || 0,
|
|
4148
|
-
paused: videoElement.paused
|
|
4149
|
-
};
|
|
4264
|
+
if (!currentNativeVideoUrl && rawUrl) {
|
|
4265
|
+
const normalizedNative = normalizeUrl(rawUrl, baseUrl);
|
|
4266
|
+
if (normalizedNative) {
|
|
4267
|
+
setCurrentNativeVideoUrl(normalizedNative);
|
|
4150
4268
|
}
|
|
4151
|
-
}
|
|
4152
|
-
|
|
4269
|
+
}
|
|
4270
|
+
if (hlsUrlCheckedRef.current === normalizedUrl) {
|
|
4271
|
+
return;
|
|
4272
|
+
}
|
|
4273
|
+
const nv = nativeVideoRef.current;
|
|
4274
|
+
if (nv && nv.currentTime > 0) {
|
|
4153
4275
|
savedStateRef.current = {
|
|
4154
|
-
time:
|
|
4155
|
-
paused:
|
|
4276
|
+
time: nv.currentTime,
|
|
4277
|
+
paused: nv.paused
|
|
4156
4278
|
};
|
|
4279
|
+
savedStateLockRef.current = true;
|
|
4157
4280
|
}
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4281
|
+
const checkHls = (attempt) => {
|
|
4282
|
+
if (cancelled) return;
|
|
4283
|
+
fetch(normalizedUrl, { method: "HEAD", credentials: "include" }).then((res) => {
|
|
4284
|
+
if (cancelled) return;
|
|
4285
|
+
hlsUrlCheckedRef.current = normalizedUrl;
|
|
4286
|
+
if (res.ok) {
|
|
4287
|
+
setCurrentVideoUrl(normalizedUrl);
|
|
4288
|
+
setPlayerError(null);
|
|
4289
|
+
setHlsFailed(false);
|
|
4290
|
+
onFallback == null ? void 0 : onFallback(false);
|
|
4291
|
+
triedFallbackUrlsRef.current.clear();
|
|
4292
|
+
} else {
|
|
4293
|
+
triedFallbackUrlsRef.current.clear();
|
|
4294
|
+
setHlsFailed(true);
|
|
4295
|
+
onFallback == null ? void 0 : onFallback(true);
|
|
4296
|
+
scheduleRetry(attempt);
|
|
4297
|
+
}
|
|
4298
|
+
}).catch((err) => {
|
|
4299
|
+
if (cancelled) return;
|
|
4300
|
+
triedFallbackUrlsRef.current.clear();
|
|
4301
|
+
setHlsFailed(true);
|
|
4302
|
+
onFallback == null ? void 0 : onFallback(true);
|
|
4303
|
+
scheduleRetry(attempt);
|
|
4304
|
+
});
|
|
4305
|
+
};
|
|
4306
|
+
const MAX_RETRIES = 10;
|
|
4307
|
+
const RETRY_INTERVAL = 5e3;
|
|
4308
|
+
const scheduleRetry = (attempt) => {
|
|
4309
|
+
if (cancelled || attempt >= MAX_RETRIES) return;
|
|
4310
|
+
retryTimer = setTimeout(() => checkHls(attempt + 1), RETRY_INTERVAL);
|
|
4311
|
+
};
|
|
4312
|
+
checkHls(0);
|
|
4162
4313
|
}
|
|
4163
4314
|
} else if (nativeVideoUrl) {
|
|
4164
4315
|
const normalizedUrl = normalizeUrl(nativeVideoUrl, baseUrl);
|
|
4165
4316
|
if (normalizedUrl) {
|
|
4166
|
-
if (previousVideoUrlRef.current
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4317
|
+
if (previousVideoUrlRef.current === normalizedUrl) {
|
|
4318
|
+
return;
|
|
4319
|
+
}
|
|
4320
|
+
const nv = nativeVideoRef.current;
|
|
4321
|
+
if (nv && nv.readyState > 0 && !nv.error && nv.src) {
|
|
4322
|
+
previousVideoUrlRef.current = normalizedUrl;
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
if (previousVideoUrlRef.current && nv && nv.currentTime > 0) {
|
|
4326
|
+
savedStateRef.current = {
|
|
4327
|
+
time: nv.currentTime,
|
|
4328
|
+
paused: nv.paused
|
|
4329
|
+
};
|
|
4174
4330
|
}
|
|
4175
4331
|
setCurrentNativeVideoUrl(normalizedUrl);
|
|
4176
4332
|
setCurrentVideoUrl(null);
|
|
4177
4333
|
previousVideoUrlRef.current = normalizedUrl;
|
|
4178
4334
|
}
|
|
4179
4335
|
}
|
|
4180
|
-
|
|
4336
|
+
return () => {
|
|
4337
|
+
cancelled = true;
|
|
4338
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
4339
|
+
};
|
|
4340
|
+
}, [primaryUrl, baseUrl, optimizedReady, nativeVideoUrl]);
|
|
4181
4341
|
useEffect(() => {
|
|
4182
4342
|
if (optimizedReady && currentVideoUrl && playerRef.current) {
|
|
4183
4343
|
const videoElement = playerRef.current.videoElement;
|
|
@@ -4191,6 +4351,9 @@ function VideoPlayerState({
|
|
|
4191
4351
|
});
|
|
4192
4352
|
}
|
|
4193
4353
|
savedStateRef.current = null;
|
|
4354
|
+
savedStateLockRef.current = false;
|
|
4355
|
+
} else {
|
|
4356
|
+
savedStateLockRef.current = false;
|
|
4194
4357
|
}
|
|
4195
4358
|
}
|
|
4196
4359
|
};
|
|
@@ -4224,10 +4387,35 @@ function VideoPlayerState({
|
|
|
4224
4387
|
}
|
|
4225
4388
|
}
|
|
4226
4389
|
}, [optimizedReady, currentVideoUrl]);
|
|
4390
|
+
useEffect(() => {
|
|
4391
|
+
const nv = nativeVideoRef.current;
|
|
4392
|
+
if (!nv) return;
|
|
4393
|
+
if (currentVideoUrl && !hlsFailed) {
|
|
4394
|
+
if (nv.currentTime > 0) {
|
|
4395
|
+
savedStateRef.current = {
|
|
4396
|
+
time: nv.currentTime,
|
|
4397
|
+
paused: nv.paused
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4400
|
+
if (!nv.paused) {
|
|
4401
|
+
nv.pause();
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
}, [currentVideoUrl, hlsFailed]);
|
|
4227
4405
|
useEffect(() => {
|
|
4228
4406
|
var _a;
|
|
4229
|
-
|
|
4230
|
-
|
|
4407
|
+
if (currentVideoUrl && !hlsFailed) {
|
|
4408
|
+
const shakaVideo = ((_a = playerRef.current) == null ? void 0 : _a.videoElement) || null;
|
|
4409
|
+
if (shakaVideo && shakaVideo.readyState >= 1) {
|
|
4410
|
+
setActiveVideoElement(shakaVideo);
|
|
4411
|
+
} else if (shakaVideo) {
|
|
4412
|
+
const onReady = () => setActiveVideoElement(shakaVideo);
|
|
4413
|
+
shakaVideo.addEventListener("loadedmetadata", onReady, { once: true });
|
|
4414
|
+
return () => shakaVideo.removeEventListener("loadedmetadata", onReady);
|
|
4415
|
+
}
|
|
4416
|
+
} else {
|
|
4417
|
+
setActiveVideoElement(nativeVideoRef.current);
|
|
4418
|
+
}
|
|
4231
4419
|
}, [optimizedReady, hlsFailed, currentVideoUrl, currentNativeVideoUrl]);
|
|
4232
4420
|
useEffect(() => {
|
|
4233
4421
|
if (!activeVideoElement) return;
|
|
@@ -4257,16 +4445,29 @@ function VideoPlayerState({
|
|
|
4257
4445
|
return () => observer.disconnect();
|
|
4258
4446
|
}, []);
|
|
4259
4447
|
useEffect(() => {
|
|
4260
|
-
var _a;
|
|
4448
|
+
var _a, _b;
|
|
4261
4449
|
if (!onPlayerInfo) return;
|
|
4262
4450
|
const shakaPlayer = ((_a = playerRef.current) == null ? void 0 : _a.player) || null;
|
|
4263
4451
|
const nativeVideo = nativeVideoRef.current || null;
|
|
4264
4452
|
const containerEl = containerRef.current || null;
|
|
4265
|
-
const mode =
|
|
4266
|
-
|
|
4267
|
-
|
|
4453
|
+
const mode = currentVideoUrl && !hlsFailed && shakaPlayer ? "shaka" : "native";
|
|
4454
|
+
let videoDuration = null;
|
|
4455
|
+
try {
|
|
4456
|
+
const range = (_b = shakaPlayer == null ? void 0 : shakaPlayer.seekRange) == null ? void 0 : _b.call(shakaPlayer);
|
|
4457
|
+
if (range && Number.isFinite(range.end) && range.end > 0) {
|
|
4458
|
+
videoDuration = range.end;
|
|
4459
|
+
}
|
|
4460
|
+
} catch {
|
|
4461
|
+
}
|
|
4462
|
+
if (videoDuration == null && activeVideoElement) {
|
|
4463
|
+
const d = activeVideoElement.duration;
|
|
4464
|
+
if (Number.isFinite(d) && d > 0) videoDuration = d;
|
|
4465
|
+
}
|
|
4466
|
+
onPlayerInfo({ shakaPlayer, nativeVideo, mode, containerEl, videoDuration });
|
|
4467
|
+
}, [onPlayerInfo, optimizedReady, hlsFailed, currentVideoUrl, currentNativeVideoUrl, activeVideoElement]);
|
|
4268
4468
|
useEffect(() => {
|
|
4269
4469
|
var _a;
|
|
4470
|
+
console.log("[Cover] posterUrl effect:", posterUrl);
|
|
4270
4471
|
const nativeVideo = nativeVideoRef.current;
|
|
4271
4472
|
if (nativeVideo) {
|
|
4272
4473
|
nativeVideo.poster = posterUrl || "";
|
|
@@ -4343,6 +4544,7 @@ function VideoPlayerState({
|
|
|
4343
4544
|
nativeVideo.play().catch(() => {
|
|
4344
4545
|
});
|
|
4345
4546
|
}
|
|
4547
|
+
savedStateLockRef.current = false;
|
|
4346
4548
|
}
|
|
4347
4549
|
};
|
|
4348
4550
|
if (nativeVideo.readyState >= 1) {
|
|
@@ -4353,10 +4555,12 @@ function VideoPlayerState({
|
|
|
4353
4555
|
});
|
|
4354
4556
|
}
|
|
4355
4557
|
const handleTimeUpdate = () => {
|
|
4558
|
+
if (savedStateLockRef.current) return;
|
|
4356
4559
|
if (timeUpdateThrottleRef.current) {
|
|
4357
4560
|
clearTimeout(timeUpdateThrottleRef.current);
|
|
4358
4561
|
}
|
|
4359
4562
|
timeUpdateThrottleRef.current = setTimeout(() => {
|
|
4563
|
+
if (savedStateLockRef.current) return;
|
|
4360
4564
|
if (!savedStateRef.current) {
|
|
4361
4565
|
savedStateRef.current = { time: 0, paused: true };
|
|
4362
4566
|
}
|
|
@@ -4364,6 +4568,7 @@ function VideoPlayerState({
|
|
|
4364
4568
|
}, 100);
|
|
4365
4569
|
};
|
|
4366
4570
|
const handlePlay = () => {
|
|
4571
|
+
if (savedStateLockRef.current) return;
|
|
4367
4572
|
if (!savedStateRef.current) {
|
|
4368
4573
|
savedStateRef.current = { time: 0, paused: false };
|
|
4369
4574
|
} else {
|
|
@@ -4371,6 +4576,7 @@ function VideoPlayerState({
|
|
|
4371
4576
|
}
|
|
4372
4577
|
};
|
|
4373
4578
|
const handlePause = () => {
|
|
4579
|
+
if (savedStateLockRef.current) return;
|
|
4374
4580
|
if (!savedStateRef.current) {
|
|
4375
4581
|
savedStateRef.current = { time: nativeVideo.currentTime, paused: true };
|
|
4376
4582
|
} else {
|
|
@@ -4388,6 +4594,12 @@ function VideoPlayerState({
|
|
|
4388
4594
|
if (timeUpdateThrottleRef.current) {
|
|
4389
4595
|
clearTimeout(timeUpdateThrottleRef.current);
|
|
4390
4596
|
}
|
|
4597
|
+
if (nativeVideo.currentTime > 0) {
|
|
4598
|
+
savedStateRef.current = {
|
|
4599
|
+
time: nativeVideo.currentTime,
|
|
4600
|
+
paused: nativeVideo.paused
|
|
4601
|
+
};
|
|
4602
|
+
}
|
|
4391
4603
|
};
|
|
4392
4604
|
}, [currentNativeVideoUrl]);
|
|
4393
4605
|
useEffect(() => {
|
|
@@ -4547,7 +4759,8 @@ function VideoPlayerState({
|
|
|
4547
4759
|
}
|
|
4548
4760
|
);
|
|
4549
4761
|
}
|
|
4550
|
-
const fallbackVideoUrl =
|
|
4762
|
+
const fallbackVideoUrl = normalizeUrl(rawUrl ?? null, baseUrl) || currentNativeVideoUrl;
|
|
4763
|
+
const shakaActive = !!(currentVideoUrl && !hlsFailed);
|
|
4551
4764
|
const isCompact = playerWidth > 0 && playerWidth < 500;
|
|
4552
4765
|
const isNarrow = playerWidth > 0 && playerWidth < 380;
|
|
4553
4766
|
const hasChapters = ((chapters == null ? void 0 : chapters.length) ?? 0) > 0;
|
|
@@ -4576,25 +4789,41 @@ function VideoPlayerState({
|
|
|
4576
4789
|
ref: containerRef,
|
|
4577
4790
|
onClick: handleContainerClick,
|
|
4578
4791
|
children: [
|
|
4579
|
-
|
|
4792
|
+
/* @__PURE__ */ jsx(
|
|
4580
4793
|
"video",
|
|
4581
4794
|
{
|
|
4582
4795
|
ref: nativeVideoRef,
|
|
4583
|
-
src: (hlsFailed ? fallbackVideoUrl :
|
|
4796
|
+
src: currentNativeVideoUrl || (hlsFailed ? fallbackVideoUrl : null) || void 0,
|
|
4584
4797
|
className: "video-player-media",
|
|
4585
|
-
|
|
4798
|
+
style: shakaActive ? { display: "none" } : void 0,
|
|
4799
|
+
poster: posterUrl || void 0,
|
|
4800
|
+
onError: (e) => {
|
|
4801
|
+
const video = e.currentTarget;
|
|
4802
|
+
const failedSrc = video.currentSrc || video.src;
|
|
4803
|
+
if (!failedSrc) return;
|
|
4804
|
+
const mediaError = video.error;
|
|
4805
|
+
if (!mediaError) return;
|
|
4806
|
+
triedFallbackUrlsRef.current.add(failedSrc);
|
|
4807
|
+
const candidates = [
|
|
4808
|
+
normalizeUrl(rawUrl ?? null, baseUrl),
|
|
4809
|
+
normalizeUrl(nativeVideoUrl, baseUrl)
|
|
4810
|
+
].filter((url) => !!url && !triedFallbackUrlsRef.current.has(url));
|
|
4811
|
+
if (candidates.length > 0) {
|
|
4812
|
+
video.src = candidates[0];
|
|
4813
|
+
video.load();
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4586
4816
|
}
|
|
4587
4817
|
),
|
|
4588
|
-
|
|
4818
|
+
shakaActive && /* @__PURE__ */ jsx(
|
|
4589
4819
|
ShakaPlayer,
|
|
4590
4820
|
{
|
|
4591
4821
|
ref: playerRef,
|
|
4592
|
-
src: currentVideoUrl,
|
|
4593
4822
|
autoPlay: false,
|
|
4594
4823
|
className: "video-player-media"
|
|
4595
4824
|
}
|
|
4596
4825
|
),
|
|
4597
|
-
(currentNativeVideoUrl || currentVideoUrl || hlsFailed && fallbackVideoUrl) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4826
|
+
(currentNativeVideoUrl || currentVideoUrl || primaryUrl || hlsFailed && fallbackVideoUrl) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4598
4827
|
isPaused && !isCompact && /* @__PURE__ */ jsx(
|
|
4599
4828
|
"button",
|
|
4600
4829
|
{
|
|
@@ -4780,6 +5009,7 @@ function VideoUploadComponent({
|
|
|
4780
5009
|
const [videoId, setVideoId] = useState(initialVideoId ?? null);
|
|
4781
5010
|
const [linkUrl, setLinkUrl] = useState("");
|
|
4782
5011
|
const [linkError, setLinkError] = useState(null);
|
|
5012
|
+
const [isHlsFallback, setIsHlsFallback] = useState(false);
|
|
4783
5013
|
const lastUploadFileRef = useRef(null);
|
|
4784
5014
|
const uploadCallbackRef = useRef(false);
|
|
4785
5015
|
const uploadIdRef = useRef(null);
|
|
@@ -4869,7 +5099,16 @@ function VideoUploadComponent({
|
|
|
4869
5099
|
const resolvedRawPlayable = sourceType === "upload" ? rawPlayable : false;
|
|
4870
5100
|
const resolvedStatusAvailable = sourceType === "upload" ? isStatusAvailable : false;
|
|
4871
5101
|
const chapters = chaptersOverride ?? (resolvedStatus == null ? void 0 : resolvedStatus.chapters) ?? void 0;
|
|
4872
|
-
const
|
|
5102
|
+
const rawSubtitles = subtitlesOverride ?? (resolvedStatus == null ? void 0 : resolvedStatus.subtitles) ?? void 0;
|
|
5103
|
+
const subtitlesRef = useRef(rawSubtitles);
|
|
5104
|
+
const subtitles = useMemo(() => {
|
|
5105
|
+
if (rawSubtitles === subtitlesRef.current) return subtitlesRef.current;
|
|
5106
|
+
if (JSON.stringify(rawSubtitles) === JSON.stringify(subtitlesRef.current)) {
|
|
5107
|
+
return subtitlesRef.current;
|
|
5108
|
+
}
|
|
5109
|
+
subtitlesRef.current = rawSubtitles;
|
|
5110
|
+
return rawSubtitles;
|
|
5111
|
+
}, [rawSubtitles]);
|
|
4873
5112
|
const inferredNativeVideoUrl = useMemo(() => {
|
|
4874
5113
|
if (fallbackNativeVideoUrl) return fallbackNativeVideoUrl;
|
|
4875
5114
|
if (platform === "link") {
|
|
@@ -4960,7 +5199,7 @@ function VideoUploadComponent({
|
|
|
4960
5199
|
const isReadyForPreview = currentState === "video-preview" && (sourceType === "link" ? true : resolvedStatusAvailable ? resolvedOptimizedReady : resolvedOptimizedReady || resolvedRawPlayable || hasPreviewSource);
|
|
4961
5200
|
const isProcessingStatus = (resolvedStatus == null ? void 0 : resolvedStatus.state) === "PROCESSING" || (resolvedStatus == null ? void 0 : resolvedStatus.state) === "UPLOADING";
|
|
4962
5201
|
const hasProcessingPercent = typeof ((_a = resolvedStatus == null ? void 0 : resolvedStatus.processing) == null ? void 0 : _a.percent) === "number";
|
|
4963
|
-
const showProcessingBanner = currentState === "video-preview" && sourceType === "upload" && !!resolvedNativeVideoUrl && resolvedStatusAvailable && !resolvedOptimizedReady && (isProcessingStatus || hasProcessingPercent);
|
|
5202
|
+
const showProcessingBanner = currentState === "video-preview" && sourceType === "upload" && !!resolvedNativeVideoUrl && resolvedStatusAvailable && !resolvedOptimizedReady && !isHlsFallback && (isProcessingStatus || hasProcessingPercent);
|
|
4964
5203
|
const maxBannerPercentRef = useRef(0);
|
|
4965
5204
|
const rawPercent = Math.max(0, Math.min(100, ((_b = resolvedStatus == null ? void 0 : resolvedStatus.processing) == null ? void 0 : _b.percent) ?? 0));
|
|
4966
5205
|
if (rawPercent > maxBannerPercentRef.current) {
|
|
@@ -5015,12 +5254,17 @@ function VideoUploadComponent({
|
|
|
5015
5254
|
setLinkError(null);
|
|
5016
5255
|
setCurrentState("video-preview");
|
|
5017
5256
|
};
|
|
5257
|
+
const videoIdRef = useRef(videoId);
|
|
5258
|
+
videoIdRef.current = videoId;
|
|
5259
|
+
const uploadVideoIdRef = useRef(uploadVideoId);
|
|
5260
|
+
uploadVideoIdRef.current = uploadVideoId;
|
|
5018
5261
|
useEffect(() => {
|
|
5019
5262
|
return editor.registerCommand(
|
|
5020
5263
|
UPLOAD_VIDEO_COMMAND,
|
|
5021
5264
|
(payload) => {
|
|
5022
|
-
if (sourceType === "upload" && payload.length > 0) {
|
|
5265
|
+
if (sourceType === "upload" && payload.length > 0 && !videoIdRef.current && !uploadVideoIdRef.current) {
|
|
5023
5266
|
handleFileSelect(payload[0]);
|
|
5267
|
+
return true;
|
|
5024
5268
|
}
|
|
5025
5269
|
return false;
|
|
5026
5270
|
},
|
|
@@ -5075,7 +5319,7 @@ function VideoUploadComponent({
|
|
|
5075
5319
|
onStateChange == null ? void 0 : onStateChange(currentState);
|
|
5076
5320
|
}, [currentState, onStateChange]);
|
|
5077
5321
|
const renderState = () => {
|
|
5078
|
-
var _a2;
|
|
5322
|
+
var _a2, _b2;
|
|
5079
5323
|
if (sourceType === "link" && currentState !== "video-preview") {
|
|
5080
5324
|
return /* @__PURE__ */ jsx(
|
|
5081
5325
|
LinkState,
|
|
@@ -5168,6 +5412,8 @@ function VideoUploadComponent({
|
|
|
5168
5412
|
}
|
|
5169
5413
|
) });
|
|
5170
5414
|
}
|
|
5415
|
+
const effectivePoster = posterUrl ?? ((_a2 = resolvedStatus == null ? void 0 : resolvedStatus.cover) == null ? void 0 : _a2.url) ?? null;
|
|
5416
|
+
console.log("[Cover] VideoUploadComponent render: posterUrl prop=", posterUrl, "cover.url=", (_b2 = resolvedStatus == null ? void 0 : resolvedStatus.cover) == null ? void 0 : _b2.url, "effective=", effectivePoster);
|
|
5171
5417
|
return /* @__PURE__ */ jsx(
|
|
5172
5418
|
VideoPlayerState,
|
|
5173
5419
|
{
|
|
@@ -5182,8 +5428,9 @@ function VideoUploadComponent({
|
|
|
5182
5428
|
chapters: showProcessingBanner ? void 0 : chapters,
|
|
5183
5429
|
isProcessing: showProcessingBanner,
|
|
5184
5430
|
subtitles,
|
|
5185
|
-
posterUrl:
|
|
5186
|
-
onPlayerInfo
|
|
5431
|
+
posterUrl: effectivePoster,
|
|
5432
|
+
onPlayerInfo,
|
|
5433
|
+
onFallback: setIsHlsFallback
|
|
5187
5434
|
}
|
|
5188
5435
|
);
|
|
5189
5436
|
default:
|
|
@@ -5323,21 +5570,6 @@ async function handleResponse(response) {
|
|
|
5323
5570
|
}
|
|
5324
5571
|
return parseJson(response);
|
|
5325
5572
|
}
|
|
5326
|
-
async function listChaptersEffective(videoId, options = {}) {
|
|
5327
|
-
const url = buildApiUrl(
|
|
5328
|
-
`/v1/videos/${videoId}/chapters/effective`,
|
|
5329
|
-
options.baseUrl
|
|
5330
|
-
);
|
|
5331
|
-
const response = await fetch(url, {
|
|
5332
|
-
method: "GET",
|
|
5333
|
-
credentials: "include",
|
|
5334
|
-
headers: {
|
|
5335
|
-
...getAuthHeaders(options.authToken)
|
|
5336
|
-
}
|
|
5337
|
-
});
|
|
5338
|
-
const data = await handleResponse(response);
|
|
5339
|
-
return Array.isArray(data) ? data : [];
|
|
5340
|
-
}
|
|
5341
5573
|
async function saveChapters(videoId, chapters, options = {}) {
|
|
5342
5574
|
const url = buildApiUrl(`/v1/videos/${videoId}/chapters`, options.baseUrl);
|
|
5343
5575
|
const response = await fetch(url, {
|
|
@@ -5465,9 +5697,7 @@ function ChapterRow({
|
|
|
5465
5697
|
/* @__PURE__ */ jsxs(
|
|
5466
5698
|
Stack,
|
|
5467
5699
|
{
|
|
5468
|
-
gap: 10,
|
|
5469
5700
|
className: "video-settings-modal-chapter-times",
|
|
5470
|
-
style: { width: 72, flex: "0 0 72px" },
|
|
5471
5701
|
children: [
|
|
5472
5702
|
/* @__PURE__ */ jsx(
|
|
5473
5703
|
TextInput,
|
|
@@ -5527,9 +5757,7 @@ function ChapterRow({
|
|
|
5527
5757
|
/* @__PURE__ */ jsx(
|
|
5528
5758
|
Flex,
|
|
5529
5759
|
{
|
|
5530
|
-
align: "center",
|
|
5531
5760
|
className: "video-settings-modal-chapter-title",
|
|
5532
|
-
style: { flex: 1, minWidth: 0, height: 82 },
|
|
5533
5761
|
children: /* @__PURE__ */ jsx(
|
|
5534
5762
|
Textarea,
|
|
5535
5763
|
{
|
|
@@ -5566,10 +5794,7 @@ function ChapterRow({
|
|
|
5566
5794
|
/* @__PURE__ */ jsx(
|
|
5567
5795
|
Flex,
|
|
5568
5796
|
{
|
|
5569
|
-
align: "center",
|
|
5570
|
-
justify: "center",
|
|
5571
5797
|
className: "video-settings-modal-chapter-actions",
|
|
5572
|
-
style: { width: 36, flex: "0 0 36px", height: 82 },
|
|
5573
5798
|
children: /* @__PURE__ */ jsxs(Menu, { position: "bottom-end", children: [
|
|
5574
5799
|
/* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(
|
|
5575
5800
|
ActionIcon,
|
|
@@ -5595,6 +5820,8 @@ function ChaptersSection({
|
|
|
5595
5820
|
videoId,
|
|
5596
5821
|
initialChapters,
|
|
5597
5822
|
nativeVideo,
|
|
5823
|
+
shakaPlayer,
|
|
5824
|
+
videoDuration: videoDurationProp,
|
|
5598
5825
|
baseUrl,
|
|
5599
5826
|
authToken,
|
|
5600
5827
|
title: title2,
|
|
@@ -5639,50 +5866,32 @@ function ChaptersSection({
|
|
|
5639
5866
|
setEditableChapters(toEditable(list));
|
|
5640
5867
|
}, []);
|
|
5641
5868
|
useEffect(() => {
|
|
5642
|
-
|
|
5643
|
-
|
|
5869
|
+
var _a;
|
|
5870
|
+
if (videoDurationProp != null && videoDurationProp > 0) {
|
|
5871
|
+
setDurationSec(videoDurationProp);
|
|
5644
5872
|
return;
|
|
5645
5873
|
}
|
|
5646
|
-
const updateDuration = () => {
|
|
5647
|
-
const d = nativeVideo.duration;
|
|
5648
|
-
setDurationSec(Number.isFinite(d) && d > 0 ? d : null);
|
|
5649
|
-
};
|
|
5650
|
-
updateDuration();
|
|
5651
|
-
nativeVideo.addEventListener("loadedmetadata", updateDuration);
|
|
5652
|
-
nativeVideo.addEventListener("durationchange", updateDuration);
|
|
5653
|
-
return () => {
|
|
5654
|
-
nativeVideo.removeEventListener("loadedmetadata", updateDuration);
|
|
5655
|
-
nativeVideo.removeEventListener("durationchange", updateDuration);
|
|
5656
|
-
};
|
|
5657
|
-
}, [nativeVideo]);
|
|
5658
|
-
const loadEffective = useCallback(async () => {
|
|
5659
|
-
if (!videoId) return;
|
|
5660
|
-
setIsLoading(true);
|
|
5661
|
-
setError(null);
|
|
5662
|
-
setValidationError(null);
|
|
5663
5874
|
try {
|
|
5664
|
-
const
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5875
|
+
const range = (_a = shakaPlayer == null ? void 0 : shakaPlayer.seekRange) == null ? void 0 : _a.call(shakaPlayer);
|
|
5876
|
+
if (range && Number.isFinite(range.end) && range.end > 0) {
|
|
5877
|
+
setDurationSec(range.end);
|
|
5878
|
+
return;
|
|
5879
|
+
}
|
|
5880
|
+
} catch {
|
|
5670
5881
|
}
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
return durationSec;
|
|
5680
|
-
}, [durationSec, editableChapters]);
|
|
5882
|
+
if (nativeVideo) {
|
|
5883
|
+
const d = nativeVideo.duration;
|
|
5884
|
+
if (Number.isFinite(d) && d > 0) {
|
|
5885
|
+
setDurationSec(d);
|
|
5886
|
+
return;
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
}, [videoDurationProp, shakaPlayer, nativeVideo]);
|
|
5681
5890
|
const getMaxAllowedSec = useCallback(() => {
|
|
5682
|
-
if (
|
|
5683
|
-
return Math.floor(
|
|
5684
|
-
}, [
|
|
5685
|
-
|
|
5891
|
+
if (durationSec == null) return null;
|
|
5892
|
+
return Math.floor(durationSec);
|
|
5893
|
+
}, [durationSec]);
|
|
5894
|
+
useCallback(() => {
|
|
5686
5895
|
const list = [...editableChapters].filter((ch) => ch.title.trim()).sort((a, b) => a.startSec - b.startSec);
|
|
5687
5896
|
const maxAllowed = getMaxAllowedSec();
|
|
5688
5897
|
for (let i = 0; i < list.length; i++) {
|
|
@@ -5700,21 +5909,19 @@ function ChaptersSection({
|
|
|
5700
5909
|
const handleSave = useCallback(async () => {
|
|
5701
5910
|
if (!videoId) return null;
|
|
5702
5911
|
setValidationError(null);
|
|
5703
|
-
if (!validateChapters()) {
|
|
5704
|
-
setValidationError(invalidRangeLabel);
|
|
5705
|
-
return null;
|
|
5706
|
-
}
|
|
5707
5912
|
setIsSaving(true);
|
|
5708
5913
|
isSavingRef.current = true;
|
|
5709
5914
|
setError(null);
|
|
5710
5915
|
try {
|
|
5711
|
-
const payload = editableChapters.filter((ch) => ch.title.trim()).sort((a, b) => a.startSec - b.startSec).map(({ startSec, title: title22 }) => ({ startSec, title: title22 }));
|
|
5916
|
+
const payload = editableChapters.filter((ch) => ch.title.trim()).filter((ch) => durationSec == null || ch.startSec < durationSec).sort((a, b) => a.startSec - b.startSec).map(({ startSec, title: title22 }) => ({ startSec, title: title22 }));
|
|
5712
5917
|
await saveChapters(videoId, payload, apiOptions);
|
|
5713
5918
|
hasLocalEditsRef.current = false;
|
|
5714
|
-
await loadEffective();
|
|
5715
5919
|
return payload;
|
|
5716
5920
|
} catch (err) {
|
|
5717
|
-
|
|
5921
|
+
const msg = err instanceof Error ? err.message : errorLabel;
|
|
5922
|
+
if (!/exceeds/i.test(msg)) {
|
|
5923
|
+
setError(msg);
|
|
5924
|
+
}
|
|
5718
5925
|
return null;
|
|
5719
5926
|
} finally {
|
|
5720
5927
|
setIsSaving(false);
|
|
@@ -5724,10 +5931,8 @@ function ChaptersSection({
|
|
|
5724
5931
|
videoId,
|
|
5725
5932
|
editableChapters,
|
|
5726
5933
|
apiOptions,
|
|
5727
|
-
loadEffective,
|
|
5728
5934
|
errorLabel,
|
|
5729
|
-
|
|
5730
|
-
invalidRangeLabel
|
|
5935
|
+
getMaxAllowedSec
|
|
5731
5936
|
]);
|
|
5732
5937
|
useEffect(() => {
|
|
5733
5938
|
onSave == null ? void 0 : onSave(handleSave);
|
|
@@ -5736,7 +5941,7 @@ function ChaptersSection({
|
|
|
5736
5941
|
hasLocalEditsRef.current = true;
|
|
5737
5942
|
setValidationError(null);
|
|
5738
5943
|
setEditableChapters((prev) => {
|
|
5739
|
-
const maxAllowed =
|
|
5944
|
+
const maxAllowed = durationSec != null ? Math.floor(durationSec) : null;
|
|
5740
5945
|
const sorted = [...prev].sort((a, b) => a.startSec - b.startSec);
|
|
5741
5946
|
const lastStart = sorted.length > 0 ? Math.floor(sorted[sorted.length - 1].startSec) : -1;
|
|
5742
5947
|
const nextStart = Math.max(0, lastStart + 1);
|
|
@@ -5807,11 +6012,8 @@ function ChaptersSection({
|
|
|
5807
6012
|
}
|
|
5808
6013
|
if (Array.isArray(initialChapters)) {
|
|
5809
6014
|
applyChapters(initialChapters);
|
|
5810
|
-
return;
|
|
5811
6015
|
}
|
|
5812
|
-
|
|
5813
|
-
});
|
|
5814
|
-
}, [videoId, initialChapters, applyChapters, loadEffective]);
|
|
6016
|
+
}, [videoId, initialChapters, applyChapters]);
|
|
5815
6017
|
return /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
|
|
5816
6018
|
/* @__PURE__ */ jsx(
|
|
5817
6019
|
Text,
|
|
@@ -5851,7 +6053,7 @@ function ChaptersSection({
|
|
|
5851
6053
|
/* @__PURE__ */ jsx(Stack, { gap: 12, children: [...editableChapters].sort((a, b) => a.startSec - b.startSec).map((chapter, index, list) => {
|
|
5852
6054
|
const next = list[index + 1] || null;
|
|
5853
6055
|
const nextNext = list[index + 2] || null;
|
|
5854
|
-
const maxAllowed =
|
|
6056
|
+
const maxAllowed = durationSec != null ? Math.floor(durationSec) : null;
|
|
5855
6057
|
const endSec = next != null ? Math.floor(next.startSec) : maxAllowed != null ? maxAllowed : null;
|
|
5856
6058
|
const startSec = Math.floor(chapter.startSec);
|
|
5857
6059
|
const prevStart = index > 0 ? Math.floor(list[index - 1].startSec) : null;
|
|
@@ -5879,7 +6081,7 @@ function ChaptersSection({
|
|
|
5879
6081
|
endMax,
|
|
5880
6082
|
disabled: false,
|
|
5881
6083
|
startTimePlaceholder: timePlaceholder,
|
|
5882
|
-
endTimePlaceholder:
|
|
6084
|
+
endTimePlaceholder: durationSec != null ? secondsToTimestamp(Math.floor(durationSec)) : endTimePlaceholder,
|
|
5883
6085
|
titlePlaceholder,
|
|
5884
6086
|
deleteChapterLabel,
|
|
5885
6087
|
onStartSecChange: handleStartSecChange,
|
|
@@ -5952,7 +6154,7 @@ function CoverSection({
|
|
|
5952
6154
|
[(_a = status == null ? void 0 : status.cover) == null ? void 0 : _a.previewId, previews]
|
|
5953
6155
|
);
|
|
5954
6156
|
const effectiveSelected = selectedPreviewId ?? coverId;
|
|
5955
|
-
|
|
6157
|
+
previews.length > 0;
|
|
5956
6158
|
const slidesCount = previews.length + 1;
|
|
5957
6159
|
const showControls = slidesCount > 3;
|
|
5958
6160
|
const setPreviewLoaded = useCallback((id) => {
|
|
@@ -6000,7 +6202,7 @@ function CoverSection({
|
|
|
6000
6202
|
}, [videoId]);
|
|
6001
6203
|
const handleCoverDrop = useCallback(
|
|
6002
6204
|
async (files) => {
|
|
6003
|
-
var _a2, _b2, _c;
|
|
6205
|
+
var _a2, _b2, _c, _d;
|
|
6004
6206
|
const file = files[0];
|
|
6005
6207
|
if (!file || !onCoverUpload) return;
|
|
6006
6208
|
const objectUrl = URL.createObjectURL(file);
|
|
@@ -6010,7 +6212,6 @@ function CoverSection({
|
|
|
6010
6212
|
onSelectedPosterChange == null ? void 0 : onSelectedPosterChange(objectUrl);
|
|
6011
6213
|
try {
|
|
6012
6214
|
const nextStatus = await Promise.resolve(onCoverUpload(file));
|
|
6013
|
-
console.log("cover upload response", nextStatus);
|
|
6014
6215
|
setOptimisticPreviewUrl(null);
|
|
6015
6216
|
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
6016
6217
|
const nextPreviews = nextStatus == null ? void 0 : nextStatus.previews;
|
|
@@ -6018,10 +6219,16 @@ function CoverSection({
|
|
|
6018
6219
|
if (nextStatus && hasPreviews && nextPreviews) {
|
|
6019
6220
|
scrollToEndAfterUpdateRef.current = true;
|
|
6020
6221
|
setStatus(nextStatus);
|
|
6021
|
-
const
|
|
6022
|
-
setSelectedPreviewId(
|
|
6023
|
-
|
|
6024
|
-
|
|
6222
|
+
const uploadedPreviewId = ((_a2 = nextPreviews[nextPreviews.length - 1]) == null ? void 0 : _a2.id) ?? ((_b2 = nextStatus == null ? void 0 : nextStatus.cover) == null ? void 0 : _b2.previewId) ?? ((_c = nextPreviews[0]) == null ? void 0 : _c.id);
|
|
6223
|
+
setSelectedPreviewId(uploadedPreviewId ?? "uploaded");
|
|
6224
|
+
if (uploadedPreviewId) {
|
|
6225
|
+
const uploadedUrl = (_d = nextPreviews.find(
|
|
6226
|
+
(p) => p.id === uploadedPreviewId
|
|
6227
|
+
)) == null ? void 0 : _d.url;
|
|
6228
|
+
if (uploadedUrl) {
|
|
6229
|
+
onSelectedPosterChange == null ? void 0 : onSelectedPosterChange(uploadedUrl);
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6025
6232
|
setLoadingIds(/* @__PURE__ */ new Set());
|
|
6026
6233
|
setErrorIds(/* @__PURE__ */ new Set());
|
|
6027
6234
|
} else {
|
|
@@ -6038,7 +6245,7 @@ function CoverSection({
|
|
|
6038
6245
|
loadStatus();
|
|
6039
6246
|
}
|
|
6040
6247
|
},
|
|
6041
|
-
[onCoverUpload, loadStatus]
|
|
6248
|
+
[onCoverUpload, loadStatus, onSelectedPosterChange]
|
|
6042
6249
|
);
|
|
6043
6250
|
const selectedUrl = useMemo(() => {
|
|
6044
6251
|
var _a2, _b2;
|
|
@@ -6159,6 +6366,7 @@ function CoverSection({
|
|
|
6159
6366
|
className: `video-settings-modal-cover-cell video-settings-modal-cover-thumb ${isSelected && !isError ? "selected" : ""} ${isLoading2 ? "loading" : ""} ${isError ? "error" : ""}`,
|
|
6160
6367
|
onClick: () => {
|
|
6161
6368
|
if (isError) return;
|
|
6369
|
+
console.log("[Cover] click preview:", preview.id, preview.url);
|
|
6162
6370
|
setSelectedPreviewId(preview.id);
|
|
6163
6371
|
onSelectedPosterChange == null ? void 0 : onSelectedPosterChange(preview.url);
|
|
6164
6372
|
},
|
|
@@ -6208,15 +6416,15 @@ function CoverSection({
|
|
|
6208
6416
|
})
|
|
6209
6417
|
]
|
|
6210
6418
|
}
|
|
6211
|
-
)
|
|
6212
|
-
!hasCoverCandidates && !isLoading && !loadError && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: emptyCoverLabel })
|
|
6419
|
+
)
|
|
6213
6420
|
] });
|
|
6214
6421
|
}
|
|
6215
6422
|
function FooterActions({
|
|
6216
6423
|
cancelLabel,
|
|
6217
6424
|
saveLabel,
|
|
6218
6425
|
onCancel,
|
|
6219
|
-
onSave
|
|
6426
|
+
onSave,
|
|
6427
|
+
isSaving
|
|
6220
6428
|
}) {
|
|
6221
6429
|
return /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: 8, children: [
|
|
6222
6430
|
/* @__PURE__ */ jsx(
|
|
@@ -6227,6 +6435,7 @@ function FooterActions({
|
|
|
6227
6435
|
color: "gray",
|
|
6228
6436
|
radius: "md",
|
|
6229
6437
|
onClick: onCancel,
|
|
6438
|
+
disabled: isSaving,
|
|
6230
6439
|
children: cancelLabel
|
|
6231
6440
|
}
|
|
6232
6441
|
),
|
|
@@ -6234,9 +6443,9 @@ function FooterActions({
|
|
|
6234
6443
|
Button,
|
|
6235
6444
|
{
|
|
6236
6445
|
size: "sm",
|
|
6237
|
-
color: "blue",
|
|
6238
6446
|
radius: "md",
|
|
6239
6447
|
onClick: onSave,
|
|
6448
|
+
loading: isSaving,
|
|
6240
6449
|
children: saveLabel
|
|
6241
6450
|
}
|
|
6242
6451
|
)
|
|
@@ -6276,23 +6485,29 @@ const LANGUAGE_LABELS = LANGUAGE_OPTIONS.reduce(
|
|
|
6276
6485
|
);
|
|
6277
6486
|
function ManualSubtitlesPanel({
|
|
6278
6487
|
videoId,
|
|
6279
|
-
mode,
|
|
6280
|
-
shakaPlayer,
|
|
6281
|
-
nativeVideo,
|
|
6282
6488
|
baseUrl,
|
|
6283
6489
|
authToken,
|
|
6284
6490
|
statusSubtitles,
|
|
6285
|
-
onTracksChange
|
|
6491
|
+
onTracksChange,
|
|
6492
|
+
onSave
|
|
6286
6493
|
}) {
|
|
6287
6494
|
const { t } = useTranslation();
|
|
6495
|
+
const { reportError } = useVideoPluginContext();
|
|
6288
6496
|
const dependencies = useVideoPluginDependencies();
|
|
6289
6497
|
const SelectComponent = dependencies.Select || Select;
|
|
6290
6498
|
const [tracks, setTracks] = useState([]);
|
|
6291
6499
|
const [drafts, setDrafts] = useState([]);
|
|
6292
|
-
const [loadError, setLoadError] = useState(null);
|
|
6293
6500
|
const uploadControllersRef = useRef(/* @__PURE__ */ new Map());
|
|
6294
|
-
const
|
|
6295
|
-
const
|
|
6501
|
+
const [autoSubtitlesDisabled, setAutoSubtitlesDisabled] = useState(false);
|
|
6502
|
+
const autoTrack = useMemo(
|
|
6503
|
+
() => tracks.find(isAutoGeneratedTrack) ?? null,
|
|
6504
|
+
[tracks]
|
|
6505
|
+
);
|
|
6506
|
+
useEffect(() => {
|
|
6507
|
+
if (autoTrack) {
|
|
6508
|
+
setAutoSubtitlesDisabled(autoTrack.isDisabled);
|
|
6509
|
+
}
|
|
6510
|
+
}, [autoTrack]);
|
|
6296
6511
|
const apiOptions = useMemo(
|
|
6297
6512
|
() => ({
|
|
6298
6513
|
baseUrl,
|
|
@@ -6300,26 +6515,54 @@ function ManualSubtitlesPanel({
|
|
|
6300
6515
|
}),
|
|
6301
6516
|
[baseUrl, authToken]
|
|
6302
6517
|
);
|
|
6518
|
+
const handleAutoSubtitlesToggle = useCallback(
|
|
6519
|
+
(disabled2) => {
|
|
6520
|
+
if (!autoTrack) return;
|
|
6521
|
+
setAutoSubtitlesDisabled(disabled2);
|
|
6522
|
+
setTracks(
|
|
6523
|
+
(prev) => prev.map(
|
|
6524
|
+
(t2) => t2.id === autoTrack.id ? { ...t2, isDisabled: disabled2 } : t2
|
|
6525
|
+
)
|
|
6526
|
+
);
|
|
6527
|
+
},
|
|
6528
|
+
[autoTrack]
|
|
6529
|
+
);
|
|
6530
|
+
const initialAutoDisabledRef = useRef(null);
|
|
6531
|
+
useEffect(() => {
|
|
6532
|
+
if (autoTrack && initialAutoDisabledRef.current === null) {
|
|
6533
|
+
initialAutoDisabledRef.current = autoTrack.isDisabled;
|
|
6534
|
+
}
|
|
6535
|
+
}, [autoTrack]);
|
|
6303
6536
|
const refreshTracks = useCallback(
|
|
6304
6537
|
async (forceApi = false) => {
|
|
6305
6538
|
if (!videoId) return;
|
|
6306
|
-
if (
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6539
|
+
if (forceApi || statusSubtitles == null) {
|
|
6540
|
+
try {
|
|
6541
|
+
const freshTracks = await listTracks(videoId, apiOptions);
|
|
6542
|
+
setTracks(freshTracks);
|
|
6543
|
+
return;
|
|
6544
|
+
} catch {
|
|
6545
|
+
}
|
|
6311
6546
|
}
|
|
6312
6547
|
try {
|
|
6313
|
-
const
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6548
|
+
const freshTracks = await listTracks(videoId, apiOptions);
|
|
6549
|
+
setTracks(freshTracks);
|
|
6550
|
+
} catch {
|
|
6551
|
+
if (statusSubtitles != null) {
|
|
6552
|
+
const nextTracks = (statusSubtitles || []).map(normalizeSubtitleTrack).filter((track) => track.id);
|
|
6553
|
+
setTracks(nextTracks);
|
|
6554
|
+
} else {
|
|
6555
|
+
try {
|
|
6556
|
+
const status = await fetchStatus(videoId);
|
|
6557
|
+
const nextTracks = ((status == null ? void 0 : status.subtitles) || []).map(normalizeSubtitleTrack).filter((track) => track.id);
|
|
6558
|
+
setTracks(nextTracks);
|
|
6559
|
+
} catch (error) {
|
|
6560
|
+
reportError(error, "subtitles", videoId);
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6320
6563
|
}
|
|
6321
6564
|
},
|
|
6322
|
-
[videoId,
|
|
6565
|
+
[videoId, apiOptions, reportError, statusSubtitles]
|
|
6323
6566
|
);
|
|
6324
6567
|
useEffect(() => {
|
|
6325
6568
|
if (!videoId) {
|
|
@@ -6333,18 +6576,44 @@ function ManualSubtitlesPanel({
|
|
|
6333
6576
|
useEffect(() => {
|
|
6334
6577
|
onTracksChange == null ? void 0 : onTracksChange(tracks);
|
|
6335
6578
|
}, [tracks, onTracksChange]);
|
|
6579
|
+
useEffect(() => {
|
|
6580
|
+
onSave == null ? void 0 : onSave(async () => {
|
|
6581
|
+
if (!videoId || !autoTrack) return;
|
|
6582
|
+
if (autoSubtitlesDisabled !== initialAutoDisabledRef.current) {
|
|
6583
|
+
await patchTrackDisabled(videoId, autoTrack.id, autoSubtitlesDisabled, apiOptions);
|
|
6584
|
+
initialAutoDisabledRef.current = autoSubtitlesDisabled;
|
|
6585
|
+
}
|
|
6586
|
+
});
|
|
6587
|
+
}, [onSave, videoId, autoTrack, autoSubtitlesDisabled, apiOptions]);
|
|
6588
|
+
const manualTracks = useMemo(() => {
|
|
6589
|
+
const manual = tracks.filter((track) => !isAutoGeneratedTrack(track));
|
|
6590
|
+
const byLang = /* @__PURE__ */ new Map();
|
|
6591
|
+
for (const track of manual) {
|
|
6592
|
+
byLang.set(track.lang, track);
|
|
6593
|
+
}
|
|
6594
|
+
return Array.from(byLang.values());
|
|
6595
|
+
}, [tracks]);
|
|
6596
|
+
const usedLanguages = useMemo(() => {
|
|
6597
|
+
const langs = new Set(manualTracks.map((t2) => t2.lang));
|
|
6598
|
+
drafts.forEach((d) => {
|
|
6599
|
+
if (d.lang) langs.add(d.lang);
|
|
6600
|
+
});
|
|
6601
|
+
return langs;
|
|
6602
|
+
}, [manualTracks, drafts]);
|
|
6336
6603
|
const handleAddDraft = useCallback(() => {
|
|
6337
|
-
|
|
6604
|
+
var _a;
|
|
6605
|
+
const availableLang = ((_a = LANGUAGE_OPTIONS.find((opt) => !usedLanguages.has(opt.value))) == null ? void 0 : _a.value) ?? (usedLanguages.has(DEFAULT_LANG) ? null : DEFAULT_LANG);
|
|
6606
|
+
const label = availableLang ? LANGUAGE_LABELS[availableLang] || availableLang : null;
|
|
6338
6607
|
setDrafts((prev) => [
|
|
6339
6608
|
...prev,
|
|
6340
6609
|
{
|
|
6341
6610
|
id: crypto.randomUUID(),
|
|
6342
|
-
lang:
|
|
6343
|
-
label
|
|
6611
|
+
lang: availableLang,
|
|
6612
|
+
label,
|
|
6344
6613
|
status: "draft"
|
|
6345
6614
|
}
|
|
6346
6615
|
]);
|
|
6347
|
-
}, []);
|
|
6616
|
+
}, [usedLanguages]);
|
|
6348
6617
|
const updateDraft = useCallback(
|
|
6349
6618
|
(id, updates) => {
|
|
6350
6619
|
setDrafts(
|
|
@@ -6388,12 +6657,13 @@ function ManualSubtitlesPanel({
|
|
|
6388
6657
|
} else {
|
|
6389
6658
|
await refreshTracks(true);
|
|
6390
6659
|
}
|
|
6391
|
-
} catch {
|
|
6660
|
+
} catch (error) {
|
|
6392
6661
|
uploadControllersRef.current.delete(draftId);
|
|
6393
6662
|
updateDraft(draftId, { status: "error" });
|
|
6663
|
+
reportError(error, "subtitles", videoId);
|
|
6394
6664
|
}
|
|
6395
6665
|
},
|
|
6396
|
-
[videoId, drafts, apiOptions, refreshTracks, updateDraft]
|
|
6666
|
+
[videoId, drafts, apiOptions, refreshTracks, updateDraft, reportError]
|
|
6397
6667
|
);
|
|
6398
6668
|
const handleDelete = useCallback(
|
|
6399
6669
|
async (trackId) => {
|
|
@@ -6403,96 +6673,38 @@ function ManualSubtitlesPanel({
|
|
|
6403
6673
|
},
|
|
6404
6674
|
[videoId, apiOptions]
|
|
6405
6675
|
);
|
|
6406
|
-
useEffect(() => {
|
|
6407
|
-
if (mode !== "native" || !nativeVideo) return;
|
|
6408
|
-
const existingTracks = nativeVideo.querySelectorAll(
|
|
6409
|
-
'track[data-subtitle-track="managed"]'
|
|
6410
|
-
);
|
|
6411
|
-
existingTracks.forEach((node) => node.remove());
|
|
6412
|
-
tracks.forEach((track) => {
|
|
6413
|
-
const element = document.createElement("track");
|
|
6414
|
-
element.setAttribute("data-subtitle-track", "managed");
|
|
6415
|
-
element.kind = track.kind || "subtitles";
|
|
6416
|
-
element.label = track.label;
|
|
6417
|
-
element.srclang = track.lang;
|
|
6418
|
-
element.src = resolveSubtitleUrl(videoId || "", track, apiOptions);
|
|
6419
|
-
element.default = track.isDefault;
|
|
6420
|
-
nativeVideo.appendChild(element);
|
|
6421
|
-
});
|
|
6422
|
-
}, [mode, nativeVideo, tracks, videoId, apiOptions]);
|
|
6423
|
-
useEffect(() => {
|
|
6424
|
-
var _a;
|
|
6425
|
-
if (mode !== "shaka" || !shakaPlayer) return;
|
|
6426
|
-
const engine = (_a = shakaPlayer.getNetworkingEngine) == null ? void 0 : _a.call(shakaPlayer);
|
|
6427
|
-
if (engine && !shakaFilterRegisteredRef.current) {
|
|
6428
|
-
engine.registerRequestFilter((type, request) => {
|
|
6429
|
-
request.allowCrossSiteCredentials = true;
|
|
6430
|
-
if (authToken) {
|
|
6431
|
-
request.headers = request.headers || {};
|
|
6432
|
-
request.headers.Authorization = `Bearer ${authToken}`;
|
|
6433
|
-
}
|
|
6434
|
-
});
|
|
6435
|
-
shakaFilterRegisteredRef.current = true;
|
|
6436
|
-
}
|
|
6437
|
-
if (shakaPlayer.getTextTracks && shakaPlayer.removeTextTrack) {
|
|
6438
|
-
const existing = shakaPlayer.getTextTracks();
|
|
6439
|
-
existing.forEach((track) => {
|
|
6440
|
-
if (shakaAddedIdsRef.current.includes(track.id)) {
|
|
6441
|
-
shakaPlayer.removeTextTrack(track);
|
|
6442
|
-
}
|
|
6443
|
-
});
|
|
6444
|
-
shakaAddedIdsRef.current = [];
|
|
6445
|
-
}
|
|
6446
|
-
const addTracks = async () => {
|
|
6447
|
-
var _a2, _b, _c;
|
|
6448
|
-
const existingTracks = ((_a2 = shakaPlayer.getTextTracks) == null ? void 0 : _a2.call(shakaPlayer)) || [];
|
|
6449
|
-
for (const track of tracks) {
|
|
6450
|
-
const vttUrl = resolveSubtitleUrl(videoId || "", track, apiOptions);
|
|
6451
|
-
const label = track.label || track.lang || "und";
|
|
6452
|
-
const lang = track.lang || "und";
|
|
6453
|
-
const kind = track.kind || "subtitles";
|
|
6454
|
-
const alreadyExists = existingTracks.some(
|
|
6455
|
-
(item) => (item.language || "") === lang && (item.label || "") === label && (item.kind || "subtitles") === kind
|
|
6456
|
-
);
|
|
6457
|
-
if (alreadyExists) {
|
|
6458
|
-
continue;
|
|
6459
|
-
}
|
|
6460
|
-
let added = null;
|
|
6461
|
-
if (typeof shakaPlayer.addTextTrackAsync === "function") {
|
|
6462
|
-
added = await shakaPlayer.addTextTrackAsync(
|
|
6463
|
-
vttUrl,
|
|
6464
|
-
lang,
|
|
6465
|
-
kind,
|
|
6466
|
-
label
|
|
6467
|
-
);
|
|
6468
|
-
} else if (typeof shakaPlayer.addTextTrack === "function") {
|
|
6469
|
-
added = shakaPlayer.addTextTrack(vttUrl, lang, kind, label);
|
|
6470
|
-
}
|
|
6471
|
-
if (added && typeof added.id === "number") {
|
|
6472
|
-
shakaAddedIdsRef.current.push(added.id);
|
|
6473
|
-
existingTracks.push(added);
|
|
6474
|
-
}
|
|
6475
|
-
}
|
|
6476
|
-
const defaultTrack = tracks.find((item) => item.isDefault);
|
|
6477
|
-
if (defaultTrack && shakaPlayer.selectTextTrack) {
|
|
6478
|
-
const list = ((_b = shakaPlayer.getTextTracks) == null ? void 0 : _b.call(shakaPlayer)) || [];
|
|
6479
|
-
const target = list.find(
|
|
6480
|
-
(item) => item.language === defaultTrack.lang && item.label === defaultTrack.label
|
|
6481
|
-
);
|
|
6482
|
-
if (target) {
|
|
6483
|
-
(_c = shakaPlayer.setTextTrackVisibility) == null ? void 0 : _c.call(shakaPlayer, true);
|
|
6484
|
-
shakaPlayer.selectTextTrack(target);
|
|
6485
|
-
}
|
|
6486
|
-
}
|
|
6487
|
-
};
|
|
6488
|
-
addTracks().catch(() => {
|
|
6489
|
-
});
|
|
6490
|
-
}, [mode, shakaPlayer, tracks, videoId, apiOptions, authToken]);
|
|
6491
6676
|
const handleRemoveDraft = useCallback((draftId) => {
|
|
6492
6677
|
setDrafts((prev) => prev.filter((item) => item.id !== draftId));
|
|
6493
6678
|
}, []);
|
|
6494
6679
|
return /* @__PURE__ */ jsxs("div", { className: "video-settings-modal-subtitles", children: [
|
|
6495
|
-
|
|
6680
|
+
autoTrack && /* @__PURE__ */ jsxs(
|
|
6681
|
+
Flex,
|
|
6682
|
+
{
|
|
6683
|
+
align: "center",
|
|
6684
|
+
gap: "md",
|
|
6685
|
+
className: "video-settings-modal-subtitles-auto-toggle",
|
|
6686
|
+
children: [
|
|
6687
|
+
/* @__PURE__ */ jsx(
|
|
6688
|
+
Text,
|
|
6689
|
+
{
|
|
6690
|
+
size: "sm",
|
|
6691
|
+
fw: 500,
|
|
6692
|
+
c: "var(--mantine-color-bright)",
|
|
6693
|
+
flex: 1,
|
|
6694
|
+
children: t("editor.video.settingsModal.hideAutoSubtitles")
|
|
6695
|
+
}
|
|
6696
|
+
),
|
|
6697
|
+
/* @__PURE__ */ jsx(
|
|
6698
|
+
Switch,
|
|
6699
|
+
{
|
|
6700
|
+
size: "sm",
|
|
6701
|
+
checked: autoSubtitlesDisabled,
|
|
6702
|
+
onChange: (event) => handleAutoSubtitlesToggle(event.currentTarget.checked)
|
|
6703
|
+
}
|
|
6704
|
+
)
|
|
6705
|
+
]
|
|
6706
|
+
}
|
|
6707
|
+
),
|
|
6496
6708
|
/* @__PURE__ */ jsx(
|
|
6497
6709
|
Text,
|
|
6498
6710
|
{
|
|
@@ -6502,7 +6714,7 @@ function ManualSubtitlesPanel({
|
|
|
6502
6714
|
children: t("editor.video.settingsModal.uploadSubtitlesManually")
|
|
6503
6715
|
}
|
|
6504
6716
|
),
|
|
6505
|
-
|
|
6717
|
+
manualTracks.map((track) => /* @__PURE__ */ jsxs(
|
|
6506
6718
|
Flex,
|
|
6507
6719
|
{
|
|
6508
6720
|
align: "center",
|
|
@@ -6555,6 +6767,7 @@ function ManualSubtitlesPanel({
|
|
|
6555
6767
|
DraftRow,
|
|
6556
6768
|
{
|
|
6557
6769
|
draft,
|
|
6770
|
+
usedLanguages,
|
|
6558
6771
|
onChange: (updates) => updateDraft(draft.id, updates),
|
|
6559
6772
|
onUpload: (file) => handleUploadDraft(draft.id, file),
|
|
6560
6773
|
onRemove: () => handleRemoveDraft(draft.id),
|
|
@@ -6574,9 +6787,15 @@ function ManualSubtitlesPanel({
|
|
|
6574
6787
|
)
|
|
6575
6788
|
] });
|
|
6576
6789
|
}
|
|
6577
|
-
function DraftRow({ draft, onChange, onUpload, onRemove, SelectComponent }) {
|
|
6790
|
+
function DraftRow({ draft, usedLanguages, onChange, onUpload, onRemove, SelectComponent }) {
|
|
6578
6791
|
const { t } = useTranslation();
|
|
6579
6792
|
const inputRef = useRef(null);
|
|
6793
|
+
const availableLanguages = useMemo(
|
|
6794
|
+
() => LANGUAGE_OPTIONS.filter(
|
|
6795
|
+
(opt) => opt.value === draft.lang || !usedLanguages.has(opt.value)
|
|
6796
|
+
),
|
|
6797
|
+
[usedLanguages, draft.lang]
|
|
6798
|
+
);
|
|
6580
6799
|
const handleFileChange = (e) => {
|
|
6581
6800
|
var _a;
|
|
6582
6801
|
const file = (_a = e.target.files) == null ? void 0 : _a[0];
|
|
@@ -6588,7 +6807,6 @@ function DraftRow({ draft, onChange, onUpload, onRemove, SelectComponent }) {
|
|
|
6588
6807
|
Flex,
|
|
6589
6808
|
{
|
|
6590
6809
|
align: "center",
|
|
6591
|
-
gap: "md",
|
|
6592
6810
|
wrap: "nowrap",
|
|
6593
6811
|
className: "video-settings-modal-subtitles-row",
|
|
6594
6812
|
children: [
|
|
@@ -6597,7 +6815,7 @@ function DraftRow({ draft, onChange, onUpload, onRemove, SelectComponent }) {
|
|
|
6597
6815
|
{
|
|
6598
6816
|
size: "sm",
|
|
6599
6817
|
placeholder: t("editor.video.settingsModal.selectLanguage"),
|
|
6600
|
-
data:
|
|
6818
|
+
data: availableLanguages,
|
|
6601
6819
|
value: draft.lang,
|
|
6602
6820
|
onChange: (value) => onChange({
|
|
6603
6821
|
lang: value,
|
|
@@ -6610,7 +6828,7 @@ function DraftRow({ draft, onChange, onUpload, onRemove, SelectComponent }) {
|
|
|
6610
6828
|
inputClassName: "video-settings-modal-language-select"
|
|
6611
6829
|
}
|
|
6612
6830
|
),
|
|
6613
|
-
/* @__PURE__ */ jsx(Flex, { align: "center", gap: 6, flex: 1, children: draft.status === "uploading" ? /* @__PURE__ */ jsx(Loader, { size: 22
|
|
6831
|
+
/* @__PURE__ */ jsx(Flex, { align: "center", gap: 6, flex: 1, children: draft.status === "uploading" ? /* @__PURE__ */ jsx(Loader, { size: 22 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6614
6832
|
/* @__PURE__ */ jsx(
|
|
6615
6833
|
"input",
|
|
6616
6834
|
{
|
|
@@ -6668,6 +6886,7 @@ function VideoSettingsModal({
|
|
|
6668
6886
|
chaptersDisabled = false,
|
|
6669
6887
|
shakaPlayer,
|
|
6670
6888
|
nativeVideo,
|
|
6889
|
+
videoDuration,
|
|
6671
6890
|
playerMode = "native"
|
|
6672
6891
|
}) {
|
|
6673
6892
|
const { t } = useTranslation();
|
|
@@ -6682,17 +6901,24 @@ function VideoSettingsModal({
|
|
|
6682
6901
|
const saveChaptersRef = useRef(
|
|
6683
6902
|
null
|
|
6684
6903
|
);
|
|
6904
|
+
const saveSubtitlesRef = useRef(null);
|
|
6905
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
6685
6906
|
const handlePosterSelect = useCallback((url) => {
|
|
6907
|
+
console.log("[Cover] handlePosterSelect:", url);
|
|
6686
6908
|
setSelectedPosterUrl(url);
|
|
6687
6909
|
}, []);
|
|
6688
6910
|
useEffect(() => {
|
|
6689
|
-
if (
|
|
6690
|
-
setStatus(
|
|
6691
|
-
return;
|
|
6911
|
+
if (initialStatus) {
|
|
6912
|
+
setStatus(initialStatus);
|
|
6692
6913
|
}
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6914
|
+
}, [initialStatus]);
|
|
6915
|
+
useEffect(() => {
|
|
6916
|
+
if (!opened || !videoId) return;
|
|
6917
|
+
fetchStatus(videoId).then((freshStatus) => {
|
|
6918
|
+
if (freshStatus) {
|
|
6919
|
+
setStatus(freshStatus);
|
|
6920
|
+
onStatusChange == null ? void 0 : onStatusChange(freshStatus);
|
|
6921
|
+
}
|
|
6696
6922
|
}).catch(() => {
|
|
6697
6923
|
});
|
|
6698
6924
|
}, [opened, videoId]);
|
|
@@ -6708,28 +6934,47 @@ function VideoSettingsModal({
|
|
|
6708
6934
|
[videoId]
|
|
6709
6935
|
);
|
|
6710
6936
|
const handleSave = useCallback(async () => {
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
if (
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6937
|
+
setIsSaving(true);
|
|
6938
|
+
try {
|
|
6939
|
+
if (saveChaptersRef.current) {
|
|
6940
|
+
const payload = await saveChaptersRef.current();
|
|
6941
|
+
if (Array.isArray(payload)) {
|
|
6942
|
+
onChaptersSaved == null ? void 0 : onChaptersSaved(payload);
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
onChaptersDisabledChange == null ? void 0 : onChaptersDisabledChange(chaptersDisabledValue);
|
|
6946
|
+
if (saveSubtitlesRef.current) {
|
|
6947
|
+
await saveSubtitlesRef.current();
|
|
6948
|
+
}
|
|
6949
|
+
onSubtitlesSaved == null ? void 0 : onSubtitlesSaved(subtitlesSnapshot);
|
|
6950
|
+
console.log("[Cover] handleSave: selectedPosterUrl=", selectedPosterUrl, "onPosterChange=", !!onPosterChange);
|
|
6951
|
+
if (selectedPosterUrl !== null) {
|
|
6952
|
+
console.log("[Cover] calling onPosterChange with:", selectedPosterUrl);
|
|
6953
|
+
onPosterChange == null ? void 0 : onPosterChange(selectedPosterUrl);
|
|
6954
|
+
} else {
|
|
6955
|
+
console.log("[Cover] selectedPosterUrl is null, skipping onPosterChange");
|
|
6956
|
+
}
|
|
6957
|
+
if (videoId) {
|
|
6958
|
+
const updatedStatus = await fetchStatus(videoId);
|
|
6959
|
+
onStatusChange == null ? void 0 : onStatusChange(updatedStatus);
|
|
6960
|
+
}
|
|
6961
|
+
onClose();
|
|
6962
|
+
} finally {
|
|
6963
|
+
setIsSaving(false);
|
|
6720
6964
|
}
|
|
6721
|
-
onClose();
|
|
6722
6965
|
}, [
|
|
6966
|
+
videoId,
|
|
6723
6967
|
chaptersDisabledValue,
|
|
6724
6968
|
onChaptersDisabledChange,
|
|
6725
6969
|
onChaptersSaved,
|
|
6726
6970
|
onClose,
|
|
6727
6971
|
onPosterChange,
|
|
6972
|
+
onStatusChange,
|
|
6728
6973
|
onSubtitlesSaved,
|
|
6729
6974
|
selectedPosterUrl,
|
|
6730
6975
|
subtitlesSnapshot
|
|
6731
6976
|
]);
|
|
6732
|
-
return /* @__PURE__ */
|
|
6977
|
+
return /* @__PURE__ */ jsxs(
|
|
6733
6978
|
Modal,
|
|
6734
6979
|
{
|
|
6735
6980
|
opened,
|
|
@@ -6740,13 +6985,14 @@ function VideoSettingsModal({
|
|
|
6740
6985
|
withinPortal: true,
|
|
6741
6986
|
zIndex: 300,
|
|
6742
6987
|
className: "video-settings-modal",
|
|
6743
|
-
children:
|
|
6744
|
-
/* @__PURE__ */ jsxs(Stack, { gap: 32, children: [
|
|
6988
|
+
children: [
|
|
6989
|
+
/* @__PURE__ */ jsx(Box, { className: "video-settings-modal-body", children: /* @__PURE__ */ jsxs(Stack, { gap: 32, children: [
|
|
6745
6990
|
/* @__PURE__ */ jsx(
|
|
6746
6991
|
CoverSection,
|
|
6747
6992
|
{
|
|
6748
6993
|
opened,
|
|
6749
6994
|
videoId,
|
|
6995
|
+
initialStatus: status,
|
|
6750
6996
|
title: t("editor.video.settingsModal.cover"),
|
|
6751
6997
|
uploadLabel: t("editor.video.settingsModal.uploadFile"),
|
|
6752
6998
|
cropLabel: t("editor.video.settingsModal.cropCoverImage"),
|
|
@@ -6785,7 +7031,10 @@ function VideoSettingsModal({
|
|
|
6785
7031
|
shakaPlayer,
|
|
6786
7032
|
nativeVideo,
|
|
6787
7033
|
statusSubtitles: status == null ? void 0 : status.subtitles,
|
|
6788
|
-
onTracksChange: setSubtitlesSnapshot
|
|
7034
|
+
onTracksChange: setSubtitlesSnapshot,
|
|
7035
|
+
onSave: (handler) => {
|
|
7036
|
+
saveSubtitlesRef.current = handler;
|
|
7037
|
+
}
|
|
6789
7038
|
}
|
|
6790
7039
|
)
|
|
6791
7040
|
] }),
|
|
@@ -6795,6 +7044,8 @@ function VideoSettingsModal({
|
|
|
6795
7044
|
videoId,
|
|
6796
7045
|
initialChapters: status == null ? void 0 : status.chapters,
|
|
6797
7046
|
nativeVideo,
|
|
7047
|
+
shakaPlayer,
|
|
7048
|
+
videoDuration,
|
|
6798
7049
|
title: t("editor.video.settingsModal.chapters"),
|
|
6799
7050
|
customLabel: t("editor.video.settingsModal.chaptersCustom"),
|
|
6800
7051
|
dontShowLabel: t("editor.video.settingsModal.chaptersDontShow"),
|
|
@@ -6824,17 +7075,18 @@ function VideoSettingsModal({
|
|
|
6824
7075
|
)
|
|
6825
7076
|
}
|
|
6826
7077
|
)
|
|
6827
|
-
] }),
|
|
6828
|
-
/* @__PURE__ */ jsx(
|
|
7078
|
+
] }) }),
|
|
7079
|
+
/* @__PURE__ */ jsx(Box, { className: "video-settings-modal-footer", children: /* @__PURE__ */ jsx(
|
|
6829
7080
|
FooterActions,
|
|
6830
7081
|
{
|
|
6831
7082
|
cancelLabel: t("editor.video.settingsModal.cancel"),
|
|
6832
7083
|
saveLabel: t("editor.video.settingsModal.save"),
|
|
6833
7084
|
onCancel: onClose,
|
|
6834
|
-
onSave: handleSave
|
|
7085
|
+
onSave: handleSave,
|
|
7086
|
+
isSaving
|
|
6835
7087
|
}
|
|
6836
|
-
)
|
|
6837
|
-
]
|
|
7088
|
+
) })
|
|
7089
|
+
]
|
|
6838
7090
|
}
|
|
6839
7091
|
);
|
|
6840
7092
|
}
|
|
@@ -6875,7 +7127,8 @@ function VideoBlock({
|
|
|
6875
7127
|
shakaPlayer: null,
|
|
6876
7128
|
nativeVideo: null,
|
|
6877
7129
|
mode: "native",
|
|
6878
|
-
containerEl: null
|
|
7130
|
+
containerEl: null,
|
|
7131
|
+
videoDuration: null
|
|
6879
7132
|
});
|
|
6880
7133
|
const fallbackNativeVideoUrl = useMemo(() => {
|
|
6881
7134
|
if (platform === "download" || platform === "link") {
|
|
@@ -6919,6 +7172,7 @@ function VideoBlock({
|
|
|
6919
7172
|
};
|
|
6920
7173
|
const handlePosterChange = useCallback(
|
|
6921
7174
|
(posterUrl2) => {
|
|
7175
|
+
console.log("[Cover] handlePosterChange: setting posterUrl=", posterUrl2);
|
|
6922
7176
|
setPosterUrl(posterUrl2);
|
|
6923
7177
|
setVideoPoster(editor, nodeKey, posterUrl2);
|
|
6924
7178
|
},
|
|
@@ -6947,7 +7201,7 @@ function VideoBlock({
|
|
|
6947
7201
|
});
|
|
6948
7202
|
}, [editor, nodeKey]);
|
|
6949
7203
|
const resolvedChaptersOverride = chaptersDisabled ? [] : chaptersOverride;
|
|
6950
|
-
const TrashIcon = /* @__PURE__ */ jsx(
|
|
7204
|
+
const TrashIcon = /* @__PURE__ */ jsx("span", { style: { color: "var(--mantine-color-red-outline)", display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: tI, size: 16 }) });
|
|
6951
7205
|
const uploadComponentProps = {
|
|
6952
7206
|
className,
|
|
6953
7207
|
format,
|
|
@@ -6986,7 +7240,7 @@ function VideoBlock({
|
|
|
6986
7240
|
event.stopPropagation();
|
|
6987
7241
|
setSettingsModalOpened(true);
|
|
6988
7242
|
},
|
|
6989
|
-
children: /* @__PURE__ */ jsx(SettingsIcon, { size: 16 })
|
|
7243
|
+
children: /* @__PURE__ */ jsx("span", { style: { color: "var(--mantine-color-gray-text)", display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx(SettingsIcon, { size: 16 }) })
|
|
6990
7244
|
}
|
|
6991
7245
|
) }),
|
|
6992
7246
|
/* @__PURE__ */ jsx(
|
|
@@ -7005,7 +7259,8 @@ function VideoBlock({
|
|
|
7005
7259
|
onChaptersDisabledChange: handleChaptersDisabledChange,
|
|
7006
7260
|
shakaPlayer: playerInfo.shakaPlayer,
|
|
7007
7261
|
nativeVideo: playerInfo.nativeVideo,
|
|
7008
|
-
playerMode: playerInfo.mode
|
|
7262
|
+
playerMode: playerInfo.mode,
|
|
7263
|
+
videoDuration: playerInfo.videoDuration
|
|
7009
7264
|
}
|
|
7010
7265
|
)
|
|
7011
7266
|
] }),
|