从 TRTC Demo 到独立音视频服务:React + TRTC Web SDK 服务化实践

1. 前言

实时音视频已经从会议、直播、在线教育等垂直场景,逐渐变成业务系统中的基础能力。一个后台系统可能需要远程协助,一个客户服务系统可能需要视频客服,一个邀请链路可能需要让双方直接进入同一个音视频房间。

传统接入方式通常是:每个业务系统自己集成 TRTC Web SDK,自己处理 SDKAppID、UserSig、进房、设备权限、摄像头采集、远端订阅、异常清理和浏览器兼容。这样做能跑通,但很快会暴露几个问题:

  • 业务代码与音视频 SDK 强耦合。
  • 多个业务系统重复实现同一套 WebRTC 生命周期。
  • SDK 升级、权限问题、浏览器策略变化需要多处维护。
  • 邀请参数、房间参数、用户身份的生成规则难以统一。

本文基于一个 React + TRTC Web SDK v5 项目实践,讨论一种更适合多业务复用的方案:把 TRTC Web SDK 能力封装成可以被外部业务系统通过 URL 和 iframe 快速调用的音视频服务。

当前项目来源于腾讯云 TRTC Web SDK v5 quick-demo-react,但已经扩展出三个独立服务入口:

服务路由职责
邀请参数生成服务/#/invite-link生成 ownerUserIdguestUserIdroomIdinviteToken 和邀请链接
音视频发布服务/#/media-publisher采集当前浏览器摄像头、麦克风,并进入 TRTC 房间发布本地音视频
远端观看服务/#/media-viewer进入同一 TRTC 房间,订阅指定 targetUserId 的远端视频

这三个服务不是 README 式的项目介绍,而是一次服务化拆分:业务系统不再直接操作 TRTC SDK,而是组合这些稳定的页面服务。

2. WebRTC 与 TRTC 基础概念

对业务系统来说,实时音视频的核心模型可以简化为:用户以某个身份进入一个房间,其中一部分用户发布音视频,另一部分用户订阅并观看音视频。

Room

Room 是音视频通信的隔离空间。当前项目使用字符串房间 ID,TRTC 进房参数名为 strRoomId,对外 URL 参数统一使用 roomId,并兼容 strRoomId

UserId

UserId 是用户进入 TRTC 房间的身份。当前服务化设计要求同一房间、同一时间内不同 iframe 使用不同的用户身份。例如发布者使用 host_1001,观看者 iframe 使用 viewer_guest_host_1001

Publisher

Publisher 是发布端。当前项目的 /#/media-publisher 会用 userId 进入房间,并调用 startLocalAudiostartLocalVideo 启动麦克风和摄像头。

Viewer

Viewer 是观看端。当前项目的 /#/media-viewer 使用 viewerUserId 进入房间,不申请本机摄像头和麦克风权限,只订阅 targetUserId 的远端视频。

Media Stream

音视频流来自浏览器摄像头和麦克风,通过 TRTC Web SDK 进入腾讯云 TRTC 房间,再由远端订阅播放。

摄像头

发布端 media-publisher

麦克风

TRTC 房间 strRoomId

观看端 media-viewer

远端画面

3. 整体架构设计

当前项目的服务化架构分成两层:

  • React 应用内部负责 TRTC SDK 生命周期。
  • 外部业务系统通过 iframe 和 postMessage 调用能力、接收状态。

业务系统

iframe

Hash 路由服务入口

React 页面

useMediaPublisher / useMediaViewer

trtc-sdk-v5

腾讯云 TRTC

postMessage 状态通知

应用入口关系如下:

index.html

src/main.tsx

initAegis()

src/App.tsx

HashRouter

/#/invite-link

/#/media-publisher

/#/media-viewer

/#/media-module-demo

useMediaPublisher

useMediaViewer

TRTC.create()

src/App.tsx 中通过 SILENT_ROUTES 隐藏导航栏,/invite-link/media-publisher/media-viewer 都属于静默服务路由。这一点非常关键:服务入口不是给用户操作 Demo 控制台,而是给业务系统嵌入使用。

