简介:这是一套面向Android与iOS双端开发者的1对1社交直播应用完整源码,适用于希望快速构建高颜值、高互动性直播平台的中高级开发者及创业团队。资源包含UI设计精美的客户端代码、收徒系统、公会管理模块及运营后台支撑逻辑,覆盖实时音视频通信、用户关系链、虚拟礼物、VIP套餐、地理位置匹配等核心业务场景。压缩包共18519个文件,主体为2501个PNG资源图、2002个Objective-C实现文件(.m)、1061个Java类、789个JSON配置及488个JAR依赖库,总大小973.18MB,结构清晰,模块化程度高,便于二次开发与功能解耦。目前已有1346人学习下载,开发者可直接复用其成熟架构——如基于TTTRTCEngineKit的音视频引擎封装、ILiveSDK集成方案、多端统一的WebSocket信令设计,以及大量已封装的UI组件(如DynamicVideoView、AnchorVideoView、YLTabBar等),显著降低从0搭建直播系统的门槛与试错成本。

1. 数诚1对1直播系统不是“套壳UI”,而是可落地的双端实时互动架构

很多人看到“UI非常漂亮”第一反应是切图堆效果,但数诚1对1直播的真实价值在于:它把高并发低延迟音视频通道、多角色权限隔离(学员/导师/公会管理员/运营后台)、以及收徒关系链的业务状态机,全部封装进一套可编译、可调试、可灰度发布的双端源码中。这不是网页嵌入第三方SDK的轻量方案,而是基于WebRTC+信令服务+自研流控策略构建的闭环系统——iOS和Android端均使用原生渲染层对接MediaStream,Windows/macOS运营后台则通过Electron+FFmpeg WASM实现本地推流监控与数据看板。适合中小教育机构快速搭建自有品牌直播平台,也适合作为技术团队学习实时音视频业务建模的参考样本。如果你正面临“用现成SaaS功能受限、自研又卡在信令同步或首帧延迟上”的困境,这套源码提供的是从 RTCPeerConnection 初始化到 学员加入公会后自动订阅导师流 的全链路实现逻辑。

2. 搭建本地开发环境:从源码解压到双端可联调的最小闭环

2.1 源码结构解析与依赖版本锚定

数诚源码包解压后呈现标准的跨端项目分层:

shu-cheng-live/
├── android/          # Android Studio工程,targetSdkVersion=34,使用Camera2+AudioRecord采集
├── ios/              # Xcode工程,依赖WebRTC.framework(已预编译v114.0.5735.198)
├── web/              # Vue3+TypeScript前端,含讲师控制台/学员观看页/公会管理页
├── server/           # Go语言后端,含信令服务(WebSocket)、流媒体网关(RTMP转WebRTC)、关系服务(收徒/公会)
├── docs/             # 部署手册、API文档、数据库ER图
└── build/            # 各平台打包脚本(含Android签名配置、iOS证书配置模板)

关键依赖版本必须严格匹配,否则出现黑屏/无法建立P2P连接等典型问题:

  • WebRTC SDK:仅兼容 v114.0.5735.198 (高版本因SDP Offer/Answer协商策略变更导致iOS端拒绝连接)
  • Go后端:要求 go1.21.6 (低于此版本无法编译 github.com/pion/webrtc/v3 的ICE候选者处理模块)
  • Vue前端: vue@3.4.15 + pinia@2.1.7 (高版本Pinia的store持久化插件与收徒关系缓存逻辑冲突)

提示:不要直接运行 npm install 或 go mod download ,先执行根目录下的 verify-deps.sh 脚本校验MD5。该脚本会比对 docs/dependency-checksums.txt 中预置的各模块哈希值,避免因网络波动下载到被篡改的依赖包。

2.2 信令服务与流媒体网关的本地启动

信令服务是整个直播系统的“神经中枢”,负责学员与导师间的SDP交换、ICE候选者中继、以及房间状态广播。启动前需配置 server/config.yaml :

signaling:
  bind_addr: ":8080"
  rtc_config:
    ice_servers: 
      - urls: "stun:stun.l.google.com:19302"  # 公共STUN,仅用于NAT类型探测
      - urls: "turn:turn.example.com:3478"   # 生产环境必须替换为自建TURN(见2.3节)
        username: "shucheng"
        credential: "live2024"
media_gateway:
  rtmp_listen: ":1935"
  webrtc_listen: ":8081"
  stream_timeout: "30s"  # 流超时自动清理,防止僵尸连接占用内存

启动命令(需在 server/ 目录下执行):

go run main.go -config ./config.yaml

成功启动后,终端将输出:

