get video restoration working on native
This commit is contained in:
@@ -42,6 +42,7 @@ import Animated, {
|
|||||||
ZoomOut,
|
ZoomOut,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
|
import * as FileSystem from 'expo-file-system'
|
||||||
import {type ImagePickerAsset} from 'expo-image-picker'
|
import {type ImagePickerAsset} from 'expo-image-picker'
|
||||||
import {
|
import {
|
||||||
AppBskyUnspeccedDefs,
|
AppBskyUnspeccedDefs,
|
||||||
@@ -320,7 +321,7 @@ export const ComposePost = ({
|
|||||||
onInitVideo()
|
onInitVideo()
|
||||||
}, [onInitVideo])
|
}, [onInitVideo])
|
||||||
|
|
||||||
const clearVideo = React.useCallback(
|
const clearVideo = useCallback(
|
||||||
(postId: string) => {
|
(postId: string) => {
|
||||||
composerDispatch({
|
composerDispatch({
|
||||||
type: 'update_post',
|
type: 'update_post',
|
||||||
@@ -333,7 +334,7 @@ export const ComposePost = ({
|
|||||||
[composerDispatch],
|
[composerDispatch],
|
||||||
)
|
)
|
||||||
|
|
||||||
const restoreVideo = React.useCallback(
|
const restoreVideo = useCallback(
|
||||||
async (postId: string, videoInfo: RestoredVideo) => {
|
async (postId: string, videoInfo: RestoredVideo) => {
|
||||||
try {
|
try {
|
||||||
logger.debug('restoring video from draft', {
|
logger.debug('restoring video from draft', {
|
||||||
@@ -354,15 +355,25 @@ export const ComposePost = ({
|
|||||||
})
|
})
|
||||||
asset = await getVideoMetadata(file)
|
asset = await getVideoMetadata(file)
|
||||||
} else {
|
} else {
|
||||||
// Native: Get video metadata using react-native-compressor
|
let uri = videoInfo.uri
|
||||||
const {getVideoMetaData} = require('react-native-compressor')
|
if (IS_ANDROID) {
|
||||||
const metadata = await getVideoMetaData(videoInfo.uri)
|
// Android: expo-file-system double-encodes filenames with special chars.
|
||||||
asset = {
|
// The file exists, but react-native-compressor's MediaMetadataRetriever
|
||||||
uri: videoInfo.uri,
|
// can't handle the double-encoded URI. Copy to a temp file with a simple name.
|
||||||
mimeType: videoInfo.mimeType,
|
const sourceFile = new FileSystem.File(videoInfo.uri)
|
||||||
width: metadata.width,
|
const tempFileName = `draft-video-${Date.now()}.${mimeToExt(videoInfo.mimeType)}`
|
||||||
height: metadata.height,
|
const tempFile = new FileSystem.File(
|
||||||
|
FileSystem.Paths.cache,
|
||||||
|
tempFileName,
|
||||||
|
)
|
||||||
|
sourceFile.copy(tempFile)
|
||||||
|
logger.debug('restoreVideo: copied to temp file', {
|
||||||
|
source: videoInfo.uri,
|
||||||
|
temp: tempFile.uri,
|
||||||
|
})
|
||||||
|
uri = tempFile.uri
|
||||||
}
|
}
|
||||||
|
asset = await getVideoMetadata(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start video processing using existing flow
|
// Start video processing using existing flow
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {getVideoMetaData} from 'react-native-compressor'
|
||||||
import {
|
import {
|
||||||
type ImagePickerAsset,
|
type ImagePickerAsset,
|
||||||
launchImageLibraryAsync,
|
launchImageLibraryAsync,
|
||||||
@@ -5,6 +6,7 @@ import {
|
|||||||
} from 'expo-image-picker'
|
} from 'expo-image-picker'
|
||||||
|
|
||||||
import {VIDEO_MAX_DURATION_MS} from '#/lib/constants'
|
import {VIDEO_MAX_DURATION_MS} from '#/lib/constants'
|
||||||
|
import {extToMime} from '#/lib/media/video/util'
|
||||||
|
|
||||||
export async function pickVideo() {
|
export async function pickVideo() {
|
||||||
return await launchImageLibraryAsync({
|
return await launchImageLibraryAsync({
|
||||||
@@ -18,6 +20,24 @@ export async function pickVideo() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getVideoMetadata = (_file: File): Promise<ImagePickerAsset> => {
|
/**
|
||||||
throw new Error('getVideoMetadata is web only')
|
* Gets video metadata from a file or uri, depending on the platform
|
||||||
|
*
|
||||||
|
* @param file File on web, uri on native
|
||||||
|
*/
|
||||||
|
export async function getVideoMetadata(
|
||||||
|
file: File | string,
|
||||||
|
): Promise<ImagePickerAsset> {
|
||||||
|
if (typeof file !== 'string')
|
||||||
|
throw new Error(
|
||||||
|
'getVideoMetadata was passed a File, when on native it should be a uri',
|
||||||
|
)
|
||||||
|
const metadata = await getVideoMetaData(file)
|
||||||
|
return {
|
||||||
|
uri: file,
|
||||||
|
mimeType: extToMime(metadata.extension),
|
||||||
|
width: metadata.width,
|
||||||
|
height: metadata.height,
|
||||||
|
duration: metadata.duration,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,13 @@ export async function pickVideo(): Promise<ImagePickerResult> {
|
|||||||
// lets us use the ImagePickerAsset type, which the rest of the code expects.
|
// lets us use the ImagePickerAsset type, which the rest of the code expects.
|
||||||
// We should unwind this and just pass the ArrayBuffer/objectUrl through the system
|
// We should unwind this and just pass the ArrayBuffer/objectUrl through the system
|
||||||
// instead of a string -sfn
|
// instead of a string -sfn
|
||||||
export const getVideoMetadata = (file: File): Promise<ImagePickerAsset> => {
|
export function getVideoMetadata(
|
||||||
|
file: File | string,
|
||||||
|
): Promise<ImagePickerAsset> {
|
||||||
|
if (typeof file === 'string')
|
||||||
|
throw new Error(
|
||||||
|
'getVideoMetadata was passed a uri, when on web it should be a File',
|
||||||
|
)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user