오티스의개발일기

[REACT-NATIVE] ffmpeg 를 활용하여 받아온 비디오 react-native 에서 컨트롤하고 정보 보여주기 본문

개발/react-native

[REACT-NATIVE] ffmpeg 를 활용하여 받아온 비디오 react-native 에서 컨트롤하고 정보 보여주기

안되면 될때까지.. 2023. 1. 16. 20:50
728x90


< 이전글

2023.01.15 - [개발/FFmpeg] - [FFMPEG] ffmpeg 오디오 변환중 오류 Automatic encoder selection failed for output stream #0:0. Default encoder for format mp3 (codec mp3) is probably disabled. Please choose an encoder manually. 해결완료

 

[FFMPEG] ffmpeg 오디오 변환중 오류 Automatic encoder selection failed for output stream #0:0. Default encoder for format

< 이전글 2023.01.15 - [개발/FFmpeg] - [FFMPEG] FFmpeg Commands 알아보기 + FFmpeg 맥북 M1 에 설치 [FFMPEG] FFmpeg Commands 알아보기 + FFmpeg 맥북 M1 에 설치 다음글 > 2023.01.15 - [개발/FFmpeg] - [FFMPEG] ffmpeg 오디오 변환

otis.tistory.com



다음글 >

 

2023.01.19 - [개발/FFmpeg] - [FFMPEG] ffmpeg-kit-react-native FFprobeKit 화가나서 존재하는 모든 함수 뽀개기...

 

[FFMPEG] ffmpeg-kit-react-native FFprobeKit 화가나서 존재하는 모든 함수 뽀개기...

++ 업데이트 ++ 예상대로 커맨드가 문제가있었다 바로앞에 -v quiet 을 넣으면 조용하다는 의미로 필요없는 데이터는 출력되지않는다.... 결과를 보도록하겠다 👇👇👇👇코드👇👇👇👇 static get

otis.tistory.com


 

오늘은 저번시간에 받아온 비디오를 클라이언트단에 보여주고 또한 영상에 대한 길이 그리고 현재 보고있는 장면이 몇분 몇초인지

알려줄수있는 클라이언트 단을 구현해볼것이다.

 

일단 결과물먼저 보도록 하겠다.

 

두가지를 보여줄건데 이작업안에서는 scrollView 를 2개를 썼고 동영상의 움직임에따라 상단에 초부분의 포지션을 변경하도록 구현하였다.

 

일단 좌우로 움직이는것을 먼저 보도록 하겠다..

 

 

 

이제 하단으로 움직이는 것을 해보도록하겠다.







간단하게 설명해보자면

크게 Scrollview(vertical)파란색 빨간색 이렇게 2곳에 썼고
파란색 Scrollview(vertical) 안에 빨간색 ScrollView(horizontal) 가 존재한다.

그리고 상단에 흰색 라인은 단순 View 태그를 사용하였고
빨간색 ScrollView(horizontal) 의 onScroll 의 이벤트를 통해 현재의 x 좌표를 useState를 통해 저장한후
그 x 좌표를 저기 흰색스타일 left: -x 라고 정의 시켜놓아 두곳다 동시에 움직일수있게된다.

이거하느라 삽질좀 하였다....아무리생각해도 답이안나왔다..
처음에는 ScrolleView 를 ref 로 등록하여 ScrollTo 를 사용해 좌표를 변경시키려 하였지만
반응속도가 너무느려 거의 따로움직이다 싶이 하였다...
역시 복잡한 문제는 간단한것부터 시도해보는게 맞는거같다..ㅎㅎ

뭐어쨋든 아래는 작성한 코드를 올리도록 하겠다.


# Camera.js

import React, { useRef, useState } from 'react';
import {
  SafeAreaView,
  Dimensions,
  Pressable,
  Text,
  StyleSheet,
  View,
  ScrollView,
  Image,
} from 'react-native';
import Ionicons from "react-native-vector-icons/Ionicons";
import ImagePicker from 'react-native-image-crop-picker';
import { Button } from 'react-native-paper';
import Video from 'react-native-video';
import FFmpegWrapper from '../../constants/FFmpegWrapper';
import { TouchableOpacity } from 'react-native-gesture-handler';

