get select video button + compression working
This commit is contained in:
+1
-1
@@ -202,7 +202,7 @@ module.exports = function (config) {
|
||||
sounds: PLATFORM === 'ios' ? ['assets/dm.aiff'] : ['assets/dm.mp3'],
|
||||
},
|
||||
],
|
||||
['@config-plugins/ffmpeg-kit-react-native', {package: 'video'}],
|
||||
['@config-plugins/ffmpeg-kit-react-native', {package: 'min-gpl'}],
|
||||
'./plugins/withAndroidManifestPlugin.js',
|
||||
'./plugins/withAndroidManifestFCMIconPlugin.js',
|
||||
'./plugins/withAndroidStylesWindowBackgroundPlugin.js',
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v2h2V5H5Zm4 0v6h6V5H9Zm8 0v2h2V5h-2Zm2 4h-2v2h2V9Zm0 4h-2v2.444h2V13Zm0 4.444h-2V19h2v-1.556ZM15 19v-6H9v6h6Zm-8 0v-2H5v2h2Zm-2-4h2v-2H5v2Zm0-4h2V9H5v2Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 370 B |
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const VideoClip_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v2h2V5H5Zm4 0v6h6V5H9Zm8 0v2h2V5h-2Zm2 4h-2v2h2V9Zm0 4h-2v2.444h2V13Zm0 4.444h-2V19h2v-1.556ZM15 19v-6H9v6h6Zm-8 0v-2H5v2h2Zm-2-4h2v-2H5v2Zm0-4h2V9H5v2Z',
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import {FFmpegKit, ReturnCode} from 'ffmpeg-kit-react-native'
|
||||
|
||||
const PRESET = 'faster'
|
||||
|
||||
export async function compressVideo(file: string) {
|
||||
const ext = file.split('.').pop()
|
||||
const newFile = file.replace(`.${ext}`, 'compressed.mp4')
|
||||
const result = await FFmpegKit.execute(
|
||||
`-i ${file} -c:v libx264 -crf 28 -preset ${PRESET} -b:v 1M -vf "scale=-1:720" -t 90 -c:a aac -b:a 128k -movflags +faststart ${newFile}`,
|
||||
)
|
||||
|
||||
return {
|
||||
uri: ReturnCode.isSuccess(await result.getReturnCode()) ? newFile : null,
|
||||
session: result,
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,9 @@ import {TextInput, TextInputRef} from './text-input/TextInput'
|
||||
import {ThreadgateBtn} from './threadgate/ThreadgateBtn'
|
||||
import {useExternalLinkFetch} from './useExternalLinkFetch'
|
||||
import hairlineWidth = StyleSheet.hairlineWidth
|
||||
import {SelectVideoBtn} from './videos/SelectVideoBtn'
|
||||
import {useVideoState} from './videos/state'
|
||||
import {VideoPreview} from './videos/VideoPreview'
|
||||
|
||||
type CancelRef = {
|
||||
onPressCancel: () => void
|
||||
@@ -150,6 +153,9 @@ export const ComposePost = observer(function ComposePost({
|
||||
const [quote, setQuote] = useState<ComposerOpts['quote'] | undefined>(
|
||||
initQuote,
|
||||
)
|
||||
const {video, onSelectVideo, setVideoPending, videoPending} = useVideoState({
|
||||
setError,
|
||||
})
|
||||
const {extLink, setExtLink} = useExternalLinkFetch({setQuote})
|
||||
const [extGif, setExtGif] = useState<Gif>()
|
||||
const [labels, setLabels] = useState<string[]>([])
|
||||
@@ -358,8 +364,8 @@ export const ComposePost = observer(function ComposePost({
|
||||
? _(msg`Write your reply`)
|
||||
: _(msg`What's up?`)
|
||||
|
||||
const canSelectImages = gallery.size < 4 && !extLink
|
||||
const hasMedia = gallery.size > 0 || Boolean(extLink)
|
||||
const canSelectImages = gallery.size < 4 && !extLink && !video
|
||||
const hasMedia = gallery.size > 0 || Boolean(extLink) || Boolean(video)
|
||||
|
||||
const onEmojiButtonPress = useCallback(() => {
|
||||
openPicker?.(textInput.current?.getCursorPosition())
|
||||
@@ -583,7 +589,8 @@ export const ComposePost = observer(function ComposePost({
|
||||
<QuoteX onRemove={() => setQuote(undefined)} />
|
||||
)}
|
||||
</View>
|
||||
) : undefined}
|
||||
) : null}
|
||||
{video && <VideoPreview video={video} />}
|
||||
</Animated.ScrollView>
|
||||
<SuggestedLanguage text={richtext.text} />
|
||||
|
||||
@@ -602,6 +609,12 @@ export const ComposePost = observer(function ComposePost({
|
||||
]}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<SelectPhotoBtn gallery={gallery} disabled={!canSelectImages} />
|
||||
<SelectVideoBtn
|
||||
onSelectVideo={onSelectVideo}
|
||||
disabled={!canSelectImages}
|
||||
pending={videoPending}
|
||||
setPending={setVideoPending}
|
||||
/>
|
||||
<OpenCameraBtn gallery={gallery} disabled={!canSelectImages} />
|
||||
<SelectGifBtn
|
||||
onClose={focusTextInput}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {
|
||||
ImagePickerAsset,
|
||||
launchImageLibraryAsync,
|
||||
MediaTypeOptions,
|
||||
} from 'expo-image-picker'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {VideoClip_Stroke2_Corner0_Rounded as VideoClipIcon} from '#/components/icons/VideoClip'
|
||||
|
||||
const VIDEO_MAX_DURATION = 90
|
||||
|
||||
type Props = {
|
||||
onSelectVideo: (video: ImagePickerAsset) => void
|
||||
disabled?: boolean
|
||||
pending: boolean
|
||||
setPending: (pending: boolean) => void
|
||||
}
|
||||
|
||||
export function SelectVideoBtn({
|
||||
onSelectVideo,
|
||||
disabled,
|
||||
pending,
|
||||
setPending,
|
||||
}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
const onPressSelectVideo = useCallback(async () => {
|
||||
try {
|
||||
setPending(true)
|
||||
const response = await launchImageLibraryAsync({
|
||||
exif: false,
|
||||
mediaTypes: MediaTypeOptions.Videos,
|
||||
videoMaxDuration: VIDEO_MAX_DURATION,
|
||||
quality: 1,
|
||||
})
|
||||
if (response.assets && response.assets.length > 0) {
|
||||
onSelectVideo(response.assets[0])
|
||||
}
|
||||
} finally {
|
||||
setPending(false)
|
||||
}
|
||||
}, [onSelectVideo, setPending])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
testID="openGifBtn"
|
||||
onPress={onPressSelectVideo}
|
||||
label={_(msg`Select GIF`)}
|
||||
accessibilityHint={_(msg`Opens GIF select dialog`)}
|
||||
style={a.p_sm}
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
color="primary"
|
||||
disabled={disabled || pending}>
|
||||
<VideoClipIcon
|
||||
size="lg"
|
||||
style={(disabled || pending) && t.atoms.text_contrast_low}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import * as FileSystem from 'expo-file-system'
|
||||
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function VideoPreview({video}: {video: FileSystem.FileInfo}) {
|
||||
return <Text>{JSON.stringify(video, null, 2)}</Text>
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {useState} from 'react'
|
||||
import * as FileSystem from 'expo-file-system'
|
||||
import {ImagePickerAsset} from 'expo-image-picker'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {compressVideo} from '#/lib/media/video/compress'
|
||||
|
||||
export function useVideoState({setError}: {setError: (error: string) => void}) {
|
||||
const [pending, setVideoPending] = useState(false)
|
||||
const {_} = useLingui()
|
||||
|
||||
const {mutate, data, isPending, isError, reset} = useMutation({
|
||||
mutationFn: async (asset: ImagePickerAsset) => {
|
||||
const compressed = await compressVideo(asset.uri)
|
||||
if (!compressed.uri) {
|
||||
throw new Error('Failed to compress video')
|
||||
}
|
||||
|
||||
const res = await FileSystem.getInfoAsync(compressed.uri, {size: true})
|
||||
if (res.exists) {
|
||||
console.log(
|
||||
'uncompressed size',
|
||||
(asset.fileSize! / 1024 / 1024).toFixed(2) + 'mb',
|
||||
)
|
||||
console.log(
|
||||
'compressed size',
|
||||
(res.size / 1024 / 1024).toFixed(2) + 'mb',
|
||||
)
|
||||
return res
|
||||
} else {
|
||||
throw new Error('Could not find output video')
|
||||
}
|
||||
},
|
||||
onError: error => {
|
||||
console.error('error', error)
|
||||
setError(_(msg`Could not compress video`))
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
video: data,
|
||||
onSelectVideo: mutate,
|
||||
videoPending: pending || isPending,
|
||||
setVideoPending,
|
||||
videoError: isError,
|
||||
clearVideo: reset,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user