diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 720ac01bb..8da6f836a 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -375,6 +375,7 @@ updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update; updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update; updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update; +updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1204,11 +1205,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; +groupCall#653dbaad flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_muted:flags.14?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant; +groupCallParticipant#a8ba51a7 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?DataJSON presentation:flags.14?DataJSON = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; @@ -1624,7 +1625,7 @@ phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates; phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; -phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool; +phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector = Vector; phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates; phone.editGroupCallParticipant#aec610e4 flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_muted:flags.3?Bool = Updates; phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; @@ -1633,6 +1634,8 @@ phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:I phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates; phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates; phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool; +phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates; +phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1649,4 +1652,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 128 +// LAYER 129 diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 021811c23..d3feabee8 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1878,6 +1878,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updatePhoneCall: case mtpc_updatePhoneCallSignalingData: case mtpc_updateGroupCallParticipants: + case mtpc_updateGroupCallConnection: case mtpc_updateGroupCall: { Core::App().calls().handleUpdate(&session(), update); } break; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 3722dad09..be7387298 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -391,6 +391,8 @@ void Instance::handleUpdate( handleSignalingData(session, data); }, [&](const MTPDupdateGroupCall &data) { handleGroupCallUpdate(session, update); + }, [&](const MTPDupdateGroupCallConnection &data) { + handleGroupCallUpdate(session, update); }, [&](const MTPDupdateGroupCallParticipants &data) { handleGroupCallUpdate(session, update); }, [](const auto &) { @@ -482,10 +484,15 @@ void Instance::handleGroupCallUpdate( && (&_currentGroupCall->peer()->session() == session)) { update.match([&](const MTPDupdateGroupCall &data) { _currentGroupCall->handlePossibleCreateOrJoinResponse(data); + }, [&](const MTPDupdateGroupCallConnection &data) { + _currentGroupCall->handlePossibleCreateOrJoinResponse(data); }, [](const auto &) { }); } + if (update.type() == mtpc_updateGroupCallConnection) { + return; + } const auto callId = update.match([](const MTPDupdateGroupCall &data) { return data.vcall().match([](const auto &data) { return data.vid().v; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 4e90510c7..8aed5a0ba 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -76,6 +76,12 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); return msgId / double(1ULL << 32); } +[[nodiscard]] std::string ReadJsonString( + const QJsonObject &object, + const char *key) { + return object.value(key).toString().toStdString(); +}; + } // namespace struct VideoParams { @@ -177,13 +183,8 @@ std::shared_ptr ParseVideoParams( return data; } - const auto readString = []( - const QJsonObject &object, - const char *key) { - return object.value(key).toString().toStdString(); - }; const auto object = document.object(); - data->description.endpointId = readString(object, "endpoint"); + data->description.endpointId = ReadJsonString(object, "endpoint"); const auto ssrcGroups = object.value("ssrc-groups").toArray(); data->description.videoSourceGroups.reserve(ssrcGroups.size()); @@ -201,58 +202,11 @@ std::shared_ptr ParseVideoParams( } data->description.videoSourceGroups.push_back({ .ssrcs = std::move(sources), - .semantics = readString(inner, "semantics"), - }); - } - - const auto payloadTypes = object.value("payload-types").toArray(); - data->description.videoPayloadTypes.reserve(payloadTypes.size()); - for (const auto &value : payloadTypes) { - const auto inner = value.toObject(); - - auto types = std::vector(); - { - const auto list = inner.value("rtcp-fbs").toArray(); - types.reserve(list.size()); - for (const auto &type : list) { - const auto inside = type.toObject(); - types.push_back({ - .type = readString(inside, "type"), - .subtype = readString(inside, "subtype"), - }); - } - } - auto parameters = std::vector>(); - { - const auto list = inner.value("parameters").toObject(); - parameters.reserve(list.size()); - for (auto i = list.begin(); i != list.end(); ++i) { - parameters.push_back({ - i.key().toStdString(), - i.value().toString().toStdString(), - }); - } - } - data->description.videoPayloadTypes.push_back({ - .id = uint32_t(inner.value("id").toDouble()), - .name = readString(inner, "name"), - .clockrate = uint32_t(inner.value("clockrate").toDouble()), - .channels = uint32_t(inner.value("channels").toDouble()), - .feedbackTypes = std::move(types), - .parameters = std::move(parameters), - }); - } - - const auto extensionMap = object.value("rtp-hdrexts").toArray(); - data->description.videoExtensionMap.reserve(extensionMap.size()); - for (const auto &extension : extensionMap) { - const auto inner = extension.toObject(); - data->description.videoExtensionMap.push_back({ - uint32_t(inner.value("id").toDouble()), - readString(inner, "uri"), + .semantics = ReadJsonString(inner, "semantics"), }); } + // videoPayloadTypes and videoExtensionMap will be in _commonVideoFields. return data; } @@ -950,7 +904,7 @@ 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::f_video_muted : Flag(0)) + //| (videoMuted ? Flag(0) : Flag::f_video) | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)); call->applyLocalUpdate( MTP_updateGroupCallParticipants( @@ -966,7 +920,8 @@ void GroupCall::applyMeInCallLocally() { MTP_int(volume), MTPstring(), // Don't update about text in local updates. MTP_long(raisedHandRating), - MTPDataJSON())), + MTPDataJSON(), // video + MTPDataJSON())), // presentation MTP_int(0)).c_updateGroupCallParticipants()); } @@ -995,7 +950,7 @@ 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::f_video_muted : Flag(0)) + //| (participant->videoMuted ? Flag(0) : Flag::f_video) | (participant->raisedHandRating ? Flag::f_raise_hand_rating : Flag(0)); @@ -1013,7 +968,8 @@ void GroupCall::applyParticipantLocally( MTP_int(volume.value_or(participant->volume)), MTPstring(), // Don't update about text in local updates. MTP_long(participant->raisedHandRating), - MTPDataJSON())), + MTPDataJSON(), // video + MTPDataJSON())), // presentation MTP_int(0)).c_updateGroupCallParticipants()); } @@ -1195,13 +1151,18 @@ void GroupCall::handlePossibleCreateOrJoinResponse( } else if (_id != data.vid().v || !_instance) { return; } - const auto streamDcId = MTP::BareDcId( - data.vstream_dc_id().value_or_empty()); - const auto params = data.vparams(); - if (!params) { + if (const auto streamDcId = data.vstream_dc_id()) { + _broadcastDcId = MTP::BareDcId(streamDcId->v); + } +} + +void GroupCall::handlePossibleCreateOrJoinResponse( + const MTPDupdateGroupCallConnection &data) { + if (data.is_presentation()) { + // #TODO calls return; } - params->match([&](const MTPDdataJSON &data) { + data.vparams().match([&](const MTPDdataJSON &data) { auto error = QJsonParseError{ 0, QJsonParseError::NoError }; const auto document = QJsonDocument::fromJson( data.vdata().v, @@ -1222,60 +1183,117 @@ void GroupCall::handlePossibleCreateOrJoinResponse( }); if (document.object().value("stream").toBool()) { - if (!streamDcId) { + if (!_broadcastDcId) { LOG(("Api Error: Empty stream_dc_id in groupCall.")); + _broadcastDcId = _peer->session().mtp().mainDcId(); } - _broadcastDcId = streamDcId - ? streamDcId - : _peer->session().mtp().mainDcId(); setInstanceMode(InstanceMode::Stream); return; } const auto readString = []( - const QJsonObject &object, - const char *key) { + const QJsonObject &object, + const char *key) { return object.value(key).toString().toStdString(); }; const auto root = document.object().value("transport").toObject(); + const auto video = document.object().value("video").toObject(); auto payload = tgcalls::GroupJoinResponsePayload(); - payload.ufrag = readString(root, "ufrag"); - payload.pwd = readString(root, "pwd"); + payload.serverVideoBandwidthProbingSsrc = uint32_t( + video.value("server_sources").toArray().at(0).toDouble()); + payload.ufrag = ReadJsonString(root, "ufrag"); + payload.pwd = ReadJsonString(root, "pwd"); const auto prints = root.value("fingerprints").toArray(); const auto candidates = root.value("candidates").toArray(); for (const auto &print : prints) { const auto object = print.toObject(); payload.fingerprints.push_back({ - .hash = readString(object, "hash"), - .setup = readString(object, "setup"), - .fingerprint = readString(object, "fingerprint"), + .hash = ReadJsonString(object, "hash"), + .setup = ReadJsonString(object, "setup"), + .fingerprint = ReadJsonString(object, "fingerprint"), }); } for (const auto &candidate : candidates) { const auto object = candidate.toObject(); payload.candidates.push_back({ - .port = readString(object, "port"), - .protocol = readString(object, "protocol"), - .network = readString(object, "network"), - .generation = readString(object, "generation"), - .id = readString(object, "id"), - .component = readString(object, "component"), - .foundation = readString(object, "foundation"), - .priority = readString(object, "priority"), - .ip = readString(object, "ip"), - .type = readString(object, "type"), - .tcpType = readString(object, "tcpType"), - .relAddr = readString(object, "relAddr"), - .relPort = readString(object, "relPort"), + .port = ReadJsonString(object, "port"), + .protocol = ReadJsonString(object, "protocol"), + .network = ReadJsonString(object, "network"), + .generation = ReadJsonString(object, "generation"), + .id = ReadJsonString(object, "id"), + .component = ReadJsonString(object, "component"), + .foundation = ReadJsonString(object, "foundation"), + .priority = ReadJsonString(object, "priority"), + .ip = ReadJsonString(object, "ip"), + .type = ReadJsonString(object, "type"), + .tcpType = ReadJsonString(object, "tcpType"), + .relAddr = ReadJsonString(object, "relAddr"), + .relPort = ReadJsonString(object, "relPort"), }); } + + parseCommonVideoFields(video); + setInstanceMode(InstanceMode::Rtc); _instance->setJoinResponsePayload(payload, {}); - - addParticipantsToInstance(); }); } +void GroupCall::parseCommonVideoFields(const QJsonObject &root) { + using namespace tgcalls; + + _commonVideoFields = std::make_unique(); + const auto raw = _commonVideoFields.get(); + + const auto payloadTypes = root.value("payload-types").toArray(); + raw->videoPayloadTypes.reserve(payloadTypes.size()); + for (const auto &value : payloadTypes) { + const auto inner = value.toObject(); + + auto types = std::vector(); + { + const auto list = inner.value("rtcp-fbs").toArray(); + types.reserve(list.size()); + for (const auto &type : list) { + const auto inside = type.toObject(); + types.push_back({ + .type = ReadJsonString(inside, "type"), + .subtype = ReadJsonString(inside, "subtype"), + }); + } + } + auto parameters = std::vector>(); + { + const auto list = inner.value("parameters").toObject(); + parameters.reserve(list.size()); + for (auto i = list.begin(); i != list.end(); ++i) { + parameters.push_back({ + i.key().toStdString(), + i.value().toString().toStdString(), + }); + } + } + raw->videoPayloadTypes.push_back({ + .id = uint32_t(inner.value("id").toDouble()), + .name = ReadJsonString(inner, "name"), + .clockrate = uint32_t(inner.value("clockrate").toDouble()), + .channels = uint32_t(inner.value("channels").toDouble()), + .feedbackTypes = std::move(types), + .parameters = std::move(parameters), + }); + } + + const auto extensionMap = root.value("rtp-hdrexts").toArray(); + raw->videoExtensionMap.reserve(extensionMap.size()); + for (const auto &extension : extensionMap) { + const auto inner = extension.toObject(); + raw->videoExtensionMap.push_back({ + uint32_t(inner.value("id").toDouble()), + ReadJsonString(inner, "uri"), + }); + } +} + void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { if (data.vid().v == _id) { LOG(("Call Info: Hangup after groupCallDiscarded.")); @@ -1286,7 +1304,9 @@ void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { void GroupCall::addParticipantsToInstance() { const auto real = lookupReal(); - if (!real || (_instanceMode == InstanceMode::None)) { + if (!real + || (_instanceMode == InstanceMode::None) + || (_instanceMode == InstanceMode::Rtc && !_commonVideoFields)) { return; } for (const auto &participant : real->participants()) { @@ -1297,10 +1317,15 @@ void GroupCall::addParticipantsToInstance() { void GroupCall::prepareParticipantForAdding( const Data::GroupCallParticipant &participant) { - _preparedParticipants.push_back(participant.videoParams + const auto withVideo = _commonVideoFields && participant.videoParams; + _preparedParticipants.push_back(withVideo ? participant.videoParams->description : tgcalls::GroupParticipantDescription()); auto &added = _preparedParticipants.back(); + if (withVideo) { + added.videoSourceGroups = _commonVideoFields->videoSourceGroups; + added.videoExtensionMap = _commonVideoFields->videoExtensionMap; + } added.audioSsrc = participant.ssrc; _unresolvedSsrcs.remove(added.audioSsrc); for (const auto &group : added.videoSourceGroups) { @@ -1624,7 +1649,7 @@ void GroupCall::ensureControllerCreated() { }); return result; }, - .videoContentType = tgcalls::VideoContentType::Screencast, + .videoContentType = tgcalls::VideoContentType::Generic, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); @@ -1953,20 +1978,33 @@ void GroupCall::checkJoined() { if (state() != State::Connecting || !_id || !_mySsrc) { return; } + auto sources = QVector(1, MTP_int(_mySsrc)); + if (_screencastSsrc) { + sources.push_back(MTP_int(_screencastSsrc)); + } _api.request(MTPphone_CheckGroupCall( inputCall(), - MTP_int(_mySsrc) - )).done([=](const MTPBool &result) { - if (!mtpIsTrue(result)) { - LOG(("Call Info: Rejoin after FALSE in checkGroupCall.")); + MTP_vector(std::move(sources)) + )).done([=](const MTPVector &result) { + if (!ranges::contains(result.v, MTP_int(_mySsrc))) { + LOG(("Call Info: Rejoin after no _mySsrc in checkGroupCall.")); rejoin(); } else if (state() == State::Connecting) { _checkJoinedTimer.callOnce(kCheckJoinedTimeout); } + if (_screencastSsrc + && !ranges::contains(result.v, MTP_int(_screencastSsrc))) { + LOG(("Call Info: " + "Rejoin presentation after _screencastSsrc not found.")); + // #TODO calls + } }).fail([=](const MTP::Error &error) { - LOG(("Call Info: Rejoin after error '%1' in checkGroupCall." + LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." ).arg(error.type())); rejoin(); + if (_screencastSsrc) { + // #TODO calls + } }).send(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 4ff1f7457..39766ea35 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -143,6 +143,8 @@ public: void join(const MTPInputGroupCall &inputCall); void handleUpdate(const MTPUpdate &update); void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data); + void handlePossibleCreateOrJoinResponse( + const MTPDupdateGroupCallConnection &data); void changeTitle(const QString &title); void toggleRecording(bool enabled, const QString &title); [[nodiscard]] bool recordingStoppedByMe() const { @@ -299,6 +301,7 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); + void parseCommonVideoFields(const QJsonObject &root); void ensureControllerCreated(); void destroyController(); @@ -391,6 +394,8 @@ private: uint64 _id = 0; uint64 _accessHash = 0; uint32 _mySsrc = 0; + uint32 _screencastSsrc = 0; + std::unique_ptr _commonVideoFields; TimeId _scheduleDate = 0; base::flat_set _mySsrcs; mtpRequestId _createRequestId = 0; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index d6895e20d..2b61c487c 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -549,8 +549,8 @@ void GroupCall::applyParticipantsSlice( .peer = participantPeer, .videoParams = (hasVideoParamsInformation ? Calls::ParseVideoParams( - (data.vparams() - ? data.vparams()->c_dataJSON().vdata().v + (data.vvideo() + ? data.vvideo()->c_dataJSON().vdata().v : QByteArray()), (i != end(_participants) ? i->videoParams @@ -568,7 +568,7 @@ void GroupCall::applyParticipantsSlice( .muted = data.is_muted(), .mutedByMe = mutedByMe, .canSelfUnmute = canSelfUnmute, - .videoMuted = data.is_video_muted(), + .videoMuted = (data.vvideo() == nullptr), .onlyMinLoaded = onlyMinLoaded, }; if (i == end(_participants)) {