Kaynağa Gözat

安科院分支
2.新增准入字段判断

JaycePC 1 hafta önce
ebeveyn
işleme
cd0493589d
29 değiştirilmiş dosya ile 1234 ekleme ve 392 silme
  1. 2 1
      app/build.gradle
  2. BIN
      app/libs/fotoapparat-2.7.0.aar
  3. 5 1
      app/src/main/java/core/ui/activity/BaseActivity.java
  4. 39 0
      app/src/main/java/core/ui/activity/BaseSignActivity.kt
  5. 19 0
      app/src/main/java/http/OkHttpUtils.java
  6. 3 0
      app/src/main/java/http/client/ApiRepository.kt
  7. 1 1
      app/src/main/java/http/client/HttpTool.java
  8. 6 0
      app/src/main/java/http/client/LabClient.kt
  9. 9 0
      app/src/main/java/http/client/retrofit/ApiService.java
  10. 9 0
      app/src/main/java/http/client/retrofit/LabRetrofit.kt
  11. 2 2
      app/src/main/java/http/config/ConfigFactory.kt
  12. 18 18
      app/src/main/java/xn/xxp/HomeActivity.java
  13. 51 15
      app/src/main/java/xn/xxp/app/LabApp.java
  14. 10 9
      app/src/main/java/xn/xxp/home/auth/ChoiceAuthActivity.java
  15. 2 2
      app/src/main/java/xn/xxp/home/setting/SettingActivity.java
  16. 2 0
      app/src/main/java/xn/xxp/home/sign/SafetyCheckFragment.kt
  17. 22 11
      app/src/main/java/xn/xxp/main/MainActivity.kt
  18. 448 95
      app/src/main/java/xn/xxp/main/monitor/MonitorActivity.java
  19. 370 69
      app/src/main/java/xn/xxp/main/monitor/MonitorDialog.java
  20. 51 14
      app/src/main/java/xn/xxp/main/monitor/MonitorInfo.java
  21. 2 2
      app/src/main/java/xn/xxp/room/bean/DeviceConfig.java
  22. 10 10
      app/src/main/res/layout/activity_auth_choice.xml
  23. 120 115
      app/src/main/res/layout/activity_home.xml
  24. 12 12
      app/src/main/res/layout/activity_main.xml
  25. 1 1
      app/src/main/res/layout/fragment_auth_card.xml
  26. 15 14
      app/src/main/res/layout/fragment_window.xml
  27. BIN
      app/src/main/res/mipmap-xhdpi/img_aky_zsw_bt.png
  28. 2 0
      gradle/libs.versions.toml
  29. 3 0
      settings.gradle

+ 2 - 1
app/build.gradle

@@ -126,7 +126,7 @@ dependencies {
     implementation libs.coil
     implementation libs.coil.svg
     // fotoapparat
-    implementation libs.fotoapparat
+    //implementation libs.fotoapparat
     // gsPlayer
     implementation libs.gsyvideoplayer.java
     implementation libs.gsyvideoplayer.armv7a
@@ -136,4 +136,5 @@ dependencies {
     implementation libs.android.pdf.viewer
     // banner
     implementation 'com.github.zhpanvip:bannerviewpager:3.5.12'
+    implementation 'com.github.NodeMedia:NodeMediaClient-Android:2.9.23'
 }

BIN
app/libs/fotoapparat-2.7.0.aar


+ 5 - 1
app/src/main/java/core/ui/activity/BaseActivity.java

@@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView;
 import androidx.viewbinding.ViewBinding;
 
 import com.blankj.utilcode.util.LogUtils;
+import com.hikvision.dmb.display.InfoDisplayApi;
 
 import core.ui.common.AbsUIDelegate;
 import core.ui.common.UIDelegateImpl;
@@ -27,8 +28,11 @@ public abstract class BaseActivity<VB extends ViewBinding> extends AppCompatActi
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
+        if(2!= InfoDisplayApi.getScreenRotate()){
+            InfoDisplayApi.setScreenRotate(2);
+        }
         super.onCreate(savedInstanceState);
-        ScreenAdapter.INSTANCE.setCustomDensity(this);
+       // ScreenAdapter.INSTANCE.setCustomDensity(this);
         uiDelegate = AbsUIDelegate.Companion.create();
         configImmersiveMode();
         binding = createViewBinding();

+ 39 - 0
app/src/main/java/core/ui/activity/BaseSignActivity.kt

@@ -27,6 +27,7 @@ import xn.xxp.room.RoomTool
 import xn.xxp.room.bean.DeviceConfig
 import xn.xxp.room.bean.LabConfig
 import xn.xxp.utils.Tool
+import kotlin.math.log
 
 /**
  * info
@@ -87,6 +88,14 @@ abstract class BaseSignActivity<VB : ViewBinding> : BaseCountDownActivity<VB>()
         }
     }
 
+
+
+    protected fun magneticDoorOpen(authType: AuthType) {
+        LabApp.userVo ?: return
+
+    }
+
+
     protected fun dispatchSignOut() {
         LabApp.userVo ?: return
 
@@ -240,6 +249,36 @@ abstract class BaseSignActivity<VB : ViewBinding> : BaseCountDownActivity<VB>()
         addDisposable(disposable)
     }
 
+
+    protected fun callMagneticDoorOpenApi(authType: Int) {
+        val labId = labConfig.labId.toString();
+        val userId = LabApp.userVo.userId
+        LogUtils.d(labId,authType)
+        showLoading("操作中...")
+        val disposable = ApiRepository.magneticDoorOpen(labId,userId, authType)
+            .subscribe({
+                dismissLoading()
+                EventBus.getDefault().post(OnlineUserEvent())
+                SafetyCheckResultDialog(
+                    this,
+                    ResultEnum.SUCCESS,
+                    "操作成功",
+                    1500
+                ).apply {
+                    setCancelable(false)
+                    setOnDismissListener {
+                        LogUtils.d("setOnDismissListener操作成功")
+                    }
+                    show()
+                }
+            }, { throwable ->
+                LogUtils.e(Log.getStackTraceString(throwable))
+                dismissLoading()
+                showNetError(throwable)
+            })
+        addDisposable(disposable)
+    }
+
     private fun pretreatmentError(throwable: Throwable, text: String): Boolean {
         if (throwable is NetException) {
             val message = throwable.message.ifNullOrEmpty("${text}失败,请联系实验室管理员。")

+ 19 - 0
app/src/main/java/http/OkHttpUtils.java

@@ -36,4 +36,23 @@ public class OkHttpUtils {
         Call call = client.newCall(request);
         return call.execute();
     }
+
+
+    //可以设置超时时间 timeoutMillis 毫秒
+    public static Response postSync(String url, String json, long timeoutMillis) throws IOException {
+        // 用新 client 覆盖超时设置
+        OkHttpClient timeoutClient = client.newBuilder()
+                .connectTimeout(timeoutMillis, java.util.concurrent.TimeUnit.MILLISECONDS)
+                .readTimeout(timeoutMillis, java.util.concurrent.TimeUnit.MILLISECONDS)
+                .writeTimeout(timeoutMillis, java.util.concurrent.TimeUnit.MILLISECONDS)
+                .build();
+
+        RequestBody body = RequestBody.create(JSON, json);
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+
+        return timeoutClient.newCall(request).execute();
+    }
 }

+ 3 - 0
app/src/main/java/http/client/ApiRepository.kt

@@ -620,6 +620,9 @@ object ApiRepository {
     }
 
 
+    fun magneticDoorOpen(subId: String,userId: String,openType: Int): Observable<Boolean> {
+        return mLibClient.magneticDoorOpen(subId,userId,openType).schedulers()
+    }
 }
 
 fun <T : Any> Observable<T>.schedulers(): Observable<T> {

+ 1 - 1
app/src/main/java/http/client/HttpTool.java

@@ -66,7 +66,7 @@ public final class HttpTool {
             LabConfig labConfig = RoomTool.getInstance().labConfigDao().getLabConfig();
             JSONObject jsonObject = new JSONObject();
             jsonObject.put("subId", labConfig.getLabId());
-            return OkHttpUtils.postSync(deviceConfig.getBaseUrl() + "terminal/sys/face/data/sub", jsonObject.toString());
+            return OkHttpUtils.postSync(deviceConfig.getBaseUrl() + "terminal/sys/face/data/sub", jsonObject.toString(),300000);
         } catch (Exception e) {
             LogUtils.e(Log.getStackTraceString(e));
         }

+ 6 - 0
app/src/main/java/http/client/LabClient.kt

@@ -298,4 +298,10 @@ interface LabClient {
      */
     fun newMsgGroup(param: NoticeReq): Observable<List<NoticeSummary>>
 
+    /**
+     * 更新记录
+     */
+    fun magneticDoorOpen(subId: String,userId: String, openType: Int): Observable<Boolean>
+
+
 }

+ 9 - 0
app/src/main/java/http/client/retrofit/ApiService.java

@@ -409,4 +409,13 @@ public interface ApiService {
     @POST("terminal/authorize")
     Observable<CommonDataResponse<String>> terminalAuth(@Body TerminalAuthReq param);
 
+
+    /**
+     * 鉴权
+     */
+    @GET("terminal/lab/magneticDoorOpen")
+    Observable<CommonDataResponse<String>> magneticDoorOpen(@Query("subId") String subId,
+                                                            @Query("userId") String userId,
+                                                            @Query("openType") Integer openType);
+
 }

+ 9 - 0
app/src/main/java/http/client/retrofit/LabRetrofit.kt

@@ -556,6 +556,15 @@ open class LabRetrofit : LabClient {
             .map(this::recordsConvert)
     }
 
