到门口开放平台open.menkou.com

接口说明 / 语音+视频 WebRTC 对接

门铃 / 到门口 · 语音+视频通话前端对接文档


1. Base URL 与鉴权

项 值
Base https://api.menling.com
路径前缀 /message/call/*(短路径 /call/* 同源,建议用完整前缀)
Content-Type application/json; charset=utf-8
鉴权 与发信相同:优先 Authorization: Bearer <JWT>;无效时回落 Cookie rong_auth_cookie
用户 ID body 里 to / destId 支持 门铃号数字 或 N3d 密文(与 send_private 一致)

统一响应壳(与现网 IM 一致):

{ "code": 200, "msg": "ok", "result": "<JSON字符串或[]>" }

注意:result 多数是 JSON 字符串,客户端需再 JSON.parse(result)。
未登录:code 为 "10000"(字符串),msg: "Not loged in."。


2. 枚举

mode(通话模式)

值 含义 一期
1 p2p_audio 两人语音 ✅ 开放
2 p2p_video 两人视频 ✅ 开放
3 group_audio 群语音 预留 → 501
4 group_video 群视频 预留 → 501
5 meeting 会议 预留 → 501
6 live 直播 预留 → 501

mediaType

值 含义 一期
1 audio ✅(配 mode=1)
2 video ✅(配 mode=2)

status(会话状态)

值 含义
0 inviting 邀请中
1 ringing 对方振铃
2 accepted 已接通
3 rejected 拒接
4 hangup 挂断
5 cancelled 主叫取消
6 timeout 超时未接(默认 45s)
7 failed 失败

role(预留给多人/直播)

peer(一期)| host | participant | audience


3. REST 接口(一期必接)

3.1 发起邀请

POST /message/call/invite

语音:

{ "to": 88017, "mediaType": 1, "mode": 1 }

视频:

{ "to": 88017, "mediaType": 2, "mode": 2 }

可选:callId(客户端预生成)、roomId(P2P 可省略)、destId/toUserId(同 to)。
缺 mode 时按 mediaType 推断;缺 mediaType 时按 mode 推断;mode=1/2 会强制对齐 mediaType。

成功 code=200,result 解析后:

{
  "callId": "c....",
  "roomId": null,
  "mode": 1,
  "mediaType": 1,
  "iceServers": [{ "urls": "stun:stun.l.google.com:19302" }],
  "ttlMs": 45000,
  "session": { "callId": "...", "from": 1, "to": 88017, "status": 0, "...": "..." }
}

主叫拿到 callId + iceServers 后:创建等 call.accept → 建 PeerConnection → 发 sdp(offer)**。

3.2 被叫振铃(可选但建议)

POST /message/call/ringing
{ "callId": "c...." }
仅被叫可调;推给主叫事件 call.ringing。

3.3 接听

POST /message/call/accept
{ "callId": "c...." }

成功 result 含 session 字段等价信息 + iceServers。
被叫接听后:建 PC → 等 call.sdp(offer) → setRemoteDescription → 回 sdp(answer)。

3.4 拒接

POST /message/call/reject
{ "callId": "c....", "reason": "reject" }

3.5 挂断 / 取消

POST /message/call/hangup
{ "callId": "c....", "reason": "hangup" }

服务端会往私聊插一条可见 tip,如 [语音通话] 12秒 / [视频通话] 12秒(按 mediaType)。

3.6 SDP 转发

POST /message/call/sdp

{
  "callId": "c....",
  "sdpType": "offer",
  "sdp": "v=0\\r\\n..."
}

sdpType:offer | answer。
不入库(隐私);仅 WS 推给对端 call.sdp。

3.7 ICE candidate 转发

POST /message/call/ice

{
  "callId": "c....",
  "candidate": { "candidate": "candidate:...", "sdpMid": "0", "sdpMLineIndex": 0 },
  "sdpMid": "0",
  "sdpMLineIndex": 0
}

candidate 可为 WebRTC 的 RTCIceCandidateInit 对象或字符串;原样转给对端。

3.8 查询状态

GET /message/call/status?callId=c....

3.9 ICE 服务器

GET /message/call/ice_servers

返回 { iceServers: [...], ttlMs: 3600000 }。
TURN/STUN 以 GET /message/call/ice_servers 返回为准,勿在客户端写死。
生产将换域名/短时凭证;客户端只消费 iceServers 数组。


4. 预留接口(多人 / 会议 / 直播)

均需登录;当前固定:

code=501,msg 含 reserved,result.reserved=true 并带回 acceptedFields。

方法 路径 预留字段
POST /message/call/room_create mode(3–6), mediaType, title, maxParticipants, audienceEnabled, members[]
POST /message/call/room_join roomId, role, callId?
POST /message/call/room_leave roomId, callId?, reason

前端可先封装方法,内部遇 501 提示「暂未开放」。


5. 实时事件(WS / 推送)

在线通道:现有 IM WS(经 9527 HTTP push 注入)。
消息 content 为 JSON 字符串:

{
  "dmkCall": 1,
  "event": "call.invite",
  "v": 1,
  "payload": { }
}

识别:typeof content === 'object' ? content : JSON.parse(content) 后看 dmkCall === 1。

event 谁收到 payload 要点
call.invite 被叫 完整 session(含 from/to/mode/mediaType/callId)
call.ringing 主叫 callId
call.accept 主叫 session + iceServers
call.reject 对端 callId, reason
call.cancel 被叫 callId(主叫未接通取消)
call.hangup 对端 callId, duration, reason
call.timeout 双方 callId, reason=timeout
call.sdp 对端 callId, sdpType, sdp
call.ice 对端 callId, candidate, sdpMid, sdpMLineIndex

离线:邀请/挂断会走现有厂商推送钩子,文案约 [语音通话邀请] / [视频通话邀请](在线优先)。

拉取历史:信令以 object_name=DMK:Call* 入库且 hide,会话列表不展示;挂断 tip 为普通文本。


6. 错误码

code 含义
200 成功
400 参数错误(msg 说明)
"10000" 未登录
403 非通话参与者 / 无权操作
404 call 不存在(进程重启后内存会话丢,以库为准或重新邀请)
409 忙线 / 状态不允许(已接通再 accept 等)
501 预留能力(群/会议/直播;非 P2P 语音/视频)
500 服务异常

7. 推荐状态机(客户端)

Idle
  --invite--> Outgoing ----ringing----> OutgoingRinging
  --收到invite--> Incoming --ringing--> IncomingRinging
Outgoing*/IncomingRinging --accept--> Connecting --ICE/SDP ok--> InCall
任意非 Idle --reject/hangup/cancel/timeout--> Ended --> Idle

