更新记录

1.2.1(2026-07-01)

更新tt-nertc-video-view.uvue

1.2.0(2026-07-01)

这个是自己使用的,做了Android和IOS的直播推流的功能,其他的未测试,要用的话,可以自己修改插件,会提供源码版本


平台兼容性

uni-app(4.36)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × × × 5.0 12 ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × × × × × × × × × ×

uni-app x(4.36)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - 5.0 12 × ×

tt-nertc-live

网易云信 NERTC CDN 直播推流 UTS 插件。

功能

  • 单人直播 CDN 推流(RTMP)
  • PK 直播(跨房间媒体转发 + 旁路合流)
  • 直播连麦(旁路合流)
  • 视频画布(本地预览 + 远端渲染)
  • 摄像头切换、镜像、缩放模式
  • 蓝牙权限处理(Android 12+)
  • 云信基础美颜(免费:美白/磨皮/红润/锐化)+ 滤镜
  • 视频预览精确定位(addContentView + 坐标计算,不遮挡 UI 按钮)

平台支持

平台 SDK 版本 集成方式
Android NERTC SDK v5.9.10 Maven(com.netease.yunxin:nertc:5.9.10
iOS NERTC SDK v5.10.0 xcframework(手动放入 Frameworks/
Harmony 预留

版本

v1.2.0(2026-07-01)— iOS 推流 + 美颜功能完整完成。

架构总览

uni_modules/tt-nertc-live/
├── utssdk/
│   ├── interface.uts                              ← 对外类型定义(含美颜/滤镜/预览坐标类型)
│   ├── unierror.uts                               ← 统一错误码
│   ├── app-android/
│   │   ├── index.uts                              ← Android 入口(1180 行)
│   │   ├── config.json                            ← Maven 依赖
│   │   └── src/main/kotlin/uts/sdk/modules/ttNertcLive/
│   │       ├── NERTCEngineHolder.kt               ← 引擎实例持有
│   │       ├── NERTCCallbackProxy.kt              ← NERtcCallbackEx → UTS 回调
│   │       └── NERTCHelper.kt                     ← 核心辅助封装(435 行,含美颜枚举映射 + 精确定位方法)
│   ├── app-ios/
│   │   ├── index.uts                              ← iOS 入口
│   │   ├── NERTCBridge.swift                      ← Swift 桥接层
│   │   └── Frameworks/                            ← NERTC xcframework
│   └── app-harmony/
│       └── index.uts                              ← Harmony 入口(预留)
└── components/
    └── tt-nertc-video-view/
        └── tt-nertc-video-view.uvue               ← 视频画布组件(255 行)

快速开始

import * as nertc from "@/uni_modules/tt-nertc-live"
import type { NERTCLiveSDK } from '@/uni_modules/tt-nertc-live'

// iOS: 模块导出直接作为 SDK,不调用 getNERTCLiveSDK()
// Android: 同样兼容(模块导出的 export function 即 SDK 方法)
const sdk = nertc as NERTCLiveSDK

// 1. 初始化
sdk.initEngine({
  appKey: "your-app-key",
  videoConfig: { width: 720, height: 1280, frameRate: "fps_15" },
  audioConfig: { profile: "high_quality", scenario: "chatroom" }
})

// 2. 开启美颜
sdk.startBeauty()
sdk.setBeautyEffect({ beautyType: "whiten", level: 0.9 })
sdk.setBeautyEffect({ beautyType: "smooth", level: 0.8 })
sdk.setBeautyEffect({ beautyType: "faceRuddy", level: 0.5 })
sdk.setBeautyEffect({ beautyType: "faceSharpen", level: 0.3 })

// 3. 本地预览
const query = uni.createSelectorQuery()
query.select('.video-box').boundingClientRect((rect: any) => {
  if (rect != null) {
    sdk.startPreview({
      x: (rect.left ?? 0) as number,
      y: (rect.top ?? 0) as number,
      width: (rect.width ?? 0) as number,
      height: (rect.height ?? 0) as number
    })
  }
}).exec()

// 4. 开始推流
sdk.startPushStreaming({
  config: {
    streamingUrl: "rtmp://your-cdn-push-url",
    roomInfo: { uid: userId, channelName: channelName, token: token }
  },
  success(res) { console.log("推流成功 channelId=" + res.channelId) },
  fail(err) { console.log("推流失败 code=" + err.errCode + ", msg=" + err.errMsg) }
})

// 5. 释放
sdk.releaseEngine()

视频预览画布方案(Android 关键技术点)

问题背景

NERtcVideoView 依赖 TextureView 的 SurfaceTexture 做 EGL 渲染。在 uni-app X 的 native-view 中通过 bindAndroidView 挂载时,不走标准 Window attach 流程 → TextureView 无有效 SurfaceTexture → EGL 初始化失败 → 黑屏。

三种方案演进

方案 方法 效果 问题
方案一 element.bindAndroidView(videoView) 黑屏 native-view 不走标准 Window attach → SurfaceTexture 未创建
方案二 addContentView(videoView, MATCH_PARENT) 有画面 全屏覆盖 → 遮挡所有 UI 操作按钮
方案三(最终) 页面层 createSelectorQuery 获取 video-box 坐标 → 传入 addContentView + FrameLayout.LayoutParams(pxX, pxY, pxW, pxH) 精确定位 画面正确、不遮按钮 需 dp→px 转换(见下方)

dp→px 转换修复

createSelectorQuery().boundingClientRect() 返回的是 dp(逻辑像素),但 FrameLayout.LayoutParams(leftMargin, topMargin, width, height) 需要 px(物理像素)。

未乘 density 前:360dp × 384dp360px × 384px → 视图只有正确尺寸的 1/density²(在 3x 屏幕上仅 1/9 大小)。

修复NERTCHelper.addVideoViewWithPosition() 中,所有 x/y/width/height 乘以 displayMetrics.density 转为 px 后再传给 LayoutParams。

// NERTCHelper.kt
fun addVideoViewWithPosition(activity: Activity, view: View,
                             x: Number, y: Number,
                             width: Number, height: Number) {
    val density = activity.resources.displayMetrics.density
    val pxWidth  = (width.toFloat() * density).toInt()
    val pxHeight = (height.toFloat() * density).toInt()
    val pxX      = (x.toFloat() * density).toInt()
    val pxY      = (y.toFloat() * density).toInt()
    val lp = FrameLayout.LayoutParams(pxWidth, pxHeight)
    lp.leftMargin = pxX
    lp.topMargin = pxY
    activity.addContentView(view, lp)
}

startPreview 完整调用流程

Step 0: setLocalVideoConfig        ← 建立视频采集/编码配置
Step 1: enableLocalVideo(true)     ← 启用视频模块
Step 1.5: _applySavedBeautyEffects ← 重新应用美颜(采集管线就绪后生效)
Step 2: addContentView + 精确定位  ← 创建 NERtcVideoView 并精确挂载到 video-box 区域
  [Handler.postDelayed 200ms 等待布局完成]
Step 3: setupLocalVideoCanvas      ← 注册画布到 SDK
Step 4: startVideoPreview          ← 启动预览

云信基础美颜(v1.1.0)

支持的美颜类型(免费 4 种)

美颜类型 枚举值 强度范围 推荐默认值 Android SDK 枚举 实际枚举值
美白 whiten 0~1 0.90 kNERtcBeautyWhiten 2
磨皮 smooth 0~1 0.80 kNERtcBeautySmooth 3
红润 faceRuddy 0~1 0.50 kNERtcBeautyFaceRuddy 12
锐化 faceSharpen 0~1 0.30 kNERtcBeautyFaceSharpen 22

默认值为实测能明显看到效果的值(0.9/0.8/0.5/0.3)。低值(如 0.1)效果微弱几乎不可见。

美颜调用时序(关键)

基础美颜必须在 enableLocalVideo(true) 之前启用,且参数需在 enableLocalVideo(true) 之后重新应用:

init() → startBeauty() → setBeautyEffect(...) → startPreview() → enableLocalVideo(true) → _applySavedBeautyEffects() → startVideoPreview()

UTS 层实现

  • setBeautyEffect() 将参数缓存到 _lastBeautyEffects: Map<string, number>
  • startPreview()enableLocalVideo(true) 之后调用 _applySavedBeautyEffects() 重新应用所有缓存参数
  • release() / stopBeauty() 时清空 _beautyStarted=false + _lastBeautyEffects.clear()

美颜资源 Maven 自动集成

SDK v5.9.10 全量包 com.netease.yunxin:nertc:5.9.10 已自动包含:

  • libNERtcBeauty.so(美颜算法)
  • libNERtcFaceDetect.so(人脸检测)
  • libNERtcnn.so(神经网络)
  • beauty/ 文件夹 → 自动打包到 assets/
  • model.dat → 自动打包到 assets/

无需手动配置任何美颜资源文件。

API 说明

// 美颜
sdk.startBeauty()                              // 开启美颜模块(必须在 enableLocalVideo 之前)
sdk.setBeautyEffect({ beautyType: "whiten", level: 0.9 })   // 美白
sdk.setBeautyEffect({ beautyType: "smooth", level: 0.8 })   // 磨皮
sdk.setBeautyEffect({ beautyType: "faceRuddy", level: 0.5 }) // 红润
sdk.setBeautyEffect({ beautyType: "faceSharpen", level: 0.3 }) // 锐化
sdk.enableBeauty(false)                        // 暂停美颜(true 恢复)
sdk.stopBeauty()                               // 结束美颜并释放资源

// 滤镜(需商务获取资源)
sdk.addBeautyFilter({ path: "/sdcard/.../filter_style_01", level: 0.5 })
sdk.setBeautyFilterLevel(0.8)
sdk.removeBeautyFilter()

Android 核心实现细节

单例统一(关键修复)

NERtc.getInstance()NERtcEx.getInstance() 是两个独立单例!

  • NERtc 是基类,NERtcEx 继承 NERtc 包含推流/转发等扩展能力
  • NERTCEngineHolder.init() 在 NERtcEx 实例上初始化
  • NERTCHelper 全部统一使用 NERtcEx.getInstance()(它继承 NERtc,包含所有基类方法),与 init 是同一实例

setClientRole 必要性

直播模式下必须调用 setClientRole(0)(BROADCASTER),SDK 默认是 AUDIENCE(1),不设角色则后续 joinChannel 时无法推流。

推流令牌管理

不要从缓存读取云信 Token! Token 有时效性,需要每次从服务端接口 getYunXinToken 实时获取:

http.ajax({
  url: '/api/getYunXinToken',
  data: { account: userId },
  success(res) {
    const { appKey, account, token } = res.data
    sdk.init({ appKey, videoConfig, audioConfig })
    sdk.joinChannel(account, channelName, token)
    sdk.startPushStreaming({ config: { streamingUrl, roomInfo: { uid: account, channelName, token } } })
  }
})

UID 精度处理

UTS 的 number 在 JS 运行时是 IEEE 754 double(float64),19 位 account ID 会丢精度。处理方式:Android UTS 侧 joinChannel 中 uid 参数为 string,在 NERTCHelper.kt 中 uidLong = uid.toLong() 转换为 Long,零精度损失。

错误码释义

错误码 含义 解决方案
0 成功
403 appKey 无效 检查网易云信控制台上的 appKey 是否正确
408 网络超时 检查网络连接
414 Token 错误 确保 token 有效、未过期、与 account 匹配
30015 RTMP 连接失败 RTMP 推流 URL 中 wsTime 时间戳过期或 wsSecret 签名不匹配;需从后端每次重新获取推流地址
40000 麦克风无权限 非致命,引擎仍可用
50000 摄像头无权限 非致命,引擎仍可用,_cameraAvailable=false

推流 30015 错误排查

30015 是 kNERtcPushStreamingErrorConnectServerFail,日志关键线索:

rtmpmodule.cc: rtmp connectStream failed
rtmpmodule.cc: WriteN, RTMP poll detect send error 115

根因:RTMP 推流 URL 中 wsTime 时间戳过期或 wsSecret 签名不匹配。推流 URL 通常有时效性(如 24 小时),需从后端每次重新获取。

蓝牙权限

Android 12+ (API 31+) 需要 BLUETOOTH_CONNECT 运行时权限。NERTC SDK 未授权时内部降级为 FakeBluetoothManager + 强制 A2DP 模式,不影响摄像头预览和推流,仅影响蓝牙耳机音频路由。插件提供 requestBluetoothPermission(element) 方法用于主动请求。

视频画布生命周期

  • 本地预览(uid=0)setupVideoCanvas 不立即绑定,只存储 _localViewElement + _localViewScalingType + _localViewMirror,实际在 startPreview 中统一创建 NERtcVideoView + 设置属性 + 挂载
  • 远端用户(uid>0)setupVideoCanvas 立即创建 NERtcVideoView + bindAndroidView + setupRemoteVideoCanvas

UTS 编译器兼容性问题(开发坑点汇总)

以下是开发过程中遇到的 UTS 编译器坑及修复:

  1. (videoView as View).post(runnable) 编译器错误:UTS 编译器把 (videoView as View).post(object : Runnable { ... }) 合并为单条 Kotlin 表达式 → Unresolved reference 'invoke'修复:先用中间变量 const videoViewForPost = videoView as View,再 videoViewForPost.post(runnable)

  2. ref<any>(null) 类型不可用:触发 None of the following candidates is applicable 错误。修复:使用 ref<UniNativeViewElement | null>(null) 等具体类型。

  3. as string 强转 NPE:Android 模板中 myData['xxx'] as string 在 Kotlin 编译产物中是 as String 非空断言,字段不存在时得到 nulljava.lang.NullPointerException: null cannot be cast to non-null type kotlin.String修复:使用 myData['xxx'] ?? '' as string

  4. boundingClientRect 回调字段在模板字符串中不可见rect.leftrect.top 等字段在 ${} 中报「找不到名称」。修复:先用局部变量 const x = (rect as UTSJSONObject)['left'],再拼接字符串。

  5. parseInt(uidStr) || 12345 条件表达式错误:UTS 要求条件表达式必须是 boolean 类型,parseInt 返回 number 不能用 || 放在条件中。修复parseInt(uidStr.value) 或显式判断 isNaN(n) ? fallback : n

  6. android.view.Choreographer 解析失败cool-unix 插件在 app-android 模块引用三方类,触发 UTS error18。

  7. numberInt 需要 .toInt():UTS number → Kotlin Number 不能隐式转 Int,需在 Kotlin helper 中统一用 .toInt() 转换。

视频画布组件

<template>
  <tt-nertc-video-view
    uid="0"
    scalingType="crop_fill"
    :mirror="true"
    :ready="isInited"
    @ready="onVideoReady"
  />
</template>

组件属性

属性 类型 默认值 说明
uid Number 0 0=本地摄像头预览,>0=远端视频
scalingType String "scale_auto" 缩放:"scale_fit" / "crop_fill" / "scale_auto"
mirror Boolean false 是否镜像
autoplay Boolean true uid=0 时是否自动开始预览
ready Boolean false 外部通知 SDK 已就绪

组件事件

事件 说明
ready 原生视图初始化完成时触发

Android 编译配置

// config.json — 全量依赖(已自动包含美颜 so + beauty 资源)
{
  "minSdkVersion": 21,
  "dependencies": [
    "com.netease.yunxin:nertc:5.9.10"
  ]
}

AndroidManifest.xml 权限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

更新日志

v1.2.0(2026-07-01)— iOS 推流功能完成

iOS 核心架构

  • SDK 桥接方案(7 次迭代后最终方案):iOS UTS→JS 桥接层无法可靠传递任何包含方法的复合对象(对象字面量/Swift class/UTS class 全部失败)。最终方案:uvue 端 const sdk = nertc as NERTCLiveSDK 直接用模块导出,每个 sdk.xxx() = nertc.xxx() = 模块 export function。Android 端 getNERTCLiveSDK() 返回对象字面量继续正常工作。
  • iOS 独立画布方案native-view 的 UIView 在 setupLocalVideoCanvaswindow 为 nil,导致 SDK 渲染不可见。改为在 keyWindow 上创建独立 UIView,用 createSelectorQuery 获取的屏幕坐标精确定位。
  • iOS 预览时序修正:iOS SDK startPreview 自行启动摄像头,不需要 enableLocalVideo。画布必须在 startPreview 之前设置,美颜必须在 startPreview 之后调用

iOS 开发问题及解决

  1. NSStringFromCGRect → iOS 17 SDK 已废弃,改用帧分量 f.origin.x/y/size.width/height 格式化
  2. _localViewElement = null → UTS 编译器对 UniNativeViewElement? = nil 生成裸 nil、无类型上下文,删除了该清理调用
  3. tailwind CSS 不支持 → blocklist 排除 static/grid/list-item/resize
  4. 引擎销毁时序 → removeLocalPreviewCanvas(需引擎存活)必须在 destroyEngine 之前

iOS 与 Android 调用差异

方面 Android iOS
画布挂载 addContentView + 精确定位 keyWindow addSubview + 坐标
预览启动 enableLocalVideo → setupCanvas → startVideoPreview setupVideoCanvasstartPreview
美颜时序 enableLocalVideo 之后 startPreview 之后
SDK 对象获取 getNERTCLiveSDK() 对象字面量 nertc as NERTCLiveSDK 模块导出
引擎初始化 异步(NERtcEx.init → onError/onWarning 回调) 同步(setupEngineWithContext 直接返回错误码)

v1.1.0(2026-06-24)

推流功能

  • Android 视频预览方案三(addContentView + createSelectorQuery 坐标精确定位)完成并验证
  • dp→px 转换修复(boundingClientRect 返回 dp,FrameLayout.LayoutParams 需 px)
  • 推流 Token 从服务端实时获取,不使用缓存
  • UID 精度处理:UTS 侧 string → Kotlin .toLong()
  • 错误码完善:403/408/414/30015 分类处理

美颜功能

  • 基础美颜 4 种(美白/磨皮/红润/锐化)完整实现并测试通过
  • 美颜调用时序:startBeauty → setBeautyEffect → startPreview → enableLocalVideo → _applySavedBeautyEffects → startVideoPreview
  • 美颜参数缓存 + 重新应用机制(采集中断后恢复)
  • 默认值调优:美白 0.9 / 磨皮 0.8 / 红润 0.5 / 锐化 0.3(实测效果明显)
  • Maven 全量依赖 nertc:5.9.10 自动引入美丽 so + assets 资源
  • 高级美颜(28 种)不集成(收费功能)

代码质量

  • NERTCHelper.kt 从方案文档同步更新为实际实现(435 行),含完整注释和关键修复说明
  • 单例统一:全部使用 NERtcEx.getInstance(),不再混用 NERtc.getInstance()
  • 预览 5 步法 + addContentView 精确定位 + Handler.postDelayed 布局等待

v1.0.0

  • Android + iOS 单人直播 CDN 推流
  • PK 直播(跨房间媒体转发 + 旁路合流)
  • 连麦(旁路合流)
  • 视频画布组件 tt-nertc-video-view
  • 蓝牙权限处理

隐私、权限声明

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

CAMERA, RECORD_AUDIO, INTERNET, BLUETOOTH_CONNECT

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

摄像头数据、麦克风数据、推流地址等

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

暂无用户评论。