+
+    /**
+     * 电磁门开启
+     */
+    override fun magneticDoorOpen(subId: String,userId: String, openType: Int): Observable<Boolean> {
+        return apiService.magneticDoorOpen(subId,userId, openType)
+            .map(this::simpleConvert) // 将 CommonResponse 的成功状态转换为 true
+    }
+
     @Throws(NetException::class)
     private fun <T> recordsConvert(response: CommonDataResponse<RecordsResponse<T>>): List<T> {
         requireSuccess(response)

+ 2 - 2
app/src/main/java/http/config/ConfigFactory.kt

@@ -15,10 +15,10 @@
 //            .build()
 //
 //        val buildReleaseConfig = ConfigParam.Builder()
-//            .baseUrl("http://172.16.0.65/api/")
+//            .baseUrl("http://192.168.166.11/api/")
 //            .signInCheckBaseUrl("http://192.168.251.2:9015/")
 //            .httpStrategy(HttpConfig.HTTP_STRATEGY_Retrofit)
-//            .mqttServerUri("tcp://172.16.0.65:31072")
+//            .mqttServerUri("tcp://192.168.166.11:31072")
 //            .mqttUName("mqtt")
 //            .mqttUPwd("mqtt@zd1883")
 //            .build()

+ 18 - 18
app/src/main/java/xn/xxp/HomeActivity.java

@@ -277,24 +277,24 @@ public class HomeActivity extends BaseCountDownActivity<ActivityHomeBinding> imp
             return null;
         }));
         // 课程列表
-        binding.eBook.setOnClickListener(new FastClickDelegate(new Function1<View, Unit>() {
-            @Override
-            public Unit invoke(View view) {
-                if (AppUtils.isAppInstalled("com.njlz.classbrand")) {
-                    try {
-                        Intent intent = new Intent();
-                        intent.setClassName("com.njlz.classbrand", "com.njlz.classbrand.mvp.ui.activity.HomeActivity");
-                        startActivity(intent);
-                    } catch (Exception e) {
-                        showToast("目标应用无权限");
-                    }
-                } else {
-                    showToast("未安装相关应用");
-                }
-
-                return null;
-            }
-        }));
+//        binding.eBook.setOnClickListener(new FastClickDelegate(new Function1<View, Unit>() {
+//            @Override
+//            public Unit invoke(View view) {
+//                if (AppUtils.isAppInstalled("com.njlz.classbrand")) {
+//                    try {
+//                        Intent intent = new Intent();
+//                        intent.setClassName("com.njlz.classbrand", "com.njlz.classbrand.mvp.ui.activity.HomeActivity");
+//                        startActivity(intent);
+//                    } catch (Exception e) {
+//                        showToast("目标应用无权限");
+//                    }
+//                } else {
+//                    showToast("未安装相关应用");
+//                }
+//
+//                return null;
+//            }
+//        }));
     }
 
     private void dispatchAccessVerify(AccessVerify accessVerify) {

+ 51 - 15
app/src/main/java/xn/xxp/app/LabApp.java

@@ -87,24 +87,60 @@ public class LabApp extends Application {
         Coil.setImageLoader(imageLoader);
     }
 
-    private void initX5() {
-        Map<String, Object> stringObjectMap = new HashMap<>();
-        stringObjectMap.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
-        stringObjectMap.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
-        QbSdk.initTbsSettings(stringObjectMap);
-        QbSdk.initX5Environment(this, new QbSdk.PreInitCallback() {
-            @Override
-            public void onCoreInitFinished() {
-                LogUtils.d("x5", "onCoreInitFinished");
-            }
+//    private void initX5() {
+//        Map<String, Object> stringObjectMap = new HashMap<>();
+//        stringObjectMap.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
+//        stringObjectMap.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
+//        QbSdk.initTbsSettings(stringObjectMap);
+//        QbSdk.initX5Environment(this, new QbSdk.PreInitCallback() {
+//            @Override
+//            public void onCoreInitFinished() {
+//                LogUtils.d("x5", "onCoreInitFinished");
+//            }
+//
+//            @Override
+//            public void onViewInitFinished(boolean b) {
+//                LogUtils.d("x5", "初始化结果" + b);
+//            }
+//        });
+//    }
 
-            @Override
-            public void onViewInitFinished(boolean b) {
-                LogUtils.d("x5", "初始化结果" + b);
-            }
-        });
+    private void initX5() {
+        // 【最简单处理】包裹在巨大的 Try-Catch 中,防止任何异常导致重启
+        try {
+            // 1. 基础配置 (只保留你 SDK 里确实存在的 Key)
+            Map<String, Object> settings = new HashMap<>();
+            settings.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true);
+            settings.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true);
+
+            QbSdk.initTbsSettings(settings);
+
+            // 2. 初始化环境
+            QbSdk.initX5Environment(this, new QbSdk.PreInitCallback() {
+                @Override
+                public void onCoreInitFinished() {
+                    LogUtils.d("TBS", "Core Init Finished");
+                }
+
+                @Override
+                public void onViewInitFinished(boolean success) {
+                    if (success) {
+                        LogUtils.d("TBS", "X5 Init Success");
+                    } else {
+                        // 【关键】初始化失败仅记录日志,不报错,不重启
+                        // 此时 WebView 会自动降级为 Android 原生 WebView,功能依然可用
+                        LogUtils.e("TBS", "X5 Init Failed (Likely due to No Network). Fallback to Native WebView.");
+                    }
+                }
+            });
+        } catch (Throwable e) {
+            // 【兜底】捕获所有可能的崩溃(包括类找不到、空指针等)
+            LogUtils.e("TBS", "TBS Initialization Crashed. Ignored to prevent boot loop.", e);
+        }
     }
 
+
+
     private void initCrash() {
         final String crashPath = "/sdcard/logs/crash/";
         FileUtils.createOrExistsDir(crashPath);

+ 10 - 9
app/src/main/java/xn/xxp/home/auth/ChoiceAuthActivity.java

@@ -100,21 +100,22 @@ public class ChoiceAuthActivity extends BaseSignActivity<ActivityAuthChoiceBindi
         });
 
         // 切换为扫码
-        binding.qrAuth.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (!(FragmentUtils.getTop(fragmentManager) instanceof QrAuthFragment)) {
-                    FragmentUtils.replace(fragmentManager, new QrAuthFragment(), R.id.container);
-                }
-            }
-        });
+//        binding.qrAuth.setOnClickListener(new View.OnClickListener() {
+//            @Override
+//            public void onClick(View v) {
+//                if (!(FragmentUtils.getTop(fragmentManager) instanceof QrAuthFragment)) {
+//                    FragmentUtils.replace(fragmentManager, new QrAuthFragment(), R.id.container);
+//                }
+//            }
+//        });
     }
 
     public void authSuccess(AuthType authType, UserVo user) {
         LabApp.userVo = user;
         if (accessVerify.equals(AccessVerify.NORMAL.getType())) {
+            callMagneticDoorOpenApi(authType.getType());
             Intent getIntent = new Intent(ChoiceAuthActivity.this, MainActivity.class);
-            getIntent.putExtra("authType", authType);
+            getIntent.putExtra("authType", authType.name());
             startActivity(getIntent);
             finish();
         } else if (accessVerify.equals(AccessVerify.SIGN_IN.getType())) {

+ 2 - 2
app/src/main/java/xn/xxp/home/setting/SettingActivity.java

@@ -128,9 +128,9 @@ public class SettingActivity extends AppCompatActivity {
             });
 
             binding.testBaseUrlBT.setOnClickListener(v -> binding.httpUriET.setText("http://192.168.1.8/api/"));
-            binding.xnBaseUrlBT.setOnClickListener(v -> binding.httpUriET.setText("http://172.16.0.65/api/"));
+            binding.xnBaseUrlBT.setOnClickListener(v -> binding.httpUriET.setText("http://192.168.166.11/api/"));
             binding.testMqttBT.setOnClickListener(v -> binding.mqttUriET.setText("tcp://192.168.1.8:1883"));
-            binding.xnMqttBT.setOnClickListener(v -> binding.mqttUriET.setText("tcp://172.16.0.65:31072"));
+            binding.xnMqttBT.setOnClickListener(v -> binding.mqttUriET.setText("tcp://192.168.166.11:31072"));
         } catch (Exception e) {
             LogUtils.e(Log.getStackTraceString(e));
         }

+ 2 - 0
app/src/main/java/xn/xxp/home/sign/SafetyCheckFragment.kt

@@ -184,6 +184,8 @@ class SafetyCheckFragment :
             .cameraErrorCallback { onError(it.message) }
             .logger(loggers(logcat(), fileLogger(requireActivity())))
             .build()
