mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-22 12:21:20 +00:00
added qortal multi publish status
This commit is contained in:
parent
979c6d4265
commit
378c80cf3c
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "qapp-core",
|
||||
"version": "1.0.31",
|
||||
"version": "1.0.32",
|
||||
"description": "Qortal's core React library with global state, UI components, and utilities",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
@ -49,7 +49,6 @@
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^7.0.1",
|
||||
"@mui/material": "^7.0.1",
|
||||
"mediainfo.js": "^0.3.5",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.2"
|
||||
|
@ -13,157 +13,307 @@ import {
|
||||
Typography,
|
||||
Box,
|
||||
LinearProgress,
|
||||
Stack
|
||||
Stack,
|
||||
DialogActions,
|
||||
Button
|
||||
} from '@mui/material';
|
||||
import { PublishStatus, useMultiplePublishStore, usePublishStatusStore } from "../../state/multiplePublish";
|
||||
import { ResourceToPublish } from "../../types/qortalRequests/types";
|
||||
import { QortalGetMetadata } from "../../types/interfaces/resources";
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
|
||||
|
||||
|
||||
|
||||
const MultiPublishDialogComponent = () => {
|
||||
const { resources, isPublishing } = useMultiplePublishStore((state) => ({
|
||||
resources: state.resources,
|
||||
isPublishing: state.isPublishing
|
||||
}));
|
||||
const { publishStatus, setPublishStatusByKey, getPublishStatusByKey } = usePublishStatusStore();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
function handleNavigation(event: any) {
|
||||
if (event.data?.action === 'PUBLISH_STATUS') {
|
||||
const data = event.data;
|
||||
console.log('datadata', data)
|
||||
// Validate required structure before continuing
|
||||
if (
|
||||
!data.publishLocation ||
|
||||
typeof data.publishLocation.name !== 'string' ||
|
||||
typeof data.publishLocation.identifier !== 'string' ||
|
||||
typeof data.publishLocation.service !== 'string'
|
||||
) {
|
||||
console.warn('Invalid PUBLISH_STATUS data, skipping:', data);
|
||||
return;
|
||||
export interface MultiplePublishError {
|
||||
error: {
|
||||
unsuccessfulPublishes: any[]
|
||||
}
|
||||
}
|
||||
|
||||
const { publishLocation, chunks, totalChunks } = data;
|
||||
|
||||
const key = `${publishLocation?.service}-${publishLocation?.name}-${publishLocation?.identifier}`;
|
||||
export const MultiPublishDialogComponent = () => {
|
||||
const {
|
||||
resources,
|
||||
isPublishing,
|
||||
failedResources,
|
||||
reset,
|
||||
reject,
|
||||
error: publishError,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
setError,
|
||||
setFailedPublishResources,
|
||||
complete
|
||||
} = useMultiplePublishStore((state) => ({
|
||||
resources: state.resources,
|
||||
isPublishing: state.isPublishing,
|
||||
failedResources: state.failedResources,
|
||||
reset: state.reset,
|
||||
reject: state.reject,
|
||||
error: state.error,
|
||||
isLoading: state.isLoading,
|
||||
setIsLoading: state.setIsLoading,
|
||||
setError: state.setError,
|
||||
setFailedPublishResources: state.setFailedPublishResources,
|
||||
complete: state.complete
|
||||
}));
|
||||
|
||||
try {
|
||||
const dataToBeSent: any = {};
|
||||
if (chunks !== undefined && chunks !== null) {
|
||||
dataToBeSent.chunks = chunks;
|
||||
}
|
||||
if (totalChunks !== undefined && totalChunks !== null) {
|
||||
dataToBeSent.totalChunks = totalChunks;
|
||||
}
|
||||
|
||||
setPublishStatusByKey(key, {
|
||||
publishLocation,
|
||||
...dataToBeSent,
|
||||
processed: data?.processed || false
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to set publish status:', err);
|
||||
}
|
||||
}
|
||||
const { publishStatus, setPublishStatusByKey } = usePublishStatusStore();
|
||||
|
||||
const resourcesToPublish = useMemo(() => {
|
||||
return resources.filter((item) =>
|
||||
failedResources.some((f) =>
|
||||
f?.name === item?.name && f?.identifier === item?.identifier && f?.service === item?.service
|
||||
)
|
||||
);
|
||||
}, [resources, failedResources]);
|
||||
|
||||
const publishMultipleResources = useCallback(async () => {
|
||||
const timeout = resources.length * 1200000;
|
||||
|
||||
try {
|
||||
resourcesToPublish.forEach((item) => {
|
||||
const key = `${item?.service}-${item?.name}-${item?.identifier}`;
|
||||
setPublishStatusByKey(key, {
|
||||
error: undefined,
|
||||
chunks: 0,
|
||||
totalChunks: 0
|
||||
});
|
||||
});
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setFailedPublishResources([]);
|
||||
|
||||
const result = await qortalRequestWithTimeout(
|
||||
{ action: 'PUBLISH_MULTIPLE_QDN_RESOURCES', resources: resourcesToPublish },
|
||||
timeout
|
||||
);
|
||||
|
||||
complete(result);
|
||||
} catch (error: any) {
|
||||
const unPublished = error?.error?.unsuccessfulPublishes;
|
||||
const failedPublishes: QortalGetMetadata[] = [];
|
||||
|
||||
if (unPublished && Array.isArray(unPublished)) {
|
||||
unPublished.forEach((item) => {
|
||||
const key = `${item?.service}-${item?.name}-${item?.identifier}`;
|
||||
setPublishStatusByKey(key, {
|
||||
error: { reason: item?.reason }
|
||||
});
|
||||
|
||||
failedPublishes.push({
|
||||
name: item?.name,
|
||||
service: item?.service,
|
||||
identifier: item?.identifier
|
||||
});
|
||||
});
|
||||
setFailedPublishResources(failedPublishes);
|
||||
} else {
|
||||
setError(error instanceof Error ? error.message : 'Error during publish');
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [resourcesToPublish, resources, setPublishStatusByKey, setIsLoading, setError, setFailedPublishResources, complete]);
|
||||
|
||||
const handleNavigation = useCallback((event: any) => {
|
||||
if (event.data?.action !== 'PUBLISH_STATUS') return;
|
||||
|
||||
const data = event.data;
|
||||
|
||||
if (
|
||||
!data.publishLocation ||
|
||||
typeof data.publishLocation?.name !== 'string' ||
|
||||
typeof data.publishLocation?.service !== 'string'
|
||||
) {
|
||||
console.warn('Invalid PUBLISH_STATUS data, skipping:', data);
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleNavigation);
|
||||
const {
|
||||
publishLocation,
|
||||
chunks,
|
||||
totalChunks,
|
||||
retry,
|
||||
filename,
|
||||
processed
|
||||
} = data;
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", handleNavigation);
|
||||
const key = `${publishLocation?.service}-${publishLocation?.name}-${publishLocation?.identifier}`;
|
||||
|
||||
const update: any = {
|
||||
publishLocation,
|
||||
processed: processed || false
|
||||
};
|
||||
}, []);
|
||||
if(!isPublishing) return null
|
||||
if (chunks != null && chunks !== undefined) update.chunks = chunks;
|
||||
if (totalChunks != null && totalChunks !== undefined) update.totalChunks = totalChunks;
|
||||
if (retry != null && retry !== undefined) update.retry = retry;
|
||||
if (filename != null && retry !== undefined) update.filename = filename;
|
||||
|
||||
try {
|
||||
setPublishStatusByKey(key, update);
|
||||
} catch (err) {
|
||||
console.error('Failed to set publish status:', err);
|
||||
}
|
||||
}, [setPublishStatusByKey]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("message", handleNavigation);
|
||||
return () => window.removeEventListener("message", handleNavigation);
|
||||
}, [handleNavigation]);
|
||||
|
||||
if (!isPublishing) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={isPublishing} fullWidth maxWidth="sm">
|
||||
<Dialog
|
||||
open={isPublishing}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
sx={{ zIndex: 999990 }}
|
||||
slotProps={{ paper: { elevation: 0 } }}
|
||||
>
|
||||
<DialogTitle>Publishing Status</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={3}>
|
||||
{resources.map((publish: ResourceToPublish) => {
|
||||
const key = `${publish?.service}-${publish?.name}-${publish?.identifier}`;
|
||||
const individualPublishStatus = publishStatus[key] || null
|
||||
return (
|
||||
<IndividualResourceComponent key={key} publishKey={key} publish={publish} publishStatus={individualPublishStatus} />
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
{publishError && (
|
||||
<Stack spacing={3}>
|
||||
<Box mt={2} sx={{ display: 'flex', gap: '5px', alignItems: 'center' }}>
|
||||
<ErrorIcon color="error" />
|
||||
<Typography variant="body2">{publishError}</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{!publishError && (
|
||||
<Stack spacing={3}>
|
||||
{resources.map((publish) => {
|
||||
const key = `${publish?.service}-${publish?.name}-${publish?.identifier}`;
|
||||
const individualPublishStatus = publishStatus[key] || null;
|
||||
return (
|
||||
<IndividualResourceComponent
|
||||
key={key}
|
||||
publishKey={key}
|
||||
publish={publish}
|
||||
publishStatus={individualPublishStatus}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button disabled={isLoading} color="error" variant="contained" onClick={() => {
|
||||
reject(new Error('Canceled Publish'));
|
||||
reset();
|
||||
}}>
|
||||
Close
|
||||
</Button>
|
||||
{failedResources?.length > 0 && (
|
||||
<Button
|
||||
disabled={isLoading || resourcesToPublish.length === 0}
|
||||
color="success"
|
||||
variant="contained"
|
||||
onClick={publishMultipleResources}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
interface IndividualResourceComponentProps {
|
||||
publish: ResourceToPublish
|
||||
publishStatus: PublishStatus
|
||||
publishKey: string
|
||||
}
|
||||
const ESTIMATED_PROCESSING_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
const IndividualResourceComponent = ({publish, publishKey, publishStatus}: IndividualResourceComponentProps)=> {
|
||||
const [now, setNow] = useState(Date.now());
|
||||
console.log('key500',publishKey, publishStatus)
|
||||
const IndividualResourceComponent = ({ publish, publishKey, publishStatus }: IndividualResourceComponentProps) => {
|
||||
const [now, setNow] = useState(Date.now());
|
||||
const [processingStart, setProcessingStart] = useState<number | undefined>();
|
||||
|
||||
const chunkPercent = useMemo(()=> {
|
||||
if(!publishStatus?.chunks || !publishStatus?.totalChunks) return 0
|
||||
return (publishStatus?.chunks / publishStatus?.totalChunks) * 100
|
||||
}, [publishStatus])
|
||||
console.log('chunkPercent', chunkPercent)
|
||||
const chunkDone = useMemo(()=> {
|
||||
if(!publishStatus?.chunks || !publishStatus?.totalChunks) return false
|
||||
return publishStatus?.chunks === publishStatus?.totalChunks
|
||||
}, [publishStatus])
|
||||
const chunkPercent = useMemo(() => {
|
||||
if (!publishStatus?.chunks || !publishStatus?.totalChunks) return 0;
|
||||
return (publishStatus.chunks / publishStatus.totalChunks) * 100;
|
||||
}, [publishStatus?.chunks, publishStatus?.totalChunks]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!chunkDone) return
|
||||
const interval = setInterval(() => {
|
||||
setNow(Date.now());
|
||||
}, 1000);
|
||||
const chunkDone = useMemo(() => {
|
||||
return (
|
||||
publishStatus?.chunks > 0 &&
|
||||
publishStatus?.totalChunks > 0 &&
|
||||
publishStatus?.chunks === publishStatus?.totalChunks
|
||||
);
|
||||
}, [publishStatus?.chunks, publishStatus?.totalChunks]);
|
||||
|
||||
// Start processing timer once chunking completes
|
||||
useEffect(() => {
|
||||
if (chunkDone && !processingStart) {
|
||||
setProcessingStart(Date.now());
|
||||
}
|
||||
}, [chunkDone, processingStart]);
|
||||
|
||||
// Keep time ticking for progress simulation
|
||||
useEffect(() => {
|
||||
if (!chunkDone) return;
|
||||
const interval = setInterval(() => setNow(Date.now()), 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [chunkDone]);
|
||||
|
||||
const [processingStart, setProcessingStart] = useState<number | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (chunkDone && !processingStart) {
|
||||
setProcessingStart(Date.now());
|
||||
}
|
||||
}, [chunkDone, processingStart]);
|
||||
|
||||
const processingPercent = useMemo(() => {
|
||||
if (!chunkDone || !processingStart || !publishStatus?.totalChunks || !now) return 0;
|
||||
if (publishStatus?.error || !chunkDone || !processingStart || !publishStatus?.totalChunks || !now) return 0;
|
||||
|
||||
const totalMB = publishStatus.totalChunks * 5;
|
||||
const estimatedProcessingMs = (300_000 / 2048) * totalMB;
|
||||
const totalMB = publishStatus.totalChunks * 5; // assume 5MB per chunk
|
||||
const estimatedProcessingMs = (300_000 / 2048) * totalMB; // 5min per 2GB scaled
|
||||
|
||||
const elapsed = now - processingStart;
|
||||
if(!elapsed || elapsed < 0) return 0
|
||||
return Math.min((elapsed / estimatedProcessingMs) * 100, 100);
|
||||
}, [chunkDone, processingStart, now, publishStatus?.totalChunks]);
|
||||
const elapsed = now - processingStart;
|
||||
if (elapsed <= 0) return 0;
|
||||
return Math.min((elapsed / estimatedProcessingMs) * 100, 100);
|
||||
}, [chunkDone, processingStart, now, publishStatus?.totalChunks, publishStatus?.error]);
|
||||
|
||||
return (
|
||||
<Box p={1} border={1} borderColor="divider" borderRadius={2}>
|
||||
<Typography variant="subtitle1" fontWeight="bold">
|
||||
{publishKey}
|
||||
</Typography>
|
||||
<Box p={1} border={1} borderColor="divider" borderRadius={2}>
|
||||
<Typography variant="subtitle1" fontWeight="bold">
|
||||
{publish?.filename || publishStatus?.filename || publishKey}
|
||||
</Typography>
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
File Chunk { (!publishStatus?.chunks || !publishStatus?.totalChunks) ? '' : publishStatus?.chunks}/{publishStatus?.totalChunks} ({(chunkPercent).toFixed(0)}%)
|
||||
</Typography>
|
||||
<LinearProgress variant="determinate" value={chunkPercent} />
|
||||
</Box>
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
File Chunk {publishStatus?.chunks || 0}/{publishStatus?.totalChunks || 0} ({chunkPercent.toFixed(0)}%)
|
||||
</Typography>
|
||||
<LinearProgress variant="determinate" value={chunkPercent} />
|
||||
</Box>
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
File Processing ({processingStart ? processingPercent.toFixed(0) : '0'}%)
|
||||
</Typography>
|
||||
<LinearProgress variant="determinate" value={processingPercent} />
|
||||
</Box>
|
||||
|
||||
{publishStatus?.processed && (
|
||||
<Box mt={2} display="flex" gap={1} alignItems="center">
|
||||
<CheckCircleIcon color="success" />
|
||||
<Typography variant="body2">Published successfully</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{publishStatus?.retry && !publishStatus?.error && !publishStatus?.processed && (
|
||||
<Box mt={2} display="flex" gap={1} alignItems="center">
|
||||
<ErrorIcon color="error" />
|
||||
<Typography variant="body2">Publish failed. Attempting retry...</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{publishStatus?.error && !publishStatus?.processed && (
|
||||
<Box mt={2} display="flex" gap={1} alignItems="center">
|
||||
<ErrorIcon color="error" />
|
||||
<Typography variant="body2">
|
||||
Publish failed. {publishStatus?.error?.reason || 'Unknown error'}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
File Processing ({(!processingPercent || !processingStart) ? '0' : processingPercent.toFixed(0)}%)
|
||||
</Typography>
|
||||
<LinearProgress variant="determinate" value={processingPercent} />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const MultiPublishDialog = React.memo(MultiPublishDialogComponent);
|
||||
|
@ -119,17 +119,14 @@ const SubtitleManagerComponent = ({
|
||||
const subtitles = useListReturn(
|
||||
`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`
|
||||
);
|
||||
console.log("subtitles222", subtitles);
|
||||
const mySubtitles = useMemo(() => {
|
||||
if (!auth?.name) return [];
|
||||
return subtitles?.filter((sub) => sub.name === auth?.name);
|
||||
}, [subtitles, auth?.name]);
|
||||
console.log("subtitles222", subtitles);
|
||||
const getPublishedSubtitles = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
|
||||
console.log("videoId", videoId);
|
||||
const postIdSearch = await identifierOperations.buildLooseSearchPrefix(
|
||||
ENTITY_SUBTITLE,
|
||||
videoId
|
||||
@ -149,7 +146,6 @@ const SubtitleManagerComponent = ({
|
||||
searchParams
|
||||
);
|
||||
lists.addList(`subs-${videoId}`, res?.filter((item)=> !!item?.metadata?.title) || []);
|
||||
console.log("resres2", res);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@ -174,7 +170,6 @@ const SubtitleManagerComponent = ({
|
||||
]);
|
||||
|
||||
const ref = useRef<any>(null)
|
||||
console.log('isOpen', open)
|
||||
|
||||
useEffect(()=> {
|
||||
if(open){
|
||||
@ -184,7 +179,6 @@ const SubtitleManagerComponent = ({
|
||||
|
||||
const handleBlur = (e: React.FocusEvent) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && !isOpenPublish) {
|
||||
console.log('handleBlur')
|
||||
close();
|
||||
setIsOpenPublish(false)
|
||||
}
|
||||
@ -235,7 +229,6 @@ const SubtitleManagerComponent = ({
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
console.log("resources", resources);
|
||||
|
||||
await qortalRequest({
|
||||
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
|
||||
@ -253,7 +246,6 @@ const SubtitleManagerComponent = ({
|
||||
};
|
||||
|
||||
const onSelectHandler = (sub: SubtitlePublishedData) => {
|
||||
console.log("onSelectHandler");
|
||||
onSelect(sub);
|
||||
close();
|
||||
};
|
||||
@ -608,7 +600,6 @@ const PublishSubtitles = ({
|
||||
return copyPrev;
|
||||
});
|
||||
};
|
||||
console.log("subtitles", subtitles);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
@ -843,7 +834,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
|
||||
const [selectedToDownload, setSelectedToDownload] = useState<null | QortalGetMetadata>(null)
|
||||
const [isReady, setIsReady] = useState(false)
|
||||
const { resource, isLoading, error, refetch } = usePublish(2, "JSON", sub, true);
|
||||
console.log("resource", resource, isLoading);
|
||||
const isSelected = currentSubtrack === resource?.data?.language;
|
||||
const [isGettingStatus, setIsGettingStatus] = useState(true)
|
||||
// useEffect(()=> {
|
||||
@ -852,7 +842,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
|
||||
// onSelect(resource?.data)
|
||||
// }
|
||||
// }, [isSelected, resource?.data])
|
||||
console.log('isReady', resource?.data)
|
||||
const getStatus = useCallback(async (service: Service, name: string, identifier: string)=> {
|
||||
try {
|
||||
if(subtitlesStatus[`${service}-${name}-${identifier}`]){
|
||||
@ -890,7 +879,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
|
||||
getStatus(sub?.service, sub?.name, sub?.identifier)
|
||||
}
|
||||
}, [sub?.identifier, sub?.name, sub?.service])
|
||||
console.log('tester', isReady,isLoading,error,resource?.data)
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
|
@ -92,7 +92,6 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
|
||||
const x = e.clientX - rect.left;
|
||||
const percent = x / rect.width;
|
||||
const time = Math.min(Math.max(0, percent * duration), duration);
|
||||
console.log("hello100");
|
||||
setHoverX(e.clientX);
|
||||
|
||||
setShowDuration(time);
|
||||
@ -129,7 +128,6 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
|
||||
console.log("thumbnailUrl", thumbnailUrl, hoverX);
|
||||
}
|
||||
|
||||
console.log("duration", duration);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -445,7 +443,6 @@ interface PlayBackMenuProps {
|
||||
export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate}: PlayBackMenuProps)=> {
|
||||
const theme = useTheme()
|
||||
const ref = useRef<any>(null)
|
||||
console.log('isOpen', isOpen)
|
||||
|
||||
useEffect(()=> {
|
||||
if(isOpen){
|
||||
|
@ -47,7 +47,6 @@ export async function srtBase64ToVttBlobUrl(
|
||||
|
||||
// Step 2: Create a Blob from the Uint8Array with correct MIME type
|
||||
const srtBlob = new Blob([bytes], { type: "application/x-subrip" });
|
||||
console.log("srtBlob", srtBlob);
|
||||
// Step 3: Use convert() with the Blob
|
||||
const vttBlobUrl: string = await convert(srtBlob);
|
||||
return vttBlobUrl;
|
||||
@ -93,14 +92,7 @@ const videoStyles = {
|
||||
video: {},
|
||||
};
|
||||
|
||||
async function loadMediaInfo(wasmPath = "/MediaInfoModule.wasm") {
|
||||
const mediaInfoModule = await import("mediainfo.js");
|
||||
return await mediaInfoModule.default({
|
||||
format: "JSON",
|
||||
full: true,
|
||||
locateFile: () => wasmPath,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function getVideoMimeTypeFromUrl(
|
||||
qortalVideoResource: any
|
||||
@ -114,71 +106,6 @@ async function getVideoMimeTypeFromUrl(
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
// const mediaInfo = await loadMediaInfo();
|
||||
// const chunkCache = new Map<string, Uint8Array>();
|
||||
|
||||
// let fileSize = 0;
|
||||
// try {
|
||||
// const headResp = await fetch(videoUrl, { method: 'HEAD' });
|
||||
// const lengthHeader = headResp.headers.get('Content-Length');
|
||||
// if (!lengthHeader) throw new Error('Missing content length');
|
||||
// fileSize = parseInt(lengthHeader, 10);
|
||||
// } catch (err) {
|
||||
// console.error('Error fetching content length:', err);
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const rawResult = await mediaInfo.analyzeData(
|
||||
// () => fileSize,
|
||||
// async (chunkSize: number, offset: number): Promise<Uint8Array> => {
|
||||
// const key = `${offset}:${chunkSize}`;
|
||||
// if (chunkCache.has(key)) return chunkCache.get(key)!;
|
||||
|
||||
// const end = Math.min(fileSize - 1, offset + chunkSize - 1);
|
||||
// const resp = await fetch(videoUrl, {
|
||||
// headers: { Range: `bytes=${offset}-${end}` },
|
||||
// });
|
||||
|
||||
// if (!resp.ok || (resp.status !== 206 && fileSize > chunkSize)) {
|
||||
// console.warn(`Range request failed: ${resp.status}`);
|
||||
// return new Uint8Array();
|
||||
// }
|
||||
|
||||
// const blob = await resp.blob();
|
||||
// const buffer = new Uint8Array(await blob.arrayBuffer());
|
||||
// chunkCache.set(key, buffer);
|
||||
// return buffer;
|
||||
// }
|
||||
// );
|
||||
|
||||
// const result = JSON.parse(rawResult);
|
||||
// const tracks = result?.media?.track;
|
||||
|
||||
// const videoTrack = tracks?.find((t: any) => t['@type'] === 'Video');
|
||||
// const format = videoTrack?.Format?.toLowerCase();
|
||||
|
||||
// switch (format) {
|
||||
// case 'avc':
|
||||
// case 'h264':
|
||||
// case 'mpeg-4':
|
||||
// case 'mp4':
|
||||
// return 'video/mp4';
|
||||
// case 'vp8':
|
||||
// case 'vp9':
|
||||
// return 'video/webm';
|
||||
// case 'hevc':
|
||||
// case 'h265':
|
||||
// return 'video/mp4'; // still usually wrapped in MP4
|
||||
// case 'matroska':
|
||||
// return 'video/webm';
|
||||
// default:
|
||||
// return 'video/mp4'; // fallback
|
||||
// }
|
||||
// } catch (err) {
|
||||
// console.error('Error analyzing media info:', err);
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
|
||||
export const VideoPlayer = ({
|
||||
@ -259,7 +186,6 @@ export const VideoPlayer = ({
|
||||
}
|
||||
},[location])
|
||||
|
||||
console.log('isFullscreen', isFullscreen)
|
||||
const { getProgress } = useProgressStore();
|
||||
|
||||
const enterFullscreen = useCallback(() => {
|
||||
@ -276,7 +202,6 @@ export const VideoPlayer = ({
|
||||
}, [isFullscreen]);
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
console.log('togglefull', isFullscreen)
|
||||
isFullscreen ? exitFullscreen() : enterFullscreen();
|
||||
}, [isFullscreen]);
|
||||
|
||||
@ -531,7 +456,6 @@ const videoLocationRef = useRef< null | string>(null)
|
||||
|
||||
return
|
||||
}
|
||||
console.log("onSelectSubtitle", subtitle);
|
||||
const player = playerRef.current;
|
||||
if (!player || !subtitle.subtitleData || !subtitle.type) return;
|
||||
|
||||
@ -604,16 +528,13 @@ const videoLocationRef = useRef< null | string>(null)
|
||||
}, 1000);
|
||||
});
|
||||
const tracksInfo = playerRef.current?.textTracks();
|
||||
console.log("tracksInfo", tracksInfo);
|
||||
if (!tracksInfo) return;
|
||||
|
||||
const tracks = Array.from(
|
||||
{ length: (tracksInfo as any).length },
|
||||
(_, i) => (tracksInfo as any)[i]
|
||||
);
|
||||
console.log("tracks", tracks);
|
||||
for (const track of tracks) {
|
||||
console.log("track", track);
|
||||
|
||||
if (track.kind === "subtitles") {
|
||||
track.mode = "showing"; // force display
|
||||
@ -706,7 +627,6 @@ savedVideoRef.current = video.current;
|
||||
{ length: (tracksInfo as any).length },
|
||||
(_, i) => (tracksInfo as any)[i]
|
||||
);
|
||||
console.log("tracks", tracks);
|
||||
for (const track of tracks) {
|
||||
|
||||
if (track.kind === 'subtitles' || track.kind === 'captions') {
|
||||
@ -718,10 +638,7 @@ savedVideoRef.current = video.current;
|
||||
}
|
||||
|
||||
if (activeTrack) {
|
||||
console.log("Subtitle active:", {
|
||||
label: activeTrack.label,
|
||||
srclang: activeTrack.language || activeTrack.srclang, // srclang for native, language for VTT
|
||||
});
|
||||
|
||||
setCurrentSubTrack(activeTrack.language || activeTrack.srclang)
|
||||
} else {
|
||||
setCurrentSubTrack(null)
|
||||
@ -748,12 +665,10 @@ savedVideoRef.current = video.current;
|
||||
console.error("useEffect start player", error);
|
||||
}
|
||||
return () => {
|
||||
console.log('hello1002')
|
||||
const video = savedVideoRef as any
|
||||
const videoEl = video?.current!;
|
||||
const player = playerRef.current;
|
||||
|
||||
console.log('videohello', videoEl);
|
||||
const isPlaying = !player?.paused();
|
||||
|
||||
if (videoEl && isPlaying && videoLocationRef.current) {
|
||||
|
@ -140,7 +140,6 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
|
||||
|
||||
const toggleMute = useCallback(() => {
|
||||
try {
|
||||
console.log('toggleMute')
|
||||
const ref = playerRef as any;
|
||||
if (!ref.current) return;
|
||||
|
||||
|
@ -16,8 +16,8 @@ export const GlobalPipPlayer = () => {
|
||||
const [playing , setPlaying] = useState(false)
|
||||
const [hasStarted, setHasStarted] = useState(false)
|
||||
const playerRef = useRef<any>(null);
|
||||
const {navigate} = useContext(GlobalContext)
|
||||
|
||||
const context = useContext(GlobalContext)
|
||||
const navigate = context?.navigate
|
||||
const videoNode = useRef<HTMLVideoElement>(null);
|
||||
const { setProgress } = useProgressStore();
|
||||
|
||||
@ -26,7 +26,6 @@ export const GlobalPipPlayer = () => {
|
||||
if (!player || typeof player?.currentTime !== "function") return;
|
||||
|
||||
const currentTime = player.currentTime();
|
||||
console.log('videoId', videoId)
|
||||
if (typeof currentTime === "number" && videoId && currentTime > 0.1) {
|
||||
setProgress(videoId, currentTime);
|
||||
}
|
||||
|
@ -5,8 +5,9 @@ import { base64ToObject, retryTransaction } from "../utils/publish";
|
||||
import { useGlobal } from "../context/GlobalProvider";
|
||||
import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
|
||||
import { useCacheStore } from "../state/cache";
|
||||
import { useMultiplePublishStore } from "../state/multiplePublish";
|
||||
import { useMultiplePublishStore, usePublishStatusStore } from "../state/multiplePublish";
|
||||
import { ResourceToPublish } from "../types/qortalRequests/types";
|
||||
import { MultiplePublishError } from "../components/MultiPublish/MultiPublishDialog";
|
||||
|
||||
interface StoredPublish {
|
||||
qortalMetadata: QortalMetadata;
|
||||
@ -31,7 +32,7 @@ interface StoredPublish {
|
||||
}>;
|
||||
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
|
||||
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>;
|
||||
publishMultipleResources: (resources: ResourceToPublish[])=> void
|
||||
publishMultipleResources: (resources: ResourceToPublish[])=> Promise<Error | QortalGetMetadata[]>
|
||||
};
|
||||
|
||||
type UsePublishWithoutMetadata = {
|
||||
@ -42,7 +43,7 @@ interface StoredPublish {
|
||||
}>;
|
||||
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
|
||||
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>;
|
||||
publishMultipleResources: (resources: ResourceToPublish[])=> void
|
||||
publishMultipleResources: (resources: ResourceToPublish[])=> Promise<Error | QortalGetMetadata[]>
|
||||
|
||||
};
|
||||
|
||||
@ -77,7 +78,7 @@ interface StoredPublish {
|
||||
const getPublish = usePublishStore(state=> state.getPublish)
|
||||
const setResourceCache = useCacheStore((s) => s.setResourceCache);
|
||||
const markResourceAsDeleted = useCacheStore((s) => s.markResourceAsDeleted);
|
||||
|
||||
const setPublishStatusByKey = usePublishStatusStore((s)=> s.setPublishStatusByKey)
|
||||
const setPublishResources = useMultiplePublishStore((state) => state.setPublishResources);
|
||||
const resetPublishResources = useMultiplePublishStore((state) => state.reset);
|
||||
const [hasResource, setHasResource] = useState<boolean | null>(null);
|
||||
@ -271,19 +272,55 @@ interface StoredPublish {
|
||||
|
||||
}, [getStorageKey, setPublish]);
|
||||
|
||||
const publishMultipleResources = useCallback(async (resources: ResourceToPublish[]) => {
|
||||
try {
|
||||
setPublishResources(resources)
|
||||
const lengthOfResources = resources?.length;
|
||||
const lengthOfTimeout = lengthOfResources * 1200000; // Time out in QR, Seconds = 20 Minutes
|
||||
return await qortalRequestWithTimeout({
|
||||
const publishMultipleResources = useCallback(async (resources: ResourceToPublish[]): Promise<Error | QortalGetMetadata[]> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const store = useMultiplePublishStore.getState();
|
||||
store.setPublishResources(resources);
|
||||
store.setIsPublishing(true);
|
||||
store.setCompletionResolver(resolve);
|
||||
store.setRejectionResolver(reject);
|
||||
try {
|
||||
store.setIsLoading(true);
|
||||
setPublishResources(resources);
|
||||
store.setError(null)
|
||||
store.setFailedPublishResources([])
|
||||
const lengthOfResources = resources?.length;
|
||||
const lengthOfTimeout = lengthOfResources * 1200000; // 20 minutes per resource
|
||||
|
||||
const result = await qortalRequestWithTimeout({
|
||||
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
|
||||
resources
|
||||
}, lengthOfTimeout);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}, [getStorageKey, setPublish]);
|
||||
store.complete(result);
|
||||
} catch (error: any) {
|
||||
const unPublished = error?.error?.unsuccessfulPublishes;
|
||||
const failedPublishes: QortalGetMetadata[] = []
|
||||
if (unPublished && Array.isArray(unPublished)) {
|
||||
unPublished.forEach((item) => {
|
||||
const key = `${item?.service}-${item?.name}-${item?.identifier}`;
|
||||
|
||||
setPublishStatusByKey(key, {
|
||||
error: {
|
||||
reason: item.reason
|
||||
}
|
||||
});
|
||||
failedPublishes.push({
|
||||
name: item?.name,
|
||||
service: item?.service,
|
||||
identifier: item?.identifier
|
||||
})
|
||||
});
|
||||
store.setFailedPublishResources(failedPublishes)
|
||||
} else {
|
||||
store.setError(error?.message || 'Error during publish')
|
||||
}
|
||||
|
||||
} finally {
|
||||
store.setIsLoading(false);
|
||||
}
|
||||
})
|
||||
}, [setPublishResources]);
|
||||
|
||||
|
||||
if (!metadata)
|
||||
return {
|
||||
|
@ -207,10 +207,8 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
|
||||
if (cancelRequests) {
|
||||
cancelAllRequests();
|
||||
}
|
||||
console.log('listName', listName)
|
||||
const cacheKey = generateCacheKey(params);
|
||||
const searchCache = getSearchCache(listName, cacheKey);
|
||||
console.log('searchCache', searchCache)
|
||||
if (searchCache) {
|
||||
const copyParams = {...params}
|
||||
delete copyParams.after
|
||||
@ -223,12 +221,10 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
|
||||
let responseData: QortalMetadata[] = [];
|
||||
let filteredResults: QortalMetadata[] = [];
|
||||
let lastCreated = params.before || undefined;
|
||||
console.log('lastCreated', lastCreated)
|
||||
const targetLimit = params.limit ?? 20; // Use `params.limit` if provided, else default to 20
|
||||
const isUnlimited = params.limit === 0;
|
||||
|
||||
while (isUnlimited || filteredResults.length < targetLimit) {
|
||||
console.log('beforebefore')
|
||||
const response = await qortalRequest({
|
||||
action: "SEARCH_QDN_RESOURCES",
|
||||
mode: "ALL",
|
||||
@ -236,14 +232,12 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
|
||||
limit: targetLimit - filteredResults.length, // Adjust limit dynamically
|
||||
before: lastCreated,
|
||||
});
|
||||
console.log('responseresponse', response)
|
||||
if (!response || response.length === 0) {
|
||||
break; // No more data available
|
||||
}
|
||||
|
||||
responseData = response;
|
||||
const validResults = responseData.filter((item) => item.size !== 32 && item.size < maxSize);
|
||||
console.log('validResults', validResults)
|
||||
filteredResults = [...filteredResults, ...validResults];
|
||||
|
||||
if (filteredResults.length >= targetLimit && !isUnlimited) {
|
||||
@ -312,7 +306,6 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
|
||||
|
||||
const addNewResources = useCallback(
|
||||
(listName: string, resources: Resource[]) => {
|
||||
console.log('resources1212', resources)
|
||||
addTemporaryResource(
|
||||
listName,
|
||||
resources.map((item) => item.qortalMetadata)
|
||||
|
@ -1,23 +1,75 @@
|
||||
import { create } from 'zustand';
|
||||
import { ResourceToPublish } from '../types/qortalRequests/types';
|
||||
import { Service } from '../types/interfaces/resources';
|
||||
import { QortalGetMetadata, Service } from '../types/interfaces/resources';
|
||||
|
||||
|
||||
interface MultiplePublishState {
|
||||
resources: ResourceToPublish[];
|
||||
setPublishResources: (resources: ResourceToPublish[])=> void
|
||||
reset: ()=> void
|
||||
isPublishing: boolean
|
||||
}
|
||||
const initialState = {
|
||||
resources: [],
|
||||
isPublishing: false
|
||||
};
|
||||
export const useMultiplePublishStore = create<MultiplePublishState>((set) => ({
|
||||
...initialState,
|
||||
setPublishResources: (resources: ResourceToPublish[]) => set(() => ({ resources, isPublishing: true })),
|
||||
reset: () => set(initialState),
|
||||
failedResources: QortalGetMetadata[];
|
||||
isPublishing: boolean;
|
||||
resolveCallback?: (result: QortalGetMetadata[]) => void;
|
||||
rejectCallback?: (error: Error) => void;
|
||||
|
||||
setPublishResources: (resources: ResourceToPublish[]) => void;
|
||||
setFailedPublishResources: (resources: QortalGetMetadata[]) => void;
|
||||
setIsPublishing: (value: boolean) => void;
|
||||
setCompletionResolver: (resolver: (result: QortalGetMetadata[]) => void) => void;
|
||||
setRejectionResolver: (resolver: (reject: Error) => void) => void;
|
||||
complete: (result: any) => void;
|
||||
reject: (Error: Error) => void;
|
||||
reset: () => void;
|
||||
setError: (message: string | null)=> void
|
||||
error: string | null
|
||||
isLoading: boolean
|
||||
setIsLoading: (val: boolean)=> void
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
resources: [],
|
||||
failedResources: [],
|
||||
isPublishing: false,
|
||||
resolveCallback: undefined,
|
||||
rejectCallback: undefined,
|
||||
error: "",
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
export const useMultiplePublishStore = create<MultiplePublishState>((set, get) => ({
|
||||
...initialState,
|
||||
|
||||
setPublishResources: (resources) => {
|
||||
set({ resources, isPublishing: true });
|
||||
},
|
||||
setFailedPublishResources: (resources) => {
|
||||
set({ failedResources: resources });
|
||||
},
|
||||
setIsPublishing: (value) => {
|
||||
set({ isPublishing: value });
|
||||
},
|
||||
setIsLoading: (value) => {
|
||||
set({ isLoading: value });
|
||||
},
|
||||
setCompletionResolver: (resolver) => {
|
||||
set({ resolveCallback: resolver });
|
||||
},
|
||||
setRejectionResolver: (reject) => {
|
||||
set({ rejectCallback: reject });
|
||||
},
|
||||
complete: (result) => {
|
||||
const resolver = get().resolveCallback;
|
||||
if (resolver) resolver(result);
|
||||
set({ resolveCallback: undefined, isPublishing: false });
|
||||
},
|
||||
reject: (result) => {
|
||||
const resolver = get().rejectCallback;
|
||||
if (resolver) resolver(result);
|
||||
set({ resolveCallback: undefined, isPublishing: false });
|
||||
},
|
||||
setError: (message) => {
|
||||
set({ error: message });
|
||||
},
|
||||
|
||||
reset: () => set(initialState),
|
||||
}));
|
||||
|
||||
export type PublishLocation = {
|
||||
@ -31,6 +83,11 @@ export type PublishStatus = {
|
||||
chunks: number;
|
||||
totalChunks: number;
|
||||
processed: boolean;
|
||||
error?: {
|
||||
reason: string
|
||||
}
|
||||
retry: boolean
|
||||
filename: string
|
||||
};
|
||||
|
||||
type PublishStatusStore = {
|
||||
@ -58,6 +115,7 @@ export const usePublishStatusStore = create<PublishStatusStore>((set, get) => ({
|
||||
chunks: 0,
|
||||
totalChunks: 0,
|
||||
processed: false,
|
||||
retry: false
|
||||
};
|
||||
|
||||
const newStatus: PublishStatus = {
|
||||
|
@ -121,7 +121,6 @@ export function base64ToObject(base64: string){
|
||||
}
|
||||
|
||||
export const base64ToBlobUrl = (base64: string, mimeType = 'text/vtt'): string => {
|
||||
console.log('base64ToBlobUrl', base64, mimeType)
|
||||
const cleanedBase64 = base64.length % 4 === 0 ? base64 : base64 + '='.repeat(4 - base64.length % 4);
|
||||
|
||||
try {
|
||||
|
@ -9,7 +9,6 @@ export default defineConfig({
|
||||
'@mui/material',
|
||||
'@mui/system',
|
||||
'@emotion/react',
|
||||
'@emotion/styled',
|
||||
'mediainfo.js'
|
||||
'@emotion/styled'
|
||||
],
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user