sound-emitter.tsx 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. "use client";
  2. import { useRef, useEffect } from "react";
  3. import { useThree } from "@react-three/fiber";
  4. import * as THREE from "three";
  5. import { useEditorStore } from "@/lib/store";
  6. interface SoundEmitterProps {
  7. position?: [number, number, number] | null;
  8. rotation?: [number, number, number] | null;
  9. scale?: [number, number, number] | null;
  10. url: string;
  11. loop?: boolean | null;
  12. volume?: number | null;
  13. positional?: boolean | null;
  14. distance?: number | null;
  15. objectId?: string | null;
  16. }
  17. export function SoundEmitter({
  18. position,
  19. url,
  20. loop,
  21. volume,
  22. positional,
  23. distance,
  24. }: SoundEmitterProps) {
  25. const isPlaying = useEditorStore((s) => s.isPlaying);
  26. const { camera } = useThree();
  27. const listenerRef = useRef<THREE.AudioListener | null>(null);
  28. const soundRef = useRef<THREE.PositionalAudio | THREE.Audio | null>(null);
  29. const pos: [number, number, number] = position ?? [0, 0, 0];
  30. useEffect(() => {
  31. if (!isPlaying || !url) return;
  32. const listener = new THREE.AudioListener();
  33. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  34. camera.add(listener as any);
  35. listenerRef.current = listener;
  36. const loader = new THREE.AudioLoader();
  37. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  38. let audio: any;
  39. if (positional !== false) {
  40. audio = new THREE.PositionalAudio(listener);
  41. audio.setRefDistance(distance ?? 10);
  42. } else {
  43. audio = new THREE.Audio(listener);
  44. }
  45. loader.load(url, (buffer) => {
  46. audio.setBuffer(buffer);
  47. audio.setLoop(loop ?? false);
  48. audio.setVolume(volume ?? 1);
  49. audio.play();
  50. });
  51. soundRef.current = audio;
  52. return () => {
  53. if (soundRef.current?.isPlaying) soundRef.current.stop();
  54. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  55. camera.remove(listener as any);
  56. };
  57. }, [isPlaying, url, loop, volume, positional, distance, camera]);
  58. // Edit mode indicator
  59. if (!isPlaying) {
  60. return (
  61. <mesh position={pos}>
  62. <sphereGeometry args={[0.2, 8, 8]} />
  63. <meshStandardMaterial color="#9b59b6" wireframe />
  64. </mesh>
  65. );
  66. }
  67. return <group position={pos} />;
  68. }