更新记录

1.0.16(2026-08-29)

新增语音通话以及新接口

1.0.15(2026-08-29)

框架重构,原1对1,改为 以1对多,语音通话下一版本

1.0.14(2026-04-23)

修复

查看更多

平台兼容性

uni-app x(3.8.5)

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

其他

多语言 暗黑模式 宽屏模式

tc-webrtc 多人视频通话插件接口文档

📌 概述

tc-webrtc 支持 多人 同时视频通话,无需媒体服务器,通过信令服务器协调连接

特别注意!!

试用前请先【导入示例项目】 导入完成后,再申请试用插件并导入到示例项目中!

目前支持 :多人视频通话多人语音通话

支持:蒸汽模式

WebSocket (官方X版本)

当前版本tc-webrtc(v2.0)(1对多)

如需其他插件可联系我定制。


接口分类

分类 方法
初始化 WebRTCapi.setIceServers(), WebRTCapi.initializeFactory(), WebRTCapi.destroy()
视图绑定 WebRTCapi.bindLocalView(), WebRTCapi.bindRemoteView()
信令 WebRTCapi.setSignalingCallback(), WebRTCapi.handleOffer(), WebRTCapi.handleAnswer(), WebRTCapi.handleIce()
连接管理 WebRTCapi.connectToUser(), WebRTCapi.removeUser(), WebRTCapi.leaveRoom()
音视频控制 WebRTCapi.setVideoEnabled(), WebRTCapi.setAudioEnabled(), WebRTCapi.switchCamera(), WebRTCapi.setSpeakerphoneOn(), WebRTCapi.setVoiceOnlyMode()

接口速查表

方法调用 参数 作用
WebRTCapi.setIceServers(configJson) JSON数组详见示例工程 配置 STUN/TURN 服务器
WebRTCapi.setSignalingCallback(callback) callback: (type, data, sdpMid, mLineIdx, targetUid) => void 设置信令发送回调
WebRTCapi.initializeFactory() 初始化引擎(摄像头/麦克风)
WebRTCapi.destroy() 彻底销毁引擎,释放全部资源
WebRTCapi.bindLocalView(view) view: any (native-view element) 绑定本地摄像头预览视图
WebRTCapi.bindRemoteView(uid, view) uid: string, view: any 绑定远端用户ID视频渲染视图
WebRTCapi.connectToUser(uid) uid: string 主动向指定用户ID发起 Offer
WebRTCapi.handleOffer(fromUid, sdp) fromUid: string, sdp: string 处理 Offer,自动回复 Answer
WebRTCapi.handleAnswer(fromUid, sdp) fromUid: string, sdp: string 处理 Answer,完成连接
WebRTCapi.handleIce(fromUid, candidate, sdpMid, mLineIdx) fromUid: string, candidate: string, sdpMid: string, mLineIdx: number 添加 ICE 候选
WebRTCapi.removeUser(uid) uid: string 移除远端用户ID(断开连接,释放渲染器)
WebRTCapi.leaveRoom() 离开房间,断开连接,保留引擎
WebRTCapi.setVideoEnabled(enabled) boolean true=启用/false=禁用摄像头
WebRTCapi.setAudioEnabled(enabled) boolean true=启用/false=禁用麦克风
WebRTCapi.switchCamera() 切换 前(默认)/后 摄像头
WebRTCapi.setSpeakerphoneOn(true) boolean true=扬声器(默认),false=听筒
WebRTCapi.setVoiceOnlyMode() boolean false=视频通话(默认),true=仅语音通话

注意事项


WebRTCapi.setVoiceOnlyMode()
在视频通话页面可以不用
在语音通话时如下所示
onLoad() {
    WebRTCapi.setVoiceOnlyMode(true);
}

加入房间

例如:用户ID+房间号
uni.navigateTo({
    url:"/pages/vd_webrtc/vd_webrtc_room?myuid=22&roomid=asd123"
});

更多查考示DEMO

一、配套信令服务端代码(PHP)(仅为快速跑通本插件,服务器安全请自行优化处理)