4. 为什么选择 iframe 服务化模式

把 TRTC 能力封装为 iframe 页面,本质上是在业务系统和 SDK 之间增加一层可复用的音视频服务边界。

它的优势是:

  • 解耦业务系统和 TRTC SDK。业务系统只拼 URL,不直接引入 trtc-sdk-v5
  • 降低接入成本。外部系统不需要理解完整 WebRTC 生命周期。
  • 统一维护。设备采集、进房、订阅、销毁、SDK 升级集中在一个模块里。
  • 多业务复用。不同业务只传不同的 userIdroomIdmode
  • 页面隔离。iframe 内部状态和业务系统的 React/Vue/原生页面互不污染。

它也带来限制:

  • iframe 使用摄像头和麦克风必须配置 allow
  • 父子页面通信依赖 postMessage,生产环境必须校验 originsource
  • 跨域部署时需要统一 HTTPS、权限策略和自动播放策略。
  • 每个 iframe 都是独立 TRTC 用户,不能复用同一个 userId

在当前项目里,iframe 不是简单展示页面,而是对外服务 API 的载体。

5. 三个 TRTC 服务设计

5.1 邀请参数生成服务

服务作用

/#/invite-link 不进入 TRTC 房间,只负责生成邀请链路所需的参数和网址。它解决的是“业务系统如何统一生成房间、房主、访客和 token 参数”的问题。

URL

https://trtc.example.com/#/invite-link?ownerUserId=host_1001&roomId=room_001&mode=business_a

参数

参数必填说明
ownerUserId房主用户 ID,兼容读取 userId
roomIdTRTC 字符串房间 ID,兼容读取 strRoomId
mode业务透传参数,默认 rtc,当前实现会转为小写

内部流程

打开 /#/invite-link

getInviteLinkParams() 解析 URL

getMissingInviteLinkParams() 校验 ownerUserId / roomId

createInviteLinkPayload()

生成 guestUserId

生成 hostViewerUserId / guestViewerUserId

生成 inviteToken

createHangwuInviteLink('/hangwu-family-invite')

页面展示并支持复制

iframe 内 postMessage: TRTC_INVITE_LINK_READY

接入示例

<iframe
  src="https://trtc.example.com/#/invite-link?ownerUserId=host_1001&roomId=room_001&mode=business_a"
  allow="clipboard-read; clipboard-write"
  style="width: 100%; height: 260px; border: 0;"
></iframe>

注意事项

当前实现中 inviteToken 由前端使用 window.btoa(encodeURIComponent(JSON.stringify(tokenPayload))) 生成,有效期一小时。它适合联调和前端闭环验证;生产环境应改为后端生成并签名。

5.2 音视频发布服务

服务作用

/#/media-publisher 负责把当前浏览器设备上的摄像头和麦克风发布到 TRTC 房间。它不会订阅远端,也不会展示复杂操作 UI。

URL

https://trtc.example.com/#/media-publisher?userId=host_1001&roomId=room_001&mode=business_a

参数

参数必填说明
userId当前发布者进入 TRTC 房间使用的用户 ID
roomIdTRTC 字符串房间 ID,兼容 strRoomId
mode业务透传参数,默认 rtc
cameraId指定摄像头设备 ID
microphoneId指定麦克风设备 ID
videoProfile视频规格,默认 1080p

内部流程

打开 /#/media-publisher

getMediaPublisherParams()

校验 SDKAppID / SecretKey / userId / roomId

TRTC.create()

绑定 ERROR / CONNECTION_STATE_CHANGED / PUBLISH_STATE_CHANGED

genTestUserSig()

enterRoom({ sdkAppId, userId, userSig, strRoomId })

startLocalAudio({ microphoneId })

startLocalVideo({ view, cameraId, profile })

状态 publishing

接入示例

<iframe
  src="https://trtc.example.com/#/media-publisher?userId=host_1001&roomId=room_001&mode=business_a"
  allow="camera; microphone; autoplay; fullscreen"
  style="width: 100%; height: 420px; border: 0; background: #000;"
></iframe>

注意事项

