File size: 3,612 Bytes
65d68f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | /**
* DocDoe Cinematic Video Engine β VideoSceneRenderer
*
* Dispatches to the correct scene component based on `scene.type`.
* Wraps with SceneCamera and cross-dissolve opacity.
*
* This is the single entry point for rendering one scene inside a
* Remotion Series.Sequence. Frame is already local (0-based) here.
*
* NO framer-motion. All motion via Remotion interpolate() / spring().
*/
import { AbsoluteFill, useCurrentFrame } from "remotion";
import type { VideoPlan, VideoScene } from "../types";
import { SceneCamera } from "../motion/camera";
import { sceneDissolve } from "../motion/animations";
import { ExplainScene } from "../scenes/ExplainScene";
import { HookScene } from "../scenes/HookScene";
import { KeywordScene } from "../scenes/KeywordScene";
import { RecapScene } from "../scenes/RecapScene";
// ββ Shared prop type for all scene components βββββββββββββββββββββββββββββ
export type SceneComponentProps = {
plan: VideoPlan;
scene: VideoScene;
sceneIndex: number;
totalScenes: number;
durationInFrames: number;
};
// ββ Scene dispatcher ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/**
* Renders the correct scene component for a given scene.type.
* All unimplemented types fall through to ExplainScene until their
* dedicated component is built.
*
* TODO: Replace each TODO comment with its dedicated scene component.
*/
function renderSceneComponent(props: SceneComponentProps) {
switch (props.scene.type) {
case "explain":
return <ExplainScene {...props} />;
case "hook":
return <HookScene {...props} />;
case "keyword":
return <KeywordScene {...props} />;
case "pyq":
// TODO: PYQScene β exam paper style, marks badge, answer reveal
return <ExplainScene {...props} />;
case "quiz":
// TODO: QuizScene β question + multiple choice or fill-in-blank
return <ExplainScene {...props} />;
case "recap":
return <RecapScene {...props} />;
case "mentor":
// TODO: MentorScene β warm close framing, personal coaching tone
return <ExplainScene {...props} />;
default:
return <ExplainScene {...props} />;
}
}
// ββ Main renderer βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/**
* VideoSceneRenderer β renders one scene with camera motion and transitions.
*
* Mount inside a Remotion Series.Sequence. The frame passed to child
* components is automatically local (reset to 0 at sequence start).
*
* @example
* <Series.Sequence durationInFrames={durationInFrames}>
* <VideoSceneRenderer
* plan={plan}
* scene={scene}
* sceneIndex={index}
* totalScenes={scenes.length}
* durationInFrames={durationInFrames}
* />
* </Series.Sequence>
*/
export function VideoSceneRenderer(props: SceneComponentProps) {
const frame = useCurrentFrame();
// First scene fades in without a leading black frame.
// All subsequent scenes cross-dissolve for a smooth cut.
const opacity = props.sceneIndex === 0
? sceneDissolve(frame, props.durationInFrames, 4, 6)
: sceneDissolve(frame, props.durationInFrames, 8, 6);
return (
<AbsoluteFill style={{ opacity }}>
<SceneCamera sceneType={props.scene.type}>
{renderSceneComponent(props)}
</SceneCamera>
</AbsoluteFill>
);
}
|