Video component (expo-av) won't render state uri unless I reload the app

57 Views Asked by At

I am merging videos using FFmpegKIT. I don't think there is anything wrong with the library but when I try to display the merged video in the video component it won't work. But when I reload the app and go to the process of merging videos again, the video component displays the merged video but only because I didn't change the name of the file (which already existed from the previous merging when it failed). I am using the FileSystem lib from expo to "store" the merged video then display it.The path of this new file is then updated into a hook which I pass for the source of the video component. My guess is the video compoenent doesnt have access to the file until I reload (not CTRL + S) the entire app but I don't know how to fix this. I am planning on creating a different file for each merge but I can't go further until I fix this. I am running on a android emulator using npx expo run:android

update : I logged the video component from expo-av and I have the error : Loading finished before preparation is complete. I don't know what it means.

here is the code I am using right now :

import { FFmpegKit} from 'ffmpeg-kit-react-native';
import { launchImageLibraryAsync } from 'expo-image-picker';
import { useState, useEffect } from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import { Video as ExpoVideo } from 'expo-av';
import * as FileSystem from 'expo-file-system';
import * as ImagePicker from 'expo-image-picker';
import * as MediaLibrary from 'expo-media-library';

const VideoEditor = () => {

  const [selectedVideos, setSelectedVideos] = useState([]);
  const [mergedVideo, setMergedVideo] = useState(null);

  const pickVideos = async () => {
    try {
      console.log('Picking videos...');
      const result = await launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Videos,
        allowsMultipleSelection: false,
      });
  
      console.log('Result:', result);
  
      if (!result.canceled) {
        const selectedVideo = result.assets[0] || result.selectedItems[0];
  
        if (selectedVideo) {
          const videoUri = selectedVideo.uri;
          console.log('Selected video URI:', videoUri);
  
          setSelectedVideos(prevVideos => [...prevVideos, videoUri]);
        } else {
          console.log('No video selected.');
        }
      } else {
        console.log('Video picking cancelled.');
      }
    } catch (error) {
      console.error('Error picking video:', error);
    }
  };
  
  useEffect(() => {
    console.log('UseEffectlog:', mergedVideo);
  }, [mergedVideo]);


  
  const mergeVideos = async () => {
    if (selectedVideos.length === 0) {
      console.error('No videos selected for merging.');
      return;
    }
  
    //const timestamp = new Date().getTime();
    const outputVideo = `${FileSystem.cacheDirectory}mergedVideo2.mp4`;
  
    const inputFiles = selectedVideos.map(uri => `-i ${uri}`).join(' ');
    const filterComplex = `concat=n=${selectedVideos.length}:v=1:a=1[outv][outa]`;
  
    const ffmpegCommand = `${inputFiles} -filter_complex ${filterComplex} -map [outv] -map [outa] ${outputVideo}`;
  
    try {
      const result = await FFmpegKit.executeAsync(ffmpegCommand);
      console.log('Merged video URI:', outputVideo);
      setMergedVideo(outputVideo);

    } catch (error) {
      console.error('Error merging videos:', error);
    }
  };
  
 


  
  return (
    <View>
      <TouchableOpacity onPress={pickVideos}>
        <Text style={{ fontSize: 20, color: 'blue', textAlign: 'center', margin: 10 }}>
          Pick Videos
        </Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={mergeVideos}>
        <Text style={{ fontSize: 20, color: 'blue', textAlign: 'center', margin: 10 }}>
          Merge Videos
        </Text>
      </TouchableOpacity>

      { mergedVideo && (
        <View>
          <Text style={{ fontSize: 40, color: 'blue', textAlign: 'center', margin: 10 }}>
            Merged Video:
          </Text>

          <ExpoVideo
          source={{uri: `${mergedVideo}` }}
          style={{ width: 300, height: 300 }}
          isMuted={true}
          resizeMode="cover"
          shouldPlay={true}
          useNativeControls
        />
        </View>
      )}
    </View>
  );
};

export default VideoEditor;

I also know for sure the state of the mergedVideo hook is not null thanks to the useEffect.

Below is a run of the logs of the whole process :

LOG  Picking videos...
 LOG  Selected video URI: file:///data/user/0/com.anakinn.ffpmegEditor/cache/ImagePicker/fa07339a-5bbd-4b8d-8969-811b287c6c97.mp4       
 LOG  Picking videos...
 LOG  Selected video URI: file:///data/user/0/com.anakinn.ffpmegEditor/cache/ImagePicker/618753e6-71f3-4294-8a0e-dad7ed6e148f.mp4       
 LOG  Loading ffmpeg-kit-react-native.
 LOG  Loaded ffmpeg-kit-react-native-android-video-x86_64-6.0.2.    
 LOG  Merged video URI: file:///data/user/0/com.anakinn.ffpmegEditor/cache/mergedVideo2.mp4
 LOG  UseEffectlog: file:///data/user/0/com.anakinn.ffpmegEditor/cache/mergedVideo2.mp4
 LOG  ffmpeg version n6.0
 LOG   Copyright (c) 2000-2023 the FFmpeg developers
 LOG 
 
 LOG  Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'file:///data/user/0/com.anakinn.ffpmegEditor/cache/ImagePicker/fa07339a-5bbd-4b8d-8969-811b287c6c97.mp4':
 
 LOG  Input #1, mov,mp4,m4a,3gp,3g2,mj2, from 'file:///data/user/0/com.anakinn.ffpmegEditor/cache/ImagePicker/618753e6-71f3-4294-8a0e-dad7ed6e148f.mp4':
 
 LOG  Output #0, mp4, to 'file:///data/user/0/com.anakinn.ffpmegEditor/cache/mergedVideo2.mp4':
 
 LOG  UseEffectlog: file:///data/user/0/com.anakinn.ffpmegEditor/cache/mergedVideo2.mp4

1

There are 1 best solutions below

1
On

can you share the output of when you console log result

const result = await FFmpegKit.executeAsync(ffmpegCommand);