const SCREEN_WIDTH = Dimensions.get('screen').width; // 화면 width 사이즈
const SCREEN_HEIGHT = Dimensions.get('screen').height; // 화면 height 사이즈
export const FRAME_PER_SEC = 1; // 몇초마다 끊을것인지
export const FRAME_WIDTH = 40; // 하나의 프레임당 width 길이 [노란색 border 의 프레임을 뜻함]
const TILE_HEIGHT = 40; // 4 sec. 의 높이 길이
const TILE_WIDTH = FRAME_WIDTH; // 현재 노란색 프레임의 반크기

const DURATION_WINDOW_DURATION = 2; // 프레임 몇개를 사용할것인지 
const DURATION_WINDOW_BORDER_WIDTH = 4; // 테두리 굵기
const DURATION_WINDOW_WIDTH =
  DURATION_WINDOW_DURATION * 5 * TILE_WIDTH; // 총 노란색 프레임의 width 길이
const POPLINE_POSITION = '50%'; // 노란색 프레임 중간 노란색 divder 의 위치 선정 50% === center 

const getFileNameFromPath = path => { // 파일 이름 가져오는 함수
  const fragments = path.split('/');
  let fileName = fragments[fragments.length - 1];
  fileName = fileName.split('.')[0];
  return fileName;
};

const FRAME_STATUS = Object.freeze({
  LOADING: { name: Symbol('LOADING') },
  READY: { name: Symbol('READY') },
});

