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 互通的做法。

相关推荐
2501_9151063215 小时前
苹果App Store上架费用及流程全面解析
android·ios·小程序·https·uni-app·iphone·webview
YF021117 小时前
Android 16系统App开机自启动方案
android
hm宋18 小时前
安卓手机卡顿优化 —— 背景与使用指南
android
OxYGC18 小时前
[Android] [WatchOS]智能手表第一篇: 实用工具与应用推荐
android·智能手表
白远山19 小时前
家政服务派单平台搭建实战指南:从需求分析到系统设计全流程解析
java·开发语言·架构·uni-app·需求分析
嘟哩DuliDuli20 小时前
创作 Agent 的任务状态该如何保存
android·java·javascript
Godikov20 小时前
两个 SDK 抢一个摄像头:Android 设备上的相机仲裁设计与踩坑实录
android
Godikov20 小时前
摄像头电机自动校准实战:为什么我用「步数计数」替换了角度传感器绝对定位
android
Godikov21 小时前
2GB 内存的 Android 工业终端上,我们是怎么把 OOM 按在地上摩擦的
android