# 门铃自研会议室 API（给 App / 前端对接）

Date: 2026-10-27（Asia/Shanghai）

> **媒体层自研**：SRS 5 WHIP 推流 + WHEP/HTTP-FLV/HLS 拉流。  
> **不接** 融云 RTC / 腾讯 TRTC / Agora 等第三方会议云。  
> 旧 Java `api.menling.com/meeting/*`（融云 RTC）**暂保留**，新 App 请切到本文档 Base URL。

---

## 1. Base URL

| 项 | 值 |
|----|-----|
| **API Base（自研站）** | `https://meeting.menling.com/api/meeting/` |
| **API Base（开放平台入口）** | `https://open.menkou.com/api/meeting/` |
| 开放平台文档 | `https://open.menkou.com/docs/meeting.html` |
| 官网首页 | `https://meeting.menling.com/` |
| 会议室 | `https://meeting.menling.com/room?id={room_id}` |
| 烟测页 | `https://meeting.menling.com/smoke` |
| 本文档 | `https://meeting.menling.com/MEETING-API.md` |
| 千人架构 | `https://meeting.menling.com/WEBINAR-1K.md` · 源 `/home/meeting/docs/WEBINAR-1K.md` |
| WHIP 推流域 | `https://live.menling.com`（同直播推流边缘） |
| WHEP / FLV / HLS 播域 | `https://zhibo.menling.com`（同直播看播边缘） |
| 业务库 | `daomenkoumenling.meeting` / `meeting_users` / `meeting_block` |

响应统一：

```json
{ "code": 200, "msg": "ok", "data": { } }
```

`code !== 200` 为失败（401 未登录、403 无权限/密码错、404 不存在、410 已结束）。

---

## 2. 鉴权（门铃号 = uid）

App 登录门铃号后，调用会议室 API 时带上其一：

### 推荐：Authorization Bearer

```
Authorization: Bearer {uid}.{token}
```

`token = md5(uid + "|" + authSecret)`。可用 `POST /api/meeting/login` 用门铃号+密码换取；或技术联调时用 `POST /api/meeting/token`。

### 联调：Header 门铃号 + 共享密钥

```
X-Doorbell-Uid: 995298414
X-Meeting-Secret: meeting-menling-selfhost-20261027
```

### Cookie（H5 / 同源）

- `meeting_door={uid}.{token}`（本站登录后下发，域 `.menling.com`）
- 或已有直播站 `zhibo_door`（同样接受）

### 登录换 Token

`POST /api/meeting/login`

```json
{ "doorbell": "10001", "password": "你的密码" }
```

成功 `data`：

```json
{
  "uid": 10001,
  "doorbell": "10001",
  "nickname": "…",
  "token": "32位md5",
  "authorization": "Bearer 10001.32位md5"
}
```

### 技术签发（仅联调）

`POST /api/meeting/token`  
Header: `X-Meeting-Secret: meeting-menling-selfhost-20261027`  
Body: `{ "uid": 10001 }`

---

## 3. 媒体模型（必读）

默认小房间 = **多路独立推流**（mesh）。千人请用 `mode=webinar`（见 §10）：

会议室 mesh：

- 每人推一条流：`meet/{stream}`，`stream = meet_{roomId}_{uid}`
- App：**自己 WHIP 推**，**其他人 WHEP（或 FLV）拉**
- v1 信令：**轮询** `userList`（建议 2–3s），无需融云 RTC 房间

| 用途 | URL 形态 |
|------|----------|
| 自己推流 WHIP | `https://live.menling.com/rtc/v1/whip/?app=meet&stream=meet_{roomId}_{uid}` |
| 拉别人 WHEP | `https://zhibo.menling.com/rtc/v1/whep/?app=meet&stream=meet_{roomId}_{对方uid}` |
| 备选 HTTP-FLV | `https://zhibo.menling.com/meet/meet_{roomId}_{uid}.flv` |
| 备选 HLS | `https://zhibo.menling.com/meet/meet_{roomId}_{uid}.m3u8` |

WebRTC 用法（浏览器 / WebView）：

1. `getUserMedia` → `RTCPeerConnection`
2. `POST` WHIP URL，`Content-Type: application/sdp`，body = local SDP offer；响应 = answer SDP
3. 拉流同理 POST WHEP
4. 可参考直播站 SDK：`https://zhibo.menling.com/assets/srs/srs.sdk.js`

原生 App：用系统 WebRTC 或 SRS 官方示例对接 WHIP/WHEP 即可。

---

## 4. 接口一览

