From ba1dade4b08369164e0da5b3d8ef81445c4f3776 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 30 Apr 2021 19:20:30 +0400 Subject: [PATCH] New API/tgcalls with two outgoing videos. --- .../calls/group/calls_group_call.cpp | 1001 ++++++++++++----- .../calls/group/calls_group_call.h | 131 ++- .../calls/group/calls_group_members.cpp | 124 +- .../calls/group/calls_group_panel.cpp | 10 +- Telegram/SourceFiles/data/data_group_call.cpp | 102 +- Telegram/SourceFiles/data/data_group_call.h | 29 +- Telegram/ThirdParty/tgcalls | 2 +- 7 files changed, 974 insertions(+), 425 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 5e97a2078e..cdc22d9e4c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -80,16 +80,36 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); const QJsonObject &object, const char *key) { return object.value(key).toString().toStdString(); -}; +} + +[[nodiscard]] uint64 FindLocalRaisedHandRating( + const std::vector &list) { + const auto i = ranges::max_element( + list, + ranges::less(), + &Data::GroupCallParticipant::raisedHandRating); + return (i == end(list)) ? 1 : (i->raisedHandRating + 1); +} + +[[nodiscard]] std::string ParseVideoEndpoint(const QByteArray &json) { + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: " + "Failed to parse presentation video params, error: %1." + ).arg(error.errorString())); + return {}; + } else if (!document.isObject()) { + LOG(("API Error: " + "Not an object received in presentation video params.")); + return {}; + } + const auto video = document.object().value("video").toObject(); + return video.value("endpoint").toString().toStdString(); +} } // namespace -struct VideoParams { - tgcalls::GroupParticipantDescription description; - base::flat_set videoSsrcs; - uint64 hash = 0; -}; - class GroupCall::LoadPartTask final : public tgcalls::BroadcastPartTask { public: LoadPartTask( @@ -117,6 +137,34 @@ private: }; +class GroupCall::MediaChannelDescriptionsTask final + : public tgcalls::RequestMediaChannelDescriptionTask { +public: + MediaChannelDescriptionsTask( + base::weak_ptr call, + const std::vector &ssrcs, + Fn&&)> done); + + [[nodiscard]] base::flat_set ssrcs() const; + + [[nodiscard]] bool finishWithAdding( + uint32 ssrc, + std::optional description, + bool screen = false); + + void cancel() override; + +private: + const base::weak_ptr _call; + base::flat_set _ssrcs; + base::flat_set _cameraAdded; + base::flat_set _screenAdded; + std::vector _result; + Fn&&)> _done; + QMutex _mutex; + +}; + struct GroupCall::LargeTrack { LargeTrack() : track(Webrtc::VideoState::Active) { } @@ -151,39 +199,72 @@ struct GroupCall::LargeTrack { return false; } -std::shared_ptr ParseVideoParams( - const QByteArray &video, - const QByteArray &screencast, - const std::shared_ptr &existing) { +[[nodiscard]] VideoParams ParseVideoParams(const QByteArray &json) { + if (json.isEmpty()) { + return {}; + } + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: " + "Failed to parse group call video params, error: %1." + ).arg(error.errorString())); + return {}; + } else if (!document.isObject()) { + LOG(("API Error: " + "Not an object received in group call video params.")); + return {}; + } + + const auto object = document.object(); + auto result = VideoParams{ + .endpoint = ReadJsonString(object, "endpoint"), + .json = json, + }; + const auto ssrcGroups = object.value("ssrc-groups").toArray(); + for (const auto &value : ssrcGroups) { + const auto inner = value.toObject(); + const auto list = inner.value("sources").toArray(); + for (const auto &source : list) { + const auto ssrc = uint32_t(source.toDouble()); + result.ssrcs.emplace(ssrc); + } + } + return result.empty() ? VideoParams() : result; +} + +std::shared_ptr ParseVideoParams( + const QByteArray &camera, + const QByteArray &screen, + const std::shared_ptr &existing) { using namespace tgcalls; - if (video.isEmpty() && screencast.isEmpty()) { + if (camera.isEmpty() && screen.isEmpty()) { return nullptr; } - const auto hash = XXH32(screencast.data(), screencast.size(), uint32(0)) - | (uint64(XXH32(video.data(), video.size(), uint32(0))) << 32); - if (existing && existing->hash == hash) { + const auto cameraHash = camera.isEmpty() + ? 0 + : XXH32(camera.data(), camera.size(), uint32(0)); + const auto screenHash = screen.isEmpty() + ? 0 + : XXH32(screen.data(), screen.size(), uint32(0)); + if (existing + && existing->camera.hash == cameraHash + && existing->screen.hash == screenHash) { return existing; } // We don't reuse existing pointer, that way we can compare pointers // to see if anything was changed in video params. const auto data = /*existing ? existing - : */std::make_shared(); - data->hash = hash; - data->description.videoInformation = video.toStdString(); - data->description.screencastInformation = screencast.toStdString(); + : */std::make_shared(); + data->camera = ParseVideoParams(camera); + data->camera.hash = cameraHash; + data->screen = ParseVideoParams(screen); + data->screen.hash = screenHash; return data; } -const base::flat_set &VideoSourcesFromParams( - const std::shared_ptr ¶ms) { - static const auto kEmpty = base::flat_set(); - return (params && !params->videoSsrcs.empty()) - ? params->videoSsrcs - : kEmpty; -} - GroupCall::LoadPartTask::LoadPartTask( base::weak_ptr call, int64 time, @@ -228,6 +309,63 @@ void GroupCall::LoadPartTask::cancel() { } } +GroupCall::MediaChannelDescriptionsTask::MediaChannelDescriptionsTask( + base::weak_ptr call, + const std::vector &ssrcs, + Fn&&)> done) +: _call(std::move(call)) +, _ssrcs(ssrcs.begin(), ssrcs.end()) +, _done(std::move(done)) { +} + +auto GroupCall::MediaChannelDescriptionsTask::ssrcs() const +-> base::flat_set { + return _ssrcs; +} + +bool GroupCall::MediaChannelDescriptionsTask::finishWithAdding( + uint32 ssrc, + std::optional description, + bool screen) { + Expects(_ssrcs.contains(ssrc)); + + using Type = tgcalls::MediaChannelDescription::Type; + _ssrcs.remove(ssrc); + if (!description) { + } else if (description->type == Type::Audio + || (!screen && _cameraAdded.emplace(description->audioSsrc).second) + || (screen && _screenAdded.emplace(description->audioSsrc).second)) { + _result.push_back(std::move(*description)); + } + + if (!_ssrcs.empty()) { + return false; + } + QMutexLocker lock(&_mutex); + if (_done) { + base::take(_done)(std::move(_result)); + } + return true; +} + +void GroupCall::MediaChannelDescriptionsTask::cancel() { + QMutexLocker lock(&_mutex); + if (!_done) { + return; + } + _done = nullptr; + lock.unlock(); + + if (_call) { + const auto that = this; + crl::on_main(_call, [weak = _call, that] { + if (const auto strong = weak.get()) { + strong->mediaChannelDescriptionsCancel(that); + } + }); + } +} + GroupCall::GroupCall( not_null delegate, Group::JoinInfo info, @@ -241,7 +379,9 @@ GroupCall::GroupCall( , _joinHash(info.joinHash) , _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(info.scheduleDate) -, _videoOutgoing( +, _cameraOutgoing( + std::make_unique(Webrtc::VideoState::Inactive)) +, _screenOutgoing( std::make_unique(Webrtc::VideoState::Inactive)) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) @@ -311,57 +451,38 @@ GroupCall::GroupCall( GroupCall::~GroupCall() { destroyController(); - const auto wasScreenSharing = isScreenSharing(); - const auto weak = wasScreenSharing - ? std::weak_ptr(_videoCapture) - : std::weak_ptr(); - _videoCapture = nullptr; - if (const auto strong = weak.lock()) { - strong->switchToDevice(_videoDeviceId.toStdString()); - } } bool GroupCall::isScreenSharing() const { - return (_videoDeviceId != _videoInputId) - && (_videoOutgoing->state() == Webrtc::VideoState::Active); + return (_screenOutgoing->state() == Webrtc::VideoState::Active); } QString GroupCall::screenSharingDeviceId() const { - return isScreenSharing() ? _videoDeviceId : QString(); + return isScreenSharing() ? _screenDeviceId : QString(); } void GroupCall::toggleVideo(bool active) { - if (!active) { - if (_videoOutgoing->state() != Webrtc::VideoState::Inactive) { - _videoOutgoing->setState(Webrtc::VideoState::Inactive); - sendSelfUpdate(SendUpdateType::VideoMuted); - } - return; - } - const auto changing = isScreenSharing(); - _videoDeviceId = _videoInputId; - if (_videoOutgoing->state() != Webrtc::VideoState::Active) { - _videoOutgoing->setState(Webrtc::VideoState::Active); - sendSelfUpdate(SendUpdateType::VideoMuted); - } - if (!_videoCapture) { - return; - } - if (changing) { - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + const auto state = active + ? Webrtc::VideoState::Active + : Webrtc::VideoState::Inactive; + if (_cameraOutgoing->state() != state) { + _cameraOutgoing->setState(state); } } -void GroupCall::switchToScreenSharing(const QString &uniqueId) { - if (_videoDeviceId == uniqueId) { +void GroupCall::toggleScreenSharing(std::optional uniqueId) { + if (!uniqueId) { + _screenOutgoing->setState(Webrtc::VideoState::Inactive); return; } - _videoDeviceId = uniqueId; - if (_videoOutgoing->state() != Webrtc::VideoState::Active) { - _videoOutgoing->setState(Webrtc::VideoState::Active); - sendSelfUpdate(SendUpdateType::VideoMuted); + const auto changed = (_screenDeviceId != *uniqueId); + _screenDeviceId = *uniqueId; + if (_screenOutgoing->state() != Webrtc::VideoState::Active) { + _screenOutgoing->setState(Webrtc::VideoState::Active); + } + if (changed) { + _screenCapture->switchToDevice(uniqueId->toStdString()); } - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } void GroupCall::setScheduledDate(TimeId date) { @@ -378,43 +499,68 @@ void GroupCall::subscribeToReal(not_null real) { setScheduledDate(date); }, _lifetime); + const auto emptyEndpoint = std::string(); + using Update = Data::GroupCall::ParticipantUpdate; real->participantUpdated( ) | rpl::start_with_next([=](const Update &data) { - auto newLarge = _videoStreamLarge.current(); - auto updateAsNotStreams = uint32(0); + auto newLarge = _videoEndpointLarge.current(); + auto updateCameraNotStreams = std::string(); + auto updateScreenNotStreams = std::string(); const auto guard = gsl::finally([&] { - if (!newLarge) { - newLarge = chooseLargeVideoSsrc(); + if (newLarge.empty()) { + newLarge = chooseLargeVideoEndpoint(); } - if (_videoStreamLarge.current() != newLarge) { - _videoStreamLarge = newLarge; + if (_videoEndpointLarge.current() != newLarge) { + _videoEndpointLarge = newLarge; } - if (updateAsNotStreams) { - _streamsVideoUpdated.fire({ updateAsNotStreams, false }); + if (!updateCameraNotStreams.empty()) { + _streamsVideoUpdated.fire({ updateCameraNotStreams, false }); + } + if (!updateScreenNotStreams.empty()) { + _streamsVideoUpdated.fire({ updateScreenNotStreams, false }); } }); - const auto wasVideoMutedSsrc = (data.was && data.was->videoMuted) - ? data.was->ssrc - : 0; - const auto nowVideoMutedSsrc = (data.now && data.now->videoMuted) - ? data.now->ssrc - : 0; - if (wasVideoMutedSsrc != nowVideoMutedSsrc) { - if (wasVideoMutedSsrc - && _videoMuted.remove(wasVideoMutedSsrc) - && _videoStreamSsrcs.contains(wasVideoMutedSsrc) - && data.now - && data.now->ssrc == wasVideoMutedSsrc) { - _streamsVideoUpdated.fire({ wasVideoMutedSsrc, true }); + const auto &wasCameraEndpoint = (data.was && data.was->videoParams) + ? data.was->videoParams->camera.endpoint + : emptyEndpoint; + const auto &nowCameraEndpoint = (data.now && data.now->videoParams) + ? data.now->videoParams->camera.endpoint + : emptyEndpoint; + if (wasCameraEndpoint != nowCameraEndpoint) { + if (!nowCameraEndpoint.empty() + && _activeVideoEndpoints.emplace(nowCameraEndpoint).second + && _incomingVideoEndpoints.contains(nowCameraEndpoint)) { + _streamsVideoUpdated.fire({ nowCameraEndpoint, true }); } - if (nowVideoMutedSsrc - && _videoMuted.emplace(nowVideoMutedSsrc).second - && _videoStreamSsrcs.contains(nowVideoMutedSsrc)) { - updateAsNotStreams = nowVideoMutedSsrc; - if (newLarge == nowVideoMutedSsrc) { - newLarge = 0; + if (!wasCameraEndpoint.empty() + && _activeVideoEndpoints.remove(wasCameraEndpoint) + && _incomingVideoEndpoints.contains(wasCameraEndpoint)) { + updateCameraNotStreams = wasCameraEndpoint; + if (newLarge == wasCameraEndpoint) { + newLarge = std::string(); + } + } + } + const auto &wasScreenEndpoint = (data.was && data.was->videoParams) + ? data.was->videoParams->screen.endpoint + : emptyEndpoint; + const auto &nowScreenEndpoint = (data.now && data.now->videoParams) + ? data.now->videoParams->screen.endpoint + : emptyEndpoint; + if (wasScreenEndpoint != nowScreenEndpoint) { + if (!nowScreenEndpoint.empty() + && _activeVideoEndpoints.emplace(nowScreenEndpoint).second + && _incomingVideoEndpoints.contains(nowScreenEndpoint)) { + _streamsVideoUpdated.fire({ nowScreenEndpoint, true }); + } + if (!wasScreenEndpoint.empty() + && _activeVideoEndpoints.remove(wasScreenEndpoint) + && _incomingVideoEndpoints.contains(wasScreenEndpoint)) { + updateScreenNotStreams = wasScreenEndpoint; + if (newLarge == wasScreenEndpoint) { + newLarge = std::string(); } } } @@ -424,42 +570,74 @@ void GroupCall::subscribeToReal(not_null real) { const auto wasSounding = data.was && data.was->sounding; if (nowSpeaking == wasSpeaking && nowSounding == wasSounding) { return; - } else if (_videoStreamPinned) { + } else if (!_videoEndpointPinned.empty()) { return; } + if (nowScreenEndpoint != newLarge + && streamsVideo(nowScreenEndpoint)) { + newLarge = nowScreenEndpoint; + } const auto &participants = real->participants(); - if ((wasSpeaking || wasSounding) - && (data.was->ssrc == newLarge)) { - auto bestWithVideoSsrc = uint32(0); + if (!nowSpeaking + && (wasSpeaking || wasSounding) + && (wasCameraEndpoint == newLarge)) { + auto screenEndpoint = std::string(); + auto speakingEndpoint = std::string(); + auto soundingEndpoint = std::string(); for (const auto &participant : participants) { - if (!participant.sounding - || !streamsVideo(participant.ssrc)) { + const auto params = participant.videoParams.get(); + if (!params) { continue; } - if (participant.speaking) { - bestWithVideoSsrc = participant.ssrc; + if (streamsVideo(params->screen.endpoint)) { + screenEndpoint = params->screen.endpoint; break; - } else if (!bestWithVideoSsrc) { - bestWithVideoSsrc = participant.ssrc; + } else if (participant.speaking + && speakingEndpoint.empty()) { + if (streamsVideo(params->camera.endpoint)) { + speakingEndpoint = params->camera.endpoint; + } + } else if (!nowSounding + && participant.sounding + && soundingEndpoint.empty()) { + if (streamsVideo(params->camera.endpoint)) { + soundingEndpoint = params->camera.endpoint; + } } } - if (bestWithVideoSsrc) { - newLarge = bestWithVideoSsrc; + if (!screenEndpoint.empty()) { + newLarge = screenEndpoint; + } else if (!speakingEndpoint.empty()) { + newLarge = speakingEndpoint; + } else if (!soundingEndpoint.empty()) { + newLarge = soundingEndpoint; } } else if ((nowSpeaking || nowSounding) - && (data.now->ssrc != newLarge) - && streamsVideo(data.now->ssrc)) { - const auto i = ranges::find( - participants, - newLarge, - &Data::GroupCallParticipant::ssrc); - const auto speaking = (i != end(participants)) && i->speaking; - const auto sounding = (i != end(participants)) && i->sounding; - if ((nowSpeaking && !speaking) || (nowSounding && !sounding)) { - newLarge = data.now->ssrc; + && (nowScreenEndpoint != newLarge) + && (nowCameraEndpoint != newLarge) + && streamsVideo(nowCameraEndpoint)) { + const auto participant = real->participantByEndpoint(newLarge); + const auto screen = participant + && (participant->videoParams->screen.endpoint == newLarge); + const auto speaking = participant && participant->speaking; + const auto sounding = participant && participant->sounding; + if (!screen + && ((nowSpeaking && !speaking) + || (nowSounding && !sounding))) { + newLarge = nowCameraEndpoint; } } }, _lifetime); + + real->participantsResolved( + ) | rpl::start_with_next([=]( + not_null*> ssrcs) { + checkMediaChannelDescriptions([&](uint32 ssrc) { + return ssrcs->contains(ssrc); + }); + }, _lifetime); } void GroupCall::checkGlobalShortcutAvailability() { @@ -595,7 +773,9 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { rejoin(); using Update = Data::GroupCall::ParticipantUpdate; - _peer->groupCall()->participantUpdated( + const auto real = lookupReal(); + Assert(real != nullptr); + real->participantUpdated( ) | rpl::filter([=](const Update &update) { return (_instance != nullptr); }) | rpl::start_with_next([=](const Update &update) { @@ -608,17 +788,6 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { ? (was->volume != now.volume || was->mutedByMe != now.mutedByMe) : (now.volume != Group::kDefaultVolume || now.mutedByMe); - if (now.videoParams - && now.ssrc - && (!was - || was->videoParams != now.videoParams - || was->ssrc != now.ssrc) - && (now.peer != _joinAs) - && (_instanceMode != InstanceMode::None)) { - prepareParticipantForAdding(now); - addPreparedParticipantsDelayed(); - } - if (volumeChanged) { _instance->setVolume( now.ssrc, @@ -630,8 +799,6 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { } }, _lifetime); - addParticipantsToInstance(); - _peer->session().updates().addActiveChat( _peerStream.events_starting_with_copy(_peer)); SubscribeToMigration(_peer, _lifetime, [=](not_null group) { @@ -744,13 +911,75 @@ void GroupCall::rejoin(not_null as) { }); } -[[nodiscard]] uint64 FindLocalRaisedHandRating( - const std::vector &list) { - const auto i = ranges::max_element( - list, - ranges::less(), - &Data::GroupCallParticipant::raisedHandRating); - return (i == end(list)) ? 1 : (i->raisedHandRating + 1); +void GroupCall::joinLeavePresentation() { + if (_screenOutgoing->state() == Webrtc::VideoState::Active) { + rejoinPresentation(); + } else { + leavePresentation(); + } +} + +void GroupCall::rejoinPresentation() { + _screenSsrc = 0; + ensureScreencastCreated(); + setScreenInstanceMode(InstanceMode::None); + LOG(("Call Info: Requesting join payload.")); + + const auto weak = base::make_weak(this); + _screenInstance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { + crl::on_main(weak, [=, payload = std::move(payload)]{ + const auto ssrc = payload.audioSsrc; + LOG(("Call Info: Join payload received, joining with ssrc: %1." + ).arg(ssrc)); + + const auto json = QByteArray::fromStdString(payload.json); + _api.request(MTPphone_JoinGroupCallPresentation( + inputCall(), + MTP_dataJSON(MTP_bytes(json)) + )).done([=](const MTPUpdates &updates) { + _screenSsrc = ssrc; + _mySsrcs.emplace(ssrc); + _peer->session().api().applyUpdates(updates); + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + LOG(("Call Error: " + "Could not screen join, error: %1").arg(type)); + + if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { + rejoinPresentation(); + return; + } + + //hangup(); + //Ui::ShowMultilineToast({ + // .text = { type == u"GROUPCALL_ANONYMOUS_FORBIDDEN"_q + // ? tr::lng_group_call_no_anonymous(tr::now) + // : type == u"GROUPCALL_PARTICIPANTS_TOO_MUCH"_q + // ? tr::lng_group_call_too_many(tr::now) + // : type == u"GROUPCALL_FORBIDDEN"_q + // ? tr::lng_group_not_accessible(tr::now) + // : Lang::Hard::ServerError() }, + //}); + }).send(); + }); + }); +} + +void GroupCall::leavePresentation() { + if (!_screenSsrc) { + return; + } + _api.request(MTPphone_LeaveGroupCallPresentation( + inputCall() + )).done([=](const MTPUpdates &updates) { + _screenSsrc = 0; + _peer->session().api().applyUpdates(updates); + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + LOG(("Call Error: " + "Could not screen leave, error: %1").arg(type)); + _screenSsrc = 0; + }).send(); } void GroupCall::applyMeInCallLocally() { @@ -775,13 +1004,14 @@ void GroupCall::applyMeInCallLocally() { : Group::kDefaultVolume; const auto canSelfUnmute = (muted() != MuteState::ForceMuted) && (muted() != MuteState::RaisedHand); - const auto videoMuted = (_videoOutgoing->state() - != Webrtc::VideoState::Active); const auto raisedHandRating = (muted() != MuteState::RaisedHand) ? uint64(0) : (i != end(participants)) ? i->raisedHandRating : FindLocalRaisedHandRating(participants); + const auto params = (i != end(participants)) + ? i->videoParams.get() + : nullptr; const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) | (lastActive ? Flag::f_active_date : Flag(0)) | (_mySsrc ? Flag(0) : Flag::f_left) @@ -789,7 +1019,10 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume // Without flag the volume is reset to 100%. | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) - //| (videoMuted ? Flag(0) : Flag::f_video) + | ((params && !params->camera.empty()) ? Flag::f_video : Flag(0)) + | ((params && !params->screen.empty()) + ? Flag::f_presentation + : Flag(0)) | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)); call->applyLocalUpdate( MTP_updateGroupCallParticipants( @@ -805,8 +1038,12 @@ void GroupCall::applyMeInCallLocally() { MTP_int(volume), MTPstring(), // Don't update about text in local updates. MTP_long(raisedHandRating), - MTPDataJSON(), // video - MTPDataJSON())), // presentation + (params + ? MTP_dataJSON(MTP_bytes(params->camera.json)) + : MTPDataJSON()), + (params + ? MTP_dataJSON(MTP_bytes(params->screen.json)) + : MTPDataJSON()))), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -824,6 +1061,7 @@ void GroupCall::applyParticipantLocally( ? participant->canSelfUnmute : (!mute || IsGroupCallAdmin(_peer, participantPeer)); const auto isMutedByYou = mute && !canManageCall; + const auto params = participant->videoParams.get(); const auto mutedCount = 0/*participant->mutedCount*/; using Flag = MTPDgroupCallParticipant::Flag; const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) @@ -835,7 +1073,10 @@ void GroupCall::applyParticipantLocally( | (isMuted ? Flag::f_muted : Flag(0)) | (isMutedByYou ? Flag::f_muted_by_you : Flag(0)) | (participantPeer == _joinAs ? Flag::f_self : Flag(0)) - //| (participant->videoMuted ? Flag(0) : Flag::f_video) + | ((params && !params->camera.empty()) ? Flag::f_video : Flag(0)) + | ((params && !params->screen.empty()) + ? Flag::f_presentation + : Flag(0)) | (participant->raisedHandRating ? Flag::f_raise_hand_rating : Flag(0)); @@ -853,8 +1094,12 @@ void GroupCall::applyParticipantLocally( MTP_int(volume.value_or(participant->volume)), MTPstring(), // Don't update about text in local updates. MTP_long(participant->raisedHandRating), - MTPDataJSON(), // video - MTPDataJSON())), // presentation + (params + ? MTP_dataJSON(MTP_bytes(params->camera.json)) + : MTPDataJSON()), + (params + ? MTP_dataJSON(MTP_bytes(params->screen.json)) + : MTPDataJSON()))), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -962,15 +1207,19 @@ void GroupCall::toggleScheduleStartSubscribed(bool subscribed) { } void GroupCall::addVideoOutput( - uint32 ssrc, + const std::string &endpointId, not_null track) { if (_instance) { - _instance->addIncomingVideoOutput(ssrc, track->sink()); + _instance->addIncomingVideoOutput(endpointId, track->sink()); } } -not_null GroupCall::outgoingVideoTrack() const { - return _videoOutgoing.get(); +not_null GroupCall::outgoingCameraTrack() const { + return _cameraOutgoing.get(); +} + +not_null GroupCall::outgoingScreenTrack() const { + return _screenOutgoing.get(); } void GroupCall::setMuted(MuteState mute) { @@ -1044,13 +1293,22 @@ void GroupCall::handlePossibleCreateOrJoinResponse( void GroupCall::handlePossibleCreateOrJoinResponse( const MTPDupdateGroupCallConnection &data) { if (data.is_presentation()) { - // #TODO calls - return; - } - data.vparams().match([&](const MTPDdataJSON &data) { + setScreenInstanceMode(InstanceMode::Rtc); + data.vparams().match([&](const MTPDdataJSON &data) { + const auto json = data.vdata().v; + _screenEndpoint = ParseVideoEndpoint(json); + if (!_screenEndpoint.empty() && _instance) { + _instance->setIgnoreVideoEndpointIds({ _screenEndpoint }); + } + _screenInstance->setJoinResponsePayload(json.toStdString()); + }); + } else { setInstanceMode(InstanceMode::Rtc); - _instance->setJoinResponsePayload(data.vdata().v.toStdString()); - }); + data.vparams().match([&](const MTPDdataJSON &data) { + _instance->setJoinResponsePayload(data.vdata().v.toStdString()); + }); + checkMediaChannelDescriptions(); + } } void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { @@ -1061,50 +1319,23 @@ void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { } } -void GroupCall::addParticipantsToInstance() { +void GroupCall::checkMediaChannelDescriptions( + Fn resolved) { const auto real = lookupReal(); if (!real || (_instanceMode == InstanceMode::None)) { return; } - for (const auto &participant : real->participants()) { - prepareParticipantForAdding(participant); - } - addPreparedParticipants(); -} - -void GroupCall::prepareParticipantForAdding( - const Data::GroupCallParticipant &participant) { - _preparedParticipants.push_back(participant.videoParams - ? participant.videoParams->description - : tgcalls::GroupParticipantDescription()); - auto &added = _preparedParticipants.back(); - added.audioSsrc = participant.ssrc; - _unresolvedSsrcs.remove(added.audioSsrc); - //for (const auto &group : added.videoSourceGroups) { // #TODO calls - // for (const auto ssrc : group.ssrcs) { - // _unresolvedSsrcs.remove(ssrc); - // } - //} -} - -void GroupCall::addPreparedParticipants() { - _addPreparedParticipantsScheduled = false; - if (!_preparedParticipants.empty()) { - _instance->addParticipants(base::take(_preparedParticipants)); - } - if (const auto real = lookupReal()) { - if (!_unresolvedSsrcs.empty()) { - real->resolveParticipants(base::take(_unresolvedSsrcs)); + for (auto i = begin(_mediaChannelDescriptionses) + ; i != end(_mediaChannelDescriptionses);) { + if (mediaChannelDescriptionsFill(i->get(), resolved)) { + i = _mediaChannelDescriptionses.erase(i); + } else { + ++i; } } -} - -void GroupCall::addPreparedParticipantsDelayed() { - if (_addPreparedParticipantsScheduled) { - return; + if (!_unresolvedSsrcs.empty()) { + real->resolveParticipants(base::take(_unresolvedSsrcs)); } - _addPreparedParticipantsScheduled = true; - crl::on_main(this, [=] { addPreparedParticipants(); }); } void GroupCall::handleUpdate(const MTPUpdate &update) { @@ -1247,25 +1478,19 @@ void GroupCall::setupMediaDevices() { _mediaDevices->videoInputId( ) | rpl::start_with_next([=](QString id) { - const auto usedCamera = !isScreenSharing(); - _videoInputId = id; - if (_videoCapture && usedCamera) { - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + _cameraInputId = id; + if (_cameraCapture) { + _cameraCapture->switchToDevice(id.toStdString()); } }, _lifetime); setupOutgoingVideo(); } void GroupCall::setupOutgoingVideo() { - _videoDeviceId = _videoInputId; - static const auto hasDevices = [] { - return !Webrtc::GetVideoInputList().empty(); - }; - const auto started = _videoOutgoing->state(); - if (!hasDevices()) { - _videoOutgoing->setState(Webrtc::VideoState::Inactive); - } - _videoOutgoing->stateValue( + //static const auto hasDevices = [] { + // return !Webrtc::GetVideoInputList().empty(); + //}; + _cameraOutgoing->stateValue( ) | rpl::start_with_next([=](Webrtc::VideoState state) { //if (state != Webrtc::VideoState::Inactive && !hasDevices()) { //_errors.fire({ ErrorType::NoCamera }); // #TODO calls @@ -1278,20 +1503,46 @@ void GroupCall::setupOutgoingVideo() { /*} else */if (state != Webrtc::VideoState::Inactive) { // Paused not supported right now. Assert(state == Webrtc::VideoState::Active); - if (!_videoCapture) { - _videoCapture = _delegate->groupCallGetVideoCapture( - _videoDeviceId); - _videoCapture->setOutput(_videoOutgoing->sink()); + if (!_cameraCapture) { + _cameraCapture = _delegate->groupCallGetVideoCapture( + _cameraInputId); + _cameraCapture->setOutput(_cameraOutgoing->sink()); } else { - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + _cameraCapture->switchToDevice(_cameraInputId.toStdString()); } if (_instance) { - _instance->setVideoCapture(_videoCapture); + _instance->setVideoCapture(_cameraCapture); } - _videoCapture->setState(tgcalls::VideoState::Active); - } else if (_videoCapture) { - _videoCapture->setState(tgcalls::VideoState::Inactive); + _cameraCapture->setState(tgcalls::VideoState::Active); + } else if (_cameraCapture) { + _cameraCapture->setState(tgcalls::VideoState::Inactive); } + sendSelfUpdate(SendUpdateType::VideoMuted); + applyMeInCallLocally(); + }, _lifetime); + + _screenOutgoing->stateValue( + ) | rpl::start_with_next([=](Webrtc::VideoState state) { + if (state != Webrtc::VideoState::Inactive) { + // Paused not supported right now. + Assert(state == Webrtc::VideoState::Active); + if (!_screenCapture) { + _screenCapture = std::shared_ptr( + tgcalls::VideoCaptureInterface::Create( + tgcalls::StaticThreads::getThreads(), + _screenDeviceId.toStdString())); + _screenCapture->setOutput(_screenOutgoing->sink()); + } else { + _screenCapture->switchToDevice(_screenDeviceId.toStdString()); + } + if (_screenInstance) { + _screenInstance->setVideoCapture(_screenCapture); + } + _screenCapture->setState(tgcalls::VideoState::Active); + } else if (_screenCapture) { + _screenCapture->setState(tgcalls::VideoState::Inactive); + } + joinLeavePresentation(); }, _lifetime); } @@ -1374,17 +1625,11 @@ void GroupCall::ensureControllerCreated() { .initialOutputDeviceId = _audioOutputId.toStdString(), .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( settings.callAudioBackend()), - .videoCapture = _videoCapture, + .videoCapture = _cameraCapture, .incomingVideoSourcesUpdated = [=]( - const std::vector &ssrcs) { - crl::on_main(weak, [=] { - setVideoStreams(ssrcs); - }); - }, - .participantDescriptionsRequired = [=]( - const std::vector &ssrcs) { - crl::on_main(weak, [=] { - requestParticipantsInformation(ssrcs); + std::vector endpointIds) { + crl::on_main(weak, [=, endpoints = std::move(endpointIds)] { + setIncomingVideoStreams(endpoints); }); }, .requestBroadcastPart = [=]( @@ -1402,6 +1647,19 @@ void GroupCall::ensureControllerCreated() { return result; }, .videoContentType = tgcalls::VideoContentType::Generic, + .requestMediaChannelDescriptions = [=]( + const std::vector &ssrcs, + std::function &&)> done) { + auto result = std::make_shared( + weak, + ssrcs, + std::move(done)); + crl::on_main(weak, [=]() mutable { + mediaChannelDescriptionsStart(std::move(result)); + }); + return result; + }, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); @@ -1421,10 +1679,11 @@ void GroupCall::ensureControllerCreated() { LOG(("Call Info: Creating group instance")); _instance = std::make_unique( std::move(descriptor)); - _videoStreamLarge.changes( - ) | rpl::start_with_next([=](uint32 ssrc) { - _instance->setFullSizeVideoSsrc(ssrc); - if (!ssrc) { + + _videoEndpointLarge.changes( + ) | rpl::start_with_next([=](const std::string &endpoint) { + _instance->setFullSizeVideoEndpointId(endpoint); + if (endpoint.empty()) { _videoLargeTrack = nullptr; _videoLargeTrackWrap = nullptr; return; @@ -1435,7 +1694,9 @@ void GroupCall::ensureControllerCreated() { } _videoLargeTrackWrap->sink = Webrtc::CreateProxySink( _videoLargeTrackWrap->track.sink()); - _instance->addIncomingVideoOutput(ssrc, _videoLargeTrackWrap->sink); + _instance->addIncomingVideoOutput( + endpoint, + _videoLargeTrackWrap->sink); }, _lifetime); updateInstanceMuteState(); @@ -1444,6 +1705,46 @@ void GroupCall::ensureControllerCreated() { //raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled()); } +void GroupCall::ensureScreencastCreated() { + if (_screenInstance) { + return; + } + //const auto &settings = Core::App().settings(); + + const auto weak = base::make_weak(this); + //const auto myLevel = std::make_shared(); + tgcalls::GroupInstanceDescriptor descriptor = { + .threads = tgcalls::StaticThreads::getThreads(), + .config = tgcalls::GroupConfig{ + }, + .networkStateUpdated = [=](tgcalls::GroupNetworkState networkState) { + crl::on_main(weak, [=] { + setScreenInstanceConnected(networkState); + }); + }, + .videoCapture = _screenCapture, + .videoContentType = tgcalls::VideoContentType::Screencast, + }; +// if (Logs::DebugEnabled()) { +// auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); +// auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt"); +// auto callLogNative = QDir::toNativeSeparators(callLogPath); +//#ifdef Q_OS_WIN +// descriptor.config.logPath.data = callLogNative.toStdWString(); +//#else // Q_OS_WIN +// const auto callLogUtf = QFile::encodeName(callLogNative); +// descriptor.config.logPath.data.resize(callLogUtf.size()); +// ranges::copy(callLogUtf, descriptor.config.logPath.data.begin()); +//#endif // Q_OS_WIN +// QFile(callLogPath).remove(); +// QDir().mkpath(callLogFolder); +// } + + LOG(("Call Info: Creating group screen instance")); + _screenInstance = std::make_unique( + std::move(descriptor)); +} + void GroupCall::broadcastPartStart(std::shared_ptr task) { const auto raw = task.get(); const auto time = raw->time(); @@ -1509,108 +1810,162 @@ void GroupCall::broadcastPartStart(std::shared_ptr task) { void GroupCall::broadcastPartCancel(not_null task) { const auto i = _broadcastParts.find(task); - if (i != _broadcastParts.end()) { + if (i != end(_broadcastParts)) { _api.request(i->second.requestId).cancel(); _broadcastParts.erase(i); } } -void GroupCall::requestParticipantsInformation( - const std::vector &ssrcs) { +void GroupCall::mediaChannelDescriptionsStart( + std::shared_ptr task) { + const auto raw = task.get(); + const auto real = lookupReal(); if (!real || (_instanceMode == InstanceMode::None)) { - for (const auto ssrc : ssrcs) { + for (const auto ssrc : task->ssrcs()) { _unresolvedSsrcs.emplace(ssrc); } + _mediaChannelDescriptionses.emplace(std::move(task)); return; } - - const auto &existing = real->participants(); - for (const auto ssrc : ssrcs) { - const auto byAudio = real->participantPeerByAudioSsrc(ssrc); - const auto participantPeer = byAudio - ? byAudio - : real->participantPeerByVideoSsrc(ssrc); - if (!participantPeer) { - _unresolvedSsrcs.emplace(ssrc); - continue; - } - const auto i = ranges::find( - existing, - not_null{ participantPeer }, - &Data::GroupCall::Participant::peer); - Assert(i != end(existing)); - - prepareParticipantForAdding(*i); + if (!mediaChannelDescriptionsFill(task.get())) { + _mediaChannelDescriptionses.emplace(std::move(task)); + Assert(!_unresolvedSsrcs.empty()); + } + if (!_unresolvedSsrcs.empty()) { + real->resolveParticipants(base::take(_unresolvedSsrcs)); } - addPreparedParticipants(); } -void GroupCall::setVideoStreams(const std::vector &ssrcs) { - const auto large = _videoStreamLarge.current(); - auto newLarge = large; - if (large && !ranges::contains(ssrcs, large)) { - newLarge = 0; - _videoStreamPinned = 0; +bool GroupCall::mediaChannelDescriptionsFill( + not_null task, + Fn resolved) { + using Channel = tgcalls::MediaChannelDescription; + auto result = false; + const auto real = lookupReal(); + Assert(real != nullptr); + const auto &existing = real->participants(); + for (const auto ssrc : task->ssrcs()) { + const auto add = [&]( + std::optional channel, + bool screen = false) { + if (task->finishWithAdding(ssrc, std::move(channel), screen)) { + result = true; + } + }; + const auto addVideoChannel = [&]( + not_null participantPeer, + const auto field) { + const auto i = ranges::find( + existing, + participantPeer, + &Data::GroupCallParticipant::peer); + Assert(i != end(existing)); + Assert(i->videoParams != nullptr); + const auto ¶ms = i->videoParams.get()->*field; + Assert(!params.empty()); + add(Channel{ + .type = Channel::Type::Video, + .audioSsrc = i->ssrc, + .videoInformation = params.json.toStdString(), + }, (field == &ParticipantVideoParams::screen)); + }; + if (const auto byAudio = real->participantPeerByAudioSsrc(ssrc)) { + add(Channel{ + .type = Channel::Type::Audio, + .audioSsrc = ssrc, + }); + } else if (const auto byCamera + = real->participantPeerByCameraSsrc(ssrc)) { + addVideoChannel(byCamera, &ParticipantVideoParams::camera); + } else if (const auto byScreen + = real->participantPeerByScreenSsrc(ssrc)) { + addVideoChannel(byScreen, &ParticipantVideoParams::screen); + } else if (resolved(ssrc)) { + add(std::nullopt); + } else if (!resolved) { + _unresolvedSsrcs.emplace(ssrc); + } } - auto removed = _videoStreamSsrcs; - for (const auto ssrc : ssrcs) { - const auto i = removed.find(ssrc); - const auto videoMuted = _videoMuted.contains(ssrc); + return result; +} + +void GroupCall::mediaChannelDescriptionsCancel( + not_null task) { + const auto i = _mediaChannelDescriptionses.find(task.get()); + if (i != end(_mediaChannelDescriptionses)) { + _mediaChannelDescriptionses.erase(i); + } +} + +void GroupCall::setIncomingVideoStreams( + const std::vector &endpoints) { + const auto large = _videoEndpointLarge.current(); + auto newLarge = large; + if (!large.empty() && !ranges::contains(endpoints, large)) { + newLarge = _videoEndpointPinned = std::string(); + } + auto removed = _incomingVideoEndpoints; + for (const auto &endpoint : endpoints) { + const auto i = removed.find(endpoint); + const auto videoActive = _activeVideoEndpoints.contains(endpoint); if (i != end(removed)) { removed.erase(i); } else { - _videoStreamSsrcs.emplace(ssrc); - if (!videoMuted) { - _streamsVideoUpdated.fire({ ssrc, true }); + _incomingVideoEndpoints.emplace(endpoint); + if (videoActive) { + _streamsVideoUpdated.fire({ endpoint, true }); } } } - if (!newLarge) { - _videoStreamLarge = chooseLargeVideoSsrc(); + if (newLarge.empty()) { + _videoEndpointLarge = chooseLargeVideoEndpoint(); } - for (const auto ssrc : removed) { - if (!_videoMuted.contains(ssrc)) { - _streamsVideoUpdated.fire({ ssrc, false }); + for (const auto &endpoint : removed) { + if (_activeVideoEndpoints.contains(endpoint)) { + _streamsVideoUpdated.fire({ endpoint, false }); } } } -uint32 GroupCall::chooseLargeVideoSsrc() const { +std::string GroupCall::chooseLargeVideoEndpoint() const { const auto real = lookupReal(); if (!real) { - return 0; + return std::string(); } - auto anySsrc = uint32(0); - auto lastSpokeVoiceSsrc = uint32(0); - auto lastSpokeAnythingSsrc = uint32(0); + auto anyEndpoint = std::string(); + auto screenEndpoint = std::string(); + auto speakingEndpoint = std::string(); + auto soundingEndpoint = std::string(); const auto &participants = real->participants(); - for (const auto ssrc : _videoStreamSsrcs) { - if (_videoMuted.contains(ssrc)) { + for (const auto &endpoint : _incomingVideoEndpoints) { + if (!_activeVideoEndpoints.contains(endpoint)) { continue; } - const auto &participants = real->participants(); - const auto i = ranges::find( - participants, - ssrc, - &Data::GroupCallParticipant::ssrc); - if (i != end(participants)) { - if (!lastSpokeVoiceSsrc && i->speaking) { - lastSpokeVoiceSsrc = ssrc; + if (const auto participant = real->participantByEndpoint(endpoint)) { + if (screenEndpoint.empty() + && participant->videoParams->screen.endpoint == endpoint) { + screenEndpoint = endpoint; + break; } - if (!lastSpokeAnythingSsrc && i->sounding) { - lastSpokeAnythingSsrc = ssrc; + if (speakingEndpoint.empty() && participant->speaking) { + speakingEndpoint = endpoint; } - if (!anySsrc) { - anySsrc = ssrc; + if (soundingEndpoint.empty() && participant->sounding) { + soundingEndpoint = endpoint; + } + if (anyEndpoint.empty()) { + anyEndpoint = endpoint; } } } - return lastSpokeVoiceSsrc - ? lastSpokeVoiceSsrc - : lastSpokeAnythingSsrc - ? lastSpokeAnythingSsrc - : anySsrc; + return !screenEndpoint.empty() + ? screenEndpoint + : !speakingEndpoint.empty() + ? speakingEndpoint + : !soundingEndpoint.empty() + ? soundingEndpoint + : anyEndpoint; } void GroupCall::updateInstanceMuteState() { @@ -1731,8 +2086,8 @@ void GroupCall::checkJoined() { return; } auto sources = QVector(1, MTP_int(_mySsrc)); - if (_screencastSsrc) { - sources.push_back(MTP_int(_screencastSsrc)); + if (_screenSsrc) { + sources.push_back(MTP_int(_screenSsrc)); } _api.request(MTPphone_CheckGroupCall( inputCall(), @@ -1744,18 +2099,21 @@ void GroupCall::checkJoined() { } else if (state() == State::Connecting) { _checkJoinedTimer.callOnce(kCheckJoinedTimeout); } - if (_screencastSsrc - && !ranges::contains(result.v, MTP_int(_screencastSsrc))) { + if (_screenSsrc + && !ranges::contains(result.v, MTP_int(_screenSsrc)) + && isScreenSharing()) { LOG(("Call Info: " - "Rejoin presentation after _screencastSsrc not found.")); - // #TODO calls + "Screen rejoin after _screenSsrc not found.")); + rejoinPresentation(); } }).fail([=](const MTP::Error &error) { LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." ).arg(error.type())); rejoin(); - if (_screencastSsrc) { - // #TODO calls + if (_screenSsrc && isScreenSharing()) { + LOG(("Call Info: " + "Full screen rejoin after _screenSsrc not found.")); + rejoinPresentation(); } }).send(); } @@ -1792,6 +2150,22 @@ void GroupCall::setInstanceConnected( } } +void GroupCall::setScreenInstanceConnected( + tgcalls::GroupNetworkState networkState) { + const auto inTransit = networkState.isTransitioningFromBroadcastToRtc; + const auto screenInstanceState = !networkState.isConnected + ? InstanceState::Disconnected + : inTransit + ? InstanceState::TransitionToRtc + : InstanceState::Connected; + const auto connected = (screenInstanceState + != InstanceState::Disconnected); + if (_screenInstanceState.current() == screenInstanceState) { + return; + } + _screenInstanceState = screenInstanceState; +} + void GroupCall::checkFirstTimeJoined() { if (_hadJoinedState || state() != State::Joined) { return; @@ -1826,6 +2200,22 @@ void GroupCall::setInstanceMode(InstanceMode mode) { }(), true); } +void GroupCall::setScreenInstanceMode(InstanceMode mode) { + Expects(_screenInstance != nullptr); + + _screenInstanceMode = mode; + + using Mode = tgcalls::GroupConnectionMode; + _screenInstance->setConnectionMode([&] { + switch (_instanceMode) { + case InstanceMode::None: return Mode::GroupConnectionModeNone; + case InstanceMode::Rtc: return Mode::GroupConnectionModeRtc; + case InstanceMode::Stream: return Mode::GroupConnectionModeBroadcast; + } + Unexpected("Mode in GroupCall::setInstanceMode."); + }(), true); +} + void GroupCall::maybeSendMutedUpdate(MuteState previous) { // Send Active <-> !Active or ForceMuted <-> RaisedHand changes. const auto now = muted(); @@ -1856,7 +2246,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { MTP_bool(muted() != MuteState::Active), MTP_int(100000), // volume MTP_bool(muted() == MuteState::RaisedHand), - MTP_bool(_videoOutgoing->state() != Webrtc::VideoState::Active) + MTP_bool(_cameraOutgoing->state() != Webrtc::VideoState::Active) )).done([=](const MTPUpdates &result) { _updateMuteRequestId = 0; _peer->session().api().applyUpdates(result); @@ -1870,12 +2260,11 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { }).send(); } -void GroupCall::pinVideoStream(uint32 ssrc) { - if (!ssrc || streamsVideo(ssrc)) { - _videoStreamPinned = ssrc; - if (ssrc) { - _videoStreamLarge = ssrc; - } +void GroupCall::pinVideoEndpoint(const std::string &endpoint) { + if (endpoint.empty()) { + _videoEndpointPinned = endpoint; + } else if (streamsVideo(endpoint)) { + _videoEndpointLarge = _videoEndpointPinned = endpoint; } } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 1aa4ea44ee..8a98f63411 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -75,18 +75,33 @@ struct LevelUpdate { }; struct StreamsVideoUpdate { - uint32 ssrc = 0; + std::string endpoint; bool streams = false; }; -struct VideoParams; +struct VideoParams { + base::flat_set ssrcs; + std::string endpoint; + QByteArray json; + uint32 hash = 0; -[[nodiscard]] std::shared_ptr ParseVideoParams( - const QByteArray &video, - const QByteArray &screencast, - const std::shared_ptr &existing); -[[nodiscard]] const base::flat_set &VideoSourcesFromParams( - const std::shared_ptr ¶ms); + [[nodiscard]] bool empty() const { + return endpoint.empty() || ssrcs.empty() || json.isEmpty(); + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } +}; + +struct ParticipantVideoParams { + VideoParams camera; + VideoParams screen; +}; + +[[nodiscard]] std::shared_ptr ParseVideoParams( + const QByteArray &camera, + const QByteArray &screen, + const std::shared_ptr &existing); class GroupCall final : public base::has_weak_ptr { public: @@ -154,8 +169,11 @@ public: void startScheduledNow(); void toggleScheduleStartSubscribed(bool subscribed); - void addVideoOutput(uint32 ssrc, not_null track); - [[nodiscard]] not_null outgoingVideoTrack() const; + void addVideoOutput( + const std::string &endpoint, + not_null track); + [[nodiscard]] not_null outgoingCameraTrack() const; + [[nodiscard]] not_null outgoingScreenTrack() const; void setMuted(MuteState mute); void setMutedAndUpdate(MuteState mute); @@ -213,20 +231,21 @@ public: -> rpl::producer { return _streamsVideoUpdated.events(); } - [[nodiscard]] bool streamsVideo(uint32 ssrc) const { - return ssrc - && _videoStreamSsrcs.contains(ssrc) - && !_videoMuted.contains(ssrc); + [[nodiscard]] bool streamsVideo(const std::string &endpoint) const { + return !endpoint.empty() + && _incomingVideoEndpoints.contains(endpoint) + && _activeVideoEndpoints.contains(endpoint); } - [[nodiscard]] uint32 videoStreamPinned() const { - return _videoStreamPinned; + [[nodiscard]] const std::string &videoEndpointPinned() const { + return _videoEndpointPinned; } - void pinVideoStream(uint32 ssrc); - [[nodiscard]] uint32 videoStreamLarge() const { - return _videoStreamLarge.current(); + void pinVideoEndpoint(const std::string &endpoint); + [[nodiscard]] std::string videoEndpointLarge() const { + return _videoEndpointLarge.current(); } - [[nodiscard]] rpl::producer videoStreamLargeValue() const { - return _videoStreamLarge.value(); + [[nodiscard]] auto videoEndpointLargeValue() const + -> rpl::producer { + return _videoEndpointLarge.value(); } [[nodiscard]] Webrtc::VideoTrack *videoLargeTrack() const { return _videoLargeTrack.current(); @@ -251,7 +270,7 @@ public: [[nodiscard]] bool isScreenSharing() const; [[nodiscard]] QString screenSharingDeviceId() const; void toggleVideo(bool active); - void switchToScreenSharing(const QString &uniqueId); + void toggleScreenSharing(std::optional uniqueId); void toggleMute(const Group::MuteRequest &data); void changeVolume(const Group::VolumeRequest &data); @@ -269,10 +288,15 @@ public: private: class LoadPartTask; + class MediaChannelDescriptionsTask; public: void broadcastPartStart(std::shared_ptr task); void broadcastPartCancel(not_null task); + void mediaChannelDescriptionsStart( + std::shared_ptr task); + void mediaChannelDescriptionsCancel( + not_null task); private: using GlobalShortcutValue = base::GlobalShortcutValue; @@ -299,12 +323,19 @@ private: VideoMuted, }; + [[nodiscard]] bool mediaChannelDescriptionsFill( + not_null task, + Fn resolved = nullptr); + void checkMediaChannelDescriptions(Fn resolved = nullptr); + void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data); void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); void ensureControllerCreated(); void destroyController(); + void ensureScreencastCreated(); + void destroyScreencast(); void setState(State state); void finish(FinishType type); @@ -319,10 +350,15 @@ private: void saveDefaultJoinAs(not_null as); void subscribeToReal(not_null real); void setScheduledDate(TimeId date); + void joinLeavePresentation(); + void rejoinPresentation(); + void leavePresentation(); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void setInstanceConnected(tgcalls::GroupNetworkState networkState); void setInstanceMode(InstanceMode mode); + void setScreenInstanceConnected(tgcalls::GroupNetworkState networkState); + void setScreenInstanceMode(InstanceMode mode); void checkLastSpoke(); void pushToTalkCancel(); @@ -335,14 +371,8 @@ private: void stopConnectingSound(); void playConnectingSoundOnce(); - void requestParticipantsInformation(const std::vector &ssrcs); - void addParticipantsToInstance(); - void prepareParticipantForAdding( - const Data::GroupCallParticipant &participant); - void addPreparedParticipants(); - void addPreparedParticipantsDelayed(); - void setVideoStreams(const std::vector &ssrcs); - [[nodiscard]] uint32 chooseLargeVideoSsrc() const; + void setIncomingVideoStreams(const std::vector &endpoints); + [[nodiscard]] std::string chooseLargeVideoEndpoint() const; void editParticipant( not_null participantPeer, @@ -368,17 +398,15 @@ private: MTP::Sender _api; rpl::event_stream> _realChanges; rpl::variable _state = State::Creating; - rpl::variable _instanceState - = InstanceState::Disconnected; - bool _instanceTransitioning = false; - InstanceMode _instanceMode = InstanceMode::None; base::flat_set _unresolvedSsrcs; - std::vector _preparedParticipants; - bool _addPreparedParticipantsScheduled = false; bool _recordingStoppedByMe = false; MTP::DcId _broadcastDcId = 0; base::flat_map, LoadingPart> _broadcastParts; + base::flat_set< + std::shared_ptr< + MediaChannelDescriptionsTask>, + base::pointer_comparator> _mediaChannelDescriptionses; not_null _joinAs; std::vector> _possibleJoinAs; @@ -395,21 +423,35 @@ private: uint64 _id = 0; uint64 _accessHash = 0; uint32 _mySsrc = 0; - uint32 _screencastSsrc = 0; + uint32 _screenSsrc = 0; TimeId _scheduleDate = 0; base::flat_set _mySsrcs; mtpRequestId _createRequestId = 0; mtpRequestId _updateMuteRequestId = 0; + rpl::variable _instanceState + = InstanceState::Disconnected; + bool _instanceTransitioning = false; + InstanceMode _instanceMode = InstanceMode::None; std::unique_ptr _instance; - std::shared_ptr _videoCapture; - const std::unique_ptr _videoOutgoing; + std::shared_ptr _cameraCapture; + const std::unique_ptr _cameraOutgoing; + + rpl::variable _screenInstanceState + = InstanceState::Disconnected; + InstanceMode _screenInstanceMode = InstanceMode::None; + std::unique_ptr _screenInstance; + std::shared_ptr _screenCapture; + const std::unique_ptr _screenOutgoing; + QString _screenDeviceId; + std::string _screenEndpoint; + rpl::event_stream _levelUpdates; rpl::event_stream _streamsVideoUpdated; - base::flat_set _videoStreamSsrcs; - base::flat_set _videoMuted; - rpl::variable _videoStreamLarge = 0; - uint32 _videoStreamPinned = 0; + base::flat_set _incomingVideoEndpoints; + base::flat_set _activeVideoEndpoints; + rpl::variable _videoEndpointLarge; + std::string _videoEndpointPinned; std::unique_ptr _videoLargeTrackWrap; rpl::variable _videoLargeTrack; base::flat_map _lastSpoke; @@ -430,8 +472,7 @@ private: std::unique_ptr _mediaDevices; QString _audioInputId; QString _audioOutputId; - QString _videoInputId; - QString _videoDeviceId; + QString _cameraInputId; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index e613850819..59c1fe66c3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -145,8 +145,10 @@ public: return _raisedHandRating; } - [[nodiscard]] not_null createVideoTrack(); + [[nodiscard]] not_null createVideoTrack( + const std::string &endpoint); void clearVideoTrack(); + [[nodiscard]] const std::string &videoTrackEndpoint() const; void setVideoTrack(not_null track); void addActionRipple(QPoint point, Fn updateCallback) override; @@ -276,6 +278,7 @@ private: std::unique_ptr _statusIcon; std::unique_ptr _videoTrack; Webrtc::VideoTrack *_videoTrackShown = nullptr; + std::string _videoTrackEndpoint; rpl::lifetime _videoTrackLifetime; // #TODO calls move to unique_ptr. Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon. Ui::Animations::Simple _mutedAnimation; // For gray/red icon. @@ -380,14 +383,16 @@ private: not_null row, uint64 raiseHandRating) const; Row *findRow(not_null participantPeer) const; - Row *findRow(uint32 audioSsrc) const; + const Data::GroupCallParticipant *findParticipant( + const std::string &endpoint) const; + Row *findRow(const std::string &endpoint) const; void appendInvitedUsers(); void scheduleRaisedHandStatusRemove(); const not_null _call; not_null _peer; - uint32 _largeSsrc = 0; + std::string _largeEndpoint; bool _prepared = false; rpl::event_stream _toggleMuteRequests; @@ -1015,14 +1020,20 @@ void Row::refreshStatus() { _speaking); } -not_null Row::createVideoTrack() { +not_null Row::createVideoTrack( + const std::string &endpoint) { _videoTrackShown = nullptr; + _videoTrackEndpoint = endpoint; _videoTrack = std::make_unique( Webrtc::VideoState::Active); setVideoTrack(_videoTrack.get()); return _videoTrack.get(); } +const std::string &Row::videoTrackEndpoint() const { + return _videoTrackEndpoint; +} + void Row::clearVideoTrack() { _videoTrackLifetime.destroy(); _videoTrackShown = nullptr; @@ -1149,27 +1160,48 @@ void MembersController::setupListChangeViewers() { } }, _lifetime); - _call->videoStreamLargeValue( - ) | rpl::filter([=](uint32 largeSsrc) { - return (_largeSsrc != largeSsrc); - }) | rpl::start_with_next([=](uint32 largeSsrc) { - if (const auto row = findRow(_largeSsrc)) { - _call->addVideoOutput(_largeSsrc, row->createVideoTrack()); + _call->videoEndpointLargeValue( + ) | rpl::filter([=](const std::string &largeEndpoint) { + return (_largeEndpoint != largeEndpoint); + }) | rpl::start_with_next([=](const std::string &largeEndpoint) { + if (const auto participant = findParticipant(_largeEndpoint)) { + if (participant->cameraEndpoint() == _largeEndpoint) { + if (const auto row = findRow(participant->peer)) { + _call->addVideoOutput( + _largeEndpoint, + row->createVideoTrack(_largeEndpoint)); + } + } } - _largeSsrc = largeSsrc; - if (const auto row = findRow(_largeSsrc)) { - row->clearVideoTrack(); + _largeEndpoint = largeEndpoint; + if (const auto participant = findParticipant(_largeEndpoint)) { + if (participant->cameraEndpoint() == _largeEndpoint) { + if (const auto row = findRow(participant->peer)) { + row->clearVideoTrack(); + } + } } }, _lifetime); _call->streamsVideoUpdates( ) | rpl::start_with_next([=](StreamsVideoUpdate update) { - Assert(update.ssrc != _largeSsrc); - if (const auto row = findRow(update.ssrc)) { + Assert(update.endpoint != _largeEndpoint); + if (const auto participant = findParticipant(update.endpoint)) { if (update.streams) { - _call->addVideoOutput(update.ssrc, row->createVideoTrack()); + if (participant->cameraEndpoint() == update.endpoint + || !_call->streamsVideo(participant->cameraEndpoint())) { + if (const auto row = findRow(participant->peer)) { + _call->addVideoOutput( + update.endpoint, + row->createVideoTrack(update.endpoint)); + } + } } else { - row->clearVideoTrack(); + if (const auto row = findRow(participant->peer)) { + if (row->videoTrackEndpoint() == update.endpoint) { + row->clearVideoTrack(); + } + } } } }, _lifetime); @@ -1493,15 +1525,18 @@ Row *MembersController::findRow(not_null participantPeer) const { delegate()->peerListFindRow(participantPeer->id.value)); } -Row *MembersController::findRow(uint32 audioSsrc) const { - if (!audioSsrc) { +const Data::GroupCallParticipant *MembersController::findParticipant( + const std::string &endpoint) const { + if (endpoint.empty()) { return nullptr; } const auto real = _call->lookupReal(); - const auto participantPeer = real - ? real->participantPeerByAudioSsrc(audioSsrc) - : nullptr; - return participantPeer ? findRow(participantPeer) : nullptr; + return real ? real->participantByEndpoint(endpoint) : nullptr; +} + +Row *MembersController::findRow(const std::string &endpoint) const { + const auto participant = findParticipant(endpoint); + return participant ? findRow(participant->peer) : nullptr; } Main::Session &MembersController::session() const { @@ -1845,15 +1880,35 @@ base::unique_qptr MembersController::createRowContextMenu( _kickParticipantRequests.fire_copy(participantPeer); }); - const auto ssrc = real->ssrc(); - if (ssrc != 0 && _call->streamsVideo(ssrc)) { - const auto pinned = (_call->videoStreamPinned() == ssrc); - const auto phrase = pinned - ? tr::lng_group_call_context_unpin_video(tr::now) - : tr::lng_group_call_context_pin_video(tr::now); - result->addAction(phrase, [=] { - _call->pinVideoStream(pinned ? 0 : ssrc); - }); + if (const auto real = _call->lookupReal()) { + const auto pinnedEndpoint = _call->videoEndpointPinned(); + const auto participant = real->participantByEndpoint(pinnedEndpoint); + if (participant && participant->peer == participantPeer) { + result->addAction( + tr::lng_group_call_context_unpin_video(tr::now), + [=] { _call->pinVideoEndpoint(std::string()); }); + } else { + const auto &participants = real->participants(); + const auto i = ranges::find( + participants, + participantPeer, + &Data::GroupCallParticipant::peer); + if (i != end(participants)) { + const auto camera = i->cameraEndpoint(); + const auto screen = i->screenEndpoint(); + const auto streamsScreen = _call->streamsVideo(screen); + if (streamsScreen || _call->streamsVideo(camera)) { + const auto callback = [=] { + _call->pinVideoEndpoint(streamsScreen + ? screen + : camera); + }; + result->addAction( + tr::lng_group_call_context_pin_video(tr::now), + callback); + } + } + } } if (real->ssrc() != 0 @@ -2217,8 +2272,9 @@ void Members::setupPinnedVideo() { _mode.changes() | rpl::filter( _1 == PanelMode::Default ) | rpl::to_empty, - _call->videoStreamLargeValue() | rpl::filter([=](uint32 ssrc) { - return ssrc == _call->videoStreamPinned(); + _call->videoEndpointLargeValue( + ) | rpl::filter([=](const std::string &endpoint) { + return endpoint == _call->videoEndpointPinned(); }) | rpl::to_empty ) | rpl::start_with_next([=] { _scroll->scrollToY(0); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index b0116ae329..732e1a9702 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -493,11 +493,11 @@ rpl::lifetime &Panel::chooseSourceInstanceLifetime() { } void Panel::chooseSourceAccepted(const QString &deviceId) { - _call->switchToScreenSharing(deviceId); + _call->toggleScreenSharing(deviceId); } void Panel::chooseSourceStop() { - _call->toggleVideo(false); + _call->toggleScreenSharing(std::nullopt); } void Panel::initWindow() { @@ -722,10 +722,8 @@ void Panel::refreshLeftButton() { &st::groupCallVideoActiveSmall); _video->show(); _video->setClickedCallback([=] { - const auto sharing = _call->isScreenSharing(); - const auto active = (_call->outgoingVideoTrack()->state() - == Webrtc::VideoState::Active); - _call->toggleVideo(sharing || !active); + _call->toggleVideo(_call->outgoingCameraTrack()->state() + != Webrtc::VideoState::Active); }); _video->setText(tr::lng_group_call_video()); _video->setColorOverrides(_mute->colorOverrides()); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 98c56c1e46..1356866d5e 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -33,8 +33,22 @@ constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000); }); } +[[nodiscard]] const std::string &EmptyEndpoint() { + static const auto result = std::string(); + return result; +} + } // namespace + +const std::string &GroupCallParticipant::cameraEndpoint() const { + return videoParams ? videoParams->camera.endpoint : EmptyEndpoint(); +} + +const std::string &GroupCallParticipant::screenEndpoint() const { + return videoParams ? videoParams->screen.endpoint : EmptyEndpoint(); +} + GroupCall::GroupCall( not_null peer, uint64 id, @@ -193,13 +207,36 @@ PeerData *GroupCall::participantPeerByAudioSsrc(uint32 ssrc) const { : nullptr; } -PeerData *GroupCall::participantPeerByVideoSsrc(uint32 ssrc) const { - const auto i = _participantPeerByVideoSsrc.find(ssrc); - return (i != end(_participantPeerByVideoSsrc)) +PeerData *GroupCall::participantPeerByCameraSsrc(uint32 ssrc) const { + const auto i = _participantPeerByCameraSsrc.find(ssrc); + return (i != end(_participantPeerByCameraSsrc)) ? i->second.get() : nullptr; } +PeerData *GroupCall::participantPeerByScreenSsrc(uint32 ssrc) const { + const auto i = _participantPeerByScreenSsrc.find(ssrc); + return (i != end(_participantPeerByScreenSsrc)) + ? i->second.get() + : nullptr; +} + +const GroupCallParticipant *GroupCall::participantByEndpoint( + const std::string &endpoint) const { + if (endpoint.empty()) { + return nullptr; + } + for (const auto &participant : _participants) { + if (const auto params = participant.videoParams.get()) { + if (params->camera.endpoint == endpoint + || params->screen.endpoint == endpoint) { + return &participant; + } + } + } + return nullptr; +} + rpl::producer<> GroupCall::participantsSliceAdded() { return _participantsSliceAdded.events(); } @@ -305,7 +342,8 @@ void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) { _participants.clear(); _speakingByActiveFinishes.clear(); _participantPeerByAudioSsrc.clear(); - _participantPeerByVideoSsrc.clear(); + _participantPeerByCameraSsrc.clear(); + _participantPeerByScreenSsrc.clear(); _allParticipantsLoaded = false; applyParticipantsSlice( @@ -499,10 +537,7 @@ void GroupCall::applyParticipantsSlice( .was = *i, }; _participantPeerByAudioSsrc.erase(i->ssrc); - const auto &all = VideoSourcesFromParams(i->videoParams); - for (const auto ssrc : all) { - _participantPeerByVideoSsrc.erase(ssrc); - } + eraseVideoSsrcs(*i); _speakingByActiveFinishes.remove(participantPeer); _participants.erase(i); if (sliceSource != ApplySliceSource::SliceLoaded) { @@ -543,8 +578,8 @@ void GroupCall::applyParticipantsSlice( && (!was || was->onlyMinLoaded); const auto raisedHandRating = data.vraise_hand_rating().value_or_empty(); - const auto hasVideoParamsInformation = (sliceSource - != ApplySliceSource::UpdateConstructed); + const auto hasVideoParamsInformation = true/*(sliceSource + != ApplySliceSource::UpdateConstructed)*/; const auto value = Participant{ .peer = participantPeer, .videoParams = (hasVideoParamsInformation @@ -571,19 +606,13 @@ void GroupCall::applyParticipantsSlice( .muted = data.is_muted(), .mutedByMe = mutedByMe, .canSelfUnmute = canSelfUnmute, - .videoMuted = (data.vvideo() == nullptr), .onlyMinLoaded = onlyMinLoaded, }; if (i == end(_participants)) { _participantPeerByAudioSsrc.emplace( value.ssrc, participantPeer); - const auto &all = VideoSourcesFromParams(value.videoParams); - for (const auto ssrc : all) { - _participantPeerByVideoSsrc.emplace( - ssrc, - participantPeer); - } + emplaceVideoSsrcs(value); _participants.push_back(value); if (const auto user = participantPeer->asUser()) { _peer->owner().unregisterInvitedToCallUser(_id, user); @@ -596,17 +625,8 @@ void GroupCall::applyParticipantsSlice( participantPeer); } if (i->videoParams != value.videoParams) { - const auto &old = VideoSourcesFromParams(i->videoParams); - for (const auto ssrc : old) { - _participantPeerByVideoSsrc.erase(ssrc); - } - const auto &now = VideoSourcesFromParams( - value.videoParams); - for (const auto ssrc : now) { - _participantPeerByVideoSsrc.emplace( - ssrc, - participantPeer); - } + eraseVideoSsrcs(*i); + emplaceVideoSsrcs(value); } *i = value; } @@ -627,6 +647,29 @@ void GroupCall::applyParticipantsSlice( } } +void GroupCall::emplaceVideoSsrcs(const Participant &participant) { + if (const auto params = participant.videoParams.get()) { + const auto participantPeer = participant.peer; + for (const auto ssrc : params->camera.ssrcs) { + _participantPeerByCameraSsrc.emplace(ssrc, participantPeer); + } + for (const auto ssrc : params->screen.ssrcs) { + _participantPeerByScreenSsrc.emplace(ssrc, participantPeer); + } + } +} + +void GroupCall::eraseVideoSsrcs(const Participant &participant) { + if (const auto params = participant.videoParams.get()) { + for (const auto ssrc : params->camera.ssrcs) { + _participantPeerByCameraSsrc.erase(ssrc); + } + for (const auto ssrc : params->screen.ssrcs) { + _participantPeerByScreenSsrc.erase(ssrc); + } + } +} + void GroupCall::applyLastSpoke( uint32 ssrc, LastSpokeTimes when, @@ -840,6 +883,9 @@ void GroupCall::requestUnknownParticipants() { } _unknownSpokenPeerIds.remove(id); } + if (!ssrcs.empty()) { + _participantsResolved.fire(&ssrcs); + } requestUnknownParticipants(); }).fail([=](const MTP::Error &error) { _unknownParticipantPeersRequestId = 0; diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 4a7a5965e6..4015f239f1 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -14,7 +14,7 @@ class PeerData; class ApiWrap; namespace Calls { -struct VideoParams; +struct ParticipantVideoParams; } // namespace Calls namespace Data { @@ -26,7 +26,7 @@ struct LastSpokeTimes { struct GroupCallParticipant { not_null peer; - std::shared_ptr videoParams; + std::shared_ptr videoParams; TimeId date = 0; TimeId lastActive = 0; uint64 raisedHandRating = 0; @@ -38,8 +38,10 @@ struct GroupCallParticipant { bool muted = false; bool mutedByMe = false; bool canSelfUnmute = false; - bool videoMuted = true; bool onlyMinLoaded = false; + + [[nodiscard]] const std::string &cameraEndpoint() const; + [[nodiscard]] const std::string &screenEndpoint() const; }; class GroupCall final { @@ -104,7 +106,10 @@ public: void requestParticipants(); [[nodiscard]] bool participantsLoaded() const; [[nodiscard]] PeerData *participantPeerByAudioSsrc(uint32 ssrc) const; - [[nodiscard]] PeerData *participantPeerByVideoSsrc(uint32 ssrc) const; + [[nodiscard]] PeerData *participantPeerByCameraSsrc(uint32 ssrc) const; + [[nodiscard]] PeerData *participantPeerByScreenSsrc(uint32 ssrc) const; + [[nodiscard]] const Participant *participantByEndpoint( + const std::string &endpoint) const; [[nodiscard]] rpl::producer<> participantsSliceAdded(); [[nodiscard]] rpl::producer participantUpdated() const; @@ -120,6 +125,12 @@ public: PeerData *participantPeerLoaded); void resolveParticipants(const base::flat_set &ssrcs); + [[nodiscard]] rpl::producer< + not_null*>> participantsResolved() const { + return _participantsResolved.events(); + } [[nodiscard]] int fullCount() const; [[nodiscard]] rpl::producer fullCountValue() const; @@ -167,6 +178,9 @@ private: void processSavedFullCall(); void finishParticipantsSliceRequest(); + void emplaceVideoSsrcs(const Participant &participant); + void eraseVideoSsrcs(const Participant &participant); + const uint64 _id = 0; const uint64 _accessHash = 0; @@ -184,7 +198,8 @@ private: std::vector _participants; base::flat_map> _participantPeerByAudioSsrc; - base::flat_map> _participantPeerByVideoSsrc; + base::flat_map> _participantPeerByCameraSsrc; + base::flat_map> _participantPeerByScreenSsrc; base::flat_map, crl::time> _speakingByActiveFinishes; base::Timer _speakingByActiveFinishTimer; QString _nextOffset; @@ -196,6 +211,10 @@ private: base::flat_map _unknownSpokenSsrcs; base::flat_map _unknownSpokenPeerIds; + rpl::event_stream< + not_null*>> _participantsResolved; mtpRequestId _unknownParticipantPeersRequestId = 0; rpl::event_stream _participantUpdates; diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 9928c00d23..697ef2ed67 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 9928c00d231c1194896d582a71e3bb6d70ee2765 +Subproject commit 697ef2ed67cfcad81b0e61caf0945a057a847327