+         // 👇 加这一行:旋转预览视图,解决前置摄像头上下颠倒
+         viewBinding.cameraView.rotation = 180f
 
         // 开始检测
         viewBinding.startCheck.setOnClickListener {

+ 22 - 11
app/src/main/java/xn/xxp/main/MainActivity.kt

@@ -12,6 +12,7 @@ import androidx.core.content.ContextCompat
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.blankj.utilcode.util.ActivityUtils
 import com.blankj.utilcode.util.AppUtils
+import com.blankj.utilcode.util.LogUtils
 import com.bumptech.glide.Glide
 import com.bumptech.glide.load.DataSource
 import com.bumptech.glide.load.engine.GlideException
@@ -125,7 +126,17 @@ class MainActivity :
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 //        mSerialPortHelper.bindService()
-        authType = (intent.getSerializableExtra("authType") as AuthType?)!!
+       // authType = (intent.getSerializableExtra("authType") as AuthType?)!!
+        val authTypeName = intent.getStringExtra("authType")
+
+        // 2. 还原为枚举
+        authType = AuthType.values().find {
+            it.name == authTypeName
+        } ?: run {
+            // 如果没找到,记录错误并给默认值
+            LogUtils.e("AuthError", "Could not find AuthType for name: $authTypeName. Available types: ${AuthType.values().joinToString { it.name }}")
+            AuthType.FACE
+        }
         // 实验室文化图 > 文化图
         val message = mBannerHandler.obtainMessage(WHAT_WORKBENCH)
         mBannerHandler.sendMessageDelayed(message, BANNER_TIME_WORKBENCH)
@@ -558,16 +569,16 @@ class MainActivity :
         binding.labName.text = laboratoryVo.address
 
         // 二维码
-        GlobalScope.launch(Dispatchers.Main) {
-            val bitmap = withContext(Dispatchers.IO) {
-                QrTool.generateQRCode(laboratoryVo.qrCodeUrl, Tool.INSTANCE.dip2px(150f))
-            }
-            Glide.with(this@MainActivity)
-                .asBitmap()
-                .load(bitmap)
-                .error(R.mipmap.img_error)
-                .into(binding.qrCode)
-        }
+//        GlobalScope.launch(Dispatchers.Main) {
+//            val bitmap = withContext(Dispatchers.IO) {
+//                QrTool.generateQRCode(laboratoryVo.qrCodeUrl, Tool.INSTANCE.dip2px(150f))
+//            }
+//            Glide.with(this@MainActivity)
+//                .asBitmap()
+//                .load(bitmap)
+//                .error(R.mipmap.img_error)
+//                .into(binding.qrCode)
+//        }
     }
 
     private fun queryBulletinBoard() {

+ 448 - 95
app/src/main/java/xn/xxp/main/monitor/MonitorActivity.java

@@ -1,27 +1,31 @@
 package xn.xxp.main.monitor;
 
+import static com.blankj.utilcode.util.ViewUtils.runOnUiThread;
+
 import android.content.DialogInterface;
-import android.graphics.SurfaceTexture;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
-import android.view.TextureView;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.GridLayout;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
 
 import com.blankj.utilcode.util.LogUtils;
 import com.blankj.utilcode.util.ThreadUtils;
-import com.hikvision.hatomplayer.DefaultHatomPlayer;
-import com.hikvision.hatomplayer.HatomPlayer;
-import com.hikvision.hatomplayer.PlayCallback;
-import com.hikvision.hatomplayer.PlayConfig;
-import com.hikvision.hatomplayer.core.Quality;
+import cn.nodemedia.NodePlayer;
+import cn.nodemedia.NodePlayerDelegate;
+import cn.nodemedia.NodePlayerView;
 
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import core.ui.activity.BaseCountDownActivity;
 import core.util.FastClickDelegate;
@@ -43,6 +47,9 @@ public class MonitorActivity extends BaseCountDownActivity<ActivityMonitorBindin
     private ActivityMonitorBinding binding;
     private List<MonitorInfo> monitorInfoList = new ArrayList<>();
     private MonitorDialog monitorDialog;
+    private volatile boolean isExiting = false;
+    private volatile boolean isDialogShowing = false;
+    private Handler mainHandler = new Handler(Looper.getMainLooper());
 
     @Override
     public ITitleBar getMTitleBar() {
@@ -70,28 +77,7 @@ public class MonitorActivity extends BaseCountDownActivity<ActivityMonitorBindin
 
             @Override
             public void onBackViewClicked() {
-                showLoading("正在退出...");
-                ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
-                    @Override
-                    public Object doInBackground() throws Throwable {
-                        if (null != monitorInfoList) {
-                            for (int i = 0; i < monitorInfoList.size(); i++) {
-                                MonitorInfo monitorInfo = monitorInfoList.get(i);
-                                HatomPlayer hatomPlayer = monitorInfo.getHatomPlayer();
-                                if (null != hatomPlayer) {
-                                    hatomPlayer.stop();
-                                }
-                            }
-                        }
-                        return null;
-                    }
-
-                    @Override
-                    public void onSuccess(Object result) {
-                        dismissLoading();
-                        finish();
-                    }
-                });
+                safeExitActivity();
             }
 
             @Override
@@ -100,9 +86,8 @@ public class MonitorActivity extends BaseCountDownActivity<ActivityMonitorBindin
             }
         });
 
-
         LabConfig labConfig = RoomTool.getInstance().labConfigDao().getLabConfig();
-        showLoading("加载...");
+        showLoading("加载监控...");
         addDisposable(ApiRepository.INSTANCE.cameraBySubjectId(String.valueOf(labConfig.getLabId()), LabApp.userVo.userId, LabApp.userVo.userName, 3).subscribe(new Consumer<List<MonitorVo>>() {
             @Override
             public void accept(List<MonitorVo> monitorVos) throws Throwable {
@@ -114,87 +99,172 @@ public class MonitorActivity extends BaseCountDownActivity<ActivityMonitorBindin
                             int width = binding.gridLayout.getWidth();
                             int height = binding.gridLayout.getHeight();
 
+                            // 计数器
+                            final AtomicInteger loadingCounter = new AtomicInteger(0);
+                            final int totalPlayers = monitorVos.size();
+
+                            // 立即关闭主loading
+                            dismissLoading();
+
                             for (int i = 0; i < monitorVos.size(); i++) {
                                 MonitorVo monitorVo = monitorVos.get(i);
                                 MonitorInfo monitorInfo = new MonitorInfo();
-                                TextureView textureView = new TextureView(MonitorActivity.this);
+
+                                // 创建 NodePlayerView
+                                NodePlayerView nodePlayerView = new NodePlayerView(MonitorActivity.this);
                                 GridLayout.LayoutParams params = new GridLayout.LayoutParams();
                                 params.width = width / 3;
                                 params.height = height / 3;
-                                params.setMargins(5, 5, 5, 5); // 设置间距
-                                textureView.setLayoutParams(params);
-                                textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
-                                    @Override
-                                    public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
-                                        for (int j = 0; j < monitorInfoList.size(); j++) {
-                                            MonitorInfo monitorInfo = monitorInfoList.get(j);
-                                            SurfaceTexture surfaceTexture = monitorInfo.getTextureView().getSurfaceTexture();
-                                            if (surfaceTexture == surface) {
-                                                play(monitorInfo.getHatomPlayer(), surface, monitorInfo.getMonitorVo().streamUrl);
-                                                break;
-                                            }
-                                        }
-                                    }
+                                params.setMargins(5, 5, 5, 5);
+                                nodePlayerView.setLayoutParams(params);
+
+                                // 加载提示
+                                TextView loadingText = new TextView(MonitorActivity.this);
+                                loadingText.setLayoutParams(new android.widget.FrameLayout.LayoutParams(
+                                        android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
+                                        android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
+                                        android.view.Gravity.CENTER
+                                ));
+                                loadingText.setText("连接中");
+                                loadingText.setTextColor(0xFFFFFFFF);
+                                loadingText.setTextSize(10);
+                                loadingText.setBackgroundColor(0x80000000);
+                                loadingText.setPadding(10, 5, 10, 5);
+                                loadingText.setVisibility(View.VISIBLE);
+
+                                // 容器布局
+                                android.widget.FrameLayout container = new android.widget.FrameLayout(MonitorActivity.this);
+                                container.setLayoutParams(params);
+                                container.addView(nodePlayerView);
+                                container.addView(loadingText);
+
+                                // 保存引用
+                                monitorInfo.setLoadingText(loadingText);
+                                monitorInfo.setNodePlayerView(nodePlayerView);
+                                monitorInfo.setMonitorVo(monitorVo);
+                                monitorInfo.setContainerView(container);
+                                monitorInfoList.add(monitorInfo);
 
-                                    @Override
-                                    public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
+                                // 立即添加到布局
+                                binding.gridLayout.addView(container);
 
-                                    }
+                                // 立即开始配置播放器
+                                final int index = i;
+                                final NodePlayer nodePlayer = new NodePlayer(MonitorActivity.this);
 
-                                    @Override
-                                    public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
-                                        return false;
-                                    }
+                                // 基础配置
+                                nodePlayer.setPlayerView(nodePlayerView);
+
+                                try {
+                                    nodePlayer.setRtspTransport("1");
+                                    nodePlayer.setVideoEnable(true);
+                                    nodePlayer.setAudioEnable(false);
+                                    nodePlayer.setBufferTime(100);
+                                    nodePlayer.setMaxBufferTime(300);
+                                } catch (Exception e) {
+                                    LogUtils.e("配置失败: " + e.getMessage());
+                                }
+
+                                // 硬件解码
+                                try {
+                                    Method setHWEnableMethod = nodePlayer.getClass()
+                                            .getMethod("setHWEnable", boolean.class);
+                                    setHWEnableMethod.invoke(nodePlayer, true);
+                                } catch (Exception e) {
+                                    // 忽略
+                                }
+
+                                // 地址优化
+                                String streamUrl = monitorVo.streamUrl;
+                                String optimizedUrl = streamUrl;
+
+                                if (streamUrl.contains("stream=0")) {
+                                    optimizedUrl = streamUrl.replace("stream=0", "stream=1");
+                                } else if (streamUrl.contains("main")) {
+                                    optimizedUrl = streamUrl.replace("main", "sub");
+                                }
+
+                                // 设置地址
+                                try {
+                                    nodePlayer.setInputUrl(optimizedUrl);
+                                } catch (Exception e) {
+                                    LogUtils.e("设置地址失败: " + e.getMessage());
+                                    nodePlayer.setInputUrl(streamUrl);
+                                }
 
+                                // 保存播放器
+                                monitorInfo.setNodePlayer(nodePlayer);
+
+                                // 立即启动播放器
+                                try {
+                                    nodePlayer.start();
+                                } catch (Exception e) {
+                                    LogUtils.e("启动失败: " + e.getMessage());
+                                }
+
+                                // 设置点击事件 - 修复版
+                                final MonitorInfo finalMonitorInfo = monitorInfo;
+                                container.setOnClickListener(new View.OnClickListener() {
                                     @Override
-                                    public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
+                                    public void onClick(View v) {
+                                        if (isExiting || isDialogShowing) {
+                                            return;
+                                        }
 
+                                        for (int j = 0; j < monitorInfoList.size(); j++) {
+                                            MonitorInfo info = monitorInfoList.get(j);
+                                            if (info.getContainerView() == v) {
+                                                showFullScreenDialog(info);
+                                                break;
+                                            }
+                                        }
                                     }
                                 });
 
-                                textureView.setOnClickListener(new FastClickDelegate(new Function1<View, Unit>() {
+                                // 状态监听
+                                final TextView finalLoadingText = loadingText;
+                                nodePlayer.setNodePlayerDelegate(new NodePlayerDelegate() {
                                     @Override
-                                    public Unit invoke(View view) {
-                                        for (int j = 0; j < monitorInfoList.size(); j++) {
-                                            MonitorInfo monitorInfo = monitorInfoList.get(j);
-                                            if (monitorInfo.getTextureView() == view) {
-                                                monitorDialog = new MonitorDialog(MonitorActivity.this, monitorInfo, new DialogInterface.OnDismissListener() {
+                                    public void onEventCallback(NodePlayer player, int event, String msg) {
+                                        {
+                                            if (event == 1000 || event == 1001 || event == 2000 || event == 2001) {
+                                                runOnUiThread(new Runnable() {
                                                     @Override
-                                                    public void onDismiss(DialogInterface dialog) {
-                                                        dialog.dismiss();
-                                                        monitorDialog = null;
+                                                    public void run() {
+                                                        if (finalLoadingText.getVisibility() == View.VISIBLE) {
+                                                            finalLoadingText.setVisibility(View.GONE);
+                                                            loadingCounter.incrementAndGet();
+                                                            if (loadingCounter.get() == totalPlayers) {
+                                                                LogUtils.d("所有播放器加载完成");
+                                                            }
+                                                        }
                                                     }
                                                 });
-                                                monitorDialog.show();
-                                                break;
                                             }
                                         }
-                                        return null;
-                                    }
-                                }));
-
-                                HatomPlayer hatomPlayer = new DefaultHatomPlayer();
-                                PlayConfig playConfig = new PlayConfig();
-                                playConfig.hardDecode = true;
-                                playConfig.privateData = true;
-                                hatomPlayer.setPlayConfig(playConfig);
-                                hatomPlayer.setPlayStatusCallback(new PlayCallback.PlayStatusCallback() {
-                                    @Override
-                                    public void onPlayerStatus(@NonNull PlayCallback.Status status, String s) {
-                                        LogUtils.d(status, s);
                                     }
                                 });
 
-                                monitorInfo.setTextureView(textureView);
-                                monitorInfo.setMonitorVo(monitorVo);
-                                monitorInfo.setHatomPlayer(hatomPlayer);
-
-                                monitorInfoList.add(monitorInfo);
+                                // 快速超时机制
+                                mainHandler.postDelayed(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        if (finalLoadingText != null && finalLoadingText.getVisibility() == View.VISIBLE) {
+                                            finalLoadingText.setText("正在连接");
+                                        }
+                                    }
+                                }, 500);
 
-                                binding.gridLayout.addView(textureView);
+                                mainHandler.postDelayed(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        if (finalLoadingText != null && finalLoadingText.getVisibility() == View.VISIBLE) {
+                                            finalLoadingText.setVisibility(View.GONE);
+                                            loadingCounter.incrementAndGet();
+                                        }
+                                    }
+                                }, 1500);
                             }
-
-                            dismissLoading();
                         }
                     });
                 } else {
@@ -207,34 +277,317 @@ public class MonitorActivity extends BaseCountDownActivity<ActivityMonitorBindin
             public void accept(Throwable throwable) throws Throwable {
                 dismissLoading();
                 LogUtils.e(Log.getStackTraceString(throwable));
+                showToast("加载监控失败");
             }
         }));
     }
 