allow="camera; microphone; autoplay; fullscreen" 必须配置。否则浏览器可能禁止 iframe 访问摄像头、麦克风或自动播放媒体。

当前 media-publisher 没有暴露设备选择 UI,设备参数只能通过 URL 传入。

5.3 远端观看服务

服务作用

/#/media-viewer 负责使用观看者身份进入房间,等待并播放指定发布者的远端视频。

URL

https://trtc.example.com/#/media-viewer?viewerUserId=viewer_guest_host_1001&targetUserId=host_1001&roomId=room_001&mode=business_a

参数

参数必填说明
viewerUserId观看者进入 TRTC 房间使用的用户 ID,兼容读取 userId
targetUserId需要观看的远端发布者 ID
roomIdTRTC 字符串房间 ID,兼容 strRoomId
mode业务透传参数,默认 rtc

内部流程

打开 /#/media-viewer

getMediaViewerParams()

校验 viewerUserId / targetUserId / roomId

TRTC.create()

监听 REMOTE_VIDEO_AVAILABLE / REMOTE_VIDEO_UNAVAILABLE / ERROR

genTestUserSig(viewerUserId)

enterRoom()

状态 waitingTarget

收到 targetUserId 的 REMOTE_VIDEO_AVAILABLE

startRemoteVideo({ userId, streamType, view })

状态 viewing

接入示例

<iframe
  src="https://trtc.example.com/#/media-viewer?viewerUserId=viewer_guest_host_1001&targetUserId=host_1001&roomId=room_001&mode=business_a"
  allow="autoplay; fullscreen"
  style="width: 100%; height: 420px; border: 0; background: #000;"
></iframe>

注意事项

viewer 不采集本机设备,因此不需要 cameramicrophone 权限。当前实现会校验 viewerUserId !== targetUserId,同一个 iframe 不能既是观看者又是被观看目标。

6. 邀请参数服务详解

邀请参数服务的价值在于统一生成一组可流转的通话身份,而不是让业务系统临时拼接用户 ID。

src/utils/silentRouteParams.tscreateInviteLinkPayload() 会生成:

  • guestUserId:被邀请者发布音视频时使用。
  • hostViewerUserId:房主观看被邀请者时使用。
  • guestViewerUserId:被邀请者观看房主时使用。
  • inviteToken:包含 ownerUserIdguestUserIdroomIdmodegeneratedAtexpiresAt
  • moduleParams:分别给房主 publisher、房主 viewer、访客 publisher、访客 viewer 使用的参数。

ownerUserId

邀请参数 payload

roomId

mode

guestUserId = guest_xxx

hostViewsGuest.viewerUserId

guestViewsHost.viewerUserId

inviteToken

网址 A: /#/hangwu-family-invite

网址 B: /#/hangwu-invite

/#/hangwu-video-call

token 校验逻辑在 src/utils/hangwuInvite.ts

export function getHangwuInviteErrors(params: HangwuInviteParams) {
  const errors = getMissingHangwuInviteParams(params);
  const token = decodeHangwuInviteToken(params.inviteToken);
  if (params.inviteToken && !token) errors.push('inviteToken 无效');
  if (token) {
    if (token.ownerUserId !== params.ownerUserId || token.guestUserId !== params.guestUserId || token.roomId !== params.roomId || token.mode !== params.mode) {
      errors.push('邀请参数与 inviteToken 不匹配');
    }
    if (!Number.isFinite(token.generatedAt) || !Number.isFinite(token.expiresAt) || token.expiresAt <= token.generatedAt) {
      errors.push('inviteToken 有效期无效');
    } else {
      if (token.generatedAt > Date.now() + 60 * 1000) errors.push('inviteToken 生成时间无效');
      if (token.expiresAt < Date.now()) errors.push('邀请链接已过期');
    }
  }
  if (params.ownerUserId && params.ownerUserId === params.guestUserId) errors.push('ownerUserId 不能等于 guestUserId');
  return errors;
}

这段逻辑说明邀请链路至少解决了四类问题:缺参、token 无效、参数被篡改、链接过期。

7. 音视频发布服务详解

