更新记录

1.0.0(2026-08-14)

  • 初始版本
  • 支持加入/离开 LiveKit 房间
  • 支持麦克风和摄像头开关
  • 支持前后摄像头切换
  • 支持屏幕共享
  • 支持通过 LiveKitVideo 组件渲染远端视频流

平台兼容性

uni-app x(5.0)

Chrome Safari Android Android插件版本 iOS 鸿蒙 微信小程序
× × 8.0 1.0.0 × × ×

livekit-plugin

基于 LiveKit 的 uni-app x 实时音视频插件,用于会议场景。目前只支持 Android。

依赖

  • uni-app x(HBuilderX 内置支持)
  • Android minSdkVersion 24
  • livekit-android 2.27.0(版本见 utssdk/app-android/config.json)

快速开始

导入插件后:

import {
  onEvent,
  connect,
  disconnect,
  enableMic,
  enableCamera,
  startScreenShare,
  stopScreenShare,
  destroy,
} from "@/uni_modules/livekit-plugin"

1. 监听事件

onEvent((event : any) => {
  switch (event.type) {
    case "connected":
      break
    case "participant_join":
      break
    case "error":
      break
  }
})

2. 连接房间

connect("wss://your-server.livekit.cloud", "jwt_token")

token 需要由自己的服务端签发,插件不负责生成。首次调用 connect 会自动初始化,一般不需要手动调 init()

3. 控制设备

enableMic(true)       // 打开麦克风
enableCamera(true)    // 打开摄像头
startScreenShare()    // 开始屏幕共享
stopScreenShare()     // 停止屏幕共享

4. 断开

disconnect()          // 断开当前房间
destroy()             // 页面卸载时调用,释放全部资源

API

init()

初始化插件,注册 Activity 回调用于接收屏幕共享授权结果。connect 内部会自动调用,一般不需要手动调用。

onEvent(callback)

注册事件回调。单回调,每次调用会覆盖上一次注册的回调。回调参数是一个对象,type 字段区分事件类型,其余字段视类型而定。

connect(url, token)

加入房间。url 是 LiveKit 服务地址(ws/wss),token 是服务端签发的 JWT。已在房间内重复调用会触发 already_connected 事件。

disconnect()

主动断开连接,触发 disconnect 事件。

enableMic(enable)

开关麦克风。未连接房间时调用无效果。

enableCamera(enable)

开关摄像头。未连接房间时调用无效果。

startScreenShare()

开始屏幕共享,会弹出系统授权对话框。用户拒绝时触发 screen_share_denied 事件。

stopScreenShare()

停止屏幕共享。

destroy()

断开连接并释放全部资源(渲染器、音频等)。页面卸载时应调用。

事件

type 字段 说明
connected 加入房间成功
already_connected 重复 connect
participant_join identity 参与者加入(有轨道订阅时也会触发)
participant_leave identity 参与者离开
camera_subscribed identity, source 摄像头轨道就绪,source 为 "CAMERA"
screen_share_subscribed identity, source 屏幕共享轨道就绪,source 为 "SCREEN_SHARE"
audio_subscribed identity, source 麦克风音频轨道就绪,source 为 "MICROPHONE"
track_unsubscribed identity, source 轨道取消订阅
track_muted identity, source, muted 轨道静音/取消静音
speakers ids 当前正在说话的用户列表
disconnected 房间断开(对端断开或掉线)
disconnect 本地调用 disconnect()
mic enable 本地麦克风状态变更
camera enable 本地摄像头状态变更
screen_share_denied 用户拒绝屏幕共享授权
error message 错误信息

source 字段取值是轨道枚举名:CAMERA / SCREEN_SHARE / MICROPHONE

远端视频渲染

native-view 承载 LiveKitVideo

<native-view @init="onNativeViewInit" style="width:100%;height:100%" />
import LiveKitVideo from "uts.sdk.modules.livekitPlugin.LiveKitVideo"

let mainVideo : LiveKitVideo | null = null

function onNativeViewInit(e : UniNativeViewInitEvent) {
  mainVideo = new LiveKitVideo(e.detail.element)
}

收到 camera_subscribed / screen_share_subscribed 事件后绑定轨道:

mainVideo?.attachRemoteTrack(identity, "CAMERA")        // 渲染摄像头
mainVideo?.attachRemoteTrack(identity, "SCREEN_SHARE")  // 渲染屏幕共享

其它方法:

  • setMuted(muted):隐藏/显示画面,远端静音时用
  • clear():解绑当前轨道
  • removeTrack(identity, source):解绑并自动切到该用户另一路视频(例如共享结束切回摄像头)
  • destroy():释放渲染器

调用示例

完整页面示例,参考 pages/livekit/livekit.uvue

