Notice
Recent Posts
Recent Comments
Link
250x250
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- react-native
- 개발
- 풀스택
- Spring
- 스프링 부트
- Redux
- react
- 국비 지원
- 개발자
- 프론트엔드
- 풀스택 개발자
- 프론트 엔드
- 스프링
- 상태관리
- ffmpeg
- 비전공자
- 리엑트
- 백엔드
- 비전공
- 국비지원
- 인스타그램
- expo
- 코딩
- Java
- spring boot
- 리엑트 네이티브
- react native
- 스타트업
- 클론코딩
- 자바
Archives
- Today
- Total
오티스의개발일기
[REACT-NATIVE] ffmpeg 를 활용하여 받아온 비디오 react-native 에서 컨트롤하고 정보 보여주기 본문
개발/react-native
[REACT-NATIVE] ffmpeg 를 활용하여 받아온 비디오 react-native 에서 컨트롤하고 정보 보여주기
안되면 될때까지.. 2023. 1. 16. 20:50728x90
< 이전글
다음글 >
2023.01.19 - [개발/FFmpeg] - [FFMPEG] ffmpeg-kit-react-native FFprobeKit 화가나서 존재하는 모든 함수 뽀개기...
오늘은 저번시간에 받아온 비디오를 클라이언트단에 보여주고 또한 영상에 대한 길이 그리고 현재 보고있는 장면이 몇분 몇초인지
알려줄수있는 클라이언트 단을 구현해볼것이다.
일단 결과물먼저 보도록 하겠다.
두가지를 보여줄건데 이작업안에서는 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
728x90
'개발 > react-native' 카테고리의 다른 글
[REACT-NATIVE] ffmpeg 사용하여 받은 오디오를 wave 형식으로 클라이언트에 보여주기 (8) | 2023.01.20 |
---|---|
[REACT-NATIVE] ffmpeg 사용을 위한 Video CurrentTime 조작하기 (1) | 2023.01.15 |
[REACT NATIVE] 인스타그램 클론 코딩 (18) 마지막....ㅠㅠ react-native-image-picker 를 사용한 새 게시물 업로드 (10) | 2023.01.04 |
[REACT NATIVE] 인스타그램 클론 코딩 (17) navigation을 이용하여 새 게시물 스택 만들기 (2) | 2023.01.02 |
[REACT NATIVE] 인스타그램 클론 코딩 (16) react-native-paper 사용하여 메뉴바 만들기! (5) | 2023.01.02 |
Comments