| Method | Path | 说明 |
|--------|------|------|
| GET/POST | `/api/meeting/health` | 健康检查 |
| POST | `/api/meeting/login` | 门铃号登录换 token |
| POST | `/api/meeting/token` | 联调签发 token |
| GET/POST | `/api/meeting/me` | 当前用户 |
| POST | `/api/meeting/create` | 创建会议室（主持人） |
| POST | `/api/meeting/join` | 加入 |
| GET/POST | `/api/meeting/list` | 我的会议室列表 |
| GET/POST | `/api/meeting/userList` | 房内成员 + 各路播流 URL |
| GET/POST | `/api/meeting/info` | 房间摘要（可不登录） |
| POST | `/api/meeting/exit` | 离开 |
| POST | `/api/meeting/end` | 主持人结束 |
| POST | `/api/meeting/kick` | 主持人踢人 |
| POST | `/api/meeting/block` | 主持人封禁 |
| POST | `/api/meeting/raiseHand` | 研讨会：观众举手 / 取消 |
| POST | `/api/meeting/approveSpeak` | 研讨会：批准/取消连麦（升嘉宾） |
| POST | `/api/meeting/muteAll` | 主持人/联席：全员静音 |
| POST | `/api/meeting/setRole` | 设置角色 host/guest/audience/cohost |

角色：`role=0` 主持人，`role=1` 嘉宾/参会者，`role=2` 观众（仅 webinar），`role=3` 联席主持。

`create` 可选 `mode=meeting|webinar`（默认 meeting，**兼容旧客户端**）。千人研讨会见下文 §10 与 [WEBINAR-1K.md](./WEBINAR-1K.md)。

---

## 5. 请求 / 响应示例

### 5.1 create

```http
POST /api/meeting/create
Authorization: Bearer 10001.xxxxx
Content-Type: application/json

{ "meeting_theme": "周会", "password": "", "mode": "meeting" }
```

可选：`password` 非空则 `is_password=1`。  
可选：`mode`=`meeting`（默认，mesh 小房间）| `webinar`（千人研讨会）。  
webinar 还可传 `audience_mode`=`hls_flv`（默认）|`hls`|`flv`，`max_guests`（默认 16）。

成功摘要：

```json
{
  "code": 200,
  "msg": "ok",
  "data": {
    "room_id": 8000041,
    "meeting_theme": "周会",
    "is_password": 0,
    "share_code": "A1B2C3D4",
    "platform": "SELF",
    "media_mode": "SRS_MESH",
    "role": 0,
    "host_uid": 10001,
    "self": {
      "uid": 10001,
      "nickname": "…",
      "role": 0,
      "publish": {
        "whip": "https://live.menling.com/rtc/v1/whip/?app=meet&stream=meet_8000041_10001",
        "rtmp": "rtmp://live.menling.com:1935/meet/meet_8000041_10001",
        "app": "meet",
        "stream": "meet_8000041_10001"
      },
      "play": {
        "whep": "https://zhibo.menling.com/rtc/v1/whep/?app=meet&stream=meet_8000041_10001",
        "http_flv": "https://zhibo.menling.com/meet/meet_8000041_10001.flv",
        "hls": "https://zhibo.menling.com/meet/meet_8000041_10001.m3u8"
      }
    },
    "signaling": { "mode": "poll", "userList": "/api/meeting/userList", "interval_ms": 3000 }
  }
}
```

### 5.2 join

```json
{ "room_id": 8000041, "password": "" }
```

也可用 `{ "share_code": "A1B2C3D4" }`（有 share_code 列时）。

成功 `data` 含：

- `role`、`self.publish` / `self.play`
- `participants[]`：每位成员的 `play`（WHEP/FLV/HLS）；自己额外带 `publish`

### 5.3 userList

```json
{ "room_id": 8000041 }
```

```json
{
  "code": 200,
  "data": {
    "room_id": 8000041,
    "is_end": 0,
    "host_uid": 10001,
    "count": 2,
    "participants": [
      {
        "user_id": 10001,
        "nickname": "…",
        "role": 0,
        "is_self": 1,
        "stream": "meet_8000041_10001",
        "publish": { "whip": "…" },
        "play": { "whep": "…", "http_flv": "…", "hls": "…" }
      },
      {
        "user_id": 1,
        "nickname": "…",
        "role": 1,
        "is_self": 0,
        "stream": "meet_8000041_1",
        "play": { "whep": "…", "http_flv": "…", "hls": "…" }
      }
    ]
  }
}
```

**App 逻辑**：对 `is_self=0` 的成员建立 WHEP 拉流；自己保持 WHIP 推流。成员增减以 `userList` 为准。

### 5.4 list

```json
{ "active": 1, "limit": 20 }
```

返回我主持或曾加入、且（默认）未结束的房间列表。

### 5.5 exit / end / kick / block

```json
// exit
{ "room_id": 8000041 }

// end（仅主持人）
{ "room_id": 8000041 }

// kick（仅主持人）
{ "room_id": 8000041, "user_id": 1 }

// block（仅主持人；time=分钟，0=永久）
{ "room_id": 8000041, "user_id": 1, "time": 60 }
```

---

## 6. App 接入步骤（建议）

