Some checks failed
ZaStoGram source guards / guards (push) Failing after 35s
Build three ZaStoGram APKs / build (armeabi-v7a, ZaStoGram-standalone-armeabi-v7a, Armv7, armv7) (push) Failing after 2m23s
Build three ZaStoGram APKs / build (arm64-v8a, ZaStoGram-standalone-arm64-v8a, Arm64, arm64) (push) Failing after 2m25s
Build three ZaStoGram APKs / build (x86, ZaStoGram-standalone-x86, X86, x86) (push) Failing after 2m31s
Слияние DrKLO/Telegram c84801762 (12.10.4) и обновления подмодулей.
- Пакет прокси остаётся в org/telegram/proxy; из апстрима взята поддержка
адреса WEB-прокси с путём (ProxySettings.webProxy, normalizeAddress) внутри
нашего движка WebProxyTransport.
- Переключатель «прокси для звонков» убран вслед за апстримом: звонки
больше не читают proxy_enabled_calls.
- Ротация прокси по-прежнему не показывается для WEB-прокси: наш
ProxyRotationController их не поддерживает.
- Страж check_web_proxy_isolation проверяет валидацию через
ProxySettings.webProxy.
- Подмодули absl, wamr, tlottie, media — на версиях апстрима.
2068 lines
82 KiB
C++
2068 lines
82 KiB
C++
#include "v2/InstanceV2ReferenceImpl.h"
|
||
|
||
#include "LogSinkImpl.h"
|
||
#include "VideoCaptureInterfaceImpl.h"
|
||
#include "VideoCapturerInterface.h"
|
||
#include "v2/NativeNetworkingImpl.h"
|
||
#include "v2/Signaling.h"
|
||
#include "v2/ContentNegotiation.h"
|
||
|
||
#include "CodecSelectHelper.h"
|
||
|
||
#include "api/audio_codecs/audio_decoder_factory_template.h"
|
||
#include "api/audio_codecs/audio_encoder_factory_template.h"
|
||
#include "api/audio_codecs/opus/audio_decoder_opus.h"
|
||
#include "api/audio_codecs/opus/audio_decoder_multi_channel_opus.h"
|
||
#include "api/audio_codecs/opus/audio_encoder_opus.h"
|
||
#include "api/audio_codecs/L16/audio_decoder_L16.h"
|
||
#include "api/audio_codecs/L16/audio_encoder_L16.h"
|
||
#include "api/task_queue/default_task_queue_factory.h"
|
||
#include "media/engine/webrtc_media_engine.h"
|
||
#include "system_wrappers/include/field_trial.h"
|
||
#include "api/video/builtin_video_bitrate_allocator_factory.h"
|
||
#include "call/call.h"
|
||
#include "api/call/audio_sink.h"
|
||
#include "modules/audio_processing/audio_buffer.h"
|
||
#include "absl/strings/match.h"
|
||
#include "audio/audio_state.h"
|
||
#include "modules/audio_coding/neteq/default_neteq_factory.h"
|
||
#include "modules/audio_coding/include/audio_coding_module.h"
|
||
#include "api/candidate.h"
|
||
#include "api/jsep_ice_candidate.h"
|
||
#include "pc/used_ids.h"
|
||
#include "media/base/sdp_video_format_utils.h"
|
||
#include "pc/media_session.h"
|
||
#include "rtc_base/rtc_certificate_generator.h"
|
||
#include "pc/peer_connection.h"
|
||
#include "pc/peer_connection_proxy.h"
|
||
#include "api/rtc_event_log/rtc_event_log_factory.h"
|
||
#include "api/stats/rtc_stats_report.h"
|
||
#include "api/enable_media.h"
|
||
#include "p2p/client/basic_port_allocator.h"
|
||
#include "p2p/base/basic_packet_socket_factory.h"
|
||
#include "rtc_base/network.h"
|
||
#include "p2p/base/default_ice_transport_factory.h"
|
||
|
||
#include "AudioFrame.h"
|
||
#include "ThreadLocalObject.h"
|
||
#include "Manager.h"
|
||
#include "NetworkManager.h"
|
||
#include "VideoCaptureInterfaceImpl.h"
|
||
#include "platform/PlatformInterface.h"
|
||
#include "LogSinkImpl.h"
|
||
#include "CodecSelectHelper.h"
|
||
#include "AudioDeviceHelper.h"
|
||
#include "SignalingEncryption.h"
|
||
#include "ReflectorRelayPortFactory.h"
|
||
#include "v2/SignalingConnection.h"
|
||
#include "v2/ExternalSignalingConnection.h"
|
||
#include "v2/SignalingSctpConnection.h"
|
||
#include "v2/ReflectorRelayPortFactory.h"
|
||
#include "v2/MtProtoIceTransport.h"
|
||
#include "v2/CustomParameters.h"
|
||
#ifdef WEBRTC_IOS
|
||
#include "platform/darwin/iOS/tgcalls_audio_device_module_ios.h"
|
||
#endif
|
||
#include <random>
|
||
#include <sstream>
|
||
#include <map>
|
||
#include <set>
|
||
|
||
#include "third-party/json11.hpp"
|
||
#include "utils/gzip.h"
|
||
|
||
namespace tgcalls {
|
||
namespace {
|
||
|
||
enum class SignalingProtocolVersion {
|
||
V1,
|
||
V2
|
||
};
|
||
|
||
SignalingProtocolVersion signalingProtocolVersion(std::string const &version) {
|
||
if (version == "10.0.0") {
|
||
return SignalingProtocolVersion::V1;
|
||
} else if (version == "11.0.0") {
|
||
return SignalingProtocolVersion::V2;
|
||
} else {
|
||
RTC_LOG(LS_ERROR) << "signalingProtocolVersion: unknown version " << version;
|
||
|
||
return SignalingProtocolVersion::V2;
|
||
}
|
||
}
|
||
|
||
bool signalingProtocolSupportsCompression(SignalingProtocolVersion version) {
|
||
switch (version) {
|
||
case SignalingProtocolVersion::V1:
|
||
return false;
|
||
case SignalingProtocolVersion::V2:
|
||
return true;
|
||
default:
|
||
RTC_DCHECK_NOTREACHED();
|
||
break;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(VideoCaptureInterface *videoCapture) {
|
||
return videoCapture
|
||
? static_cast<VideoCaptureInterfaceImpl*>(videoCapture)->object()->getSyncAssumingSameThread()
|
||
: nullptr;
|
||
}
|
||
|
||
class SetSessionDescriptionObserver : public webrtc::SetLocalDescriptionObserverInterface, public webrtc::SetRemoteDescriptionObserverInterface {
|
||
public:
|
||
SetSessionDescriptionObserver(std::function<void(webrtc::RTCError)> &&completion) :
|
||
_completion(std::move(completion)) {
|
||
}
|
||
|
||
virtual void OnSetLocalDescriptionComplete(webrtc::RTCError error) override {
|
||
OnCompelete(error);
|
||
}
|
||
|
||
virtual void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override {
|
||
OnCompelete(error);
|
||
}
|
||
|
||
private:
|
||
void OnCompelete(webrtc::RTCError error) {
|
||
_completion(error);
|
||
}
|
||
|
||
std::function<void(webrtc::RTCError)> _completion;
|
||
};
|
||
|
||
class PeerConnectionDelegateAdapter: public webrtc::PeerConnectionObserver {
|
||
public:
|
||
struct Parameters {
|
||
std::function<void()> onRenegotiationNeeded;
|
||
std::function<void(const webrtc::IceCandidateInterface *)> onIceCandidate;
|
||
std::function<void(webrtc::PeerConnectionInterface::SignalingState state)> onSignalingChange;
|
||
std::function<void(webrtc::PeerConnectionInterface::IceConnectionState state)> onConnectionChange;
|
||
std::function<void(webrtc::scoped_refptr<webrtc::DataChannelInterface>)> onDataChannel;
|
||
std::function<void(webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>)> onTransceiverAdded;
|
||
std::function<void(webrtc::scoped_refptr<webrtc::RtpReceiverInterface>)> onTransceiverRemoved;
|
||
std::function<void(const cricket::CandidatePairChangeEvent &)> onCandidatePairChangeEvent;
|
||
};
|
||
|
||
public:
|
||
PeerConnectionDelegateAdapter(
|
||
Parameters &¶meters
|
||
) : _parameters(std::move(parameters)) {
|
||
}
|
||
|
||
~PeerConnectionDelegateAdapter() override {
|
||
}
|
||
|
||
void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override {
|
||
if (_parameters.onSignalingChange) {
|
||
_parameters.onSignalingChange(new_state);
|
||
}
|
||
}
|
||
|
||
void OnAddStream(webrtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {
|
||
}
|
||
|
||
void OnRemoveStream(webrtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {
|
||
}
|
||
|
||
void OnTrack(webrtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) override {
|
||
if (_parameters.onTransceiverAdded) {
|
||
_parameters.onTransceiverAdded(transceiver);
|
||
}
|
||
}
|
||
|
||
void OnDataChannel(webrtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) override {
|
||
if (_parameters.onDataChannel) {
|
||
_parameters.onDataChannel(data_channel);
|
||
}
|
||
}
|
||
|
||
void OnRenegotiationNeeded() override {
|
||
if (_parameters.onRenegotiationNeeded) {
|
||
_parameters.onRenegotiationNeeded();
|
||
}
|
||
}
|
||
|
||
void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override {
|
||
if (_parameters.onConnectionChange) {
|
||
_parameters.onConnectionChange(new_state);
|
||
}
|
||
}
|
||
|
||
void OnStandardizedIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override {
|
||
}
|
||
|
||
void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) override {
|
||
}
|
||
|
||
void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override {
|
||
}
|
||
|
||
void OnIceCandidate(const webrtc::IceCandidateInterface *candidate) override {
|
||
if (_parameters.onIceCandidate) {
|
||
_parameters.onIceCandidate(candidate);
|
||
}
|
||
}
|
||
|
||
void OnIceCandidatesRemoved(const std::vector<cricket::Candidate> &candidates) override {
|
||
}
|
||
|
||
void OnIceSelectedCandidatePairChanged(const cricket::CandidatePairChangeEvent &event) override {
|
||
if (_parameters.onCandidatePairChangeEvent) {
|
||
_parameters.onCandidatePairChangeEvent(event);
|
||
}
|
||
}
|
||
|
||
void OnAddTrack(webrtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, const std::vector<webrtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams) override {
|
||
|
||
}
|
||
|
||
void OnRemoveTrack(webrtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override {
|
||
if (_parameters.onTransceiverRemoved) {
|
||
_parameters.onTransceiverRemoved(receiver);
|
||
}
|
||
}
|
||
|
||
private:
|
||
Parameters _parameters;
|
||
};
|
||
|
||
class StatsCollectorCallbackAdapter : public webrtc::RTCStatsCollectorCallback {
|
||
public:
|
||
StatsCollectorCallbackAdapter(std::function<void(const webrtc::scoped_refptr<const webrtc::RTCStatsReport> &)> &&completion_) :
|
||
completion(std::move(completion_)) {
|
||
}
|
||
|
||
void OnStatsDelivered(const webrtc::scoped_refptr<const webrtc::RTCStatsReport> &report) override {
|
||
completion(report);
|
||
}
|
||
|
||
private:
|
||
std::function<void(const webrtc::scoped_refptr<const webrtc::RTCStatsReport> &)> completion;
|
||
};
|
||
|
||
class VideoSinkImpl : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
|
||
public:
|
||
VideoSinkImpl() {
|
||
}
|
||
|
||
virtual ~VideoSinkImpl() {
|
||
}
|
||
|
||
virtual void OnFrame(const webrtc::VideoFrame& frame) override {
|
||
//_lastFrame = frame;
|
||
for (int i = (int)(_sinks.size()) - 1; i >= 0; i--) {
|
||
auto strong = _sinks[i].lock();
|
||
if (!strong) {
|
||
_sinks.erase(_sinks.begin() + i);
|
||
} else {
|
||
strong->OnFrame(frame);
|
||
}
|
||
}
|
||
}
|
||
|
||
virtual void OnDiscardedFrame() override {
|
||
for (int i = (int)(_sinks.size()) - 1; i >= 0; i--) {
|
||
auto strong = _sinks[i].lock();
|
||
if (!strong) {
|
||
_sinks.erase(_sinks.begin() + i);
|
||
} else {
|
||
strong->OnDiscardedFrame();
|
||
}
|
||
}
|
||
}
|
||
|
||
void addSink(std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> impl) {
|
||
_sinks.push_back(impl);
|
||
if (_lastFrame) {
|
||
auto strong = impl.lock();
|
||
if (strong) {
|
||
strong->OnFrame(_lastFrame.value());
|
||
}
|
||
}
|
||
}
|
||
|
||
private:
|
||
std::vector<std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>>> _sinks;
|
||
absl::optional<webrtc::VideoFrame> _lastFrame;
|
||
};
|
||
|
||
class DataChannelObserverImpl : public webrtc::DataChannelObserver {
|
||
public:
|
||
struct Parameters {
|
||
std::function<void()> onStateChange;
|
||
std::function<void(webrtc::DataBuffer const &)> onMessage;
|
||
};
|
||
|
||
public:
|
||
DataChannelObserverImpl(Parameters &¶meters) :
|
||
_parameters(std::move(parameters)) {
|
||
}
|
||
|
||
virtual void OnStateChange() override {
|
||
if (_parameters.onStateChange) {
|
||
_parameters.onStateChange();
|
||
}
|
||
}
|
||
|
||
virtual void OnMessage(webrtc::DataBuffer const &buffer) override {
|
||
if (_parameters.onMessage) {
|
||
_parameters.onMessage(buffer);
|
||
}
|
||
}
|
||
|
||
virtual ~DataChannelObserverImpl() {
|
||
}
|
||
|
||
private:
|
||
Parameters _parameters;
|
||
};
|
||
|
||
template<typename T>
|
||
struct StateLogRecord {
|
||
int64_t timestamp = 0;
|
||
T record;
|
||
|
||
explicit StateLogRecord(int32_t timestamp_, T &&record_) :
|
||
timestamp(timestamp_),
|
||
record(std::move(record_)) {
|
||
}
|
||
};
|
||
|
||
struct NetworkStateLogRecord {
|
||
bool isConnected = false;
|
||
bool isFailed = false;
|
||
absl::optional<InstanceNetworking::RouteDescription> route;
|
||
absl::optional<InstanceNetworking::ConnectionDescription> connection;
|
||
|
||
bool operator==(NetworkStateLogRecord const &rhs) const {
|
||
if (isConnected != rhs.isConnected) {
|
||
return false;
|
||
}
|
||
if (isFailed != rhs.isFailed) {
|
||
return false;
|
||
}
|
||
if (route != rhs.route) {
|
||
return false;
|
||
}
|
||
if (connection != rhs.connection) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
};
|
||
|
||
struct NetworkBitrateLogRecord {
|
||
int32_t bitrate = 0;
|
||
};
|
||
|
||
} // namespace
|
||
|
||
class InstanceV2ReferenceImplInternal : public std::enable_shared_from_this<InstanceV2ReferenceImplInternal> {
|
||
public:
|
||
InstanceV2ReferenceImplInternal(Descriptor &&descriptor, std::shared_ptr<Threads> threads) :
|
||
_signalingProtocolVersion(signalingProtocolVersion(descriptor.version)),
|
||
_threads(threads),
|
||
_rtcServers(descriptor.rtcServers),
|
||
_proxy(std::move(descriptor.proxy)),
|
||
_enableP2P(descriptor.config.enableP2P),
|
||
_relayTcpTls(descriptor.config.relayTcpTls),
|
||
_encryptionKey(std::move(descriptor.encryptionKey)),
|
||
_stateUpdated(descriptor.stateUpdated),
|
||
_signalBarsUpdated(descriptor.signalBarsUpdated),
|
||
_audioLevelsUpdated(descriptor.audioLevelsUpdated),
|
||
_remoteBatteryLevelIsLowUpdated(descriptor.remoteBatteryLevelIsLowUpdated),
|
||
_remoteMediaStateUpdated(descriptor.remoteMediaStateUpdated),
|
||
_remotePrefferedAspectRatioUpdated(descriptor.remotePrefferedAspectRatioUpdated),
|
||
_signalingDataEmitted(descriptor.signalingDataEmitted),
|
||
_createAudioDeviceModule(descriptor.createAudioDeviceModule),
|
||
_createWrappedAudioDeviceModule(descriptor.createWrappedAudioDeviceModule),
|
||
_statsLogPath(descriptor.config.statsLogPath),
|
||
_eventLog(std::make_unique<webrtc::RtcEventLogNull>()),
|
||
_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()),
|
||
_videoCapture(descriptor.videoCapture) {
|
||
if (!descriptor.config.customParameters.empty()) {
|
||
std::string parsingError;
|
||
auto customParametersJson = json11::Json::parse(descriptor.config.customParameters, parsingError);
|
||
if (customParametersJson.is_object()) {
|
||
_customParameters = customParametersJson.object_items();
|
||
}
|
||
}
|
||
|
||
_useAddTrack = getCustomParameterBool(_customParameters, "network_reference_use_addtrack");
|
||
|
||
webrtc::field_trial::InitFieldTrialsFromString(
|
||
"WebRTC-DataChannel-Dcsctp/Enabled/"
|
||
"WebRTC-Audio-iOS-Holding/Enabled/"
|
||
);
|
||
}
|
||
|
||
~InstanceV2ReferenceImplInternal() {
|
||
disconnectAllIncomingVideoSinks();
|
||
_currentStrongSink.reset();
|
||
|
||
_threads->getWorkerThread()->BlockingCall([&]() {
|
||
_audioDeviceModule = nullptr;
|
||
});
|
||
|
||
if (_dataChannel) {
|
||
_dataChannel->UnregisterObserver();
|
||
_dataChannel = nullptr;
|
||
}
|
||
_dataChannelObserver.reset();
|
||
|
||
_peerConnection = nullptr;
|
||
_peerConnectionObserver.reset();
|
||
_peerConnectionFactory = nullptr;
|
||
}
|
||
|
||
void start() {
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
|
||
PlatformInterface::SharedInstance()->configurePlatformAudio();
|
||
|
||
RTC_DCHECK(_threads->getMediaThread()->IsCurrent());
|
||
|
||
if (_signalingProtocolVersion == SignalingProtocolVersion::V2) {
|
||
_signalingConnection = std::make_unique<SignalingSctpConnection>(
|
||
_threads,
|
||
[threads = _threads, weak](const std::vector<uint8_t> &data) {
|
||
threads->getMediaThread()->PostTask([weak, data] {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
strong->onSignalingData(data);
|
||
});
|
||
},
|
||
[signalingDataEmitted = _signalingDataEmitted](const std::vector<uint8_t> &data) {
|
||
signalingDataEmitted(data);
|
||
},
|
||
_encryptionKey.isOutgoing
|
||
);
|
||
}
|
||
if (!_signalingConnection) {
|
||
_signalingConnection = std::make_unique<ExternalSignalingConnection>(
|
||
[threads = _threads, weak](const std::vector<uint8_t> &data) {
|
||
threads->getMediaThread()->PostTask([weak, data] {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
strong->onSignalingData(data);
|
||
});
|
||
},
|
||
[signalingDataEmitted = _signalingDataEmitted](const std::vector<uint8_t> &data) {
|
||
signalingDataEmitted(data);
|
||
}
|
||
);
|
||
}
|
||
|
||
_signalingConnection->start();
|
||
|
||
_threads->getWorkerThread()->BlockingCall([&]() {
|
||
_audioDeviceModule = createAudioDeviceModule();
|
||
});
|
||
|
||
webrtc::PeerConnectionFactoryDependencies peerConnectionFactoryDependencies;
|
||
peerConnectionFactoryDependencies.network_thread = _threads->getNetworkThread();
|
||
peerConnectionFactoryDependencies.signaling_thread = _threads->getMediaThread();
|
||
peerConnectionFactoryDependencies.worker_thread = _threads->getWorkerThread();
|
||
peerConnectionFactoryDependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
|
||
peerConnectionFactoryDependencies.network_monitor_factory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory();
|
||
|
||
peerConnectionFactoryDependencies.adm = _audioDeviceModule;
|
||
|
||
webrtc:: AudioProcessingBuilder builder;
|
||
peerConnectionFactoryDependencies.audio_processing = builder.Create();
|
||
|
||
peerConnectionFactoryDependencies.audio_encoder_factory = webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus>();
|
||
peerConnectionFactoryDependencies.audio_decoder_factory = webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus>();
|
||
|
||
peerConnectionFactoryDependencies.video_encoder_factory = PlatformInterface::SharedInstance()->makeVideoEncoderFactory(true);
|
||
peerConnectionFactoryDependencies.video_decoder_factory = PlatformInterface::SharedInstance()->makeVideoDecoderFactory();
|
||
|
||
webrtc::EnableMedia(peerConnectionFactoryDependencies);
|
||
|
||
peerConnectionFactoryDependencies.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(peerConnectionFactoryDependencies.task_queue_factory.get());
|
||
|
||
_peerConnectionFactory = webrtc::CreateModularPeerConnectionFactory(std::move(peerConnectionFactoryDependencies));
|
||
|
||
_useMtProto = getCustomParameterBool(_customParameters, "network_use_mtproto");
|
||
if (_useMtProto) {
|
||
// Selects CreateUnencryptedRtpTransport - a plain RtpTransport, the
|
||
// same class 13.0.0 uses - instead of DtlsSrtpTransport. NOT optional:
|
||
// SrtpTransport hard-fails when SRTP is inactive (send returns false,
|
||
// receive drops), so without this the call is either double-encrypted
|
||
// or dead. Nothing ends up unencrypted: mtproto replaces DTLS-SRTP.
|
||
// Must precede CreatePeerConnectionOrError, where DtlsEnabled() is read.
|
||
webrtc::PeerConnectionFactoryInterface::Options factoryOptions;
|
||
factoryOptions.disable_encryption = true;
|
||
_peerConnectionFactory->SetOptions(factoryOptions);
|
||
}
|
||
|
||
webrtc::PeerConnectionDependencies peerConnectionDependencies(nullptr);
|
||
|
||
PeerConnectionDelegateAdapter::Parameters delegateParameters;
|
||
delegateParameters.onRenegotiationNeeded = [weak, threads = _threads]() {
|
||
threads->getMediaThread()->PostTask([weak]() {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
if (strong->_isMakingOffer) {
|
||
// An offer is already in flight and it already covers whatever
|
||
// triggered this event - the data channel created moments ago in
|
||
// start(). Stock suppresses this via the is_negotiation_needed_
|
||
// latch; we override the legacy OnRenegotiationNeeded, which
|
||
// bypasses that, so we suppress it here instead.
|
||
RTC_LOG(LS_INFO) << "onRenegotiationNeeded: offer already in flight, skipping";
|
||
return;
|
||
}
|
||
|
||
if (strong->_didBeginNegotiation) {
|
||
if (strong->_encryptionKey.isOutgoing || strong->_peerConnection->remote_description()) {
|
||
strong->sendLocalDescription();
|
||
}
|
||
} else {
|
||
RTC_LOG(LS_INFO) << "onRenegotiationNeeded: not sending local description";
|
||
}
|
||
});
|
||
};
|
||
delegateParameters.onIceCandidate = [weak](const webrtc::IceCandidateInterface *iceCandidate) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
strong->sendIceCandidate(iceCandidate);
|
||
};
|
||
delegateParameters.onSignalingChange = [weak](webrtc::PeerConnectionInterface::SignalingState state) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
/*switch (state) {
|
||
case webrtc::PeerConnectionInterface::SignalingState::kStable: {
|
||
State mappedState = State::Established;
|
||
strong->_stateUpdated(mappedState);
|
||
break;
|
||
}
|
||
default: {
|
||
State mappedState = State::Reconnecting;
|
||
strong->_stateUpdated(mappedState);
|
||
break;
|
||
}
|
||
}*/
|
||
};
|
||
delegateParameters.onConnectionChange = [weak](webrtc::PeerConnectionInterface::IceConnectionState state) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
bool isConnected = false;
|
||
switch (state) {
|
||
case webrtc::PeerConnectionInterface::IceConnectionState::kIceConnectionConnected:
|
||
case webrtc::PeerConnectionInterface::IceConnectionState::kIceConnectionCompleted: {
|
||
isConnected = true;
|
||
break;
|
||
}
|
||
default: {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (state == webrtc::PeerConnectionInterface::IceConnectionState::kIceConnectionFailed) {
|
||
strong->maybeRestartIce();
|
||
}
|
||
|
||
strong->updateIsConnected(isConnected);
|
||
};
|
||
delegateParameters.onDataChannel = [weak](webrtc::scoped_refptr<webrtc::DataChannelInterface> dataChannel) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
if (!strong->_dataChannel) {
|
||
strong->attachDataChannel(dataChannel);
|
||
} else {
|
||
RTC_LOG(LS_WARNING) << "onDataChannel invoked, but data channel already exists";
|
||
}
|
||
};
|
||
delegateParameters.onTransceiverAdded = [weak](webrtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
if (!transceiver->mid()) {
|
||
return;
|
||
}
|
||
std::string mid = transceiver->mid().value();
|
||
|
||
switch (transceiver->media_type()) {
|
||
case cricket::MediaType::MEDIA_TYPE_VIDEO: {
|
||
if (strong->_incomingVideoTransceivers.find(mid) == strong->_incomingVideoTransceivers.end()) {
|
||
strong->_incomingVideoTransceivers.insert(std::make_pair(mid, transceiver));
|
||
|
||
strong->connectIncomingVideoSink(transceiver);
|
||
}
|
||
break;
|
||
}
|
||
default: {
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
delegateParameters.onTransceiverRemoved = [weak](webrtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
// _incomingVideoTransceivers is keyed by mid (see onTransceiverAdded),
|
||
// but RtpReceiverInterface exposes no mid() - receiver->track()->id()
|
||
// is the TRACK id, so looking it up by that never matched and entries
|
||
// were never erased. Find the entry by its receiver instead.
|
||
for (auto it = strong->_incomingVideoTransceivers.begin(); it != strong->_incomingVideoTransceivers.end(); it++) {
|
||
if (it->second->receiver() != receiver) {
|
||
continue;
|
||
}
|
||
|
||
strong->disconnectIncomingVideoSink(it->second);
|
||
strong->_incomingVideoTransceivers.erase(it);
|
||
|
||
break;
|
||
}
|
||
};
|
||
delegateParameters.onCandidatePairChangeEvent = [weak](const cricket::CandidatePairChangeEvent &event) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
InstanceNetworking::ConnectionDescription connectionDescription;
|
||
|
||
connectionDescription.local = InstanceNetworking::connectionDescriptionFromCandidate(event.selected_candidate_pair.local);
|
||
connectionDescription.remote = InstanceNetworking::connectionDescriptionFromCandidate(event.selected_candidate_pair.remote);
|
||
|
||
if (!strong->_currentConnectionDescription || strong->_currentConnectionDescription.value() != connectionDescription) {
|
||
strong->_currentConnectionDescription = std::move(connectionDescription);
|
||
strong->onNetworkStateUpdated();
|
||
}
|
||
};
|
||
_peerConnectionObserver = std::make_unique<PeerConnectionDelegateAdapter>(std::move(delegateParameters));
|
||
|
||
peerConnectionDependencies.observer = _peerConnectionObserver.get();
|
||
|
||
_networkMonitorFactory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory();
|
||
_socketFactory = std::make_unique<rtc::BasicPacketSocketFactory>(_threads->getNetworkThread()->socketserver());
|
||
_networkManager = std::make_unique<rtc::BasicNetworkManager>(_networkMonitorFactory.get(), _threads->getNetworkThread()->socketserver());
|
||
_relayPortFactory = std::make_unique<ReflectorRelayPortFactory>(_rtcServers, false, 0, _threads->getNetworkThread()->socketserver(), getCustomParameterBool(_customParameters, "network_reflector_resolve_remote_candidate_ip"));
|
||
|
||
auto portAllocator = std::make_unique<cricket::BasicPortAllocator>(_networkManager.get(), _socketFactory.get(), nullptr, _relayPortFactory.get());
|
||
|
||
if (getCustomParameterBool(_customParameters, "network_disable_stun_when_unconfigured")) {
|
||
bool hasStunServer = false;
|
||
for (const auto &server : _rtcServers) {
|
||
// Unlike the mapping loop below, this does not check address.IsComplete(),
|
||
// so a malformed STUN host still counts as "has STUN" here. That only
|
||
// suppresses PORTALLOCATOR_DISABLE_STUN, i.e. it errs on the safe side.
|
||
if (!server.isTurn && !server.isTcp) {
|
||
hasStunServer = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!hasStunServer) {
|
||
// PeerConnection forces PORTALLOCATOR_ENABLE_SHARED_SOCKET on every
|
||
// allocator, which auto-promotes each UDP relay into the STUN server
|
||
// set and sends it real Binding Requests. A reflector cannot parse
|
||
// those - it expects a 16-byte peer tag first.
|
||
//
|
||
// The WebRTC-UseTurnServerAsStunServer field trial does NOT help
|
||
// here: BasicPortAllocator bypasses it when the STUN set is empty,
|
||
// which is exactly the reflector case.
|
||
//
|
||
// InitializePortAllocator_n ORs onto the existing flags, so setting
|
||
// this before the allocator is moved survives.
|
||
portAllocator->set_flags(portAllocator->flags() | cricket::PORTALLOCATOR_DISABLE_STUN);
|
||
}
|
||
}
|
||
|
||
peerConnectionDependencies.allocator = std::move(portAllocator);
|
||
|
||
webrtc::PeerConnectionInterface::RTCConfiguration peerConnectionConfiguration;
|
||
if (_enableP2P) {
|
||
peerConnectionConfiguration.type = webrtc::PeerConnectionInterface::IceTransportsType::kAll;
|
||
} else {
|
||
peerConnectionConfiguration.type = webrtc::PeerConnectionInterface::IceTransportsType::kRelay;
|
||
}
|
||
peerConnectionConfiguration.tcp_candidate_policy = webrtc::PeerConnectionInterface::TcpCandidatePolicy::kTcpCandidatePolicyDisabled;
|
||
peerConnectionConfiguration.enable_ice_renomination = true;
|
||
peerConnectionConfiguration.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
|
||
peerConnectionConfiguration.bundle_policy = webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle;
|
||
peerConnectionConfiguration.rtcp_mux_policy = webrtc::PeerConnectionInterface::RtcpMuxPolicy::kRtcpMuxPolicyRequire;
|
||
peerConnectionConfiguration.enable_implicit_rollback = true;
|
||
peerConnectionConfiguration.continual_gathering_policy = webrtc::PeerConnectionInterface::ContinualGatheringPolicy::GATHER_CONTINUALLY;
|
||
peerConnectionConfiguration.audio_jitter_buffer_fast_accelerate = true;
|
||
peerConnectionConfiguration.prioritize_most_likely_ice_candidate_pairs = true;
|
||
|
||
const bool allowHostnameIceServers = getCustomParameterBoolDefaultTrue(_customParameters, "network_reference_allow_hostname_ice_servers");
|
||
|
||
for (auto &server : _rtcServers) {
|
||
// Резервные маршруты по TCP и TLS имеют смысл только для релеев и
|
||
// только когда их разрешили: без этого берутся одни UDP-адреса.
|
||
if (server.isTcp && !(_relayTcpTls && server.isTurn)) {
|
||
continue;
|
||
}
|
||
|
||
rtc::SocketAddress address(server.host, server.port);
|
||
|
||
// SocketAddress is only "complete" for IP literals, so this check used
|
||
// to discard every TURN/STUN server given as a DNS name - which is how
|
||
// Cloudflare TURN is configured. With enableP2P=false (kRelay) and TCP
|
||
// candidates disabled, a hostname-only server set gathers ZERO
|
||
// candidates and the call can never connect.
|
||
//
|
||
// HostAsURIString() already returns a non-literal host unchanged and
|
||
// brackets IPv6 literals, and ParseIceServersOrError resolves names
|
||
// itself, so passing the hostname through is all that is needed.
|
||
//
|
||
// Reflectors are exempt: ReflectorRelayPortFactory matches the
|
||
// allocator's relay address against SocketAddress(host, port) by
|
||
// equality, which cannot match an unresolved hostname - it would
|
||
// return no port at all. Reflectors are always IP literals in
|
||
// practice, so this only keeps the previous behaviour for them.
|
||
if (!address.IsComplete()) {
|
||
const bool isReflector = server.isTurn && server.login == "reflector";
|
||
if (!allowHostnameIceServers || isReflector || server.host.empty()) {
|
||
RTC_LOG(LS_ERROR) << "Invalid ICE server host: " << server.host;
|
||
continue;
|
||
}
|
||
RTC_LOG(LS_INFO) << "Passing through non-literal ICE server host: " << server.host;
|
||
}
|
||
|
||
if (server.isTurn) {
|
||
if (server.login.empty() || server.password.empty()) {
|
||
// ParseIceServersOrError returns on the FIRST bad entry, so one
|
||
// empty credential would kill the whole list. Skip just this one.
|
||
RTC_LOG(LS_ERROR) << "Skipping TURN server with empty credentials: " << server.host;
|
||
continue;
|
||
}
|
||
|
||
webrtc::PeerConnectionInterface::IceServer mappedServer;
|
||
|
||
const std::string scheme = server.isTls ? "turns:" : "turn:";
|
||
const std::string transport = server.isTcp ? "?transport=tcp" : "";
|
||
const std::string url =
|
||
scheme + address.HostAsURIString() + ":" + std::to_string(server.port) + transport;
|
||
RTC_LOG(LS_INFO) << "Adding ICE server: " << url;
|
||
mappedServer.urls.push_back(url);
|
||
mappedServer.username = server.login;
|
||
mappedServer.password = server.password;
|
||
if (server.isTls) {
|
||
// Релей адресуется по IP, поэтому цепочку сертификата проверять
|
||
// не по чему. TLS здесь не защищает медиа — оно зашифровано выше
|
||
// по стеку — а только придаёт трафику вид HTTPS.
|
||
mappedServer.tls_cert_policy = webrtc::PeerConnectionInterface::TlsCertPolicy::kTlsCertPolicyInsecureNoCheck;
|
||
}
|
||
|
||
peerConnectionConfiguration.servers.push_back(mappedServer);
|
||
} else {
|
||
webrtc::PeerConnectionInterface::IceServer mappedServer;
|
||
|
||
mappedServer.urls.push_back(
|
||
"stun:" + address.HostAsURIString() + ":" + std::to_string(server.port));
|
||
|
||
peerConnectionConfiguration.servers.push_back(mappedServer);
|
||
}
|
||
}
|
||
|
||
if (_useMtProto) {
|
||
peerConnectionDependencies.ice_transport_factory = std::make_unique<MtProtoIceTransportFactory>(_encryptionKey);
|
||
}
|
||
|
||
auto peerConnectionOrError = _peerConnectionFactory->CreatePeerConnectionOrError(peerConnectionConfiguration, std::move(peerConnectionDependencies));
|
||
if (peerConnectionOrError.ok()) {
|
||
_peerConnection = peerConnectionOrError.value();
|
||
} else {
|
||
RTC_LOG(LS_ERROR) << "CreatePeerConnectionOrError failed: " << peerConnectionOrError.error().message();
|
||
_isFailed = true;
|
||
onNetworkStateUpdated();
|
||
return;
|
||
}
|
||
|
||
if (_peerConnection) {
|
||
RTC_LOG(LS_INFO) << "Creating Data Channel";
|
||
|
||
if (_encryptionKey.isOutgoing) {
|
||
webrtc::DataChannelInit dataChannelInit;
|
||
webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::DataChannelInterface>> dataChannelOrError = _peerConnection->CreateDataChannelOrError("data", &dataChannelInit);
|
||
if (dataChannelOrError.ok()) {
|
||
attachDataChannel(dataChannelOrError.value());
|
||
}
|
||
}
|
||
|
||
webrtc::RtpTransceiverInit transceiverInit;
|
||
transceiverInit.stream_ids = { "0" };
|
||
|
||
cricket::AudioOptions audioSourceOptions;
|
||
webrtc::scoped_refptr<webrtc::AudioSourceInterface> audioSource = _peerConnectionFactory->CreateAudioSource(audioSourceOptions);
|
||
|
||
webrtc::scoped_refptr<webrtc::AudioTrackInterface> audioTrack = _peerConnectionFactory->CreateAudioTrack("0", audioSource.get());
|
||
webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>> audioTransceiverOrError = [&]() -> webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>> {
|
||
if (!_useAddTrack) {
|
||
return _peerConnection->AddTransceiver(audioTrack, transceiverInit);
|
||
}
|
||
// AddTrack sets created_by_addtrack(), which
|
||
// FindAvailableTransceiverToReceive requires before it will associate
|
||
// the callee's own transceiver with the offerer's m= section. Without
|
||
// it the callee mints a fresh recvonly transceiver and needs a second
|
||
// offer/answer before it can send.
|
||
auto senderOrError = _peerConnection->AddTrack(audioTrack, transceiverInit.stream_ids);
|
||
if (!senderOrError.ok()) {
|
||
return senderOrError.MoveError();
|
||
}
|
||
for (const auto &transceiver : _peerConnection->GetTransceivers()) {
|
||
if (transceiver->sender() == senderOrError.value()) {
|
||
return transceiver;
|
||
}
|
||
}
|
||
return webrtc::RTCError(webrtc::RTCErrorType::INTERNAL_ERROR, "AddTrack produced no matching transceiver");
|
||
}();
|
||
if (audioTransceiverOrError.ok()) {
|
||
_outgoingAudioTrack = audioTrack;
|
||
_outgoingAudioTransceiver = audioTransceiverOrError.value();
|
||
|
||
webrtc::RtpParameters parameters = _outgoingAudioTransceiver->sender()->GetParameters();
|
||
if (parameters.encodings.empty()) {
|
||
parameters.encodings.push_back(webrtc::RtpEncodingParameters());
|
||
}
|
||
parameters.encodings[0].max_bitrate_bps = 32 * 1024;
|
||
_outgoingAudioTransceiver->sender()->SetParameters(parameters);
|
||
|
||
_outgoingAudioTrack->set_enabled(true);
|
||
}
|
||
}
|
||
|
||
if (_videoCapture) {
|
||
setVideoCapture(_videoCapture);
|
||
}
|
||
|
||
_signalingEncryptedConnection = std::make_unique<EncryptedConnection>(
|
||
EncryptedConnection::Type::Signaling,
|
||
_encryptionKey,
|
||
[weak, threads = _threads](int delayMs, int cause) {
|
||
if (delayMs == 0) {
|
||
threads->getMediaThread()->PostTask([weak, cause]() {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
strong->sendPendingSignalingServiceData(cause);
|
||
});
|
||
} else {
|
||
threads->getMediaThread()->PostDelayedTask([weak, cause]() {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
strong->sendPendingSignalingServiceData(cause);
|
||
}, webrtc::TimeDelta::Millis(delayMs));
|
||
}
|
||
}
|
||
);
|
||
|
||
beginSignaling();
|
||
|
||
beginLogTimer(0);
|
||
_lastDisconnectedTimestamp = rtc::TimeMillis();
|
||
beginCheckConnectionTimer();
|
||
|
||
// Emit a baseline record so a call that never transitions still uploads a
|
||
// timeline with a call-start origin. InstanceV2Impl::start() ends the same
|
||
// way, which is why 13.0.0 cannot produce an empty "network" array.
|
||
onNetworkStateUpdated();
|
||
}
|
||
|
||
void sendPendingSignalingServiceData(int cause) {
|
||
commitSendSignalingMessage(_signalingEncryptedConnection->prepareForSendingService(cause));
|
||
}
|
||
|
||
void sendSignalingMessage(signaling::Message const &message) {
|
||
auto data = message.serialize();
|
||
sendRawSignalingMessage(data);
|
||
}
|
||
|
||
void sendRawSignalingMessage(std::vector<uint8_t> const &data) {
|
||
RTC_LOG(LS_INFO) << "sendSignalingMessage: " << std::string(data.begin(), data.end());
|
||
|
||
if (_signalingConnection && _signalingEncryptedConnection) {
|
||
switch (_signalingProtocolVersion) {
|
||
case SignalingProtocolVersion::V1: {
|
||
rtc::CopyOnWriteBuffer message;
|
||
message.AppendData(data.data(), data.size());
|
||
|
||
commitSendSignalingMessage(_signalingEncryptedConnection->prepareForSendingRawMessage(message, true));
|
||
|
||
break;
|
||
}
|
||
case SignalingProtocolVersion::V2: {
|
||
std::vector<uint8_t> packetData;
|
||
if (signalingProtocolSupportsCompression(_signalingProtocolVersion)) {
|
||
if (const auto compressedData = gzipData(data)) {
|
||
packetData = std::move(compressedData.value());
|
||
} else {
|
||
// Do NOT fall through to the send: packetData is still
|
||
// empty, and encrypting and sending it produces a packet
|
||
// the peer drops after decrypt/JSON-parse. If the dropped
|
||
// message was the offer or answer, the call would simply
|
||
// never connect, with no error on either side.
|
||
RTC_LOG(LS_ERROR) << "Could not gzip signaling message";
|
||
break;
|
||
}
|
||
} else {
|
||
packetData = data;
|
||
}
|
||
|
||
if (const auto message = _signalingEncryptedConnection->encryptRawPacket(rtc::CopyOnWriteBuffer(packetData.data(), packetData.size()))) {
|
||
_signalingConnection->send(std::vector<uint8_t>(message.value().data(), message.value().data() + message.value().size()));
|
||
} else {
|
||
RTC_LOG(LS_ERROR) << "Could not encrypt signaling message";
|
||
}
|
||
break;
|
||
}
|
||
default: {
|
||
RTC_DCHECK_NOTREACHED();
|
||
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
RTC_LOG(LS_ERROR) << "sendSignalingMessage encryption not available";
|
||
}
|
||
}
|
||
|
||
void commitSendSignalingMessage(absl::optional<EncryptedConnection::EncryptedPacket> packet) {
|
||
if (!packet) {
|
||
return;
|
||
}
|
||
|
||
if (_signalingConnection) {
|
||
_signalingConnection->send(packet.value().bytes);
|
||
}
|
||
}
|
||
|
||
void beginLogTimer(int delayMs) {
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
_threads->getMediaThread()->PostDelayedTask([weak]() {
|
||
auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
strong->writeStateLogRecords();
|
||
|
||
strong->beginLogTimer(1000);
|
||
}, webrtc::TimeDelta::Millis(delayMs));
|
||
}
|
||
|
||
void beginCheckConnectionTimer() {
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
_threads->getMediaThread()->PostDelayedTask([weak]() {
|
||
auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
if (strong->_isStopped.load()) {
|
||
return;
|
||
}
|
||
|
||
int64_t currentTimestamp = rtc::TimeMillis();
|
||
const int64_t maxTimeout = 20000;
|
||
|
||
if (!strong->_isConnected && !strong->_isFailed && strong->_lastDisconnectedTimestamp + maxTimeout < currentTimestamp) {
|
||
RTC_LOG(LS_INFO) << "InstanceV2ReferenceImpl: connection timeout " << (currentTimestamp - strong->_lastDisconnectedTimestamp) << " ms";
|
||
|
||
strong->_isFailed = true;
|
||
strong->onNetworkStateUpdated();
|
||
}
|
||
|
||
strong->beginCheckConnectionTimer();
|
||
}, webrtc::TimeDelta::Millis(1000));
|
||
}
|
||
|
||
void updateIsConnected(bool isConnected) {
|
||
if (_isConnected == isConnected) {
|
||
return;
|
||
}
|
||
_isConnected = isConnected;
|
||
|
||
if (isConnected) {
|
||
onNetworkStateUpdated();
|
||
} else {
|
||
_lastDisconnectedTimestamp = rtc::TimeMillis();
|
||
|
||
// The legacy ICE state reports kIceConnectionDisconnected on a ~2.5s
|
||
// receiving timeout, so brief loss blips would surface as Reconnecting
|
||
// episodes. Only report (and log) the disconnect if it persists.
|
||
int32_t generation = ++_disconnectReportGeneration;
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
_threads->getMediaThread()->PostDelayedTask([weak, generation]() {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
if (strong->_isStopped.load()) {
|
||
return;
|
||
}
|
||
if (generation != strong->_disconnectReportGeneration) {
|
||
return;
|
||
}
|
||
if (!strong->_isConnected) {
|
||
strong->onNetworkStateUpdated();
|
||
}
|
||
}, webrtc::TimeDelta::Millis(2000));
|
||
}
|
||
}
|
||
|
||
void maybeRestartIce() {
|
||
if (_isFailed) {
|
||
return;
|
||
}
|
||
// Restart only from the offerer side: ICE failure is symmetric, so the
|
||
// offerer observes it too, and a single restart offer (new ufrag/pwd)
|
||
// re-pairs both directions without offer glare.
|
||
if (!_encryptionKey.isOutgoing) {
|
||
return;
|
||
}
|
||
|
||
int64_t timestamp = rtc::TimeMillis();
|
||
const int64_t minRestartIntervalMs = 5000;
|
||
if (_lastIceRestartTimestamp != 0 && _lastIceRestartTimestamp + minRestartIntervalMs > timestamp) {
|
||
return;
|
||
}
|
||
_lastIceRestartTimestamp = timestamp;
|
||
|
||
RTC_LOG(LS_INFO) << "InstanceV2ReferenceImpl: requesting ICE restart";
|
||
_peerConnection->RestartIce();
|
||
}
|
||
|
||
void writeStateLogRecords() {
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
auto call = ((webrtc::PeerConnectionProxyWithInternal<webrtc::PeerConnection> *)_peerConnection.get())->internal()->call_ptr();
|
||
if (!call) {
|
||
return;
|
||
}
|
||
|
||
_threads->getWorkerThread()->PostTask([weak, call]() {
|
||
auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
if (strong->_isStopped.load()) {
|
||
return;
|
||
}
|
||
|
||
auto stats = call->GetStats();
|
||
float sendBitrateKbps = ((float)stats.send_bandwidth_bps / 1024.0f);
|
||
|
||
strong->_threads->getMediaThread()->PostTask([weak, sendBitrateKbps]() {
|
||
auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
float bitrateNorm = 16.0f;
|
||
if (strong->_outgoingVideoTransceiver) {
|
||
bitrateNorm = 600.0f;
|
||
}
|
||
|
||
float signalBarsNorm = 4.0f;
|
||
float adjustedQuality = sendBitrateKbps / bitrateNorm;
|
||
adjustedQuality = fmaxf(0.0f, adjustedQuality);
|
||
adjustedQuality = fminf(1.0f, adjustedQuality);
|
||
if (strong->_signalBarsUpdated) {
|
||
strong->_signalBarsUpdated((int)(adjustedQuality * signalBarsNorm));
|
||
}
|
||
|
||
NetworkBitrateLogRecord networkBitrateLogRecord;
|
||
networkBitrateLogRecord.bitrate = (int32_t)sendBitrateKbps;
|
||
|
||
strong->_networkBitrateLogRecords.emplace_back(rtc::TimeMillis(), std::move(networkBitrateLogRecord));
|
||
});
|
||
});
|
||
}
|
||
|
||
void sendLocalDescription() {
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
|
||
_isMakingOffer = true;
|
||
|
||
webrtc::scoped_refptr<webrtc::SetLocalDescriptionObserverInterface> observer(new rtc::RefCountedObject<SetSessionDescriptionObserver>([threads = _threads, weak](webrtc::RTCError error) {
|
||
const bool isOk = error.ok();
|
||
if (!isOk) {
|
||
RTC_LOG(LS_ERROR) << "SetLocalDescription failed: " << error.message();
|
||
}
|
||
threads->getMediaThread()->PostTask([weak, isOk]() {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
// Always clear the in-flight flag, or a failure would wedge
|
||
// renegotiation permanently (see the guard in onRenegotiationNeeded).
|
||
strong->_isMakingOffer = false;
|
||
|
||
if (!isOk) {
|
||
return;
|
||
}
|
||
|
||
strong->doSendLocalDescription();
|
||
|
||
strong->maybeCommitPendingIceCandidates();
|
||
});
|
||
}));
|
||
RTC_LOG(LS_INFO) << "Calling SetLocalDescription";
|
||
_peerConnection->SetLocalDescription(observer);
|
||
}
|
||
|
||
void sendIceCandidate(const webrtc::IceCandidateInterface *iceCandidate) {
|
||
std::string sdp;
|
||
iceCandidate->ToString(&sdp);
|
||
|
||
json11::Json::object jsonCandidate;
|
||
jsonCandidate.insert(std::make_pair("@type", json11::Json("candidate")));
|
||
jsonCandidate.insert(std::make_pair("sdp", json11::Json(sdp)));
|
||
jsonCandidate.insert(std::make_pair("mid", json11::Json(iceCandidate->sdp_mid())));
|
||
jsonCandidate.insert(std::make_pair("mline", json11::Json(iceCandidate->sdp_mline_index())));
|
||
|
||
auto jsonData = json11::Json(std::move(jsonCandidate));
|
||
auto jsonResult = jsonData.dump();
|
||
sendRawSignalingMessage(std::vector<uint8_t>(jsonResult.begin(), jsonResult.end()));
|
||
}
|
||
|
||
void doSendLocalDescription() {
|
||
auto localDescription = _peerConnection->local_description();
|
||
if (localDescription) {
|
||
std::string sdp;
|
||
localDescription->ToString(&sdp);
|
||
std::string type = localDescription->type();
|
||
|
||
json11::Json::object jsonDescription;
|
||
jsonDescription.insert(std::make_pair("@type", json11::Json(type)));
|
||
jsonDescription.insert(std::make_pair("sdp", json11::Json(sdp)));
|
||
|
||
auto jsonData = json11::Json(std::move(jsonDescription));
|
||
auto jsonResult = jsonData.dump();
|
||
sendRawSignalingMessage(std::vector<uint8_t>(jsonResult.begin(), jsonResult.end()));
|
||
}
|
||
}
|
||
|
||
void beginSignaling() {
|
||
_didBeginNegotiation = true;
|
||
|
||
if (_encryptionKey.isOutgoing) {
|
||
sendLocalDescription();
|
||
}
|
||
}
|
||
|
||
void receiveSignalingData(const std::vector<uint8_t> &data) {
|
||
if (_signalingConnection) {
|
||
_signalingConnection->receiveExternal(data);
|
||
}
|
||
}
|
||
|
||
void onSignalingData(const std::vector<uint8_t> &data) {
|
||
if (_signalingEncryptedConnection) {
|
||
switch (_signalingProtocolVersion) {
|
||
case SignalingProtocolVersion::V1: {
|
||
if (const auto packet = _signalingEncryptedConnection->handleIncomingRawPacket((const char *)data.data(), data.size())) {
|
||
processSignalingMessage(packet.value().main.message);
|
||
|
||
for (const auto &additional : packet.value().additional) {
|
||
processSignalingMessage(additional.message);
|
||
}
|
||
}
|
||
|
||
break;
|
||
}
|
||
case SignalingProtocolVersion::V2: {
|
||
if (const auto message = _signalingEncryptedConnection->decryptRawPacket(rtc::CopyOnWriteBuffer(data.data(), data.size()))) {
|
||
processSignalingMessage(message.value());
|
||
} else {
|
||
RTC_LOG(LS_ERROR) << "receiveSignalingData could not decrypt signaling data";
|
||
}
|
||
|
||
break;
|
||
}
|
||
default: {
|
||
RTC_DCHECK_NOTREACHED();
|
||
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
RTC_LOG(LS_ERROR) << "receiveSignalingData encryption not available";
|
||
}
|
||
}
|
||
|
||
void processSignalingMessage(rtc::CopyOnWriteBuffer const &data) {
|
||
std::vector<uint8_t> decryptedData = std::vector<uint8_t>(data.data(), data.data() + data.size());
|
||
|
||
if (isGzip(decryptedData)) {
|
||
if (const auto decompressedData = gunzipData(decryptedData, 2 * 1024 * 1024)) {
|
||
processSignalingData(decompressedData.value());
|
||
} else {
|
||
RTC_LOG(LS_ERROR) << "receiveSignalingData could not decompress gzipped data";
|
||
}
|
||
} else {
|
||
processSignalingData(decryptedData);
|
||
}
|
||
}
|
||
|
||
void processSignalingData(const std::vector<uint8_t> &data) {
|
||
RTC_LOG(LS_INFO) << "processSignalingData: " << std::string(data.begin(), data.end());
|
||
|
||
std::string parsingError;
|
||
auto json = json11::Json::parse(std::string(data.begin(), data.end()), parsingError);
|
||
if (json.type() != json11::Json::OBJECT) {
|
||
RTC_LOG(LS_ERROR) << "Signaling: message must be an object";
|
||
return;
|
||
}
|
||
|
||
const auto jsonType = json.object_items().find("@type");
|
||
if (jsonType == json.object_items().end()) {
|
||
RTC_LOG(LS_ERROR) << "Signaling: @type is missing";
|
||
return;
|
||
}
|
||
std::string type = jsonType->second.string_value();
|
||
|
||
if (type == "offer" || type == "answer") {
|
||
const auto jsonSdp = json.object_items().find("sdp");
|
||
if (jsonSdp == json.object_items().end()) {
|
||
RTC_LOG(LS_ERROR) << "Signaling: sdp is missing";
|
||
return;
|
||
}
|
||
std::string sdp = jsonSdp->second.string_value();
|
||
handleRemoteSdp(type, sdp);
|
||
} else if (type == "candidate") {
|
||
auto jsonMid = json.object_items().find("mid");
|
||
if (jsonMid == json.object_items().end()) {
|
||
return;
|
||
}
|
||
|
||
auto jsonMLineIndex = json.object_items().find("mline");
|
||
if (jsonMLineIndex == json.object_items().end()) {
|
||
return;
|
||
}
|
||
|
||
auto jsonSdp = json.object_items().find("sdp");
|
||
if (jsonSdp == json.object_items().end()) {
|
||
return;
|
||
}
|
||
|
||
webrtc::SdpParseError parseError;
|
||
webrtc::IceCandidateInterface *iceCandidate = webrtc::CreateIceCandidate(jsonMid->second.string_value(), jsonMLineIndex->second.int_value(), jsonSdp->second.string_value(), &parseError);
|
||
if (iceCandidate) {
|
||
std::unique_ptr<webrtc::IceCandidateInterface> candidarePtr;
|
||
candidarePtr.reset(iceCandidate);
|
||
|
||
_pendingIceCandidates.push_back(std::move(candidarePtr));
|
||
maybeCommitPendingIceCandidates();
|
||
}
|
||
} else {
|
||
const auto message = signaling::Message::parse(data);
|
||
if (!message) {
|
||
return;
|
||
}
|
||
const auto messageData = &message->data;
|
||
|
||
if (const auto mediaState = absl::get_if<signaling::MediaStateMessage>(messageData)) {
|
||
AudioState mappedAudioState;
|
||
if (mediaState->isMuted) {
|
||
mappedAudioState = AudioState::Muted;
|
||
} else {
|
||
mappedAudioState = AudioState::Active;
|
||
}
|
||
|
||
VideoState mappedVideoState;
|
||
switch (mediaState->videoState) {
|
||
case signaling::MediaStateMessage::VideoState::Inactive: {
|
||
mappedVideoState = VideoState::Inactive;
|
||
break;
|
||
}
|
||
case signaling::MediaStateMessage::VideoState::Suspended: {
|
||
mappedVideoState = VideoState::Paused;
|
||
break;
|
||
}
|
||
case signaling::MediaStateMessage::VideoState::Active: {
|
||
mappedVideoState = VideoState::Active;
|
||
break;
|
||
}
|
||
default: {
|
||
RTC_FATAL() << "Unknown videoState";
|
||
break;
|
||
}
|
||
}
|
||
|
||
VideoState mappedScreencastState;
|
||
switch (mediaState->screencastState) {
|
||
case signaling::MediaStateMessage::VideoState::Inactive: {
|
||
mappedScreencastState = VideoState::Inactive;
|
||
break;
|
||
}
|
||
case signaling::MediaStateMessage::VideoState::Suspended: {
|
||
mappedScreencastState = VideoState::Paused;
|
||
break;
|
||
}
|
||
case signaling::MediaStateMessage::VideoState::Active: {
|
||
mappedScreencastState = VideoState::Active;
|
||
break;
|
||
}
|
||
default: {
|
||
RTC_FATAL() << "Unknown videoState";
|
||
break;
|
||
}
|
||
}
|
||
|
||
VideoState effectiveVideoState = mappedVideoState;
|
||
if (mappedScreencastState == VideoState::Active || mappedScreencastState == VideoState::Paused) {
|
||
effectiveVideoState = mappedScreencastState;
|
||
}
|
||
|
||
if (_remoteMediaStateUpdated) {
|
||
_remoteMediaStateUpdated(mappedAudioState, effectiveVideoState);
|
||
}
|
||
|
||
if (_remoteBatteryLevelIsLowUpdated) {
|
||
_remoteBatteryLevelIsLowUpdated(mediaState->isBatteryLow);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void handleRemoteSdp(std::string const &type, std::string const &sdp) {
|
||
webrtc::SdpParseError sdpParseError;
|
||
std::unique_ptr<webrtc::SessionDescriptionInterface> remoteDescription(webrtc::CreateSessionDescription(type, sdp, &sdpParseError));
|
||
if (!remoteDescription) {
|
||
RTC_LOG(LS_ERROR) << "Failed to parse remote SDP";
|
||
return;
|
||
}
|
||
|
||
bool isReadyForOffer = !_isMakingOffer && (_peerConnection->signaling_state() == webrtc::PeerConnectionInterface::SignalingState::kStable || _isSettingRemoteAnswerPending);
|
||
bool isOfferCollision = (type == "offer") && !isReadyForOffer;
|
||
bool ignoreOffer = !_encryptionKey.isOutgoing && isOfferCollision;
|
||
if (ignoreOffer) {
|
||
RTC_LOG(LS_INFO) << "Ingoring remote sdp";
|
||
return;
|
||
}
|
||
|
||
_isSettingRemoteAnswerPending = type == "answer";
|
||
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
webrtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface> observer(new rtc::RefCountedObject<SetSessionDescriptionObserver>([threads = _threads, weak, type](webrtc::RTCError error) {
|
||
const bool isOk = error.ok();
|
||
if (!isOk) {
|
||
RTC_LOG(LS_ERROR) << "SetRemoteDescription failed: " << error.message();
|
||
}
|
||
threads->getMediaThread()->PostTask([weak, type, isOk]() {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
strong->_isSettingRemoteAnswerPending = false;
|
||
|
||
if (!isOk) {
|
||
// Do NOT fall through to sendLocalDescription(): a failed
|
||
// SetRemoteDescription(offer) leaves signaling state at kStable,
|
||
// and the implicit SetLocalDescription overload picks
|
||
// offer-vs-answer from signaling_state() - so we would send an
|
||
// offer where the peer awaits an answer.
|
||
return;
|
||
}
|
||
|
||
strong->maybeCommitPendingIceCandidates();
|
||
|
||
if (type == "offer") {
|
||
strong->sendLocalDescription();
|
||
}
|
||
});
|
||
}));
|
||
RTC_LOG(LS_INFO) << "Calling SetRemoteDescription";
|
||
_peerConnection->SetRemoteDescription(std::move(remoteDescription), observer);
|
||
}
|
||
|
||
void maybeCommitPendingIceCandidates() {
|
||
if (_pendingIceCandidates.size() == 0) {
|
||
return;
|
||
}
|
||
|
||
if (_peerConnection->local_description() && _peerConnection->remote_description()) {
|
||
for (const auto &candidate : _pendingIceCandidates) {
|
||
if (candidate) {
|
||
_peerConnection->AddIceCandidate(candidate.get());
|
||
}
|
||
}
|
||
|
||
_pendingIceCandidates.clear();
|
||
}
|
||
}
|
||
|
||
void onNetworkStateUpdated() {
|
||
NetworkStateLogRecord record;
|
||
record.isConnected = _isConnected;
|
||
record.connection = _currentConnectionDescription;
|
||
record.isFailed = _isFailed;
|
||
|
||
if (!_currentNetworkStateLogRecord || !(_currentNetworkStateLogRecord.value() == record)) {
|
||
_currentNetworkStateLogRecord = record;
|
||
_networkStateLogRecords.emplace_back(rtc::TimeMillis(), std::move(record));
|
||
}
|
||
|
||
State mappedState;
|
||
if (_isFailed) {
|
||
mappedState = State::Failed;
|
||
} else if (_isConnected) {
|
||
mappedState = State::Established;
|
||
} else {
|
||
mappedState = State::Reconnecting;
|
||
}
|
||
_stateUpdated(mappedState);
|
||
}
|
||
|
||
void attachDataChannel(webrtc::scoped_refptr<webrtc::DataChannelInterface> dataChannel) {
|
||
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
||
|
||
DataChannelObserverImpl::Parameters dataChannelObserverParams;
|
||
dataChannelObserverParams.onStateChange = [threads = _threads, weak]() {
|
||
threads->getMediaThread()->PostTask([weak]() {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
strong->onDataChannelStateUpdated();
|
||
});
|
||
};
|
||
dataChannelObserverParams.onMessage = [threads = _threads, weak](webrtc::DataBuffer const &buffer) {
|
||
const auto strong = weak.lock();
|
||
if (!strong) {
|
||
return;
|
||
}
|
||
|
||
std::string message(buffer.data.data(), buffer.data.data() + buffer.data.size());
|
||
|
||
if (!buffer.binary) {
|
||
RTC_LOG(LS_INFO) << "dataChannelMessage received: " << message;
|
||
std::vector<uint8_t> data(message.begin(), message.end());
|
||
strong->processSignalingData(data);
|
||
} else {
|
||
RTC_LOG(LS_INFO) << "dataChannelMessage rejecting binary message";
|
||
}
|
||
};
|
||
_dataChannelObserver = std::make_unique<DataChannelObserverImpl>(std::move(dataChannelObserverParams));
|
||
_dataChannel = dataChannel;
|
||
|
||
onDataChannelStateUpdated();
|
||
|
||
_dataChannel->RegisterObserver(_dataChannelObserver.get());
|
||
}
|
||
|
||
void onDataChannelStateUpdated() {
|
||
if (_dataChannel) {
|
||
switch (_dataChannel->state()) {
|
||
case webrtc::DataChannelInterface::DataState::kOpen: {
|
||
if (!_isDataChannelOpen) {
|
||
_isDataChannelOpen = true;
|
||
sendMediaState();
|
||
}
|
||
break;
|
||
}
|
||
default: {
|
||
_isDataChannelOpen = false;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void sendDataChannelMessage(signaling::Message const &message) {
|
||
if (!_isDataChannelOpen) {
|
||
RTC_LOG(LS_ERROR) << "sendDataChannelMessage called, but data channel is not open";
|
||
return;
|
||
}
|
||
auto data = message.serialize();
|
||
std::string stringData(data.begin(), data.end());
|
||
RTC_LOG(LS_INFO) << "sendDataChannelMessage: " << stringData;
|
||
if (_dataChannel) {
|
||
_dataChannel->Send(webrtc::DataBuffer(stringData));
|
||
}
|
||
}
|
||
|
||
void onDataChannelMessage(std::string const &message) {
|
||
RTC_LOG(LS_INFO) << "dataChannelMessage received: " << message;
|
||
std::vector<uint8_t> data(message.begin(), message.end());
|
||
processSignalingData(data);
|
||
}
|
||
|
||
void sendMediaState() {
|
||
if (!_isDataChannelOpen) {
|
||
return;
|
||
}
|
||
signaling::Message message;
|
||
signaling::MediaStateMessage data;
|
||
data.isMuted = _isMicrophoneMuted;
|
||
data.isBatteryLow = _isBatteryLow;
|
||
if (_outgoingVideoTransceiver) {
|
||
if (_videoCapture) {
|
||
data.videoState = signaling::MediaStateMessage::VideoState::Active;
|
||
} else {
|
||
data.videoState = signaling::MediaStateMessage::VideoState::Inactive;
|
||
}
|
||
} else {
|
||
data.videoState = signaling::MediaStateMessage::VideoState::Inactive;
|
||
data.videoRotation = signaling::MediaStateMessage::VideoRotation::Rotation0;
|
||
}
|
||
message.data = std::move(data);
|
||
sendDataChannelMessage(message);
|
||
}
|
||
|
||
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) {
|
||
if (!_peerConnection) {
|
||
// start() bailed out because CreatePeerConnectionOrError failed. The app
|
||
// can still call setVideoCapture() through the public Instance interface,
|
||
// and _peerConnectionFactory is valid here, so CreateVideoTrack would
|
||
// succeed and the AddTransceiver below would dereference null.
|
||
return;
|
||
}
|
||
|
||
_isPerformingConfiguration = true;
|
||
|
||
if (_outgoingVideoTransceiver) {
|
||
_peerConnection->RemoveTrackOrError(_outgoingVideoTransceiver->sender());
|
||
}
|
||
if (_outgoingVideoTrack) {
|
||
_outgoingVideoTrack = nullptr;
|
||
}
|
||
_outgoingVideoTransceiver = nullptr;
|
||
|
||
auto videoCaptureImpl = GetVideoCaptureAssumingSameThread(videoCapture.get());
|
||
if (videoCaptureImpl) {
|
||
if (videoCaptureImpl->isScreenCapture()) {
|
||
} else {
|
||
_videoCapture = videoCapture;
|
||
|
||
auto videoTrack = _peerConnectionFactory->CreateVideoTrack(videoCaptureImpl->source(), "1");
|
||
if (videoTrack) {
|
||
webrtc::RtpTransceiverInit transceiverInit;
|
||
transceiverInit.stream_ids = { "0" };
|
||
|
||
// Deliberately still AddTransceiver: see network_reference_use_addtrack.
|
||
// AddTrack reuse is once-only, so it would not cure m-line growth on
|
||
// repeated camera toggles, and it would add a second variable to the A/B.
|
||
webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>> videoTransceiverOrError = _peerConnection->AddTransceiver(videoTrack, transceiverInit);
|
||
if (videoTransceiverOrError.ok()) {
|
||
_outgoingVideoTrack = videoTrack;
|
||
_outgoingVideoTransceiver = videoTransceiverOrError.value();
|
||
|
||
auto currentCapabilities = _peerConnectionFactory->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO);
|
||
|
||
std::vector<std::string> codecPreferences = {
|
||
#ifndef WEBRTC_DISABLE_H265
|
||
cricket::kH265CodecName,
|
||
#endif
|
||
cricket::kH264CodecName
|
||
};
|
||
|
||
for (const auto &codecCapability : currentCapabilities.codecs) {
|
||
if (std::find_if(codecPreferences.begin(), codecPreferences.end(), [&](std::string const &value) {
|
||
return value == codecCapability.name;
|
||
}) != codecPreferences.end()) {
|
||
continue;
|
||
}
|
||
codecPreferences.push_back(codecCapability.name);
|
||
}
|
||
|
||
std::vector<webrtc::RtpCodecCapability> codecCapabilities;
|
||
for (const auto &name : codecPreferences) {
|
||
for (const auto &codecCapability : currentCapabilities.codecs) {
|
||
if (codecCapability.name == name) {
|
||
codecCapabilities.push_back(codecCapability);
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
_outgoingVideoTransceiver->SetCodecPreferences(codecCapabilities);
|
||
|
||
webrtc::RtpParameters parameters = _outgoingVideoTransceiver->sender()->GetParameters();
|
||
if (parameters.encodings.empty()) {
|
||
parameters.encodings.push_back(webrtc::RtpEncodingParameters());
|
||
}
|
||
parameters.encodings[0].max_bitrate_bps = 1200 * 1024;
|
||
_outgoingVideoTransceiver->sender()->SetParameters(parameters);
|
||
|
||
_outgoingVideoTrack->set_enabled(true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
_isPerformingConfiguration = false;
|
||
|
||
if (_didBeginNegotiation) {
|
||
sendMediaState();
|
||
sendLocalDescription();
|
||
}
|
||
}
|
||
|
||
void setRequestedVideoAspect(float aspect) {
|
||
}
|
||
|
||
void setNetworkType(NetworkType networkType) {
|
||
}
|
||
|
||
void setMuteMicrophone(bool muteMicrophone) {
|
||
if (_isMicrophoneMuted != muteMicrophone) {
|
||
_isMicrophoneMuted = muteMicrophone;
|
||
|
||
if (_outgoingAudioTrack) {
|
||
_outgoingAudioTrack->set_enabled(!_isMicrophoneMuted);
|
||
}
|
||
|
||
sendMediaState();
|
||
}
|
||
}
|
||
|
||
void connectIncomingVideoSink(webrtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
|
||
if (!_currentStrongSink) {
|
||
return;
|
||
}
|
||
auto track = transceiver->receiver()->track();
|
||
if (!track) {
|
||
return;
|
||
}
|
||
webrtc::VideoTrackInterface *videoTrack = (webrtc::VideoTrackInterface *)track.get();
|
||
videoTrack->AddOrUpdateSink(_currentStrongSink.get(), rtc::VideoSinkWants());
|
||
_attachedSinkTracks.insert(videoTrack);
|
||
}
|
||
|
||
void disconnectIncomingVideoSink(webrtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
|
||
if (!_currentStrongSink) {
|
||
return;
|
||
}
|
||
auto track = transceiver->receiver()->track();
|
||
if (!track) {
|
||
return;
|
||
}
|
||
webrtc::VideoTrackInterface *videoTrack = (webrtc::VideoTrackInterface *)track.get();
|
||
if (_attachedSinkTracks.erase(videoTrack) == 0) {
|
||
// Never attached to this track - RemoveSink would trip
|
||
// RTC_DCHECK(FindSinkPair(sink)) in a debug build.
|
||
return;
|
||
}
|
||
videoTrack->RemoveSink(_currentStrongSink.get());
|
||
}
|
||
|
||
void disconnectAllIncomingVideoSinks() {
|
||
if (!_currentStrongSink) {
|
||
return;
|
||
}
|
||
for (const auto &it : _incomingVideoTransceivers) {
|
||
disconnectIncomingVideoSink(it.second);
|
||
}
|
||
_attachedSinkTracks.clear();
|
||
}
|
||
|
||
void setIncomingVideoOutput(std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
|
||
disconnectAllIncomingVideoSinks();
|
||
|
||
_currentStrongSink = sink.lock();
|
||
|
||
if (_currentStrongSink) {
|
||
if (!_incomingVideoTransceivers.empty()) {
|
||
connectIncomingVideoSink(_incomingVideoTransceivers.begin()->second);
|
||
}
|
||
}
|
||
|
||
/*if (_incomingVideoChannel) {
|
||
_incomingVideoChannel->addSink(sink);
|
||
}
|
||
if (_incomingScreencastChannel) {
|
||
_incomingScreencastChannel->addSink(sink);
|
||
}*/
|
||
}
|
||
|
||
void setAudioInputDevice(std::string id) {
|
||
SetAudioInputDeviceById(_audioDeviceModule.get(), id);
|
||
}
|
||
|
||
void setAudioOutputDevice(std::string id) {
|
||
SetAudioOutputDeviceById(_audioDeviceModule.get(), id);
|
||
}
|
||
|
||
void setIsLowBatteryLevel(bool isLowBatteryLevel) {
|
||
if (_isBatteryLow != isLowBatteryLevel) {
|
||
_isBatteryLow = isLowBatteryLevel;
|
||
sendMediaState();
|
||
}
|
||
}
|
||
|
||
void stop(std::function<void(FinalState)> completion) {
|
||
_isStopped = true;
|
||
if (_peerConnection) {
|
||
_peerConnection->Close();
|
||
}
|
||
|
||
FinalState finalState;
|
||
|
||
json11::Json::object statsLog;
|
||
|
||
statsLog.insert(std::make_pair("v", std::move(3)));
|
||
|
||
for (int i = (int)_networkStateLogRecords.size() - 1; i >= 1; i--) {
|
||
// coalesce events within 5ms
|
||
if (_networkStateLogRecords[i].timestamp - _networkStateLogRecords[i - 1].timestamp < 5) {
|
||
_networkStateLogRecords.erase(_networkStateLogRecords.begin() + i - 1);
|
||
}
|
||
}
|
||
|
||
json11::Json::array jsonNetworkStateLogRecords;
|
||
int64_t baseTimestamp = 0;
|
||
for (const auto &record : _networkStateLogRecords) {
|
||
json11::Json::object jsonRecord;
|
||
|
||
if (baseTimestamp == 0) {
|
||
baseTimestamp = record.timestamp;
|
||
}
|
||
jsonRecord.insert(std::make_pair("t", json11::Json(std::to_string(record.timestamp - baseTimestamp))));
|
||
jsonRecord.insert(std::make_pair("c", json11::Json(record.record.isConnected ? 1 : 0)));
|
||
if (record.record.route) {
|
||
jsonRecord.insert(std::make_pair("local", json11::Json(record.record.route->localDescription)));
|
||
jsonRecord.insert(std::make_pair("remote", json11::Json(record.record.route->remoteDescription)));
|
||
}
|
||
if (record.record.connection) {
|
||
json11::Json::object jsonConnection;
|
||
|
||
auto serializeCandidate = [](InstanceNetworking::ConnectionDescription::CandidateDescription const &candidate) -> json11::Json::object {
|
||
json11::Json::object jsonCandidate;
|
||
|
||
jsonCandidate.insert(std::make_pair("type", json11::Json(candidate.type)));
|
||
jsonCandidate.insert(std::make_pair("protocol", json11::Json(candidate.protocol)));
|
||
jsonCandidate.insert(std::make_pair("address", json11::Json(candidate.address)));
|
||
|
||
return jsonCandidate;
|
||
};
|
||
|
||
jsonConnection.insert(std::make_pair("local", serializeCandidate(record.record.connection->local)));
|
||
jsonConnection.insert(std::make_pair("remote", serializeCandidate(record.record.connection->remote)));
|
||
|
||
jsonRecord.insert(std::make_pair("network", std::move(jsonConnection)));
|
||
}
|
||
if (record.record.isFailed) {
|
||
jsonRecord.insert(std::make_pair("failed", json11::Json(1)));
|
||
}
|
||
|
||
jsonNetworkStateLogRecords.push_back(std::move(jsonRecord));
|
||
}
|
||
statsLog.insert(std::make_pair("network", std::move(jsonNetworkStateLogRecords)));
|
||
|
||
json11::Json::array jsonNetworkBitrateLogRecords;
|
||
for (const auto &record : _networkBitrateLogRecords) {
|
||
json11::Json::object jsonRecord;
|
||
|
||
jsonRecord.insert(std::make_pair("b", json11::Json(record.record.bitrate)));
|
||
|
||
jsonNetworkBitrateLogRecords.push_back(std::move(jsonRecord));
|
||
}
|
||
statsLog.insert(std::make_pair("bitrate", std::move(jsonNetworkBitrateLogRecords)));
|
||
|
||
auto jsonStatsLog = json11::Json(std::move(statsLog));
|
||
|
||
if (!_statsLogPath.data.empty()) {
|
||
std::ofstream file;
|
||
file.open(_statsLogPath.data);
|
||
|
||
file << jsonStatsLog.dump();
|
||
|
||
file.close();
|
||
}
|
||
|
||
completion(finalState);
|
||
}
|
||
|
||
/*void adjustBitratePreferences(bool resetStartBitrate) {
|
||
if (_outgoingAudioChannel) {
|
||
_outgoingAudioChannel->setMaxBitrate(32 * 1024);
|
||
}
|
||
if (_outgoingVideoChannel) {
|
||
_outgoingVideoChannel->setMaxBitrate(1000 * 1024);
|
||
}
|
||
}*/
|
||
|
||
private:
|
||
webrtc::scoped_refptr<webrtc::AudioDeviceModule> createAudioDeviceModule() {
|
||
const auto create = [&](webrtc::AudioDeviceModule::AudioLayer layer) {
|
||
#ifdef WEBRTC_IOS
|
||
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, false, false, 1);
|
||
#else
|
||
return webrtc::AudioDeviceModule::Create(
|
||
layer,
|
||
_taskQueueFactory.get());
|
||
#endif
|
||
};
|
||
const auto check = [&](const webrtc::scoped_refptr<webrtc::AudioDeviceModule> &result) {
|
||
return (result && result->Init() == 0) ? result : nullptr;
|
||
};
|
||
if (_createWrappedAudioDeviceModule) {
|
||
auto result = _createWrappedAudioDeviceModule(_taskQueueFactory.get());
|
||
if (result) {
|
||
return result;
|
||
}
|
||
}
|
||
if (_createAudioDeviceModule) {
|
||
if (const auto result = check(_createAudioDeviceModule(_taskQueueFactory.get()))) {
|
||
return result;
|
||
}
|
||
}
|
||
return check(create(webrtc::AudioDeviceModule::kPlatformDefaultAudio));
|
||
}
|
||
|
||
private:
|
||
SignalingProtocolVersion _signalingProtocolVersion;
|
||
std::shared_ptr<Threads> _threads;
|
||
std::vector<RtcServer> _rtcServers;
|
||
std::map<std::string, json11::Json> _customParameters;
|
||
std::unique_ptr<Proxy> _proxy;
|
||
bool _enableP2P = false;
|
||
bool _relayTcpTls = false;
|
||
EncryptionKey _encryptionKey;
|
||
std::function<void(State)> _stateUpdated;
|
||
std::function<void(int)> _signalBarsUpdated;
|
||
std::function<void(float, float)> _audioLevelsUpdated;
|
||
std::function<void(bool)> _remoteBatteryLevelIsLowUpdated;
|
||
std::function<void(AudioState, VideoState)> _remoteMediaStateUpdated;
|
||
std::function<void(float)> _remotePrefferedAspectRatioUpdated;
|
||
std::function<void(const std::vector<uint8_t> &)> _signalingDataEmitted;
|
||
std::function<webrtc::scoped_refptr<webrtc::AudioDeviceModule>(webrtc::TaskQueueFactory*)> _createAudioDeviceModule;
|
||
std::function<webrtc::scoped_refptr<WrappedAudioDeviceModule>(webrtc::TaskQueueFactory*)> _createWrappedAudioDeviceModule;
|
||
FilePath _statsLogPath;
|
||
|
||
std::unique_ptr<SignalingConnection> _signalingConnection;
|
||
std::unique_ptr<EncryptedConnection> _signalingEncryptedConnection;
|
||
|
||
bool _isConnected = false;
|
||
bool _isFailed = false;
|
||
int64_t _lastDisconnectedTimestamp = 0;
|
||
int32_t _disconnectReportGeneration = 0;
|
||
int64_t _lastIceRestartTimestamp = 0;
|
||
absl::optional<InstanceNetworking::ConnectionDescription> _currentConnectionDescription;
|
||
|
||
absl::optional<NetworkStateLogRecord> _currentNetworkStateLogRecord;
|
||
std::vector<StateLogRecord<NetworkStateLogRecord>> _networkStateLogRecords;
|
||
std::vector<StateLogRecord<NetworkBitrateLogRecord>> _networkBitrateLogRecords;
|
||
|
||
bool _didBeginNegotiation = false;
|
||
bool _isMakingOffer = false;
|
||
bool _isSettingRemoteAnswerPending = false;
|
||
bool _isPerformingConfiguration = false;
|
||
bool _useAddTrack = false;
|
||
bool _useMtProto = false;
|
||
|
||
webrtc::scoped_refptr<webrtc::AudioTrackInterface> _outgoingAudioTrack;
|
||
webrtc::scoped_refptr<webrtc::RtpTransceiverInterface> _outgoingAudioTransceiver;
|
||
bool _isMicrophoneMuted = false;
|
||
|
||
webrtc::scoped_refptr<webrtc::VideoTrackInterface> _outgoingVideoTrack;
|
||
webrtc::scoped_refptr<webrtc::RtpTransceiverInterface> _outgoingVideoTransceiver;
|
||
|
||
std::map<std::string, webrtc::scoped_refptr<webrtc::RtpTransceiverInterface>> _incomingVideoTransceivers;
|
||
|
||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> _pendingIceCandidates;
|
||
|
||
std::unique_ptr<DataChannelObserverImpl> _dataChannelObserver;
|
||
webrtc::scoped_refptr<webrtc::DataChannelInterface> _dataChannel;
|
||
bool _isDataChannelOpen = false;
|
||
|
||
std::unique_ptr<webrtc::RtcEventLogNull> _eventLog;
|
||
std::unique_ptr<webrtc::TaskQueueFactory> _taskQueueFactory;
|
||
|
||
std::unique_ptr<rtc::NetworkMonitorFactory> _networkMonitorFactory;
|
||
std::unique_ptr<rtc::BasicPacketSocketFactory> _socketFactory;
|
||
std::unique_ptr<rtc::BasicNetworkManager> _networkManager;
|
||
std::unique_ptr<cricket::RelayPortFactoryInterface> _relayPortFactory;
|
||
|
||
webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> _peerConnectionFactory;
|
||
std::unique_ptr<PeerConnectionDelegateAdapter> _peerConnectionObserver;
|
||
webrtc::scoped_refptr<webrtc::PeerConnectionInterface> _peerConnection;
|
||
|
||
webrtc::LocalAudioSinkAdapter _audioSource;
|
||
|
||
webrtc::scoped_refptr<webrtc::AudioDeviceModule> _audioDeviceModule;
|
||
|
||
bool _isBatteryLow = false;
|
||
std::atomic<bool> _isStopped{false};
|
||
|
||
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> _currentStrongSink;
|
||
// Exactly the tracks we called AddOrUpdateSink on. RemoveSink DCHECKs when the
|
||
// sink was never added, and attachment is asymmetric (a transceiver added while
|
||
// no sink is set is never attached, and setIncomingVideoOutput attaches only the
|
||
// first entry), so removal must be driven by this set, not by the transceiver map.
|
||
std::set<webrtc::VideoTrackInterface*> _attachedSinkTracks;
|
||
|
||
std::shared_ptr<VideoCaptureInterface> _videoCapture;
|
||
};
|
||
|
||
InstanceV2ReferenceImpl::InstanceV2ReferenceImpl(Descriptor &&descriptor) {
|
||
if (descriptor.config.logPath.data.size() != 0) {
|
||
_logSink = std::make_unique<LogSinkImpl>(descriptor.config.logPath);
|
||
}
|
||
rtc::LogMessage::LogToDebug(rtc::LS_INFO);
|
||
rtc::LogMessage::SetLogToStderr(false);
|
||
if (_logSink) {
|
||
rtc::LogMessage::AddLogToStream(_logSink.get(), rtc::LS_INFO);
|
||
}
|
||
|
||
_threads = StaticThreads::getThreads();
|
||
_internal.reset(new ThreadLocalObject<InstanceV2ReferenceImplInternal>(_threads->getMediaThread(), [descriptor = std::move(descriptor), threads = _threads]() mutable {
|
||
return std::make_shared<InstanceV2ReferenceImplInternal>(std::move(descriptor), threads);
|
||
}));
|
||
_internal->perform([](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->start();
|
||
});
|
||
}
|
||
|
||
InstanceV2ReferenceImpl::~InstanceV2ReferenceImpl() {
|
||
rtc::LogMessage::RemoveLogToStream(_logSink.get());
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::receiveSignalingData(const std::vector<uint8_t> &data) {
|
||
_internal->perform([data](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->receiveSignalingData(data);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) {
|
||
_internal->perform([videoCapture](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setVideoCapture(videoCapture);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setRequestedVideoAspect(float aspect) {
|
||
_internal->perform([aspect](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setRequestedVideoAspect(aspect);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setNetworkType(NetworkType networkType) {
|
||
_internal->perform([networkType](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setNetworkType(networkType);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setMuteMicrophone(bool muteMicrophone) {
|
||
_internal->perform([muteMicrophone](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setMuteMicrophone(muteMicrophone);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setIncomingVideoOutput(std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
|
||
_internal->perform([sink](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setIncomingVideoOutput(sink);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setAudioInputDevice(std::string id) {
|
||
_internal->perform([id](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setAudioInputDevice(id);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setAudioOutputDevice(std::string id) {
|
||
_internal->perform([id](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setAudioOutputDevice(id);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setIsLowBatteryLevel(bool isLowBatteryLevel) {
|
||
_internal->perform([isLowBatteryLevel](InstanceV2ReferenceImplInternal *internal) {
|
||
internal->setIsLowBatteryLevel(isLowBatteryLevel);
|
||
});
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setInputVolume(float level) {
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setOutputVolume(float level) {
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setAudioOutputDuckingEnabled(bool enabled) {
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setAudioOutputGainControlEnabled(bool enabled) {
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::setEchoCancellationStrength(int strength) {
|
||
}
|
||
|
||
std::vector<std::string> InstanceV2ReferenceImpl::GetVersions() {
|
||
std::vector<std::string> result;
|
||
result.push_back("10.0.0");
|
||
result.push_back("11.0.0");
|
||
return result;
|
||
}
|
||
|
||
int InstanceV2ReferenceImpl::GetConnectionMaxLayer() {
|
||
return 92;
|
||
}
|
||
|
||
std::string InstanceV2ReferenceImpl::getLastError() {
|
||
return "";
|
||
}
|
||
|
||
std::string InstanceV2ReferenceImpl::getDebugInfo() {
|
||
return "";
|
||
}
|
||
|
||
int64_t InstanceV2ReferenceImpl::getPreferredRelayId() {
|
||
return 0;
|
||
}
|
||
|
||
TrafficStats InstanceV2ReferenceImpl::getTrafficStats() {
|
||
return {};
|
||
}
|
||
|
||
PersistentState InstanceV2ReferenceImpl::getPersistentState() {
|
||
return {};
|
||
}
|
||
|
||
void InstanceV2ReferenceImpl::stop(std::function<void(FinalState)> completion) {
|
||
std::string debugLog;
|
||
if (_logSink) {
|
||
debugLog = _logSink->result();
|
||
}
|
||
_internal->perform([completion, debugLog = std::move(debugLog)](InstanceV2ReferenceImplInternal *internal) mutable {
|
||
internal->stop([completion, debugLog = std::move(debugLog)](FinalState finalState) mutable {
|
||
finalState.debugLog = debugLog;
|
||
completion(finalState);
|
||
});
|
||
});
|
||
}
|
||
|
||
template <>
|
||
bool Register<InstanceV2ReferenceImpl>() {
|
||
return Meta::RegisterOne<InstanceV2ReferenceImpl>();
|
||
}
|
||
|
||
} // namespace tgcalls
|