<?php
$ws = new Swoole\WebSocket\Server("0.0.0.0", 9505);//服务器放行9505
$ws->set([
    'open_tcp_keepalive' => 1,
    'tcp_fastopen' => true,
    'max_conn' => 1000,
    'dispatch_mode' => 1,
    'daemonize' => false,
    'user' => 'www',
    'group' => 'www',
    'heartbeat_check_interval' => 30,
    'heartbeat_idle_time' => 60,
    'enable_signalfd' => true,
    'log_file' => '/tmp/swoole_ws.log',
]);

$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 60);
$redis->auth('123456') or die("redis 连接失败");//Redis密码

function getRoomUsers($redis, $roomid) {
    $users = [];
    $entries = $redis->hGetAll("rtc_roomid_{$roomid}");
    foreach ($entries as $uid => $serialized) {
        $data = unserialize($serialized);
        if (isset($data['uid'])) $users[] = $data['uid'];
    }
    return $users;
}

function broadcastToRoom($ws, $redis, $roomid, $myuid, $message, $excludeFd = null) {
    $entries = $redis->hGetAll("rtc_roomid_{$roomid}");
    foreach ($entries as $uid => $serialized) {
        if ($uid == $myuid) continue;
        $fd = $redis->get("rtc_uid_{$uid}");
        if ($fd && $fd != $excludeFd) {
            $ws->push($fd, json_encode($message));
        }
    }
}

function unicastToUser($ws, $redis, $targetUid, $message) {
    $fd = $redis->get("rtc_uid_{$targetUid}");
    if ($fd) {
        $ws->push($fd, json_encode($message));
        return true;
    }
    return false;
}

function registerFdMapping($redis, $fd, $uid, $roomid) {
    $redis->hset("rtc_fd_{$fd}", 'uid', $uid);
    $redis->hset("rtc_fd_{$fd}", 'roomid', $roomid);
}

function clearFdMapping($redis, $fd) {
    $redis->del("rtc_fd_{$fd}");
}

$ws->on('open', function ($ws, $request) {
    //echo "New connection fd={$request->fd}\n";
});

$ws->on('message', function ($ws, $frame) use ($redis) {
    $data = json_decode($frame->data);
    if (!$data) {
        return;
    }

    $type = $data->type ?? '';
    $roomid = $data->roomid ?? '';
    $myuid = $data->myuid ?? '';
    $targetUid = $data->targetUid ?? null;
    switch ($type) {
        case 'addroom':
            $redis->hset("rtc_roomid_{$roomid}", $myuid, serialize(['uid' => $myuid]));
            $redis->set("rtc_uid_{$myuid}", $frame->fd);
            registerFdMapping($redis, $frame->fd, $myuid, $roomid);
            $users = getRoomUsers($redis, $roomid);
            $ws->push($frame->fd, json_encode(['type' => 'room_users', 'users' => $users]));
            broadcastToRoom($ws, $redis, $roomid, $myuid, ['type' => 'new_peer', 'uid' => $myuid]);
            break;

        case 'leave':
            $redis->hdel("rtc_roomid_{$roomid}", $myuid);
            $redis->del("rtc_uid_{$myuid}");
            clearFdMapping($redis, $frame->fd);
            broadcastToRoom($ws, $redis, $roomid, $myuid, ['type' => 'peer_left', 'uid' => $myuid]);
            break;

        case 'ping':
            // 忽略,仅保持连接
            break;

        case 'offer':
        case 'answer':
        case 'ice':
            // 优先单播
            if ($targetUid) {
                unicastToUser($ws, $redis, $targetUid, (array)$data);
            } else {
                // 降级广播(保留兼容性)
                broadcastToRoom($ws, $redis, $roomid, $myuid, (array)$data);
            }
            break;

        default:
            broadcastToRoom($ws, $redis, $roomid, $myuid, (array)$data);
            break;
    }
});

$ws->on('close', function ($ws, $fd) use ($redis) {
    $mapping = $redis->hGetAll("rtc_fd_{$fd}");
    if (empty($mapping)) return;
    $uid = $mapping['uid'] ?? null;
    $roomid = $mapping['roomid'] ?? null;
    if ($uid && $roomid) {
        $redis->hdel("rtc_roomid_{$roomid}", $uid);
        $redis->del("rtc_uid_{$uid}");
        clearFdMapping($redis, $fd);
        broadcastToRoom($ws, $redis, $roomid, $uid, ['type' => 'peer_left', 'uid' => $uid]);
    }
});

