我目前正在开展一个项目,试图将视频从 Tello 无人机流式传输到使用 React Native 和 Expo 构建的移动应用程序,我已经在我的应用程序上完成了 EAS Build ...
我目前正在开展一个项目,试图将视频从 Tello 无人机传输到使用 React Native 和 Expo 构建的移动应用程序,我已经在手机上为应用程序进行了 EAS Build。我正在使用 react-native-vlc-media-player 库来处理视频流。
以下是我的 DroneScreen.js 文件中的相关代码:
import React, { useEffect, useState, useRef } from "react";
import {
Button,
StyleSheet,
View,
Dimensions,
Image,
TouchableOpacity,
ScrollView,
Animated,
ActivityIndicator,
} from "react-native";
import { useFocusEffect } from "@react-navigation/native";
import dgram from "react-native-udp";
import KText from "../components/KText";
import { VLCPlayer } from "react-native-vlc-media-player";
import _ from "lodash";
import Icon from "react-native-vector-icons/FontAwesome5";
const screenWidth = Dimensions.get("window").width;
export default function DroneScreen() {
const [isConnected, setIsConnected] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isInitialScreen, setIsInitialScreen] = useState(true);
const [fadeAnim] = useState(new Animated.Value(0));
const clientRef = useRef(null);
const serverRef = useRef(null);
const videoServerRef = useRef(null);
const playerRef = useRef(null);
useFocusEffect(
React.useCallback(() => {
fadeAnim.setValue(0);
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
}, [])
);
useEffect(() => {
clientRef.current = dgram.createSocket("udp4");
serverRef.current = dgram.createSocket("udp4");
videoServerRef.current = dgram.createSocket("udp4");
try {
clientRef.current.bind(8889);
serverRef.current.bind(8890);
videoServerRef.current.bind(11111);
} catch (err) {
console.error("Failed to bind socket", err);
return;
}
let timeoutId = null;
let intervalId = null;
let state = null;
let rinfo = null;
serverRef.current.on("message", (msg, rinfo) => {
state = parseState(msg.toString());
rinfo = rinfo;
setIsConnected(true);
});
intervalId = setInterval(() => {
if (state && rinfo) {
console.log(`server got: ${state} from ${rinfo.address}:${rinfo.port}`);
}
}, 15000);
videoServerRef.current.on("message", (msg, rinfo) => {
setIsStreaming(true);
});
timeoutId = setTimeout(() => {
setIsConnected(false);
}, 5000);
return () => {
clientRef.current.close();
serverRef.current.close();
videoServerRef.current.close();
clearTimeout(timeoutId);
clearInterval(intervalId);
};
}, []);
function parseState(state) {
return state
.split(";")
.map((x) => x.split(":"))
.reduce((data, [key, value]) => {
data[key] = value;
return data;
}, {});
}
const sendCommand = (command) => {
clientRef.current.send(
command,
0,
command.length,
8889,
"192.168.10.1",
(err) => {
if (err) {
console.error("Failed to send command", err);
setIsConnected(false);
} else {
console.log("Command sent: " + command);
}
}
);
};
const connectAndStartStreaming = () => {
setIsLoading(true);
sendCommand("command");
setTimeout(() => {
sendCommand("streamon");
setIsInitialScreen(false);
setIsLoading(false);
}, 5000);
};
return (
<ScrollView contentContainerStyle={styles.container}>
{isInitialScreen || !isConnected ? (
<View style={styles.innerContainer}>
<Animated.Image
source={require("../assets/tello-img.png")}
style={{
width: screenWidth * 1.3,
resizeMode: "contain",
position: "absolute",
opacity: fadeAnim,
}}
/>
<View style={styles.greyContainer}>
<TouchableOpacity
style={[
styles.connectButton,
{ bottom: 120, flexDirection: "row" },
]}
onPress={connectAndStartStreaming}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<>
<Icon name="link" size={20} color="#fff" />
<KText style={styles.connectButtonText}>Connect</KText>
</>
)}
</TouchableOpacity>
</View>
</View>
) : (
<>
{isStreaming &&
(console.log("isStreaming:", isStreaming),
(
<VLCPlayer
style={{ width: "100%", height: "100%" }}
source={{ uri: "udp://@0.0.0.0:11111" }}
autoplay={false}
initOptions={["--network-caching=150", "--rtsp-tcp"]}
onError={(e) => {
console.log("onError", e);
setIsConnected(false);
setIsStreaming(false);
}}
onOpen={(e) => {
console.log("onOpen", e);
if (
playerRef.current &&
typeof playerRef.current.play === "function"
) {
playerRef.current.play();
}
}}
onBuffering={(e) => {
console.log("onBuffering", e);
if (
e.bufferRate > 0 &&
playerRef.current &&
typeof playerRef.current.play === "function"
) {
playerRef.current.play();
}
}}
onPlaying={(e) => console.log("onPlaying", e)}
ref={playerRef}
/>
))}
<View style={styles.innerContainer}>
<TouchableOpacity
style={styles.buttonContainer}
onPress={() => sendCommand("takeoff")}
>
<KText style={styles.buttonText}>Take Off</KText>
</TouchableOpacity>
<TouchableOpacity
style={styles.buttonContainer}
onPress={() => sendCommand("land")}
>
<KText style={styles.buttonText}>Land</KText>
</TouchableOpacity>
<TouchableOpacity
style={styles.buttonContainer}
onPress={() => sendCommand("streamoff")}
>
<KText style={styles.buttonText}>Off Stream</KText>
</TouchableOpacity>
</View>
</>
)}
</ScrollView>
);
}
....
我面临的问题是,我无法在移动应用程序上接收来自无人机的视频流。我已确认我的手机已通过 WiFi 连接到无人机,并且无人机已配置为将视频流发送到端口 11111。我能够连接并发送有效的命令(如“takeoff”)。isStreaming 状态变量已设置为 true,这表明“streamon”命令正在运行。
我尝试了各种解决方案,包括将套接字绑定到端口 11111,但这似乎不起作用,网上大多数解决方案都是针对 Python 定制的。我还检查了控制台日志中是否有任何错误或警告,但似乎没有与此问题相关的。我手机上的官方 Tello 应用程序能够显示视频。
我将非常感激任何关于如何解决此问题的帮助或建议。谢谢!