| | |
| | | package com.safeluck.floatwindow.manager; |
| | | |
| | | import android.content.Context; |
| | | import android.os.Handler; |
| | | import android.os.Looper; |
| | | import android.view.SurfaceHolder; |
| | | import android.view.SurfaceView; |
| | | import android.view.WindowManager; |
| | | |
| | | import com.alivc.live.pusher.AlivcAudioAACProfileEnum; |
| | | import timber.log.Timber; |
| | |
| | | // 阿里推流相关 |
| | | private AlivcLivePusher alivcPusher; |
| | | private AlivcLivePushConfig alivcLivePushConfig; |
| | | private SurfaceView previewSurfaceView; |
| | | |
| | | // USB摄像头相关 |
| | | private UsbCamera usbCamera; |
| | | private PushThread pushThread; |
| | | private boolean isRunning = false; |
| | | private boolean cameraExists = false; |
| | | private volatile boolean pushStarted = false; |
| | | |
| | | // 推流URL |
| | | private String pushUrl; |
| | |
| | | |
| | | // 是否开启摄像头加密 |
| | | private boolean ay_encrypt = false; |
| | | |
| | | // 预览 SurfaceView 和隐藏的 Window |
| | | private WindowManager windowManager; |
| | | |
| | | /** |
| | | * 推流回调接口 |
| | |
| | | try { |
| | | // 初始化推流SDK |
| | | initAlivcPusher(); |
| | | pushStarted = false; |
| | | |
| | | // 检查并打开USB摄像头 |
| | | if (!openUsbCamera()) { |
| | |
| | | cameraExists = true; |
| | | Timber.d("USB摄像头打开成功"); |
| | | |
| | | // 启动摄像头数据推送线程 |
| | | startPushThread(); |
| | | |
| | | notifyCallback(1, 0, "推流已启动"); |
| | | notifyCallback(1, 0, "推流线程已启动,等待推流状态就绪"); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to start push"); |
| | | notifyCallback(1, -3, "启动推流失败: " + e.getMessage()); |
| | |
| | | if (usbCamera != null) { |
| | | usbCamera.stopCamera(); |
| | | } |
| | | pushStarted = false; |
| | | notifyCallback(1, 4, "推流已停止"); |
| | | } |
| | | |
| | |
| | | alivcPusher = new AlivcLivePusher(); |
| | | alivcPusher.init(context.getApplicationContext(), alivcLivePushConfig); |
| | | |
| | | // 外部自定义流模式下,同样需要先开启预览,让状态从 INIT 进入 PREVIEWED |
| | | // 创建一个隐藏的 Window 来承载 SurfaceView,确保 Surface 能够被创建 |
| | | windowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); |
| | | previewSurfaceView = new SurfaceView(context.getApplicationContext()); |
| | | |
| | | // 在 SurfaceView 的 surfaceCreated 回调中再启动预览,确保 Surface 已经创建 |
| | | previewSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { |
| | | @Override |
| | | public void surfaceCreated(SurfaceHolder holder) { |
| | | try { |
| | | Timber.d("previewSurfaceView surfaceCreated, startPreviewAysnc"); |
| | | if (alivcPusher != null) { |
| | | alivcPusher.startPreviewAysnc(previewSurfaceView); |
| | | |
| | | |
| | | |
| | | // 启动摄像头数据推送线程 |
| | | startPushThread(); |
| | | } |
| | | } catch (Exception e) { |
| | | Timber.e(e, "startPreviewAysnc in surfaceCreated failed"); |
| | | notifyCallback(1, -3, "预览启动失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
| | | Timber.d("previewSurfaceView surfaceChanged: %dx%d", width, height); |
| | | } |
| | | |
| | | @Override |
| | | public void surfaceDestroyed(SurfaceHolder holder) { |
| | | Timber.d("previewSurfaceView surfaceDestroyed"); |
| | | } |
| | | }); |
| | | |
| | | // 将 SurfaceView 添加到隐藏的 Window 中,这样 Surface 才会被创建 |
| | | WindowManager.LayoutParams params = new WindowManager.LayoutParams( |
| | | 1, 1, // 1x1 像素,几乎不可见 |
| | | WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, |
| | | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| | | | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
| | | | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
| | | | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, |
| | | android.graphics.PixelFormat.TRANSLUCENT |
| | | ); |
| | | params.x = -1000; // 移到屏幕外 |
| | | params.y = -1000; |
| | | params.alpha = 0.0f; // 完全透明 |
| | | |
| | | try { |
| | | windowManager.addView(previewSurfaceView, params); |
| | | Timber.d("previewSurfaceView added to window"); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Failed to add previewSurfaceView to window"); |
| | | // 如果添加失败,尝试使用 TYPE_APPLICATION 类型 |
| | | params.type = WindowManager.LayoutParams.TYPE_APPLICATION; |
| | | try { |
| | | windowManager.addView(previewSurfaceView, params); |
| | | Timber.d("previewSurfaceView added to window with TYPE_APPLICATION"); |
| | | } catch (Exception e2) { |
| | | Timber.e(e2, "Failed to add previewSurfaceView with TYPE_APPLICATION"); |
| | | } |
| | | } |
| | | |
| | | // 设置监听器 |
| | | setupListeners(); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private Handler mainHandler = new Handler(Looper.getMainLooper()); |
| | | /** |
| | | * 设置监听器 |
| | | */ |
| | |
| | | @Override |
| | | public void onPreviewStarted(AlivcLivePusher alivcLivePusher) { |
| | | Timber.d("onPreviewStarted"); |
| | | android.os.Handler handler = new android.os.Handler(android.os.Looper.getMainLooper()); |
| | | handler.postDelayed(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | if (alivcPusher != null && |
| | | alivcPusher.getCurrentStatus() != AlivcLivePushStats.PREVIEWED && |
| | | alivcPusher.getCurrentStatus() != AlivcLivePushStats.PUSHED) { |
| | | Timber.w("Preview状态异常"); |
| | | } else { |
| | | if (cameraExists && pushUrl != null && !pushUrl.isEmpty()) { |
| | | mainHandler.postDelayed(()->{ |
| | | // 预览就绪后再启动推流,避免 INIT 状态直接 startPush 报错 |
| | | if (alivcPusher != null && pushUrl != null && !pushUrl.isEmpty()) { |
| | | try { |
| | | AlivcLivePushStats s = alivcPusher.getCurrentStatus(); |
| | | Timber.i("onPreviewStarted, current status=%s", s != null ? s.name() : "null"); |
| | | Timber.d("开始推流: %s", pushUrl); |
| | | alivcPusher.startPushAysnc(pushUrl); |
| | | } |
| | | } catch (Exception e) { |
| | | Timber.e(e, "startPushAysnc failed"); |
| | | notifyCallback(1, -3, "启动推流失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | }, 1000); |
| | | |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Override |
| | | public void onPushStarted(AlivcLivePusher alivcLivePusher) { |
| | | Timber.d("onPushStarted"); |
| | | pushStarted = true; |
| | | notifyCallback(1, 0, "推流已开始,分辨率: " + resolutionArr[0] + "x" + resolutionArr[1]); |
| | | } |
| | | |
| | |
| | | @Override |
| | | public void onPushStoped(AlivcLivePusher alivcLivePusher) { |
| | | Timber.d("onPushStoped"); |
| | | pushStarted = false; |
| | | notifyCallback(1, 4, "推流已停止"); |
| | | } |
| | | |
| | |
| | | // 打开摄像头之前先调用setenv |
| | | usbCamera.setenv(); |
| | | |
| | | // 使用prepareCamera方法,camera_id范围[0,9] |
| | | int[] cameraIds = {0, 9}; |
| | | // 使用prepareCamera方法,camera_id范围[0,2] |
| | | int[] cameraIds = {0, 2}; |
| | | String cameraName = null; // 不指定特定名称 |
| | | |
| | | // 如果返回非0,代表打开失败,则先stopCamera再重试,最多3次 |
| | |
| | | * 释放阿里推流资源 |
| | | */ |
| | | private void releaseAlivcPusher() { |
| | | // 移除隐藏的 SurfaceView |
| | | if (previewSurfaceView != null && windowManager != null) { |
| | | try { |
| | | windowManager.removeView(previewSurfaceView); |
| | | Timber.d("previewSurfaceView removed from window"); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error removing previewSurfaceView from window"); |
| | | } |
| | | previewSurfaceView = null; |
| | | } |
| | | |
| | | if (alivcPusher != null) { |
| | | try { |
| | | AlivcLivePushStats stats = alivcPusher.getCurrentStatus(); |
| | |
| | | alivcPusher = null; |
| | | } |
| | | alivcLivePushConfig = null; |
| | | pushStarted = false; |
| | | } |
| | | |
| | | /** |
| | |
| | | try { |
| | | int width = resolutionArr[0]; |
| | | int height = resolutionArr[1]; |
| | | final long startTimeNs = System.nanoTime(); |
| | | |
| | | // 计算YUV420缓冲区大小 |
| | | int bufferSize = width * height * 3 / 2; |
| | |
| | | usbCamera.rgba(1, buffer); |
| | | |
| | | // 推流数据到阿里云 |
| | | if (alivcPusher != null && cameraExists) { |
| | | if (alivcPusher != null && cameraExists && pushStarted) { |
| | | try { |
| | | long ptsUs = (System.nanoTime() - startTimeNs) / 1000; |
| | | alivcPusher.inputStreamVideoData( |
| | | buffer, |
| | | width, |
| | | height, |
| | | buffer.length, |
| | | System.nanoTime() / 1000, // 转换为微秒 |
| | | ptsUs, // 单调递增的时间戳(微秒) |
| | | 0 // rotation |
| | | ); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error pushing frame"); |
| | | } |
| | | } else if (!pushStarted) { |
| | | // 等待 onPushStarted 后再喂帧,避免 SDK invalid state |
| | | Thread.sleep(20); |
| | | } |
| | | |
| | | // 控制帧率,约20fps |