1. 用户门铃登录 → 拿 `Authorization: Bearer …`（或沿用你们现有会话后换成本站 token）
2. `create` 或 `join` → 拿到 `self.publish.whip` 与 `participants[].play.whep`
3. 本地 WHIP 推音视频
4. 每 2–3 秒 `userList`：新成员开 WHEP，离开的关 PeerConnection；`is_end=1` 则退出 UI
5. 离开调 `exit`；主持人散会调 `end`
6. IM 邀请仍可用现有融云/自有 IM 发 `room_id` / `share_code`（**只替换媒体房间**）

---



---

## 10. 千人研讨会（mode=webinar）

产品：**主讲在讲、大家听**；观众端与直播看播相同（HLS/HTTP-FLV）。自建 SRS，不接腾讯云。

架构详见：[WEBINAR-1K.md](./WEBINAR-1K.md) · 开放站 https://open.menkou.com/docs/meeting.html（千人章节）

### 10.1 角色与媒体

| role | 名称 | 推流 | 拉流 |
|------|------|------|------|
| 0 | host | WHIP 个人流 + **节目流** `webinar_{roomId}` | WHEP 嘉宾 |
| 1 | guest | WHIP 个人流 | WHEP 主持/嘉宾 |
| 2 | audience | 无 | **仅** `program` 的 HLS/FLV |
| 3 | cohost | 同嘉宾 | 同嘉宾 + 可审批 |

节目流（观众，形态同直播）：

```
https://zhibo.menling.com/meet/webinar_{roomId}.flv
https://zhibo.menling.com/meet/webinar_{roomId}.m3u8
```

主持推节目流：

```
https://live.menling.com/rtc/v1/whip/?app=meet&stream=webinar_{roomId}
```

### 10.2 create webinar

```http
POST https://open.menkou.com/api/meeting/create
Authorization: Bearer 10001.xxxxx
Content-Type: application/json

{
  "meeting_theme": "千人分享会",
  "mode": "webinar",
  "audience_mode": "hls_flv",
  "max_guests": 16,
  "password": ""
}
```

成功要点：`mode=webinar`，`media_mode=SRS_WEBINAR`，`self.publish` + `self.publish_program`，`program.http_flv` / `program.hls`。

### 10.3 join（默认观众）

```json
{ "room_id": 8000100 }
```

观众：`role=2`，`can_publish=0`，`publish=null`，`play`/`program` 为 HLS/FLV。  
已被批准的嘉宾重进会保留 `role=1` 并带回 WHIP/WHEP。

### 10.4 raiseHand / approveSpeak / muteAll / setRole

```json
// 观众举手
POST /api/meeting/raiseHand
{ "room_id": 8000100, "raise": 1 }

// 主持批准连麦 → 升为嘉宾（返回 media.publish / play）
POST /api/meeting/approveSpeak
{ "room_id": 8000100, "user_id": 10002, "approve": 1 }

// 全员静音（主持除外）
POST /api/meeting/muteAll
{ "room_id": 8000100, "mute": 1 }

// 设联席主持
POST /api/meeting/setRole
{ "room_id": 8000100, "user_id": 10003, "role": "cohost" }
```

`userList` 在 webinar 下额外返回 `program`、`hand_raises[]`、`mute_all`；观众行不附带每人 play URL（防千人包体膨胀）。

### 10.5 App 建议（webinar）

1. 主持：`create mode=webinar` → WHIP 推 `publish_program.whip`（主画面）  
2. 观众：`join` → 只播 `program.http_flv` 或 `program.hls`（同直播播放器）  
3. 观众举手 → 主持 `approveSpeak` → 客户端改走 WHIP/WHEP  
4. 轮询 `userList` 建议 5s  

---
## 7. 错误码

| code | 含义 |
|------|------|
| 200 | 成功 |
| 400 | 参数错误 |
| 401 | 未登录 / token 无效 |
| 403 | 密码错 / 非主持人 / 被封禁 / Secret 错 |
| 404 | 房间或接口不存在 |
| 410 | 会议已结束 |
| 500 | 服务端错误 |

---

## 8. 与旧接口关系

| | 旧（融云 RTC） | 新（自研） |
|--|----------------|------------|
| 主机 | `https://api.menling.com/meeting/*` | `https://meeting.menling.com/api/meeting/*` |
| 媒体 | RongCloud `/v2/rtc/rooms/` | SRS WHIP/WHEP |
| 身份 | SealTalk cookie | Bearer / X-Doorbell-Uid / meeting_door |
| 表 | 同库 `meeting*` | 同表；自研房 `platform=SELF` |

旧接口**先不要删**，App 发版切换后再下线融云 RTC。

---

## 9. 运维备忘

- 代码根：`/home/meeting`
- 备份与状态：`/home/backup/cleanup/meeting-selfhost-20261027/`
- SRS：本机 `5.0.225`，HTTP-API `1985`，RTC candidate `14.18.194.196`
- Apache：`meeting.menling.com` → php82 `meeting-yii3`；`/rtc/` 代理到 SRS（与 live/zhibo 一致）