const Camera = () => {
  const [selectedVideo, setSelectedVideo] = useState(); // {uri: <string>, localFileName: <string>, creationDate: <Date>}
  const [frames, setFrames] = useState(); // <[{status: <FRAME_STATUS>, uri: <string>}]>
  const [audio, setAudio] = useState(); // <[{status: <FRAME_STATUS>, uri: <string>}]>
  const [x, setX] = useState(null);
  const [currentTime, setCurrentTime] = useState(0);

  const handlePressSelectVideoButton = () => {
    ImagePicker.openPicker({
      mediaType: 'video',
    }).then(videoAsset => {
      console.log(`Selected video ${JSON.stringify(videoAsset, null, 2)}`);
      setSelectedVideo({
        uri: videoAsset.sourceURL || videoAsset.path,
        localFileName: getFileNameFromPath(videoAsset.path),
        creationDate: videoAsset.creationDate,
      });
    });
  };

  const handleVideoLoad = async videoAssetLoaded => { // 영상이 업로드 된후 동작 videoAssetLoaded 안에는 들어온 영상의 정보가 담겨져있다.
    const numberOfFrames = Math.ceil(videoAssetLoaded.duration); // 영상의 초를 반올림함  5.758999824523926 -> 6
    setFrames( // 영상이 로드가된후 초마다 만들어진 프레임의 상태를 전부 loading 으로 채워준다.
      Array(numberOfFrames).fill({
        status: FRAME_STATUS.LOADING.name.description,
      }),
    );

    await FFmpegWrapper.getFrames( // 업로드된 영사을 FFmpeg Command 를 통하여 원하는 초마다 프레임을자르고 반환
      selectedVideo.localFileName, // 업로드된 영상의 이름
      selectedVideo.uri, // 업로드된 영상의 uri
      numberOfFrames, // 프레임의 갯수
      filePath => { // successCallback
        const _framesURI = []; // 각 프레임을 담을 배열
        for (let i = 0; i < numberOfFrames; i++) {
          _framesURI.push( // 각프레임을 하나하나 담는다. 
            `${filePath.replace('%4d', String(i + 1).padStart(4, 0))}`, // FFmpegWrapper 에서 지정한 outputImagePath 의 이름중 %4d' -> padStart 를 통해 받은 인덱스 예) 0,1,2,3 -> 0001 ,0002,0003,0004 로 변경후 교체
          );
        }
        const _frames = _framesURI.map(_frameURI => ({ // 받은 배열들을 다시 map으로 반복문을 돌려 프레임마다 uri 를 저장해주고 상태를 LOADING -> READY 으로 변경해준다.
          uri: _frameURI,
          status: FRAME_STATUS.READY.name.description,
        }));
        setFrames(_frames); // 변경된 값들을 useState 를통해 다시 저장
      },
    );
    await FFmpegWrapper.getAudio( // 업로드된 영사을 FFmpeg Command 를 통하여 원하는 초마다 프레임을자르고 반환
      selectedVideo.localFileName, // 업로드된 영상의 이름
      selectedVideo.uri, // 업로드된 영상의 uri
      filePath => { // successCallback
        console.log(filePath)
        setAudio(filePath); // 변경된 값들을 useState 를통해 다시 저장
      },
    );
  };

  const renderFrame = (frame, index) => {
    if (frame.status === FRAME_STATUS.LOADING.name.description) { // 받은 프레임의 상태가 LOADING 이라면
      return <View style={styles.loadingFrame} key={index} />; // 로딩중인 프레임 반환
    } else { // 받은 프레임의 상태가 READY 이라면
      var borderTopLeftRadius = 0;
      var borderBottomLeftRadius = 0;
      var borderTopRightRadius = 0;
      var borderBottomRightRadius = 0;
      if (index === 0) {
        borderTopLeftRadius = 10;
        borderBottomLeftRadius = 10;
      } else if (index === frames.length - 1) {
        borderTopRightRadius = 10;
        borderBottomRightRadius = 10;
      }
      return ( // 정상적인 프레임 반환
        <Image
          key={index}
          source={{ uri: 'file://' + frame.uri }} // 파일은 저장했지만 아이폰 기준으로 file://을 붙여줘야하므로 file 포함애서 반환
          style={{
            width: TILE_WIDTH,
            height: TILE_HEIGHT,
            borderTopLeftRadius: borderTopLeftRadius,
            borderBottomLeftRadius: borderBottomLeftRadius,
            borderTopRightRadius: borderTopRightRadius,
            borderBottomRightRadius: borderBottomRightRadius,
          }}
          onLoad={() => { // 다되면 이미지가 반환됬다고 알림
            console.log('Image loaded');
          }}
        />
      );
    }
  };

  const renderFrameSecond = (frame, index) => {
    var text = "•"
    if (index%5 === 0) {
      text = index + "s"
    }
    return (
      <Text style={{width: FRAME_WIDTH, color: 'white', fontSize: 10 }} index={index}>{text}</Text>
    )
  }
  
  const handleScroll = (data) => {
    //console.log(ThumbNailScrollView)
    setX(data.nativeEvent.contentOffset.x)
    setCurrentTime(data.nativeEvent.contentOffset.x / (FRAME_WIDTH))
  }

  return (
    <SafeAreaView style={styles.mainContainer}>
      {selectedVideo ? ( // 선택된 비디오가 있다면
        <>
          <View style={styles.videoContainer}>
            <Video
              style={styles.video}
              resizeMode={'cover'}
              source={{ uri: selectedVideo.uri }}
              repeat={true}
              onLoad={handleVideoLoad}
              paused={true}
              currentTime={currentTime}
            />
          </View>
          {frames && (
            <View style={styles.durationWindowAndFramesLineContainer}>
              <View style={{ flexDirection: 'row', overflow: 'hidden' }}>
                <View style={{ zIndex: 10, backgroundColor: '#676666', width: FRAME_WIDTH + 10 }}>
                  <Text style={{justifyContent: 'center', fontSize: 10, alignSelf: 'center', color: 'white' }}>
                    {parseInt(((x/FRAME_WIDTH)%3600)/60)+ '.' + parseInt((x/FRAME_WIDTH)%60) + '.' + parseInt(x%100)}
                  </Text>
                </View>
                <View style={{left: -x, flexDirection: 'row'}}>
                  <View style={{ width: FRAME_WIDTH * 2 , backgroundColor: '#60000096'}}></View>
                  <View style={{ width: FRAME_WIDTH * frames.length, flexDirection: 'row'}}>
                    {frames.map((frame, index) => renderFrameSecond(frame, index))}
                  </View>
                  <View style={{ width: FRAME_WIDTH * 2 , backgroundColor: '#60000096'}}></View>
                </View>
              </View>
              <ScrollView
                vertical
                bounces={true}
                style={{ width: DURATION_WINDOW_WIDTH * 2}}
              >
                <View style={{ flexDirection: 'row' }}>
                  <View style={{ width: FRAME_WIDTH + 10 }}>
                    <View style={{ height: FRAME_WIDTH, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 5, backgroundColor: '#676666'}}>
                      <TouchableOpacity style={{}}>
                        <Ionicons name={"musical-notes"} color="white" size={25}/>
                      </TouchableOpacity>
                    </View>
                    <View style={{ height: FRAME_WIDTH, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 5, backgroundColor: '#676666' }}>
                    <TouchableOpacity style={{}}>
                        <Ionicons name={"scan-sharp"} color="white" size={25}/>
                      </TouchableOpacity>
                    </View>
                    <View style={{ height: FRAME_WIDTH, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 5 }}>
                      <Text>video</Text>
                    </View>
                  </View>
                  <View style={styles.popLineContainer}>
                    <View style={styles.popLine} />
                  </View>
                  <View>
                    <View style={{ marginBottom: 5 }}>
                      <ScrollView
                        onScroll={handleScroll}
                        showsHorizontalScrollIndicator={false}
                        horizontal={true}
                        bounces={false}
                        style={styles.framesLine}
                        scrollEventThrottle={1}>
                        <View style={{ width: FRAME_WIDTH * 2 }}></View>
                        <View>
                          <View style={{ flexDirection: 'row', borderRadius: 10 , marginBottom: 5}}>
                            {frames.map((frame, index) => renderFrame(frame, index))}
                          </View>
                          <View style={{ flexDirection: 'row', borderRadius: 10 , marginBottom: 5}}>
                            {frames.map((frame, index) => renderFrame(frame, index))}
                          </View>
                        </View>
                        <View style={{ width: FRAME_WIDTH * 8 - 10}}></View>
                      </ScrollView>
                    </View>
                  </View>
                </View>
              </ScrollView>
              <View>
                <Text>asdasdsa</Text>
              </View>
            </View>
          )}
        </>
      ) : ( // 선택된 비디오가 없다면
        <Pressable
          style={styles.buttonContainer}
          onPress={handlePressSelectVideoButton}>
          <Text style={styles.buttonText}>Select a video</Text>
        </Pressable>
      )}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'black'
  },
  buttonContainer: {
    backgroundColor: 'white',
    paddingVertical: 12,
    paddingHorizontal: 32,
    borderRadius: 16,
  },
  buttonText: {
    color: 'blacks',
  },
  videoContainer: {
    width: SCREEN_WIDTH,
    height: 0.5 * SCREEN_HEIGHT,
    backgroundColor: 'rgba(255,255,255,0.1)',
    zIndex: 0,
  },
  video: {
    height: '100%',
    width: '100%',
  },
  durationWindowAndFramesLineContainer: {
    width: SCREEN_WIDTH ,
    zIndex: 10,
    backgroundColor: 'black'
  },
  popLineContainer: {
    position: 'absolute',
    alignSelf: POPLINE_POSITION === '50%' && 'center',
    zIndex: 25,
    left: FRAME_WIDTH * 3 + 10
  },
  popLine: {
    width: 3,
    height: SCREEN_HEIGHT * 2,
    backgroundColor: 'yellow',
  },
  framesLine: {
    width: SCREEN_WIDTH,
  },
  loadingFrame: {
    width: TILE_WIDTH,
    height: TILE_HEIGHT,
    backgroundColor: 'rgba(0,0,0,0.05)',
    borderColor: 'rgba(0,0,0,0.1)',
    borderWidth: 1,
  },
});