// 定时清理僵尸连接(每60秒)
Swoole\Timer::tick(60000, function () use ($ws, $redis) {
    $keys = $redis->keys("rtc_fd_*");
    foreach ($keys as $key) {
        $fd = str_replace("rtc_fd_", "", $key);
        $info = $ws->getClientInfo($fd);
        if (!$info || $info['websocket_status'] != WEBSOCKET_STATUS_ACTIVE) {
            $mapping = $redis->hGetAll($key);
            if (isset($mapping['uid']) && isset($mapping['roomid'])) {
                $uid = $mapping['uid'];
                $roomid = $mapping['roomid'];
                $redis->hdel("rtc_roomid_{$roomid}", $uid);
                $redis->del("rtc_uid_{$uid}");
                $redis->del($key);
                broadcastToRoom($ws, $redis, $roomid, $uid, ['type' => 'peer_left', 'uid' => $uid]);
            }
        }
    }
});

$ws->start();

二、STUN/TURN 服务器架设(仅为快速跑通本插件,服务器安全请自行优化处理)

以CentOS/RHEL 系统为例使用 YUM 包管理器安装 

1.安装 EPEL 仓库
sudo yum install epel-release

2.安装 Coturn
sudo yum install coturn

装完成后,需要编辑配置文件
配置文件通常在 【/etc/coturn/turnserver.conf】
找到并编辑【turnserver.conf】在最后一行添加如下配置

no-loopback-peers
response-origin-only-with-rfc5780
listening-port=3478
tls-listening-port=5349
relay-ip=【你服务器IP地址】
relay-threads=50
lt-cred-mech
cert=/etc/coturn/ca.crt
pkey=/etc/coturn/ca.key
realm=【你的域名】(重要。例如:st.myrtc.cn)
min-port=49152
max-port=65535
user=cs123:cs456

注意user=cs123:cs456
分别为
cs123:cs456 
账号:密码
配置完成后保存 防火墙也要开放TCP/UDP 3478和 49152-65535端口

coturn相关命令

sudo systemctl daemon-reload      # 重新加载 systemd 配置
sudo systemctl enable coturn      # 设置开机自启
sudo systemctl start coturn       # 立即启动服务
sudo systemctl status coturn      # 检查服务状态 (coturn安装完成后一定要使用此命令检查)。
返回
确保状态是active (running)

然后前往
https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
测试结果
URL (if present) 显示以下就可以使用了
stun:st.myrtc.cn:3478 
turn:st.myrtc.cn:3478?transport=tcp

三、wss地址以及其他(以宝塔面板为例)

假如我们有一个域名:myrtc.cn (以下均已这个域名为示例)
准备一个二级域名 
例如:rtc.myrtc.cn

1.创建一个站点 rtc.myrtc.cn 》打开网站目录》创建成一个名字为 myrtc.php文件、将上方的PHP代码 复制并保存
具体请自行操作

2.设置网站伪静态(注意域名rtc.myrtc.cn 需要开启 ssl 也就是https://)
location /wsss { 
  proxy_pass http://wsss;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "Upgrade";
}
3.配置文件
在server{}前添加代码

map $http_upgrade $connection_upgrade {  
default upgrade;  ''      close;
}
upstream wsss {
server localhost:9505;
}

server
{
    listen 80;
    listen 443 ssl  ;
........//原配置不要动
}

保存就得到了这样的地址: wss://rtc.myrtc.cn/wsss 

总结

最终会得到如下信息

stun/turn地址: stun:st.myrtc.cn:3478 turn:st.myrtc.cn:3478?transport=tcp

turn账号 cs123 turn密码 cs456

WebSocket地址: wss://rtc.myrtc.cn/wsss

当然还有 Swoole 、Redis 的安装,包括以上不明白的请自行解决,不懂问度娘或各大AI。

隐私、权限声明

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

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-feature android:name="android.hardware.camera" android:required="true" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

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

插件不采集任何数据

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