shreyask commited on
Commit
09093e0
·
verified ·
1 Parent(s): ff324d9

add ability to screenshare or upload local video

Browse files
src/App.tsx CHANGED
@@ -2,28 +2,31 @@ import { useState, useEffect, useRef, useCallback, useMemo } from "react";
2
  import LoadingScreen from "./components/LoadingScreen";
3
  import CaptioningView from "./components/CaptioningView";
4
  import WelcomeScreen from "./components/WelcomeScreen";
5
- import WebcamPermissionDialog from "./components/WebcamPermissionDialog";
6
  import type { AppState } from "./types";
7
 
8
  export default function App() {
9
- const [appState, setAppState] = useState<AppState>("requesting-permission");
10
- const [webcamStream, setWebcamStream] = useState<MediaStream | null>(null);
 
11
  const [isVideoReady, setIsVideoReady] = useState(false);
12
  const videoRef = useRef<HTMLVideoElement | null>(null);
13
 
14
- const handlePermissionGranted = useCallback((stream: MediaStream) => {
15
- setWebcamStream(stream);
16
- setAppState("welcome");
 
17
  }, []);
18
 
19
  const handleStart = useCallback(() => {
20
- setAppState("loading");
21
  }, []);
22
 
23
  const handleLoadingComplete = useCallback(() => {
24
  setAppState("captioning");
25
  }, []);
26
 
 
27
  const playVideo = useCallback(async (video: HTMLVideoElement) => {
28
  try {
29
  await video.play();
@@ -34,7 +37,17 @@ export default function App() {
34
 
35
  const setupVideo = useCallback(
36
  (video: HTMLVideoElement, stream: MediaStream) => {
37
- video.srcObject = stream;
 
 
 
 
 
 
 
 
 
 
38
 
39
  const handleCanPlay = () => {
40
  setIsVideoReady(true);
@@ -51,23 +64,23 @@ export default function App() {
51
  );
52
 
53
  useEffect(() => {
54
- if (webcamStream && videoRef.current) {
55
  const video = videoRef.current;
56
 
57
  video.srcObject = null;
58
  video.load();
59
 
60
- const cleanup = setupVideo(video, webcamStream);
61
  return cleanup;
62
  }
63
- }, [webcamStream, setupVideo]);
64
 
65
  const videoBlurState = useMemo(() => {
66
  switch (appState) {
67
- case "requesting-permission":
68
- return "blur(20px) brightness(0.2) saturate(0.5)";
69
  case "welcome":
70
  return "blur(12px) brightness(0.3) saturate(0.7)";
 
 
71
  case "loading":
72
  return "blur(8px) brightness(0.4) saturate(0.8)";
73
  case "captioning":
@@ -81,7 +94,7 @@ export default function App() {
81
  <div className="App relative h-screen overflow-hidden">
82
  <div className="absolute inset-0 bg-gray-900" />
83
 
84
- {webcamStream && (
85
  <video
86
  ref={videoRef}
87
  autoPlay
@@ -97,13 +110,13 @@ export default function App() {
97
 
98
  {appState !== "captioning" && <div className="absolute inset-0 bg-gray-900/80 backdrop-blur-sm" />}
99
 
100
- {appState === "requesting-permission" && <WebcamPermissionDialog onPermissionGranted={handlePermissionGranted} />}
101
-
102
  {appState === "welcome" && <WelcomeScreen onStart={handleStart} />}
103
 
 
 
104
  {appState === "loading" && <LoadingScreen onComplete={handleLoadingComplete} />}
105
 
106
- {appState === "captioning" && <CaptioningView videoRef={videoRef} />}
107
  </div>
108
  );
109
  }
 
2
  import LoadingScreen from "./components/LoadingScreen";
3
  import CaptioningView from "./components/CaptioningView";
4
  import WelcomeScreen from "./components/WelcomeScreen";
5
+ import InputSourceDialog from "./components/InputSourceDialog";
6
  import type { AppState } from "./types";
7
 
8
  export default function App() {
9
+ const [appState, setAppState] = useState<AppState>("welcome");
10
+ const [mediaStream, setMediaStream] = useState<MediaStream | null>(null);
11
+ const [sourceType, setSourceType] = useState<'webcam' | 'screen' | 'file' | null>(null);
12
  const [isVideoReady, setIsVideoReady] = useState(false);
13
  const videoRef = useRef<HTMLVideoElement | null>(null);
14
 
15
+ const handleSourceSelected = useCallback((stream: MediaStream, type: 'webcam' | 'screen' | 'file') => {
16
+ setMediaStream(stream);
17
+ setSourceType(type);
18
+ setAppState("loading");
19
  }, []);
20
 
21
  const handleStart = useCallback(() => {
22
+ setAppState("source-selection");
23
  }, []);
24
 
25
  const handleLoadingComplete = useCallback(() => {
26
  setAppState("captioning");
27
  }, []);
28
 
29
+
30
  const playVideo = useCallback(async (video: HTMLVideoElement) => {
31
  try {
32
  await video.play();
 
37
 
38
  const setupVideo = useCallback(
39
  (video: HTMLVideoElement, stream: MediaStream) => {
40
+ // Check if this is a video file (mock stream with videoFileUrl)
41
+ const videoFileUrl = (stream as any).videoFileUrl;
42
+
43
+ if (videoFileUrl) {
44
+ // For video files, use the file URL directly
45
+ video.src = videoFileUrl;
46
+ video.srcObject = null;
47
+ } else {
48
+ // For webcam/screen, use the stream
49
+ video.srcObject = stream;
50
+ }
51
 
52
  const handleCanPlay = () => {
53
  setIsVideoReady(true);
 
64
  );
65
 
66
  useEffect(() => {
67
+ if (mediaStream && videoRef.current) {
68
  const video = videoRef.current;
69
 
70
  video.srcObject = null;
71
  video.load();
72
 
73
+ const cleanup = setupVideo(video, mediaStream);
74
  return cleanup;
75
  }
76
+ }, [mediaStream, setupVideo]);
77
 
78
  const videoBlurState = useMemo(() => {
79
  switch (appState) {
 
 
80
  case "welcome":
81
  return "blur(12px) brightness(0.3) saturate(0.7)";
82
+ case "source-selection":
83
+ return "blur(20px) brightness(0.2) saturate(0.5)";
84
  case "loading":
85
  return "blur(8px) brightness(0.4) saturate(0.8)";
86
  case "captioning":
 
94
  <div className="App relative h-screen overflow-hidden">
95
  <div className="absolute inset-0 bg-gray-900" />
96
 
97
+ {mediaStream && (
98
  <video
99
  ref={videoRef}
100
  autoPlay
 
110
 
111
  {appState !== "captioning" && <div className="absolute inset-0 bg-gray-900/80 backdrop-blur-sm" />}
112
 
 
 
113
  {appState === "welcome" && <WelcomeScreen onStart={handleStart} />}
114
 
115
+ {appState === "source-selection" && <InputSourceDialog onSourceSelected={handleSourceSelected} />}
116
+
117
  {appState === "loading" && <LoadingScreen onComplete={handleLoadingComplete} />}
118
 
119
+ {appState === "captioning" && <CaptioningView videoRef={videoRef} sourceType={sourceType} />}
120
  </div>
121
  );
122
  }
src/components/CaptioningView.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import { useState, useRef, useEffect, useCallback } from "react";
2
  import WebcamCapture from "./WebcamCapture";
 
3
  import DraggableContainer from "./DraggableContainer";
4
  import PromptInput from "./PromptInput";
5
  import LiveCaption from "./LiveCaption";
@@ -8,6 +9,7 @@ import { PROMPTS, TIMING } from "../constants";
8
 
9
  interface CaptioningViewProps {
10
  videoRef: React.RefObject<HTMLVideoElement | null>;
 
11
  }
12
 
13
  function useCaptioningLoop(
@@ -67,7 +69,7 @@ function useCaptioningLoop(
67
  }, [isRunning, isLoaded, runInference, promptRef, videoRef]);
68
  }
69
 
70
- export default function CaptioningView({ videoRef }: CaptioningViewProps) {
71
  const [caption, setCaption] = useState<string>("");
72
  const [isLoopRunning, setIsLoopRunning] = useState<boolean>(true);
73
  const [currentPrompt, setCurrentPrompt] = useState<string>(PROMPTS.default);
@@ -108,13 +110,25 @@ export default function CaptioningView({ videoRef }: CaptioningViewProps) {
108
  <div className="relative w-full h-full">
109
  <WebcamCapture isRunning={isLoopRunning} onToggleRunning={handleToggleLoop} error={error} />
110
 
111
- {/* Draggable Prompt Input - Bottom Left */}
112
- <DraggableContainer initialPosition="bottom-left">
 
 
 
 
 
 
 
 
 
113
  <PromptInput onPromptChange={handlePromptChange} />
114
  </DraggableContainer>
115
 
116
- {/* Draggable Live Caption - Bottom Right */}
117
- <DraggableContainer initialPosition="bottom-right">
 
 
 
118
  <LiveCaption caption={caption} isRunning={isLoopRunning} error={error} />
119
  </DraggableContainer>
120
  </div>
 
1
  import { useState, useRef, useEffect, useCallback } from "react";
2
  import WebcamCapture from "./WebcamCapture";
3
+ import VideoScrubber from "./VideoScrubber";
4
  import DraggableContainer from "./DraggableContainer";
5
  import PromptInput from "./PromptInput";
6
  import LiveCaption from "./LiveCaption";
 
9
 
10
  interface CaptioningViewProps {
11
  videoRef: React.RefObject<HTMLVideoElement | null>;
12
+ sourceType?: 'webcam' | 'screen' | 'file' | null;
13
  }
14
 
15
  function useCaptioningLoop(
 
69
  }, [isRunning, isLoaded, runInference, promptRef, videoRef]);
70
  }
71
 
72
+ export default function CaptioningView({ videoRef, sourceType }: CaptioningViewProps) {
73
  const [caption, setCaption] = useState<string>("");
74
  const [isLoopRunning, setIsLoopRunning] = useState<boolean>(true);
75
  const [currentPrompt, setCurrentPrompt] = useState<string>(PROMPTS.default);
 
110
  <div className="relative w-full h-full">
111
  <WebcamCapture isRunning={isLoopRunning} onToggleRunning={handleToggleLoop} error={error} />
112
 
113
+ {/* Video Scrubber - Only show for video files */}
114
+ <VideoScrubber
115
+ videoRef={videoRef}
116
+ isVisible={sourceType === 'file'}
117
+ />
118
+
119
+ {/* Draggable Prompt Input - Bottom Left (above scrubber) */}
120
+ <DraggableContainer
121
+ initialPosition={sourceType === 'file' ? { x: 20, y: window.innerHeight - 200 } : "bottom-left"}
122
+ className="z-[150]"
123
+ >
124
  <PromptInput onPromptChange={handlePromptChange} />
125
  </DraggableContainer>
126
 
127
+ {/* Draggable Live Caption - Bottom Right (above scrubber) */}
128
+ <DraggableContainer
129
+ initialPosition={sourceType === 'file' ? { x: window.innerWidth - 170, y: window.innerHeight - 200 } : "bottom-right"}
130
+ className="z-[150]"
131
+ >
132
  <LiveCaption caption={caption} isRunning={isLoopRunning} error={error} />
133
  </DraggableContainer>
134
  </div>
src/components/WelcomeScreen.tsx CHANGED
@@ -33,20 +33,6 @@ export default function WelcomeScreen({ onStart }: WelcomeScreenProps) {
33
  </div>
34
  </GlassContainer>
35
 
36
- {/* Webcam Status Card */}
37
- <GlassContainer
38
- bgColor={GLASS_EFFECTS.COLORS.SUCCESS_BG}
39
- className="rounded-2xl shadow-2xl hover:scale-105 transition-transform duration-200"
40
- role="status"
41
- aria-label="Camera status"
42
- >
43
- <div className="p-4">
44
- <div className="flex items-center justify-center space-x-2">
45
- <div className="w-3 h-3 rounded-full bg-green-500 animate-pulse"></div>
46
- <p className="text-green-400 font-medium">Camera ready</p>
47
- </div>
48
- </div>
49
- </GlassContainer>
50
 
51
  {/* How It Works Card */}
52
  <GlassContainer
 
33
  </div>
34
  </GlassContainer>
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  {/* How It Works Card */}
38
  <GlassContainer
src/constants/index.ts CHANGED
@@ -16,6 +16,7 @@ export const LAYOUT = {
16
  MARGINS: {
17
  DEFAULT: 20,
18
  BOTTOM: 20,
 
19
  },
20
  DIMENSIONS: {
21
  PROMPT_WIDTH: 420,
 
16
  MARGINS: {
17
  DEFAULT: 20,
18
  BOTTOM: 20,
19
+ BOTTOM_WITH_SCRUBBER: 100,
20
  },
21
  DIMENSIONS: {
22
  PROMPT_WIDTH: 420,
src/types/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type AppState = "requesting-permission" | "welcome" | "loading" | "captioning";
2
 
3
  export interface GlassEffectProps {
4
  baseFrequency?: number;
 
1
+ export type AppState = "welcome" | "source-selection" | "loading" | "captioning";
2
 
3
  export interface GlassEffectProps {
4
  baseFrequency?: number;