export default Camera;



이번엔 나머지 UI 를 제작하여보도록 하겠다.

import React, { useRef, useState } from 'react';
import {
  SafeAreaView,
  Dimensions,
  Pressable,
  Text,
  StyleSheet,
  View,
  ScrollView,
  Image,
} from 'react-native';
import Ionicons from "react-native-vector-icons/Ionicons";
import ImagePicker from 'react-native-image-crop-picker';
import { Button } from 'react-native-paper';
import Video from 'react-native-video';
import FFmpegWrapper from '../../constants/FFmpegWrapper';
import { TouchableOpacity } from 'react-native-gesture-handler';

const SCREEN_WIDTH = Dimensions.get('screen').width; // 화면 width 사이즈
const SCREEN_HEIGHT = Dimensions.get('screen').height; // 화면 height 사이즈
export const FRAME_PER_SEC = 1; // 몇초마다 끊을것인지
export const FRAME_WIDTH = 40; // 하나의 프레임당 width 길이 [노란색 border 의 프레임을 뜻함]
const TILE_HEIGHT = 40; // 4 sec. 의 높이 길이
const TILE_WIDTH = FRAME_WIDTH; // 현재 노란색 프레임의 반크기

const DURATION_WINDOW_DURATION = 2; // 프레임 몇개를 사용할것인지 
const DURATION_WINDOW_BORDER_WIDTH = 4; // 테두리 굵기
const DURATION_WINDOW_WIDTH =
  DURATION_WINDOW_DURATION * 5 * TILE_WIDTH; // 총 노란색 프레임의 width 길이