-    private void play(HatomPlayer hatomPlayer, SurfaceTexture surfaceTexture, String url) {
-        ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
+    /**
+     * 显示全屏Dialog
+     */
+    private void showFullScreenDialog(final MonitorInfo monitorInfo) {
+        if (isExiting || isDialogShowing || monitorInfo == null) {
+            return;
+        }
+
+        isDialogShowing = true;
+
+        // 先暂停小窗口播放(避免切换时的闪烁)
+        NodePlayer player = monitorInfo.getNodePlayer();
+        if (player != null) {
+            try {
+                player.pause();
+            } catch (Exception e) {
+                LogUtils.e("暂停播放失败: " + e.getMessage());
+            }
+        }
+
+        monitorDialog = new MonitorDialog(MonitorActivity.this, monitorInfo, new DialogInterface.OnDismissListener() {
             @Override
-            public Object doInBackground() throws Throwable {
-                hatomPlayer.setSurfaceTexture(surfaceTexture);
-                hatomPlayer.setDataSource(url, null);
-                hatomPlayer.changeStream(Quality.SUB_STREAM_LOW);
-                hatomPlayer.start();
-                return null;
+            public void onDismiss(DialogInterface dialog) {
+                LogUtils.d("Dialog已关闭,恢复小窗口播放");
+
+                // 延迟恢复小窗口播放
+                mainHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        restoreSmallWindow(monitorInfo);
+                    }
+                }, 100);
+
+                isDialogShowing = false;
+                monitorDialog = null;
+            }
+        });
+
+        // 显示Dialog
+        try {
+            monitorDialog.show();
+        } catch (Exception e) {
+            LogUtils.e("显示Dialog失败: " + e.getMessage());
+            isDialogShowing = false;
+            monitorDialog = null;
+
+            // 失败时恢复小窗口
+            restoreSmallWindow(monitorInfo);
+        }
+    }
+
+    /**
+     * 恢复小窗口播放
+     */
+    private void restoreSmallWindow(MonitorInfo monitorInfo) {
+        if (monitorInfo == null || isExiting) {
+            return;
+        }
+
+        NodePlayer player = monitorInfo.getNodePlayer();
+        NodePlayerView originalView = monitorInfo.getNodePlayerView();
+
+        if (player != null && originalView != null) {
+            try {
+                // 确保播放器视图是原始小窗口视图
+                player.setPlayerView(originalView);
+
+                // 延迟后开始播放
+                mainHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!isExiting) {
+                            try {
+                                player.start();
+                                LogUtils.d("小窗口播放已恢复");
+                            } catch (Exception e) {
+                                LogUtils.e("恢复播放失败: " + e.getMessage());
+                            }
+                        }
+                    }
+                }, 200);
+            } catch (Exception e) {
+                LogUtils.e("恢复小窗口视图失败: " + e.getMessage());
             }
+        }
+    }
+
+    /**
+     * 安全退出Activity
+     */
+    private void safeExitActivity() {
+        if (isExiting) {
+            return;
+        }
+        isExiting = true;
+
+        LogUtils.d("开始安全退出流程");
+        showLoading("正在退出...");
+
+        // 1. 先关闭Dialog(如果有)
+        if (isDialogShowing && monitorDialog != null && monitorDialog.isShowing()) {
+            LogUtils.d("正在关闭全屏Dialog");
+            monitorDialog.dismiss();
+            monitorDialog = null;
+            isDialogShowing = false;
+
+            // 等待Dialog完全关闭
+            mainHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    executeExitProcedure();
+                }
+            }, 300);
+        } else {
+            // 直接执行退出流程
+            executeExitProcedure();
+        }
+    }
+
+    /**
+     * 执行退出流程
+     */
+    private void executeExitProcedure() {
+        LogUtils.d("执行退出流程");
+
+        // 1. 停止所有播放器
+        stopAllPlayers();
 
+        // 2. 清理UI并释放资源
+        runOnUiThread(new Runnable() {
             @Override
-            public void onSuccess(Object result) {
+            public void run() {
+                try {
+                    // 清理网格布局
+                    binding.gridLayout.removeAllViews();
+
+                    // 释放播放器资源
+                    releaseAllPlayers();
+
+                    dismissLoading();
+
+                    // 延迟finish确保资源释放完成
+                    mainHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!isFinishing()) {
+                                finish();
+                            }
+                        }
+                    }, 200);
 
+                } catch (Exception e) {
+                    LogUtils.e("退出流程异常: " + e.getMessage());
+                    // 即使异常也要finish
+                    dismissLoading();
+                    if (!isFinishing()) {
+                        finish();
+                    }
+                }
             }
         });
     }
 