INFO[0000] Signaling server listening on :8080
INFO[0000] Media gateway RTMP listener on :1935
INFO[0000] Media gateway WebRTC listener on :8081

此时可通过curl验证信令服务健康状态:

curl -X POST http://localhost:8080/api/v1/room/create \
  -H "Content-Type: application/json" \
  -d '{"room_id":"test_room","creator_id":"mentor_001"}'

返回 {"room_id":"test_room","status":"created"} 即表示信令服务就绪。

2.3 自建TURN服务器解决内网穿透失败问题

当学员与导师处于不同局域网(如家庭宽带+企业防火墙)时,P2P直连大概率失败,必须依赖TURN中继。数诚源码不提供TURN服务,需自行部署Coturn:

# Ubuntu 22.04安装Coturn
sudo apt update && sudo apt install coturn

# 编辑配置 /etc/turnserver.conf
listening-port=3478
tls-listening-port=5349
fingerprint
lt-cred-mech
use-auth-secret
static-auth-secret=shucheng-turn-secret
realm=shucheng.live
total-quota=100
bps-capacity=0
stale-nonce=600
no-multicast-peers
no-cli
log-file=/var/log/turnserver.log
verbose

# 启动服务
sudo systemctl enable coturn
sudo systemctl start coturn

配置生效后,在 server/config.yaml 中将 ice_servers 的TURN地址指向本机:

ice_servers:
  - urls: "turn:localhost:3478"
    username: "mentor_001"  # 用户名需与数据库中导师ID一致
    credential: "shucheng-turn-secret"

注意:Coturn的 static-auth-secret 必须与后端代码中硬编码的密钥完全一致(位于 server/internal/signaling/turn.go 第42行),否则客户端获取的TURN凭证无效,导致所有内网用户黑屏。

3. 双端联调实操:从创建直播间到完成1对1音视频互通

3.1 iOS端接入信令并建立音视频轨道

iOS工程使用 WebRTC.framework 封装了底层音视频处理,开发者只需关注业务层信令交互。关键步骤在 ViewController.swift 中:

// 1. 初始化信令客户端(连接到localhost:8080)
let signalingClient = SignalingClient(url: URL(string: "ws://localhost:8080/ws")!)

// 2. 创建房间并获取Offer(导师端)
signalingClient.createRoom(roomId: "math_101", creatorId: "teacher_zhang") { result in
  switch result {
  case .success(let roomInfo):
    // 3. 创建本地PeerConnection
    let pc = self.createPeerConnection()
    
    // 4. 创建Offer并发送给信令服务
    pc.offer(for: RTCMediaConstraints(mandatory: [:], optional: ["DtlsSrtpKeyAgreement": true])) { offer, error in
      guard let offer = offer else { return }
      self.signalingClient.sendOffer(roomId: "math_101", sdp: offer.sdp) { _ in }
    }
  }
}

// 5. 处理学员发来的Answer(收到信令后调用)
func handleAnswer(sdp: String) {
  let answer = RTCSessionDescription(type: .answer, sdp: sdp)
  peerConnection.setRemoteDescription(answer) { error in
    if let error = error { print("setRemoteDescription failed: \(error)") }
  }
}

参数说明:

  • DtlsSrtpKeyAgreement:true :强制启用DTLS-SRTP加密,避免Chrome 120+浏览器拒绝非加密流
  • RTCSessionDescription.type:.answer :必须严格区分 offer / answer 类型,否则iOS端 setRemoteDescription 会静默失败

3.2 Android端动态权限申请与摄像头预览

Android端需在 MainActivity.kt 中处理运行时权限,尤其注意Android 12+对摄像头/麦克风的特殊限制:

// 检查并请求必要权限
private fun requestPermissions() {
  val permissions = arrayOf(
    Manifest.permission.CAMERA,
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.FOREGROUND_SERVICE
  )
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    // Android 13+需额外申请POST_NOTIFICATIONS
    permissions += Manifest.permission.POST_NOTIFICATIONS
  }
  ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
}

// 权限回调中初始化WebRTC
override fun onRequestPermissionsResult(
  requestCode: Int,
  permissions: Array<out String>,
  grantResults: IntArray
) {
  if (requestCode == PERMISSION_REQUEST_CODE && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
    initWebRTC() // 此方法内部调用createPeerConnection并绑定SurfaceView
  }
}

关键点: SurfaceViewRenderer 必须在 onResume() 中调用 startRendering() ,否则出现“黑屏但有声音”的典型问题。该逻辑位于 VideoCallFragment.kt 第187行。