const POPLINE_POSITION = '50%'; // 노란색 프레임 중간 노란색 divder 의 위치 선정 50% === center 

const getFileNameFromPath = path => { // 파일 이름 가져오는 함수
  const fragments = path.split('/');
  let fileName = fragments[fragments.length - 1];
  fileName = fileName.split('.')[0];
  return fileName;
};

const FRAME_STATUS = Object.freeze({
  LOADING: { name: Symbol('LOADING') },
  READY: { name: Symbol('READY') },
});

const Camera = () => {
  const [selectedVideo, setSelectedVideo] = useState(); // {uri: <string>, localFileName: <string>, creationDate: <Date>}
  const [frames, setFrames] = useState(); // <[{status: <FRAME_STATUS>, uri: <string>}]>
  const [audio, setAudio] = useState(); // <[{status: <FRAME_STATUS>, uri: <string>}]>
  const [x, setX] = useState(null);
  const [currentTime, setCurrentTime] = useState(0);

  const handlePressSelectVideoButton = () => {
    ImagePicker.openPicker({
      mediaType: 'video',
    }).then(videoAsset => {
      console.log(`Selected video ${JSON.stringify(videoAsset, null, 2)}`);
      setSelectedVideo({
        uri: videoAsset.sourceURL || videoAsset.path,
        localFileName: getFileNameFromPath(videoAsset.path),
        creationDate: videoAsset.creationDate,
      });
    });
  };

  const handleVideoLoad = async videoAssetLoaded => { // 영상이 업로드 된후 동작 videoAssetLoaded 안에는 들어온 영상의 정보가 담겨져있다.
    const numberOfFrames = Math.ceil(videoAssetLoaded.duration); // 영상의 초를 반올림함  5.758999824523926 -> 6
    setFrames( // 영상이 로드가된후 초마다 만들어진 프레임의 상태를 전부 loading 으로 채워준다.
      Array(numberOfFrames).fill({
        status: FRAME_STATUS.LOADING.name.description,
      }),
    );

    await FFmpegWrapper.getFrames( // 업로드된 영사을 FFmpeg Command 를 통하여 원하는 초마다 프레임을자르고 반환
      selectedVideo.localFileName, // 업로드된 영상의 이름
      selectedVideo.uri, // 업로드된 영상의 uri
      numberOfFrames, // 프레임의 갯수
      filePath => { // successCallback
        const _framesURI = []; // 각 프레임을 담을 배열
        for (let i = 0; i < numberOfFrames; i++) {
          _framesURI.push( // 각프레임을 하나하나 담는다. 
            `${filePath.replace('%4d', String(i + 1).padStart(4, 0))}`, // FFmpegWrapper 에서 지정한 outputImagePath 의 이름중 %4d' -> padStart 를 통해 받은 인덱스 예) 0,1,2,3 -> 0001 ,0002,0003,0004 로 변경후 교체
          );
        }
        const _frames = _framesURI.map(_frameURI => ({ // 받은 배열들을 다시 map으로 반복문을 돌려 프레임마다 uri 를 저장해주고 상태를 LOADING -> READY 으로 변경해준다.
          uri: _frameURI,
          status: FRAME_STATUS.READY.name.description,
        }));
        setFrames(_frames); // 변경된 값들을 useState 를통해 다시 저장
      },
    );
    await FFmpegWrapper.getAudio( // 업로드된 영사을 FFmpeg Command 를 통하여 원하는 초마다 프레임을자르고 반환
      selectedVideo.localFileName, // 업로드된 영상의 이름
      selectedVideo.uri, // 업로드된 영상의 uri
      filePath => { // successCallback
        console.log(filePath)
        setAudio(filePath); // 변경된 값들을 useState 를통해 다시 저장
      },
    );
  };

  const renderFrame = (frame, index) => {
    if (frame.status === FRAME_STATUS.LOADING.name.description) { // 받은 프레임의 상태가 LOADING 이라면
      return <View style={styles.loadingFrame} key={index} />; // 로딩중인 프레임 반환
    } else { // 받은 프레임의 상태가 READY 이라면
      var borderTopLeftRadius = 0;
      var borderBottomLeftRadius = 0;
      var borderTopRightRadius = 0;
      var borderBottomRightRadius = 0;
      if (index === 0) {
        borderTopLeftRadius = 10;
        borderBottomLeftRadius = 10;
      } else if (index === frames.length - 1) {
        borderTopRightRadius = 10;
        borderBottomRightRadius = 10;
      }
      return ( // 정상적인 프레임 반환
        <Image
          key={index}
          source={{ uri: 'file://' + frame.uri }} // 파일은 저장했지만 아이폰 기준으로 file://을 붙여줘야하므로 file 포함애서 반환
          style={{
            width: TILE_WIDTH,
            height: TILE_HEIGHT,
            borderTopLeftRadius: borderTopLeftRadius,
            borderBottomLeftRadius: borderBottomLeftRadius,
            borderTopRightRadius: borderTopRightRadius,
            borderBottomRightRadius: borderBottomRightRadius,
          }}
          onLoad={() => { // 다되면 이미지가 반환됬다고 알림
            console.log('Image loaded');
          }}
        />
      );
    }
  };

  const renderFrameSecond = (frame, index) => {
    var text = "•"
    if (index % 5 === 0) {
      text = index + "s"
    }
    return (
      <Text style={{ width: FRAME_WIDTH, color: 'white', fontSize: 10 }} index={index}>{text}</Text>
    )
  }

  const handleScroll = (data) => {
    //console.log(ThumbNailScrollView)
    setX(data.nativeEvent.contentOffset.x)
    setCurrentTime(data.nativeEvent.contentOffset.x / (FRAME_WIDTH))
  }

  return (
    <SafeAreaView style={styles.mainContainer}>
      {selectedVideo ? ( // 선택된 비디오가 있다면
        <>
          <View style={styles.videoContainer}>
            <Video
              style={styles.video}
              resizeMode={'cover'}
              source={{ uri: selectedVideo.uri }}
              repeat={true}
              onLoad={handleVideoLoad}
              paused={true}
              currentTime={currentTime}
            />
          </View>
          {frames && (
            <View style={styles.durationWindowAndFramesLineContainer}>
              <View style={{ flexDirection: 'row', overflow: 'hidden', paddingVertical: 10 }}>
                <View style={{ zIndex: 10, backgroundColor: '#676666', width: FRAME_WIDTH + 10 }}>
                  <Text style={{ justifyContent: 'center', fontSize: 10, alignSelf: 'center', color: 'white' }}>
                    {parseInt(((x / FRAME_WIDTH) % 3600) / 60) + '.' + parseInt((x / FRAME_WIDTH) % 60) + '.' + parseInt(x % 100)}
                  </Text>
                </View>
                <View style={{ left: -x, flexDirection: 'row' }}>
                  <View style={{ width: FRAME_WIDTH * 2, backgroundColor: '#60000096' }}></View>
                  <View style={{ width: FRAME_WIDTH * frames.length, flexDirection: 'row' }}>
                    {frames.map((frame, index) => renderFrameSecond(frame, index))}
                  </View>
                  <View style={{ width: SCREEN_WIDTH, backgroundColor: '#60000096' }}></View>
                </View>
              </View>
              <ScrollView
                vertical
                bounces={true}
                style={{ width: DURATION_WINDOW_WIDTH * 2, overflow: 'hidden', height: SCREEN_HEIGHT * 0.2}}
              >
                <View style={{ flexDirection: 'row' }}>
                  <View style={{ width: FRAME_WIDTH + 10}}>
                    <View style={{ height: FRAME_WIDTH, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 5, backgroundColor: '#676666' }}>
                      <TouchableOpacity style={{}}>
                        <Ionicons name={"musical-notes"} color="white" size={25} />
                      </TouchableOpacity>
                    </View>
                    <View style={{ height: FRAME_WIDTH, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 5, backgroundColor: '#676666' }}>
                      <TouchableOpacity style={{}}>
                        <Ionicons name={"scan-sharp"} color="white" size={25} />
                      </TouchableOpacity>
                    </View>
                    <View style={{ height: FRAME_WIDTH, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 5, backgroundColor: '#676666' }}>
                      <TouchableOpacity style={{}}>
                        <Ionicons name={"mic"} color="white" size={25} />
                      </TouchableOpacity>
                    </View>
                    <View style={{ height: FRAME_WIDTH, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginBottom: 5, backgroundColor: '#676666' }}>
                      <TouchableOpacity style={{}}>
                        <Ionicons name={"md-add"} color="white" size={25} />
                      </TouchableOpacity>
                    </View>
                  </View>
                  <View style={styles.popLineContainer}>
                    <View style={styles.popLine} />
                  </View>
                  <View>
                    <View style={{ marginBottom: 5 }}>
                      <ScrollView
                        onScroll={handleScroll}
                        showsHorizontalScrollIndicator={false}
                        horizontal={true}
                        bounces={true}
                        style={styles.framesLine}
                        scrollEventThrottle={1}>
                        <View style={{ width: FRAME_WIDTH * 2 }}></View>
                        <View>
                          <View style={{ flexDirection: 'row', borderRadius: 10, marginBottom: 5 }}>
                            {frames.map((frame, index) => renderFrame(frame, index))}
                          </View>
                          <View style={{ flexDirection: 'row', borderRadius: 10, marginBottom: 5 }}>
                            {frames.map((frame, index) => renderFrame(frame, index))}
                          </View>
                        </View>
                        <View style={{ width: FRAME_WIDTH * 8 - 10 }}></View>
                      </ScrollView>
                    </View>
                  </View>
                </View>
              </ScrollView>
              <View style={{
                width: SCREEN_WIDTH,
                shadowColor: "black",
                shadowOffset: {
                  width: 0,
                  height: -10,
                },
                shadowOpacity: 0.9,
              }}>
                <View style={{ width: SCREEN_WIDTH, flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 25,alignItems: 'center' }}>
                  <View>
                    <TouchableOpacity style={{}}>
                      <Ionicons name={"ios-options-sharp"} color="white" size={25} />
                    </TouchableOpacity>
                  </View>
                  <View style={{ flexDirection: 'row', justifyContent: 'space-between', width: SCREEN_WIDTH * 0.45,alignItems: 'center' }}>
                    <View>
                      <TouchableOpacity style={{}}>
                        <Ionicons name={"play-skip-back"} color="white" size={25} />
                      </TouchableOpacity>
                    </View>
                    <View>
                      <TouchableOpacity style={{}}>
                        <Ionicons name={"radio-button-on"} color="red" size={55} />
                      </TouchableOpacity>
                    </View>
                    <View>
                      <TouchableOpacity>
                        <Ionicons name={"play-sharp"} color="white" size={25} />
                      </TouchableOpacity>
                    </View>
                  </View>
                  <View>
                    <TouchableOpacity style={{}}>
                      <View style={{ backgroundColor: 'white', borderRadius: 20, paddingHorizontal: 10, paddingVertical: 10 }}>
                        <Text style={{ fontWeight: 'bold' }}>다음 ></Text>
                      </View>
                    </TouchableOpacity>
                  </View>
                </View>
              </View>
              <View style={{width: SCREEN_WIDTH, paddingHorizontal: 15}}>
                <ScrollView
                  horizontal
                >
                  <View style={{flexDirection: 'row'}}>
                    <TouchableOpacity style={{backgroundColor: '#676666', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, marginRight: 5}}>
                      <Ionicons name={"md-pulse-sharp"} color="white" size={20} style={{marginBottom: 3,alignSelf: 'center'}}/>
                      <Text style={{color: 'white', fontSize: 10}}>이팩트</Text>
                    </TouchableOpacity>
                    <TouchableOpacity style={{backgroundColor: '#676666', paddingHorizontal: 10, paddingVertical: 8, borderRadius: 8, marginRight: 5}}>
                      <Ionicons name={"md-pause-outline"} color="white" size={20} style={{marginBottom: 3,alignSelf: 'center'}}/>
                      <Text style={{color: 'white', fontSize: 10}}>브레이크</Text>
                    </TouchableOpacity>
                    <TouchableOpacity style={{backgroundColor: '#676666', paddingHorizontal: 10, paddingVertical: 8, borderRadius: 8, marginRight: 5}}>
                      <Ionicons name={"speedometer-outline"} color="white" size={20} style={{marginBottom: 3,alignSelf: 'center'}}/>
                      <Text style={{color: 'white', fontSize: 10}}>싱크 조정</Text>
                    </TouchableOpacity>
                    <TouchableOpacity style={{backgroundColor: '#676666', paddingHorizontal: 10, paddingVertical: 8, borderRadius: 8, marginRight: 5}}>
                      <Ionicons name={"md-copy-outline"} color="white" size={20} style={{marginBottom: 3,alignSelf: 'center'}}/>
                      <Text style={{color: 'white', fontSize: 10}}>트랙 복제</Text>
                    </TouchableOpacity>
                    <TouchableOpacity style={{backgroundColor: '#676666', paddingHorizontal: 10, paddingVertical: 8, borderRadius: 8, marginRight: 5}}>
                      <Ionicons name={"trash-outline"} color="white" size={20} style={{marginBottom: 3,alignSelf: 'center'}}/>
                      <Text style={{color: 'white', fontSize: 10}}>트랙 삭제</Text>
                    </TouchableOpacity>
                    <TouchableOpacity style={{backgroundColor: '#676666', paddingHorizontal: 16, paddingVertical: 9, borderRadius: 8, marginRight: 5}}>
                      <Ionicons name={"reload"} color="white" size={20} style={{marginBottom: 3,alignSelf: 'center'}}/>
                      <Text style={{color: 'white', fontSize: 10}}>초기화</Text>
                    </TouchableOpacity>
                  </View>
                </ScrollView>
                </View>
            </View>
          )}
        </>
      ) : ( // 선택된 비디오가 없다면
        <Pressable
          style={styles.buttonContainer}
          onPress={handlePressSelectVideoButton}>
          <Text style={styles.buttonText}>Select a video</Text>
        </Pressable>
      )}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  mainContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'black'
  },
  buttonContainer: {
    backgroundColor: 'white',
    paddingVertical: 12,
    paddingHorizontal: 32,
    borderRadius: 16,
  },
  buttonText: {
    color: 'blacks',
  },
  videoContainer: {
    width: SCREEN_WIDTH,
    height: 0.5 * SCREEN_HEIGHT,
    backgroundColor: 'rgba(255,255,255,0.1)',
    zIndex: 0,
  },
  video: {
    height: '100%',
    width: '100%',
  },
  durationWindowAndFramesLineContainer: {
    width: SCREEN_WIDTH,
    zIndex: 10,
    backgroundColor: 'black'
  },
  popLineContainer: {
    position: 'absolute',
    alignSelf: POPLINE_POSITION === '50%' && 'center',
    zIndex: 25,
    left: FRAME_WIDTH * 3 + 10
  },
  popLine: {
    width: 3,
    height: SCREEN_HEIGHT * 2,
    backgroundColor: 'yellow',
  },
  framesLine: {
    width: SCREEN_WIDTH,
  },
  loadingFrame: {
    width: TILE_WIDTH,
    height: TILE_HEIGHT,
    backgroundColor: 'rgba(0,0,0,0.05)',
    borderColor: 'rgba(0,0,0,0.1)',
    borderWidth: 1,
  },
});

