Procházet zdrojové kódy

feat:
1、osd上报数据加上飞行总时长以及飞行总距离
2、监听无人机起飞和降落事件

yingjian.wu před 3 měsíci
rodič
revize
3ad32fbd0f

+ 22 - 17
app/src/main/java/com/paul/drone/SteamControlActivity.java

@@ -7,7 +7,6 @@ import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -25,6 +24,8 @@ import dji.v5.manager.datacenter.livestream.settings.RtmpSettings;
 import dji.sdk.keyvalue.value.common.ComponentIndexType;
 import dji.v5.common.callback.CommonCallbacks;
 import dji.v5.common.error.IDJIError;
+
+import com.paul.drone.data.mqtt.LiveControlMessage;
 import com.paul.drone.service.MqttService;
 import com.paul.drone.util.DeviceInfoManager;
 import com.paul.drone.util.mqtt.MqttTopic;
@@ -265,6 +266,7 @@ public class SteamControlActivity extends DefaultLayoutActivity {
             Log.d(TAG, "Received live control action: " + action);
 
             if ("start".equals(action)) {
+                // 原有启动直播逻辑保持不变
                 String url = jsonMessage.optString("rtmp_url", "");
                 String quality = jsonMessage.optString("quality", "HD");
 
@@ -277,12 +279,19 @@ public class SteamControlActivity extends DefaultLayoutActivity {
                 setRTMPConfig(url);
                 setLiveStreamQualityByStr(quality);
                 setCameraIndex(ComponentIndexType.LEFT_OR_MAIN);
-
-                // 确认相机索引设置
-                Log.d(TAG, "Camera index after setting: " + cameraIndex);
                 startStreaming();
             } else if ("stop".equals(action)) {
+                // 原有停止直播逻辑保持不变
                 stopStreaming();
+            } else if ("set_quality".equals(action)) {
+                // 新增单独设置分辨率的逻辑
+                String quality = jsonMessage.optString("quality", LiveControlMessage.Quality.HD.getValue());
+                if (quality.isEmpty()) {
+                    Log.e(TAG, "Quality parameter is empty");
+                    quality = LiveControlMessage.Quality.HD.getValue();
+                }
+                // 设置直播质量/分辨率
+                setLiveStreamQualityByStr(quality);
             } else {
                 Log.w(TAG, "Unknown action: " + action);
                 Toast.makeText(this, "未知命令: " + action, Toast.LENGTH_SHORT).show();
@@ -331,20 +340,16 @@ public class SteamControlActivity extends DefaultLayoutActivity {
      * 解析直播质量字符串
      */
     private StreamQuality parseQuality(String quality) {
-        switch (quality.toUpperCase()) {
-            case "SD":
-                return StreamQuality.SD;
-            case "HD":
-                return StreamQuality.HD;
-            case "FHD":
-            case "FULL_HD":
-                return StreamQuality.FULL_HD;
-            case "ORIGINAL":
-                return StreamQuality.ORIGINAL;
-            default:
+        return switch (quality.toUpperCase()) {
+            case "SD" -> StreamQuality.SD;
+            case "HD" -> StreamQuality.HD;
+            case "FHD", "FULL_HD" -> StreamQuality.FULL_HD;
+            case "ORIGINAL" -> StreamQuality.ORIGINAL;
+            default -> {
                 Log.w(TAG, "Unknown quality: " + quality + ", using HD as default");
-                return StreamQuality.HD;
-        }
+                yield StreamQuality.HD;
+            }
+        };
     }
 
     /**

+ 294 - 0
app/src/main/java/com/paul/drone/SteamControlActivityBak.java

@@ -0,0 +1,294 @@
+package com.paul.drone;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+
+import com.paul.drone.data.mqtt.LiveControlMessage;
+import com.paul.drone.service.MqttService;
+import com.paul.drone.util.DeviceInfoManager;
+import com.paul.drone.util.mqtt.MqttTopic;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import dji.sdk.keyvalue.value.common.ComponentIndexType;
+import dji.v5.common.callback.CommonCallbacks;
+import dji.v5.common.error.IDJIError;
+import dji.v5.manager.datacenter.MediaDataCenter;
+import dji.v5.manager.datacenter.livestream.LiveStreamSettings;
+import dji.v5.manager.datacenter.livestream.LiveStreamStatus;
+import dji.v5.manager.datacenter.livestream.LiveStreamStatusListener;
+import dji.v5.manager.datacenter.livestream.LiveStreamType;
+import dji.v5.manager.datacenter.livestream.LiveVideoBitrateMode;
+import dji.v5.manager.datacenter.livestream.StreamQuality;
+import dji.v5.manager.datacenter.livestream.settings.RtmpSettings;
+import dji.v5.manager.interfaces.ILiveStreamManager;
+import dji.v5.ux.sample.showcase.defaultlayout.DefaultLayoutActivity;
+
+public class SteamControlActivityBak extends DefaultLayoutActivity {
+
+    private static final String TAG = "SteamControlActivity";
+
+    // UI控件
+    private TextView tvLiveStatus;
+
+    // 核心组件
+    private MqttService mqttService;
+    private boolean isMqttBound = false;
+    private final ILiveStreamManager streamManager = MediaDataCenter.getInstance().getLiveStreamManager();
+    private String rtmpUrl = "";
+    private StreamQuality streamQuality = StreamQuality.HD;
+    private ComponentIndexType cameraIndex = ComponentIndexType.LEFT_OR_MAIN;
+    private boolean isStreaming = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setupWindow();
+        addLiveStatusView();
+        initLiveStreamManager();
+        bindMqttService();
+    }
+
+    private void setupWindow() {
+        getWindow().setFlags(
+                android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN
+        );
+        View decorView = getWindow().getDecorView();
+        int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        decorView.setSystemUiVisibility(uiOptions);
+    }
+
+    private void addLiveStatusView() {
+        tvLiveStatus = new TextView(this);
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.BOTTOM | Gravity.START
+        );
+        params.setMargins(20, 0, 0, 20);
+        tvLiveStatus.setLayoutParams(params);
+        tvLiveStatus.setText("直播状态: 未连接");
+        tvLiveStatus.setTextSize(12);
+        tvLiveStatus.setTextColor(ContextCompat.getColor(this, android.R.color.white));
+        tvLiveStatus.setBackgroundColor(0x33000000); // 20%透明度黑色
+        tvLiveStatus.setPadding(16, 8, 16, 8);
+
+        ((FrameLayout) findViewById(android.R.id.content)).addView(tvLiveStatus);
+    }
+
+    private void initLiveStreamManager() {
+        streamManager.setCameraIndex(cameraIndex);
+        streamManager.addLiveStreamStatusListener(liveStreamStatusListener);
+        isStreaming = streamManager.isStreaming();
+        updateLiveStatus(isStreaming);
+    }
+
+    private void bindMqttService() {
+        Intent intent = new Intent(this, MqttService.class);
+        bindService(intent, mqttConnection, BIND_AUTO_CREATE);
+    }
+
+    private ServiceConnection mqttConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            MqttService.LocalBinder binder = (MqttService.LocalBinder) service;
+            mqttService = binder.getService();
+            isMqttBound = true;
+            Log.d(TAG, "MqttService bound successfully");
+            subscribeToLiveControlTopic();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            isMqttBound = false;
+            mqttService = null;
+            Log.d(TAG, "MqttService disconnected");
+        }
+    };
+
+    private void subscribeToLiveControlTopic() {
+        if (mqttService == null) return;
+        String productId = DeviceInfoManager.getInstance().getFlySerialNumber();
+        String liveControlTopic = MqttTopic.getTopic(MqttTopic.REMOTE_LIVE, productId);
+        mqttService.subscribeToTopic(liveControlTopic, this::handleLiveControlMessage,
+                error -> Log.e(TAG, "Failed to subscribe: " + error)
+        );
+    }
+
+    private void handleLiveControlMessage(String message) {
+        runOnUiThread(() -> {
+            try {
+                if (message == null || message.isEmpty()) {
+                    Toast.makeText(this, "收到空的直播控制消息", Toast.LENGTH_SHORT).show();
+                    return;
+                }
+                JSONObject jsonMessage = new JSONObject(message);
+                String action = jsonMessage.optString("action", "unknown");
+
+                if ("start".equals(action)) {
+                    String url = jsonMessage.optString("rtmp_url", "");
+                    String quality = jsonMessage.optString("quality", "HD");
+                    if (url.isEmpty()) {
+                        Toast.makeText(this, "RTMP URL不能为空", Toast.LENGTH_SHORT).show();
+                        return;
+                    }
+                    setRTMPConfig(url);
+                    setLiveStreamQualityByStr(quality);
+                    setCameraIndex(ComponentIndexType.LEFT_OR_MAIN);
+                    startStreaming();
+                } else if ("stop".equals(action)) {
+                    stopStreaming();
+                } else if ("set_quality".equals(action)) {
+                    String quality = jsonMessage.optString("quality", LiveControlMessage.Quality.HD.getValue());
+                    setLiveStreamQualityByStr(quality);
+
+                    if (isStreaming) {
+                        Toast.makeText(this, "正在切换清晰度至 " + quality + "...", Toast.LENGTH_SHORT).show();
+                        stopStreaming(new CommonCallbacks.CompletionCallback() {
+                            @Override
+                            public void onSuccess() {
+                                Log.d(TAG, "Stream stopped for quality change, now restarting...");
+                                startStreaming();
+                            }
+                            @Override
+                            public void onFailure(IDJIError error) {
+                                Toast.makeText(SteamControlActivityBak.this, "切换失败: 停止当前直播出错", Toast.LENGTH_SHORT).show();
+                            }
+                        });
+                    } else {
+                        Toast.makeText(this, "清晰度已设置为 " + quality + ",下次直播将生效", Toast.LENGTH_SHORT).show();
+                    }
+                } else {
+                    Toast.makeText(this, "未知命令: " + action, Toast.LENGTH_SHORT).show();
+                }
+            } catch (JSONException e) {
+                Toast.makeText(this, "解析直播控制消息失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+            }
+        });
+    }
+
+    private void setRTMPConfig(String rtmpUrl) {
+        this.rtmpUrl = rtmpUrl;
+        LiveStreamSettings settings = new LiveStreamSettings.Builder()
+                .setLiveStreamType(LiveStreamType.RTMP)
+                .setRtmpSettings(new RtmpSettings.Builder().setUrl(rtmpUrl).build())
+                .build();
+        streamManager.setLiveStreamSettings(settings);
+    }
+
+    private void setLiveStreamQualityByStr(String quality) {
+        this.streamQuality = parseQuality(quality);
+        streamManager.setLiveStreamQuality(this.streamQuality);
+    }
+
+    private StreamQuality parseQuality(String quality) {
+        return switch (quality.toUpperCase()) {
+            case "SD" -> StreamQuality.SD;
+            case "FHD", "FULL_HD" -> StreamQuality.FULL_HD;
+            case "ORIGINAL" -> StreamQuality.ORIGINAL;
+            default -> StreamQuality.HD;
+        };
+    }
+
+    private void setCameraIndex(ComponentIndexType cameraIndex) {
+        this.cameraIndex = cameraIndex;
+        streamManager.setCameraIndex(cameraIndex);
+        streamManager.setLiveVideoBitrateMode(LiveVideoBitrateMode.AUTO);
+    }
+
+    private void startStreaming() {
+        streamManager.startStream(new CommonCallbacks.CompletionCallback() {
+            @Override
+            public void onSuccess() {
+                Log.d(TAG, "Live stream start command sent successfully.");
+            }
+            @Override
+            public void onFailure(IDJIError error) {
+                Log.e(TAG, "Failed to start live stream: " + error.description());
+                runOnUiThread(() -> Toast.makeText(SteamControlActivityBak.this, "直播启动失败: " + error.description(), Toast.LENGTH_LONG).show());
+            }
+        });
+    }
+
+    private void stopStreaming() {
+        stopStreaming(null);
+    }
+
+    private void stopStreaming(CommonCallbacks.CompletionCallback customCallback) {
+        streamManager.stopStream(new CommonCallbacks.CompletionCallback() {
+            @Override
+            public void onSuccess() {
+                Log.d(TAG, "Live stream stop command sent successfully.");
+                if (customCallback != null) customCallback.onSuccess();
+            }
+            @Override
+            public void onFailure(IDJIError error) {
+                Log.e(TAG, "Failed to stop live stream: " + error.description());
+                if (customCallback != null) customCallback.onFailure(error);
+            }
+        });
+    }
+
+    private void updateLiveStatus(boolean streaming) {
+        runOnUiThread(() -> {
+            if (tvLiveStatus == null) return;
+            if (streaming) {
+                tvLiveStatus.setText("直播状态: 正在直播");
+                tvLiveStatus.setTextColor(ContextCompat.getColor(this, android.R.color.holo_green_dark));
+            } else {
+                tvLiveStatus.setText("直播状态: 未直播");
+                tvLiveStatus.setTextColor(ContextCompat.getColor(this, android.R.color.holo_red_dark));
+            }
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (isStreaming) {
+            stopStreaming();
+            Log.d(TAG, "Stopping stream on activity destroy.");
+        }
+        if (isMqttBound) {
+            unbindService(mqttConnection);
+            isMqttBound = false;
+        }
+        streamManager.removeLiveStreamStatusListener(liveStreamStatusListener);
+    }
+
+    // FIX: 恢复为您的SDK版本所兼容的Listener接口实现
+    private final LiveStreamStatusListener liveStreamStatusListener = new LiveStreamStatusListener() {
+        @Override
+        public void onLiveStreamStatusUpdate(LiveStreamStatus status) {
+            Log.d(TAG, "Live stream status changed: " + status);
+            isStreaming = streamManager.isStreaming();
+            updateLiveStatus(isStreaming);
+        }
+
+        @Override
+        public void onError(IDJIError error) {
+            if (error != null) {
+                Log.e(TAG, "Live stream error: " + error.description());
+                runOnUiThread(() -> Toast.makeText(SteamControlActivityBak.this, "直播错误: " + error.description(), Toast.LENGTH_SHORT).show());
+                isStreaming = false;
+                updateLiveStatus(false);
+            }
+        }
+    };
+}

+ 198 - 100
app/src/main/java/com/paul/drone/connection/DeviceConnectionManager.java

@@ -10,56 +10,78 @@ import dji.v5.manager.KeyManager;
 import dji.v5.manager.SDKManager;
 import dji.sdk.keyvalue.key.FlightControllerKey;
 import dji.sdk.keyvalue.key.RemoteControllerKey;
+import lombok.Getter;
+import lombok.Setter;
 
 import static dji.sdk.keyvalue.key.DJIKey.create;
 
 import com.paul.drone.manager.DJISDKManager;
 
 /**
- * 设备连接状态管理器
- * 
+ * 设备连接与飞行状态管理器
+ *
  * 功能:
  * 1. 监听无人机连接状态 (FlightControllerKey.KeyConnection)
  * 2. 监听遥控器连接状态 (RemoteControllerKey.KeyConnection)
- * 3. 使用弱引用避免内存泄漏
- * 4. 支持生命周期管理
+ * 3. 监听无人机起飞与降落事件 (FlightControllerKey.KeyIsFlying) // MODIFIED
+ * 4. 使用弱引用避免内存泄漏
+ * 5. 支持生命周期管理
  */
-public class DeviceConnectionManager {
+public class DeviceConnectionManager{
 
     private static final String TAG = "DeviceConnectionManager";
-    
+
+    @Getter
+    @Setter
+    private volatile Float cachedTotalFlightTime = 0.0f;
+    @Getter
+    @Setter
+    private volatile Double cachedTotalFlightDistance = 0.0;
+
     // 单例
     private static volatile DeviceConnectionManager instance;
-    
-    // 回调接口
+
+    // MODIFIED: 接口增加了起飞和降落的回调
     public interface ConnectionStateCallback {
         /**
          * 无人机连接状态变化
          * @param isConnected 是否已连接
          */
         void onDroneConnectionChanged(boolean isConnected);
-        
+
         /**
          * 遥控器连接状态变化
-         * @param isConnected 是否已连ected
+         * @param isConnected 是否已连
          */
         void onRemoteControllerConnectionChanged(boolean isConnected);
+
+        /**
+         * 无人机起飞事件
+         */
+        void onDroneTakeoff();
+
+        /**
+         * 无人机降落事件
+         */
+        void onDroneLanding();
     }
-    
+
     // 使用弱引用存储回调,避免内存泄漏
     private final ConcurrentHashMap<String, WeakReference<ConnectionStateCallback>> callbacks = new ConcurrentHashMap<>();
-    
+
     // 当前连接状态
     private final AtomicBoolean isDroneConnected = new AtomicBoolean(false);
     private final AtomicBoolean isRemoteControllerConnected = new AtomicBoolean(false);
-    
+    // ADDED: 新增无人机飞行状态
+    private final AtomicBoolean isDroneFlying = new AtomicBoolean(false);
+
     // 监听器状态
     private volatile boolean isListening = false;
-    
+
     private DeviceConnectionManager() {
         Log.i(TAG, "DeviceConnectionManager 初始化");
     }
-    
+
     /**
      * 获取单例实例
      */
@@ -84,20 +106,20 @@ public class DeviceConnectionManager {
             Log.w(TAG, "注册回调失败:tag 或 callback 为空");
             return;
         }
-        
+
         Log.i(TAG, "注册连接状态回调: " + tag);
         callbacks.put(tag, new WeakReference<>(callback));
-        
+
         // 如果还未开始监听,则启动监听
         if (!isListening) {
             startListening();
         }
-        
+
         // 立即回调当前状态
         callback.onDroneConnectionChanged(isDroneConnected.get());
         callback.onRemoteControllerConnectionChanged(isRemoteControllerConnected.get());
     }
-    
+
     /**
      * 取消注册回调
      * @param tag 标识符
@@ -106,30 +128,35 @@ public class DeviceConnectionManager {
         if (tag == null) {
             return;
         }
-        
+
         Log.i(TAG, "取消注册连接状态回调: " + tag);
         callbacks.remove(tag);
-        
-        // 如果没有任何回调了,停止监听以节省资源
-        if (callbacks.isEmpty()) {
-            stopListening();
-        }
+
+//        // 如果没有任何回调了,停止监听以节省资源
+//        if (callbacks.isEmpty()) {
+//            stopListening();
+//        }
     }
-    
+
     /**
      * 获取当前无人机连接状态
      */
     public boolean isDroneConnected() {
         return isDroneConnected.get();
     }
-    
+
     /**
      * 获取当前遥控器连接状态
      */
     public boolean isRemoteControllerConnected() {
         return isRemoteControllerConnected.get();
     }
-    
+
+    // ADDED: 获取当前无人机是否在飞行
+    public boolean isDroneFlying() {
+        return isDroneFlying.get();
+    }
+
     /**
      * 开始监听连接状态
      */
@@ -137,47 +164,88 @@ public class DeviceConnectionManager {
         if (isListening) {
             return;
         }
-        
+
         if (!DJISDKManager.getInstance().isSDKRegistered()) {
             Log.w(TAG, "SDK 未初始化,无法开始监听");
             return;
         }
-        
+
         try {
-            Log.i(TAG, "开始监听设备连接状态");
-            
+            Log.i(TAG, "开始监听设备连接与飞行状态");
+
             // 监听无人机连接状态
             KeyManager.getInstance().listen(create(FlightControllerKey.KeyConnection), this, (oldValue, newValue) -> {
                 boolean connected = newValue != null && newValue;
                 Log.i(TAG, "无人机连接状态变化: " + connected);
-                
-                // 更新状态
+
                 isDroneConnected.set(connected);
-                
-                // 通知所有有效的回调
                 notifyDroneConnectionChanged(connected);
+
+                // ADDED: 如果无人机断开连接,重置飞行状态
+                if (!connected) {
+                    if (isDroneFlying.get()) {
+                        isDroneFlying.set(false);
+                        notifyDroneLanding(); // 认为连接断开即降落
+                    }
+                }
             });
-            
+
             // 监听遥控器连接状态
             KeyManager.getInstance().listen(create(RemoteControllerKey.KeyConnection), this, (oldValue, newValue) -> {
                 boolean connected = newValue != null && newValue;
                 Log.i(TAG, "遥控器连接状态变化: " + connected);
-                
-                // 更新状态
+
                 isRemoteControllerConnected.set(connected);
-                
-                // 通知所有有效的回调
                 notifyRemoteControllerConnectionChanged(connected);
             });
-            
+
+            //监听无人机飞行状态
+            KeyManager.getInstance().listen(create(FlightControllerKey.KeyIsFlying), this, (oldValue, newValue) -> {
+                boolean nowFlying = newValue != null && newValue;
+                boolean wasFlying = isDroneFlying.get(); // 获取上一次的状态
+
+                if (nowFlying != wasFlying) {
+                    Log.i(TAG, "无人机飞行状态变化: " + nowFlying);
+
+                    if (nowFlying) {
+                        // 如果新状态是在飞行(因为状态变了,说明之前肯定是不在飞)
+                        // -> 起飞事件
+                        Log.i(TAG, "事件: 无人机起飞");
+                        notifyDroneTakeoff();
+                    } else {
+                        // 如果新状态是不在飞行(因为状态变了,说明之前肯定是在飞)
+                        // -> 降落事件
+                        Log.i(TAG, "事件: 无人机降落");
+                        notifyDroneLanding();
+                    }
+                }
+
+                // 更新当前状态
+                isDroneFlying.set(nowFlying);
+            });
+
+            //监听无人机的飞行总距离,以及飞行总时长
+            KeyManager.getInstance().listen(create(FlightControllerKey.KeyAircraftTotalFlightDuration), this, (oldValue, newValue) -> {
+                if (newValue != null) {
+                    this.cachedTotalFlightTime = Float.valueOf(String.valueOf(newValue));
+                }
+            });
+
+            // 监听总飞行距离
+            KeyManager.getInstance().listen(create(FlightControllerKey.KeyAircraftTotalFlightDistance), this, (oldValue, newValue) -> {
+                if (newValue != null) {
+                    this.cachedTotalFlightDistance = newValue;
+                }
+            });
+
             isListening = true;
-            Log.i(TAG, "设备连接状态监听已启动");
-            
+            Log.i(TAG, "设备连接与飞行状态监听已启动");
+
         } catch (Exception e) {
-            Log.e(TAG, "启动设备连接状态监听失败", e);
+            Log.e(TAG, "启动设备状态监听失败", e);
         }
     }
-    
+
     /**
      * 停止监听连接状态
      */
@@ -185,67 +253,92 @@ public class DeviceConnectionManager {
         if (!isListening) {
             return;
         }
-        
+
         try {
-            Log.i(TAG, "停止监听设备连接状态");
+            Log.i(TAG, "停止监听设备连接与飞行状态");
             KeyManager.getInstance().cancelListen(this);
             isListening = false;
-            Log.i(TAG, "设备连接状态监听已停止");
+            Log.i(TAG, "设备连接与飞行状态监听已停止");
         } catch (Exception e) {
-            Log.e(TAG, "停止设备连接状态监听失败", e);
+            Log.e(TAG, "停止设备状态监听失败", e);
         }
     }
-    
+
     /**
      * 通知无人机连接状态变化
      */
     private void notifyDroneConnectionChanged(boolean isConnected) {
-        // 遍历所有回调,清理失效的弱引用
-        callbacks.entrySet().removeIf(entry -> {
-            WeakReference<ConnectionStateCallback> weakRef = entry.getValue();
+        callbacks.forEach((tag, weakRef) -> {
             ConnectionStateCallback callback = weakRef.get();
-            
-            if (callback == null) {
-                // 回调已被垃圾回收,移除这个条目
-                Log.d(TAG, "清理失效的回调: " + entry.getKey());
-                return true;
-            }
-            
-            try {
-                callback.onDroneConnectionChanged(isConnected);
-            } catch (Exception e) {
-                Log.e(TAG, "回调执行失败: " + entry.getKey(), e);
+            if (callback != null) {
+                try {
+                    callback.onDroneConnectionChanged(isConnected);
+                } catch (Exception e) {
+                    Log.e(TAG, "回调 onDroneConnectionChanged 执行失败: " + tag, e);
+                }
             }
-            
-            return false;
         });
+        // 清理失效的弱引用
+        callbacks.entrySet().removeIf(entry -> entry.getValue().get() == null);
     }
-    
+
     /**
      * 通知遥控器连接状态变化
      */
     private void notifyRemoteControllerConnectionChanged(boolean isConnected) {
-        // 遍历所有回调,清理失效的弱引用
-        callbacks.entrySet().removeIf(entry -> {
-            WeakReference<ConnectionStateCallback> weakRef = entry.getValue();
+        callbacks.forEach((tag, weakRef) -> {
             ConnectionStateCallback callback = weakRef.get();
-            
-            if (callback == null) {
-                // 回调已被垃圾回收,移除这个条目
-                Log.d(TAG, "清理失效的回调: " + entry.getKey());
-                return true;
+            if (callback != null) {
+                try {
+                    callback.onRemoteControllerConnectionChanged(isConnected);
+                } catch (Exception e) {
+                    Log.e(TAG, "回调 onRemoteControllerConnectionChanged 执行失败: " + tag, e);
+                }
+            }
+        });
+        // 清理失效的弱引用
+        callbacks.entrySet().removeIf(entry -> entry.getValue().get() == null);
+    }
+
+    // ADDED: 新增通知方法
+    /**
+     * 通知无人机起飞
+     */
+    private void notifyDroneTakeoff() {
+        Log.i(TAG, "通知无人机起飞");
+        callbacks.forEach((tag, weakRef) -> {
+            ConnectionStateCallback callback = weakRef.get();
+            if (callback != null) {
+                try {
+                    callback.onDroneTakeoff();
+                } catch (Exception e) {
+                    Log.e(TAG, "回调 onDroneTakeoff 执行失败: " + tag, e);
+                }
             }
-            
-            try {
-                callback.onRemoteControllerConnectionChanged(isConnected);
-            } catch (Exception e) {
-                Log.e(TAG, "回调执行失败: " + entry.getKey(), e);
+        });
+        // 清理失效的弱引用
+        callbacks.entrySet().removeIf(entry -> entry.getValue().get() == null);
+    }
+
+    /**
+     * 通知无人机降落
+     */
+    private void notifyDroneLanding() {
+        Log.i(TAG, "通知无人机降落");
+        callbacks.forEach((tag, weakRef) -> {
+            ConnectionStateCallback callback = weakRef.get();
+            if (callback != null) {
+                try {
+                    callback.onDroneLanding();
+                } catch (Exception e) {
+                    Log.e(TAG, "回调 onDroneLanding 执行失败: " + tag, e);
+                }
             }
-            
-            return false;
         });
+        // 清理失效的弱引用
+        callbacks.entrySet().removeIf(entry -> entry.getValue().get() == null);
     }
-    
+
     /**
      * 手动刷新连接状态(用于 SDK 初始化后)
      */
@@ -254,52 +347,57 @@ public class DeviceConnectionManager {
             Log.w(TAG, "SDK 未初始化,无法刷新连接状态");
             return;
         }
-        
+
         try {
-            // 获取当前连接状态
             Boolean droneConnected = KeyManager.getInstance().getValue(create(FlightControllerKey.KeyConnection));
             Boolean rcConnected = KeyManager.getInstance().getValue(create(RemoteControllerKey.KeyConnection));
-            
-            // 更新状态
+            // ADDED: 刷新飞行状态
+            Boolean droneFlying = KeyManager.getInstance().getValue(create(FlightControllerKey.KeyIsFlying));
+
             if (droneConnected != null) {
                 isDroneConnected.set(droneConnected);
                 notifyDroneConnectionChanged(droneConnected);
             }
-            
+
             if (rcConnected != null) {
                 isRemoteControllerConnected.set(rcConnected);
                 notifyRemoteControllerConnectionChanged(rcConnected);
             }
-            
-            Log.i(TAG, "连接状态已刷新 - 无人机: " + isDroneConnected.get() + ", 遥控器: " + isRemoteControllerConnected.get());
-            
+
+            // ADDED: 更新飞行状态
+            if (droneFlying != null) {
+                isDroneFlying.set(droneFlying);
+            }
+
+            Log.i(TAG, "连接状态已刷新 - 无人机: " + isDroneConnected.get() + ", 遥控器: " + isRemoteControllerConnected.get() + ", 飞行中: " + isDroneFlying.get());
+
         } catch (Exception e) {
             Log.e(TAG, "刷新连接状态失败", e);
         }
     }
-    
+
     /**
      * 获取有效回调数量(用于调试)
      */
     public int getActiveCallbackCount() {
-        // 清理失效的回调并返回数量
         callbacks.entrySet().removeIf(entry -> entry.getValue().get() == null);
         return callbacks.size();
     }
-    
+
     /**
      * 销毁管理器(通常在应用退出时调用)
      */
     public void destroy() {
         Log.i(TAG, "销毁 DeviceConnectionManager");
-        
+
         stopListening();
         callbacks.clear();
-        
-        // 重置状态
+
         isDroneConnected.set(false);
         isRemoteControllerConnected.set(false);
-        
+        // ADDED: 重置飞行状态
+        isDroneFlying.set(false);
+
         instance = null;
     }
-} 
+}

+ 2 - 1
app/src/main/java/com/paul/drone/data/mqtt/LiveControlMessage.java

@@ -38,7 +38,8 @@ public class LiveControlMessage {
     
     public enum Action {
         START("start"),
-        STOP("stop");
+        STOP("stop"),
+        SET_QUALITY("set_quality");
         
         private final String value;
         

+ 15 - 0
app/src/main/java/com/paul/drone/data/mqtt/OsdData.java

@@ -96,6 +96,21 @@ public class OsdData {
     
     @SerializedName("wind_warning")
     private String windWarning; // 风速预警等级(如 "LEVEL_0")
+
+    /**
+     * 飞行器累计飞行总距离
+     * 单位:米 / m
+     */
+    @SerializedName("total_flight_distance")
+    private Double totalFlightDistance;
+
+    /**
+     * 飞行器累计飞行总时间
+     * 单位:秒秒 / s
+     */
+    @SerializedName("total_flight_time")
+    private Float totalFlightTime;
+
     
     public OsdData() {}
     

+ 24 - 0
app/src/main/java/com/paul/drone/data/mqtt/OsdDataBuilder.java

@@ -3,12 +3,14 @@ package com.paul.drone.data.mqtt;
 import android.content.Context;
 import android.util.Log;
 
+import com.paul.drone.connection.DeviceConnectionManager;
 import com.paul.drone.network.SessionManager;
 import com.paul.drone.util.DeviceInfoManager;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 
+import java.nio.DoubleBuffer;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -44,6 +46,8 @@ public class OsdDataBuilder {
     
     private final Context context;
     private final DeviceInfoManager deviceInfoManager;
+
+    private final DeviceConnectionManager connectionManager;
     private final SessionManager sessionManager;
     private final SimpleDateFormat dateFormat;
     
@@ -56,6 +60,7 @@ public class OsdDataBuilder {
         this.context = context.getApplicationContext();
         this.deviceInfoManager = DeviceInfoManager.getInstance();
         this.sessionManager = new SessionManager(this.context);
+        this.connectionManager = DeviceConnectionManager.getInstance();
 
         // UTC时间格式化器
         this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
@@ -83,6 +88,8 @@ public class OsdDataBuilder {
         
         // 基础信息
         osdData.setActivationTime(Long.valueOf(getActivationTime()));
+        osdData.setTotalFlightDistance(getKeyAircraftTotalFlightDistance());
+        osdData.setTotalFlightTime(getKeyAircraftTotalFlightDuration());
         
         // 姿态信息
         osdData.setAttitudeHead(getYaw());
@@ -212,6 +219,23 @@ public class OsdDataBuilder {
         return value != null ? value : 0;
     }
 
+    //获取飞行时间 KeyFlightTimeInSeconds
+    private String getKeyFlightTimeInSeconds() {
+        Integer value = KeyManager.getInstance().getValue(DJIKey.create(FlightControllerKey.KeyFlightTimeInSeconds));
+        return value != null ? value.toString() : "0";
+    }
+
+    //获取飞行总时长 KeyAircraftTotalFlightDuration
+    private Float getKeyAircraftTotalFlightDuration() {
+        return connectionManager.getCachedTotalFlightTime();
+    }
+
+    //获取飞行总距离  double KeyAircraftTotalFlightDistance
+    private Double getKeyAircraftTotalFlightDistance() {
+        return connectionManager.getCachedTotalFlightDistance();
+    }
+
+
     /**
      * 获取风速警告级别
      *

+ 1 - 1
app/src/main/java/com/paul/drone/keyvalue/DroneTypeEnum.java

@@ -55,7 +55,7 @@ public enum DroneTypeEnum {
     /**
      * M30T
      */
-    M30T(CameraType.M30.name(), "67-1-0"),
+    M30T(CameraType.M30T.name(), "67-1-0"),
 
     // ==================== M300 RTK 系列 ====================
     /**