发布服务的目标是把浏览器设备采集和 TRTC 发布链路封装成一个 URL。

发布端页面只有两个核心文件:

  • src/pages/MediaPublisherPage.tsx:解析参数,渲染本地视频容器和状态浮层。
  • src/hooks/useMediaPublisher.ts:创建 TRTC 实例,进入房间,启动音频和视频。

页面组件非常薄:

export default function MediaPublisherPage() {
  const params = useMemo(() => getMediaPublisherParams(), []);
  const { status, error, localVideoRef } = useMediaPublisher(params);
  const pending = status !== 'publishing' && status !== 'failed' && status !== 'destroyed';

  return (
    <div className="silent-media-page">
      <div className="silent-media-video" ref={localVideoRef} />
      {pending && <div className="silent-media-overlay">连接中...</div>}
      {status === 'destroyed' && <div className="silent-media-overlay">已退出</div>}
      {error && <div className="silent-media-error">{error}</div>}
    </div>
  );
}

真正的生命周期在 hook 中:

const trtc = TRTC.create();

await trtc.enterRoom({
  sdkAppId: currentParams.sdkAppId,
  userId: currentParams.userId,
  userSig,
  strRoomId: currentParams.roomId,
});

await trtc.startLocalAudio({
  option: {
    microphoneId: currentParams.microphoneId || undefined,
  },
});

await trtc.startLocalVideo({
  view: localVideoRef.current,
  option: {
    cameraId: currentParams.cameraId || undefined,
    profile: (currentParams.videoProfile || '1080p') as any,
  },
});

这里有一个实践细节:当前 TRTC Web SDK v5 封装中,发布端没有单独暴露 publish() 代码路径,而是在进房后启动本地音频和视频。博客或接入文档中如果泛泛写“创建 LocalStream 后 publish”,就和这个实现不一致。

iframe 示例:

<iframe
  src="https://trtc.example.com/#/media-publisher?userId=host_1001&roomId=room_001&mode=business_a&videoProfile=1080p"
  allow="camera; microphone; autoplay; fullscreen"
  style="width: 100%; height: 420px; border: 0; background: #000;"
></iframe>

allow 的意义不是样式配置,而是浏览器权限声明。发布端需要摄像头和麦克风,所以必须包含 cameramicrophone

8. 远端观看服务详解

观看服务的关键是 targetUserId。它把“我是谁”和“我要看谁”分离开。

host_1001 media-publisher

room_001

viewer_guest_host_1001 media-viewer

targetUserId = host_1001

src/hooks/useMediaViewer.ts 的过滤逻辑非常明确:

trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, ({ userId, streamType }: any) => {
  if (String(userId) !== String(paramsRef.current.targetUserId)) {
    return;
  }
  window.requestAnimationFrame(() => {
    startTargetRemoteVideo(userId, streamType);
  });
});

也就是说,即使房间里有多个远端用户,当前 viewer 也只会播放 targetUserId 对应的远端视频。

当目标用户下线或停止视频时,观看端会处理 REMOTE_VIDEO_UNAVAILABLE

trtc.on(TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, ({ userId, streamType }: any) => {
  if (String(userId) !== String(paramsRef.current.targetUserId)) {
    return;
  }
  startedRemoteVideoRef.current = false;
  clearRemoteVideoContainer();
  trtc.stopRemoteVideo({ userId, streamType });
  notifyStatus('targetOffline');
});

这使 viewer 可以先进入房间并等待发布端上线。页面状态为 waitingTargettargetOffline 时,会显示“等待远端画面”。

9. 外部业务完整接入流程

假设第三方业务系统要做一条双方音视频通话链路,可以这样组合三个服务:

腾讯云 TRTC 房间访客 /访客 /房主 /房主 //业务系统腾讯云 TRTC 房间访客 /访客 /房主 /房主 //业务系统ownerUserId + roomId + modeTRTC_INVITE_LINK_READY(inviteUrl, guestUserId, inviteToken)userId=ownerUserId, roomIdenterRoom + startLocalAudio + startLocalVideoviewerUserId=viewer_host_xxx, targetUserId=guestUserIdenterRoom, wait target打开邀请链接,访客进入业务页viewerUserId=viewer_guest_xxx, targetUserId=ownerUserIduserId=guestUserId, enterRoom + startLocalAudio + startLocalVideoREMOTE_VIDEO_AVAILABLE guestUserIdREMOTE_VIDEO_AVAILABLE ownerUserId

