ReactVision / viro

ViroReact: The AR and VR library for React Native 📳💙💛🤍💚

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Can't record video [Error: Argument appears to not be a ReactComponent.]

colbymchenry opened this issue · comments

Requirements:

Please go through this checklist before opening a new issue

Environment

Developing on Mac OS,
"@viro-community/react-viro": "^2.23.0", "react-native": "0.71.7",
Device is iPhone 14 Pro Max

Description

For whatever reason I'm getting this error when I attempt to start recording:
recording error: [Error: Argument appears to not be a ReactComponent. Keys: push,pop,popN,jump,replace,startVideoRecording,stopVideoRecording,takeScreenshot,resetARSession,setWorldOrigin,project,unproject,viroAppProps]

Reproducible Demo

Here is my code to help visualize what I have:

You'll notice I use a Wrapper class that extends React.Component. This was something I found here:
#137 but didn't seem to help

import React, {useEffect, useMemo, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {type NativeStackScreenProps} from '@react-navigation/native-stack';
import {RootStackParamList} from '../../../../../routes';
import {
  ViroARScene,
  Viro3DObject,
  ViroARSceneNavigator,
  ViroDirectionalLight,
  ViroARPlaneSelector,
  ViroNode,
  ViroButton,
  ViroPolyline,
  ViroMaterials,
} from '@viro-community/react-viro';
import {Button, Text} from 'react-native-paper';
import Svg, {Path} from 'react-native-svg';
import {Viro3DPoint} from '@viro-community/react-viro/dist/components/Types/ViroUtils';
import {Slider} from '@miblanchard/react-native-slider';
import ARProvider, {useAR} from './context';

const recordButtonSize = 80;
const styles = StyleSheet.create({
  planeSelector: {
    backgroundColor: 'red',
  },
  marginAuto: {
    margin: '0 auto',
  },
  recordButton: {
    minHeight: recordButtonSize,
    minWidth: recordButtonSize,
    maxHeight: recordButtonSize,
    maxWidth: recordButtonSize,
    borderRadius: 100,
    backgroundColor: 'rgba(255, 255, 255, 0.6)',
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    position: 'relative',
  },
  recordButtonContent: {
    marginLeft: -4,
    marginBottom: -5,
  },
  sliderContainer: {
    width: '100%',
  },
  sliderThumb: {
    backgroundColor: 'white',
  },
  sliderTrack: {
    backgroundColor: 'white',
  },
});

const MODEL_HEIGHT = 0.83;
const TOP_WIDTH = 0.29;
const BUTTON_SIZE = 0.2;

const SIDES = [
  {
    side: 'back',
    source: require('../../../../assets/amni3dmodel_back.glb'),
    btnPosition: [0, 0.5, -0.5],
    btnRotation: [-160, 0, 0],
    polylines: [
      [-0.57, 0.1, -0.65],
      [0.57, 0.1, -0.65],
      [0.29, MODEL_HEIGHT - 0.05, -0.36],
      [-0.29, MODEL_HEIGHT - 0.05, -0.36],
      [-0.57, 0.1, -0.65],
    ],
  },
  {
    side: 'front',
    source: require('../../../../assets/amni3dmodel_front.glb'),
    btnPosition: [0, 0.5, 0.5],
    btnRotation: [-25, 0, 0],
    polylines: [
      [0.57, 0.1, 0.65],
      [-0.57, 0.1, 0.65],
      [-0.29, MODEL_HEIGHT - 0.05, 0.36],
      [0.29, MODEL_HEIGHT - 0.05, 0.36],
      [0.57, 0.1, 0.65],
    ],
  },
  {
    side: 'left',
    source: require('../../../../assets/amni3dmodel_left.glb'),
    btnPosition: [-0.5, 0.5, 0],
    btnRotation: [-80, 90, 75],
    polylines: [
      [-0.65, 0.1, 0.57],
      [-0.65, 0.1, -0.57],
      [-0.36, 0.78, -0.29],
      [-0.36, 0.78, 0.29],
      [-0.65, 0.1, 0.57],
    ],
  },
  {
    side: 'right',
    source: require('../../../../assets/amni3dmodel_right.glb'),
    btnPosition: [0.5, 0.5, 0],
    btnRotation: [90, 10, 115],
    polylines: [
      [0.65, 0.1, 0.57],
      [0.65, 0.1, -0.57],
      [0.36, MODEL_HEIGHT - 0.05, -0.29],
      [0.36, MODEL_HEIGHT - 0.05, 0.29],
      [0.65, 0.1, 0.57],
    ],
  },
  {
    side: 'top',
    source: require('../../../../assets/amni3dmodel_top.glb'),
    btnPosition: [0, MODEL_HEIGHT + 0.02, 0],
    btnRotation: [25, 90, 115],
    polylines: [
      [TOP_WIDTH, MODEL_HEIGHT, TOP_WIDTH],
      [TOP_WIDTH, MODEL_HEIGHT, -TOP_WIDTH],
      [-TOP_WIDTH, MODEL_HEIGHT, -TOP_WIDTH],
      [-TOP_WIDTH, MODEL_HEIGHT, TOP_WIDTH],
      [TOP_WIDTH, MODEL_HEIGHT, TOP_WIDTH],
    ],
  },
];

class Wrapper extends React.Component {
  render() {
    return <HelloWorldSceneAR setReset={() => {}} />;
  }
}

const HelloWorldSceneAR = (props: any) => {
  const arRef: any = React.useRef();
  const arSceneRef: any = React.useRef();
  const [plane, setPlane] = useState<any>();
  const [position, setPosition] = useState<Viro3DPoint>([0, 0, 0]);
  const [accumulatedScale, setAccumulatedScale] = useState<number>(1); // start at a scale of 1
  const [activePinchScale, setActivePinchScale] = useState<number>(1); // active pinch scale starts at 1
  const {arValues, setArValues} = useAR();
  const [animatedOpacity, setAnimatedOpacity] = useState<number>(0); // The animated opacity value for the hovering effect
  const [activeSide, setActiveSide] = useState<string>(''); // What side the user is hovering on

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  function onInitialized(state: any, reason: any) {}

  const reset = () => {
    if (arRef.current) {
      arRef.current.reset();
    }
    setPosition([0, 0, 0]);
    setPlane(undefined);
    setAccumulatedScale(1);
    setActivePinchScale(1);
  };

  const modelScale = useMemo(() => {
    return (
      (plane ? plane.width * 0.35 : 0.25) * accumulatedScale * activePinchScale
    );
  }, [plane, activePinchScale, accumulatedScale]);

  const handlePinchEffect = (pinchState: number, scaleFactor: number) => {
    if (pinchState === 3) {
      // end of pinch gesture
      // Update the accumulated scale with the final scale for this pinch gesture
      setAccumulatedScale(
        oldAccumulatedScale => oldAccumulatedScale * activePinchScale,
      );

      // Reset active pinch scale
      setActivePinchScale(1);
    } else {
      // ongoing pinch gesture
      setActivePinchScale(scaleFactor);
    }
  };

  useEffect(() => {
    props.setReset(() => {
      return () => reset();
    });
  }, []);

  const onButtonHover = (side: string, isHovering: boolean) => {
    setActiveSide(isHovering ? side : '');
  };

  // Function to start the opacity animation
  const startOpacityAnimation = () => {
    let opacity = 0;
    setAnimatedOpacity(0);
    const animationInterval = setInterval(() => {
      opacity += 0.2; // You can adjust the step size as needed
      if (opacity >= 1) {
        clearInterval(animationInterval);
      } else {
        setAnimatedOpacity(opacity);
      }
    }, 100); // You can adjust the interval duration as needed
  };

  // Start the animation every time the active side changes
  useEffect(() => {
    startOpacityAnimation();
  }, [activeSide]);

  ViroMaterials.createMaterials({
    white: {
      lightingModel: 'Blinn',
      diffuseTexture: require('../../../../assets/white.jpg'),
      specularTexture: require('../../../../assets/white.jpg'),
    },
  });

  const renderSide = (arg: any) => {
    return (
      <>
        <Viro3DObject
          source={arg.source}
          type='GLB'
          highAccuracyEvents={true}
        />
        <ViroButton
          source={require('../../../../assets/blue_reticle.png')}
          gazeSource={require('../../../../assets/blue_reticle.png')}
          tapSource={require('../../../../assets/blue_reticle.png')}
          position={arg.btnPosition}
          rotation={arg.btnRotation}
          height={BUTTON_SIZE}
          width={BUTTON_SIZE}
          renderingOrder={1}
          onHover={(isHovering: boolean) => onButtonHover(arg.side, isHovering)}
          // onTap={this._onButtonTap}
        />
        <ViroPolyline
          position={[0, 0, 0]}
          points={arg.polylines}
          thickness={0.005}
          materials={'white'}
          ignoreEventHandling={true}
          opacity={activeSide === arg.side ? animatedOpacity : 0}
        />
      </>
    );
  };

  return (
    <ViroARScene
      onTrackingUpdated={onInitialized}
      // onAnchorFound={anchor => console.log()}
      // onAnchorUpdated={() => console.log('onAnchorUpdated')}
      // onAnchorRemoved={() => console.log('onAnchorRemoved')}
      ref={arSceneRef}
    >
      <ViroARPlaneSelector
        minHeight={0.1}
        minWidth={0.1}
        onPlaneSelected={e => setPlane(e)}
        alignment='Horizontal'
        style={styles.planeSelector}
        ref={arRef}
      >
        {/* Two supportive lights, casting inside the model and outside */}
        <ViroDirectionalLight
          color='#FFFFFF'
          intensity={4500}
          direction={[-1, -1, -1]}
          shadowOrthographicPosition={[0, 3, -5]}
          shadowOrthographicSize={10}
          shadowNearZ={2}
          shadowFarZ={9}
          castsShadow={true}
          shadowOpacity={1}
        />
        <ViroDirectionalLight
          color='#FFFFFF'
          intensity={4500}
          direction={[0, -1, 0]}
          shadowOrthographicPosition={[0, 3, -5]}
          shadowOrthographicSize={10}
          shadowNearZ={2}
          shadowFarZ={9}
          castsShadow={true}
          shadowOpacity={1}
        />

        {/* Node/container for the entire modal, buttons, and polylines */}
        <ViroNode
          position={position}
          scale={[modelScale, modelScale, modelScale]}
          onPinch={handlePinchEffect}
          onDrag={() => {}}
          dragType='FixedToWorld'
          rotation={[0, arValues.rotation || 0, 0]}
        >
          {SIDES.map((obj: any) => {
            return renderSide(obj);
          })}
        </ViroNode>
      </ViroARPlaneSelector>
    </ViroARScene>
  );
};

const MainScreen: React.FC<NativeStackScreenProps<RootStackParamList>> = () => {
  const [recording, setRecording] = useState<boolean>(false);
  const [reset, setReset] = useState<any>({});
  const {arValues, setArValues} = useAR();
  const [viroRef, setViroRef] = useState<ViroARSceneNavigator>();

  const onRecordBtnPress = async () => {
    // if (recording) {
    //   await stopVideoRecording();
    // } else {
    startVideoRecording();
    // }
  };

  const startVideoRecording = () => {
    function onErr(error: any) {
      console.error(error);
    }

    if (
      !viroRef ||
      !viroRef.sceneNavigator ||
      !viroRef.sceneNavigator.startVideoRecording
    ) {
      console.error('viroRef has not been loaded');
    } else {
      let now: any = new Date();
      now = now.toISOString();
      now = now.replace(/\:|\./g, '-');
      let fileName = '_' + now;

      try {
        viroRef.sceneNavigator.startVideoRecording(fileName, false, onErr);
      } catch (err) {
        console.log('recording error:', err);
      }
    }
  };

  const stopVideoRecording = async () => {
    // if (!recording) {
    //   return;
    // }
    // const resp: {
    //   success: boolean;
    //   url: string;
    //   errorCode: number;
    // } = await arSceneNavigatorRef.arSceneNavigator.stopVideoRecording();
    // if (!resp.success) {
    //   // warn user of failure
    //   return;
    // }
    // setRecording(false);
    // // let user know of success
  };

  return (
    <View className='relative flex flex-column flex-1'>
      <ViroARSceneNavigator
        ref={(c: ViroARSceneNavigator) => setViroRef(c)}
        autofocus={true}
        pbrEnabled={true}
        shadowsEnabled={true}
        initialScene={{
          scene: Wrapper,
        }}
      />

      {/* Start Reset Plane Detection Code */}
      <View className='absolute top-2 right-0 z-10'>
        <Button
          className='bg-transparent p-2'
          onPress={() => {
            reset();
          }}
          mode='elevated'
        >
          <Svg
            fill='none'
            viewBox='0 0 24 24'
            strokeWidth='1.5'
            stroke='#FFF'
            className='w-12 h-12'
          >

          </Svg>
        </Button>
      </View>
      {/* End Reset Plane Detection Code */}

      {/* Start Record Button Code */}
      <View className='absolute left-0 bottom-12 z-10 w-full flex items-center justify-center'>
        <Button
          onPress={onRecordBtnPress}
          mode='elevated'
          style={styles.recordButton}
          contentStyle={styles.recordButtonContent}
        >
          {recording ? (
            <View className='bg-red-500 rounded-sm w-10 h-10' />
          ) : (
            <View className='bg-gray-400/30 rounded-full w-10 h-10' />
          )}
        </Button>
      </View>
      {/* End Record Button Code */}

      {/* Start Rotation Slider Code */}
      <View className='absolute left-0 bottom-0 z-10 w-full flex items-center justify-center'>
        <Slider
          value={arValues.rotation}
          onValueChange={value =>
            setArValues((oldValues: any) => {
              return {...oldValues, rotation: value[0]};
            })
          }
          maximumValue={359}
          minimumValue={0}
          step={1}
          containerStyle={styles.sliderContainer}
          thumbStyle={styles.sliderThumb}
          trackStyle={styles.sliderTrack}
          disabled={recording}
        />
        <Text className='absolute right-0 bottom-8'>{arValues.rotation}°</Text>
      </View>
      {/* End Rotation Slider Code */}

      {/* Start Reticle Code */}
      <View
        className='absolute z-10 top-0 left-0 w-full h-full flex justify-center items-center'
        pointerEvents='none'
      >
        <View className='flex text-red-500'>
          <Svg viewBox='0 0 24 24' fill='#FFF' className='absolute w-12 h-12'>
         
          </Svg>
          <Svg
            viewBox='0 0 24 24'
            fill='none'
            stroke='#FFF'
            strokeWidth='1'
            strokeLinecap='round'
            strokeLinejoin='round'
            className='scale-[2] w-12 h-12 lucide lucide-scan'
          >
            
          </Svg>
        </View>
      </View>
      {/* End Reticle Code */}
    </View>
  );
};

export const AutoLabelScreen: React.FC<
  NativeStackScreenProps<RootStackParamList>
> = props => {
  return (
    <ARProvider>
      <MainScreen {...props} />
    </ARProvider>
  );
};

Same pb, any solution here?

I've fixed this in 2.23.2-beta and I'm hoping to verify on Android before I ship a release. Can you verify that this version fixed your issue?