WebRTC improvements:

- Return ICE headers for WHIP/WHEP/WISH OPTIONS requests already to help clients that cannot change ICE servers post-SDP-request-generation
- Fixes to WebRTC WHIP/WHEP GET param parsing
- Added "constant" GET param for forcing constant-rate playback
- Use correct syntax for WebRTC Link headers
This commit is contained in:
Thulinma 2023-09-15 00:34:44 +02:00
parent 5f70d387a5
commit 9fd34c95fa
4 changed files with 79 additions and 71 deletions

View file

@ -150,7 +150,7 @@ std::string &HTTP::Parser::BuildRequest(){
/// \todo Include POST variable handling for vars?
std::map<std::string, std::string>::iterator it;
if (protocol.size() < 5 || protocol[4] != '/'){protocol = "HTTP/1.0";}
if (method != "POST" && vars.size() && url.find('?') == std::string::npos){
if (!(method == "POST" && GetHeader("Content-Type") == "application/x-www-form-urlencoded") && vars.size() && url.find('?') == std::string::npos){
builder = method + " " + Encodings::URL::encode(url, "/:=@[]") + allVars() + " " + protocol + "\r\n";
}else{
builder = method + " " + Encodings::URL::encode(url, "/:=@[]") + " " + protocol + "\r\n";

View file

@ -833,9 +833,8 @@ namespace Mist{
respondHTTP(reqH, headersOnly);
}
/// Default implementation of preHTTP simply calls initialize and selectDefaultTracks.
/// Default implementation of preHTTP simply calls selectDefaultTracks.
void HTTPOutput::preHTTP(){
initialize();
selectDefaultTracks();
}

View file

@ -429,14 +429,83 @@ namespace Mist{
HTTPOutput::requestHandler();
}
// If ICE headers are configured, sets them on the given HTTP::Parser instance.
void OutWebRTC::setIceHeaders(HTTP::Parser & H){
if (!config->getString("iceservers").size()){return;}
std::deque<std::string> links;
JSON::Value iceConf = JSON::fromString(config->getString("iceservers"));
jsonForEach(iceConf, i){
if (i->isMember("url") && (*i)["url"].isString()){
JSON::Value &u = (*i)["url"];
std::string str = "<"+u.asString()+">; rel=\"ice-server\";";
if (i->isMember("username")){
str += " username=" + (*i)["username"].toString() + ";";
}
if (i->isMember("credential")){
str += " credential=" + (*i)["credential"].toString() + ";";
}
if (i->isMember("credentialType")){
str += " credential-type=" + (*i)["credentialType"].toString() + ";";
}
links.push_back(str);
}
if (i->isMember("urls") && (*i)["urls"].isString()){
JSON::Value &u = (*i)["urls"];
std::string str = "<"+u.asString()+">; rel=\"ice-server\";";
if (i->isMember("username")){
str += " username=" + (*i)["username"].toString() + ";";
}
if (i->isMember("credential")){
str += " credential=" + (*i)["credential"].toString() + ";";
}
if (i->isMember("credentialType")){
str += " credential-type=" + (*i)["credentialType"].toString() + ";";
}
links.push_back(str);
}
if (i->isMember("urls") && (*i)["urls"].isArray()){
jsonForEach((*i)["urls"], j){
JSON::Value &u = *j;
std::string str = "<"+u.asString()+">; rel=\"ice-server\";";
if (i->isMember("username")){
str += " username=" + (*i)["username"].toString() + ";";
}
if (i->isMember("credential")){
str += " credential=" + (*i)["credential"].toString() + ";";
}
if (i->isMember("credentialType")){
str += " credential-type=" + (*i)["credentialType"].toString() + ";";
}
links.push_back(str);
}
}
}
if (links.size()){
if (links.size() == 1){
H.SetHeader("Link", *links.begin());
}else{
std::deque<std::string>::iterator it = links.begin();
std::string linkHeader = *it;
++it;
while (it != links.end()){
linkHeader += "\r\nLink: " + *it;
++it;
}
H.SetHeader("Link", linkHeader);
}
}
}
void OutWebRTC::respondHTTP(const HTTP::Parser & req, bool headersOnly){
// Generic header/parameter handling
HTTPOutput::respondHTTP(req, headersOnly);
INFO_MSG("HTTP: %s", req.method.c_str());
// Check for WHIP payload
// Always send the ICE headers, because why not?
setIceHeaders(H);
// Check for WHIP/WHEP payload
if (headersOnly){
H.setCORSHeaders();
H.StartResponse("200", "All good", req, myConn);
// Options can be used to get the ICE config, so we should include it in the response
H.StartResponse("200", "All good, have some ICE config", req, myConn);
H.Chunkify(0, 0, myConn);
return;
}
@ -470,71 +539,10 @@ namespace Mist{
noSignalling = true;
H.SetHeader("Content-Type", "application/sdp");
H.SetHeader("Location", streamName + "/" + JSON::Value(getpid()).asString());
if (config->getString("iceservers").size()){
std::deque<std::string> links;
JSON::Value iceConf = JSON::fromString(config->getString("iceservers"));
jsonForEach(iceConf, i){
if (i->isMember("url") && (*i)["url"].isString()){
JSON::Value &u = (*i)["url"];
std::string str = u.asString()+"; rel=\"ice-server\";";
if (i->isMember("username")){
str += " username=" + (*i)["username"].toString() + ";";
}
if (i->isMember("credential")){
str += " credential=" + (*i)["credential"].toString() + ";";
}
if (i->isMember("credentialType")){
str += " credential-type=" + (*i)["credentialType"].toString() + ";";
}
links.push_back(str);
}
if (i->isMember("urls") && (*i)["urls"].isString()){
JSON::Value &u = (*i)["urls"];
std::string str = u.asString()+"; rel=\"ice-server\";";
if (i->isMember("username")){
str += " username=" + (*i)["username"].toString() + ";";
}
if (i->isMember("credential")){
str += " credential=" + (*i)["credential"].toString() + ";";
}
if (i->isMember("credentialType")){
str += " credential-type=" + (*i)["credentialType"].toString() + ";";
}
links.push_back(str);
}
if (i->isMember("urls") && (*i)["urls"].isArray()){
jsonForEach((*i)["urls"], j){
JSON::Value &u = *j;
std::string str = u.asString()+"; rel=\"ice-server\";";
if (i->isMember("username")){
str += " username=" + (*i)["username"].toString() + ";";
}
if (i->isMember("credential")){
str += " credential=" + (*i)["credential"].toString() + ";";
}
if (i->isMember("credentialType")){
str += " credential-type=" + (*i)["credentialType"].toString() + ";";
}
links.push_back(str);
}
}
}
if (links.size()){
if (links.size() == 1){
H.SetHeader("Link", *links.begin());
}else{
std::deque<std::string>::iterator it = links.begin();
std::string linkHeader = *it;
++it;
while (it != links.end()){
linkHeader += "\r\nLink: " + *it;
++it;
}
H.SetHeader("Link", linkHeader);
}
}
if (req.GetVar("constant").size()){
INFO_MSG("Disabling automatic playback rate control");
maxSkipAhead = 1;//disable automatic rate control
}
H.setCORSHeaders();
H.StartResponse("201", "Created", req, myConn);
H.Chunkify(sdpAnswer.toString(), myConn);
H.Chunkify(0, 0, myConn);

View file

@ -90,6 +90,7 @@ namespace Mist{
static void init(Util::Config *cfg);
virtual void sendNext();
virtual void onWebsocketFrame();
void setIceHeaders(HTTP::Parser & H);
virtual void respondHTTP(const HTTP::Parser & req, bool headersOnly);
virtual void preHTTP(){}
virtual void preWebsocketConnect();