+    /**
+     * 停止所有播放器
+     */
+    private void stopAllPlayers() {
+        if (monitorInfoList != null) {
+            for (MonitorInfo monitorInfo : monitorInfoList) {
+                NodePlayer nodePlayer = monitorInfo.getNodePlayer();
+                if (nodePlayer != null) {
+                    try {
+                        nodePlayer.stop();
+                        LogUtils.d("停止播放器");
+                    } catch (Exception e) {
+                        LogUtils.e("停止播放器失败: " + e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 释放所有播放器资源
+     */
+    private void releaseAllPlayers() {
+        LogUtils.d("开始释放播放器资源");
+        if (monitorInfoList != null) {
+            for (MonitorInfo monitorInfo : monitorInfoList) {
+                NodePlayer nodePlayer = monitorInfo.getNodePlayer();
+                if (nodePlayer != null) {
+                    try {
+                        nodePlayer.release();
+                        monitorInfo.setNodePlayer(null);
+                        LogUtils.d("释放播放器成功");
+                    } catch (Exception e) {
+                        LogUtils.e("释放播放器失败: " + e.getMessage());
+                    }
+                }
+
+                // 清理其他引用
+                monitorInfo.setNodePlayerView(null);
+                monitorInfo.setContainerView(null);
+                monitorInfo.setLoadingText(null);
+                monitorInfo.setMonitorVo(null);
+            }
+            monitorInfoList.clear();
+        }
+        LogUtils.d("资源释放完成");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        LogUtils.d("onResume - isExiting:" + isExiting);
+
+        // 恢复播放(如果不在退出状态)
+        if (!isExiting && monitorInfoList != null) {
+            // 只恢复前几个播放器,避免同时启动
+            int count = Math.min(monitorInfoList.size(), 4);
+            for (int i = 0; i < count; i++) {
+                MonitorInfo monitorInfo = monitorInfoList.get(i);
+                NodePlayer nodePlayer = monitorInfo.getNodePlayer();
+                if (nodePlayer != null) {
+                    try {
+                        // 第一个立即恢复,其他的延迟一点
+                        if (i == 0) {
+                            nodePlayer.start();
+                        } else {
+                            final int index = i;
+                            mainHandler.postDelayed(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (!isExiting && index < monitorInfoList.size()) {
+                                        NodePlayer player = monitorInfoList.get(index).getNodePlayer();
+                                        if (player != null) {
+                                            try {
+                                                player.start();
+                                            } catch (Exception e) {
+                                                LogUtils.e("恢复播放失败: " + e.getMessage());
+                                            }
+                                        }
+                                    }
+                                }
+                            }, i * 100);
+                        }
+                    } catch (Exception e) {
+                        LogUtils.e("恢复播放失败: " + e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        LogUtils.d("onPause - isExiting:" + isExiting);
+
+        // 暂停播放但不停止(如果不在退出状态且没有显示Dialog)
+        if (!isExiting && !isDialogShowing && monitorInfoList != null) {
+            for (int i = 0; i < monitorInfoList.size(); i++) {
+                MonitorInfo monitorInfo = monitorInfoList.get(i);
+                NodePlayer nodePlayer = monitorInfo.getNodePlayer();
+                if (null != nodePlayer) {
+                    try {
+                        nodePlayer.pause();
+                    } catch (Exception e) {
+                        LogUtils.e("暂停播放失败: " + e.getMessage());
+                    }
+                }
+            }
+        }
+    }
+
     @Override
     protected void onDestroy() {
+        LogUtils.d("onDestroy - 开始清理资源");
+        isExiting = true;
+        isDialogShowing = false;
+
+        // 清理Handler
+        if (mainHandler != null) {
+            mainHandler.removeCallbacksAndMessages(null);
+        }
+
+        // 关闭Dialog
+        if (monitorDialog != null && monitorDialog.isShowing()) {
+            try {
+                monitorDialog.dismiss();
+            } catch (Exception e) {
+                LogUtils.e("关闭Dialog失败: " + e.getMessage());
+            }
+            monitorDialog = null;
+        }
+
+        // 释放所有资源
+        releaseAllPlayers();
+
+        // 清理Fragment
         Fragment fragment = getSupportFragmentManager().findFragmentByTag("video_dialog");
         if (fragment instanceof VideoDialogFragment) {
             ((VideoDialogFragment) fragment).dismissAllowingStateLoss();
         }
+
         super.onDestroy();
+        LogUtils.d("onDestroy - 资源清理完成");
     }
 }

+ 370 - 69
app/src/main/java/xn/xxp/main/monitor/MonitorDialog.java

@@ -2,140 +2,441 @@ package xn.xxp.main.monitor;
 
 import android.app.Dialog;
 import android.content.Context;
-import android.graphics.SurfaceTexture;
 import android.os.Bundle;
-import android.view.TextureView;
 import android.view.View;
 import android.view.Window;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.blankj.utilcode.util.LogUtils;
 import com.blankj.utilcode.util.ThreadUtils;
-import com.hikvision.hatomplayer.DefaultHatomPlayer;
-import com.hikvision.hatomplayer.HatomPlayer;
-import com.hikvision.hatomplayer.PlayCallback;
-import com.hikvision.hatomplayer.PlayConfig;
-import com.hikvision.hatomplayer.core.Quality;
+
+import cn.nodemedia.NodePlayer;
+import cn.nodemedia.NodePlayerDelegate;
+import cn.nodemedia.NodePlayerView;
 
 import core.util.FastClickDelegate;
 import kotlin.Unit;
 import kotlin.jvm.functions.Function1;
 import xn.xxp.R;
 
-
 public class MonitorDialog extends Dialog {
 
     private final MonitorInfo monitorInfo;
-    private HatomPlayer hatomPlayer;
+    private NodePlayer nodePlayer;
     private TextView textView;
+    private TextView loadingText;
+    private ProgressBar loadingProgress;
+    private NodePlayerView nodePlayerView;
+    private boolean isUsingOriginalPlayer = false;
 
     public MonitorDialog(@NonNull Context context, MonitorInfo monitorInfo, OnDismissListener dismissListener) {
         super(context, R.style.FullScreenDialog);
         this.monitorInfo = monitorInfo;
         setOnDismissListener(dismissListener);
+
+        // 设置对话框为全屏
+        if (getWindow() != null) {
+            getWindow().setFlags(
+                    android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                    android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN
+            );
+        }
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.dialog_monitor);
+
+        // 设置窗口动画
         Window window = getWindow();
         if (window != null) {
             window.setWindowAnimations(R.style.DialogAnimation);
+            window.setLayout(
+                    android.view.WindowManager.LayoutParams.MATCH_PARENT,
+                    android.view.WindowManager.LayoutParams.MATCH_PARENT
+            );
         }
+
         initVideoPlayer();
     }
 
     private void initVideoPlayer() {
-        TextureView textureView = findViewById(R.id.textureView);
+        LogUtils.d("初始化全屏播放器 - 复用原始播放器");
+
+        // 获取返回按钮
         textView = findViewById(R.id.back);
-        textView.setOnClickListener(new FastClickDelegate(new Function1<View, Unit>() {
+        if (textView == null) {
+            LogUtils.e("找不到返回按钮");
+            return;
+        }
+
+        // 设置返回按钮点击事件
+        textView.setOnClickListener(new View.OnClickListener() {
             @Override
-            public Unit invoke(View view) {
+            public void onClick(View v) {
+                LogUtils.d("点击返回按钮");
                 textView.setText("正在退出...");
-                releasePlayer();
-                return null;
+                dismiss();
             }
-        }));
-        textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
+        });
+
+        // 获取布局中的TextureView
+        View oldTextureView = findViewById(R.id.textureView);
+        if (oldTextureView == null) {
+            LogUtils.e("找不到textureView");
+            return;
+        }
+
+        // 获取父布局
+        android.view.ViewGroup parent = (android.view.ViewGroup) oldTextureView.getParent();
+        if (parent == null) {
+            LogUtils.e("textureView没有父布局");
+            return;
+        }
+
+        // 创建NodePlayerView(用于全屏显示)
+        nodePlayerView = new NodePlayerView(getContext());
+        android.widget.RelativeLayout.LayoutParams params = new android.widget.RelativeLayout.LayoutParams(
+                android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+                android.view.ViewGroup.LayoutParams.MATCH_PARENT
+        );
+        nodePlayerView.setLayoutParams(params);
+        nodePlayerView.setKeepScreenOn(true);
+
+        // 替换原TextureView
+        int index = parent.indexOfChild(oldTextureView);
+        parent.removeView(oldTextureView);
+        parent.addView(nodePlayerView, index);
+
+        LogUtils.d("NodePlayerView已添加到布局");
+
+        // 获取原始播放器
+        nodePlayer = monitorInfo.getNodePlayer();
+        if (nodePlayer == null) {
+            LogUtils.e("原始播放器为空,创建新的");
+            createNewPlayer();
+        } else {
+            // === 关键优化:复用原始播放器 ===
+            reuseOriginalPlayer();
+        }
+    }
+
+    /**
+     * 复用原始播放器(0延迟)
+     */
+    private void reuseOriginalPlayer() {
+        LogUtils.d("复用原始播放器 - 立即显示");
+        isUsingOriginalPlayer = true;
+
+        try {
+            // 1. 立即设置新的播放器视图
+            nodePlayer.setPlayerView(nodePlayerView);
+            LogUtils.d("已切换播放器视图到全屏");
+
+            // 2. 如果播放器是暂停状态,恢复播放
+            nodePlayer.start();
+
+            // 3. 立即显示(不需要等待缓冲)
+            showImmediately();
+
+            // 4. 添加监听器(可选)
+            nodePlayer.setNodePlayerDelegate(new NodePlayerDelegate() {
+                @Override
+                public void onEventCallback(NodePlayer player, int event, String msg) {
+                    {
+                        if (event == 1000 || event == 1001 || event == 2000 || event == 2001) {
+                            LogUtils.d("全屏播放器开始播放");
+                            hideLoadingImmediately();
+                        } else if (event == 1003 || event == 2003) {
+                            LogUtils.e("全屏播放失败: " + msg);
+                        }
+                    }
+                }
+            });
+
+        } catch (Exception e) {
+            LogUtils.e("复用播放器失败: " + e.getMessage());
+            // 失败时创建新播放器
+            createNewPlayer();
+        }
+    }
+
+    /**
+     * 立即显示(隐藏loading)
+     */
+    private void showImmediately() {
+        // 不需要loading,因为视频已经在播放
+        if (loadingText != null) {
+            loadingText.setVisibility(View.GONE);
+        }
+        if (loadingProgress != null) {
+            loadingProgress.setVisibility(View.GONE);
+        }
+        LogUtils.d("立即显示视频画面");
+    }
+
+    /**
+     * 创建新的播放器(备用方案)
+     */
+    private void createNewPlayer() {
+        LogUtils.d("创建新的全屏播放器");
+        isUsingOriginalPlayer = false;
+
+        // 显示加载提示
+        setupLoadingView();
+
+        ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
             @Override
-            public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
-                ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
-                    @Override
-                    public Object doInBackground() throws Throwable {
-                        hatomPlayer = new DefaultHatomPlayer();
-                        PlayConfig playConfig = new PlayConfig();
-                        playConfig.hardDecode = true;
-                        playConfig.privateData = true;
-                        hatomPlayer.setPlayConfig(playConfig);
-                        hatomPlayer.setPlayStatusCallback(new PlayCallback.PlayStatusCallback() {
-                            @Override
-                            public void onPlayerStatus(@NonNull PlayCallback.Status status, String s) {
-                                LogUtils.d(status, s);
-                            }
-                        });
-                        hatomPlayer.setSurfaceTexture(surface);
-                        hatomPlayer.setDataSource(monitorInfo.getMonitorVo().streamUrl, null);
-                        hatomPlayer.changeStream(Quality.STREAM_SUPER_CLEAR);
-                        hatomPlayer.start();
+            public Object doInBackground() throws Throwable {
+                try {
+                    // 检查流地址
+                    if (monitorInfo == null || monitorInfo.getMonitorVo() == null) {
+                        LogUtils.e("监控信息为空");
+                        hideLoading();
                         return null;
                     }
 
-                    @Override
-                    public void onSuccess(Object result) {
+                    String streamUrl = monitorInfo.getMonitorVo().streamUrl;
+                    if (streamUrl == null || streamUrl.isEmpty()) {
+                        LogUtils.e("流地址为空");
+                        hideLoading();
+                        return null;
+                    }
+
+                    LogUtils.d("创建新播放器,流地址: " + streamUrl);
+
+                    // 创建新的NodePlayer
+                    nodePlayer = new NodePlayer(getContext());
+
+                    // 设置播放视图
+                    if (nodePlayerView != null) {
+                        nodePlayer.setPlayerView(nodePlayerView);
+                    }
 
+                    // 快速配置
+                    nodePlayer.setRtspTransport("1");
+                    nodePlayer.setVideoEnable(true);
+                    nodePlayer.setAudioEnable(false);
+
+                    // 使用稍大的缓冲(200ms),保证快速连接
+                    try {
+                        nodePlayer.setBufferTime(200);
+                        nodePlayer.setMaxBufferTime(600);
+                    } catch (Exception e) {
+                        // 忽略
+                    }
+
+                    // 硬件解码
+                    try {
+                        java.lang.reflect.Method setHWEnableMethod = nodePlayer.getClass()
+                                .getMethod("setHWEnable", boolean.class);
+                        setHWEnableMethod.invoke(nodePlayer, true);
+                    } catch (Exception e) {
+                        // 忽略
                     }
-                });
+
+                    // 使用子码流(如果可用)
+                    String optimizedUrl = streamUrl;
+                    if (streamUrl.contains("stream=0")) {
+                        optimizedUrl = streamUrl.replace("stream=0", "stream=1");
+                    } else if (streamUrl.contains("main")) {
+                        optimizedUrl = streamUrl.replace("main", "sub");
+                    }
+
+                    // 设置地址并启动
+                    nodePlayer.setInputUrl(optimizedUrl);
+                    nodePlayer.start();
+
+                    // 添加监听器
+                    nodePlayer.setNodePlayerDelegate(new NodePlayerDelegate() {
+                        @Override
+                        public void onEventCallback(NodePlayer player, int event, String msg) {
+                            {
+                                if (event == 1000 || event == 1001 || event == 2000 || event == 2001) {
+                                    LogUtils.d("新播放器开始播放");
+                                    // 1秒后隐藏loading(给缓冲时间)
+                                    ThreadUtils.runOnUiThreadDelayed(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            hideLoading();
+                                        }
+                                    }, 1000);
+                                }
+                            }
+                        }
+                    });
+
+                    // 3秒超时强制隐藏loading
+                    ThreadUtils.runOnUiThreadDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            hideLoading();
+                        }
+                    }, 3000);
+
+                } catch (Exception e) {
+                    LogUtils.e("创建播放器失败: " + e.getMessage());
+                    hideLoadingWithError("播放失败");
+                }
+                return null;
             }
 
             @Override
-            public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
+            public void onSuccess(Object result) {
+                LogUtils.d("创建播放器成功");
+            }
+        });
+    }
+
+    private void setupLoadingView() {
+        // 创建加载视图(与之前类似但简化)
+        if (loadingText == null) {
+            loadingText = new TextView(getContext());
+            android.widget.RelativeLayout.LayoutParams textParams = new android.widget.RelativeLayout.LayoutParams(
+                    android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT,
+                    android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT
+            );
+            textParams.addRule(android.widget.RelativeLayout.CENTER_IN_PARENT);
+            loadingText.setLayoutParams(textParams);
+            loadingText.setText("加载中...");
+            loadingText.setTextColor(0xFFFFFFFF);
+            loadingText.setTextSize(14);
+            loadingText.setVisibility(View.VISIBLE);
 
+            if (nodePlayerView != null && nodePlayerView.getParent() != null) {
+                ((android.view.ViewGroup) nodePlayerView.getParent()).addView(loadingText);
             }
+        }
+    }
 
+    private void hideLoadingImmediately() {
+        ThreadUtils.runOnUiThread(new Runnable() {
             @Override
-            public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
-                return false;
+            public void run() {
+                if (loadingText != null) {
+                    loadingText.setVisibility(View.GONE);
+                }
+                if (loadingProgress != null) {
+                    loadingProgress.setVisibility(View.GONE);
+                }
             }
+        });
+    }
 
-            @Override
-            public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
+    private void hideLoading() {
+        hideLoadingImmediately();
+    }
 
+    private void hideLoadingWithError(String error) {
+        ThreadUtils.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (loadingText != null) {
+                    loadingText.setText(error);
+                    // 3秒后隐藏
+                    ThreadUtils.runOnUiThreadDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (loadingText != null) {
+                                loadingText.setVisibility(View.GONE);
+                            }
+                        }
+                    }, 3000);
+                }
             }
         });
     }
 
     @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        releasePlayer();
