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.kt:mediaProjection 前台服务
  • 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 时,需要符合策略的前台服务,否则系统会中断采集
  • 通知文案「正在共享屏幕,点按返回会议」,并 PendingIntent 回 MeetingActivity
  • 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.js → index.uts → MeetingLauncher(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 互通的做法。

相关推荐
火柴就是我8 小时前
Flutter 项目本地 Maven 引入 AAR 找不到?一次 Gradle 路径问题排查
android
火柴就是我8 小时前
Android 打包报错 25.0.3
android·前端
码上有光9 小时前
Linux进程通信——共享内存、消息队列和信号量
android·linux·运维·共享内存·通信
Android打工仔13 小时前
Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?
android·kotlin
打工仔折腾 AI13 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
Dovis(誓平步青云)14 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
恋猫de小郭17 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter
执明wa18 小时前
Android RecyclerView 实战:点击频道栏切换对应内容
android·开发语言·windows·microsoft·android studio
新鲜势力呀18 小时前
PHP 定时数据同步实战:从第三方接口超时到异步同步 + 增量更新架构优化全过程
android
星间都市山脉20 小时前
RecoveryUI 圆角屏安全边距配置及代码调用链
android·java·linux·windows·ubuntu