HarmonyOS APP开发---"云客服"远程视频客服App,需要用到这个库

HarmonyOS APP开发---"云客服"远程视频客服App,需要用到这个库

金融视频柜员的场景很特殊:客户可能转横屏看资料、可能切后台去付款、网络还可能很差。任何一个环节黑屏,都是业务事故。@ohos/webrtc 提供完整 RTC 能力,而 Surface 时序和摄像头方向这两处细节,决定了它能不能上生产。
📦 仓库地址 | ohpm install @ohos/webrtc


场景

"云客服"面向银行/保险的远程视频柜员:

  • 客户办业务遇到问题 → 一键呼叫客户经理
  • 双方视频通话,核实身份、讲解流程
  • 客户操作不熟练 → 客户经理请求屏幕共享,看着客户的屏幕指导
  • 通话可能持续十几分钟,中途客户可能转横屏看资料、切后台去付款

四个硬性要求:不能黑屏 、摄像头方向要准 、镜像要正确 、网络差时要保语音清晰。


为什么是 WebRTC

需求 自研 RTP/SRTP @ohos/webrtc
NAT 穿透 手写 ICE/STUN/TURN ✅ 内置
音频 3A 手写 AEC/ANS/AGC ✅ 内置
视频编解码 手写 H.264/VP8 ✅ 硬件加速
拥塞控制 手写带宽估计 ✅ 内置
屏幕共享 ❌ 极难 ✅ getDisplayMedia
跨平台互通 ❌ ✅ 与浏览器/iOS/Android 互通
开发周期 数月到数年 ✅ 数天

客服场景还有个隐藏要求 :TURN 中继必须配。客户可能在企业内网或运营商 NAT 后面,纯 P2P 穿透成功率不够。TURN 虽然增加延迟,但保证"一定能通"------客服场景接通率比延迟重要。


关键实现

Surface 时序:避免黑屏的核心

远端视频轨道到达的时机和 Surface 创建的时机不确定。轨道先到、Surface 后创建时直接绑定会失败,结果就是黑屏。

用「暂存 + onLoad 回调」兜住两种时序:

typescript 复制代码
@Entry
@Component
struct ServiceCallPage {
  private remoteRenderer: VideoRenderer | null = null
  private pendingRemoteTrack: MediaStreamTrack | null = null   // ✅ 暂存
  private surfaceReady: boolean = false

  build() {
    Stack() {
      XComponent({ id: 'remote-video', type: 'SURFACE', libraryname: 'ohos_webrtc' })
        .onLoad(() => {
          // ✅ Surface 已创建,此时绑定才生效
          this.surfaceReady = true
          this.remoteRenderer = createVideoRenderer()

          if (this.pendingRemoteTrack) {
            this.remoteRenderer.setVideoTrack(this.pendingRemoteTrack)
            this.pendingRemoteTrack = null
          }
        })
        .width('100%').height('100%')
    }
  }

  // ✅ 轨道到达:Surface 就绪则直接绑,否则暂存
  private onRemoteTrack(track: MediaStreamTrack) {
    if (this.surfaceReady && this.remoteRenderer) {
      this.remoteRenderer.setVideoTrack(track)
    } else {
      this.pendingRemoteTrack = track
    }
  }

  // ✅ 切后台 Surface 被销毁重建,回前台要重挂
  onPageShow() {
    if (this.remoteRenderer && this.pendingRemoteTrack) {
      this.remoteRenderer.setVideoTrack(this.pendingRemoteTrack)
    }
  }
}

这个模式解决三类黑屏:轨道先到、Surface 先到、切后台后 Surface 重建。

建连与状态监控

typescript 复制代码
async startCall(roomId: string) {
  this.localStream = await mediaDevices.getUserMedia({
    audio: true,
    video: { width: 1280, height: 720, facingMode: 'user' },
  })

  this.pc = new RTCPeerConnection({
    iceServers: [
      { urls: ['stun:stun.service.com:3478'] },
      { urls: ['turn:turn.service.com:3478'], username: 'u', credential: 'p' },  // ✅ 必配
    ],
  })

  this.localStream.getTracks().forEach((t) => this.pc!.addTrack(t, this.localStream!))
  this.pc.ontrack = (e) => e.streams[0].getVideoTracks().forEach((t) => this.onRemoteTrack(t))
  this.pc.onicecandidate = (e) => { if (e.candidate) signaling.send({ type: 'ice', candidate: e.candidate, roomId }) }

  // ✅ 连接状态分级处理
  this.pc.onconnectionstatechange = () => {
    switch (this.pc!.connectionState) {
      case 'connected':    this.callState = '通话中'; break
      case 'disconnected': this.callState = '连接不稳定'; this.tryIceRestart(); break
      case 'failed':       this.callState = '连接失败'; this.reconnect(); break
    }
  }

  const offer = await this.pc.createOffer()
  await this.pc.setLocalDescription(offer)
  signaling.send({ type: 'offer', sdp: offer.sdp, roomId })
}