3.3 Web端学员视角的流订阅与收徒关系绑定

Web端使用Vue3 Composition API管理流状态,核心逻辑在 composables/useRoom.ts 中:

// 学员加入房间时自动订阅导师流
export function useRoom(roomId: string) {
  const remoteStream = ref<MediaStream | null>(null)
  
  // 1. 连接信令服务
  const ws = new WebSocket(`ws://localhost:8080/ws`)
  
  // 2. 收到导师Offer后创建Answer
  ws.addEventListener('message', (e) => {
    const data = JSON.parse(e.data)
    if (data.type === 'offer') {
      const answer = await pc.createAnswer()
      await pc.setLocalDescription(answer)
      ws.send(JSON.stringify({
        type: 'answer',
        roomId,
        sdp: answer.sdp
      }))
    }
  })
  
  // 3. 收到导师流后绑定到video元素
  pc.addEventListener('track', (event) => {
    remoteStream.value = event.streams[0]
    const video = document.getElementById('remote-video') as HTMLVideoElement
    if (video && remoteStream.value) {
      video.srcObject = remoteStream.value
      video.play().catch(e => console.error('Auto-play prevented:', e))
    }
  })
}

收徒关系在此处注入:

// 学员点击“拜师”按钮时触发
const enrollMentor = async () => {
  try {
    const res = await fetch('http://localhost:8080/api/v1/mentor/enroll', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        student_id: 'student_001',
        mentor_id: 'teacher_zhang',
        fee: 29900 // 单位:分
      })
    })
    const data = await res.json()
    if (data.status === 'success') {
      // 成功后自动加入导师的专属直播间(room_id格式:mentor_{id}_private)
      joinRoom(`mentor_teacher_zhang_private`)
    }
  } catch (e) {
    console.error('Enroll failed:', e)
  }
}

4. 公会与运营后台的核心配置项及数据流向

4.1 公会层级模型与数据库表设计

公会功能并非简单“群组聊天”,而是包含三级权限体系:

  • 公会会长 :可创建子公会、审核导师入驻、分配收益比例
  • 公会导师 :可开班授课、设置课程价格、查看本公会学员数据
  • 公会学员 :仅能观看公会内导师直播,不可跨公会访问

对应数据库表(PostgreSQL):

