UniApp + LiveKit:Android 端为什么要用原生做屏幕共享,以及我们怎么落地的
2026/8/28 17:56:05 网站建设 项目流程

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

项目里会议能力基于 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

进房时壳页拿到 LiveKiturl/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 互通的做法。

效果:

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询