matrixion/src/WebRTCSession.cpp

1155 lines
45 KiB
C++
Raw Normal View History

2020-09-22 19:07:36 +03:00
#include <QQmlEngine>
2020-10-27 20:14:06 +03:00
#include <QQuickItem>
#include <algorithm>
2020-07-16 22:44:07 +03:00
#include <cctype>
2020-11-25 04:18:13 +03:00
#include <chrono>
2020-10-27 20:14:06 +03:00
#include <cstdlib>
#include <cstring>
#include <optional>
#include <string_view>
2020-11-25 04:18:13 +03:00
#include <thread>
2020-10-27 20:14:06 +03:00
#include <utility>
2020-07-16 22:44:07 +03:00
2021-02-18 23:55:29 +03:00
#include "CallDevices.h"
2020-10-28 00:26:46 +03:00
#include "ChatPage.h"
2020-07-11 02:19:48 +03:00
#include "Logging.h"
2020-10-27 20:14:06 +03:00
#include "UserSettingsPage.h"
2020-08-01 21:31:10 +03:00
#include "WebRTCSession.h"
2020-07-11 02:19:48 +03:00
#ifdef GSTREAMER_AVAILABLE
2020-08-01 21:31:10 +03:00
extern "C"
{
2020-07-11 02:19:48 +03:00
#include "gst/gst.h"
#include "gst/sdp/sdp.h"
#define GST_USE_UNSTABLE_API
#include "gst/webrtc/webrtc.h"
}
#endif
2020-07-11 02:19:48 +03:00
2020-10-27 20:14:06 +03:00
// https://github.com/vector-im/riot-web/issues/10173
2020-10-31 00:23:56 +03:00
#define STUN_SERVER "stun://turn.matrix.org:3478"
2020-10-27 20:14:06 +03:00
2021-02-18 23:55:29 +03:00
Q_DECLARE_METATYPE(webrtc::CallType)
2020-09-22 19:07:36 +03:00
Q_DECLARE_METATYPE(webrtc::State)
2021-02-18 23:55:29 +03:00
using webrtc::CallType;
2020-09-22 19:07:36 +03:00
using webrtc::State;
2020-07-23 04:15:45 +03:00
2020-08-01 21:31:10 +03:00
WebRTCSession::WebRTCSession()
: QObject()
, devices_(CallDevices::instance())
2020-07-23 04:15:45 +03:00
{
2021-02-18 23:55:29 +03:00
qRegisterMetaType<webrtc::CallType>();
qmlRegisterUncreatableMetaObject(
webrtc::staticMetaObject, "im.nheko", 1, 0, "CallType", "Can't instantiate enum");
2020-09-22 19:07:36 +03:00
qRegisterMetaType<webrtc::State>();
qmlRegisterUncreatableMetaObject(
2020-09-22 19:14:15 +03:00
webrtc::staticMetaObject, "im.nheko", 1, 0, "WebRTCState", "Can't instantiate enum");
2020-09-22 19:07:36 +03:00
2020-08-01 21:31:10 +03:00
connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
init();
2020-07-23 04:15:45 +03:00
}
2020-07-11 02:19:48 +03:00
bool
WebRTCSession::init(std::string *errorMessage)
{
#ifdef GSTREAMER_AVAILABLE
2020-08-01 21:31:10 +03:00
if (initialised_)
return true;
GError *error = nullptr;
if (!gst_init_check(nullptr, nullptr, &error)) {
2020-10-27 20:14:06 +03:00
std::string strError("WebRTC: failed to initialise GStreamer: ");
2020-08-01 21:31:10 +03:00
if (error) {
strError += error->message;
g_error_free(error);
}
nhlog::ui()->error(strError);
if (errorMessage)
*errorMessage = strError;
return false;
}
2020-10-27 20:14:06 +03:00
initialised_ = true;
2020-08-01 21:31:10 +03:00
gchar *version = gst_version_string();
2020-10-27 20:14:06 +03:00
nhlog::ui()->info("WebRTC: initialised {}", version);
2020-08-01 21:31:10 +03:00
g_free(version);
devices_.init();
2020-10-27 20:14:06 +03:00
return true;
#else
(void)errorMessage;
return false;
#endif
2020-07-11 02:19:48 +03:00
}
#ifdef GSTREAMER_AVAILABLE
2020-08-01 21:31:10 +03:00
namespace {
2020-10-27 20:14:06 +03:00
2020-08-01 21:31:10 +03:00
std::string localsdp_;
std::vector<mtx::events::msg::CallCandidates::Candidate> localcandidates_;
bool haveAudioStream_ = false;
bool haveVideoStream_ = false;
GstPad *localPiPSinkPad_ = nullptr;
GstPad *remotePiPSinkPad_ = nullptr;
2020-10-27 20:14:06 +03:00
2020-08-01 21:31:10 +03:00
gboolean
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
WebRTCSession *session = static_cast<WebRTCSession *>(user_data);
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
nhlog::ui()->error("WebRTC: end of stream");
session->end();
break;
case GST_MESSAGE_ERROR:
GError *error;
gchar *debug;
gst_message_parse_error(msg, &error, &debug);
nhlog::ui()->error(
"WebRTC: error from element {}: {}", GST_OBJECT_NAME(msg->src), error->message);
g_clear_error(&error);
g_free(debug);
session->end();
break;
default:
break;
}
return TRUE;
2020-07-11 02:19:48 +03:00
}
2020-08-01 21:31:10 +03:00
GstWebRTCSessionDescription *
parseSDP(const std::string &sdp, GstWebRTCSDPType type)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
GstSDPMessage *msg;
gst_sdp_message_new(&msg);
if (gst_sdp_message_parse_buffer((guint8 *)sdp.c_str(), sdp.size(), msg) == GST_SDP_OK) {
return gst_webrtc_session_description_new(type, msg);
} else {
nhlog::ui()->error("WebRTC: failed to parse remote session description");
2020-10-27 20:14:06 +03:00
gst_sdp_message_free(msg);
2020-08-01 21:31:10 +03:00
return nullptr;
}
2020-07-11 02:19:48 +03:00
}
2020-08-01 21:31:10 +03:00
void
setLocalDescription(GstPromise *promise, gpointer webrtc)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
const GstStructure *reply = gst_promise_get_reply(promise);
gboolean isAnswer = gst_structure_id_has_field(reply, g_quark_from_string("answer"));
GstWebRTCSessionDescription *gstsdp = nullptr;
gst_structure_get(reply,
isAnswer ? "answer" : "offer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
&gstsdp,
nullptr);
gst_promise_unref(promise);
g_signal_emit_by_name(webrtc, "set-local-description", gstsdp, nullptr);
gchar *sdp = gst_sdp_message_as_text(gstsdp->sdp);
localsdp_ = std::string(sdp);
g_free(sdp);
gst_webrtc_session_description_free(gstsdp);
nhlog::ui()->debug(
"WebRTC: local description set ({}):\n{}", isAnswer ? "answer" : "offer", localsdp_);
2020-07-11 02:19:48 +03:00
}
2020-08-01 21:31:10 +03:00
void
createOffer(GstElement *webrtc)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
// create-offer first, then set-local-description
GstPromise *promise =
gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
g_signal_emit_by_name(webrtc, "create-offer", nullptr, promise);
2020-07-11 02:19:48 +03:00
}
void
2020-08-01 21:31:10 +03:00
createAnswer(GstPromise *promise, gpointer webrtc)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
// create-answer first, then set-local-description
gst_promise_unref(promise);
promise = gst_promise_new_with_change_func(setLocalDescription, webrtc, nullptr);
g_signal_emit_by_name(webrtc, "create-answer", nullptr, promise);
2020-07-11 02:19:48 +03:00
}
void
iceGatheringStateChanged(GstElement *webrtc,
GParamSpec *pspec G_GNUC_UNUSED,
gpointer user_data G_GNUC_UNUSED)
{
GstWebRTCICEGatheringState newState;
g_object_get(webrtc, "ice-gathering-state", &newState, nullptr);
if (newState == GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE) {
nhlog::ui()->debug("WebRTC: GstWebRTCICEGatheringState -> Complete");
2020-10-27 20:14:06 +03:00
if (WebRTCSession::instance().isOffering()) {
emit WebRTCSession::instance().offerCreated(localsdp_, localcandidates_);
2020-09-22 19:14:15 +03:00
emit WebRTCSession::instance().stateChanged(State::OFFERSENT);
} else {
emit WebRTCSession::instance().answerCreated(localsdp_, localcandidates_);
2020-09-22 19:14:15 +03:00
emit WebRTCSession::instance().stateChanged(State::ANSWERSENT);
}
}
}
2020-07-11 02:19:48 +03:00
void
2020-08-01 21:31:10 +03:00
addLocalICECandidate(GstElement *webrtc G_GNUC_UNUSED,
guint mlineIndex,
gchar *candidate,
gpointer G_GNUC_UNUSED)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
nhlog::ui()->debug("WebRTC: local candidate: (m-line:{}):{}", mlineIndex, candidate);
2020-10-27 20:14:06 +03:00
localcandidates_.push_back({std::string() /*max-bundle*/, (uint16_t)mlineIndex, candidate});
2020-07-11 02:19:48 +03:00
}
2020-08-01 21:31:10 +03:00
void
iceConnectionStateChanged(GstElement *webrtc,
GParamSpec *pspec G_GNUC_UNUSED,
gpointer user_data G_GNUC_UNUSED)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
GstWebRTCICEConnectionState newState;
g_object_get(webrtc, "ice-connection-state", &newState, nullptr);
switch (newState) {
case GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
nhlog::ui()->debug("WebRTC: GstWebRTCICEConnectionState -> Checking");
2020-09-22 19:07:36 +03:00
emit WebRTCSession::instance().stateChanged(State::CONNECTING);
2020-08-01 21:31:10 +03:00
break;
case GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
nhlog::ui()->error("WebRTC: GstWebRTCICEConnectionState -> Failed");
2020-09-22 19:07:36 +03:00
emit WebRTCSession::instance().stateChanged(State::ICEFAILED);
2020-08-01 21:31:10 +03:00
break;
default:
break;
}
2020-07-11 02:19:48 +03:00
}
2020-10-27 20:14:06 +03:00
// https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1164
struct KeyFrameRequestData
{
GstElement *pipe = nullptr;
GstElement *decodebin = nullptr;
gint packetsLost = 0;
guint timerid = 0;
std::string statsField;
} keyFrameRequestData_;
void
sendKeyFrameRequest()
{
GstPad *sinkpad = gst_element_get_static_pad(keyFrameRequestData_.decodebin, "sink");
if (!gst_pad_push_event(sinkpad,
gst_event_new_custom(GST_EVENT_CUSTOM_UPSTREAM,
gst_structure_new_empty("GstForceKeyUnit"))))
nhlog::ui()->error("WebRTC: key frame request failed");
else
nhlog::ui()->debug("WebRTC: sent key frame request");
gst_object_unref(sinkpad);
}
2020-07-11 02:19:48 +03:00
void
2020-10-27 20:14:06 +03:00
testPacketLoss_(GstPromise *promise, gpointer G_GNUC_UNUSED)
2020-07-11 02:19:48 +03:00
{
2020-10-27 20:14:06 +03:00
const GstStructure *reply = gst_promise_get_reply(promise);
gint packetsLost = 0;
GstStructure *rtpStats;
if (!gst_structure_get(reply,
keyFrameRequestData_.statsField.c_str(),
GST_TYPE_STRUCTURE,
&rtpStats,
nullptr)) {
nhlog::ui()->error("WebRTC: get-stats: no field: {}",
keyFrameRequestData_.statsField);
gst_promise_unref(promise);
2020-08-01 21:31:10 +03:00
return;
2020-10-27 20:14:06 +03:00
}
gst_structure_get_int(rtpStats, "packets-lost", &packetsLost);
gst_structure_free(rtpStats);
gst_promise_unref(promise);
if (packetsLost > keyFrameRequestData_.packetsLost) {
nhlog::ui()->debug("WebRTC: inbound video lost packet count: {}", packetsLost);
keyFrameRequestData_.packetsLost = packetsLost;
sendKeyFrameRequest();
}
}
2020-08-01 21:31:10 +03:00
2020-10-27 20:14:06 +03:00
gboolean
testPacketLoss(gpointer G_GNUC_UNUSED)
{
if (keyFrameRequestData_.pipe) {
GstElement *webrtc =
gst_bin_get_by_name(GST_BIN(keyFrameRequestData_.pipe), "webrtcbin");
GstPromise *promise =
gst_promise_new_with_change_func(testPacketLoss_, nullptr, nullptr);
g_signal_emit_by_name(webrtc, "get-stats", nullptr, promise);
gst_object_unref(webrtc);
return TRUE;
}
return FALSE;
}
2020-08-01 21:31:10 +03:00
2020-10-27 20:14:06 +03:00
void
setWaitForKeyFrame(GstBin *decodebin G_GNUC_UNUSED, GstElement *element, gpointer G_GNUC_UNUSED)
{
if (!std::strcmp(
gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(gst_element_get_factory(element))),
"rtpvp8depay"))
g_object_set(element, "wait-for-keyframe", TRUE, nullptr);
}
2020-11-09 18:51:17 +03:00
GstElement *
newAudioSinkChain(GstElement *pipe)
{
GstElement *queue = gst_element_factory_make("queue", nullptr);
GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
GstElement *resample = gst_element_factory_make("audioresample", nullptr);
GstElement *sink = gst_element_factory_make("autoaudiosink", nullptr);
gst_bin_add_many(GST_BIN(pipe), queue, convert, resample, sink, nullptr);
gst_element_link_many(queue, convert, resample, sink, nullptr);
gst_element_sync_state_with_parent(queue);
gst_element_sync_state_with_parent(convert);
gst_element_sync_state_with_parent(resample);
gst_element_sync_state_with_parent(sink);
return queue;
}
GstElement *
newVideoSinkChain(GstElement *pipe)
{
// use compositor for now; acceleration needs investigation
GstElement *queue = gst_element_factory_make("queue", nullptr);
GstElement *compositor = gst_element_factory_make("compositor", "compositor");
GstElement *glupload = gst_element_factory_make("glupload", nullptr);
GstElement *glcolorconvert = gst_element_factory_make("glcolorconvert", nullptr);
GstElement *qmlglsink = gst_element_factory_make("qmlglsink", nullptr);
GstElement *glsinkbin = gst_element_factory_make("glsinkbin", nullptr);
g_object_set(compositor, "background", 1, nullptr);
2020-11-09 18:51:17 +03:00
g_object_set(qmlglsink, "widget", WebRTCSession::instance().getVideoItem(), nullptr);
g_object_set(glsinkbin, "sink", qmlglsink, nullptr);
gst_bin_add_many(
GST_BIN(pipe), queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
gst_element_link_many(queue, compositor, glupload, glcolorconvert, glsinkbin, nullptr);
gst_element_sync_state_with_parent(queue);
gst_element_sync_state_with_parent(compositor);
gst_element_sync_state_with_parent(glupload);
gst_element_sync_state_with_parent(glcolorconvert);
gst_element_sync_state_with_parent(glsinkbin);
return queue;
}
std::pair<int, int>
getResolution(GstPad *pad)
{
std::pair<int, int> ret;
GstCaps *caps = gst_pad_get_current_caps(pad);
const GstStructure *s = gst_caps_get_structure(caps, 0);
gst_structure_get_int(s, "width", &ret.first);
gst_structure_get_int(s, "height", &ret.second);
gst_caps_unref(caps);
return ret;
}
void
addLocalPiP(GstElement *pipe, const std::pair<int, int> &videoCallSize)
2020-11-09 18:51:17 +03:00
{
// embed localUser's camera into received video
2020-12-17 20:45:54 +03:00
GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe), "videosrctee");
if (!tee)
return;
2020-11-09 18:51:17 +03:00
GstElement *queue = gst_element_factory_make("queue", nullptr);
GstElement *videorate = gst_element_factory_make("videorate", nullptr);
gst_bin_add_many(GST_BIN(pipe), queue, videorate, nullptr);
gst_element_link_many(tee, queue, videorate, nullptr);
gst_element_sync_state_with_parent(queue);
gst_element_sync_state_with_parent(videorate);
gst_object_unref(tee);
GstElement *camerafilter = gst_bin_get_by_name(GST_BIN(pipe), "camerafilter");
GstPad *filtersinkpad = gst_element_get_static_pad(camerafilter, "sink");
auto cameraResolution = getResolution(filtersinkpad);
int pipWidth = videoCallSize.first / 4;
int pipHeight =
static_cast<double>(cameraResolution.second) / cameraResolution.first * pipWidth;
nhlog::ui()->debug("WebRTC: local picture-in-picture: {}x{}", pipWidth, pipHeight);
2020-11-09 18:51:17 +03:00
gst_object_unref(filtersinkpad);
gst_object_unref(camerafilter);
GstPad *camerapad = gst_element_get_static_pad(videorate, "src");
GstElement *compositor = gst_bin_get_by_name(GST_BIN(pipe), "compositor");
localPiPSinkPad_ = gst_element_get_request_pad(compositor, "sink_%u");
g_object_set(localPiPSinkPad_, "zorder", 2, nullptr);
g_object_set(localPiPSinkPad_, "width", pipWidth, "height", pipHeight, nullptr);
2020-11-09 18:51:17 +03:00
gint offset = videoCallSize.first / 80;
g_object_set(localPiPSinkPad_, "xpos", offset, "ypos", offset, nullptr);
if (GST_PAD_LINK_FAILED(gst_pad_link(camerapad, localPiPSinkPad_)))
nhlog::ui()->error("WebRTC: failed to link local PiP elements");
2020-11-09 18:51:17 +03:00
gst_object_unref(camerapad);
gst_object_unref(compositor);
}
void
addRemotePiP(GstElement *pipe)
{
// embed localUser's camera into screen image being shared
if (remotePiPSinkPad_) {
GstElement *screen = gst_bin_get_by_name(GST_BIN(pipe), "screenshare");
GstPad *srcpad = gst_element_get_static_pad(screen, "src");
auto resolution = getResolution(srcpad);
nhlog::ui()->debug(
"WebRTC: screen share: {}x{}", resolution.first, resolution.second);
gst_object_unref(srcpad);
gst_object_unref(screen);
int pipWidth = resolution.first / 5;
int pipHeight =
static_cast<double>(resolution.second) / resolution.first * pipWidth;
nhlog::ui()->debug(
"WebRTC: screen share picture-in-picture: {}x{}", pipWidth, pipHeight);
gint offset = resolution.first / 100;
g_object_set(remotePiPSinkPad_, "zorder", 2, nullptr);
g_object_set(remotePiPSinkPad_, "width", pipWidth, "height", pipHeight, nullptr);
g_object_set(remotePiPSinkPad_,
"xpos",
resolution.first - pipWidth - offset,
"ypos",
resolution.second - pipHeight - offset,
nullptr);
}
}
2020-10-27 20:14:06 +03:00
void
linkNewPad(GstElement *decodebin, GstPad *newpad, GstElement *pipe)
{
GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
GstCaps *sinkcaps = gst_pad_get_current_caps(sinkpad);
const GstStructure *structure = gst_caps_get_structure(sinkcaps, 0);
gchar *mediaType = nullptr;
guint ssrc = 0;
gst_structure_get(
structure, "media", G_TYPE_STRING, &mediaType, "ssrc", G_TYPE_UINT, &ssrc, nullptr);
gst_caps_unref(sinkcaps);
gst_object_unref(sinkpad);
WebRTCSession *session = &WebRTCSession::instance();
2020-11-09 18:51:17 +03:00
GstElement *queue = nullptr;
2020-10-27 20:14:06 +03:00
if (!std::strcmp(mediaType, "audio")) {
2020-08-01 21:31:10 +03:00
nhlog::ui()->debug("WebRTC: received incoming audio stream");
2020-11-09 18:51:17 +03:00
haveAudioStream_ = true;
queue = newAudioSinkChain(pipe);
2020-10-27 20:14:06 +03:00
} else if (!std::strcmp(mediaType, "video")) {
nhlog::ui()->debug("WebRTC: received incoming video stream");
if (!session->getVideoItem()) {
g_free(mediaType);
nhlog::ui()->error("WebRTC: video call item not set");
return;
}
haveVideoStream_ = true;
keyFrameRequestData_.statsField =
std::string("rtp-inbound-stream-stats_") + std::to_string(ssrc);
2020-11-09 18:51:17 +03:00
queue = newVideoSinkChain(pipe);
auto videoCallSize = getResolution(newpad);
nhlog::ui()->info("WebRTC: incoming video resolution: {}x{}",
videoCallSize.first,
videoCallSize.second);
2021-02-18 23:55:29 +03:00
if (session->callType() == CallType::VIDEO)
addLocalPiP(pipe, videoCallSize);
2020-10-27 20:14:06 +03:00
} else {
g_free(mediaType);
nhlog::ui()->error("WebRTC: unknown pad type: {}", GST_PAD_NAME(newpad));
return;
2020-08-01 21:31:10 +03:00
}
2020-10-27 20:14:06 +03:00
GstPad *queuepad = gst_element_get_static_pad(queue, "sink");
2020-08-01 21:31:10 +03:00
if (queuepad) {
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, queuepad)))
nhlog::ui()->error("WebRTC: unable to link new pad");
else {
2021-02-18 23:55:29 +03:00
if (session->callType() == CallType::VOICE ||
2020-10-27 20:14:06 +03:00
(haveAudioStream_ &&
(haveVideoStream_ || session->isRemoteVideoRecvOnly()))) {
emit session->stateChanged(State::CONNECTED);
if (haveVideoStream_) {
keyFrameRequestData_.pipe = pipe;
keyFrameRequestData_.decodebin = decodebin;
keyFrameRequestData_.timerid =
g_timeout_add_seconds(3, testPacketLoss, nullptr);
}
addRemotePiP(pipe);
2020-10-27 20:14:06 +03:00
}
2020-08-01 21:31:10 +03:00
}
gst_object_unref(queuepad);
}
2020-10-27 20:14:06 +03:00
g_free(mediaType);
2020-07-11 02:19:48 +03:00
}
void
2020-08-01 21:31:10 +03:00
addDecodeBin(GstElement *webrtc G_GNUC_UNUSED, GstPad *newpad, GstElement *pipe)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
if (GST_PAD_DIRECTION(newpad) != GST_PAD_SRC)
return;
nhlog::ui()->debug("WebRTC: received incoming stream");
GstElement *decodebin = gst_element_factory_make("decodebin", nullptr);
2020-10-27 20:14:06 +03:00
// hardware decoding needs investigation; eg rendering fails if vaapi plugin installed
g_object_set(decodebin, "force-sw-decoders", TRUE, nullptr);
2020-08-01 21:31:10 +03:00
g_signal_connect(decodebin, "pad-added", G_CALLBACK(linkNewPad), pipe);
2020-10-30 03:13:34 +03:00
g_signal_connect(decodebin, "element-added", G_CALLBACK(setWaitForKeyFrame), nullptr);
2020-08-01 21:31:10 +03:00
gst_bin_add(GST_BIN(pipe), decodebin);
gst_element_sync_state_with_parent(decodebin);
GstPad *sinkpad = gst_element_get_static_pad(decodebin, "sink");
if (GST_PAD_LINK_FAILED(gst_pad_link(newpad, sinkpad)))
2020-11-09 18:51:17 +03:00
nhlog::ui()->error("WebRTC: unable to link decodebin");
2020-08-01 21:31:10 +03:00
gst_object_unref(sinkpad);
2020-07-11 02:19:48 +03:00
}
2020-10-27 20:14:06 +03:00
bool
2020-10-30 02:17:10 +03:00
contains(std::string_view str1, std::string_view str2)
2020-07-11 02:19:48 +03:00
{
2020-10-27 20:14:06 +03:00
return std::search(str1.cbegin(),
str1.cend(),
str2.cbegin(),
str2.cend(),
[](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2);
}) != str1.cend();
2020-08-01 21:31:10 +03:00
}
2020-07-26 01:11:11 +03:00
2020-10-27 20:14:06 +03:00
bool
getMediaAttributes(const GstSDPMessage *sdp,
const char *mediaType,
const char *encoding,
int &payloadType,
2021-02-18 23:55:29 +03:00
bool &recvOnly,
bool &sendOnly)
2020-08-01 21:31:10 +03:00
{
2020-10-27 20:14:06 +03:00
payloadType = -1;
recvOnly = false;
2021-02-18 23:55:29 +03:00
sendOnly = false;
2020-10-27 20:14:06 +03:00
for (guint mlineIndex = 0; mlineIndex < gst_sdp_message_medias_len(sdp); ++mlineIndex) {
const GstSDPMedia *media = gst_sdp_message_get_media(sdp, mlineIndex);
if (!std::strcmp(gst_sdp_media_get_media(media), mediaType)) {
recvOnly = gst_sdp_media_get_attribute_val(media, "recvonly") != nullptr;
2021-02-18 23:55:29 +03:00
sendOnly = gst_sdp_media_get_attribute_val(media, "sendonly") != nullptr;
2020-10-27 20:14:06 +03:00
const gchar *rtpval = nullptr;
for (guint n = 0; n == 0 || rtpval; ++n) {
rtpval = gst_sdp_media_get_attribute_val_n(media, "rtpmap", n);
2020-10-30 02:17:10 +03:00
if (rtpval && contains(rtpval, encoding)) {
2020-10-27 20:14:06 +03:00
payloadType = std::atoi(rtpval);
break;
}
}
return true;
}
2020-08-01 21:31:10 +03:00
}
2020-10-27 20:14:06 +03:00
return false;
}
2020-08-01 21:31:10 +03:00
2020-10-27 20:14:06 +03:00
}
bool
WebRTCSession::havePlugins(bool isVideo, std::string *errorMessage)
{
if (!initialised_ && !init(errorMessage))
return false;
if (!isVideo && haveVoicePlugins_)
return true;
if (isVideo && haveVideoPlugins_)
return true;
const gchar *voicePlugins[] = {"audioconvert",
"audioresample",
"autodetect",
"dtls",
"nice",
"opus",
"playback",
"rtpmanager",
"srtp",
"volume",
"webrtc",
nullptr};
2020-11-09 18:51:17 +03:00
const gchar *videoPlugins[] = {
"compositor", "opengl", "qmlgl", "rtp", "videoconvert", "vpx", nullptr};
2020-10-27 20:14:06 +03:00
std::string strError("Missing GStreamer plugins: ");
const gchar **needed = isVideo ? videoPlugins : voicePlugins;
bool &havePlugins = isVideo ? haveVideoPlugins_ : haveVoicePlugins_;
havePlugins = true;
GstRegistry *registry = gst_registry_get();
for (guint i = 0; i < g_strv_length((gchar **)needed); i++) {
GstPlugin *plugin = gst_registry_find_plugin(registry, needed[i]);
if (!plugin) {
havePlugins = false;
strError += std::string(needed[i]) + " ";
continue;
2020-08-01 21:31:10 +03:00
}
2020-10-27 20:14:06 +03:00
gst_object_unref(plugin);
2020-08-01 21:31:10 +03:00
}
2020-10-27 20:14:06 +03:00
if (!havePlugins) {
nhlog::ui()->error(strError);
if (errorMessage)
*errorMessage = strError;
return false;
}
if (isVideo) {
// load qmlglsink to register GStreamer's GstGLVideoItem QML type
GstElement *qmlglsink = gst_element_factory_make("qmlglsink", nullptr);
gst_object_unref(qmlglsink);
}
return true;
2020-08-01 21:31:10 +03:00
}
2020-07-11 02:19:48 +03:00
2020-08-01 21:31:10 +03:00
bool
2021-02-18 23:55:29 +03:00
WebRTCSession::createOffer(CallType callType)
2020-08-01 21:31:10 +03:00
{
clear();
isOffering_ = true;
callType_ = callType;
2020-10-30 02:17:10 +03:00
// opus and vp8 rtp payload types must be defined dynamically
// therefore from the range [96-127]
// see for example https://tools.ietf.org/html/rfc7587
constexpr int opusPayloadType = 111;
2020-10-30 03:13:34 +03:00
constexpr int vp8PayloadType = 96;
2020-10-30 02:17:10 +03:00
return startPipeline(opusPayloadType, vp8PayloadType);
2020-08-01 21:31:10 +03:00
}
2020-07-11 02:19:48 +03:00
2020-08-01 21:31:10 +03:00
bool
WebRTCSession::acceptOffer(const std::string &sdp)
{
nhlog::ui()->debug("WebRTC: received offer:\n{}", sdp);
if (state_ != State::DISCONNECTED)
return false;
clear();
2020-08-01 21:31:10 +03:00
GstWebRTCSessionDescription *offer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_OFFER);
if (!offer)
return false;
2020-10-27 20:14:06 +03:00
int opusPayloadType;
bool recvOnly;
2021-02-18 23:55:29 +03:00
bool sendOnly;
if (getMediaAttributes(offer->sdp, "audio", "opus", opusPayloadType, recvOnly, sendOnly)) {
2020-10-27 20:14:06 +03:00
if (opusPayloadType == -1) {
nhlog::ui()->error("WebRTC: remote audio offer - no opus encoding");
gst_webrtc_session_description_free(offer);
return false;
}
} else {
nhlog::ui()->error("WebRTC: remote offer - no audio media");
gst_webrtc_session_description_free(offer);
return false;
}
int vp8PayloadType;
2021-02-18 23:55:29 +03:00
bool isVideo = getMediaAttributes(offer->sdp,
"video",
"vp8",
vp8PayloadType,
isRemoteVideoRecvOnly_,
isRemoteVideoSendOnly_);
if (isVideo && vp8PayloadType == -1) {
2020-10-27 20:14:06 +03:00
nhlog::ui()->error("WebRTC: remote video offer - no vp8 encoding");
gst_webrtc_session_description_free(offer);
return false;
}
2021-02-18 23:55:29 +03:00
callType_ = isVideo ? CallType::VIDEO : CallType::VOICE;
2020-10-27 20:14:06 +03:00
if (!startPipeline(opusPayloadType, vp8PayloadType)) {
2020-08-01 21:31:10 +03:00
gst_webrtc_session_description_free(offer);
return false;
}
// avoid a race that sometimes leaves the generated answer without media tracks (a=ssrc
// lines)
2020-11-25 04:18:13 +03:00
std::this_thread::sleep_for(std::chrono::milliseconds(200));
2020-08-01 21:31:10 +03:00
// set-remote-description first, then create-answer
GstPromise *promise = gst_promise_new_with_change_func(createAnswer, webrtc_, nullptr);
g_signal_emit_by_name(webrtc_, "set-remote-description", offer, promise);
gst_webrtc_session_description_free(offer);
return true;
2020-07-11 02:19:48 +03:00
}
2020-08-01 21:31:10 +03:00
bool
WebRTCSession::acceptAnswer(const std::string &sdp)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
nhlog::ui()->debug("WebRTC: received answer:\n{}", sdp);
if (state_ != State::OFFERSENT)
return false;
GstWebRTCSessionDescription *answer = parseSDP(sdp, GST_WEBRTC_SDP_TYPE_ANSWER);
if (!answer) {
end();
return false;
}
2021-02-18 23:55:29 +03:00
if (callType_ != CallType::VOICE) {
2020-10-27 20:14:06 +03:00
int unused;
2021-02-18 23:55:29 +03:00
if (!getMediaAttributes(answer->sdp,
"video",
"vp8",
unused,
isRemoteVideoRecvOnly_,
isRemoteVideoSendOnly_))
2020-10-27 20:14:06 +03:00
isRemoteVideoRecvOnly_ = true;
}
2020-08-01 21:31:10 +03:00
g_signal_emit_by_name(webrtc_, "set-remote-description", answer, nullptr);
gst_webrtc_session_description_free(answer);
return true;
2020-07-11 02:19:48 +03:00
}
2020-07-26 17:59:50 +03:00
void
2020-08-01 21:31:10 +03:00
WebRTCSession::acceptICECandidates(
const std::vector<mtx::events::msg::CallCandidates::Candidate> &candidates)
2020-07-26 17:59:50 +03:00
{
2020-08-01 21:31:10 +03:00
if (state_ >= State::INITIATED) {
for (const auto &c : candidates) {
nhlog::ui()->debug(
"WebRTC: remote candidate: (m-line:{}):{}", c.sdpMLineIndex, c.candidate);
2020-09-13 17:21:29 +03:00
if (!c.candidate.empty()) {
g_signal_emit_by_name(webrtc_,
"add-ice-candidate",
c.sdpMLineIndex,
c.candidate.c_str());
}
2020-08-01 21:31:10 +03:00
}
}
2020-07-26 17:59:50 +03:00
}
2020-08-01 21:31:10 +03:00
bool
2020-10-27 20:14:06 +03:00
WebRTCSession::startPipeline(int opusPayloadType, int vp8PayloadType)
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
if (state_ != State::DISCONNECTED)
return false;
emit stateChanged(State::INITIATING);
2020-10-27 20:14:06 +03:00
if (!createPipeline(opusPayloadType, vp8PayloadType)) {
end();
2020-08-01 21:31:10 +03:00
return false;
2020-10-27 20:14:06 +03:00
}
2020-08-01 21:31:10 +03:00
webrtc_ = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
2020-10-28 00:26:46 +03:00
if (ChatPage::instance()->userSettings()->useStunServer()) {
2020-10-27 20:14:06 +03:00
nhlog::ui()->info("WebRTC: setting STUN server: {}", STUN_SERVER);
g_object_set(webrtc_, "stun-server", STUN_SERVER, nullptr);
2020-08-01 21:31:10 +03:00
}
for (const auto &uri : turnServers_) {
nhlog::ui()->info("WebRTC: setting TURN server: {}", uri);
gboolean udata;
g_signal_emit_by_name(webrtc_, "add-turn-server", uri.c_str(), (gpointer)(&udata));
}
if (turnServers_.empty())
nhlog::ui()->warn("WebRTC: no TURN server provided");
// generate the offer when the pipeline goes to PLAYING
2020-10-27 20:14:06 +03:00
if (isOffering_)
2020-08-01 21:31:10 +03:00
g_signal_connect(
webrtc_, "on-negotiation-needed", G_CALLBACK(::createOffer), nullptr);
// on-ice-candidate is emitted when a local ICE candidate has been gathered
g_signal_connect(webrtc_, "on-ice-candidate", G_CALLBACK(addLocalICECandidate), nullptr);
// capture ICE failure
g_signal_connect(
webrtc_, "notify::ice-connection-state", G_CALLBACK(iceConnectionStateChanged), nullptr);
// incoming streams trigger pad-added
gst_element_set_state(pipe_, GST_STATE_READY);
g_signal_connect(webrtc_, "pad-added", G_CALLBACK(addDecodeBin), pipe_);
// capture ICE gathering completion
g_signal_connect(
webrtc_, "notify::ice-gathering-state", G_CALLBACK(iceGatheringStateChanged), nullptr);
2020-08-01 21:31:10 +03:00
// webrtcbin lifetime is the same as that of the pipeline
gst_object_unref(webrtc_);
// start the pipeline
GstStateChangeReturn ret = gst_element_set_state(pipe_, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
nhlog::ui()->error("WebRTC: unable to start pipeline");
end();
return false;
}
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_));
2020-09-16 14:29:26 +03:00
busWatchId_ = gst_bus_add_watch(bus, newBusMessage, this);
2020-08-01 21:31:10 +03:00
gst_object_unref(bus);
emit stateChanged(State::INITIATED);
return true;
2020-07-11 02:19:48 +03:00
}
2020-08-01 21:31:10 +03:00
bool
2020-10-27 20:14:06 +03:00
WebRTCSession::createPipeline(int opusPayloadType, int vp8PayloadType)
2020-07-11 02:19:48 +03:00
{
GstDevice *device = devices_.audioDevice();
if (!device)
2020-08-06 00:56:44 +03:00
return false;
GstElement *source = gst_device_create_element(device, nullptr);
2020-08-06 00:56:44 +03:00
GstElement *volume = gst_element_factory_make("volume", "srclevel");
GstElement *convert = gst_element_factory_make("audioconvert", nullptr);
GstElement *resample = gst_element_factory_make("audioresample", nullptr);
GstElement *queue1 = gst_element_factory_make("queue", nullptr);
GstElement *opusenc = gst_element_factory_make("opusenc", nullptr);
GstElement *rtp = gst_element_factory_make("rtpopuspay", nullptr);
GstElement *queue2 = gst_element_factory_make("queue", nullptr);
GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
"media",
G_TYPE_STRING,
"audio",
"encoding-name",
G_TYPE_STRING,
"OPUS",
"payload",
G_TYPE_INT,
opusPayloadType,
nullptr);
g_object_set(capsfilter, "caps", rtpcaps, nullptr);
gst_caps_unref(rtpcaps);
GstElement *webrtcbin = gst_element_factory_make("webrtcbin", "webrtcbin");
g_object_set(webrtcbin, "bundle-policy", GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE, nullptr);
pipe_ = gst_pipeline_new(nullptr);
gst_bin_add_many(GST_BIN(pipe_),
source,
volume,
convert,
resample,
queue1,
opusenc,
rtp,
queue2,
capsfilter,
webrtcbin,
nullptr);
if (!gst_element_link_many(source,
volume,
convert,
resample,
queue1,
opusenc,
rtp,
queue2,
capsfilter,
webrtcbin,
nullptr)) {
2020-10-27 20:14:06 +03:00
nhlog::ui()->error("WebRTC: failed to link audio pipeline elements");
return false;
}
2020-11-09 18:51:17 +03:00
2021-02-18 23:55:29 +03:00
return callType_ == CallType::VOICE || isRemoteVideoSendOnly_
? true
: addVideoPipeline(vp8PayloadType);
2020-10-27 20:14:06 +03:00
}
bool
WebRTCSession::addVideoPipeline(int vp8PayloadType)
{
// allow incoming video calls despite localUser having no webcam
2021-02-18 23:55:29 +03:00
if (callType_ == CallType::VIDEO && !devices_.haveCamera())
2020-10-27 20:14:06 +03:00
return !isOffering_;
auto settings = ChatPage::instance()->userSettings();
if (callType_ == CallType::SCREEN && settings->screenSharePiP() && !devices_.haveCamera())
return false;
GstElement *camerafilter = nullptr;
GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr);
GstElement *tee = gst_element_factory_make("tee", "videosrctee");
gst_bin_add_many(GST_BIN(pipe_), videoconvert, tee, nullptr);
if (callType_ == CallType::VIDEO || settings->screenSharePiP()) {
2021-02-18 23:55:29 +03:00
std::pair<int, int> resolution;
std::pair<int, int> frameRate;
GstDevice *device = devices_.videoDevice(resolution, frameRate);
if (!device)
return false;
GstElement *camera = gst_device_create_element(device, nullptr);
GstCaps *caps = gst_caps_new_simple("video/x-raw",
"width",
G_TYPE_INT,
resolution.first,
"height",
G_TYPE_INT,
resolution.second,
"framerate",
GST_TYPE_FRACTION,
frameRate.first,
frameRate.second,
nullptr);
camerafilter = gst_element_factory_make("capsfilter", "camerafilter");
g_object_set(camerafilter, "caps", caps, nullptr);
gst_caps_unref(caps);
gst_bin_add_many(GST_BIN(pipe_), camera, camerafilter, nullptr);
if (!gst_element_link_many(camera, videoconvert, camerafilter, nullptr)) {
nhlog::ui()->error("WebRTC: failed to link camera elements");
return false;
}
if (callType_ == CallType::VIDEO && !gst_element_link(camerafilter, tee)) {
nhlog::ui()->error("WebRTC: failed to link camerafilter -> tee");
2021-02-18 23:55:29 +03:00
return false;
}
}
if (callType_ == CallType::SCREEN) {
nhlog::ui()->debug("WebRTC: screen share frame rate: {} fps",
settings->screenShareFrameRate());
nhlog::ui()->debug("WebRTC: screen share picture-in-picture: {}",
settings->screenSharePiP());
nhlog::ui()->debug("WebRTC: screen share request remote camera: {}",
settings->screenShareRemoteVideo());
nhlog::ui()->debug("WebRTC: screen share hide mouse cursor: {}",
settings->screenShareHideCursor());
2020-10-27 20:14:06 +03:00
GstElement *ximagesrc = gst_element_factory_make("ximagesrc", "screenshare");
if (!ximagesrc) {
nhlog::ui()->error("WebRTC: failed to create ximagesrc");
return false;
}
g_object_set(ximagesrc, "use-damage", FALSE, nullptr);
g_object_set(ximagesrc, "xid", 0, nullptr);
g_object_set(
ximagesrc, "show-pointer", !settings->screenShareHideCursor(), nullptr);
GstCaps *caps = gst_caps_new_simple("video/x-raw",
"framerate",
GST_TYPE_FRACTION,
settings->screenShareFrameRate(),
1,
nullptr);
GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr);
g_object_set(capsfilter, "caps", caps, nullptr);
gst_caps_unref(caps);
gst_bin_add_many(GST_BIN(pipe_), ximagesrc, capsfilter, nullptr);
if (settings->screenSharePiP()) {
GstElement *compositor = gst_element_factory_make("compositor", nullptr);
g_object_set(compositor, "background", 1, nullptr);
gst_bin_add(GST_BIN(pipe_), compositor);
if (!gst_element_link_many(
ximagesrc, compositor, capsfilter, tee, nullptr)) {
nhlog::ui()->error("WebRTC: failed to link screen share elements");
return false;
}
GstPad *srcpad = gst_element_get_static_pad(camerafilter, "src");
remotePiPSinkPad_ = gst_element_get_request_pad(compositor, "sink_%u");
if (GST_PAD_LINK_FAILED(gst_pad_link(srcpad, remotePiPSinkPad_))) {
nhlog::ui()->error(
"WebRTC: failed to link camerafilter -> compositor");
gst_object_unref(srcpad);
return false;
}
gst_object_unref(srcpad);
} else if (!gst_element_link_many(
ximagesrc, videoconvert, capsfilter, tee, nullptr)) {
nhlog::ui()->error("WebRTC: failed to link screen share elements");
return false;
}
}
2021-02-18 23:55:29 +03:00
2020-11-09 18:51:17 +03:00
GstElement *queue = gst_element_factory_make("queue", nullptr);
GstElement *vp8enc = gst_element_factory_make("vp8enc", nullptr);
2020-10-27 20:14:06 +03:00
g_object_set(vp8enc, "deadline", 1, nullptr);
g_object_set(vp8enc, "error-resilient", 1, nullptr);
2020-11-09 18:51:17 +03:00
GstElement *rtpvp8pay = gst_element_factory_make("rtpvp8pay", nullptr);
GstElement *rtpqueue = gst_element_factory_make("queue", nullptr);
2020-10-27 20:14:06 +03:00
GstElement *rtpcapsfilter = gst_element_factory_make("capsfilter", nullptr);
GstCaps *rtpcaps = gst_caps_new_simple("application/x-rtp",
"media",
G_TYPE_STRING,
"video",
"encoding-name",
G_TYPE_STRING,
"VP8",
"payload",
G_TYPE_INT,
vp8PayloadType,
nullptr);
g_object_set(rtpcapsfilter, "caps", rtpcaps, nullptr);
gst_caps_unref(rtpcaps);
gst_bin_add_many(
GST_BIN(pipe_), queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, nullptr);
2020-10-27 20:14:06 +03:00
GstElement *webrtcbin = gst_bin_get_by_name(GST_BIN(pipe_), "webrtcbin");
if (!gst_element_link_many(
tee, queue, vp8enc, rtpvp8pay, rtpqueue, rtpcapsfilter, webrtcbin, nullptr)) {
nhlog::ui()->error("WebRTC: failed to link rtp video elements");
2020-11-09 18:51:17 +03:00
gst_object_unref(webrtcbin);
2020-08-01 21:31:10 +03:00
return false;
}
2021-02-18 23:55:29 +03:00
if (callType_ == CallType::SCREEN &&
!ChatPage::instance()->userSettings()->screenShareRemoteVideo()) {
GArray *transceivers;
g_signal_emit_by_name(webrtcbin, "get-transceivers", &transceivers);
GstWebRTCRTPTransceiver *transceiver =
g_array_index(transceivers, GstWebRTCRTPTransceiver *, 1);
transceiver->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
g_array_unref(transceivers);
}
2020-10-27 20:14:06 +03:00
gst_object_unref(webrtcbin);
2020-08-01 21:31:10 +03:00
return true;
2020-07-11 02:19:48 +03:00
}
2020-12-17 20:45:54 +03:00
bool
2021-02-18 23:55:29 +03:00
WebRTCSession::haveLocalCamera() const
2020-12-17 20:45:54 +03:00
{
2021-02-18 23:55:29 +03:00
if (callType_ == CallType::VIDEO && state_ >= State::INITIATED) {
2020-12-17 20:45:54 +03:00
GstElement *tee = gst_bin_get_by_name(GST_BIN(pipe_), "videosrctee");
if (tee) {
gst_object_unref(tee);
return true;
}
}
return false;
}
2020-08-01 21:31:10 +03:00
bool
WebRTCSession::isMicMuted() const
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
if (state_ < State::INITIATED)
return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
gboolean muted;
g_object_get(srclevel, "mute", &muted, nullptr);
gst_object_unref(srclevel);
return muted;
}
bool
WebRTCSession::toggleMicMute()
2020-07-11 02:19:48 +03:00
{
2020-08-01 21:31:10 +03:00
if (state_ < State::INITIATED)
return false;
GstElement *srclevel = gst_bin_get_by_name(GST_BIN(pipe_), "srclevel");
gboolean muted;
g_object_get(srclevel, "mute", &muted, nullptr);
g_object_set(srclevel, "mute", !muted, nullptr);
gst_object_unref(srclevel);
2020-09-22 19:07:36 +03:00
return !muted;
2020-07-11 02:19:48 +03:00
}
2020-11-09 18:51:17 +03:00
void
WebRTCSession::toggleCameraView()
{
if (localPiPSinkPad_) {
2020-11-09 18:51:17 +03:00
guint zorder;
g_object_get(localPiPSinkPad_, "zorder", &zorder, nullptr);
g_object_set(localPiPSinkPad_, "zorder", zorder ? 0 : 2, nullptr);
2020-11-09 18:51:17 +03:00
}
}
void
WebRTCSession::clear()
{
callType_ = webrtc::CallType::VOICE;
isOffering_ = false;
isRemoteVideoRecvOnly_ = false;
isRemoteVideoSendOnly_ = false;
videoItem_ = nullptr;
pipe_ = nullptr;
webrtc_ = nullptr;
busWatchId_ = 0;
haveAudioStream_ = false;
haveVideoStream_ = false;
localPiPSinkPad_ = nullptr;
remotePiPSinkPad_ = nullptr;
localsdp_.clear();
localcandidates_.clear();
}
2020-08-01 21:31:10 +03:00
void
WebRTCSession::end()
{
nhlog::ui()->debug("WebRTC: ending session");
2020-10-27 20:14:06 +03:00
keyFrameRequestData_ = KeyFrameRequestData{};
2020-08-01 21:31:10 +03:00
if (pipe_) {
gst_element_set_state(pipe_, GST_STATE_NULL);
gst_object_unref(pipe_);
pipe_ = nullptr;
if (busWatchId_) {
g_source_remove(busWatchId_);
busWatchId_ = 0;
}
2020-08-01 21:31:10 +03:00
}
2020-11-09 18:51:17 +03:00
clear();
2020-08-01 21:31:10 +03:00
if (state_ != State::DISCONNECTED)
emit stateChanged(State::DISCONNECTED);
2020-07-11 02:19:48 +03:00
}
2020-08-06 00:56:44 +03:00
2020-10-27 20:14:06 +03:00
#else
bool
2020-10-27 20:14:06 +03:00
WebRTCSession::havePlugins(bool, std::string *)
{
return false;
}
2020-12-17 20:45:54 +03:00
bool
2021-02-18 23:55:29 +03:00
WebRTCSession::haveLocalCamera() const
2020-12-17 20:45:54 +03:00
{
return false;
}
2021-02-18 23:55:29 +03:00
bool WebRTCSession::createOffer(webrtc::CallType) { return false; }
bool
2020-10-27 20:14:06 +03:00
WebRTCSession::acceptOffer(const std::string &)
{
return false;
}
bool
2020-10-27 20:14:06 +03:00
WebRTCSession::acceptAnswer(const std::string &)
{
return false;
}
2020-10-27 20:14:06 +03:00
void
WebRTCSession::acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &)
{}
bool
2020-09-25 18:10:45 +03:00
WebRTCSession::isMicMuted() const
{
return false;
}
bool
WebRTCSession::toggleMicMute()
{
return false;
}
2020-11-09 18:51:17 +03:00
void
WebRTCSession::toggleCameraView()
{}
void
WebRTCSession::end()
2020-08-14 10:01:56 +03:00
{}
#endif