fix(webrtc): fix GStreamer WHEP interop (#4720)

## Summary

This PR fixes GStreamer interoperability issues during WebRTC/WHEP
negotiation with ZLMediaServer.

GStreamer could fail to establish the connection for two separate
reasons:

1. ZLMediaServer generated a non-compliant ICE `ufrag`. The generated
value contained `_`, which is not a valid ICE character, so GStreamer
rejected the SDP.
2. ZLMediaServer did not correctly handle `bundle-only` offers and could
answer an accepted bundled m-line with `port=0`, which caused the later
WHEP negotiation to fail.

## Changes

- Generate ICE `ufrag` values using ICE-compliant characters only.
- Preserve and handle `a=bundle-only` correctly during SDP parsing and
answer generation.
- Return `port=9` instead of `port=0` for accepted bundled m-lines.
- Add regression coverage for `bundle-only` SDP handling.
- URL-encode `delete_webrtc` query parameters in the returned `Location`
header so ICE-safe identifiers remain round-trippable over HTTP.

## Validation

- Built with WebRTC and SCTP enabled.
- Added regression test: `test_webrtc_regression`
- Verified:
  - ICE-safe identifier round-trip through `delete_webrtc`
  - `bundle-only` SDP answer generation
This commit is contained in:
Miau Lightouch
2026-04-21 11:37:08 +08:00
committed by GitHub
parent 56f2cfba7c
commit a9e0e1a81e
6 changed files with 165 additions and 27 deletions

View File

@@ -2180,10 +2180,14 @@ void installWebApi() {
WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable {
auto &handler = const_cast<WebRtcInterface &>(exchanger);
try {
// Encode query params since transport id/token may contain '+' or '/'.
HttpArgs delete_args;
delete_args["id"] = exchanger.getIdentifier();
delete_args["token"] = exchanger.deleteRandStr();
// 设置返回类型 [AUTO-TRANSLATED:ffc2a31a]
// Set return type
headerOut["Content-Type"] = "application/sdp";
headerOut["Location"] = location + "?id=" + exchanger.getIdentifier() + "&token=" + exchanger.deleteRandStr();
headerOut["Location"] = location + "?" + delete_args.make();
invoker(201, headerOut, handler.getAnswerSdp(offer));
} catch (std::exception &ex) {
headerOut["Content-Type"] = "text/plain";