UniApp + LiveKit:Android 端为什么要用原生做屏幕共享,以及我们怎么落地的

一、背景:同一套会议,三端能力并不对等

项目里会议能力基于 LiveKit。H5 / PC 端用 navigator.mediaDevices.getDisplayMedia() 就能发起屏幕共享;但在 App 内嵌 WebView(尤其 Android) 上,这条路基本走不通。

H5 会议室里对「能不能共享」有明确判断:

原因可以概括成三点:

  1. API 能力:手机浏览器 / WebView 普遍不支持 getDisplayMedia,即便有也多是桌面 Chrome 那一套。
  2. 系统权限模型:Android 录屏走的是 MediaProjection,必须弹系统授权页,拿 Intent 结果才能采屏;WebView 拿不到这条系统链路。
  3. 进程与生命周期:投屏在 Android 10+ 要求 mediaProjection 类型前台服务 常驻通知;纯 H5 很难稳定满足,切后台就容易被杀或采集中断。

因此产品策略是:

会议室实现 屏幕共享
H5 / PC LiveKit JS getDisplayMedia
iOS App web-view 加载 hybrid 页 基本不支持发起
Android App UTS 原生 MeetingActivity MediaProjection + LiveKit Android SDK

二、整体架构:JS 壳 + UTS 桥 + Kotlin 原生会议室

不是「整 App 重写成原生」,而是 只把会议室(含投屏)下沉到 Android 原生,业务壳仍在 UniApp。

关键模块:

  • uni_modules/yn-livekit-meeting:UTS 插件(依赖 livekit-android:2.28.0
  • MeetingActivity.kt:会议室 UI、进房、订阅、投屏
  • ScreenShareService.ktmediaProjection 前台服务
  • common/livekit/meeting-native.js:Vue 侧桥接
  • subpages/meeting/room.vue:Android 透明壳,负责凭证与业务 API

进房时壳页拿到 LiveKit url/token 后调用:


三、为什么必须走 Android 原生:MediaProjection 机制

Android 从 API 21 起用 MediaProjection 做「用户授权后的屏幕采集」:

  1. 通过 MediaProjectionManager.createScreenCaptureIntent() 拉起系统授权界面
  2. 用户同意后,ActivityResult 带回 RESULT_OK + data Intent
  3. 用这份 Intent 创建 MediaProjection,再从 VirtualDisplay 采帧
  4. LiveKit Android SDK 封装了后续编码 / 发布,业务侧只要把授权结果塞进 ScreenCaptureParams

对应代码在 toggleShare()

这就是「必须原生」的核心:系统授权页只能由 Activity 发起,授权结果也只能由原生拿回。WebView 里的 JS 过不了这道关。


四、实现拆解:从点击「共享屏幕」到对端看到画面

1. Manifest:权限 + 前台服务类型

Android 14 对前台服务类型校验更严,mediaProjection 声明缺失会直接启动失败。

2. 授权回调:先起前台服务,再打开 LiveKit 屏幕轨

顺序很重要:

  1. 用户授权成功
  2. 立刻 ScreenShareService.start()(前台通知)
  3. setScreenShareEnabled(true, ScreenCaptureParams(...))
  4. 失败则停服务、回滚 UI

授权弹窗期间可能有人已经开了共享,所以回来后还要再判一次互斥。

3. 把系统授权结果交给 LiveKit

这里业务代码不自己管 VirtualDisplay / 编码,只做两件事:

  • 把系统 Intent 交给 SDK
  • onStop 里停掉自己的前台服务

LiveKit 会发布 Track.Source.SCREEN_SHARE,对端(H5/PC/原生)按屏幕轨订阅即可。

4. 前台服务:为什么「多写一个 Service」

要点:

  • Android 10+ 使用 MediaProjection 时,需要符合策略的前台服务,否则系统会中断采集
  • 通知文案「正在共享屏幕,点按返回会议」,并 PendingIntentMeetingActivity
  • startForegroundService / STOP_FOREGROUND_REMOVE 成对出现,停止共享、离开会议、异常失败都要 ScreenShareService.stop()

五、业务细节:不只是「能采屏」

1. 全员互斥:同时只允许一路屏幕共享

发起前、授权回来后都检查 shareFromId;远端已有人共享时,本端误开会主动停掉:

H5 侧同样有「已有人在共享屏幕」逻辑,两端产品规则一致。

2. 识别屏幕轨:兼容 source / name

这样和 JS 侧判断对齐,避免不同端/SDK 版本字段差异导致「共享了但大屏不切」。

3. 渲染坑:GONE 时不要 init TextureView

共享大屏用 TextureViewRenderer。注释里写得很实在:

shareRenderer 只 init 一次;GONE 时 init 会导致 Surface 黑屏

要先把容器设为 VISIBLE,再 post { bindShareRenderer }

这是 WebRTC/Android 渲染里的常见坑:Surface 还没就绪就绑轨,画面会一直黑。

4. 停共享后恢复摄像头

stopShareView 会停服务、清状态;若本端之前开着摄像头,再 restoreLocalCamera(),避免「停共享后摄像头再也开不回来」。

5. 状态同步回 UniApp 壳

原生通过 MeetingBridge.emit("media", { screen: true/false }) 入队,room.vue 短轮询 pollMeetingNativeEvent(),再调业务侧 apiMeetingMedia,让成员列表 / IM 状态和真实媒体轨一致。


六、完整时序


七、工程侧注意点

  1. 必须自定义调试基座 / 云打包

    标准基座不含 LiveKit 原生依赖;config.json 里声明了 io.livekit:livekit-android:2.28.0 等 Maven 依赖,要由 HBuilderX 编进基座。

  2. UTS 分层

    Vue → meeting-native.jsindex.utsMeetingLauncher(Java)→ MeetingActivity(Kotlin)。Java 入口是为了避免 UTS 直接啃 Kotlin Companion / 类型转换翻车。

  3. 双端体验策略

    Android 用原生补齐投屏;iOS 仍走 web-view,共享提示不支持------先保证主端能力,而不是强行一套 Web 通吃。


八、总结

Android App 里做会议屏幕共享,本质不是「LiveKit API 换一个写法」,而是:

系统能力(MediaProjection + 前台服务)只能原生拿到;直播推流(LiveKit)在拿到授权后再发布屏幕轨;UniApp 继续做业务壳。

若坚持 WebView + getDisplayMedia,在手机端几乎必然失败;把会议室下沉到 UTS/Kotlin,用官方 ScreenCaptureParams 接 MediaProjection,再用 mediaProjection 前台服务保活,才是符合 Android 平台约束、且能和对端 H5/PC 互通的做法。

相关推荐
蓝速科技1 小时前
蓝速科技信创落地:平衡安全合规与项目成本的实战方案
android·运维·人工智能·科技·安全·电脑·鸿蒙
又见情义3 小时前
RK3568 Android 13 SDK 开机自动开启共享热点实践
android
mmsx4 小时前
return false 不是拦截:一次按键触发两个动作的事件冒泡陷阱
android·bug
DU_YULIN5 小时前
Camera HAL ThreadManager概述
android·camera hal
淡淡的香烟5 小时前
Android中的MQTT通信原理解析
android·物联网
淡淡的香烟5 小时前
Android Camera开发详解
android·音视频
mmsx7 小时前
基于 Android 的校园信息管理系统源码
android·java·okhttp
YF02117 小时前
如何进入Android工程模式
android
宠友信息8 小时前
IM系统开发技术路线分析,即时通讯源码助力快速搭建聊天应用
java·spring boot·redis·websocket·mysql·uni-app·vue