通用双 iframe 组合方式如下。

房主端:

<iframe
  src="https://trtc.example.com/#/media-publisher?userId=host_1001&roomId=room_001&mode=business_a"
  allow="camera; microphone; autoplay; fullscreen"
></iframe>

<iframe
  src="https://trtc.example.com/#/media-viewer?viewerUserId=viewer_host_guest_2001&targetUserId=guest_2001&roomId=room_001&mode=business_a"
  allow="autoplay; fullscreen"
></iframe>

被邀请者端:

<iframe
  src="https://trtc.example.com/#/media-viewer?viewerUserId=viewer_guest_host_1001&targetUserId=host_1001&roomId=room_001&mode=business_a"
  allow="autoplay; fullscreen"
></iframe>

<iframe
  src="https://trtc.example.com/#/media-publisher?userId=guest_2001&roomId=room_001&mode=business_a"
  allow="camera; microphone; autoplay; fullscreen"
></iframe>

当前项目还提供了 /#/media-module-demo,用于测试邀请参数、房主端组合和被邀请者端组合。

10. iframe 跨页面通信设计

iframe 接入后,父页面需要知道子模块是否连接成功、是否正在发布、是否失败、是否已经退出。当前项目用 postMessage 实现跨页面通信。

状态通知

状态通知函数在 src/utils/postMessage.ts

export function postTrtcModuleStatus(payload: Omit<TrtcModuleStatusPayload, 'type'>) {
  if (window.parent && window.parent !== window) {
    window.parent.postMessage({ type: 'TRTC_MODULE_STATUS', ...payload }, '*');
  }
}

publisher 状态示例:

{
  "type": "TRTC_MODULE_STATUS",
  "page": "media-publisher",
  "status": "publishing",
  "userId": "host_1001",
  "roomId": "room_001",
  "mode": "business_a"
}

viewer 状态示例:

{
  "type": "TRTC_MODULE_STATUS",
  "page": "media-viewer",
  "status": "viewing",
  "userId": "viewer_guest_host_1001",
  "roomId": "room_001",
  "mode": "business_a",
  "targetUserId": "host_1001"
}

publisher 状态机来自 src/types/silentRoom.ts

idle -> resolvingParams -> creating -> entering -> startingDevice -> publishing
failed
leaving -> destroyed

viewer 状态机:

idle -> resolvingParams -> creating -> entering -> waitingTarget -> viewing
targetOffline
failed
destroyed

命令控制

父页面可以向 iframe 发送命令:

const iframe = document.querySelector('#hostPublisherIframe');

iframe.contentWindow.postMessage({
  type: 'TRTC_MODULE_COMMAND',
  command: 'leave'
}, '*');

命令订阅逻辑如下:

export function subscribeTrtcModuleCommand(onCommand: (payload: TrtcModuleCommandPayload) => void) {
  const handleMessage = (event: MessageEvent) => {
    const data = event.data;
    if (!data || typeof data !== 'object') return;
    if (data.type !== 'TRTC_MODULE_COMMAND') return;
    onCommand(data as TrtcModuleCommandPayload);
  };

  window.addEventListener('message', handleMessage);
  return () => window.removeEventListener('message', handleMessage);
}

src/types/silentRoom.ts 预定义了 restartmuteAudiounmuteAudiomuteVideounmuteVideoswitchCameraswitchMicrophone 等命令类型,但当前 useMediaPublisheruseMediaViewer 实际只处理 leave

生产环境中,父页面监听消息时不应直接信任 event.data,还要校验:

  • event.origin 是否为 TRTC 模块服务域名。
  • event.source 是否为当前 iframe 的 contentWindow
  • 消息里的 userIdroomIdtargetUserId 是否与业务系统记录一致。

