diff options
-rw-r--r-- | src/components/comments/ZoomInCropper.tsx | 62 | ||||
-rw-r--r-- | src/components/moments/trimmer.tsx | 98 | ||||
-rw-r--r-- | src/screens/moments/CameraScreen.tsx | 60 | ||||
-rw-r--r-- | src/screens/profile/CaptionScreen.tsx | 2 | ||||
-rw-r--r-- | src/utils/camera.ts | 34 | ||||
-rw-r--r-- | src/utils/users.ts | 1 |
6 files changed, 192 insertions, 65 deletions
diff --git a/src/components/comments/ZoomInCropper.tsx b/src/components/comments/ZoomInCropper.tsx index 6f8ff97c..b4333cbb 100644 --- a/src/components/comments/ZoomInCropper.tsx +++ b/src/components/comments/ZoomInCropper.tsx @@ -9,13 +9,14 @@ import CloseIcon from '../../assets/ionicons/close-outline.svg'; import {MainStackParams} from '../../routes'; import { cropVideo, + trimVideo, HeaderHeight, SCREEN_HEIGHT, SCREEN_WIDTH, } from '../../utils'; -import {TaggSquareButton} from '../common'; +import {TaggSquareButton, TaggLoadingIndicator} from '../common'; import ReactNativeZoomableView from '@dudigital/react-native-zoomable-view/src/ReactNativeZoomableView'; -import Video from 'react-native-video'; +import {TrimmerPlayer} from '../moments/trimmer'; type ZoomInCropperRouteProps = RouteProp<MainStackParams, 'ZoomInCropper'>; type ZoomInCropperNavigationProps = StackNavigationProp< @@ -36,6 +37,7 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ // width and height of video, if video const [origDimensions, setOrigDimensions] = useState<number[]>([0, 0]); const vidRef = useRef<View>(null); + const [cropLoading, setCropLoading] = useState<boolean>(false); // Stores the coordinates of the cropped image const [x0, setX0] = useState<number>(); @@ -51,6 +53,15 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ cropOffsetY?: number; }>({}); + // Stores the current trim endpoints + const [trimEnds, setTrimEnds] = useState<{ + end: number; + start: number; + }>({ + end: 60, + start: 0, + }); + const checkIfUriImage = (uri: string) => { return ( uri.endsWith('jpg') || @@ -136,19 +147,26 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ cropHeight: origDimensions[1], })); } - cropVideo( + setCropLoading(true); + trimVideo( media.uri, - (croppedURL: string) => { - navigation.navigate('CaptionScreen', { - screenType, - media: { - uri: croppedURL, - isVideo: true, + (trimmedURL: string) => + cropVideo( + trimmedURL, + (croppedURL: string) => { + setCropLoading(false); + navigation.navigate('CaptionScreen', { + screenType, + media: { + uri: croppedURL, + isVideo: true, + }, + selectedCategory, + }); }, - selectedCategory, - }); - }, - videoCrop, + videoCrop, + ), + trimEnds, ); } }; @@ -255,6 +273,7 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ return ( <View style={styles.container}> + {cropLoading && <TaggLoadingIndicator fullscreen />} <TouchableOpacity style={styles.closeButton} onPress={() => navigation.goBack()}> @@ -304,22 +323,23 @@ export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ }} style={styles.zoomView}> <View style={styles.videoParent} ref={vidRef}> - <Video - source={{uri: media.uri}} - volume={1} - style={[ + <TrimmerPlayer + hideTrimmer={false} + source={media.uri} + videoStyles={[ styles.media, { height: SCREEN_WIDTH / aspectRatio, }, ]} - repeat={true} - resizeMode={'contain'} - onLoad={(response) => { - const {width, height} = response.naturalSize; + handleLoad={(response: {width: number; height: number}) => { + const {width, height} = response; setOrigDimensions([width, height]); setAspectRatio(width / height); }} + onChangedEndpoints={(response: {start: number; end: number}) => + setTrimEnds(response) + } /> </View> </ReactNativeZoomableView> diff --git a/src/components/moments/trimmer.tsx b/src/components/moments/trimmer.tsx new file mode 100644 index 00000000..c99eaa6f --- /dev/null +++ b/src/components/moments/trimmer.tsx @@ -0,0 +1,98 @@ +import React, {useEffect, useState} from 'react'; +import Video from 'react-native-video'; +import {Trimmer} from 'react-native-video-processing'; +import {useRef} from 'react'; +import {SCREEN_WIDTH} from '../../utils'; + +export const TrimmerPlayer: React.FC<{ + source: string; + videoStyles: Object; + hideTrimmer: boolean; + handleLoad: Function; + onChangedEndpoints: Function; +}> = ({source, videoStyles, hideTrimmer, handleLoad, onChangedEndpoints}) => { + // Stores the reference to player for seeking + const playerRef = useRef<Video>(); + // Stores where the video is playing (seekTime) + const [seekTime, setSeekTime] = useState<number>(0); + const [paused, setPaused] = useState<boolean>(false); + // Stores where the tracker is + const [trackerTime, setTrackerTime] = useState<number>(0); + // Stores start/end of desired trimmed video + const [end, setEnd] = useState<number>(60); + const [start, setStart] = useState<number>(0); + + useEffect(() => { + playerRef.current?.seek(seekTime); + }, [seekTime]); + useEffect(() => { + if (!paused && (trackerTime >= end || trackerTime < start)) { + setTrackerTime(start); + playerRef.current?.seek(start); + } + }, [trackerTime]); + useEffect(() => { + setSeekTime(start); + setTrackerTime(start); + }, [start]); + useEffect(() => { + setSeekTime(end); + setTrackerTime(start); + }, [end]); + // Callback so parent knows where the trimming endpts are + useEffect(() => onChangedEndpoints({end, start}), [end, start]); + + return ( + <> + <Video + // link to descr and use of props of video player -> + // https://github.com/react-native-video/react-native-video + ref={(ref) => { + playerRef.current = ref || undefined; + }} + source={{uri: source}} + rate={1.0} + volume={1.0} + muted={false} + paused={paused} + resizeMode={'contain'} + repeat={true} + onLoad={(payload) => { + console.log(payload, source); + setEnd(payload.duration); + handleLoad(payload.naturalSize); + }} + onProgress={(e) => { + if (!paused) { + setTrackerTime(e.currentTime); + } + }} // Callback every ~250ms with currentTime + style={videoStyles} + onTouchEnd={() => { + setPaused((state) => !state); + }} + /> + <Trimmer + // link to descr and use of props for trimmer -> + // https://github.com/shahen94/react-native-video-processing + source={source} + height={hideTrimmer ? 0 : 75} + width={hideTrimmer ? 0 : SCREEN_WIDTH} + borderWidth={hideTrimmer ? 0 : 100} + onTrackerMove={(e: {currentTime: number}) => { + setPaused(true); + setSeekTime(e.currentTime); + }} + currentTime={trackerTime} + themeColor={'white'} + thumbWidth={10} + trackerColor={'white'} + onChange={(e: {endTime: number; startTime: number}) => { + setPaused(true); + setEnd(e.endTime); + setStart(e.startTime); + }} + /> + </> + ); +}; diff --git a/src/screens/moments/CameraScreen.tsx b/src/screens/moments/CameraScreen.tsx index ee5834cb..18e04261 100644 --- a/src/screens/moments/CameraScreen.tsx +++ b/src/screens/moments/CameraScreen.tsx @@ -123,33 +123,37 @@ const CameraScreen: React.FC<CameraScreenProps> = ({route, navigation}) => { ) : ( <FlipButton cameraType={cameraType} setCameraType={setCameraType} /> )} - <TouchableOpacity - activeOpacity={1} - onLongPress={() => { - takeVideo(cameraRef, (vid) => { - navigateToCaptionScreen(true, vid.uri); - }); - setIsRecording(true); - }} - onPressOut={async () => { - if (await cameraRef.current?.isRecording()) { - cameraRef.current?.stopRecording(); - setIsRecording(false); + {!showSaveButton ? ( + <TouchableOpacity + style={ + isRecording + ? styles.captureButtonVideoContainer + : styles.captureButtonContainer } - }} - onPress={() => { - takePicture(cameraRef, (pic) => { - setShowSaveButton(true); - setMediaFromGallery(pic.uri); - }); - }} - style={ - isRecording - ? styles.captureButtonVideoContainer - : styles.captureButtonContainer - }> - <View style={styles.captureButton} /> - </TouchableOpacity> + activeOpacity={1} + onLongPress={() => { + takeVideo(cameraRef, (vid) => { + navigateToCaptionScreen(true, vid.uri); + }); + setIsRecording(true); + }} + onPressOut={async () => { + if (await cameraRef.current?.isRecording()) { + cameraRef.current?.stopRecording(); + setIsRecording(false); + } + }} + onPress={() => { + takePicture(cameraRef, (pic) => { + setShowSaveButton(true); + setMediaFromGallery(pic.uri); + }); + }}> + <View style={styles.captureButton} /> + </TouchableOpacity> + ) : ( + <View style={styles.captureButtonPlaceholder} /> + )} {isRecording && ( <AnimatedCircularProgress size={95} @@ -217,6 +221,10 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, + captureButtonPlaceholder: { + width: 93, + height: 93, + }, captureButtonContainer: { alignSelf: 'center', backgroundColor: 'transparent', diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 6bcf0e24..eba3e4bf 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -49,7 +49,6 @@ import {RootState} from '../../store/rootReducer'; import {MomentTagType} from '../../types'; import {isIPhoneX, normalize, SCREEN_WIDTH, StatusBarHeight} from '../../utils'; import {mentionPartTypes} from '../../utils/comments'; - /** * Upload Screen to allow users to upload posts to Tagg */ @@ -362,6 +361,7 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { </SearchBackground> ); }; + const styles = StyleSheet.create({ contentContainer: { paddingTop: StatusBarHeight, diff --git a/src/utils/camera.ts b/src/utils/camera.ts index 9e37d62e..5485b1ca 100644 --- a/src/utils/camera.ts +++ b/src/utils/camera.ts @@ -9,7 +9,7 @@ import { TakePictureResponse, } from 'react-native-camera'; import {ProcessingManager} from 'react-native-video-processing'; -import ImagePicker, {ImageOrVideo, Video} from 'react-native-image-crop-picker'; +import ImagePicker, {ImageOrVideo} from 'react-native-image-crop-picker'; import {ERROR_UPLOAD} from '../constants/strings'; /* @@ -66,6 +66,7 @@ export const navigateToImagePicker = ( 'UserLibrary', ], mediaType: 'any', + compressVideoPreset: 'Passthrough', }) .then((media) => { callback(media); @@ -77,22 +78,6 @@ export const navigateToImagePicker = ( }); }; -export const navigateToVideoPicker = (callback: (vid: Video) => void) => { - ImagePicker.openPicker({ - mediaType: 'video', - }) - .then(async (vid) => { - if (vid.path) { - callback(vid); - } - }) - .catch((err) => { - if (err.code && err.code !== 'E_PICKER_CANCELLED') { - Alert.alert(ERROR_UPLOAD); - } - }); -}; - export const showGIFFailureAlert = (onSuccess: () => void) => Alert.alert( 'Warning', @@ -118,6 +103,21 @@ export const showGIFFailureAlert = (onSuccess: () => void) => }, ); +export const trimVideo = ( + sourceUri: string, + handleData: (data: any) => any, + ends: { + start: number; + end: number; + }, +) => { + ProcessingManager.trim(sourceUri, { + startTime: ends.start / 2, //needed divide by 2 for bug in module + endTime: ends.end, + quality: 'passthrough', + }).then((data: any) => handleData(data)); +}; + export const cropVideo = ( sourceUri: string, handleData: (data: any) => any, diff --git a/src/utils/users.ts b/src/utils/users.ts index c1c3b8bc..992d7721 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -280,6 +280,7 @@ export const patchProfile = async ( screenTitle = ''; requestTitle = ''; fileName = ''; + imageSettings = {}; } return await ImagePicker.openPicker(imageSettings) |