-- 公会主表
CREATE TABLE guilds (
  id SERIAL PRIMARY KEY,
  name VARCHAR(64) NOT NULL,
  owner_id VARCHAR(32) NOT NULL, -- 会长用户ID
  profit_ratio NUMERIC(5,2) DEFAULT 0.3, -- 公会抽成比例(30%)
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 导师-公会关联表(支持一个导师加入多个公会)
CREATE TABLE guild_mentors (
  guild_id INTEGER REFERENCES guilds(id),
  mentor_id VARCHAR(32),
  join_time TIMESTAMPTZ DEFAULT NOW(),
  status VARCHAR(16) DEFAULT 'active', -- active/inactive/banned
  PRIMARY KEY (guild_id, mentor_id)
);

-- 公会收益流水表(用于财务对账)
CREATE TABLE guild_revenue_logs (
  id SERIAL PRIMARY KEY,
  guild_id INTEGER,
  amount BIGINT, -- 分
  source_type VARCHAR(20), -- 'course_fee'/'gift'/'membership'
  order_id VARCHAR(64),
  created_at TIMESTAMPTZ DEFAULT NOW()
);

提示: profit_ratio 字段必须为 NUMERIC(5,2) 类型,若误设为 FLOAT 会导致金额计算出现0.01元误差,引发财务纠纷。

4.2 运营后台的Windows命令行工具集

运营人员无需登录Web后台,可通过Windows命令行快速执行高频操作。工具位于 server/tools/win-cli/ 目录:

工具名称 功能 示例命令
kick_user.exe 踢出违规学员 kick_user.exe --room math_101 --user student_002 --reason "广告刷屏"
refund.exe 课程费原路退款 refund.exe --order O20240520123456 --amount 29900
guild_split.exe 手动拆分公会收益 guild_split.exe --guild 123 --date 2024-05-19

guild_split.exe 执行逻辑:

  1. 查询 guild_revenue_logs 中指定日期的全部流水
  2. 按 profit_ratio 计算公会应得金额(例:30% × 100000分 = 30000分)
  3. 将结果写入 guild_payouts 表,并触发Webhook通知财务系统

该工具使用Go编写,编译后生成单文件可执行程序,无运行时依赖。

4.3 收徒关系的幂等性保障机制

收徒操作必须满足“同一学员对同一导师只能成功一次”,后端通过数据库唯一约束实现:

-- 收徒关系表(关键约束)
CREATE TABLE mentor_enrollments (
  id SERIAL PRIMARY KEY,
  student_id VARCHAR(32) NOT NULL,
  mentor_id VARCHAR(32) NOT NULL,
  fee BIGINT NOT NULL,
  status VARCHAR(16) DEFAULT 'paid', -- paid/refunded/canceled
  created_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE (student_id, mentor_id) -- 核心:防止重复拜师
);

当学员重复点击“拜师”时,数据库抛出 unique_violation 错误,后端捕获后返回:

{
  "code": 409,
  "message": "Already enrolled to this mentor",
  "data": {
    "enrollment_id": 88234,
    "status": "paid"
  }
}

前端据此提示“您已是该导师学员”,而非显示“操作失败”。

5. 延迟优化与匿名截流防护的实战配置

5.1 端到端首帧延迟压测与关键参数调优

在千兆局域网环境下,数诚系统理论首帧延迟应≤800ms。实际压测发现常见瓶颈在三个环节:

环节 默认值 推荐值 效果
WebRTC iceTransportPolicy all relay 强制走TURN,避免P2P协商耗时,首帧降低120ms
iOS AVCaptureSession preset .photo .hd1280x720 降低采集分辨率,CPU占用下降35%,延迟稳定在650ms内
Go信令服务 WriteTimeout 10s 2s 防止慢连接阻塞goroutine,QPS提升2.3倍

修改 ios/Source/VideoCapture.swift 第58行:

// 原代码:session.sessionPreset = .photo
session.sessionPreset = .hd1280x720 // 显式指定720p采集

修改 server/internal/signaling/server.go 第142行:

// 原代码:upgrader.CheckOrigin = func(r *http.Request) bool { return true }
upgrader.WriteTimeout = 2 * time.Second // 关键:写超时从10秒降至2秒

5.2 防止直播内容被匿名截流的技术手段

“直播匿名截流”指第三方工具通过抓取页面JS或模拟信令协议,绕过用户体系盗取直播流。数诚采用三重防护:

  1. 信令Token时效性 :每次创建房间时,后端生成JWT Token,有效期15分钟,且绑定 client_ip 和 user_agent :

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
      "room_id": roomId,
      "user_id": userId,
      "exp": time.Now().Add(15 * time.Minute).Unix(),
      "ip": r.RemoteAddr, // 记录发起请求的IP
      "ua": r.UserAgent(), // 记录User-Agent
    })
    
  2. 流URL动态签名 :RTMP推流地址带时间戳+HMAC签名:

    rtmp://localhost:1935/live/math_101?ts=1716234567&sig=abc123def456
    

    签名算法: HMAC-SHA256("math_101:1716234567", "shucheng_secret_key")

  3. Web端反调试 :在 web/src/main.ts 注入混淆代码:

    // 检测Chrome DevTools是否打开
    const devtools = () => {
      const width = window.outerWidth - window.innerWidth > 160
      const height = window.outerHeight - window.innerHeight > 160
      return width || height
    }
    setInterval(() => {
      if (devtools()) {
        // 触发流中断(向信令服务发送leave_room)
        fetch('/api/v1/room/leave', {method: 'POST'})
      }
    }, 2000)
    

注意: devtools() 检测仅作为辅助手段,不能替代服务端鉴权。真正的安全边界在信令服务的Token校验逻辑( server/internal/signaling/auth.go 第89行),此处必须验证 exp 、 ip 、 ua 三要素完全匹配。

5.3 Windows系统命令行直播推流的标准化流程

运营人员常需用Windows电脑推流至数诚系统,推荐使用OBS Studio配合命令行参数:

:: 启动OBS并自动推流(需提前在OBS设置好场景)
"C:\Program Files\obs-studio\bin\64bit\obs64.exe" ^
  --startrecording ^
  --minimize-to-tray ^
  --collection "ShuCheng_Live" ^
  --profile "1080p_30fps" ^
  --multi-rtmp ^
  --rtmp-url "rtmp://localhost:1935/live/ops_daily?ts=%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%&sig=auto_gen"

:: 等待5秒后检查OBS进程是否存在
timeout /t 5 >nul
tasklist /fi "imagename eq obs64.exe" | findstr /i "obs64.exe" >nul
if %errorlevel% equ 0 (
  echo OBS推流已启动
) else (
  echo OBS启动失败,请检查配置
)

关键点: --multi-rtmp 参数启用多路RTMP输出, --rtmp-url 中的 ts 和 sig 由批处理动态生成,确保每次推流URL唯一,防止被恶意复用。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