4_oil.tsx (3111B)
1 import { Canvas } from "@react-three/fiber"; 2 import { OrbitControls } from "@react-three/drei"; 3 import { OilArtAPI, OilArtPlane } from "./OilArt/OilArtPlane.tsx"; 4 import { useEffect, useRef, useState } from "react"; 5 import * as THREE from "three"; 6 import { StageProps } from "../ctrl/page.tsx"; 7 const AUDIO_SOURCES: Record<number, string> = { 8 1: "/audio/004-1.flac", 9 2: "/audio/004-2.flac", 10 }; 11 const useSequentialAudio = ( 12 initialState: number, 13 audioSources: Record<number, string>, 14 onComplete: () => void, 15 ) => { 16 const [sceneState, setSceneState] = useState(initialState); 17 const audioRef = useRef<HTMLAudioElement | null>(null); 18 const currentAudioUrl = audioSources[sceneState]; 19 useEffect(() => { 20 if (sceneState > Math.max(...Object.keys(audioSources).map(Number))) { 21 console.log("All audio finished. Calling onComplete."); 22 onComplete(); 23 return; 24 } 25 if (!currentAudioUrl) { 26 return; 27 } 28 console.log(`Starting setup for state ${sceneState}: ${currentAudioUrl}`); 29 const existingAudio = audioRef.current; 30 if (existingAudio) { 31 existingAudio.pause(); 32 } 33 const audio = new Audio(currentAudioUrl); 34 audioRef.current = audio; 35 const handleAudioEnded = () => { 36 console.log(`Audio ${sceneState} finished. Transitioning state.`); 37 setSceneState((prev) => prev + 1); 38 }; 39 audio.addEventListener("ended", handleAudioEnded); 40 audio.play().catch((e) => { 41 console.warn( 42 `Audio playback failed for state ${sceneState} (requires user interaction):`, 43 e, 44 ); 45 }); 46 return () => { 47 if (audio === audioRef.current) { 48 // 現在設定したAudioオブジェクトであることを確認 49 audio.pause(); 50 audio.removeEventListener("ended", handleAudioEnded); 51 } 52 }; 53 }, [sceneState, audioSources, onComplete]); // sceneStateとcurrentAudioUrlは基本的に連動するため、sceneStateを依存配列に含める 54 return sceneState; 55 }; 56 export default function OilArtCanvasWrapper({ onComplete }: StageProps) { 57 const apiRef = useRef<OilArtAPI>({} as OilArtAPI); 58 const sceneState = useSequentialAudio(1, AUDIO_SOURCES, onComplete); 59 useEffect(() => { 60 const intervalId = setInterval(() => { 61 if (apiRef.current.dropOil) { 62 const x = Math.random() * 2 - 1; // -1.0〜1.0 63 const y = Math.random() * 2 - 1; // -1.0〜1.0 64 const color = new THREE.Color( 65 Math.random(), 66 Math.random(), 67 Math.random(), 68 ); 69 apiRef.current.dropOil({ x, y }, color); 70 const tiltX = Math.random() * 0.8 - 0.4; 71 const tiltY = Math.random() * 0.8 - 0.4; 72 apiRef.current.tilt({ x: tiltX, y: tiltY }); 73 } 74 }, 1500); 75 return () => clearInterval(intervalId); 76 }, []); 77 return ( 78 <Canvas camera={{ position: [0, 0, 1] }}> 79 <color attach="background" args={[0x000000]} /> 80 <OilArtPlane onTriggerAPI={(api) => (apiRef.current = api)} /> 81 <OrbitControls 82 enableZoom={false} 83 enablePan={false} 84 enableRotate={false} 85 /> 86 </Canvas> 87 ); 88 }