屏幕共享指导客户

typescript 复制代码
async requestScreenShare() {
  try {
    const screenStream = await mediaDevices.getDisplayMedia({ video: true })  // 拉起系统授权

    screenStream.getVideoTracks().forEach((track) => {
      this.pc!.addTrack(track, screenStream)
      track.onended = () => {           // 用户从通知栏停止共享
        this.isScreenSharing = false
        this.callState = '通话中'
      }
    })

    this.isScreenSharing = true
    this.callState = '屏幕共享中'
  } catch (err) {
    promptAction.showToast({ message: '屏幕共享不可用,请检查授权' })
  }
}

屏幕共享涉及系统级能力,测试清单要覆盖:正常共享与停止、来电打断、切其他 App、锁屏解锁、横竖屏切换、通知栏停止、弱网流畅度。


两个容易踩的点

1. 摄像头方向要真机全场景测

社区修过一个典型问题:前置摄像头在锁定横屏窗口下,发布流与预览都 180° 倒置。竖屏测试和模拟器都发现不了,必须真机横屏实测。

上线前按这张表过一遍:

场景 预期
竖屏 + 前置 本地预览镜像、远端正常
横屏 + 前置 ✅ 不倒置
通话中旋转屏幕 画面平滑过渡
切后台再回前台 ✅ 不黑屏

2. 网络差时优先保语音

客服场景语音清晰度比画质重要:

typescript 复制代码
private startQualityMonitor() {
  this.timer = setInterval(async () => {
    const stats = await this.pc!.getStats()
    let rtt = 0, loss = 0
    stats.forEach((r) => {
      if (r.type === 'candidate-pair' && r.nominated) rtt = r.currentRoundTripTime * 1000
      if (r.type === 'inbound-rtp' && r.kind === 'video') {
        loss = r.packetsLost / (r.packetsReceived + r.packetsLost) * 100
      }
    })

    if (rtt > 300 || loss > 5) {
      // ✅ 降视频保音频
      this.localStream?.getVideoTracks()[0]?.applyConstraints({
        width: 640, height: 360, maxBitrate: 300000,
      })
      promptAction.showToast({ message: '网络较差,已降低画质保证通话' })
    }
  }, 3000)
}

音频轨不动------客户听不清经理说什么,比看不清脸严重得多。


小结

"云客服"这个场景里,@ohos/webrtc 解决了三件事:完整 RTC 协议栈让 ICE 穿透和音频 3A 开箱即用、getDisplayMedia 让经理能看着客户屏幕指导操作、Stats API 让网络质量可监控可降级。

金融客服对稳定性零容忍。Surface 时序管理、摄像头方向真机全场景验收、网络差时降视频保语音------这三条工程实践做到了,才敢上生产。

相关推荐
逆回16 小时前
给鸿蒙手机做一只 3D 宠物:五个把我卡住的问题
harmonyos
钉先生贾18 小时前
性能、体积与稳定性:怎样写出可信的适配结论
华为·harmonyos
笑 总不会犯错1 天前
摆脱多端数据撕裂,鸿蒙分布式数据库同步与冲突合并
harmonyos
用户117910488331 天前
鸿蒙签名失败不是玄学:5 个证据点把 hapsigntool 一次排通 GitHub:swsgbl/hmharness
开源·ai编程·harmonyos
redtank20001 天前
纯血鸿蒙小工具三件套实测:弹幕手持、爱记录与仓库管理软件
学习·华为·智能手机·harmonyos
用户86194177207412 天前
uni-app 的蓝牙 API 在鸿蒙上不生效?我写了个兼容层,让老代码零改动跑起来
harmonyos
HarmonyOS_SDK2 天前
【HarmonyOS SDK Skills】挂载 Agent Skill,轻松跑通鸿蒙应用广告变现
harmonyos
闫东阳LordGalaxy2 天前
给小艺装上数学能力:数易 App 接入智能体 FunctionComponent 与 A2A 双通路实战
harmonyos
Dream-Y.ocean2 天前
鸿蒙PC平台逻控 PLC 编辑器适配实战:基于 Electron 壳方案的纯 JavaScript 梯形图编程与仿真引擎开发
javascript·编辑器·harmonyos