11. React 内部架构映射

当前项目没有 services/ 目录,也没有额外的 React Context / Provider。服务化能力主要分布在:

层级文件职责
入口src/main.tsx初始化 Aegis、加载 i18n、渲染 App
路由src/App.tsx使用 HashRouter 注册页面路由,静默服务路由隐藏 NavBar
页面src/pages/InviteLinkPage.tsx生成邀请链接和 TRTC_INVITE_LINK_READY
页面src/pages/MediaPublisherPage.tsx渲染本地视频容器、连接状态和错误
页面src/pages/MediaViewerPage.tsx渲染远端视频容器、等待状态和错误
Hooksrc/hooks/useMediaPublisher.ts发布端 TRTC 生命周期
Hooksrc/hooks/useMediaViewer.ts观看端 TRTC 生命周期
Utilssrc/utils/silentRouteParams.ts服务路由 URL 参数解析、缺参校验、邀请 payload 生成
Utilssrc/utils/postMessage.tsiframe 状态通知和命令订阅
Utilssrc/utils/generateTestUserSig.ts浏览器端测试 UserSig 生成
Typessrc/types/silentRoom.ts服务页面、状态、命令和参数类型

主 Demo 页仍保留在 HomePage + useTRTC + Zustand 架构中,支持手动进房、设备选择、屏幕共享、日志和邀请链接。服务化页面没有复用主页面 UI,而是用更薄的页面加专用 hook,降低 iframe 使用时的干扰。

MediaPublisherPage / MediaViewerPage

silentRouteParams

useMediaPublisher / useMediaViewer

React useState 状态机

useRef: TRTC 实例和 video 容器

trtc-sdk-v5

postMessage

清理逻辑也集中在 hook 中。发布端卸载时会:

stopLocalVideo
stopLocalAudio
exitRoom
off('*')
destroy
清空本地视频容器

观看端卸载时会:

stopRemoteVideo({ userId: targetUserId })
exitRoom
off('*')
destroy
清空远端视频容器

这类清理对 iframe 模式尤其重要,因为父页面移除 iframe 时,子页面必须释放摄像头、麦克风和 TRTC 连接。

12. 浏览器安全限制

HTTPS

摄像头、麦克风和 WebRTC 依赖浏览器安全上下文。当前 README 和部署文档都明确提醒:生产环境或跨设备访问应使用 HTTPS。否则可能出现 navigator.mediaDevices 不可用、摄像头无法打开或 iframe 权限被拦截。

localhost

本地开发时,localhost127.0.0.1 通常被浏览器视为可用于调试的安全环境。当前 vite.config.ts 会从 5173 开始寻找可用端口,并监听 0.0.0.0,便于本机和局域网调试。

需要注意:局域网 IP + HTTP 通常不是安全上下文,不能等同于 localhost

iframe 权限

发布端 iframe 必须包含:

camera; microphone; autoplay; fullscreen

观看端 iframe 至少应包含:

autoplay; fullscreen

发布端需要摄像头和麦克风;观看端不采集本机设备,但仍可能受自动播放策略影响。

浏览器兼容

当前 HomePage 调用了 TRTC.isSupported(),如果不支持会提示使用最新版本 Chrome。静默路由没有单独做这层拦截,因此外部业务系统接入时建议在父页面或测试流程中补充浏览器检查。

实际生产中至少要覆盖:

  • Chrome:当前项目明确提示的推荐浏览器。
  • Edge:基于 Chromium 的 Edge 通常与 Chrome 接近,但仍应实际验证设备权限和自动播放。
  • Safari:需要重点验证 getUserMedia、iframe 权限和自动播放策略。

13. 部署注意事项

当前项目使用 Vite 构建,package.json 中脚本为:

{
  "dev": "vite",
  "build": "tsc && vite build",
  "preview": "vite preview"
}

Dockerfile 采用两阶段构建:

node:20-alpine build

npm ci

npm run build

/app/dist

nginx:1.27-alpine runtime

/usr/share/nginx/html

