我正在开发一个 React Native 应用程序,我需要使用 ffmpeg-kit-react-native 库处理视频。但是,我在 FFmpegKitConfig 初始化期间遇到问题。错误信息是:
ERROR Error initializing FFmpegKit: [TypeError: Cannot read property 'getLogLevel' of null]
这是我的App.js代码
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Alert, Dimensions, ScrollView, LayoutAnimation, UIManager, Platform } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import * as FileSystem from 'expo-file-system';
import { Video } from 'expo-av';
import { MaterialIcons } from '@expo/vector-icons';
import { FFmpegKit, FFmpegKitConfig, ReturnCode } from 'ffmpeg-kit-react-native';
const windowWidth = Dimensions.get('window').width;
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
}
export default function App() {
const [videoFiles, setVideoFiles] = useState([]);
const [isGridView, setIsGridView] = useState(false);
const [isConverting, setIsConverting] = useState(false);
useEffect(() => {
FFmpegKitConfig.init()
.then(() => {
console.log('FFmpegKit initialized');
})
.catch((error) => {
console.error('Error initializing FFmpegKit:', error);
});
}, []);
const pickVideo = async () => {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need media library permissions to make this work!');
return;
}
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Videos,
allowsMultipleSelection: true,
});
if (!result.canceled && result.assets.length > 0) {
const newFiles = result.assets.filter(
(newFile) => !videoFiles.some((existingFile) => existingFile.uri === newFile.uri)
);
if (newFiles.length < result.assets.length) {
Alert.alert('Duplicate Files', 'Some files were already added.');
}
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setVideoFiles([...videoFiles, ...newFiles]);
}
};
const convertVideos = async () => {
setIsConverting(true);
const outputDir = `${FileSystem.documentDirectory}Output`;
const dirInfo = await FileSystem.getInfoAsync(outputDir);
if (!dirInfo.exists) {
await FileSystem.makeDirectoryAsync(outputDir, { intermediates: true });
}
for (const video of videoFiles) {
const { uri } = video;
const filename = uri.split('/').pop();
const outputFilePath = `${outputDir}/${filename.split('.').slice(0, -1).join('.')}_modified.mp4`;
const ffmpegCommand = `-y -i "${uri}" -af "atempo=1.02, bass=g=4:f=80:w=3, treble=g=4:f=3200:w=3, firequalizer=gain_entry='entry(0,0);entry(62,2);entry(125,1.5);entry(250,1);entry(500,1);entry(1000,1);entry(2000,1.5);entry(4000,2.5);entry(8000,3);entry(16000,4)', compand=attacks=0.05:decays=0.25:points=-80/-80-50/-15-30/-10-10/-2:soft-knee=4:gain=2, deesser, highpass=f=35, lowpass=f=17000, loudnorm=I=-16:LRA=11:TP=-1.5, volume=3.9dB" -c:v copy -c:a aac -b:a 224k -ar 48000 -threads 0 "${outputFilePath}"`;
try {
const session = await FFmpegKit.execute(ffmpegCommand);
const returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
console.log(`Video converted: ${outputFilePath}`);
} else if (ReturnCode.isCancel(returnCode)) {
console.log('Conversion cancelled');
} else {
console.error(`FFmpeg process failed: ${session.getFailStackTrace()}`);
}
} catch (error) {
console.error(`Error converting video: ${error.message}`);
}
}
setIsConverting(false);
Alert.alert('Conversion Complete', 'All videos have been converted.');
};
const deleteVideo = (uri) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setVideoFiles(videoFiles.filter((video) => video.uri !== uri));
};
const clearAllVideos = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setVideoFiles([]);
};
const toggleLayout = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setIsGridView(!isGridView);
};
return (
<View style={styles.container}>
<Text style={styles.header}>Video Converter App</Text>
<TouchableOpacity style={styles.addButton} onPress={pickVideo}>
<Text style={styles.addButtonText}>Select or Browse Videos</Text>
</TouchableOpacity>
<View style={styles.headerContainer}>
<Text style={styles.videoCount}>Total Videos: {videoFiles.length}</Text>
{videoFiles.length > 0 && (
<>
<TouchableOpacity onPress={clearAllVideos} style={styles.clearButtonContainer}>
<MaterialIcons name="clear" size={24} color="red" style={styles.clearIcon} />
<Text style={styles.clearAllText}>Clear All</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.toggleLayoutButton} onPress={toggleLayout}>
<MaterialIcons name={isGridView ? 'view-list' : 'view-module'} size={24} color="#fff" />
</TouchableOpacity>
</>
)}
</View>
{isGridView ? (
<ScrollView contentContainerStyle={styles.gridContainer}>
{videoFiles.map((item, index) => (
<View key={index} style={styles.videoItemGrid}>
<Video
source={{ uri: item.uri }}
rate={1.0}
volume={1.0}
isMuted={true}
resizeMode="cover"
shouldPlay={false}
style={styles.thumbnailGrid}
/>
<TouchableOpacity onPress={() => deleteVideo(item.uri)} style={styles.deleteButtonGrid}>
<MaterialIcons name="delete" size={24} color="red" />
</TouchableOpacity>
</View>
))}
</ScrollView>
) : (
<View style={styles.list}>
{videoFiles.map((item, index) => (
<View key={index} style={styles.videoItem}>
<Video
source={{ uri: item.uri }}
rate={1.0}
volume={1.0}
isMuted={true}
resizeMode="cover"
shouldPlay={false}
style={styles.thumbnail}
/>
<Text style={styles.fileName}>{decodeURI(item.fileName || item.uri.split('/').pop() || 'Unknown File')}</Text>
<TouchableOpacity onPress={() => deleteVideo(item.uri)} style={styles.deleteButton}>
<MaterialIcons name="delete" size={24} color="red" />
</TouchableOpacity>
</View>
))}
</View>
)}
{videoFiles.length > 0 && (
<TouchableOpacity style={styles.convertButton} onPress={convertVideos} disabled={isConverting}>
<Text style={styles.convertButtonText}>{isConverting ? 'Converting...' : 'Convert'}</Text>
</TouchableOpacity>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
padding: 10,
},
header: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 5,
},
addButton: {
backgroundColor: '#007BFF',
padding: 15,
borderRadius: 10,
alignItems: 'center',
marginBottom: 5,
width: '100%',
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 5 },
shadowOpacity: 0.8,
shadowRadius: 2,
},
addButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
headerContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
marginBottom: 10,
},
videoCount: {
fontSize: 18,
},
clearButtonContainer: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 10,
},
clearIcon: {
marginRight: 5,
},
clearAllText: {
fontSize: 16,
color: 'red',
textDecorationLine: 'underline',
},
toggleLayoutButton: {
backgroundColor: '#007BFF',
padding: 1,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
},
list: {
flex: 1,
width: '100%',
},
videoItem: {
padding: 5,
borderBottomColor: '#ccc',
borderBottomWidth: 0.7,
flexDirection: 'row',
alignItems: 'center',
},
videoItemGrid: {
flexDirection: 'column',
alignItems: 'center',
margin: 4,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
padding: 2,
position: 'relative',
},
thumbnail: {
width: 70,
height: 70,
marginRight: 10,
},
thumbnailGrid: {
width: 80,
height: 80,
marginBottom: 0,
},
fileName: {
fontSize: 16,
marginLeft: 10,
flex: 1,
},
deleteButton: {
marginLeft: 60,
width: 20,
height: 20,
},
deleteButtonGrid: {
position: 'absolute',
bottom: 5,
right: 5,
},
convertButton: {
backgroundColor: '#007BFF',
padding: 15,
borderRadius: 10,
alignItems: 'center',
marginTop: 20,
width: '100%',
},
convertButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
gridContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
paddingVertical: 5,
paddingHorizontal: 5,
width: '100%',
},
});
App.json
{
"expo": {
"name": "VidoeConvert",
"slug": "VidoeConvert",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.anonymous.VidoeConvert"
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
"@config-plugins/ffmpeg-kit-react-native",
"expo-build-properties"
]
}
}
Package.json
{
"name": "vidoeconvert",
"version": "1.0.0",
"main": "expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web"
},
"dependencies": {
"@config-plugins/ffmpeg-kit-react-native": "^8.0.0",
"@expo/metro-runtime": "~3.2.1",
"expo": "~51.0.17",
"expo-asset": "~10.0.10",
"expo-av": "^14.0.6",
"expo-document-picker": "~12.0.2",
"expo-file-system": "~17.0.1",
"expo-image-picker": "~15.0.7",
"expo-media-library": "~16.0.4",
"expo-status-bar": "~1.12.1",
"ffmpeg-kit-react-native": "^6.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "^0.74.3",
"react-native-document-picker": "^9.3.0",
"react-native-ffmpeg": "^0.5.2",
"react-native-vector-icons": "^10.1.0",
"react-native-web": "~0.19.10",
"expo-build-properties": "~0.12.3"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}
有没有人遇到过类似的问题,或者可以为我指出解决此错误的正确方向?任何帮助将不胜感激!
如何消除错误?
这个项目需要什么配置吗?
我这几天也遇到同样的问题。你找到解决办法了吗?