<template>
    <view class="page">
        <native-view @init="onNativeViewInit" style="width:100%;height:100%" />
        <view class="toolbar">
            <text>{{connected ? "已连接" : "连接中..."}}</text>
            <text @click="leaveMeeting">离开</text>
        </view>
        <view class="action-bar">
            <text @click="toggleMic">{{micOn ? "关麦" : "开麦"}}</text>
            <text @click="toggleCamera">{{cameraOn ? "关摄像头" : "开摄像头"}}</text>
            <text @click="toggleScreenShare">{{isSharing ? "停止共享" : "屏幕共享"}}</text>
        </view>
    </view>
</template>

<script setup lang="uts">
    import {
        onEvent, connect, disconnect, destroy,
        enableMic, enableCamera, startScreenShare, stopScreenShare,
    } from "@/uni_modules/livekit-plugin"
    import LiveKitVideo from "uts.sdk.modules.livekitPlugin.LiveKitVideo"

    const connected = ref<boolean>(false)
    const micOn = ref<boolean>(true)
    const cameraOn = ref<boolean>(true)
    const isSharing = ref<boolean>(false)

    let mainVideo : LiveKitVideo | null = null

    function onNativeViewInit(e : UniNativeViewInitEvent) {
        const mv = mainVideo
        if (mv != null) {
            mv.destroy()
        }
        mainVideo = new LiveKitVideo(e.detail.element)
    }

    onEvent((event : any) => {
        const e = event as UTSJSONObject
        const type = e["type"] as string

        switch (type) {
            case "connected":
                connected.value = true
                break
            case "camera_subscribed":
            case "screen_share_subscribed":
                // 有远端画面轨道就绪,绑到渲染器上
                mainVideo?.attachRemoteTrack(e["identity"] as string, e["source"] as string)
                break
            case "track_muted":
                mainVideo?.setMuted(e["muted"] as boolean)
                break
            case "screen_share_denied":
                isSharing.value = false
                break
            case "error":
                uni.showToast({ title: e["message"] as string, icon: "none" })
                break
            case "disconnected":
                connected.value = false
                break
        }
    })

    function toggleMic() {
        micOn.value = !micOn.value
        enableMic(micOn.value)
    }

    function toggleCamera() {
        cameraOn.value = !cameraOn.value
        enableCamera(cameraOn.value)
    }

    function toggleScreenShare() {
        if (isSharing.value) {
            stopScreenShare()
            isSharing.value = false
        } else {
            startScreenShare()
            isSharing.value = true
        }
    }

    function leaveMeeting() {
        disconnect()
        destroy()
        uni.navigateBack()
    }

    onShow(() => {
        // 相机/麦克风运行时权限,需要业务方自行申请
        try {
            const pages = getCurrentPages()
            const currentPage = pages[pages.length - 1] as UniPage
            const activity = currentPage.getAndroidActivity()
            if (activity != null) {
                UTSAndroid.requestSystemPermission(
                    activity,
                    ["android.permission.CAMERA", "android.permission.RECORD_AUDIO"],
                    () => {},
                    () => {
                        uni.showToast({ title: "请在设置中开启相机/麦克风权限", icon: "none" })
                    },
                    true
                )
            }
        } catch (err) {
            console.log("request permission error", err)
        }
    })

    onMounted(() => {
        // url / token 一般由自己的服务端下发,这里只是示例
        connect("wss://your-server.livekit.cloud", "jwt_token")
    })

    onUnmounted(() => {
        mainVideo?.destroy()
        mainVideo = null
        destroy()
    })
</script>

注意点:

  • onShow 里申请权限是因为此时页面已入栈、能拿到 Activity,直接传给 requestSystemPermission 才能弹窗。
  • 一个页面同时只有一路画面(native-view 只有一个),要看谁的画面由业务自己决定,示例里简化成谁先订阅就显示谁。
  • 离开页面时记得 mainVideo.destroy() 和插件 destroy(),避免渲染器泄漏。

权限

插件已在 manifest 中声明以下权限:

  • INTERNET
  • CAMERA
  • RECORD_AUDIO
  • FOREGROUND_SERVICE
  • FOREGROUND_SERVICE_MEDIA_PROJECTION
  • MODIFY_AUDIO_SETTINGS

相机/麦克风是运行时权限,需要业务方在页面里自行申请(示例用 UTSAndroid.requestSystemPermission)。屏幕共享授权由系统弹窗处理,不需要额外申请。

已知限制

  • 仅 Android,不支持 iOS。
  • onEvent 是单回调,多个页面同时监听时后注册的会覆盖之前的。
  • 插件不提供本地画面预览,native-view 只渲染远端画面。
  • 屏幕共享开启时,本地麦克风关闭会把系统声音通过麦克风轨道推出去,远端听到的是该用户的 mic 轨道声音。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。