部署时需要注意:

  • VITE_TRTC_SDK_APP_IDVITE_TRTC_SDK_SECRET_KEY 是构建期注入。
  • 修改 .env 后,开发环境要重启 Vite,Docker 生产环境要重新构建镜像。
  • nginx.conf 使用 try_files $uri $uri/ /index.html;,适配前端路由。
  • 当前 Docker Compose 将容器 80 端口映射到宿主机 18080
  • 生产环境必须使用 HTTPS 域名暴露服务,例如 https://trtc.example.com/

14. TRTC 安全设计

当前项目的 UserSig 生成逻辑在浏览器端:

const generator = new (window as any).LibGenerateTestUserSig(sdkAppId, sdkSecretKey, EXPIRETIME);
const userSig = generator.genTestUserSig(userId);

public/lib-generate-test-usersig.min.js 提供了测试用 UserSig 生成能力。README 中也明确说明:这只适合调试,生产环境必须迁移到后端。

推荐生产架构如下:

业务前端 / TRTC 服务页面

业务后端 UserSig 服务

SDKSecretKey 只保存在服务端

腾讯云 TRTC

trtc-sdk-v5 enterRoom

生产化改造建议:

  • 前端不保存 SDKSecretKey
  • inviteToken 由后端签发,绑定 roomIdownerUserIdguestUserId、有效期和权限。
  • UserSig 由后端按用户身份按需签发。
  • postMessage 的 originsource 和参数一致性必须校验。
  • 邀请链接中的敏感参数应控制有效期,并支持服务端失效。

15. 常见问题

摄像头打不开

优先检查:

  • 当前页面是否是 HTTPS、localhost127.0.0.1
  • iframe 是否配置 allow="camera; microphone; autoplay; fullscreen"
  • 浏览器是否已授权摄像头和麦克风。
  • 摄像头是否被其他程序占用。
  • VITE_TRTC_SDK_APP_IDVITE_TRTC_SDK_SECRET_KEY 是否在构建产物中正确注入。

viewer 黑屏

优先检查:

  • publisher 和 viewer 是否使用相同 roomId
  • targetUserId 是否等于发布端的 userId
  • 发布端是否已经进入 publishing 状态。
  • viewer 是否一直处于 waitingTargettargetOffline
  • viewerUserId 是否与 targetUserId 重复。

iframe 无声音

优先检查:

  • iframe 是否包含 allow="autoplay; fullscreen"
  • 浏览器是否因为自动播放策略阻止了音频。
  • 观看端是否真正收到 REMOTE_VIDEO_AVAILABLE 并进入 viewing
  • 发布端麦克风权限是否成功授权。

多窗口冲突

TRTC 用户身份必须唯一。同一个页面中每个 iframe 都应使用不同的 userIdviewerUserId

hostUserId != guestUserId
viewerUserId != targetUserId
同一页面中的每个 iframe 都使用不同身份
双方使用同一个 roomId

缺少 VITE_TRTC_SDK_APP_ID / VITE_TRTC_SDK_SECRET_KEY

这不是外部业务 URL 参数缺失,而是 TRTC 模块自身构建产物缺少 SDK 凭据。开发环境检查 .env 并重启 Vite;Docker 环境通过 build args 重新构建镜像。

16. 总结

这次实践的核心不是“写了三个页面”,而是把 TRTC Web SDK 的复杂生命周期转换成三个稳定的服务入口:

  • /#/invite-link 负责邀请参数和链路生成。
  • /#/media-publisher 负责本地设备采集和发布。
  • /#/media-viewer 负责指定远端用户订阅和播放。

业务系统通过 iframe 组合这些入口,就能快速获得音视频能力,而不用在每个系统里重复实现 TRTC 初始化、进房、设备启动、事件监听和资源释放。

TRTC Web SDK 复杂生命周期

React 静默服务路由

iframe 接入

多业务复用

降低接入和维护成本

这类服务化封装适合被多个业务系统复用:音视频模块集中演进,业务系统只关注用户、房间、邀请链路和页面编排。对于企业内部多业务接入实时音视频,这是比“每个系统集成一次 SDK”更可维护的架构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值