1754 lines
66 KiB
C++
1754 lines
66 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 "platform/PlatformInterface.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 "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"
|
|
#ifdef WEBRTC_IOS
|
|
#include "platform/darwin/iOS/tgcalls_audio_device_module_ios.h"
|
|
#endif
|
|
#include <random>
|
|
#include <sstream>
|
|
#include <map>
|
|
|
|
#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::PeerConnectionState state)> onConnectionChange;
|
|
std::function<void(rtc::scoped_refptr<webrtc::DataChannelInterface>)> onDataChannel;
|
|
std::function<void(rtc::scoped_refptr<webrtc::RtpTransceiverInterface>)> onTransceiverAdded;
|
|
std::function<void(rtc::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(rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {
|
|
}
|
|
|
|
void OnRemoveStream(rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {
|
|
}
|
|
|
|
void OnTrack(rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) override {
|
|
if (_parameters.onTransceiverAdded) {
|
|
_parameters.onTransceiverAdded(transceiver);
|
|
}
|
|
}
|
|
|
|
void OnDataChannel(rtc::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 {
|
|
}
|
|
|
|
void OnStandardizedIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override {
|
|
}
|
|
|
|
void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) override {
|
|
if (_parameters.onConnectionChange) {
|
|
_parameters.onConnectionChange(new_state);
|
|
}
|
|
}
|
|
|
|
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(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver, const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams) override {
|
|
|
|
}
|
|
|
|
void OnRemoveTrack(rtc::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 rtc::scoped_refptr<const webrtc::RTCStatsReport> &)> &&completion_) :
|
|
completion(std::move(completion_)) {
|
|
}
|
|
|
|
void OnStatsDelivered(const rtc::scoped_refptr<const webrtc::RTCStatsReport> &report) override {
|
|
completion(report);
|
|
}
|
|
|
|
private:
|
|
std::function<void(const rtc::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),
|
|
_encryptionKey(std::move(descriptor.encryptionKey)),
|
|
_stateUpdated(descriptor.stateUpdated),
|
|
_signalBarsUpdated(descriptor.signalBarsUpdated),
|
|
_audioLevelUpdated(descriptor.audioLevelsUpdated),
|
|
_remoteBatteryLevelIsLowUpdated(descriptor.remoteBatteryLevelIsLowUpdated),
|
|
_remoteMediaStateUpdated(descriptor.remoteMediaStateUpdated),
|
|
_remotePrefferedAspectRatioUpdated(descriptor.remotePrefferedAspectRatioUpdated),
|
|
_signalingDataEmitted(descriptor.signalingDataEmitted),
|
|
_createAudioDeviceModule(descriptor.createAudioDeviceModule),
|
|
_statsLogPath(descriptor.config.statsLogPath),
|
|
_eventLog(std::make_unique<webrtc::RtcEventLogNull>()),
|
|
_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()),
|
|
_videoCapture(descriptor.videoCapture),
|
|
_platformContext(descriptor.platformContext) {
|
|
webrtc::field_trial::InitFieldTrialsFromString(
|
|
"WebRTC-DataChannel-Dcsctp/Enabled/"
|
|
"WebRTC-Audio-iOS-Holding/Enabled/"
|
|
);
|
|
}
|
|
|
|
~InstanceV2ReferenceImplInternal() {
|
|
_currentStrongSink.reset();
|
|
|
|
_threads->getWorkerThread()->BlockingCall([&]() {
|
|
_audioDeviceModule = nullptr;
|
|
});
|
|
|
|
if (_dataChannel) {
|
|
_dataChannel->UnregisterObserver();
|
|
_dataChannel = nullptr;
|
|
}
|
|
_dataChannelObserver.reset();
|
|
|
|
_peerConnection = nullptr;
|
|
_peerConnectionObserver.reset();
|
|
_peerConnectionFactory = nullptr;
|
|
}
|
|
|
|
void start() {
|
|
PlatformInterface::SharedInstance()->configurePlatformAudio();
|
|
|
|
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);
|
|
}
|
|
);
|
|
}
|
|
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();
|
|
});
|
|
|
|
_relayPortFactory.reset(new ReflectorRelayPortFactory(_rtcServers));
|
|
|
|
webrtc::PeerConnectionFactoryDependencies peerConnectionFactoryDependencies;
|
|
peerConnectionFactoryDependencies.signaling_thread = _threads->getMediaThread();
|
|
peerConnectionFactoryDependencies.worker_thread = _threads->getWorkerThread();
|
|
peerConnectionFactoryDependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
|
|
peerConnectionFactoryDependencies.network_monitor_factory = PlatformInterface::SharedInstance()->createNetworkMonitorFactory();
|
|
|
|
cricket::MediaEngineDependencies mediaDeps;
|
|
mediaDeps.adm = _audioDeviceModule;
|
|
|
|
webrtc:: AudioProcessingBuilder builder;
|
|
mediaDeps.audio_processing = builder.Create();
|
|
|
|
|
|
mediaDeps.task_queue_factory = peerConnectionFactoryDependencies.task_queue_factory.get();
|
|
mediaDeps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus>();
|
|
mediaDeps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus>();
|
|
|
|
mediaDeps.video_encoder_factory = PlatformInterface::SharedInstance()->makeVideoEncoderFactory(_platformContext, true);
|
|
mediaDeps.video_decoder_factory = PlatformInterface::SharedInstance()->makeVideoDecoderFactory(_platformContext);
|
|
|
|
std::unique_ptr<cricket::MediaEngineInterface> mediaEngine = cricket::CreateMediaEngine(std::move(mediaDeps));
|
|
peerConnectionFactoryDependencies.media_engine = std::move(mediaEngine);
|
|
|
|
peerConnectionFactoryDependencies.call_factory = webrtc::CreateCallFactory();
|
|
peerConnectionFactoryDependencies.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(peerConnectionFactoryDependencies.task_queue_factory.get());
|
|
|
|
_peerConnectionFactory = webrtc::CreateModularPeerConnectionFactory(std::move(peerConnectionFactoryDependencies));
|
|
|
|
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->_didBeginNegotiation && !strong->_isPerformingConfiguration) {
|
|
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::PeerConnectionState state) {
|
|
const auto strong = weak.lock();
|
|
if (!strong) {
|
|
return;
|
|
}
|
|
|
|
bool isConnected = false;
|
|
bool isFailed = false;
|
|
|
|
switch (state) {
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected: {
|
|
isConnected = true;
|
|
break;
|
|
}
|
|
case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed: {
|
|
isFailed = true;
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strong->_isConnected != isConnected || strong->_isFailed != isFailed) {
|
|
strong->_isConnected = isConnected;
|
|
strong->_isFailed = isFailed;
|
|
|
|
strong->onNetworkStateUpdated();
|
|
}
|
|
};
|
|
delegateParameters.onDataChannel = [weak](rtc::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](rtc::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](rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
|
|
const auto strong = weak.lock();
|
|
if (!strong) {
|
|
return;
|
|
}
|
|
|
|
std::string mid = receiver->track()->id();
|
|
if (mid.empty()) {
|
|
return;
|
|
}
|
|
|
|
const auto transceiver = strong->_incomingVideoTransceivers.find(mid);
|
|
if (transceiver != strong->_incomingVideoTransceivers.end()) {
|
|
strong->disconnectIncomingVideoSink();
|
|
|
|
strong->_incomingVideoTransceivers.erase(transceiver);
|
|
}
|
|
};
|
|
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();
|
|
|
|
webrtc::PeerConnectionInterface::RTCConfiguration peerConnectionConfiguration;
|
|
if (_enableP2P) {
|
|
peerConnectionConfiguration.type = webrtc::PeerConnectionInterface::IceTransportsType::kAll;
|
|
} else {
|
|
peerConnectionConfiguration.type = webrtc::PeerConnectionInterface::IceTransportsType::kRelay;
|
|
}
|
|
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;
|
|
|
|
for (auto &server : _rtcServers) {
|
|
if (server.isTcp) {
|
|
continue;
|
|
}
|
|
|
|
rtc::SocketAddress address(server.host, server.port);
|
|
if (!address.IsComplete()) {
|
|
RTC_LOG(LS_ERROR) << "Invalid ICE server host: " << server.host;
|
|
continue;
|
|
}
|
|
|
|
if (server.isTurn) {
|
|
webrtc::PeerConnectionInterface::IceServer mappedServer;
|
|
|
|
mappedServer.urls.push_back(
|
|
"turn:" + address.HostAsURIString() + ":" + std::to_string(server.port));
|
|
mappedServer.username = server.login;
|
|
mappedServer.password = server.password;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
auto peerConnectionOrError = _peerConnectionFactory->CreatePeerConnectionOrError(peerConnectionConfiguration, std::move(peerConnectionDependencies));
|
|
if (peerConnectionOrError.ok()) {
|
|
_peerConnection = peerConnectionOrError.value();
|
|
}
|
|
|
|
if (_peerConnection) {
|
|
RTC_LOG(LS_INFO) << "Creating Data Channel";
|
|
|
|
if (_encryptionKey.isOutgoing) {
|
|
webrtc::DataChannelInit dataChannelInit;
|
|
webrtc::RTCErrorOr<rtc::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;
|
|
rtc::scoped_refptr<webrtc::AudioSourceInterface> audioSource = _peerConnectionFactory->CreateAudioSource(audioSourceOptions);
|
|
|
|
rtc::scoped_refptr<webrtc::AudioTrackInterface> audioTrack = _peerConnectionFactory->CreateAudioTrack("0", audioSource.get());
|
|
webrtc::RTCErrorOr<rtc::scoped_refptr<webrtc::RtpTransceiverInterface>> audioTransceiverOrError = _peerConnection->AddTransceiver(audioTrack, transceiverInit);
|
|
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(!_isMicrophoneMuted);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 {
|
|
RTC_LOG(LS_ERROR) << "Could not gzip signaling message";
|
|
}
|
|
} 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 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;
|
|
}
|
|
|
|
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;
|
|
|
|
rtc::scoped_refptr<webrtc::SetLocalDescriptionObserverInterface> observer(new rtc::RefCountedObject<SetSessionDescriptionObserver>([threads = _threads, weak](webrtc::RTCError error) {
|
|
threads->getMediaThread()->PostTask([weak]() {
|
|
const auto strong = weak.lock();
|
|
if (!strong) {
|
|
return;
|
|
}
|
|
|
|
strong->sentLocalDescription();
|
|
|
|
strong->_isMakingOffer = false;
|
|
});
|
|
}));
|
|
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 sentLocalDescription() {
|
|
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() {
|
|
if (_encryptionKey.isOutgoing) {
|
|
_didBeginNegotiation = true;
|
|
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();
|
|
|
|
bool offerCollision = (type == "offer") && (_isMakingOffer || _peerConnection->signaling_state() != webrtc::PeerConnectionInterface::SignalingState::kStable);
|
|
bool ignoreOffer = _encryptionKey.isOutgoing && offerCollision;
|
|
if (ignoreOffer) {
|
|
return;
|
|
} else {
|
|
applyRemoteSdp(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);
|
|
|
|
if (_handshakeCompleted) {
|
|
_peerConnection->AddIceCandidate(candidarePtr.get());
|
|
} else {
|
|
_pendingIceCandidates.push_back(std::move(candidarePtr));
|
|
}
|
|
}
|
|
} 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 applyRemoteSdp(std::string const &type, std::string const &sdp) {
|
|
webrtc::SdpParseError sdpParseError;
|
|
std::unique_ptr<webrtc::SessionDescriptionInterface> remoteDescription(webrtc::CreateSessionDescription(type, sdp, &sdpParseError));
|
|
const auto weak = std::weak_ptr<InstanceV2ReferenceImplInternal>(shared_from_this());
|
|
rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface> observer(new rtc::RefCountedObject<SetSessionDescriptionObserver>([threads = _threads, weak, type](webrtc::RTCError error) {
|
|
threads->getMediaThread()->PostTask([weak, type]() {
|
|
const auto strong = weak.lock();
|
|
if (!strong) {
|
|
return;
|
|
}
|
|
|
|
if (type == "offer") {
|
|
strong->_didBeginNegotiation = true;
|
|
strong->sendLocalDescription();
|
|
}
|
|
});
|
|
}));
|
|
RTC_LOG(LS_INFO) << "Calling SetRemoteDescription";
|
|
_peerConnection->SetRemoteDescription(std::move(remoteDescription), observer);
|
|
|
|
if (!_handshakeCompleted) {
|
|
_handshakeCompleted = true;
|
|
|
|
commitPendingIceCandidates();
|
|
}
|
|
}
|
|
|
|
void commitPendingIceCandidates() {
|
|
if (_pendingIceCandidates.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
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(rtc::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 sendCandidate(const cricket::Candidate &candidate) {
|
|
cricket::Candidate patchedCandidate = candidate;
|
|
patchedCandidate.set_component(1);
|
|
|
|
signaling::CandidatesMessage data;
|
|
|
|
signaling::IceCandidate serializedCandidate;
|
|
|
|
webrtc::JsepIceCandidate iceCandidate{ std::string(), 0 };
|
|
iceCandidate.SetCandidate(patchedCandidate);
|
|
std::string serialized;
|
|
const auto success = iceCandidate.ToString(&serialized);
|
|
assert(success);
|
|
(void)success;
|
|
|
|
serializedCandidate.sdpString = serialized;
|
|
|
|
data.iceCandidates.push_back(std::move(serializedCandidate));
|
|
|
|
signaling::Message message;
|
|
message.data = std::move(data);
|
|
sendSignalingMessage(message);
|
|
}
|
|
|
|
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) {
|
|
_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("1", videoCaptureImpl->source().get());
|
|
if (videoTrack) {
|
|
webrtc::RtpTransceiverInit transceiverInit;
|
|
transceiverInit.stream_ids = { "0" };
|
|
|
|
webrtc::RTCErrorOr<rtc::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(rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
|
|
if (_currentStrongSink) {
|
|
webrtc::VideoTrackInterface *videoTrack = (webrtc::VideoTrackInterface *)transceiver->receiver()->track().get();
|
|
videoTrack->AddOrUpdateSink(_currentStrongSink.get(), rtc::VideoSinkWants());
|
|
}
|
|
}
|
|
|
|
void disconnectIncomingVideoSink() {
|
|
}
|
|
|
|
void setIncomingVideoOutput(std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
|
|
_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) {
|
|
_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:
|
|
rtc::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, 1);
|
|
#else
|
|
return webrtc::AudioDeviceModule::Create(
|
|
layer,
|
|
_taskQueueFactory.get());
|
|
#endif
|
|
};
|
|
const auto check = [&](const rtc::scoped_refptr<webrtc::AudioDeviceModule> &result) {
|
|
return (result && result->Init() == 0) ? result : nullptr;
|
|
};
|
|
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::unique_ptr<Proxy> _proxy;
|
|
bool _enableP2P = false;
|
|
EncryptionKey _encryptionKey;
|
|
std::function<void(State)> _stateUpdated;
|
|
std::function<void(int)> _signalBarsUpdated;
|
|
std::function<void(float, float )> _audioLevelUpdated;
|
|
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<rtc::scoped_refptr<webrtc::AudioDeviceModule>(webrtc::TaskQueueFactory*)> _createAudioDeviceModule;
|
|
FilePath _statsLogPath;
|
|
|
|
std::unique_ptr<SignalingConnection> _signalingConnection;
|
|
std::unique_ptr<EncryptedConnection> _signalingEncryptedConnection;
|
|
|
|
bool _isConnected = false;
|
|
bool _isFailed = false;
|
|
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 _isPerformingConfiguration = false;
|
|
|
|
rtc::scoped_refptr<webrtc::AudioTrackInterface> _outgoingAudioTrack;
|
|
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> _outgoingAudioTransceiver;
|
|
bool _isMicrophoneMuted = false;
|
|
|
|
rtc::scoped_refptr<webrtc::VideoTrackInterface> _outgoingVideoTrack;
|
|
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> _outgoingVideoTransceiver;
|
|
|
|
std::map<std::string, rtc::scoped_refptr<webrtc::RtpTransceiverInterface>> _incomingVideoTransceivers;
|
|
|
|
bool _handshakeCompleted = false;
|
|
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> _pendingIceCandidates;
|
|
|
|
std::unique_ptr<DataChannelObserverImpl> _dataChannelObserver;
|
|
rtc::scoped_refptr<webrtc::DataChannelInterface> _dataChannel;
|
|
bool _isDataChannelOpen = false;
|
|
|
|
std::unique_ptr<webrtc::RtcEventLogNull> _eventLog;
|
|
std::unique_ptr<webrtc::TaskQueueFactory> _taskQueueFactory;
|
|
std::unique_ptr<cricket::RelayPortFactoryInterface> _relayPortFactory;
|
|
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> _peerConnectionFactory;
|
|
std::unique_ptr<PeerConnectionDelegateAdapter> _peerConnectionObserver;
|
|
rtc::scoped_refptr<webrtc::PeerConnectionInterface> _peerConnection;
|
|
|
|
webrtc::LocalAudioSinkAdapter _audioSource;
|
|
|
|
rtc::scoped_refptr<webrtc::AudioDeviceModule> _audioDeviceModule;
|
|
|
|
bool _isBatteryLow = false;
|
|
|
|
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> _currentStrongSink;
|
|
|
|
std::shared_ptr<VideoCaptureInterface> _videoCapture;
|
|
std::shared_ptr<PlatformContext> _platformContext;
|
|
};
|
|
|
|
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 new 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
|