2025-02-07 00:35:38 +02:00
|
|
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
|
|
import { toast } from "sonner";
|
|
|
|
import { WebSocketMessage } from "@/lib/types/websocket";
|
2025-01-21 17:08:23 +02:00
|
|
|
|
2025-02-07 00:35:38 +02:00
|
|
|
interface PreviewRendererConfig {
|
|
|
|
onMessage?: (data: any) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const usePreviewRenderer = (
|
|
|
|
websocket: WebSocket | null,
|
|
|
|
config?: PreviewRendererConfig
|
|
|
|
) => {
|
2025-01-21 17:08:23 +02:00
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
|
const [isPreviewAvailable, setIsPreviewAvailable] = useState(false);
|
2025-02-07 00:35:38 +02:00
|
|
|
const websocketRef = useRef<WebSocket | null>(websocket);
|
|
|
|
const onMessageCallback = useRef(config?.onMessage);
|
2025-01-21 17:08:23 +02:00
|
|
|
|
2025-02-07 00:35:38 +02:00
|
|
|
// Update refs when dependencies change
|
|
|
|
useEffect(() => {
|
|
|
|
websocketRef.current = websocket;
|
|
|
|
if (!websocket) {
|
|
|
|
setIsPlaying(false);
|
|
|
|
setIsPreviewAvailable(false);
|
2025-01-21 17:08:23 +02:00
|
|
|
}
|
|
|
|
}, [websocket]);
|
|
|
|
|
2025-02-07 00:35:38 +02:00
|
|
|
useEffect(() => {
|
|
|
|
onMessageCallback.current = config?.onMessage;
|
|
|
|
}, [config]);
|
|
|
|
|
|
|
|
const checkConnection = useCallback(() => {
|
|
|
|
if (
|
|
|
|
!websocketRef.current ||
|
|
|
|
websocketRef.current.readyState !== WebSocket.OPEN
|
|
|
|
) {
|
|
|
|
toast.error("WebSocket connection not available");
|
|
|
|
return false;
|
2025-01-21 17:08:23 +02:00
|
|
|
}
|
2025-02-07 00:35:38 +02:00
|
|
|
return true;
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const sendMessage = useCallback(
|
|
|
|
(message: WebSocketMessage) => {
|
|
|
|
if (!checkConnection()) return;
|
|
|
|
websocketRef.current!.send(JSON.stringify(message));
|
|
|
|
},
|
|
|
|
[checkConnection]
|
|
|
|
);
|
|
|
|
|
2025-03-09 14:13:45 +02:00
|
|
|
const shootPreview = useCallback(() => {
|
|
|
|
if (!checkConnection()) return;
|
2025-01-21 17:08:23 +02:00
|
|
|
|
2025-03-09 14:13:45 +02:00
|
|
|
setIsLoading(true);
|
|
|
|
setIsPreviewAvailable(false);
|
|
|
|
sendMessage({
|
|
|
|
command: "start_preview_rendering",
|
|
|
|
params: {}, // Empty params since scene updates are sent separately
|
|
|
|
});
|
|
|
|
toast.info("Starting preview rendering...");
|
|
|
|
}, [checkConnection, sendMessage]);
|
2025-02-07 00:35:38 +02:00
|
|
|
|
|
|
|
const playbackPreview = useCallback(() => {
|
|
|
|
if (!checkConnection()) return;
|
|
|
|
|
|
|
|
setIsPlaying(true);
|
|
|
|
sendMessage({
|
|
|
|
command: "start_broadcast",
|
|
|
|
});
|
|
|
|
toast.info("Starting preview playback");
|
|
|
|
}, [checkConnection, sendMessage]);
|
|
|
|
|
|
|
|
const stopPlaybackPreview = useCallback(() => {
|
|
|
|
if (!checkConnection()) return;
|
|
|
|
|
|
|
|
setIsPlaying(false);
|
|
|
|
sendMessage({
|
|
|
|
command: "stop_broadcast",
|
|
|
|
});
|
|
|
|
toast.info("Stopping preview playback");
|
|
|
|
}, [checkConnection, sendMessage]);
|
|
|
|
|
|
|
|
const generateVideo = useCallback(() => {
|
|
|
|
if (!checkConnection()) return;
|
|
|
|
|
|
|
|
setIsLoading(true);
|
|
|
|
sendMessage({
|
|
|
|
command: "generate_video",
|
|
|
|
});
|
|
|
|
toast.info("Starting video generation...");
|
|
|
|
}, [checkConnection, sendMessage]);
|
|
|
|
|
|
|
|
// Handle incoming messages
|
|
|
|
useEffect(() => {
|
|
|
|
if (!websocket) return;
|
|
|
|
|
|
|
|
const handleMessage = (event: MessageEvent) => {
|
|
|
|
try {
|
|
|
|
const data = JSON.parse(event.data);
|
|
|
|
|
|
|
|
// Handle connection acknowledgment
|
|
|
|
if (data.status === "ACK") {
|
|
|
|
return; // Prevent further processing of ACK messages
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update internal states based on message type
|
|
|
|
if (data.type === "frame") {
|
|
|
|
setIsPreviewAvailable(true);
|
|
|
|
setIsLoading(false);
|
|
|
|
} else if (data.type === "viewport_stream_error") {
|
|
|
|
setIsLoading(false);
|
|
|
|
setIsPlaying(false);
|
2025-02-09 00:27:22 +02:00
|
|
|
} else if (data.type === "broadcast_complete") {
|
|
|
|
setIsPlaying(false);
|
2025-02-07 00:35:38 +02:00
|
|
|
} else if (data.type === "video_generation_complete") {
|
|
|
|
setIsLoading(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Forward message to callback if provided
|
|
|
|
if (onMessageCallback.current) {
|
|
|
|
onMessageCallback.current(data);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Error handling WebSocket message:", error);
|
|
|
|
toast.error("Failed to process server message");
|
|
|
|
setIsLoading(false);
|
|
|
|
setIsPlaying(false);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
websocket.addEventListener("message", handleMessage);
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
websocket.removeEventListener("message", handleMessage);
|
|
|
|
};
|
|
|
|
}, [websocket]);
|
|
|
|
|
|
|
|
// Reset states when websocket changes
|
|
|
|
useEffect(() => {
|
|
|
|
if (!websocket) {
|
2025-01-21 17:08:23 +02:00
|
|
|
setIsLoading(false);
|
2025-02-07 00:35:38 +02:00
|
|
|
setIsPlaying(false);
|
|
|
|
setIsPreviewAvailable(false);
|
2025-01-21 17:08:23 +02:00
|
|
|
}
|
|
|
|
}, [websocket]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
isLoading,
|
|
|
|
isPlaying,
|
|
|
|
isPreviewAvailable,
|
|
|
|
shootPreview,
|
|
|
|
playbackPreview,
|
|
|
|
stopPlaybackPreview,
|
2025-03-09 14:13:45 +02:00
|
|
|
generateVideo,
|
2025-01-21 17:08:23 +02:00
|
|
|
};
|
|
|
|
};
|