更新记录
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 × 384dp → 360px × 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 编译器坑及修复:
-
(videoView as View).post(runnable)编译器错误:UTS 编译器把(videoView as View).post(object : Runnable { ... })合并为单条 Kotlin 表达式 →Unresolved reference 'invoke'。修复:先用中间变量const videoViewForPost = videoView as View,再videoViewForPost.post(runnable)。 -
ref<any>(null)类型不可用:触发None of the following candidates is applicable错误。修复:使用ref<UniNativeViewElement | null>(null)等具体类型。 -
as string强转 NPE:Android 模板中myData['xxx'] as string在 Kotlin 编译产物中是as String非空断言,字段不存在时得到null→java.lang.NullPointerException: null cannot be cast to non-null type kotlin.String。修复:使用myData['xxx'] ?? '' as string。 -
boundingClientRect回调字段在模板字符串中不可见:rect.left、rect.top等字段在${}中报「找不到名称」。修复:先用局部变量const x = (rect as UTSJSONObject)['left'],再拼接字符串。 -
parseInt(uidStr) || 12345条件表达式错误:UTS 要求条件表达式必须是 boolean 类型,parseInt返回 number 不能用||放在条件中。修复:parseInt(uidStr.value)或显式判断isNaN(n) ? fallback : n。 -
android.view.Choreographer解析失败:cool-unix插件在 app-android 模块引用三方类,触发 UTS error18。 -
number转Int需要.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 在setupLocalVideoCanvas时window为 nil,导致 SDK 渲染不可见。改为在keyWindow上创建独立 UIView,用createSelectorQuery获取的屏幕坐标精确定位。 - iOS 预览时序修正:iOS SDK
startPreview自行启动摄像头,不需要enableLocalVideo。画布必须在startPreview之前设置,美颜必须在startPreview之后调用
iOS 开发问题及解决
NSStringFromCGRect→ iOS 17 SDK 已废弃,改用帧分量f.origin.x/y/size.width/height格式化_localViewElement = null→ UTS 编译器对UniNativeViewElement? = nil生成裸nil、无类型上下文,删除了该清理调用tailwindCSS 不支持 →blocklist排除static/grid/list-item/resize- 引擎销毁时序 →
removeLocalPreviewCanvas(需引擎存活)必须在destroyEngine之前
iOS 与 Android 调用差异
| 方面 | Android | iOS |
|---|---|---|
| 画布挂载 | addContentView + 精确定位 |
keyWindow addSubview + 坐标 |
| 预览启动 | enableLocalVideo → setupCanvas → startVideoPreview |
setupVideoCanvas → startPreview |
| 美颜时序 | 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 - 蓝牙权限处理

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 0
赞赏 0
下载 12372371
赞赏 1927
赞赏
京公网安备:11010802035340号