-    }
+    public void dismiss() {
+        LogUtils.d("Dialog dismiss - 恢复小窗口播放");
 
-    /**
-     * 释放播放器资源
-     */
-    private void releasePlayer() {
-        if (hatomPlayer != null) {
-            dismiss();
-            ThreadUtils.executeByCached(new ThreadUtils.SimpleTask<Object>() {
-                @Override
-                public Object doInBackground() throws Throwable {
-                    hatomPlayer.stop(); // 释放播放器
-                    return null;
-                }
+        // === 关键修复:先恢复原始视图,再清理Dialog ===
+        if (isUsingOriginalPlayer && nodePlayer != null && monitorInfo != null) {
+            try {
+                // 1. 先暂停播放(避免切换时的闪烁)
+                nodePlayer.pause();
 
-                @Override
-                public void onSuccess(Object result) {
-                    dismiss();
+                // 2. 切换到原始视图
+                NodePlayerView originalView = monitorInfo.getNodePlayerView();
+                if (originalView != null && originalView.getParent() != null) {
+                    nodePlayer.setPlayerView(originalView);
+                    LogUtils.d("已恢复小窗口视图");
+                } else {
+                    LogUtils.e("原始视图无效或已被移除");
                 }
-            });
-            textView.setOnClickListener(null);
-            hatomPlayer = null; // 置空防止内存泄漏
-        } else {
-            dismiss();
+
+                // 3. 重新开始播放(在原始视图中)
+                ThreadUtils.runOnUiThreadDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            nodePlayer.start();
+                        } catch (Exception e) {
+                            LogUtils.e("恢复播放失败: " + e.getMessage());
+                        }
+                    }
+                }, 100);
+
+            } catch (Exception e) {
+                LogUtils.e("恢复小窗口失败: " + e.getMessage());
+            }
+        }
+
+        // === 关键:不要释放播放器!只是清理Dialog的资源 ===
+        if (!isUsingOriginalPlayer && nodePlayer != null) {
+            // 只释放新创建的播放器
+            try {
+                nodePlayer.stop();
+                nodePlayer.release();
+            } catch (Exception e) {
+                LogUtils.e("释放新播放器失败: " + e.getMessage());
+            }
+        } else if (isUsingOriginalPlayer) {
+            // === 关键:不要释放原始播放器!置空引用即可 ===
+            nodePlayer = null;
+        }
+
+        // 清理Dialog的视图
+        if (nodePlayerView != null && nodePlayerView.getParent() != null) {
+            ((android.view.ViewGroup) nodePlayerView.getParent()).removeView(nodePlayerView);
+        }
+
+        // 清理加载视图
+        if (loadingText != null && loadingText.getParent() != null) {
+            ((android.view.ViewGroup) loadingText.getParent()).removeView(loadingText);
+        }
+        if (loadingProgress != null && loadingProgress.getParent() != null) {
+            ((android.view.ViewGroup) loadingProgress.getParent()).removeView(loadingProgress);
+        }
+
+        // 清理引用
+        nodePlayerView = null;
+        loadingText = null;
+        loadingProgress = null;
+
+        // 最后调用父类的dismiss
+        try {
+            super.dismiss();
+        } catch (Exception e) {
+            LogUtils.e("super.dismiss()失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // 只暂停新创建的播放器,复用播放器不处理
+        if (!isUsingOriginalPlayer && nodePlayer != null) {
+            try {
+                nodePlayer.pause();
+            } catch (Exception e) {
+                // 忽略
+            }
         }
     }
-}
+}