export default Camera;

 

 

생각보다 나머지 작업은 쉽게 끝나서 기부니가 너무 좋았다...ㅎㅎㅎ

 

참고로 저기있는 고양이는 우리집 고양이 숙희이다

 

그리고 현재 하고있는 프로잭트는 baund 라는 어플리케이션을 클론하고있다.

궁금한사람은 baund 를 다운받아서 비교해봐도 좋다!!!

백퍼센트 똑같지는 않지만 어느정도 비슷하다 ㅎㅎㅎㅎ

 

그럼 이번 포스팅은 이것으로 마치겠고

 

다음시간에 상단의 영상대신 어떻게든 오디오를 불러와 보여주는 작업을 해볼것이다....

하지만 엄청 어려워보인다 단순 오디오를 가져오는것이아니고 wave 를 보여줄생각이다..

그러려면 오디오의 정보를 다 받아와 해석해서 뿌려줄수있어야하는데 아무래도 이쪽전문가가 아니니 한동안 삽질하느라 포스팅을 못올릴것같다...ㅋㅋ

 

그럼 다음시간에 보도록하겠다

 

그럼!

이만!






# 깃허브 주소



https://github.com/1domybest/react-native-baund-clone.git

 

GitHub - 1domybest/react-native-baund-clone

Contribute to 1domybest/react-native-baund-clone development by creating an account on GitHub.

github.com



728x90
Comments