超时:服务端 45s 未接自动 call.timeout;客户端可同时本地计时。

语音 vs 视频(客户端差异)

项 语音 mode=1 视频 mode=2
getUserMedia { audio: true } { audio: true, video: true }(可设 facingMode / 分辨率)
PeerConnection tracks 仅 audio audio + video;offer/answer 须带 video m-line
本地预览 无 接听前可选本地预览(前置摄像头);接听后再 addTrack / renegotiate 若预览流未入 PC
远端渲染 AudioTrack → 扬声器 VideoTrack → RemoteView;AudioTrack → 扬声器
权限 RECORD_AUDIO + CAMERA;接通后建议前台服务
UI 入口 资料页/聊天「语音通话」 「视频通话」;参数 mode=2,mediaType=2
切换 本期不做语音↔视频中途升级 同左;要升档需重新 invite

权限:接通后建议前台服务(可复用旧 CallForegroundService 壳,换 WebRTC)。


8. 最小联调顺序(两人同网)

8.1 语音(mode=1)

  1. A:POST invite {"to":<门铃号>,"mode":1,"mediaType":1} → 得 callId、iceServers
  2. B:WS 收到 call.invite → POST ringing → UI 振铃
  3. B:POST accept
  4. A:WS call.accept → getUserMedia({audio:true}) → PeerConnection(iceServers) → addTrack → createOffer → POST sdp
  5. B:WS call.sdp(offer) → setRemote → createAnswer → POST sdp
  6. 双方:onicecandidate → POST ice;对端 addIceCandidate
  7. 有声音后任一方 POST hangup
  8. 查库:av_call_session / av_log 有 invite→accept→hangup

8.2 视频(mode=2)— 在 8.1 之上

  1. A:POST invite {"to":<门铃号>,"mode":2,"mediaType":2} → 得 callId、iceServers(字段与语音相同,注意 mode/mediaType=2)
  2. B:WS call.invite(payload.mode=2)→ 视频来电 UI;可选本地预览 getUserMedia({audio:true,video:true})
  3. B:POST accept
  4. A:getUserMedia({audio:true,video:true}) → 本地预览绑 LocalView → PC addTrack(audio+video) → createOffer(SDP 含 video)→ POST sdp
  5. B:收到 offer → setRemote → addTrack → createAnswer → POST sdp;远端 ontrack 绑 RemoteView
  6. ICE 同语音;确认双向画面+声音
  7. POST hangup;tip 应为 [视频通话] …
  8. 负例:mode=3 或 room_create 须拿到 code=501

9. curl 烟测示例

# 需有效 JWT
TOK='Bearer eyJ...'
API=https://api.menling.com

# 语音
curl -sS -X POST "$API/message/call/invite" \
  -H "Authorization: $TOK" -H "Content-Type: application/json" \
  -d '{"to":88017,"mediaType":1,"mode":1}'

# 视频(期望 code=200 + mode=2 + iceServers)
curl -sS -X POST "$API/message/call/invite" \
  -H "Authorization: $TOK" -H "Content-Type: application/json" \
  -d '{"to":88017,"mediaType":2,"mode":2}'

curl -sS "$API/message/call/ice_servers" -H "Authorization: $TOK"

# 群/会议仍 501
curl -sS -X POST "$API/message/call/invite" \
  -H "Authorization: $TOK" -H "Content-Type: application/json" \
  -d '{"to":88017,"mediaType":2,"mode":4}'
curl -sS -X POST "$API/message/call/room_create" \
  -H "Authorization: $TOK" -H "Content-Type: application/json" \
  -d '{"mode":5,"title":"周会"}'
# 期望 code=501 reserved

10. App 改造清单(给中国宇宙研究院)

  1. 卸/隐藏 RongCallKit 入口(Android 仓库已藏;iOS 见 im-webrtc-work/patches/ios-hide-callkit.md)。
  2. 引入 Google WebRTC(Android org.webrtc:google-webrtc / iOS GoogleWebRTC)。
  3. 新建 CallSignalingClient:封装上表 REST + 解析 WS dmkCall(按 payload.mode 分支语音/视频 UI)。
  4. VoiceCallActivity:仅音频;VideoCallActivity:LocalView/RemoteView + 前后摄像头切换(可选)。
  5. 聊天「+」与资料页:语音调 mode=1,mediaType=1;视频调 mode=2,mediaType=2;不再 RongVoIPIntent。
  6. 勿把 SDP 打到持久日志。

11. 相关文件

路径 说明
.196:[内部路径] 信令实现
.196:[内部路径] 挂载路由
av_call_session 表 可扩展会话
av_log 兼容旧时长/风控
一期语音进度
两人视频进度
方案总览