mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-01-12 00:04:03 +08:00
305 lines
10 KiB
C++
Executable File
305 lines
10 KiB
C++
Executable File
/*
|
||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||
*
|
||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||
*
|
||
* Use of this source code is governed by MIT-like license that can be found in the
|
||
* LICENSE file in the root of the source tree. All contributing project authors
|
||
* may be found in the AUTHORS file in the root of the source tree.
|
||
*/
|
||
|
||
#include "Network/TcpClient.h"
|
||
#include "Common/config.h"
|
||
#include "Common/Parser.h"
|
||
#include "WebRtcClient.h"
|
||
|
||
using namespace std;
|
||
using namespace toolkit;
|
||
|
||
namespace mediakit {
|
||
|
||
// # WebRTCUrl format
|
||
// ## whep/whip over http sfu: webrtc://server_host:server_port/{{app}}/{{streamid}}
|
||
// ## whep/whip over https sfu: webrtcs://server_host:server_port/{{app}}/{{streamid}}
|
||
// ## websocket p2p: webrtc://{{signaling_server_host}}:{{signaling_server_port}}/{{app}}/{{streamid}}?room_id={{peer_room_id}}
|
||
// ## websockets p2p: webrtcs://{{signaling_server_host}}:{{signaling_server_port}}/{{app}}/{{streamid}}?room_id={{peer_room_id}}
|
||
void WebRTCUrl::parse(const string &strUrl, bool isPlayer) {
|
||
DebugL << "url: " << strUrl;
|
||
_full_url = strUrl;
|
||
auto url = strUrl;
|
||
auto pos = url.find("?");
|
||
if (pos != string::npos) {
|
||
_params = url.substr(pos + 1);
|
||
url.erase(pos);
|
||
}
|
||
|
||
auto schema_pos = url.find("://");
|
||
if (schema_pos != string::npos) {
|
||
auto schema = url.substr(0, schema_pos);
|
||
_is_ssl = strcasecmp(schema.data(), "webrtcs") == 0;
|
||
} else {
|
||
schema_pos = -3;
|
||
}
|
||
// set default port
|
||
_port = _is_ssl ? 443 : 80;
|
||
auto split_vec = split(url.substr(schema_pos + 3), "/");
|
||
if (split_vec.size() > 0) {
|
||
splitUrl(split_vec[0], _host, _port);
|
||
_vhost = _host;
|
||
if (_vhost == "localhost" || isIP(_vhost.data())) {
|
||
// 如果访问的是localhost或ip,那么则为默认虚拟主机
|
||
_vhost = DEFAULT_VHOST;
|
||
}
|
||
}
|
||
if (split_vec.size() > 1) {
|
||
_app = split_vec[1];
|
||
}
|
||
if (split_vec.size() > 2) {
|
||
string stream_id;
|
||
for (size_t i = 2; i < split_vec.size(); ++i) {
|
||
stream_id.append(split_vec[i] + "/");
|
||
}
|
||
if (stream_id.back() == '/') {
|
||
stream_id.pop_back();
|
||
}
|
||
_stream = stream_id;
|
||
}
|
||
|
||
// for vhost
|
||
auto kv = Parser::parseArgs(_params);
|
||
auto it = kv.find(VHOST_KEY);
|
||
if (it != kv.end()) {
|
||
_vhost = it->second;
|
||
}
|
||
|
||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||
if (!enableVhost || _vhost.empty()) {
|
||
// 如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
|
||
_vhost = DEFAULT_VHOST;
|
||
}
|
||
|
||
// for peer_room_id
|
||
it = kv.find("peer_room_id");
|
||
if (it != kv.end()) {
|
||
_peer_room_id = it->second;
|
||
}
|
||
|
||
it = kv.find("signaling_protocols");
|
||
if (it != kv.end()) {
|
||
_signaling_protocols = (WebRtcTransport::SignalingProtocols)(stoi(it->second));
|
||
}
|
||
|
||
auto suffix = _host + ":" + to_string(_port);
|
||
suffix += (isPlayer ? "/index/api/whep" : "/index/api/whip");
|
||
suffix += "?app=" + _app + "&stream=" + _stream;
|
||
if (!_params.empty()) {
|
||
suffix += "&" + _params;
|
||
}
|
||
if (_is_ssl) {
|
||
_negotiate_url = StrPrinter << "https://" << suffix << endl;
|
||
} else {
|
||
_negotiate_url = StrPrinter << "http://" << suffix << endl;
|
||
}
|
||
}
|
||
|
||
//////////// WebRtcClient //////////////////////////
|
||
|
||
WebRtcClient::WebRtcClient(toolkit::EventPoller::Ptr poller) {
|
||
DebugL;
|
||
_poller = poller ? std::move(poller) : EventPollerPool::Instance().getPoller();
|
||
}
|
||
|
||
WebRtcClient::~WebRtcClient() {
|
||
doBye();
|
||
DebugL;
|
||
}
|
||
|
||
void WebRtcClient::startConnect() {
|
||
DebugL;
|
||
doNegotiate();
|
||
}
|
||
|
||
void WebRtcClient::connectivityCheck() {
|
||
DebugL;
|
||
return _transport->connectivityCheckForSFU();
|
||
}
|
||
|
||
void WebRtcClient::onNegotiateFinish() {
|
||
DebugL;
|
||
_is_negotiate_finished = true;
|
||
if (WebRtcTransport::SignalingProtocols::WEBSOCKET == _url._signaling_protocols) {
|
||
// P2P模式需要gathering candidates
|
||
gatheringCandidate(_peer->getIceServer());
|
||
} else if (WebRtcTransport::SignalingProtocols::WHEP_WHIP == _url._signaling_protocols) {
|
||
// SFU模式不会存在IP不通的情况, answer中就携带了candidates, 直接进行connectivityCheck
|
||
connectivityCheck();
|
||
}
|
||
}
|
||
|
||
void WebRtcClient::doNegotiate() {
|
||
DebugL;
|
||
switch (_url._signaling_protocols) {
|
||
case WebRtcTransport::SignalingProtocols::WHEP_WHIP: return doNegotiateWhepOrWhip();
|
||
case WebRtcTransport::SignalingProtocols::WEBSOCKET: return doNegotiateWebsocket();
|
||
default: throw std::invalid_argument(StrPrinter << "not support signaling_protocols: " << (int)_url._signaling_protocols);
|
||
}
|
||
}
|
||
|
||
void WebRtcClient::doNegotiateWhepOrWhip() {
|
||
DebugL << _url._negotiate_url;
|
||
|
||
weak_ptr<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(shared_from_this());
|
||
auto offer_sdp = _transport->createOfferSdp();
|
||
DebugL << "send offer:\n" << offer_sdp;
|
||
|
||
_negotiate = make_shared<HttpRequester>();
|
||
_negotiate->setMethod("POST");
|
||
_negotiate->addHeader("Content-Type", "application/sdp");
|
||
_negotiate->setBody(std::move(offer_sdp));
|
||
_negotiate->startRequester(_url._negotiate_url, [weak_self](const toolkit::SockException &ex, const Parser &response) {
|
||
auto strong_self = weak_self.lock();
|
||
if (!strong_self) {
|
||
return;
|
||
}
|
||
if (ex) {
|
||
WarnL << "network err:" << ex;
|
||
strong_self->onResult(ex);
|
||
return;
|
||
}
|
||
|
||
DebugL << "status:" << response.status() << "\r\n"
|
||
<< "Location:\r\n"
|
||
<< response.getHeader()["Location"] << "\r\nrecv answer:\n"
|
||
<< response.content();
|
||
strong_self->_url._delete_url = response.getHeader()["Location"];
|
||
if ("201" != response.status()) {
|
||
strong_self->onResult(SockException(Err_other, response.content()));
|
||
return;
|
||
}
|
||
strong_self->_transport->setAnswerSdp(response.content());
|
||
strong_self->onNegotiateFinish();
|
||
}, getTimeOutSec());
|
||
}
|
||
|
||
void WebRtcClient::doNegotiateWebsocket() {
|
||
DebugL;
|
||
#if 0
|
||
//TODO: 当前暂将每一路呼叫都使用一个独立的peer_connection,不复用
|
||
_peer = getWebrtcRoomKeeper(_url._host, _url._port);
|
||
if (_peer) {
|
||
checkIn();
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
// 未注册的,先增加注册流程,并在此次播放结束后注销
|
||
InfoL << (StrPrinter << "register to signaling server " << _url._host << "::" << _url._port << " first");
|
||
auto room_id = "ringing_" + makeRandStr(16);
|
||
_peer = make_shared<WebRtcSignalingPeer>(_url._host, _url._port, _url._is_ssl, room_id);
|
||
weak_ptr<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(shared_from_this());
|
||
_peer->setOnConnect([weak_self](const SockException &ex) {
|
||
if (auto strong_self = weak_self.lock()) {
|
||
if (ex) {
|
||
strong_self->onResult(ex);
|
||
return;
|
||
}
|
||
|
||
auto cb = [weak_self](const SockException &ex, const string &key) {
|
||
if (auto strong_self = weak_self.lock()) {
|
||
strong_self->checkIn();
|
||
}
|
||
};
|
||
strong_self->_peer->regist(cb);
|
||
}
|
||
});
|
||
_peer->connect();
|
||
}
|
||
|
||
void WebRtcClient::checkIn() {
|
||
DebugL;
|
||
weak_ptr<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(shared_from_this());
|
||
auto tuple = MediaTuple(_url._vhost, _url._app, _url._stream, _url._params);
|
||
_peer->checkIn(_url._peer_room_id, tuple, _transport->getIdentifier(), _transport->createOfferSdp(), isPlayer(),
|
||
[weak_self](const SockException &ex, const std::string &answer) {
|
||
auto strong_self = weak_self.lock();
|
||
if (!strong_self) {
|
||
return;
|
||
}
|
||
if (ex) {
|
||
WarnL << "network err:" << ex;
|
||
strong_self->onResult(ex);
|
||
return;
|
||
}
|
||
|
||
strong_self->_transport->setAnswerSdp(answer);
|
||
strong_self->onNegotiateFinish();
|
||
}, getTimeOutSec());
|
||
}
|
||
|
||
void WebRtcClient::checkOut() {
|
||
DebugL;
|
||
auto tuple = MediaTuple(_url._vhost, _url._app, _url._stream);
|
||
if (_peer) {
|
||
_peer->checkOut(_url._peer_room_id);
|
||
_peer->unregist([](const SockException &ex) {});
|
||
}
|
||
}
|
||
|
||
void WebRtcClient::candidate(const std::string &candidate, const std::string &ufrag, const std::string &pwd) {
|
||
_peer->candidate(_transport->getIdentifier(), candidate, ufrag, pwd);
|
||
}
|
||
|
||
void WebRtcClient::gatheringCandidate(IceServerInfo::Ptr ice_server) {
|
||
DebugL;
|
||
std::weak_ptr<WebRtcClient> weak_self = std::static_pointer_cast<WebRtcClient>(shared_from_this());
|
||
_transport->gatheringCandidate(ice_server, [weak_self](const std::string& transport_identifier, const std::string& candidate,
|
||
const std::string& ufrag, const std::string& pwd) {
|
||
auto strong_self = weak_self.lock();
|
||
if (!strong_self) {
|
||
return;
|
||
}
|
||
strong_self->candidate(candidate, ufrag, pwd);
|
||
});
|
||
}
|
||
|
||
void WebRtcClient::doBye() {
|
||
DebugL;
|
||
if (!_is_negotiate_finished) {
|
||
return;
|
||
}
|
||
|
||
switch (_url._signaling_protocols) {
|
||
case WebRtcTransport::SignalingProtocols::WHEP_WHIP: return doByeWhepOrWhip();
|
||
case WebRtcTransport::SignalingProtocols::WEBSOCKET: return checkOut();
|
||
default: throw std::invalid_argument(StrPrinter << "not support signaling_protocols: " << (int)_url._signaling_protocols);
|
||
}
|
||
_is_negotiate_finished = false;
|
||
}
|
||
|
||
void WebRtcClient::doByeWhepOrWhip() {
|
||
DebugL;
|
||
if (!_negotiate) {
|
||
return;
|
||
}
|
||
_negotiate->setMethod("DELETE");
|
||
_negotiate->setBody("");
|
||
_negotiate->startRequester(_url._delete_url, [](const toolkit::SockException &ex, const Parser &response) {
|
||
if (ex) {
|
||
WarnL << "network err:" << ex;
|
||
return;
|
||
}
|
||
DebugL << "status:" << response.status();
|
||
}, getTimeOutSec());
|
||
}
|
||
|
||
float WebRtcClient::getTimeOutSec() {
|
||
GET_CONFIG(uint32_t, timeout, Rtc::kTimeOutSec);
|
||
if (timeout <= 0) {
|
||
WarnL << "config rtc. " << Rtc::kTimeOutSec << ": " << timeout << " not vaild";
|
||
return 5.0;
|
||
}
|
||
return (float)timeout;
|
||
}
|
||
|
||
} /* namespace mediakit */
|