From 31d0b48774e1c32d3b827100cdfe63f65ae42dfc Mon Sep 17 00:00:00 2001
From: langhuihui <178529795@qq.com>
Date: Tue, 11 Nov 2025 13:02:56 +0800
Subject: [PATCH] feat: add redirectAdvisor to rtsp plugin
---
plugin/hls/README.md | 160 +++++
plugin/hls/README_CN.md | 158 +++++
plugin/rtmp/pkg/net-connection.go | 2 +-
plugin/rtsp/index.go | 27 +-
plugin/rtsp/server.go | 15 +
plugin/webrtc/README.md | 801 +++++++++++++++++++++++++
plugin/webrtc/README_CN.md | 966 ++++++++++++++++++++++++++++++
7 files changed, 2124 insertions(+), 5 deletions(-)
create mode 100644 plugin/hls/README.md
create mode 100644 plugin/hls/README_CN.md
create mode 100644 plugin/webrtc/README.md
create mode 100644 plugin/webrtc/README_CN.md
diff --git a/plugin/hls/README.md b/plugin/hls/README.md
new file mode 100644
index 0000000..6d07c13
--- /dev/null
+++ b/plugin/hls/README.md
@@ -0,0 +1,160 @@
+# HLS Plugin Guide
+
+## Table of Contents
+
+- [Introduction](#introduction)
+- [Key Features](#key-features)
+- [Configuration](#configuration)
+- [Live Playback Endpoints](#live-playback-endpoints)
+- [Pulling Remote HLS Streams](#pulling-remote-hls-streams)
+- [Recording and VOD](#recording-and-vod)
+- [Built-in Player Assets](#built-in-player-assets)
+- [Low-Latency HLS (LL-HLS)](#low-latency-hls-ll-hls)
+- [Troubleshooting](#troubleshooting)
+
+## Introduction
+
+The HLS plugin turns Monibuca into an HTTP Live Streaming (HLS) origin, providing on-the-fly segmenting, in-memory caching, recording, download, and proxy capabilities. It exposes classic `.m3u8` playlists under `/hls`, supports time-ranged VOD playback, and can pull external HLS feeds back into the platform. The plugin ships with a pre-packaged `hls.js` demo so you can verify playback instantly.
+
+## Key Features
+
+- **Live HLS output** – Publish any stream in Monibuca and play it back at `http(s)://{host}/hls/{streamPath}.m3u8` with configurable segment length/window.
+- **Adaptive in-memory cache** – Keeps the latest `Window` segments in memory; default `5s x 3` produces ~15 seconds of rolling content.
+- **Remote HLS ingestion** – Pull `.m3u8` feeds, repackage the TS segments, and republish them through Monibuca.
+- **Recording workflows** – Record HLS segments to disk, start/stop via config or REST API, and export TS/MP4/FMP4 archives.
+- **Time-range download** – Generate stitched TS files from historical recordings or auto-convert MP4/FMP4 archives when TS is unavailable.
+- **VOD playlists** – Serve historical playlists under `/vod/{streamPath}.m3u8` with support for `start`, `end`, or `range` queries.
+- **Bundled player** – Access `hls.js` demos (benchmark, metrics, light UI) directly from the plugin without external hosting.
+
+## Configuration
+
+Basic YAML example:
+
+```yaml
+hls:
+ onpub:
+ transform:
+ ^live/.+: 5s x 3 # fragment duration x playlist window
+ record:
+ ^live/.+:
+ filepath: record/$0
+ fragment: 1m # split TS files every minute
+ pull:
+ live/apple-demo:
+ url: https://devstreaming-cdn.apple.com/.../prog_index.m3u8
+ relaymode: mix # remux (default), relay, or mix
+ onsub:
+ pull:
+ ^vod_hls_\d+/(.+)$: $1 # lazy pull VOD streams on demand
+```
+
+### Transform Input Format
+
+`onpub.transform` accepts a string literal `{fragment} x {window}`. For example `5s x 3` tells the transformer to create 5-second segments and retain 3 completed segments (plus one in-progress) in-memory. Increase the window to improve DVR length; decrease it to lower latency.
+
+### Recording Options
+
+`onpub.record` uses the shared `config.Record` structure:
+
+- `fragment` – TS file duration (`1m`, `10s`, etc.).
+- `filepath` – Directory pattern; `$0` expands to the matched stream path.
+- `realtime` – `true` writes segments as they are generated; `false` caches in memory and flushes when the segment closes.
+- `append` – Append to existing files instead of creating new ones.
+
+You can also trigger recordings dynamically via the HTTP API (see [Recording and VOD](#recording-and-vod)).
+
+### Pull Parameters
+
+`hls.pull` entries inherit from `config.Pull`:
+
+- `url` – Remote playlist URL (HTTPS recommended).
+- `maxretry`, `retryinterval` – Automatic retry behaviour (default 5 seconds between attempts).
+- `proxy` – Optional HTTP proxy.
+- `header` – Extra HTTP headers (cookies, tokens, user agents).
+- `relaymode` – Choose how TS data is handled:
+ - `remux` (default) – Decode TS to Monibuca tracks only; segments are regenerated.
+ - `relay` – Cache raw TS in memory and skip remux/recording.
+ - `mix` – Remux for playback and keep a copy of each TS segment in memory (best for redistributing the original segments).
+
+## Live Playback Endpoints
+
+- `GET /hls/{streamPath}.m3u8` – Rolling playlist. Add `?timeout=30s` to wait for a publisher to appear; the plugin auto-subscribes internally during the wait window.
+- `GET /hls/{streamPath}/{segment}.ts` – TS segments held in memory or read from disk when available.
+- `GET /hls/{resource}` – Any other path (e.g. `index.html`, `hls.js`) is served from the embedded `hls.js.zip` archive.
+
+When Monibuca listens on standard ports, `PlayAddr` entries like `http://{host}/hls/live/demo.m3u8` and `https://{host}/hls/live/demo.m3u8` are announced automatically.
+
+## Pulling Remote HLS Streams
+
+1. Configure a `pull` block under the `hls` section or use the unified API:
+ ```bash
+ curl -X POST http://localhost:8080/api/stream/pull \
+ -H "Content-Type: application/json" \
+ -d '{
+ "protocol": "hls",
+ "streamPath": "live/apple-demo",
+ "remoteURL": "https://devstreaming-cdn.apple.com/.../prog_index.m3u8"
+ }'
+ ```
+2. The puller fetches the primary playlist, optionally follows variant streams, downloads the newest TS segments, and republishes them to Monibuca.
+3. Choose `relaymode: mix` if you need the original TS bytes for downstream consumers (`MemoryTs` keeps a rolling window of segments).
+
+Progress telemetry is available through the task system with step names `m3u8_fetch`, `parse`, and `ts_download`.
+
+## Recording and VOD
+
+### Start/Stop via REST
+
+- `POST /hls/api/record/start/{streamPath}?fragment=30s&filePath=record/live` – Starts a TS recorder and returns a numeric task ID.
+- `POST /hls/api/record/stop/{id}` – Stops the recorder (`id` is the value returned from the start call).
+
+If no recorder exists for the same `streamPath` + `filePath`, the plugin creates one and persists metadata in the configured database (if enabled).
+
+### Time-Range Playlists
+
+- `GET /vod/{streamPath}.m3u8?start=2024-12-01T08:00:00&end=2024-12-01T09:00:00`
+- `GET /vod/{streamPath}.m3u8?range=1700000000-1700003600`
+
+The handler looks up matching `RecordStream` rows (`ts`, `mp4`, or `fmp4`) and builds a playlist that points either to existing files or to `/mp4/download/{stream}.fmp4?id={recordID}` for fMP4 archives.
+
+### TS Download API
+
+`GET /hls/download/{streamPath}.ts?start=1700000000&end=1700003600`
+
+- If TS recordings exist, the plugin stitches them together, skipping duplicate PAT/PMT packets after the first file.
+- When only MP4/FMP4 recordings are available, it invokes the MP4 demuxer, converts samples to TS on the fly, and streams the result to the client.
+- Responses set `Content-Type: video/mp2t` and `Content-Disposition: attachment` so browsers download the merged file.
+
+## Built-in Player Assets
+
+Static assets bundled in `hls.js.zip` are served directly from `/hls`. Useful entry points:
+
+- `/hls/index.html` – Full-featured `hls.js` demo.
+- `/hls/index-light.html` – Minimal UI variant.
+- `/hls/basic-usage.html` – Step-by-step example demonstrating basic playback controls.
+- `/hls/metrics.html` – Visualise playlist latency, buffer length, and network metrics.
+
+These pages load the local `/hls/hls.js` script, making it easy to sanity-check streams without external CDNs.
+
+## Low-Latency HLS (LL-HLS)
+
+The same package registers an `llhls` plugin for Low-Latency HLS output. Enable it by adding a transform:
+
+```yaml
+llhls:
+ onpub:
+ transform:
+ ^live/.+: 1s x 7 # duration x segment count (SegmentMinDuration x SegmentCount)
+```
+
+LL-HLS exposes playlists at `http(s)://{host}/llhls/{streamPath}/index.m3u8` and keeps a `SegmentMinDuration` and `SegmentCount` tuned for sub-two-second glass-to-glass latency. The muxer automatically maps H.264/H.265 video and AAC audio using `gohlslib`.
+
+## Troubleshooting
+
+- **Playlist stays empty** – Confirm the publisher is active and that `onpub.transform` matches the stream path. Use `?timeout=30s` on the playlist request to give the server time to subscribe.
+- **Segments expire too quickly** – Increase the transform window (e.g. `5s x 6`) or switch pull jobs to `relaymode: mix` to preserve original TS segments longer.
+- **Download returns 404** – Ensure the database is enabled and recording metadata exists; the plugin relies on `RecordStream` entries to discover files.
+- **Large time-range downloads stall** – The downloader streams sequentially; consider slicing the range or moving recordings to faster storage.
+- **Access from browsers** – The `/hls` paths are plain HTTP GET endpoints. Configure CORS or a reverse proxy if you plan to fetch playlists from another origin.
+
+
diff --git a/plugin/hls/README_CN.md b/plugin/hls/README_CN.md
new file mode 100644
index 0000000..8baf28e
--- /dev/null
+++ b/plugin/hls/README_CN.md
@@ -0,0 +1,158 @@
+# HLS 插件指南
+
+## 目录
+
+- [简介](#简介)
+- [核心特性](#核心特性)
+- [配置示例](#配置示例)
+- [直播播放接口](#直播播放接口)
+- [拉取远端 HLS 流](#拉取远端-hls-流)
+- [录像与点播](#录像与点播)
+- [内置播放器资源](#内置播放器资源)
+- [低延迟 HLS(LL-HLS)](#低延迟-hlsll-hls)
+- [故障排查](#故障排查)
+
+## 简介
+
+HLS 插件让 Monibuca 具备完整的 HTTP Live Streaming 产出能力:实时切片、内存缓存、录像、下载、代理等功能一应俱全。它会在 `/hls` 下暴露标准的 `.m3u8` 播放列表,支持携带时间范围参数播放历史录像,还能将外部 HLS 流拉回 Monibuca。插件自带打包好的 `hls.js` 演示页面,可直接验证播放效果。
+
+## 核心特性
+
+- **直播 HLS 输出**:任何进驻 Monibuca 的流都可以在 `http(s)://{host}/hls/{streamPath}.m3u8` 访问,分片长度与窗口可配置。
+- **内存缓存**:默认配置 `5s x 3`,即 5 秒分片、保留 3 个完整分片,形成约 15 秒滚动缓存,可按需调整。
+- **远端 HLS 拉流**:拉取外部 `.m3u8`,重新封装 TS 分片并再次发布。
+- **录像流程**:可录制 TS,支持通过配置或 REST API 启停,并导出 TS/MP4/FMP4 文件。
+- **时间段下载**:根据录制记录拼接 TS,若不存在 TS 则自动将 MP4/FMP4 转码为 TS 并下发。
+- **点播播放列表**:`/vod/{streamPath}.m3u8` 支持 `start`、`end`、`range` 等查询参数,快速构建点播列表。
+- **自带播放器**:插件打包常用的 `hls.js` Demo(benchmark、metrics、light 等),无需额外托管即可测试。
+
+## 配置示例
+
+```yaml
+hls:
+ onpub:
+ transform:
+ ^live/.+: 5s x 3 # 分片时长 x 播放列表窗口
+ record:
+ ^live/.+:
+ filepath: record/$0
+ fragment: 1m # 每 1 分钟切一个 TS 文件
+ pull:
+ live/apple-demo:
+ url: https://devstreaming-cdn.apple.com/.../prog_index.m3u8
+ relaymode: mix # remux(默认)、relay 或 mix
+ onsub:
+ pull:
+ ^vod_hls_\d+/(.+)$: $1 # 拉取按需点播资源
+```
+
+### Transform 的输入格式
+
+`onpub.transform` 接受字符串 `{分片时长} x {窗口大小}`,例如 `5s x 3` 表示生成 5 秒分片并保留 3 个已完成分片(外加一个进行中的分片)。窗口越大,类 DVR 时长越长;窗口越小,首屏延迟越低。
+
+### 录像参数
+
+`onpub.record` 复用全局的 `config.Record` 结构:
+
+- `fragment`:TS 文件时长(`1m`、`10s` 等)。
+- `filepath`:存储目录,`$0` 会替换为匹配到的流路径。
+- `realtime`:`true` 代表实时写入文件;`false` 表示先写入内存,分片结束后再落盘。
+- `append`:是否向已有文件追加内容。
+
+也可通过 HTTP 接口动态开启或停止录像,详见[录像与点播](#录像与点播)。
+
+### 拉流参数
+
+`hls.pull` 使用 `config.Pull`,常用字段如下:
+
+- `url`:远端播放列表 URL(建议 HTTPS)。
+- `maxretry`、`retryinterval`:断开重试策略(默认每 5 秒重试一次)。
+- `proxy`:可选的 HTTP 代理。
+- `header`:自定义请求头(Cookie、Token、UA 等)。
+- `relaymode`:TS 处理方式:
+ - `remux`(默认)—— 只解复用 TS,按 Monibuca 内部轨道重新封装。
+ - `relay` —— 保留原始 TS 分片,跳过解复用/录制。
+ - `mix` —— 既重新封装供播放,又保留 TS 分片,便于下游复用。
+
+## 直播播放接口
+
+- `GET /hls/{streamPath}.m3u8`:实时播放列表,可携带 `?timeout=30s` 等参数等待发布者上线(等待期间插件会自动内部订阅)。
+- `GET /hls/{streamPath}/{segment}.ts`:TS 分片,会从内存缓存或磁盘读取。
+- `GET /hls/{resource}`:访问 `hls.js.zip` 中的静态资源,例如 `index.html`、`hls.js`。
+
+若使用标准端口监听,插件会自动在 `PlayAddr` 中登记 `http://{host}/hls/live/demo.m3u8` 与 `https://{host}/hls/live/demo.m3u8` 等地址。
+
+## 拉取远端 HLS 流
+
+1. 在配置中新增 `pull` 项,或调统一接口:
+ ```bash
+ curl -X POST http://localhost:8080/api/stream/pull \
+ -H "Content-Type: application/json" \
+ -d '{
+ "protocol": "hls",
+ "streamPath": "live/apple-demo",
+ "remoteURL": "https://devstreaming-cdn.apple.com/.../prog_index.m3u8"
+ }'
+ ```
+2. 拉流器会抓取主播放列表,必要时跟进多码率分支,下载最新 TS 分片并在 Monibuca 中重新发布。
+3. 若需要最大限度保留原始 TS,可设置 `relaymode: mix`;插件会在 `MemoryTs` 中维持滚动分片缓存。
+
+任务进度可通过任务系统查看,关键步骤名称包括 `m3u8_fetch`、`parse`、`ts_download`。
+
+## 录像与点播
+
+### REST 启停
+
+- `POST /hls/api/record/start/{streamPath}?fragment=30s&filePath=record/live`:启动 TS 录像,返回任务指针 ID。
+- `POST /hls/api/record/stop/{id}`:停止录像(`id` 为启动接口返回的数值)。
+
+如果同一 `streamPath` + `filePath` 已存在任务会返回错误;插件会在启用数据库时将录像元数据写入 `RecordStream` 表。
+
+### 时间范围播放列表
+
+- `GET /vod/{streamPath}.m3u8?start=2024-12-01T08:00:00&end=2024-12-01T09:00:00`
+- `GET /vod/{streamPath}.m3u8?range=1700000000-1700003600`
+
+处理逻辑会查询数据库中类型为 `ts`、`mp4` 或 `fmp4` 的录像记录,返回一个指向文件路径或 `/mp4/download/{stream}.fmp4?id={recordID}` 的播放列表。
+
+### TS 下载接口
+
+`GET /hls/download/{streamPath}.ts?start=1700000000&end=1700003600`
+
+- 如果存在 TS 录像,会拼接多个分片,并在首个文件后跳过重复 PAT/PMT。
+- 若只有 MP4/FMP4,插件会调用 MP4 解复用器,将样本转为 TS 实时输出。
+- 响应包含 `Content-Type: video/mp2t` 与 `Content-Disposition: attachment`,方便浏览器直接下载。
+
+## 内置播放器资源
+
+`hls.js.zip` 中打包的静态文件可直接从 `/hls` 访问,常用入口:
+
+- `/hls/index.html` —— 全功能 Demo。
+- `/hls/index-light.html` —— 精简界面版本。
+- `/hls/basic-usage.html` —— 入门示例。
+- `/hls/metrics.html` —— 延迟、缓冲等指标可视化。
+
+这些页面加载本地的 `/hls/hls.js`,无需外部 CDN 即可测试拉流。
+
+## 低延迟 HLS(LL-HLS)
+
+同目录还注册了 `llhls` 插件,可生成低延迟播放列表。示例:
+
+```yaml
+llhls:
+ onpub:
+ transform:
+ ^live/.+: 1s x 7 # SegmentMinDuration x SegmentCount
+```
+
+LL-HLS 的访问路径为 `http(s)://{host}/llhls/{streamPath}/index.m3u8`,内部使用 `gohlslib` 将 H.264/H.265 与 AAC 轨道写入低延迟分片,默认总时移小于 2 秒。
+
+## 故障排查
+
+- **播放列表为空**:确认发布者在线,并确保 `onpub.transform` 正则匹配流路径;可在请求中增加 `?timeout=30s` 给予自动订阅时间。
+- **分片过快被清理**:增大窗口(例如 `5s x 6`),或在拉流任务中改用 `relaymode: mix` 以延长原始 TS 的保留时长。
+- **下载返回 404**:确认已启用数据库并存在对应 `RecordStream` 元数据,插件依赖数据库定位文件。
+- **长时间段下载卡顿**:下载流程串行读写,建议拆分时间段或使用更快的存储介质。
+- **浏览器跨域访问**:`/hls` 是标准 HTTP GET 接口,跨域访问需自行配置 CORS 或反向代理。
+
+
diff --git a/plugin/rtmp/pkg/net-connection.go b/plugin/rtmp/pkg/net-connection.go
index 8d6b9c8..6ad33ea 100644
--- a/plugin/rtmp/pkg/net-connection.go
+++ b/plugin/rtmp/pkg/net-connection.go
@@ -472,7 +472,7 @@ func (nc *NetConnection) SendMessage(t byte, msg RtmpMessage) (err error) {
}
func (nc *NetConnection) sendChunk(mem gomem.Memory, head *ChunkHeader, headType byte) (err error) {
- nc.SetWriteDeadline(time.Now().Add(time.Second * 5)) // 设置写入超时时间为5秒
+ nc.SetWriteDeadline(time.Now().Add(time.Second * 10)) // 设置写入超时时间为5秒
head.WriteTo(headType, &nc.chunkHeaderBuf)
defer func(reuse net.Buffers) {
nc.sendBuffers = reuse
diff --git a/plugin/rtsp/index.go b/plugin/rtsp/index.go
index 5f71f78..b1af32d 100644
--- a/plugin/rtsp/index.go
+++ b/plugin/rtsp/index.go
@@ -4,6 +4,7 @@ import (
"fmt"
"net"
"strings"
+ "sync"
task "github.com/langhuihui/gotask"
"m7s.live/v5/pkg/util"
@@ -23,10 +24,16 @@ var _ = m7s.InstallPlugin[RTSPPlugin](m7s.PluginMeta{
type RTSPPlugin struct {
m7s.Plugin
- UserName string `desc:"用户名"`
- Password string `desc:"密码"`
- UdpPort util.Range[uint16] `default:"20001-30000" desc:"媒体端口范围"` //媒体端口范围
- udpPorts chan uint16
+ UserName string `desc:"用户名"`
+ Password string `desc:"密码"`
+ UdpPort util.Range[uint16] `default:"20001-30000" desc:"媒体端口范围"` //媒体端口范围
+ udpPorts chan uint16
+ advisorOnce sync.Once
+ redirectAdvisor rtspRedirectAdvisor
+}
+
+type rtspRedirectAdvisor interface {
+ ShouldRedirectRTSP(streamPath, currentHost string) (string, bool)
}
func (p *RTSPPlugin) OnTCPConnect(conn *net.TCPConn) task.ITask {
@@ -52,6 +59,18 @@ func (p *RTSPPlugin) Start() (err error) {
return
}
+func (p *RTSPPlugin) findRedirectAdvisor() rtspRedirectAdvisor {
+ p.advisorOnce.Do(func() {
+ for plugin := range p.Server.Plugins.Range {
+ if advisor, ok := plugin.GetHandler().(rtspRedirectAdvisor); ok {
+ p.redirectAdvisor = advisor
+ break
+ }
+ }
+ })
+ return p.redirectAdvisor
+}
+
// 初始化UDP端口池
func (p *RTSPPlugin) initUDPPortPool() {
if p.UdpPort.Valid() {
diff --git a/plugin/rtsp/server.go b/plugin/rtsp/server.go
index e9b560c..d3c1e0c 100644
--- a/plugin/rtsp/server.go
+++ b/plugin/rtsp/server.go
@@ -113,6 +113,21 @@ func (task *RTSPServer) Go() (err error) {
if rawQuery != "" {
streamPath += "?" + rawQuery
}
+ if advisor := task.conf.findRedirectAdvisor(); advisor != nil {
+ if location, ok := advisor.ShouldRedirectRTSP(streamPath, task.URL.Host); ok {
+ res := &util.Response{
+ StatusCode: http.StatusFound,
+ Status: "Found",
+ Header: textproto.MIMEHeader{
+ "Location": {location},
+ },
+ Request: req,
+ }
+ task.Info("RTSP redirect issued", "location", location, "streamPath", streamPath)
+ _ = task.WriteResponse(res)
+ return nil
+ }
+ }
sender.Subscriber, err = task.conf.Subscribe(task, streamPath)
if err != nil {
res := &util.Response{
diff --git a/plugin/webrtc/README.md b/plugin/webrtc/README.md
new file mode 100644
index 0000000..f4b0ae2
--- /dev/null
+++ b/plugin/webrtc/README.md
@@ -0,0 +1,801 @@
+# WebRTC Plugin Guide
+
+## Table of Contents
+
+- [Introduction](#introduction)
+- [Plugin Overview](#plugin-overview)
+- [Configuration](#configuration)
+- [Basic Usage](#basic-usage)
+- [Publishing](#publishing)
+- [Playing](#playing)
+- [WHIP/WHEP Support](#whipwhep-support)
+- [Advanced Features](#advanced-features)
+- [Docker Notes](#docker-notes)
+- [STUN/TURN Reference](#stunturn-reference)
+- [FAQ](#faq)
+
+## Introduction
+
+WebRTC (Web Real-Time Communication) is an open standard jointly developed by W3C and IETF for real-time audio/video communication in browsers and mobile apps. Key characteristics include:
+
+### Highlights
+
+1. **Peer-to-Peer Communication** – Direct browser-to-browser connections reduce server load and latency.
+2. **Low Latency** – UDP transport and modern codecs deliver millisecond-level latency.
+3. **Adaptive Bitrate** – Automatically adjusts quality based on network conditions.
+4. **NAT Traversal** – ICE negotiation handles NAT/firewall traversal automatically.
+
+### WebRTC Flow
+
+1. **Signaling** – Exchange SDP (Session Description Protocol) and ICE candidates via HTTP/WebSocket, etc.
+2. **ICE Candidate Gathering** – Collect local and remote network candidates.
+3. **Connection Establishment** – Use ICE to traverse NAT and set up the P2P link.
+4. **Media Transport** – Stream encrypted audio/video over SRTP.
+
+### Connection Sequence Diagram
+
+```mermaid
+sequenceDiagram
+ participant Client as Client
+ participant Server as Monibuca Server
+
+ Note over Client,Server: 1. Signaling
+ Client->>Server: POST /webrtc/push/{streamPath}
Body: SDP Offer
+ Server->>Server: Create PeerConnection
+ Server->>Server: Prepare SDP Answer
+ Server->>Client: HTTP 201 Created
Body: SDP Answer
+
+ Note over Client,Server: 2. ICE Exchange
+ Client->>Client: Gather local ICE candidates
+ Server->>Server: Gather local ICE candidates
+ Client->>Server: Send ICE candidates
+ Server->>Client: Send ICE candidates
+
+ Note over Client,Server: 3. Connectivity Checks
+ Client->>Server: Attempt Host/SRFLX candidates
+ alt Connectivity succeeds
+ Client->>Server: Establish P2P connection
+ Server-->>Client: Confirm connection
+ else Continue negotiation
+ Client->>Server: Send additional candidates
+ Server-->>Client: Return negotiation result
+ end
+
+ Note over Client,Server: 4. Media Transport
+ Client->>Server: Send SRTP media
+ Server-->>Client: Send RTCP feedback
+```
+
+## Plugin Overview
+
+The Monibuca WebRTC plugin is built on Pion WebRTC v4 and provides complete WebRTC publishing/playing capabilities:
+
+- ✅ Publishing via WHIP
+- ✅ Playback via WHEP
+- ✅ Video codecs: H.264, H.265, AV1, VP9
+- ✅ Audio codecs: Opus, PCMA, PCMU
+- ✅ TCP/UDP transport
+- ✅ ICE server configuration
+- ✅ DataChannel fallback
+- ✅ Built-in test pages
+
+## Configuration
+
+### Basic Configuration
+
+Example `config.yaml` snippet:
+
+```yaml
+webrtc:
+ # Optional ICE servers. See "STUN/TURN Reference" for details.
+ iceservers: []
+
+ # Listening port options:
+ # - tcp:9000 (TCP port)
+ # - udp:9000 (UDP port)
+ # - udp:10000-20000 (UDP port range)
+ port: tcp:9000
+
+ # Interval for sending PLI after video packet loss
+ pli: 2s
+
+ # Enable DataChannel fallback when codecs are unsupported
+ enabledc: false
+
+ # MimeType filter; empty means no restriction
+ mimetype:
+ - video/H264
+ - video/H265
+ - audio/PCMA
+ - audio/PCMU
+```
+
+### Parameter Details
+
+#### ICE Servers
+
+Configure the ICE server list for negotiation. See the [STUN/TURN Reference](#stunturn-reference) section for full details and examples.
+
+#### Port Configuration
+
+1. **TCP Port** – Suitable for restrictive firewalls.
+ ```yaml
+ port: tcp:9000
+ ```
+2. **UDP Port** – Lower latency.
+ ```yaml
+ port: udp:9000
+ ```
+3. **UDP Range** – Allocate ports per session.
+ ```yaml
+ port: udp:10000-20000
+ ```
+
+#### PLI Interval
+
+Duration between Picture Loss Indication (PLI) requests, default 2s.
+
+#### DataChannel
+
+Fallback transport for unsupported codecs (e.g., MP4A). Data is sent as FLV over DataChannel.
+
+#### MimeType Filter
+
+Restrict allowed codec types. Leave empty to accept all supported codecs.
+
+#### Public IP Configuration
+
+Required when the server is behind NAT (e.g., Docker, private network).
+
+##### Public IP Workflow
+
+```yaml
+webrtc:
+ publicip: 203.0.113.1 # IPv4 address
+ publicipv6: 2001:db8::1 # Optional IPv6 address
+
+ port: tcp:9000
+ pli: 2s
+ enabledc: false
+```
+
+##### Diagram
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ Public Internet │
+│ │
+│ ┌──────────────┐ │
+│ │ Client │ │
+│ │ │ │
+│ └──────┬───────┘ │
+│ │ │
+│ │ 1. Obtain public address information │
+│ │ (via STUN/TURN if needed) │
+│ │ │
+└─────────┼───────────────────────────────────────────────┘
+ │
+┌─────────▼───────────────────────────────────────────────┐
+│ NAT / Firewall │
+│ │
+│ ┌──────────────┐ │
+│ │ Monibuca │ │
+│ │ Server │ │
+│ │ │ │
+│ │ Private IP: │ │
+│ │ 192.168.1.100 │
+│ │ │ │
+│ │ PublicIP: 203.0.113.1 │
+│ └──────────────┘ │
+│ │
+│ 2. Use PublicIP when creating ICE candidates │
+│ 3. SDP answer contains public address │
+│ 4. Client connects via public address │
+└─────────────────────────────────────────────────────────┘
+```
+
+##### Notes
+
+1. Always configure `publicip` if the server sits behind NAT.
+2. Ensure the IP matches the actual public address.
+3. Verify port forwarding when using Docker or reverse proxies.
+4. Set both IPv4 and IPv6 if dual-stack connectivity is required.
+
+## Basic Usage
+
+### Start the Service
+
+After enabling the WebRTC plugin, Monibuca exposes the following endpoints:
+
+- `POST /webrtc/push/{streamPath}` – WHIP publish endpoint
+- `POST /webrtc/play/{streamPath}` – WHEP playback endpoint
+- `GET /webrtc/test/{name}` – Built-in test pages
+
+### Test Pages
+
+- Publish test: `http://localhost:8080/webrtc/test/publish`
+- Subscribe test: `http://localhost:8080/webrtc/test/subscribe`
+- Screen share test: `http://localhost:8080/webrtc/test/screenshare`
+
+## Publishing
+
+### Using the Test Page
+
+1. Visit `http://localhost:8080/webrtc/test/publish`.
+2. Allow camera/microphone permissions.
+3. Select a camera if multiple devices are available.
+4. The page automatically starts WebRTC publishing.
+
+### Custom Publishing
+
+#### JavaScript Example
+
+```javascript
+const mediaStream = await navigator.mediaDevices.getUserMedia({
+ video: true,
+ audio: true,
+});
+
+const pc = new RTCPeerConnection({
+ // Configure ICE servers if needed, see STUN/TURN reference
+ iceServers: [],
+});
+
+mediaStream.getTracks().forEach(track => {
+ pc.addTrack(track, mediaStream);
+});
+
+const offer = await pc.createOffer();
+await pc.setLocalDescription(offer);
+
+const response = await fetch('/webrtc/push/live/test', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/sdp' },
+ body: offer.sdp,
+});
+
+const answerSdp = await response.text();
+await pc.setRemoteDescription(
+ new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
+);
+```
+
+#### Forcing H.265
+
+```javascript
+const transceiver = pc.getTransceivers().find(
+ t => t.sender.track && t.sender.track.kind === 'video'
+);
+
+if (transceiver) {
+ const capabilities = RTCRtpSender.getCapabilities('video');
+ const h265 = capabilities.codecs.find(
+ c => c.mimeType.toLowerCase() === 'video/h265'
+ );
+ if (h265) {
+ transceiver.setCodecPreferences([h265]);
+ }
+}
+```
+
+Add `?h265` to the test page URL to attempt H.265 publishing: `/webrtc/test/publish?h265`.
+
+### Publish URL Parameters
+
+- `streamPath` – e.g., `live/test`
+- `bearer` – Bearer token for authentication
+
+Example:
+
+```
+POST /webrtc/push/live/test?bearer=token
+```
+
+### Stop Publishing
+
+```javascript
+pc.close();
+```
+
+## Playing
+
+### Using the Test Page
+
+1. Ensure a stream is already publishing.
+2. Visit `http://localhost:8080/webrtc/test/subscribe?streamPath=live/test`.
+3. The page automatically starts playback.
+
+### Custom Playback
+
+#### JavaScript Example
+
+```javascript
+const pc = new RTCPeerConnection({
+ // Configure ICE servers if needed, see STUN/TURN reference
+ iceServers: [],
+});
+
+pc.ontrack = event => {
+ if (event.streams.length > 0) {
+ videoElement.srcObject = event.streams[0];
+ videoElement.play();
+ }
+};
+
+pc.addTransceiver('video', { direction: 'recvonly' });
+pc.addTransceiver('audio', { direction: 'recvonly' });
+
+const offer = await pc.createOffer();
+await pc.setLocalDescription(offer);
+
+const response = await fetch('/webrtc/play/live/test', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/sdp' },
+ body: offer.sdp,
+});
+
+const answerSdp = await response.text();
+await pc.setRemoteDescription(
+ new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
+);
+```
+
+### Playback URL Parameters
+
+- `streamPath` – e.g., `live/test`
+
+Example:
+
+```
+POST /webrtc/play/live/test
+```
+
+### Stop Playback
+
+```javascript
+pc.close();
+```
+
+## WHIP/WHEP Support
+
+### WHIP (WebRTC-HTTP Ingestion Protocol)
+
+#### Workflow
+
+1. Client creates PeerConnection and Offer.
+2. Client `POST /webrtc/push/{streamPath}` with SDP Offer.
+3. Server returns SDP Answer (HTTP 201 Created).
+4. Client sets Answer.
+5. Media streaming starts.
+
+#### Sequence Diagram
+
+```mermaid
+sequenceDiagram
+ participant Client as Client
+ participant Server as Monibuca Server
+
+ Note over Client,Server: 1. Preparation
+ Client->>Client: getUserMedia()
+ Client->>Client: New RTCPeerConnection
+ Client->>Client: Add tracks
+
+ Note over Client,Server: 2. Create Offer
+ Client->>Client: createOffer()
+ Client->>Client: setLocalDescription()
+ Client->>Client: Gather ICE candidates
+
+ Note over Client,Server: 3. Send Offer
+ Client->>Server: POST /webrtc/push/{streamPath}
+ Server->>Server: Parse Offer
+ Server->>Server: New PeerConnection
+ Server->>Server: setRemoteDescription()
+ Server->>Server: Publish stream
+ Server->>Server: Gather ICE candidates
+
+ Note over Client,Server: 4. Server Answer
+ Server->>Server: createAnswer()
+ Server->>Server: setLocalDescription()
+ Server->>Client: HTTP 201 + SDP Answer
+ Server->>Client: Location: /webrtc/api/stop/push/{streamPath}
+
+ Note over Client,Server: 5. Client Processes Answer
+ Client->>Client: setRemoteDescription()
+ Client->>Client: ICE exchange
+ Server->>Client: ICE exchange
+
+ Note over Client,Server: 6. Connection
+ Client->>Server: ICE connected
+ Server->>Client: ICE connected
+ Client->>Server: Create DataChannel (optional)
+ Server-->>Client: DataChannel confirmation
+
+ Note over Client,Server: 7. Streaming
+ Client->>Server: Send SRTP media
+ Server-->>Client: RTCP feedback
+```
+
+#### Client Example
+
+```javascript
+const pc = new RTCPeerConnection();
+// Add tracks, create offer...
+```
+
+### WHEP (WebRTC HTTP Egress Protocol)
+
+#### Workflow
+
+1. Client creates PeerConnection and Offer (recvonly tracks).
+2. Client `POST /webrtc/play/{streamPath}` with SDP Offer.
+3. Server returns SDP Answer.
+4. Client sets Answer.
+5. Media streaming starts.
+
+#### Sequence Diagram
+
+```mermaid
+sequenceDiagram
+ participant Client as Client
+ participant Server as Monibuca Server
+
+ Note over Client,Server: 1. Preparation
+ Client->>Client: New RTCPeerConnection
+ Client->>Client: Add recvonly transceivers
+ Client->>Client: Listen ontrack
+
+ Note over Client,Server: 2. Create Offer
+ Client->>Client: createOffer()
+ Client->>Client: setLocalDescription()
+ Client->>Client: Gather ICE candidates
+
+ Note over Client,Server: 3. Send Offer
+ Client->>Server: POST /webrtc/play/{streamPath}
+ Server->>Server: Parse Offer
+ Server->>Server: Ensure stream exists
+ alt Stream missing
+ Server->>Client: HTTP 404 Not Found
+ else Stream available
+ Server->>Server: New PeerConnection
+ Server->>Server: setRemoteDescription()
+ Server->>Server: Subscribe to stream
+ Server->>Server: Add tracks
+ Server->>Server: Gather ICE candidates
+
+ Note over Client,Server: 4. Server Answer
+ Server->>Server: createAnswer()
+ Server->>Server: setLocalDescription()
+ Server->>Client: HTTP 200 OK + SDP Answer
+
+ Note over Client,Server: 5. Client Processes Answer
+ Client->>Client: setRemoteDescription()
+ Client->>Client: ICE exchange
+ Server->>Client: ICE exchange
+
+ Note over Client,Server: 6. Connection
+ Client->>Server: ICE connected
+ Server->>Client: ICE connected
+ Client->>Server: DataChannel setup (optional)
+ Server-->>Client: DataChannel confirmation
+
+ Note over Client,Server: 7. Streaming
+ Server->>Client: Send SRTP media
+ Client->>Client: Play media
+ end
+```
+
+## Acting as WHIP/WHEP Client
+
+### Pull via WHEP
+
+```yaml
+pull:
+ streams:
+ - url: https://whep.example.com/play/stream1
+ streamPath: live/stream1
+```
+
+### Push via WHIP
+
+```yaml
+push:
+ streams:
+ - url: https://whip.example.com/push/stream1
+ streamPath: live/stream1
+```
+
+## Advanced Features
+
+### Codec Support
+
+- **Video**: H.264, H.265/HEVC, AV1, VP9
+- **Audio**: Opus, PCMA (G.711 A-law), PCMU (G.711 μ-law)
+
+### DataChannel Transport
+
+Enable DataChannel for unsupported codecs (e.g., MP4A audio) by setting `enabledc: true`. Data is encapsulated in FLV over the DataChannel.
+
+### NAT Traversal
+
+Configure `publicip`/`publicipv6` when running behind NAT; see [Public IP Configuration](#public-ip-configuration).
+
+### Multi-stream Support
+
+Use `http://localhost:8080/webrtc/test/batchv2` to test multi-stream scenarios.
+
+#### BatchV2 Mode
+
+- **Signaling channel**: WebSocket endpoint `ws(s)://{host}/webrtc/batchv2` (upgrade from HTTP).
+- **Initial handshake**:
+ 1. Create a `RTCPeerConnection`, run `createOffer`/`setLocalDescription`.
+ 2. Send `{ "type": "offer", "sdp": "..." }` over the WebSocket.
+ 3. Server replies `{ "type": "answer", "sdp": "..." }`; call `setRemoteDescription`.
+- **Common commands** (all JSON text frames):
+ - `getStreamList`
+ ```json
+ { "type": "getStreamList" }
+ ```
+ Response example: `{ "type": "streamList", "streams": [{ "path": "live/cam1", "codec": "H264", "width": 1280, "height": 720, "fps": 25 }] }`.
+ - `subscribe`
+ ```json
+ {
+ "type": "subscribe",
+ "streamList": ["live/cam1", "live/cam2"],
+ "offer": "SDP..."
+ }
+ ```
+ Server renegotiates and returns `{ "type": "answer", "sdp": "..." }`; call `setRemoteDescription` again.
+ - `unsubscribe` – same structure as `subscribe`, with `streamList` containing the streams to remove.
+ - `publish`
+ ```json
+ {
+ "type": "publish",
+ "streamPath": "live/cam3",
+ "offer": "SDP..."
+ }
+ ```
+ Server responds with a new SDP answer that must be applied client-side.
+ - `unpublish`
+ ```json
+ { "type": "unpublish", "streamPath": "live/cam3" }
+ ```
+ Triggers renegotiation; server returns a fresh answer.
+ - `ping`: `{ "type": "ping" }` keeps the connection alive; server answers with `pong`.
+- **Media scope**: current implementation subscribes video only (`SubAudio` disabled). Extend as needed if audio tracks are required.
+- **Client helper**: `web/BatchV2Client.ts` implements the browser-side workflow; see `webrtc/test/batchv2` for a functional demo (stream list, publish, subscribe management).
+- **Troubleshooting**:
+ - Errors arrive as `{ "type": "error", "message": "..." }`; inspect browser console/WebSocket inspector for details.
+ - Each `subscribe`/`publish` triggers a new SDP cycle; ensure the app performs `setLocalDescription` → send message → `setRemoteDescription` without skipping.
+
+### Connection Monitoring
+
+```javascript
+pc.oniceconnectionstatechange = () => {
+ console.log('ICE State:', pc.iceConnectionState);
+ // new, checking, connected, completed, failed, disconnected, closed
+};
+
+pc.onconnectionstatechange = () => {
+ console.log('Connection State:', pc.connectionState);
+};
+```
+
+## Docker Notes
+
+### 1. Network Mode
+
+Prefer `host` mode:
+
+```bash
+docker run --network host monibuca/monibuca
+```
+
+When using `bridge` mode:
+
+- Map WebRTC ports (TCP/UDP)
+- Configure correct public IP
+- Ensure port mapping matches plugin config
+
+```bash
+docker run -p 8080:8080 -p 9000:9000/udp monibuca/monibuca
+```
+
+### 2. Port Mapping
+
+**TCP mode**
+
+```bash
+docker run -p 8080:8080 -p 9000:9000/tcp monibuca/monibuca
+```
+
+**UDP mode**
+
+```bash
+docker run -p 8080:8080 -p 9000:9000/udp monibuca/monibuca
+```
+
+**UDP range**
+
+```bash
+docker run -p 8080:8080 -p 10000-20000:10000-20000/udp monibuca/monibuca
+```
+
+> Note: Mapping large UDP ranges can be tricky; prefer a single UDP port or `host` mode when possible.
+
+### 3. Public IP
+
+Always set `publicip` when running inside Docker (container IPs are private).
+
+```bash
+curl ifconfig.me
+dig +short myip.opendns.com @resolver1.opendns.com
+```
+
+Example configuration:
+
+```yaml
+webrtc:
+ publicip: 203.0.113.1
+ port: udp:9000
+```
+
+### 4. Docker Compose Example
+
+```yaml
+version: '3.8'
+
+services:
+ monibuca:
+ image: monibuca/monibuca:latest
+ network_mode: host # recommended
+ # Or bridge mode:
+ # ports:
+ # - "8080:8080"
+ # - "9000:9000/udp"
+ volumes:
+ - ./config.yaml:/app/config.yaml
+ - ./logs:/app/logs
+ environment:
+ - PUBLICIP=203.0.113.1
+```
+
+### 5. Common Docker Issues
+
+- **Connection failures** – Configure `publicip`, prefer `host` network, verify port mapping.
+- **Unstable UDP mapping** – Prefer `host` mode or TCP mode (`port: tcp:9000`); inspect firewall rules.
+- **Multiple instances** – Assign different ports (e.g., `tcp:9000`, `tcp:9001`) and map accordingly.
+
+### 6. Best Practices
+
+1. Prefer `host` network for better performance.
+2. Always provide `publicip`/`publicipv6` when behind NAT.
+3. Switch to TCP mode if UDP mapping is problematic.
+4. Monitor WebRTC logs to track connection states.
+5. Configure TURN servers as a fallback to improve success rates.
+
+## STUN/TURN Reference
+
+### Why STUN/TURN Matters
+
+- **STUN (Session Traversal Utilities for NAT)** helps endpoints discover public addresses and ports.
+- **TURN (Traversal Using Relays around NAT)** relays media when direct connectivity fails (e.g., symmetric NAT).
+- STUN is sufficient for most public/home networks; enterprise/mobile scenarios often require TURN fallback.
+
+### Configuration Example
+
+```yaml
+webrtc:
+ iceservers:
+ - urls:
+ - stun:stun.l.google.com:19302
+ - urls:
+ - turn:turn.example.com:3478
+ username: user
+ credential: password
+```
+
+- `urls` accepts multiple entries, mixing `stun:`, `turn:`, `turns:` URIs.
+- Rotate TURN credentials regularly; consider short-lived tokens (e.g., coturn REST API).
+
+### Deployment Tips
+
+1. **Deploy close to users** – Lower latency boosts stability.
+2. **Reserve bandwidth** – TURN relays bidirectional media streams.
+3. **Secure access** – Protect TURN credentials with authentication or token mechanisms.
+4. **Monitor usage** – Track sessions, bandwidth, failure rates, and alert on anomalies.
+5. **Multi-region redundancy** – Provide regional STUN/TURN nodes for global coverage.
+
+## FAQ
+
+### 1. Connection Fails
+
+**Problem**: WebRTC connection cannot be established.
+
+**Solutions**:
+- Verify ICE server configuration.
+- Ensure firewall rules allow the configured ports.
+- Try TCP mode: `port: tcp:9000`.
+- Configure TURN as a relay fallback.
+
+### 2. Video Not Displayed
+
+**Problem**: Connection succeeds but no video is shown.
+
+**Solutions**:
+- Check browser console errors.
+- Confirm the stream path is correct.
+- Ensure the codec is supported.
+- Test with the built-in subscribe page.
+- Inspect the Network panel to confirm SDP responses and track creation.
+- Run `pc.getReceivers().map(r => r.track)` in the console and verify tracks are `live`.
+- Review server logs to confirm the subscriber receives video frames.
+- Use `chrome://webrtc-internals` or `edge://webrtc-internals` for detailed stats (bitrate, frame rate, ICE state).
+
+### 3. H.265 Unsupported
+
+**Problem**: Browser lacks H.265 decoding support.
+
+**Solutions**:
+- Enable DataChannel fallback: `enabledc: true`.
+- Publish H.264 instead.
+- Wait for browser support (Chrome 113+ provides partial support).
+
+### 4. CORS Issues
+
+**Problem**: Requests are blocked due to CORS.
+
+**Solutions**:
+- Configure correct CORS headers.
+- Deploy under the same origin.
+- Use a reverse proxy.
+
+### 5. Port Already in Use
+
+**Problem**: Configured port is unavailable.
+
+**Solutions**:
+- Change the port: `port: tcp:9001`.
+- Check whether other services occupy the port.
+- Use a UDP range: `port: udp:10000-20000`.
+
+### 6. Docker Connection Issues
+
+**Problem**: WebRTC fails when running inside Docker.
+
+**Solutions**:
+- Configure `publicip` correctly.
+- Prefer `host` network mode.
+- Double-check port mappings (remember `/udp`).
+- Ensure firewall rules allow the traffic.
+- Review the [Docker Notes](#docker-notes).
+
+### 7. PublicIP Not Working
+
+**Problem**: Configured `publicip`, but clients still cannot connect.
+
+**Solutions**:
+- Verify the value matches the actual public address.
+- Ensure port forwarding aligns with the plugin configuration.
+- Test with `host` network mode.
+- Inspect firewall/NAT rules.
+- Check server logs to confirm ICE candidates include the public IP.
+
+### 8. AAC Audio Not Playing
+
+**Problem**: Audio unavailable when the source uses AAC/MP4A.
+
+**Solutions**:
+- The plugin currently supports Opus, PCMA, and PCMU.
+- Options:
+ - Publish Opus instead of AAC.
+ - Enable DataChannel (`enabledc: true`) to transport FLV with AAC.
+ - Transcode audio to a supported codec before publishing.
+
+## Summary
+
+The Monibuca WebRTC plugin delivers full WHIP/WHEP functionality for low-latency real-time streaming in modern browsers. Follow the configuration, deployment, and troubleshooting guidance above to integrate WebRTC publishing and playback into your Monibuca deployment quickly.
+
+Further reading:
+- [WebRTC official site](https://webrtc.org/)
+- [WHIP draft](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whip)
+- [WHEP draft](https://datatracker.ietf.org/doc/html/draft-murillo-whep)
diff --git a/plugin/webrtc/README_CN.md b/plugin/webrtc/README_CN.md
new file mode 100644
index 0000000..9693887
--- /dev/null
+++ b/plugin/webrtc/README_CN.md
@@ -0,0 +1,966 @@
+# WebRTC 插件使用教程
+
+## 目录
+
+- [WebRTC 简介](#webrtc-简介)
+- [插件概述](#插件概述)
+- [配置说明](#配置说明)
+- [基本使用](#基本使用)
+- [推流(Publish)](#推流publish)
+- [拉流(Play)](#拉流play)
+- [WHIP/WHEP 协议支持](#whipwhep-协议支持)
+- [高级功能](#高级功能)
+- [Docker 使用注意事项](#docker-使用注意事项)
+- [STUN/TURN 服务器说明](#stunturn-服务器说明)
+- [常见问题](#常见问题)
+
+## WebRTC 简介
+
+WebRTC(Web Real-Time Communication)是一个由 W3C 和 IETF 共同制定的开放标准,用于在浏览器和移动应用中实现实时音视频通信。WebRTC 的核心特点包括:
+
+### 核心特性
+
+1. **点对点通信(P2P)**:WebRTC 允许浏览器之间直接建立连接,减少服务器负载和延迟
+2. **低延迟**:通过 UDP 传输和优化的编解码器,实现毫秒级延迟
+3. **自适应码率**:根据网络状况自动调整视频质量
+4. **NAT 穿透**:通过 ICE(Interactive Connectivity Establishment)协议自动处理 NAT 和防火墙
+
+### WebRTC 工作流程
+
+1. **信令交换(Signaling)**:通过 HTTP/WebSocket 等协议交换 SDP(Session Description Protocol)和 ICE 候选
+2. **ICE 候选收集**:收集本地和远程的网络地址信息
+3. **连接建立**:通过 ICE 协商完成 NAT 穿透并建立 P2P 连接
+4. **媒体传输**:使用 SRTP(Secure Real-time Transport Protocol)加密传输音视频数据
+
+### WebRTC 连接建立时序图
+
+```mermaid
+sequenceDiagram
+ participant Client as 客户端
+ participant Server as Monibuca 服务器
+
+ Note over Client,Server: 1. 信令交换阶段
+ Client->>Server: POST /webrtc/push/{streamPath}
Body: SDP Offer
+ Server->>Server: 创建 PeerConnection
+ Server->>Server: 准备应答
+ Server->>Client: HTTP 201 Created
Body: SDP Answer
+
+ Note over Client,Server: 2. ICE 候选交换
+ Client->>Client: 收集本地 ICE 候选
+ Server->>Server: 收集本地 ICE 候选
+ Client->>Server: 发送 ICE 候选
+ Server->>Client: 发送 ICE 候选
+
+ Note over Client,Server: 3. 连接尝试
+ Client->>Server: 连接尝试 (Host/SRFLX 候选)
+ alt 连接成功
+ Client->>Server: 建立 P2P 连接
+ Server-->>Client: 确认连接
+ else 继续协商
+ Client->>Server: 发送更多候选
+ Server-->>Client: 返回协商结果
+ end
+
+ Note over Client,Server: 4. 媒体传输
+ Client->>Server: SRTP 媒体发送
+ Server-->>Client: RTCP 反馈
+```
+
+## 插件概述
+
+Monibuca 的 WebRTC 插件基于 Pion WebRTC v4 实现,提供了完整的 WebRTC 推拉流功能。插件支持:
+
+- ✅ 推流(WHIP 协议)
+- ✅ 拉流(WHEP 协议)
+- ✅ 多种视频编解码器(H.264、H.265、AV1、VP9)
+- ✅ 多种音频编解码器(Opus、PCMA、PCMU)
+- ✅ TCP/UDP 传输支持
+- ✅ ICE 服务器配置
+- ✅ DataChannel 备用传输
+- ✅ 内置测试页面
+
+## 配置说明
+
+### 基本配置
+
+在 `config.yaml` 中配置 WebRTC 插件:
+
+```yaml
+webrtc:
+ # ICE 服务器配置(可选)。如需配置 STUN/TURN,请参见“STUN/TURN 服务器说明”。
+ iceservers: []
+
+ # 监听端口配置
+ # 支持格式:
+ # - tcp:9000 (TCP 端口)
+ # - udp:9000 (UDP 端口)
+ # - udp:10000-20000 (UDP 端口范围)
+ port: tcp:9000
+
+ # PLI 请求间隔(视频丢包后请求关键帧)
+ pli: 2s
+
+ # 是否启用 DataChannel(用于不支持编码格式时的备用传输)
+ enabledc: false
+
+ # MimeType 过滤列表(为空则不过滤)
+ mimetype:
+ - video/H264
+ - video/H265
+ - audio/PCMA
+ - audio/PCMU
+```
+
+### 配置参数详解
+
+#### ICE 服务器(ICEServers)
+
+用于配置用于 ICE 协商的服务器列表。详细说明与示例请参见文末的“STUN/TURN 服务器说明”。
+
+#### 端口配置(Port)
+
+支持三种端口配置方式:
+
+1. **TCP 端口**:使用 TCP 传输,适合防火墙限制严格的环境
+ ```yaml
+ port: tcp:9000
+ ```
+
+2. **UDP 端口**:使用 UDP 传输,延迟更低
+ ```yaml
+ port: udp:9000
+ ```
+
+3. **UDP 端口范围**:为每个连接分配不同的端口
+ ```yaml
+ port: udp:10000-20000
+ ```
+
+#### PLI 间隔(PLI)
+
+PLI(Picture Loss Indication)用于在视频丢包时请求关键帧。默认 2 秒发送一次 PLI 请求。
+
+#### DataChannel(EnableDC)
+
+当客户端不支持某些编码格式(如 H.265)时,可以启用 DataChannel 作为备用传输方式。DataChannel 会将媒体数据封装为 FLV 格式传输。
+
+#### MimeType 过滤(MimeType)
+
+限制允许的编解码器类型。如果为空,则不进行过滤,支持所有编解码器。
+
+#### 公网 IP 配置(PublicIP)
+
+当 Monibuca 服务器部署在 NAT 后面(如 Docker 容器、内网服务器)时,需要配置公网 IP 地址,以便客户端能够正确建立 WebRTC 连接。
+
+##### PublicIP 的作用原理
+
+在 NAT 环境中,服务器只能获取到内网 IP 地址(如 `192.168.1.100`),但客户端需要知道服务器的公网 IP 地址才能建立连接。`PublicIP` 配置的作用是:
+
+1. **NAT 1:1 IP 映射**:通过 `SetNAT1To1IPs` 方法,将内网 IP 映射到公网 IP
+2. **ICE 候选生成**:在生成 ICE 候选时,使用配置的公网 IP 而不是内网 IP
+3. **SDP 中的 IP 地址**:SDP Answer 中的 `c=` 行和 ICE 候选中的 IP 地址会使用公网 IP
+
+##### 配置示例
+
+```yaml
+webrtc:
+ # WebRTC 插件的公网地址配置
+ publicip: 203.0.113.1 # IPv4 公网 IP
+ publicipv6: 2001:db8::1 # IPv6 公网 IP(可选)
+
+ # 其他常见配置...
+ port: tcp:9000
+ pli: 2s
+ enabledc: false
+```
+
+##### 工作原理示意图
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ 公网环境 │
+│ │
+│ ┌──────────────┐ │
+│ │ 客户端 │ │
+│ │ │ │
+│ └──────┬───────┘ │
+│ │ │
+│ │ 1. 获取可用公网地址信息 │
+│ │ (可通过 STUN/TURN,见说明章节) │
+│ │ │
+└─────────┼───────────────────────────────────────────────┘
+ │
+┌─────────▼───────────────────────────────────────────────┐
+│ NAT/防火墙 │
+│ │
+│ ┌──────────────┐ │
+│ │ Monibuca │ │
+│ │ 服务器 │ │
+│ │ │ │
+│ │ 内网 IP: │ │
+│ │ 192.168.1.100 │
+│ │ │ │
+│ │ 配置 PublicIP: 203.0.113.1 │
+│ └──────────────┘ │
+│ │
+│ 2. 服务器使用配置的 PublicIP 生成 ICE 候选 │
+│ 3. SDP Answer 中包含公网 IP │
+│ 4. 客户端根据公网 IP 与服务器建立连接 │
+└─────────────────────────────────────────────────────────┘
+```
+
+##### 注意事项
+
+1. **必须配置**:如果服务器在 NAT 后面,必须配置正确的公网 IP,否则客户端无法连接
+2. **IP 地址准确性**:确保配置的公网 IP 是服务器实际对外的公网 IP
+3. **端口映射**:如果使用 Docker 或端口转发,确保公网端口已正确映射
+4. **IPv6 支持**:如果服务器有 IPv6 地址,可以同时配置 `publicipv6`
+
+## 基本使用
+
+### 启动服务
+
+确保 WebRTC 插件已启用,启动 Monibuca 服务后,插件会自动注册以下 HTTP 端点:
+
+- `POST /webrtc/push/{streamPath}` - 推流端点(WHIP)
+- `POST /webrtc/play/{streamPath}` - 拉流端点(WHEP)
+- `GET /webrtc/test/{name}` - 测试页面
+
+### 测试页面
+
+插件提供了内置的测试页面,方便快速测试功能:
+
+- **推流测试**:`http://localhost:8080/webrtc/test/publish`
+- **拉流测试**:`http://localhost:8080/webrtc/test/subscribe`
+- **屏幕共享**:`http://localhost:8080/webrtc/test/screenshare`
+
+## 推流(Publish)
+
+### 使用测试页面推流
+
+1. 打开浏览器访问:`http://localhost:8080/webrtc/test/publish`
+2. 浏览器会请求摄像头和麦克风权限
+3. 选择摄像头设备(如果有多个)
+4. 页面会自动创建 WebRTC 连接并开始推流
+
+### 自定义推流实现
+
+#### JavaScript 示例
+
+```javascript
+// 获取媒体流
+const mediaStream = await navigator.mediaDevices.getUserMedia({
+ video: true,
+ audio: true
+});
+
+// 创建 PeerConnection
+const pc = new RTCPeerConnection({
+ // 如需配置 STUN/TURN,请参见“STUN/TURN 服务器说明”
+ iceServers: []
+});
+
+// 添加媒体轨道
+mediaStream.getTracks().forEach(track => {
+ pc.addTrack(track, mediaStream);
+});
+
+// 创建 Offer
+const offer = await pc.createOffer();
+await pc.setLocalDescription(offer);
+
+// 发送 Offer 到服务器
+const response = await fetch('/webrtc/push/live/test', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/sdp' },
+ body: offer.sdp
+});
+
+// 接收 Answer
+const answerSdp = await response.text();
+await pc.setRemoteDescription(
+ new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
+);
+```
+
+#### 支持 H.265 推流
+
+如果浏览器支持 H.265,可以在推流时指定使用 H.265:
+
+```javascript
+// 添加视频轨道后,设置编解码器偏好
+const videoTransceiver = pc.getTransceivers().find(
+ t => t.sender.track && t.sender.track.kind === 'video'
+);
+
+if (videoTransceiver) {
+ const capabilities = RTCRtpSender.getCapabilities('video');
+ const h265Codec = capabilities.codecs.find(
+ c => c.mimeType.toLowerCase() === 'video/h265'
+ );
+
+ if (h265Codec) {
+ videoTransceiver.setCodecPreferences([h265Codec]);
+ }
+}
+```
+
+访问测试页面时添加 `?h265` 参数即可:`/webrtc/test/publish?h265`
+
+### 推流 URL 参数
+
+推流端点支持以下 URL 参数:
+
+- `streamPath`:流路径,例如 `live/test`
+- `bearer`:Bearer Token(用于认证)
+
+示例:
+```
+POST /webrtc/push/live/test?bearer=your_token
+```
+
+### 停止推流
+
+关闭 PeerConnection 即可停止推流:
+
+```javascript
+pc.close();
+```
+
+## 拉流(Play)
+
+### 使用测试页面拉流
+
+1. 确保已有流在推流(使用推流测试页面或其他方式)
+2. 打开浏览器访问:`http://localhost:8080/webrtc/test/subscribe?streamPath=live/test`
+3. 页面会自动创建 WebRTC 连接并开始播放
+
+### 自定义拉流实现
+
+#### JavaScript 示例
+
+```javascript
+// 创建 PeerConnection
+const pc = new RTCPeerConnection({
+ // 如需配置 STUN/TURN,请参见“STUN/TURN 服务器说明”
+ iceServers: []
+});
+
+// 监听远程轨道
+pc.ontrack = (event) => {
+ if (event.streams.length > 0) {
+ videoElement.srcObject = event.streams[0];
+ videoElement.play();
+ }
+};
+
+// 添加接收器
+pc.addTransceiver('video', { direction: 'recvonly' });
+pc.addTransceiver('audio', { direction: 'recvonly' });
+
+// 创建 Offer
+const offer = await pc.createOffer();
+await pc.setLocalDescription(offer);
+
+// 发送 Offer 到服务器
+const response = await fetch('/webrtc/play/live/test', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/sdp' },
+ body: offer.sdp
+});
+
+// 接收 Answer
+const answerSdp = await response.text();
+await pc.setRemoteDescription(
+ new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
+);
+```
+
+### 拉流 URL 参数
+
+拉流端点支持以下 URL 参数:
+
+- `streamPath`:流路径,例如 `live/test`
+
+示例:
+```
+POST /webrtc/play/live/test
+```
+
+### 停止拉流
+
+关闭 PeerConnection 即可停止拉流:
+
+```javascript
+pc.close();
+```
+
+## WHIP/WHEP 协议支持
+
+### WHIP(WebRTC-HTTP Ingestion Protocol)
+
+WHIP 是一个基于 HTTP 的 WebRTC 推流协议,Monibuca 的 WebRTC 插件完全支持 WHIP 协议。
+
+#### WHIP 推流流程
+
+1. 客户端创建 PeerConnection 和 Offer
+2. 客户端发送 `POST /webrtc/push/{streamPath}` 请求,Body 为 SDP Offer
+3. 服务器返回 SDP Answer(HTTP 201 Created)
+4. 客户端设置 Answer,建立连接
+5. 开始传输媒体数据
+
+#### WHIP 推流时序图
+
+```mermaid
+sequenceDiagram
+ participant Client as 客户端
+ participant Server as Monibuca 服务器
+
+ Note over Client,Server: 1. 准备阶段
+ Client->>Client: 获取媒体流 (getUserMedia)
+ Client->>Client: 创建 RTCPeerConnection
+ Client->>Client: 添加媒体轨道 (addTrack)
+
+ Note over Client,Server: 2. 创建 Offer
+ Client->>Client: 创建 Offer (createOffer)
+ Client->>Client: 设置本地描述 (setLocalDescription)
+ Client->>Client: 收集 ICE 候选
+
+ Note over Client,Server: 3. 发送 Offer 到服务器
+ Client->>Server: POST /webrtc/push/{streamPath}
Content-Type: application/sdp
Body: SDP Offer
+ Server->>Server: 解析 SDP Offer
+ Server->>Server: 创建 PeerConnection
+ Server->>Server: 设置远程描述 (setRemoteDescription)
+ Server->>Server: 创建发布者 (Publish)
+ Server->>Server: 收集 ICE 候选
+
+ Note over Client,Server: 4. 服务器返回 Answer
+ Server->>Server: 创建 Answer (createAnswer)
+ Server->>Server: 设置本地描述 (setLocalDescription)
+ Server->>Client: HTTP 201 Created
Content-Type: application/sdp
Body: SDP Answer
+ Server->>Client: Location: /webrtc/api/stop/push/{streamPath}
+
+ Note over Client,Server: 5. 客户端处理 Answer
+ Client->>Client: 设置远程描述 (setRemoteDescription)
+ Client->>Client: ICE 候选交换
+ Server->>Client: ICE 候选交换
+
+ Note over Client,Server: 6. 建立连接
+ Client->>Server: ICE 连接建立
+ Server->>Client: ICE 连接建立
+ Client->>Server: 数据通道建立
+ Server-->>Client: 数据通道确认
+
+ Note over Client,Server: 7. 媒体传输
+ Client->>Server: 发送媒体数据 (SRTP)
+ Server-->>Client: RTCP 反馈
+```
+
+#### WHIP 客户端实现
+
+```javascript
+const pc = new RTCPeerConnection();
+// ... 添加轨道和创建 Offer ...
+
+const response = await fetch('http://server:8080/webrtc/push/live/test', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/sdp' },
+ body: offer.sdp
+});
+
+if (response.status === 201) {
+ const answerSdp = await response.text();
+ await pc.setRemoteDescription(
+ new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
+ );
+}
+```
+
+### WHEP(WebRTC HTTP Egress Protocol)
+
+WHEP 是一个基于 HTTP 的 WebRTC 拉流协议,Monibuca 的 WebRTC 插件完全支持 WHEP 协议。
+
+#### WHEP 拉流流程
+
+1. 客户端创建 PeerConnection 和 Offer(包含 recvonly 轨道)
+2. 客户端发送 `POST /webrtc/play/{streamPath}` 请求,Body 为 SDP Offer
+3. 服务器返回 SDP Answer
+4. 客户端设置 Answer,建立连接
+5. 开始接收媒体数据
+
+#### WHEP 拉流时序图
+
+```mermaid
+sequenceDiagram
+ participant Client as 客户端
+ participant Server as Monibuca 服务器
+
+ Note over Client,Server: 1. 准备阶段
+ Client->>Client: 创建 RTCPeerConnection
+ Client->>Client: 添加接收器 (addTransceiver recvonly)
+ Client->>Client: 监听远程轨道 (ontrack)
+
+ Note over Client,Server: 2. 创建 Offer
+ Client->>Client: 创建 Offer (createOffer)
+ Client->>Client: 设置本地描述 (setLocalDescription)
+ Client->>Client: 收集 ICE 候选
+
+ Note over Client,Server: 3. 发送 Offer 到服务器
+ Client->>Server: POST /webrtc/play/{streamPath}
Content-Type: application/sdp
Body: SDP Offer
+ Server->>Server: 解析 SDP Offer
+ Server->>Server: 检查流是否存在
+ alt 流不存在
+ Server->>Client: HTTP 404 Not Found
+ else 流存在
+ Server->>Server: 创建 PeerConnection
+ Server->>Server: 设置远程描述 (setRemoteDescription)
+ Server->>Server: 创建订阅者 (Subscribe)
+ Server->>Server: 添加媒体轨道 (AddTrack)
+ Server->>Server: 收集 ICE 候选
+
+ Note over Client,Server: 4. 服务器返回 Answer
+ Server->>Server: 创建 Answer (createAnswer)
+ Server->>Server: 设置本地描述 (setLocalDescription)
+ Server->>Client: HTTP 200 OK
Content-Type: application/sdp
Body: SDP Answer
+
+ Note over Client,Server: 5. 客户端处理 Answer
+ Client->>Client: 设置远程描述 (setRemoteDescription)
+ Client->>Client: ICE 候选交换
+ Server->>Client: ICE 候选交换
+
+ Note over Client,Server: 6. 建立连接
+ Client->>Server: ICE 连接建立
+ Server->>Client: ICE 连接建立
+ Client->>Server: 数据通道建立
+ Server-->>Client: 数据通道确认
+
+ Note over Client,Server: 7. 媒体传输
+ Server->>Client: 发送媒体数据 (SRTP)
+ Client->>Client: 接收并播放媒体流
+ end
+```
+
+#### WHEP 客户端实现
+
+```javascript
+const pc = new RTCPeerConnection();
+pc.addTransceiver('video', { direction: 'recvonly' });
+pc.addTransceiver('audio', { direction: 'recvonly' });
+
+const offer = await pc.createOffer();
+await pc.setLocalDescription(offer);
+
+const response = await fetch('http://server:8080/webrtc/play/live/test', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/sdp' },
+ body: offer.sdp
+});
+
+const answerSdp = await response.text();
+await pc.setRemoteDescription(
+ new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
+);
+```
+
+### 作为 WHIP/WHEP 客户端
+
+Monibuca 的 WebRTC 插件也可以作为客户端,从其他 WHIP/WHEP 服务器拉流或推流。
+
+#### 配置拉流(WHEP)
+
+在 `config.yaml` 中配置:
+
+```yaml
+pull:
+ streams:
+ - url: https://whep-server.example.com/play/stream1
+ streamPath: live/stream1
+```
+
+#### 配置推流(WHIP)
+
+在 `config.yaml` 中配置:
+
+```yaml
+push:
+ streams:
+ - url: https://whip-server.example.com/push/stream1
+ streamPath: live/stream1
+```
+
+## 高级功能
+
+### 编解码器支持
+
+插件支持以下编解码器:
+
+#### 视频编解码器
+
+- **H.264**:最广泛支持的视频编解码器
+- **H.265/HEVC**:更高效的视频编解码器(需要浏览器支持)
+- **AV1**:新一代开源视频编解码器
+- **VP9**:Google 开发的视频编解码器
+
+#### 音频编解码器
+
+- **Opus**:现代音频编解码器,质量高
+- **PCMA**:G.711 A-law,常用于电话系统
+- **PCMU**:G.711 μ-law,常用于电话系统
+
+### DataChannel 传输
+
+当客户端不支持某些编码格式时,可以启用 DataChannel 作为备用传输方式。DataChannel 会将媒体数据封装为 FLV 格式传输。
+
+启用 DataChannel:
+
+```yaml
+webrtc:
+ enabledc: true
+```
+
+### NAT 穿透配置
+
+如果服务器部署在 NAT 后面,需要配置公网 IP。详细原理请参考 [公网 IP 配置(PublicIP)](#公网-ip-配置publicip) 部分。
+
+### Docker 使用注意事项
+
+在 Docker 环境中使用 WebRTC 插件时,需要注意以下事项:
+
+#### 1. 网络模式配置
+
+推荐使用 `host` 网络模式,这样可以避免端口映射问题:
+
+```bash
+docker run --network host monibuca/monibuca
+```
+
+如果必须使用 `bridge` 网络模式,需要:
+
+- 映射 WebRTC 端口(TCP 或 UDP)
+- 配置正确的公网 IP
+- 确保端口映射正确
+
+```bash
+# 使用 bridge 模式
+docker run -p 8080:8080 -p 9000:9000/udp monibuca/monibuca
+```
+
+#### 2. 端口映射配置
+
+##### TCP 模式
+
+如果使用 TCP 模式(`port: tcp:9000`),需要映射 TCP 端口:
+
+```bash
+docker run -p 8080:8080 -p 9000:9000/tcp monibuca/monibuca
+```
+
+##### UDP 模式
+
+如果使用 UDP 模式(`port: udp:9000`),需要映射 UDP 端口:
+
+```bash
+docker run -p 8080:8080 -p 9000:9000/udp monibuca/monibuca
+```
+
+##### UDP 端口范围
+
+如果使用端口范围(`port: udp:10000-20000`),需要映射整个端口范围:
+
+```bash
+docker run -p 8080:8080 -p 10000-20000:10000-20000/udp monibuca/monibuca
+```
+
+**注意**:Docker 的端口范围映射可能有限制,建议使用单个 UDP 端口或 `host` 网络模式。
+
+#### 3. PublicIP 配置
+
+在 Docker 环境中,**必须配置公网 IP**,因为容器内的 IP 地址是内网地址。
+
+##### 获取公网 IP
+
+可以通过以下方式获取服务器的公网 IP:
+
+```bash
+# 方法 1: 使用 curl
+curl ifconfig.me
+
+# 方法 2: 使用 dig
+dig +short myip.opendns.com @resolver1.opendns.com
+
+# 方法 3: 查看服务器配置
+# 在云服务商控制台查看
+```
+
+##### 配置示例
+
+```yaml
+# config.yaml
+publicip: 203.0.113.1 # 替换为实际公网 IP
+
+webrtc:
+ port: udp:9000
+```
+
+#### 4. Docker Compose 配置示例
+
+```yaml
+version: '3.8'
+
+services:
+ monibuca:
+ image: monibuca/monibuca:latest
+ network_mode: host # 推荐使用 host 模式
+ # 或者使用 bridge 模式
+ # ports:
+ # - "8080:8080"
+ # - "9000:9000/udp"
+ volumes:
+ - ./config.yaml:/app/config.yaml
+ - ./logs:/app/logs
+ environment:
+ - PUBLICIP=203.0.113.1 # 如果通过环境变量配置
+```
+
+#### 5. 常见问题
+
+##### 问题 1: 连接失败
+
+**原因**:Docker 容器内的 IP 地址是内网地址,客户端无法直接连接。
+
+**解决方案**:
+- 配置正确的 `publicip`
+- 使用 `host` 网络模式
+- 确保端口映射正确
+
+##### 问题 2: UDP 端口无法映射
+
+**原因**:Docker 的 UDP 端口映射在某些情况下可能不稳定。
+
+**解决方案**:
+- 使用 `host` 网络模式
+- 使用 TCP 模式:`port: tcp:9000`
+- 检查防火墙规则
+
+##### 问题 3: 多容器部署
+
+如果需要在同一台服务器上部署多个 Monibuca 实例:
+
+```yaml
+# 实例 1
+webrtc:
+ port: tcp:9000
+
+# 实例 2
+webrtc:
+ port: tcp:9001
+```
+
+然后分别映射不同的端口:
+
+```bash
+docker run -p 8080:8080 -p 9000:9000/tcp monibuca1
+docker run -p 8081:8081 -p 9001:9001/tcp monibuca2
+```
+
+#### 6. 最佳实践
+
+1. **使用 host 网络模式**:避免端口映射问题,性能更好
+2. **配置 PublicIP**:确保客户端能够正确连接
+3. **使用 TCP 模式**:在 Docker 环境中更稳定
+4. **监控连接状态**:通过日志监控 WebRTC 连接状态
+5. **配置 TURN 服务器**:作为备用方案,提高连接成功率
+
+### 多路流支持
+
+插件支持在单个 WebRTC 连接中传输多个流,通过 BatchV2 API 实现。
+
+访问批量流页面:`http://localhost:8080/webrtc/test/batchv2`
+
+#### BatchV2 多流模式
+
+- **信令通道**:通过 WebSocket 与服务器的 `/webrtc/batchv2` Endpoint 通信(HTTP → WebSocket Upgrade)。
+- **初始握手**:
+ 1. 客户端创建 `RTCPeerConnection`,执行 `createOffer`/`setLocalDescription`。
+ 2. 通过 WebSocket 发送 `{ "type": "offer", "sdp": "..." }`。
+ 3. 服务器返回 `{ "type": "answer", "sdp": "..." }`,客户端执行 `setRemoteDescription`。
+- **常用指令**(全部为 JSON 文本帧):
+ - `getStreamList`:`{ "type": "getStreamList" }` → 服务器返回 `{ "type": "streamList", "streams": [{ "path": "live/cam1", "codec": "H264", "width": 1280, "height": 720, "fps": 25 }, ...] }`。
+ - `subscribe`:向现有连接追加拉流,消息格式
+ ```json
+ {
+ "type": "subscribe",
+ "streamList": ["live/cam1", "live/cam2"],
+ "offer": "SDP..."
+ }
+ ```
+ 服务器完成重协商后返回 `{ "type": "answer", "sdp": "..." }`,需再次 `setRemoteDescription`。
+ - `unsubscribe`:移除指定流,结构与 `subscribe` 相同(`streamList` 填写需移除的流列表)。
+ - `publish`:在同一连接中推流
+ ```json
+ {
+ "type": "publish",
+ "streamPath": "live/cam3",
+ "offer": "SDP..."
+ }
+ ```
+ 服务器返回应答后设置远端描述即可开始发送。
+ - `unpublish`:`{ "type": "unpublish", "streamPath": "live/cam3" }`,服务器会返回新的 SDP 应答。
+ - `ping`:`{ "type": "ping" }`,服务器回 `pong` 维持在线状态。
+- **媒体限制**:当前订阅侧默认只拉取视频轨道(`SubAudio` 关闭),如需音频需自行扩展。
+- **客户端工具**:`web/BatchV2Client.ts` 提供了完整的浏览器端实现,示例页面 `webrtc/test/batchv2` 演示了多路拉流、推流与流列表操作。
+- **失败排查**:
+ - 若服务器返回 `error`,消息体中包含 `message` 字段,可在前端控制台或 WebSocket 调试工具中查看。
+ - 每次 `subscribe`/`publish` 都会触发新的 SDP,确保在应用层完成 `setLocalDescription` → 发送 → `setRemoteDescription` 的协商流程。
+
+### 连接状态监控
+
+可以通过监听 PeerConnection 的状态事件来监控连接:
+
+```javascript
+pc.oniceconnectionstatechange = () => {
+ console.log('ICE Connection State:', pc.iceConnectionState);
+ // 可能的值:
+ // - new: 新建连接
+ // - checking: 正在检查连接
+ // - connected: 已连接
+ // - completed: 连接完成
+ // - failed: 连接失败
+ // - disconnected: 连接断开
+ // - closed: 连接关闭
+};
+
+pc.onconnectionstatechange = () => {
+ console.log('Connection State:', pc.connectionState);
+};
+```
+
+## STUN/TURN 服务器说明
+
+### 为什么需要 STUN/TURN
+
+- **STUN(Session Traversal Utilities for NAT)**:帮助 WebRTC 终端获知自身的公网地址与端口,用于打洞和构建 ICE 候选。
+- **TURN(Traversal Using Relays around NAT)**:在双方均无法直接穿透 NAT/防火墙时提供中继服务,保证连接成功率。
+- 在大多数公网或家庭网络环境下,STUN 足以完成 P2P 建立;但在企业网络、移动网络或对称 NAT 场景下,需要 TURN 作为兜底。
+
+### 配置示例
+
+在 `config.yaml` 中配置 STUN/TURN 服务器列表:
+
+```yaml
+webrtc:
+ iceservers:
+ - urls:
+ - stun:stun.l.google.com:19302
+ - urls:
+ - turn:turn.example.com:3478
+ username: user
+ credential: password
+```
+
+- `urls` 支持同时配置多个地址,可混合 `stun:`、`turn:`、`turns:` 等 URI。
+- TURN 服务器通常需要用户名、密码,建议使用长期凭据或临时 Token(如使用 [coturn](https://github.com/coturn/coturn))。
+
+### 部署建议
+
+1. **优选就近节点**:STUN/TURN 延迟会直接影响媒体传输质量,建议部署在离客户端较近的区域。
+2. **保证带宽**:TURN 会中继双向媒体流,应为其预留足够的带宽与并发资源。
+3. **安全控制**:TURN 用户名、密码应定期轮换;若提供给外部用户,建议结合鉴权或令牌机制。
+4. **监控告警**:建议监控连接数、带宽和失败率,便于快速发现穿透异常。
+5. **多区域容灾**:为全球用户提供服务时,可按地域部署多套 STUN/TURN,并通过 DNS 或业务逻辑选择最优节点。
+
+## 常见问题
+
+### 1. 连接失败
+
+**问题**:WebRTC 连接无法建立
+
+**解决方案**:
+- 检查 ICE 服务器配置是否正确
+- 检查防火墙是否开放了相应端口
+- 尝试使用 TCP 模式:`port: tcp:9000`
+- 配置 TURN 服务器作为中继
+
+### 2. 视频无法显示
+
+**问题**:连接成功但视频无法显示
+
+**解决方案**:
+- 检查浏览器控制台是否有错误
+- 确认流路径是否正确
+- 检查编解码器是否支持
+- 尝试使用测试页面验证
+- 打开浏览器开发者工具的 Network 面板,确认 SDP 应答正常返回且媒体流轨道已创建
+- 在浏览器控制台执行 `pc.getReceivers().map(r => r.track)`,确认远端轨道状态是否为 `live`
+- 查看服务器日志,确认对应的订阅者已成功获取视频帧
+- 在 Chrome 中访问 `chrome://webrtc-internals`(或 Edge 中访问 `edge://webrtc-internals`),查看对应 PeerConnection 的统计信息与远端流状态,排查码率、帧率及 ICE 状态问题
+
+### 3. H.265 不支持
+
+**问题**:浏览器不支持 H.265
+
+**解决方案**:
+- 启用 DataChannel:`enabledc: true`
+- 使用 H.264 编解码器
+- 等待浏览器支持 H.265(Chrome 113+ 已支持)
+
+### 4. 跨域问题
+
+**问题**:跨域请求被阻止
+
+**解决方案**:
+- 配置 CORS 头
+- 使用同源部署
+- 配置代理服务器
+
+### 5. 端口被占用
+
+**问题**:端口已被占用
+
+**解决方案**:
+- 更改端口配置:`port: tcp:9001`
+- 检查是否有其他服务占用端口
+- 使用端口范围:`port: udp:10000-20000`
+
+### 6. Docker 环境连接失败
+
+**问题**:在 Docker 环境中 WebRTC 连接失败
+
+**解决方案**:
+- 配置正确的 `publicip`
+- 使用 `host` 网络模式:`docker run --network host`
+- 确保端口映射正确(UDP 端口需要 `/udp` 后缀)
+- 检查防火墙是否开放端口
+- 参考 [Docker 使用注意事项](#docker-使用注意事项) 部分
+
+### 7. PublicIP 配置无效
+
+**问题**:配置了 PublicIP 但客户端仍无法连接
+
+**解决方案**:
+- 确认 PublicIP 是服务器实际对外的公网 IP
+- 检查端口映射是否正确(Docker 环境)
+- 使用 `host` 网络模式测试
+- 检查防火墙和 NAT 规则
+- 查看服务器日志确认 ICE 候选中的 IP 地址
+
+### 8. AAC 音频无法播放
+
+**问题**:拉流时音频不可用,日志显示 AAC 或 MP4A 编解码不受支持。
+
+**解决方案**:
+- WebRTC 插件当前仅支持 Opus、PCMA、PCMU 等音频编码。
+- 如源流为 AAC(MP4A),可选择:
+ - 在编码端改用 Opus;
+ - 启用 DataChannel(`enabledc: true`),通过 DataChannel 传输 FLV 封装的 AAC 音频;
+ - 在推流前进行音频转码(如通过其他插件或外部转码流程)。
+
+## 总结
+
+Monibuca 的 WebRTC 插件提供了完整的 WebRTC 推拉流功能,支持标准的 WHIP/WHEP 协议,可以轻松集成到现有的 Web 应用中。通过合理的配置和优化,可以实现低延迟、高质量的实时音视频传输。
+
+更多信息请参考:
+- [WebRTC 官方文档](https://webrtc.org/)
+- [WHIP 协议规范](https://datatracker.ietf.org/doc/html/draft-ietf-wish-whip)
+- [WHEP 协议规范](https://datatracker.ietf.org/doc/html/draft-murillo-whep)
+