added qortal multi publish status

This commit is contained in:
PhilReact 2025-06-20 20:18:32 +03:00
parent 979c6d4265
commit 378c80cf3c
12 changed files with 393 additions and 260 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "qapp-core", "name": "qapp-core",
"version": "1.0.31", "version": "1.0.32",
"description": "Qortal's core React library with global state, UI components, and utilities", "description": "Qortal's core React library with global state, UI components, and utilities",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.mjs", "module": "dist/index.mjs",
@ -49,7 +49,6 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.0.1", "@mui/icons-material": "^7.0.1",
"@mui/material": "^7.0.1", "@mui/material": "^7.0.1",
"mediainfo.js": "^0.3.5",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-router-dom": "^7.6.2" "react-router-dom": "^7.6.2"

View File

@ -13,157 +13,307 @@ import {
Typography, Typography,
Box, Box,
LinearProgress, LinearProgress,
Stack Stack,
DialogActions,
Button
} from '@mui/material'; } from '@mui/material';
import { PublishStatus, useMultiplePublishStore, usePublishStatusStore } from "../../state/multiplePublish"; import { PublishStatus, useMultiplePublishStore, usePublishStatusStore } from "../../state/multiplePublish";
import { ResourceToPublish } from "../../types/qortalRequests/types"; 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';
export interface MultiplePublishError {
error: {
unsuccessfulPublishes: any[]
}
}
export const MultiPublishDialogComponent = () => {
const {
const MultiPublishDialogComponent = () => { resources,
const { resources, isPublishing } = useMultiplePublishStore((state) => ({ isPublishing,
failedResources,
reset,
reject,
error: publishError,
isLoading,
setIsLoading,
setError,
setFailedPublishResources,
complete
} = useMultiplePublishStore((state) => ({
resources: state.resources, resources: state.resources,
isPublishing: state.isPublishing 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
})); }));
const { publishStatus, setPublishStatusByKey, getPublishStatusByKey } = usePublishStatusStore();
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;
useEffect(() => {
function handleNavigation(event: any) {
if (event.data?.action === 'PUBLISH_STATUS') {
const data = event.data; const data = event.data;
console.log('datadata', data)
// Validate required structure before continuing
if ( if (
!data.publishLocation || !data.publishLocation ||
typeof data.publishLocation.name !== 'string' || typeof data.publishLocation?.name !== 'string' ||
typeof data.publishLocation.identifier !== 'string' || typeof data.publishLocation?.service !== 'string'
typeof data.publishLocation.service !== 'string'
) { ) {
console.warn('Invalid PUBLISH_STATUS data, skipping:', data); console.warn('Invalid PUBLISH_STATUS data, skipping:', data);
return; return;
} }
const { publishLocation, chunks, totalChunks } = data; const {
publishLocation,
chunks,
totalChunks,
retry,
filename,
processed
} = data;
const key = `${publishLocation?.service}-${publishLocation?.name}-${publishLocation?.identifier}`; const key = `${publishLocation?.service}-${publishLocation?.name}-${publishLocation?.identifier}`;
try { const update: any = {
const dataToBeSent: any = {};
if (chunks !== undefined && chunks !== null) {
dataToBeSent.chunks = chunks;
}
if (totalChunks !== undefined && totalChunks !== null) {
dataToBeSent.totalChunks = totalChunks;
}
setPublishStatusByKey(key, {
publishLocation, publishLocation,
...dataToBeSent, processed: processed || false
processed: data?.processed || false };
}); 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) { } catch (err) {
console.error('Failed to set publish status:', err); console.error('Failed to set publish status:', err);
} }
} }, [setPublishStatusByKey]);
}
useEffect(() => {
window.addEventListener("message", handleNavigation); window.addEventListener("message", handleNavigation);
return () => window.removeEventListener("message", handleNavigation);
}, [handleNavigation]);
if (!isPublishing) return null;
return () => {
window.removeEventListener("message", handleNavigation);
};
}, []);
if(!isPublishing) return null
return ( return (
<Dialog open={isPublishing} fullWidth maxWidth="sm"> <Dialog
open={isPublishing}
fullWidth
maxWidth="sm"
sx={{ zIndex: 999990 }}
slotProps={{ paper: { elevation: 0 } }}
>
<DialogTitle>Publishing Status</DialogTitle> <DialogTitle>Publishing Status</DialogTitle>
<DialogContent> <DialogContent>
{publishError && (
<Stack spacing={3}> <Stack spacing={3}>
{resources.map((publish: ResourceToPublish) => { <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 key = `${publish?.service}-${publish?.name}-${publish?.identifier}`;
const individualPublishStatus = publishStatus[key] || null const individualPublishStatus = publishStatus[key] || null;
return ( return (
<IndividualResourceComponent key={key} publishKey={key} publish={publish} publishStatus={individualPublishStatus} /> <IndividualResourceComponent
key={key}
publishKey={key}
publish={publish}
publishStatus={individualPublishStatus}
/>
); );
})} })}
</Stack> </Stack>
)}
</DialogContent> </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> </Dialog>
); );
}; };
interface IndividualResourceComponentProps { interface IndividualResourceComponentProps {
publish: ResourceToPublish publish: ResourceToPublish
publishStatus: PublishStatus publishStatus: PublishStatus
publishKey: string publishKey: string
} }
const ESTIMATED_PROCESSING_MS = 5 * 60 * 1000; // 5 minutes
const IndividualResourceComponent = ({ publish, publishKey, publishStatus }: IndividualResourceComponentProps) => { const IndividualResourceComponent = ({ publish, publishKey, publishStatus }: IndividualResourceComponentProps) => {
const [now, setNow] = useState(Date.now()); const [now, setNow] = useState(Date.now());
console.log('key500',publishKey, publishStatus)
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])
useEffect(() => {
if(!chunkDone) return
const interval = setInterval(() => {
setNow(Date.now());
}, 1000);
return () => clearInterval(interval);
}, [chunkDone]);
const [processingStart, setProcessingStart] = useState<number | undefined>(); const [processingStart, setProcessingStart] = useState<number | undefined>();
const chunkPercent = useMemo(() => {
if (!publishStatus?.chunks || !publishStatus?.totalChunks) return 0;
return (publishStatus.chunks / publishStatus.totalChunks) * 100;
}, [publishStatus?.chunks, publishStatus?.totalChunks]);
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(() => { useEffect(() => {
if (chunkDone && !processingStart) { if (chunkDone && !processingStart) {
setProcessingStart(Date.now()); setProcessingStart(Date.now());
} }
}, [chunkDone, processingStart]); }, [chunkDone, processingStart]);
const processingPercent = useMemo(() => { // Keep time ticking for progress simulation
if (!chunkDone || !processingStart || !publishStatus?.totalChunks || !now) return 0; useEffect(() => {
if (!chunkDone) return;
const interval = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(interval);
}, [chunkDone]);
const totalMB = publishStatus.totalChunks * 5; const processingPercent = useMemo(() => {
const estimatedProcessingMs = (300_000 / 2048) * totalMB; if (publishStatus?.error || !chunkDone || !processingStart || !publishStatus?.totalChunks || !now) return 0;
const totalMB = publishStatus.totalChunks * 5; // assume 5MB per chunk
const estimatedProcessingMs = (300_000 / 2048) * totalMB; // 5min per 2GB scaled
const elapsed = now - processingStart; const elapsed = now - processingStart;
if(!elapsed || elapsed < 0) return 0 if (elapsed <= 0) return 0;
return Math.min((elapsed / estimatedProcessingMs) * 100, 100); return Math.min((elapsed / estimatedProcessingMs) * 100, 100);
}, [chunkDone, processingStart, now, publishStatus?.totalChunks]); }, [chunkDone, processingStart, now, publishStatus?.totalChunks, publishStatus?.error]);
return ( return (
<Box p={1} border={1} borderColor="divider" borderRadius={2}> <Box p={1} border={1} borderColor="divider" borderRadius={2}>
<Typography variant="subtitle1" fontWeight="bold"> <Typography variant="subtitle1" fontWeight="bold">
{publishKey} {publish?.filename || publishStatus?.filename || publishKey}
</Typography> </Typography>
<Box mt={2}> <Box mt={2}>
<Typography variant="body2" gutterBottom> <Typography variant="body2" gutterBottom>
File Chunk { (!publishStatus?.chunks || !publishStatus?.totalChunks) ? '' : publishStatus?.chunks}/{publishStatus?.totalChunks} ({(chunkPercent).toFixed(0)}%) File Chunk {publishStatus?.chunks || 0}/{publishStatus?.totalChunks || 0} ({chunkPercent.toFixed(0)}%)
</Typography> </Typography>
<LinearProgress variant="determinate" value={chunkPercent} /> <LinearProgress variant="determinate" value={chunkPercent} />
</Box> </Box>
<Box mt={2}> <Box mt={2}>
<Typography variant="body2" gutterBottom> <Typography variant="body2" gutterBottom>
File Processing ({(!processingPercent || !processingStart) ? '0' : processingPercent.toFixed(0)}%) File Processing ({processingStart ? processingPercent.toFixed(0) : '0'}%)
</Typography> </Typography>
<LinearProgress variant="determinate" value={processingPercent} /> <LinearProgress variant="determinate" value={processingPercent} />
</Box> </Box>
{publishStatus?.processed && (
<Box mt={2} display="flex" gap={1} alignItems="center">
<CheckCircleIcon color="success" />
<Typography variant="body2">Published successfully</Typography>
</Box> </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>
);
};
export const MultiPublishDialog = React.memo(MultiPublishDialogComponent); export const MultiPublishDialog = React.memo(MultiPublishDialogComponent);

View File

@ -119,17 +119,14 @@ const SubtitleManagerComponent = ({
const subtitles = useListReturn( const subtitles = useListReturn(
`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}` `subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`
); );
console.log("subtitles222", subtitles);
const mySubtitles = useMemo(() => { const mySubtitles = useMemo(() => {
if (!auth?.name) return []; if (!auth?.name) return [];
return subtitles?.filter((sub) => sub.name === auth?.name); return subtitles?.filter((sub) => sub.name === auth?.name);
}, [subtitles, auth?.name]); }, [subtitles, auth?.name]);
console.log("subtitles222", subtitles);
const getPublishedSubtitles = useCallback(async () => { const getPublishedSubtitles = useCallback(async () => {
try { try {
setIsLoading(true); setIsLoading(true);
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`; const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
console.log("videoId", videoId);
const postIdSearch = await identifierOperations.buildLooseSearchPrefix( const postIdSearch = await identifierOperations.buildLooseSearchPrefix(
ENTITY_SUBTITLE, ENTITY_SUBTITLE,
videoId videoId
@ -149,7 +146,6 @@ const SubtitleManagerComponent = ({
searchParams searchParams
); );
lists.addList(`subs-${videoId}`, res?.filter((item)=> !!item?.metadata?.title) || []); lists.addList(`subs-${videoId}`, res?.filter((item)=> !!item?.metadata?.title) || []);
console.log("resres2", res);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally { } finally {
@ -174,7 +170,6 @@ const SubtitleManagerComponent = ({
]); ]);
const ref = useRef<any>(null) const ref = useRef<any>(null)
console.log('isOpen', open)
useEffect(()=> { useEffect(()=> {
if(open){ if(open){
@ -184,7 +179,6 @@ const SubtitleManagerComponent = ({
const handleBlur = (e: React.FocusEvent) => { const handleBlur = (e: React.FocusEvent) => {
if (!e.currentTarget.contains(e.relatedTarget) && !isOpenPublish) { if (!e.currentTarget.contains(e.relatedTarget) && !isOpenPublish) {
console.log('handleBlur')
close(); close();
setIsOpenPublish(false) setIsOpenPublish(false)
} }
@ -235,7 +229,6 @@ const SubtitleManagerComponent = ({
data: data, data: data,
}); });
} }
console.log("resources", resources);
await qortalRequest({ await qortalRequest({
action: "PUBLISH_MULTIPLE_QDN_RESOURCES", action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
@ -253,7 +246,6 @@ const SubtitleManagerComponent = ({
}; };
const onSelectHandler = (sub: SubtitlePublishedData) => { const onSelectHandler = (sub: SubtitlePublishedData) => {
console.log("onSelectHandler");
onSelect(sub); onSelect(sub);
close(); close();
}; };
@ -608,7 +600,6 @@ const PublishSubtitles = ({
return copyPrev; return copyPrev;
}); });
}; };
console.log("subtitles", subtitles);
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
@ -843,7 +834,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
const [selectedToDownload, setSelectedToDownload] = useState<null | QortalGetMetadata>(null) const [selectedToDownload, setSelectedToDownload] = useState<null | QortalGetMetadata>(null)
const [isReady, setIsReady] = useState(false) const [isReady, setIsReady] = useState(false)
const { resource, isLoading, error, refetch } = usePublish(2, "JSON", sub, true); const { resource, isLoading, error, refetch } = usePublish(2, "JSON", sub, true);
console.log("resource", resource, isLoading);
const isSelected = currentSubtrack === resource?.data?.language; const isSelected = currentSubtrack === resource?.data?.language;
const [isGettingStatus, setIsGettingStatus] = useState(true) const [isGettingStatus, setIsGettingStatus] = useState(true)
// useEffect(()=> { // useEffect(()=> {
@ -852,7 +842,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
// onSelect(resource?.data) // onSelect(resource?.data)
// } // }
// }, [isSelected, resource?.data]) // }, [isSelected, resource?.data])
console.log('isReady', resource?.data)
const getStatus = useCallback(async (service: Service, name: string, identifier: string)=> { const getStatus = useCallback(async (service: Service, name: string, identifier: string)=> {
try { try {
if(subtitlesStatus[`${service}-${name}-${identifier}`]){ if(subtitlesStatus[`${service}-${name}-${identifier}`]){
@ -890,7 +879,6 @@ const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
getStatus(sub?.service, sub?.name, sub?.identifier) getStatus(sub?.service, sub?.name, sub?.identifier)
} }
}, [sub?.identifier, sub?.name, sub?.service]) }, [sub?.identifier, sub?.name, sub?.service])
console.log('tester', isReady,isLoading,error,resource?.data)
return ( return (
<ButtonBase <ButtonBase

View File

@ -92,7 +92,6 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
const x = e.clientX - rect.left; const x = e.clientX - rect.left;
const percent = x / rect.width; const percent = x / rect.width;
const time = Math.min(Math.max(0, percent * duration), duration); const time = Math.min(Math.max(0, percent * duration), duration);
console.log("hello100");
setHoverX(e.clientX); setHoverX(e.clientX);
setShowDuration(time); setShowDuration(time);
@ -129,7 +128,6 @@ export const ProgressSlider = ({ progress, duration, playerRef }: any) => {
console.log("thumbnailUrl", thumbnailUrl, hoverX); console.log("thumbnailUrl", thumbnailUrl, hoverX);
} }
console.log("duration", duration);
return ( return (
<Box <Box
@ -445,7 +443,6 @@ interface PlayBackMenuProps {
export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate}: PlayBackMenuProps)=> { export const PlayBackMenu = ({close, onSelect, isOpen, playbackRate}: PlayBackMenuProps)=> {
const theme = useTheme() const theme = useTheme()
const ref = useRef<any>(null) const ref = useRef<any>(null)
console.log('isOpen', isOpen)
useEffect(()=> { useEffect(()=> {
if(isOpen){ if(isOpen){

View File

@ -47,7 +47,6 @@ export async function srtBase64ToVttBlobUrl(
// Step 2: Create a Blob from the Uint8Array with correct MIME type // Step 2: Create a Blob from the Uint8Array with correct MIME type
const srtBlob = new Blob([bytes], { type: "application/x-subrip" }); const srtBlob = new Blob([bytes], { type: "application/x-subrip" });
console.log("srtBlob", srtBlob);
// Step 3: Use convert() with the Blob // Step 3: Use convert() with the Blob
const vttBlobUrl: string = await convert(srtBlob); const vttBlobUrl: string = await convert(srtBlob);
return vttBlobUrl; return vttBlobUrl;
@ -93,14 +92,7 @@ const videoStyles = {
video: {}, 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( async function getVideoMimeTypeFromUrl(
qortalVideoResource: any qortalVideoResource: any
@ -114,71 +106,6 @@ async function getVideoMimeTypeFromUrl(
} catch (error) { } catch (error) {
return null; 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 = ({ export const VideoPlayer = ({
@ -259,7 +186,6 @@ export const VideoPlayer = ({
} }
},[location]) },[location])
console.log('isFullscreen', isFullscreen)
const { getProgress } = useProgressStore(); const { getProgress } = useProgressStore();
const enterFullscreen = useCallback(() => { const enterFullscreen = useCallback(() => {
@ -276,7 +202,6 @@ export const VideoPlayer = ({
}, [isFullscreen]); }, [isFullscreen]);
const toggleFullscreen = useCallback(() => { const toggleFullscreen = useCallback(() => {
console.log('togglefull', isFullscreen)
isFullscreen ? exitFullscreen() : enterFullscreen(); isFullscreen ? exitFullscreen() : enterFullscreen();
}, [isFullscreen]); }, [isFullscreen]);
@ -531,7 +456,6 @@ const videoLocationRef = useRef< null | string>(null)
return return
} }
console.log("onSelectSubtitle", subtitle);
const player = playerRef.current; const player = playerRef.current;
if (!player || !subtitle.subtitleData || !subtitle.type) return; if (!player || !subtitle.subtitleData || !subtitle.type) return;
@ -604,16 +528,13 @@ const videoLocationRef = useRef< null | string>(null)
}, 1000); }, 1000);
}); });
const tracksInfo = playerRef.current?.textTracks(); const tracksInfo = playerRef.current?.textTracks();
console.log("tracksInfo", tracksInfo);
if (!tracksInfo) return; if (!tracksInfo) return;
const tracks = Array.from( const tracks = Array.from(
{ length: (tracksInfo as any).length }, { length: (tracksInfo as any).length },
(_, i) => (tracksInfo as any)[i] (_, i) => (tracksInfo as any)[i]
); );
console.log("tracks", tracks);
for (const track of tracks) { for (const track of tracks) {
console.log("track", track);
if (track.kind === "subtitles") { if (track.kind === "subtitles") {
track.mode = "showing"; // force display track.mode = "showing"; // force display
@ -706,7 +627,6 @@ savedVideoRef.current = video.current;
{ length: (tracksInfo as any).length }, { length: (tracksInfo as any).length },
(_, i) => (tracksInfo as any)[i] (_, i) => (tracksInfo as any)[i]
); );
console.log("tracks", tracks);
for (const track of tracks) { for (const track of tracks) {
if (track.kind === 'subtitles' || track.kind === 'captions') { if (track.kind === 'subtitles' || track.kind === 'captions') {
@ -718,10 +638,7 @@ savedVideoRef.current = video.current;
} }
if (activeTrack) { 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) setCurrentSubTrack(activeTrack.language || activeTrack.srclang)
} else { } else {
setCurrentSubTrack(null) setCurrentSubTrack(null)
@ -748,12 +665,10 @@ savedVideoRef.current = video.current;
console.error("useEffect start player", error); console.error("useEffect start player", error);
} }
return () => { return () => {
console.log('hello1002')
const video = savedVideoRef as any const video = savedVideoRef as any
const videoEl = video?.current!; const videoEl = video?.current!;
const player = playerRef.current; const player = playerRef.current;
console.log('videohello', videoEl);
const isPlaying = !player?.paused(); const isPlaying = !player?.paused();
if (videoEl && isPlaying && videoLocationRef.current) { if (videoEl && isPlaying && videoLocationRef.current) {

View File

@ -140,7 +140,6 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
const toggleMute = useCallback(() => { const toggleMute = useCallback(() => {
try { try {
console.log('toggleMute')
const ref = playerRef as any; const ref = playerRef as any;
if (!ref.current) return; if (!ref.current) return;

View File

@ -16,8 +16,8 @@ export const GlobalPipPlayer = () => {
const [playing , setPlaying] = useState(false) const [playing , setPlaying] = useState(false)
const [hasStarted, setHasStarted] = useState(false) const [hasStarted, setHasStarted] = useState(false)
const playerRef = useRef<any>(null); const playerRef = useRef<any>(null);
const {navigate} = useContext(GlobalContext) const context = useContext(GlobalContext)
const navigate = context?.navigate
const videoNode = useRef<HTMLVideoElement>(null); const videoNode = useRef<HTMLVideoElement>(null);
const { setProgress } = useProgressStore(); const { setProgress } = useProgressStore();
@ -26,7 +26,6 @@ export const GlobalPipPlayer = () => {
if (!player || typeof player?.currentTime !== "function") return; if (!player || typeof player?.currentTime !== "function") return;
const currentTime = player.currentTime(); const currentTime = player.currentTime();
console.log('videoId', videoId)
if (typeof currentTime === "number" && videoId && currentTime > 0.1) { if (typeof currentTime === "number" && videoId && currentTime > 0.1) {
setProgress(videoId, currentTime); setProgress(videoId, currentTime);
} }

View File

@ -5,8 +5,9 @@ import { base64ToObject, retryTransaction } from "../utils/publish";
import { useGlobal } from "../context/GlobalProvider"; import { useGlobal } from "../context/GlobalProvider";
import { ReturnType } from "../components/ResourceList/ResourceListDisplay"; import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
import { useCacheStore } from "../state/cache"; import { useCacheStore } from "../state/cache";
import { useMultiplePublishStore } from "../state/multiplePublish"; import { useMultiplePublishStore, usePublishStatusStore } from "../state/multiplePublish";
import { ResourceToPublish } from "../types/qortalRequests/types"; import { ResourceToPublish } from "../types/qortalRequests/types";
import { MultiplePublishError } from "../components/MultiPublish/MultiPublishDialog";
interface StoredPublish { interface StoredPublish {
qortalMetadata: QortalMetadata; qortalMetadata: QortalMetadata;
@ -31,7 +32,7 @@ interface StoredPublish {
}>; }>;
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>; updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>; deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>;
publishMultipleResources: (resources: ResourceToPublish[])=> void publishMultipleResources: (resources: ResourceToPublish[])=> Promise<Error | QortalGetMetadata[]>
}; };
type UsePublishWithoutMetadata = { type UsePublishWithoutMetadata = {
@ -42,7 +43,7 @@ interface StoredPublish {
}>; }>;
updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>; updatePublish: (publish: QortalGetMetadata, data: any) => Promise<void>;
deletePublish: (publish: QortalGetMetadata) => Promise<boolean | undefined>; 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 getPublish = usePublishStore(state=> state.getPublish)
const setResourceCache = useCacheStore((s) => s.setResourceCache); const setResourceCache = useCacheStore((s) => s.setResourceCache);
const markResourceAsDeleted = useCacheStore((s) => s.markResourceAsDeleted); const markResourceAsDeleted = useCacheStore((s) => s.markResourceAsDeleted);
const setPublishStatusByKey = usePublishStatusStore((s)=> s.setPublishStatusByKey)
const setPublishResources = useMultiplePublishStore((state) => state.setPublishResources); const setPublishResources = useMultiplePublishStore((state) => state.setPublishResources);
const resetPublishResources = useMultiplePublishStore((state) => state.reset); const resetPublishResources = useMultiplePublishStore((state) => state.reset);
const [hasResource, setHasResource] = useState<boolean | null>(null); const [hasResource, setHasResource] = useState<boolean | null>(null);
@ -271,19 +272,55 @@ interface StoredPublish {
}, [getStorageKey, setPublish]); }, [getStorageKey, setPublish]);
const publishMultipleResources = useCallback(async (resources: ResourceToPublish[]) => { 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 { try {
setPublishResources(resources) store.setIsLoading(true);
setPublishResources(resources);
store.setError(null)
store.setFailedPublishResources([])
const lengthOfResources = resources?.length; const lengthOfResources = resources?.length;
const lengthOfTimeout = lengthOfResources * 1200000; // Time out in QR, Seconds = 20 Minutes const lengthOfTimeout = lengthOfResources * 1200000; // 20 minutes per resource
return await qortalRequestWithTimeout({
const result = await qortalRequestWithTimeout({
action: "PUBLISH_MULTIPLE_QDN_RESOURCES", action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
resources resources
}, lengthOfTimeout); }, lengthOfTimeout);
} catch (error) { 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
} }
}, [getStorageKey, setPublish]); });
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) if (!metadata)
return { return {

View File

@ -207,10 +207,8 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
if (cancelRequests) { if (cancelRequests) {
cancelAllRequests(); cancelAllRequests();
} }
console.log('listName', listName)
const cacheKey = generateCacheKey(params); const cacheKey = generateCacheKey(params);
const searchCache = getSearchCache(listName, cacheKey); const searchCache = getSearchCache(listName, cacheKey);
console.log('searchCache', searchCache)
if (searchCache) { if (searchCache) {
const copyParams = {...params} const copyParams = {...params}
delete copyParams.after delete copyParams.after
@ -223,12 +221,10 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
let responseData: QortalMetadata[] = []; let responseData: QortalMetadata[] = [];
let filteredResults: QortalMetadata[] = []; let filteredResults: QortalMetadata[] = [];
let lastCreated = params.before || undefined; let lastCreated = params.before || undefined;
console.log('lastCreated', lastCreated)
const targetLimit = params.limit ?? 20; // Use `params.limit` if provided, else default to 20 const targetLimit = params.limit ?? 20; // Use `params.limit` if provided, else default to 20
const isUnlimited = params.limit === 0; const isUnlimited = params.limit === 0;
while (isUnlimited || filteredResults.length < targetLimit) { while (isUnlimited || filteredResults.length < targetLimit) {
console.log('beforebefore')
const response = await qortalRequest({ const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES", action: "SEARCH_QDN_RESOURCES",
mode: "ALL", mode: "ALL",
@ -236,14 +232,12 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
limit: targetLimit - filteredResults.length, // Adjust limit dynamically limit: targetLimit - filteredResults.length, // Adjust limit dynamically
before: lastCreated, before: lastCreated,
}); });
console.log('responseresponse', response)
if (!response || response.length === 0) { if (!response || response.length === 0) {
break; // No more data available break; // No more data available
} }
responseData = response; responseData = response;
const validResults = responseData.filter((item) => item.size !== 32 && item.size < maxSize); const validResults = responseData.filter((item) => item.size !== 32 && item.size < maxSize);
console.log('validResults', validResults)
filteredResults = [...filteredResults, ...validResults]; filteredResults = [...filteredResults, ...validResults];
if (filteredResults.length >= targetLimit && !isUnlimited) { if (filteredResults.length >= targetLimit && !isUnlimited) {
@ -312,7 +306,6 @@ export const useResources = (retryAttempts: number = 2, maxSize = 5242880) => {
const addNewResources = useCallback( const addNewResources = useCallback(
(listName: string, resources: Resource[]) => { (listName: string, resources: Resource[]) => {
console.log('resources1212', resources)
addTemporaryResource( addTemporaryResource(
listName, listName,
resources.map((item) => item.qortalMetadata) resources.map((item) => item.qortalMetadata)

View File

@ -1,23 +1,75 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { ResourceToPublish } from '../types/qortalRequests/types'; import { ResourceToPublish } from '../types/qortalRequests/types';
import { Service } from '../types/interfaces/resources'; import { QortalGetMetadata, Service } from '../types/interfaces/resources';
interface MultiplePublishState { interface MultiplePublishState {
resources: ResourceToPublish[]; resources: ResourceToPublish[];
setPublishResources: (resources: ResourceToPublish[])=> void failedResources: QortalGetMetadata[];
reset: ()=> void isPublishing: boolean;
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 = { const initialState = {
resources: [], resources: [],
isPublishing: false failedResources: [],
isPublishing: false,
resolveCallback: undefined,
rejectCallback: undefined,
error: "",
isLoading: false
}; };
export const useMultiplePublishStore = create<MultiplePublishState>((set) => ({
...initialState,
setPublishResources: (resources: ResourceToPublish[]) => set(() => ({ resources, isPublishing: true })),
reset: () => set(initialState),
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 = { export type PublishLocation = {
@ -31,6 +83,11 @@ export type PublishStatus = {
chunks: number; chunks: number;
totalChunks: number; totalChunks: number;
processed: boolean; processed: boolean;
error?: {
reason: string
}
retry: boolean
filename: string
}; };
type PublishStatusStore = { type PublishStatusStore = {
@ -58,6 +115,7 @@ export const usePublishStatusStore = create<PublishStatusStore>((set, get) => ({
chunks: 0, chunks: 0,
totalChunks: 0, totalChunks: 0,
processed: false, processed: false,
retry: false
}; };
const newStatus: PublishStatus = { const newStatus: PublishStatus = {

View File

@ -121,7 +121,6 @@ export function base64ToObject(base64: string){
} }
export const base64ToBlobUrl = (base64: string, mimeType = 'text/vtt'): 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); const cleanedBase64 = base64.length % 4 === 0 ? base64 : base64 + '='.repeat(4 - base64.length % 4);
try { try {

View File

@ -9,7 +9,6 @@ export default defineConfig({
'@mui/material', '@mui/material',
'@mui/system', '@mui/system',
'@emotion/react', '@emotion/react',
'@emotion/styled', '@emotion/styled'
'mediainfo.js'
], ],
}); });