+ 51 - 14
app/src/main/java/xn/xxp/main/monitor/MonitorInfo.java

@@ -1,16 +1,21 @@
 package xn.xxp.main.monitor;
 
-import android.view.TextureView;
-
-import com.hikvision.hatomplayer.HatomPlayer;
+import android.view.View;
+import android.widget.TextView;
 
+import cn.nodemedia.NodePlayer;
+import cn.nodemedia.NodePlayerView;
 import http.vo.response.MonitorVo;
 
 public class MonitorInfo {
-    private TextureView textureView;
-    private HatomPlayer hatomPlayer;
+    private NodePlayerView nodePlayerView;
+    private NodePlayer nodePlayer;
     private MonitorVo monitorVo;
     private boolean isFull;
+    private View containerView;
+    private TextView loadingText; // 添加加载文本引用
+    private boolean isPlaying = false; // 添加播放状态
+    private TextView loadingTextView; // 加载文本视图
 
     public boolean isFull() {
         return isFull;
@@ -20,20 +25,20 @@ public class MonitorInfo {
         isFull = full;
     }
 
-    public TextureView getTextureView() {
-        return textureView;
+    public NodePlayerView getNodePlayerView() {
+        return nodePlayerView;
     }
 
-    public void setTextureView(TextureView textureView) {
-        this.textureView = textureView;
+    public void setNodePlayerView(NodePlayerView nodePlayerView) {
+        this.nodePlayerView = nodePlayerView;
     }
 
-    public HatomPlayer getHatomPlayer() {
-        return hatomPlayer;
+    public NodePlayer getNodePlayer() {
+        return nodePlayer;
     }
 
-    public void setHatomPlayer(HatomPlayer hatomPlayer) {
-        this.hatomPlayer = hatomPlayer;
+    public void setNodePlayer(NodePlayer nodePlayer) {
+        this.nodePlayer = nodePlayer;
     }
 
     public MonitorVo getMonitorVo() {
@@ -43,4 +48,36 @@ public class MonitorInfo {
     public void setMonitorVo(MonitorVo monitorVo) {
         this.monitorVo = monitorVo;
     }
-}
+
+    public View getContainerView() {
+        return containerView;
+    }
+
+    public void setContainerView(View containerView) {
+        this.containerView = containerView;
+    }
+
+    public TextView getLoadingText() {
+        return loadingText;
+    }
+
+    public void setLoadingText(TextView loadingText) {
+        this.loadingText = loadingText;
+    }
+
+    public boolean isPlaying() {
+        return isPlaying;
+    }
+
+    public void setPlaying(boolean playing) {
+        isPlaying = playing;
+    }
+
+    public TextView getLoadingTextView() {
+        return loadingTextView;
+    }
+
+    public void setLoadingTextView(TextView loadingTextView) {
+        this.loadingTextView = loadingTextView;
+    }
+}

+ 2 - 2
app/src/main/java/xn/xxp/room/bean/DeviceConfig.java

@@ -10,10 +10,10 @@ public class DeviceConfig {
 
     private String ip = "";
     private String devId = "";
-    private String baseUrl = "http://172.16.0.65/api/";
+    private String baseUrl = "http://192.168.166.11/api/";
     private String mqttName = "mqtt";
     private String mqttPas = "mqtt@zd1883";
-    private String mqttAddress = "tcp://172.16.0.65:31072";
+    private String mqttAddress = "tcp://192.168.166.11:31072";
     private String adminPas = "admin@098&";
     private String terminalAuth = "";
 

+ 10 - 10
app/src/main/res/layout/activity_auth_choice.xml

@@ -113,16 +113,16 @@
                 android:textSize="13sp"
                 android:visibility="gone" />
 
-            <RadioButton
-                android:id="@+id/qrAuth"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:button="@null"
-                android:gravity="center"
-                android:text="扫码验证"
-                android:textColor="@color/textcolor_cbx_black"
-                android:textSize="13sp" />
+<!--            <RadioButton-->
+<!--                android:id="@+id/qrAuth"-->
+<!--                android:layout_width="0dp"-->
+<!--                android:layout_height="match_parent"-->
+<!--                android:layout_weight="1"-->
+<!--                android:button="@null"-->
+<!--                android:gravity="center"-->
+<!--                android:text="扫码验证"-->
+<!--                android:textColor="@color/textcolor_cbx_black"-->
+<!--                android:textSize="13sp" />-->
 
         </RadioGroup>
 

+ 120 - 115
app/src/main/res/layout/activity_home.xml

@@ -30,7 +30,7 @@
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/dutyLayout"
             android:layout_width="match_parent"
-            android:layout_height="90dp"
+            android:layout_height="100dp"
             app:layout_constraintBottom_toTopOf="@id/experimentLayout"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -116,7 +116,7 @@
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/experimentLayout"
             android:layout_width="match_parent"
-            android:layout_height="90dp"
+            android:layout_height="110dp"
             android:layout_marginTop="10dp"
             android:layout_marginBottom="10dp"
             app:layout_constraintBottom_toTopOf="@id/accessLayout"
@@ -128,6 +128,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:layout_marginStart="10dp"
+                android:layout_marginTop="10dp"
                 android:background="@drawable/shape_rect_round_11_solid_white"
                 android:orientation="vertical"
                 app:layout_constraintStart_toStartOf="parent">
@@ -213,124 +214,127 @@
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/accessLayout"
             android:layout_width="match_parent"
-            android:layout_height="90dp"
+            android:layout_height="120dp"
             android:layout_marginBottom="10dp"
-            app:layout_constraintBottom_toTopOf="@id/eBook"
+
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/experimentLayout">
 
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_marginStart="10dp"
-                android:background="@drawable/shape_rect_round_11_solid_white"
-                android:orientation="vertical"
-                app:layout_constraintStart_toStartOf="parent">
-
-                <TextView
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="18dp"
-                    android:layout_marginTop="8dp"
-                    android:text="准入人员"
-                    android:textColor="#FF0B85F8"
-                    android:textSize="14sp" />
-
-                <View
-                    android:layout_width="match_parent"
-                    android:layout_height="1dp"
-                    android:layout_marginTop="6dp"
-                    android:background="#E0E0E0" />
-
-                <AdapterViewFlipper
-                    android:id="@+id/accessPerson"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginStart="11dp"
-                    android:layout_marginTop="11dp"
-                    android:layout_marginEnd="11dp"
-                    android:layout_marginBottom="11dp" />
-
-                <ImageView
-                    android:id="@+id/accessPersonEmpty"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_marginStart="11dp"
-                    android:layout_marginTop="11dp"
-                    android:layout_marginEnd="11dp"
-                    android:layout_marginBottom="11dp"
-                    android:contentDescription="@null"
-                    android:scaleType="centerInside"
-                    android:src="@mipmap/no_data" />
-
-            </LinearLayout>
-
-            <View
-                android:id="@+id/accessExpand_bg"
-                android:layout_width="10dp"
-                android:layout_height="33dp"
-                android:background="@mipmap/img_tx"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-            <ImageView
-                android:id="@+id/accessExpand"
-                android:layout_width="22dp"
-                android:layout_height="22dp"
-                android:contentDescription="@null"
-                android:paddingStart="6dp"
-                android:paddingTop="7dp"
-                android:paddingEnd="6dp"
-                android:paddingBottom="7dp"
-                android:src="@mipmap/icon_sy_zzk"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-            <androidx.constraintlayout.widget.Group
-                android:id="@+id/accessExpand_group"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:constraint_referenced_ids="accessExpand_bg,accessExpand" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
-
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/eBook"
-            android:layout_width="match_parent"
-            android:layout_height="52dp"
-            android:layout_marginBottom="10dp"
-            android:background="@mipmap/img_bg_kclb"
-            app:layout_constraintBottom_toTopOf="@id/singIn"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent">
-
-            <ImageView
-                android:id="@+id/eBook_image"
-                android:layout_width="26dp"
-                android:layout_height="23dp"
-                android:layout_marginStart="19dp"
-                android:contentDescription="@null"
-                android:src="@mipmap/icon_sy_kclb"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="18dp"
-                android:text="课程列表"
-                android:textColor="@android:color/white"
-                android:textSize="11sp"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintStart_toEndOf="@id/eBook_image"
-                app:layout_constraintTop_toTopOf="parent"
-                tools:ignore="SmallSp" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
+            <!--app:layout_constraintBottom_toTopOf="@id/eBook" -->
+
+           <LinearLayout
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:layout_marginStart="10dp"
+               android:layout_marginTop="20dp"
+               android:background="@drawable/shape_rect_round_11_solid_white"
+               android:orientation="vertical"
+               app:layout_constraintStart_toStartOf="parent">
+
+               <TextView
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:layout_marginStart="18dp"
+                   android:layout_marginTop="8dp"
+                   android:text="准入人员"
+                   android:textColor="#FF0B85F8"
+                   android:textSize="14sp" />
+
+               <View
+                   android:layout_width="match_parent"
+                   android:layout_height="1dp"
+                   android:layout_marginTop="6dp"
+                   android:background="#E0E0E0" />
+
+               <AdapterViewFlipper
+                   android:id="@+id/accessPerson"
+                   android:layout_width="match_parent"
+                   android:layout_height="wrap_content"
+                   android:layout_marginStart="11dp"
+                   android:layout_marginTop="11dp"
+                   android:layout_marginEnd="11dp"
+                   android:layout_marginBottom="11dp" />
+
+               <ImageView
+                   android:id="@+id/accessPersonEmpty"
+                   android:layout_width="match_parent"
+                   android:layout_height="match_parent"
+                   android:layout_marginStart="11dp"
+                   android:layout_marginTop="11dp"
+                   android:layout_marginEnd="11dp"
+                   android:layout_marginBottom="11dp"
+                   android:contentDescription="@null"
+                   android:scaleType="centerInside"
+                   android:src="@mipmap/no_data" />
+
+           </LinearLayout>
+
+           <View
+               android:id="@+id/accessExpand_bg"
+               android:layout_width="10dp"
+               android:layout_height="33dp"
+               android:background="@mipmap/img_tx"
+               app:layout_constraintBottom_toBottomOf="parent"
+               app:layout_constraintStart_toStartOf="parent"
+               app:layout_constraintTop_toTopOf="parent" />
+
+           <ImageView
+               android:id="@+id/accessExpand"
+               android:layout_width="22dp"
+               android:layout_height="22dp"
+               android:contentDescription="@null"
+               android:paddingStart="6dp"
+               android:paddingTop="7dp"
+               android:paddingEnd="6dp"
+               android:paddingBottom="7dp"
+               android:src="@mipmap/icon_sy_zzk"
+               app:layout_constraintBottom_toBottomOf="parent"
+               app:layout_constraintStart_toStartOf="parent"
+               app:layout_constraintTop_toTopOf="parent" />
+
+           <androidx.constraintlayout.widget.Group
+               android:id="@+id/accessExpand_group"
+               android:layout_width="wrap_content"
+               android:layout_height="wrap_content"
+               app:constraint_referenced_ids="accessExpand_bg,accessExpand" />
+
+       </androidx.constraintlayout.widget.ConstraintLayout>
+
+<!--        <androidx.constraintlayout.widget.ConstraintLayout-->
+<!--            android:id="@+id/eBook"-->
+<!--            android:layout_width="match_parent"-->
+<!--            android:layout_height="52dp"-->
+<!--            android:layout_marginBottom="10dp"-->
+<!--            android:background="@mipmap/img_bg_kclb"-->
+<!--            app:layout_constraintBottom_toTopOf="@id/singIn"-->
+<!--            app:layout_constraintEnd_toEndOf="parent"-->
+<!--            app:layout_constraintStart_toStartOf="parent">-->
+
+<!--            <ImageView-->
+<!--                android:id="@+id/eBook_image"-->
+<!--                android:layout_width="26dp"-->
+<!--                android:layout_height="23dp"-->
+<!--                android:layout_marginStart="19dp"-->
+<!--                android:contentDescription="@null"-->
+<!--                android:src="@mipmap/icon_sy_kclb"-->
+<!--                app:layout_constraintBottom_toBottomOf="parent"-->
+<!--                app:layout_constraintStart_toStartOf="parent"-->
+<!--                app:layout_constraintTop_toTopOf="parent" />-->
+
+<!--            <TextView-->
+<!--                android:layout_width="wrap_content"-->
+<!--                android:layout_height="wrap_content"-->
+<!--                android:layout_marginStart="18dp"-->
+<!--                android:text="课程列表"-->
+<!--                android:textColor="@android:color/white"-->
+<!--                android:textSize="11sp"-->
+<!--                app:layout_constraintBottom_toBottomOf="parent"-->
+<!--                app:layout_constraintStart_toEndOf="@id/eBook_image"-->
+<!--                app:layout_constraintTop_toTopOf="parent"-->
+<!--                tools:ignore="SmallSp" />-->
+
+<!--        </androidx.constraintlayout.widget.ConstraintLayout>-->
 
         <androidx.constraintlayout.widget.Guideline
             android:id="@+id/guideline"
@@ -459,6 +463,7 @@
 <!--            app:layout_constraintBottom_toBottomOf="@id/bulletin_bg"-->
 <!--            app:layout_constraintEnd_toEndOf="@id/bulletin_bg"-->
 <!--            app:layout_constraintStart_toStartOf="@id/bulletin_bg" />-->
+
         <xn.xxp.widget.AutoScrollRecyclerView
             android:id="@+id/bulletinBoardView"
             android:layout_width="0dp"

+ 12 - 12
app/src/main/res/layout/activity_main.xml

@@ -226,18 +226,18 @@
                 app:layout_constraintTop_toTopOf="parent"
                 tools:text="大气污染控制工程实验室" />
 
-            <androidx.constraintlayout.utils.widget.ImageFilterView
-                android:id="@+id/qrCode"
-                android:layout_width="62dp"
-                android:layout_height="62dp"
-                android:layout_marginEnd="12dp"
-                android:contentDescription="@null"
-                android:scaleType="fitXY"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toTopOf="parent"
-                app:round="6dp"
-                tools:src="@mipmap/img_sy_ewm" />
+<!--            <androidx.constraintlayout.utils.widget.ImageFilterView-->
+<!--                android:id="@+id/qrCode"-->
+<!--                android:layout_width="62dp"-->
+<!--                android:layout_height="62dp"-->
+<!--                android:layout_marginEnd="12dp"-->
+<!--                android:contentDescription="@null"-->
+<!--                android:scaleType="fitXY"-->
+<!--                app:layout_constraintBottom_toBottomOf="parent"-->
+<!--                app:layout_constraintEnd_toEndOf="parent"-->
+<!--                app:layout_constraintTop_toTopOf="parent"-->
+<!--                app:round="6dp"-->
+<!--                tools:src="@mipmap/img_sy_ewm" />-->
 
         </androidx.constraintlayout.widget.ConstraintLayout>
 

+ 1 - 1
app/src/main/res/layout/fragment_auth_card.xml

@@ -11,7 +11,7 @@
         android:layout_height="345dp"
         android:contentDescription="@null"
         android:scaleType="fitXY"
-        android:src="@mipmap/img_sk_xt"
+        android:src="@mipmap/img_aky_zsw_bt"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />

+ 15 - 14
app/src/main/res/layout/fragment_window.xml

@@ -54,7 +54,7 @@
                 </LinearLayout>
 
                 <LinearLayout
-                    android:layout_width="220px"
+                    android:layout_width="200px"
                     android:layout_height="match_parent"
                     android:background="@drawable/shape_rect_e0e0e0"
                     android:orientation="vertical">
@@ -67,7 +67,7 @@
                         android:gravity="center"
                         android:text="单位名称"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -82,7 +82,7 @@
                         android:gravity="center"
                         android:text="实验室名称"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -97,7 +97,7 @@
                         android:gravity="center"
                         android:text="实验室负责人"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                 </LinearLayout>
 
@@ -120,7 +120,7 @@
                         android:layout_weight="1"
                         android:gravity="center_vertical"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -135,8 +135,9 @@
                         android:layout_marginEnd="30px"
                         android:layout_weight="1"
                         android:gravity="center_vertical"
+                        android:maxLines="1"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -152,7 +153,7 @@
                         android:layout_weight="1"
                         android:gravity="center_vertical"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -174,7 +175,7 @@
                         android:gravity="center"
                         android:text="楼宇"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -189,7 +190,7 @@
                         android:gravity="center"
                         android:text="房间号"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -204,7 +205,7 @@
                         android:gravity="center"
                         android:text="负责人电话"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                 </LinearLayout>
 
@@ -227,7 +228,7 @@
                         android:layout_weight="1"
                         android:gravity="center_vertical"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -243,7 +244,7 @@
                         android:layout_weight="1"
                         android:gravity="center_vertical"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -259,7 +260,7 @@
                         android:layout_weight="1"
                         android:gravity="center_vertical"
                         android:textColor="#333333"
-                        android:textSize="24px" />
+                        android:textSize="22px" />
 
                     <View
                         android:layout_width="match_parent"
@@ -268,7 +269,7 @@
                 </LinearLayout>
 
                 <RelativeLayout
-                    android:layout_width="200px"
+                    android:layout_width="220px"
                     android:layout_height="match_parent"
                     android:background="@drawable/shape_rect_e0e0e0">
 

BIN
app/src/main/res/mipmap-xhdpi/img_aky_zsw_bt.png


+ 2 - 0
gradle/libs.versions.toml

@@ -41,6 +41,7 @@ swiperefreshlayout = "1.1.0"
 utilcodex = "1.31.1"
 workRuntime = "2.10.0"
 xxpermissions = "20.0"
+media3Exoplayer = "1.8.0"
 
 [libraries]
 adapter-rxjava3 = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "retrofit" }
@@ -89,6 +90,7 @@ rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroi
 rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
 utilcodex = { module = "com.blankj:utilcodex", version.ref = "utilcodex" }
 xxpermissions = { module = "com.github.getActivity:XXPermissions", version.ref = "xxpermissions" }
+androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3Exoplayer" }
 
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }

+ 3 - 0
settings.gradle

@@ -13,6 +13,9 @@ pluginManagement {
         maven {
             url "https://maven.aliyun.com/repository/public"
         }
+        maven { url 'https://exoplayer.dev/repo' }
+        // 2. ZLMediaKit 示例里用到的 rtsp-extension 快照
+        maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
     }
 }
 dependencyResolutionManagement {