diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b963ac3abc..9aa8dfdb84 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1302,6 +1302,8 @@ PRIVATE media/view/media_view_playback_progress.h media/view/media_view_playback_sponsored.cpp media/view/media_view_playback_sponsored.h + media/view/media_view_video_stream.cpp + media/view/media_view_video_stream.h media/system_media_controls_manager.h media/system_media_controls_manager.cpp menu/menu_antispam_validator.cpp diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index bdd36e2710..47d516be9b 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -295,7 +295,7 @@ void Instance::startedConferenceReady( migrationInfo); _currentGroupCall = std::move(_startingGroupCall); _currentGroupCallChanges.fire_copy(call); - const auto real = call->conferenceCall().get(); + const auto real = call->sharedCall().get(); const auto link = real->conferenceInviteLink(); const auto slug = Group::ExtractConferenceSlug(link); finishConferenceInvitations(args); @@ -637,6 +637,10 @@ FnMut Instance::addAsyncWaiter() { }; } +void Instance::registerVideoStream(not_null call) { + _streams[&call->peer()->session()].push_back(call); +} + bool Instance::isSharingScreen() const { return (_currentCall && _currentCall->isSharingScreen()) || (_currentGroupCall && _currentGroupCall->isSharingScreen()); @@ -707,6 +711,31 @@ void Instance::handleCallUpdate( void Instance::handleGroupCallUpdate( not_null session, const MTPUpdate &update) { + if (const auto i = _streams.find(session); i != end(_streams)) { + for (auto j = begin(i->second); j != end(i->second);) { + if (const auto strong = j->get()) { + update.match([&](const MTPDupdateGroupCall &data) { + strong->handlePossibleCreateOrJoinResponse(data); + strong->handleUpdate(update); + }, [&](const MTPDupdateGroupCallConnection &data) { + strong->handlePossibleCreateOrJoinResponse(data); + }, [&](const MTPDupdateGroupCallMessage &data) { + strong->handleIncomingMessage(data); + }, [&](const MTPDupdateGroupCallEncryptedMessage &data) { + strong->handleIncomingMessage(data); + }, [&](const MTPDupdateGroupCallParticipants &data) { + strong->handleUpdate(update); + }, [&](const MTPDupdateGroupCallChainBlocks &data) { + strong->handleUpdate(update); + }, [](const auto &) { + }); + ++j; + } else { + j = i->second.erase(j); + } + } + } + const auto groupCall = _currentGroupCall ? _currentGroupCall.get() : _startingGroupCall.get(); @@ -1111,7 +1140,7 @@ void Instance::showConferenceInvite( return; } else if (inGroupCall() && _currentGroupCall->conference() - && _currentGroupCall->conferenceCall()->id() == conferenceId) { + && _currentGroupCall->sharedCall()->id() == conferenceId) { return; } diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 1c3d8b9212..651cdded99 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -147,6 +147,8 @@ public: [[nodiscard]] FnMut addAsyncWaiter(); + void registerVideoStream(not_null call); + [[nodiscard]] bool isSharingScreen() const; [[nodiscard]] bool isQuitPrevent(); @@ -217,6 +219,10 @@ private: base::flat_set> _asyncWaiters; + base::flat_map< + not_null, + std::vector>> _streams; + }; } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index 9715382af0..75cf5fd3d0 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -584,7 +584,7 @@ void TopBar::subscribeToMembersChanges(not_null call) { const auto group = _groupCall.get(); const auto conference = group && group->conference(); auto realValue = conference - ? (rpl::single(group->conferenceCall().get()) | rpl::type_erased()) + ? (rpl::single(group->sharedCall().get()) | rpl::type_erased()) : peer->session().changes().peerFlagsValue( peer, Data::PeerUpdate::Flag::GroupCall diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 66f5ec841a..771bc0ed23 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -591,10 +591,10 @@ GroupCall::GroupCall( GroupCall::GroupCall( not_null delegate, Group::JoinInfo join, - StartConferenceInfo conference, + StartConferenceInfo startInfo, const MTPInputGroupCall &inputCall) : _delegate(delegate) -, _conferenceCall(std::move(conference.call)) +, _sharedCall(std::move(startInfo.call)) , _peer(join.peer) , _history(_peer->owner().history(_peer)) , _api(&_peer->session().mtp()) @@ -602,8 +602,8 @@ GroupCall::GroupCall( , _joinAs(join.joinAs) , _possibleJoinAs(std::move(join.possibleJoinAs)) , _joinHash(join.joinHash) -, _conferenceLinkSlug(conference.linkSlug) -, _conferenceJoinMessageId(conference.joinMessageId) +, _conferenceLinkSlug(startInfo.linkSlug) +, _conferenceJoinMessageId(startInfo.joinMessageId) , _rtmpUrl(join.rtmpInfo.url) , _rtmpKey(join.rtmpInfo.key) , _canManage(Data::CanManageGroupCallValue(_peer)) @@ -664,7 +664,7 @@ GroupCall::GroupCall( if (!canManage() && real->joinMuted()) { _muted = MuteState::ForceMuted; } - } else if (!conference.migrating && !conference.show) { + } else if (!startInfo.migrating && !startInfo.show) { _peer->session().changes().peerFlagsValue( _peer, Data::PeerUpdate::Flag::GroupCall @@ -684,21 +684,21 @@ GroupCall::GroupCall( setupMediaDevices(); setupOutgoingVideo(); - if (_conferenceCall) { + if (_sharedCall && conference()) { setupConferenceCall(); initConferenceE2E(); - } else if (conference.migrating || conference.show) { + } else if (!_sharedCall && (startInfo.migrating || startInfo.show)) { initConferenceE2E(); } - if (conference.migrating || (conference.show && !_conferenceCall)) { - if (!conference.muted) { + if (startInfo.migrating || (startInfo.show && !_sharedCall)) { + if (!startInfo.muted) { setMuted(MuteState::Active); } _startConferenceInfo = std::make_shared( - std::move(conference)); + std::move(startInfo)); } - if (_id || (!_conferenceCall && _startConferenceInfo)) { + if (_id || (!_sharedCall && _startConferenceInfo)) { initialJoin(); } else { start(join.scheduleDate, join.rtmp); @@ -788,16 +788,16 @@ void GroupCall::initConferenceE2E() { } void GroupCall::setupConferenceCall() { - Expects(_conferenceCall != nullptr); + Expects(_sharedCall != nullptr); - _conferenceCall->staleParticipantIds( + _sharedCall->staleParticipantIds( ) | rpl::start_with_next([=](const base::flat_set &staleIds) { removeConferenceParticipants(staleIds, true); }, _lifetime); } void GroupCall::trackParticipantsWithAccess() { - if (!_conferenceCall || !_e2e) { + if (!_sharedCall || !_e2e) { return; } @@ -808,7 +808,7 @@ void GroupCall::trackParticipantsWithAccess() { for (const auto &id : set.list) { users.emplace(UserId(id.v)); } - _conferenceCall->setParticipantsWithAccess(std::move(users)); + _sharedCall->setParticipantsWithAccess(std::move(users)); }, _e2e->lifetime()); } @@ -1217,7 +1217,28 @@ bool GroupCall::rtmp() const { } bool GroupCall::conference() const { - return _conferenceCall || _startConferenceInfo; + if (const auto raw = _sharedCall.get()) { + return (raw->origin() == Data::GroupCallOrigin::Conference); + } else if (const auto info = _startConferenceInfo.get()) { + return true; + } + return false; +} + +bool GroupCall::videoStream() const { + if (const auto raw = _sharedCall.get()) { + return (raw->origin() == Data::GroupCallOrigin::VideoStream); + } + return false; +} + +Data::GroupCallOrigin GroupCall::origin() const { + if (const auto raw = _sharedCall.get()) { + return raw->origin(); + } else if (const auto info = _startConferenceInfo.get()) { + return Data::GroupCallOrigin::Conference; + } + return Data::GroupCallOrigin::Group; } bool GroupCall::listenersHidden() const { @@ -1246,15 +1267,15 @@ void GroupCall::setRtmpInfo(const Calls::Group::RtmpInfo &value) { } Data::GroupCall *GroupCall::lookupReal() const { - if (const auto conference = _conferenceCall.get()) { - return conference; + if (const auto shared = _sharedCall.get()) { + return shared; } const auto real = _peer->groupCall(); return (real && real->id() == _id) ? real : nullptr; } -std::shared_ptr GroupCall::conferenceCall() const { - return _conferenceCall; +std::shared_ptr GroupCall::sharedCall() const { + return _sharedCall; } rpl::producer> GroupCall::real() const { @@ -1329,8 +1350,8 @@ void GroupCall::initialJoinRequested() { } }, _lifetime); - if (_conferenceCall) { - _canManage = _conferenceCall->canManage(); + if (_sharedCall) { + _canManage = _sharedCall->canManage(); return; } _peer->session().updates().addActiveChat( @@ -1519,7 +1540,7 @@ void GroupCall::startRejoin() { for (const auto &[task, part] : _broadcastParts) { _api.request(part.requestId).cancel(); } - if (_conferenceCall || _startConferenceInfo) { + if (conference()) { initConferenceE2E(); } setState(State::Joining); @@ -1589,10 +1610,11 @@ void GroupCall::rejoin(not_null as) { }; LOG(("Call Info: Join payload received, joining with ssrc: %1." ).arg(_joinState.payload.ssrc)); - if (!_conferenceCall && _startConferenceInfo) { + if (!_sharedCall && _startConferenceInfo) { startConference(); - } else if (_conferenceCall - && !_conferenceCall->blockchainMayBeEmpty() + } else if (conference() + && _sharedCall + && !_sharedCall->blockchainMayBeEmpty() && !_e2e->hasLastBlock0()) { refreshLastBlockAndJoin(); } else { @@ -1715,13 +1737,13 @@ void GroupCall::startConference() { const MTPUpdates &result, const MTP::Response &response) { _createRequestId = 0; - _conferenceCall = _peer->owner().sharedConferenceCallFind(result); - if (!_conferenceCall) { + _sharedCall = _peer->owner().sharedConferenceCallFind(result); + if (!_sharedCall) { joinFail(u"Call not found!"_q); return; } - applyInputCall(_conferenceCall->input()); - _realChanges.fire_copy(_conferenceCall.get()); + applyInputCall(_sharedCall->input()); + _realChanges.fire_copy(_sharedCall.get()); initialJoinRequested(); joinDone( @@ -1770,10 +1792,10 @@ void GroupCall::joinDone( } if (justCreated) { - subscribeToReal(_conferenceCall.get()); + subscribeToReal(_sharedCall.get()); setupConferenceCall(); _conferenceLinkSlug = Group::ExtractConferenceSlug( - _conferenceCall->conferenceInviteLink()); + _sharedCall->conferenceInviteLink()); Core::App().calls().startedConferenceReady( this, *_startConferenceInfo); @@ -3973,7 +3995,7 @@ void GroupCall::inviteToConference( inputCall(), user->inputUser )).done([=](const MTPUpdates &result) { - const auto call = _conferenceCall.get(); + const auto call = _sharedCall.get(); user->owner().registerInvitedToCallUser(_id, call, user, true); _peer->session().api().applyUpdates(result); resultAddress()->invited.push_back(user); @@ -4025,7 +4047,7 @@ void GroupCall::inviteUsers( } }; - if (_conferenceCall.get()) { + if (_sharedCall.get()) { for (const auto &request : requests) { inviteToConference(request, [=] { return &state->result; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 22de681c1c..8b49ddbfaf 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -41,6 +41,7 @@ namespace Data { struct LastSpokeTimes; struct GroupCallParticipant; class GroupCall; +enum class GroupCallOrigin : uchar; } // namespace Data namespace TdE2E { @@ -255,6 +256,8 @@ public: [[nodiscard]] bool scheduleStartSubscribed() const; [[nodiscard]] bool rtmp() const; [[nodiscard]] bool conference() const; + [[nodiscard]] bool videoStream() const; + [[nodiscard]] Data::GroupCallOrigin origin() const; [[nodiscard]] bool listenersHidden() const; [[nodiscard]] bool emptyRtmp() const; [[nodiscard]] rpl::producer emptyRtmpValue() const; @@ -265,7 +268,7 @@ public: void setRtmpInfo(const Group::RtmpInfo &value); [[nodiscard]] Data::GroupCall *lookupReal() const; - [[nodiscard]] std::shared_ptr conferenceCall() const; + [[nodiscard]] std::shared_ptr sharedCall() const; [[nodiscard]] rpl::producer> real() const; [[nodiscard]] rpl::producer emojiHashValue() const; @@ -671,7 +674,7 @@ private: [[nodiscard]] MTPInputGroupCall inputCallSafe() const; const not_null _delegate; - std::shared_ptr _conferenceCall; + std::shared_ptr _sharedCall; std::unique_ptr _e2e; std::shared_ptr _e2eEncryptDecrypt; rpl::variable _emojiHash; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 7391cc191b..4ee9e08288 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -132,6 +132,7 @@ struct JoinInfo { enum class PanelMode { Default, Wide, + VideoStream, }; enum class VideoQuality { diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 67a82ccb7d..41da9b5483 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -545,8 +545,8 @@ bool Members::Controller::appendWithAccessUsers() { } void Members::Controller::setupWithAccessUsers() { - const auto conference = _call->conferenceCall().get(); - if (!conference) { + const auto conference = _call->sharedCall().get(); + if (!_call->conference() || !conference) { return; } conference->participantsWithAccessValue( @@ -1333,7 +1333,7 @@ base::unique_qptr Members::Controller::createRowContextMenu( } const auto muted = (muteState == Row::State::Muted) || (muteState == Row::State::RaisedHand); - const auto addCover = !_call->rtmp(); + const auto addCover = !_call->rtmp() && !_call->videoStream(); const auto addVolumeItem = (!muted || isMe(participantPeer)); const auto admin = IsGroupCallAdmin(_peer, participantPeer); const auto session = &_peer->session(); @@ -1452,7 +1452,7 @@ base::unique_qptr Members::Controller::createRowContextMenu( } } - if (_call->rtmp()) { + if (_call->rtmp() || _call->videoStream()) { addMuteActionsToContextMenu( result, row->peer(), @@ -1484,8 +1484,9 @@ base::unique_qptr Members::Controller::createRowContextMenu( } else { const auto invited = (muteState == Row::State::Invited) || (muteState == Row::State::Calling); - const auto conference = _call->conferenceCall().get(); - if (conference + const auto conference = _call->sharedCall().get(); + if (_call->conference() + && conference && participantPeer->isUser() && invited) { const auto id = conference->id(); @@ -1645,7 +1646,9 @@ void Members::Controller::addMuteActionsToContextMenu( menu->addAction(std::move(volumeItem)); - if (!_call->rtmp() && !isMe(participantPeer)) { + if (!_call->rtmp() + && !_call->videoStream() + && !isMe(participantPeer)) { menu->addSeparator(); } }; @@ -1655,6 +1658,7 @@ void Members::Controller::addMuteActionsToContextMenu( || muteState == Row::State::Calling || muteState == Row::State::WithAccess || _call->rtmp() + || _call->videoStream() || isMe(participantPeer) || (muteState == Row::State::Inactive && participantIsCallAdmin diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 01e10e296b..34b42db855 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -1071,7 +1071,7 @@ Fn Panel::shareConferenceLinkCallback() { return [=] { Expects(_call->conference()); - ShowConferenceCallLinkBox(uiShow(), _call->conferenceCall(), { + ShowConferenceCallLinkBox(uiShow(), _call->sharedCall(), { .st = DarkConferenceCallLinkStyle(), }); }; @@ -1080,7 +1080,7 @@ Fn Panel::shareConferenceLinkCallback() { void Panel::migrationShowShareLink() { ShowConferenceCallLinkBox( uiShow(), - _call->conferenceCall(), + _call->sharedCall(), { .st = DarkConferenceCallLinkStyle() }); } @@ -1656,7 +1656,7 @@ void Panel::addMembers() { const auto &appConfig = _call->peer()->session().appConfig(); const auto conferenceLimit = appConfig.confcallSizeLimit(); if (_call->conference() - && _call->conferenceCall()->fullCount() >= conferenceLimit) { + && _call->sharedCall()->fullCount() >= conferenceLimit) { uiShow()->showToast({ tr::lng_group_call_invite_limit(tr::now) }); } const auto showToastCallback = [=](TextWithEntities &&text) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp index dc890266ef..30b5b2d9ad 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp @@ -76,7 +76,7 @@ void Viewport::setup() { _content->sizeValue( ) | rpl::filter([=] { - return wide(); + return wide() || videoStream(); }) | rpl::start_with_next([=] { updateTilesGeometry(); }, lifetime()); @@ -106,7 +106,7 @@ void Viewport::setup() { } void Viewport::setGeometry(bool fullscreen, QRect geometry) { - Expects(wide()); + Expects(wide() || videoStream()); const auto changed = (_fullscreen != fullscreen); if (changed) { @@ -122,7 +122,7 @@ void Viewport::setGeometry(bool fullscreen, QRect geometry) { } void Viewport::resizeToWidth(int width) { - Expects(!wide()); + Expects(!wide() && !videoStream()); updateTilesGeometry(width); } @@ -139,6 +139,10 @@ bool Viewport::wide() const { return (_mode == PanelMode::Wide); } +bool Viewport::videoStream() const { + return (_mode == PanelMode::VideoStream); +} + void Viewport::setMode(PanelMode mode, not_null parent) { if (_mode == mode && widget()->parent() == parent) { return; @@ -153,7 +157,7 @@ void Viewport::setMode(PanelMode mode, not_null parent) { widget()->show(); } } - if (!wide()) { + if (!wide() && !videoStream()) { for (const auto &tile : _tiles) { tile->toggleTopControlsShown(false); } @@ -173,7 +177,9 @@ void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) { setPressed({}); if (const auto tile = pressed.tile) { if (pressed == _selected) { - if (button == Qt::RightButton) { + if (videoStream()) { + return; + } else if (button == Qt::RightButton) { tile->row()->showContextMenu(); } else if (!wide() || (_hasTwoOrMore && !_large) @@ -584,7 +590,7 @@ void Viewport::updateTilesGeometry(int outerWidth) { return; } - if (wide()) { + if (wide() || videoStream()) { updateTilesGeometryWide(outerWidth, outerHeight); refreshHasTwoOrMore(); _fullHeight = 0; @@ -777,8 +783,10 @@ void Viewport::setTileGeometry(not_null tile, QRect geometry) { const auto kSmall = style::ConvertScale(240); const auto &endpoint = tile->endpoint(); const auto forceThumbnailQuality = !wide() + && !videoStream() && (ranges::count(_tiles, false, &VideoTile::hidden) > 1); - const auto forceFullQuality = wide() && (tile.get() == _large); + const auto forceFullQuality = videoStream() + || (wide() && (tile.get() == _large)); const auto quality = forceThumbnailQuality ? VideoQuality::Thumbnail : (forceFullQuality || min >= kMedium) diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.h b/Telegram/SourceFiles/calls/group/calls_group_viewport.h index 9d5021f85a..7c7b9a705a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.h @@ -140,6 +140,7 @@ private: void setup(); [[nodiscard]] bool wide() const; + [[nodiscard]] bool videoStream() const; void updateCursor(); void updateTilesGeometry(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp index 894d846d0a..ccb468f6fd 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp @@ -443,7 +443,7 @@ void Viewport::RendererGL::paint( } std::optional Viewport::RendererGL::clearColor() { - return st::groupCallBg->c; + return _owner->videoStream() ? st::mediaviewBg->c : st::groupCallBg->c; } void Viewport::RendererGL::validateUserpicFrame( @@ -784,12 +784,12 @@ void Viewport::RendererGL::paintTile( program->setUniformValue("viewport", uniformViewport); program->setUniformValue( "frameBg", - fullscreen ? QColor(0, 0, 0) : st::groupCallBg->c); + fullscreen ? QColor(0, 0, 0) : *clearColor()); program->setUniformValue("radiusOutline", QVector2D( GLfloat(st::roundRadiusLarge * _factor * (fullscreen ? 0. : 1.)), (outline > 0) ? (st::groupCallOutline * _factor) : 0.f)); program->setUniformValue("roundRect", Uniform(rect)); - program->setUniformValue("roundBg", st::groupCallBg->c); + program->setUniformValue("roundBg", *clearColor()); program->setUniformValue("outlineFg", QVector4D( st::groupCallMemberActiveIcon->c.redF(), st::groupCallMemberActiveIcon->c.greenF(), diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp index f1414263ca..23761fc58f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp @@ -53,7 +53,11 @@ void Viewport::RendererSW::paintFallback( paintTile(p, tile.get(), bounding, bg); } const auto fullscreen = _owner->_fullscreen; - const auto color = fullscreen ? QColor(0, 0, 0) : st::groupCallBg->c; + const auto color = fullscreen + ? QColor(0, 0, 0) + : _owner->videoStream() + ? st::mediaviewBg->c + : st::groupCallBg->c; for (const auto &rect : bg) { p.fillRect(rect, color); } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 4f3a59d691..f348378de0 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1099,7 +1099,7 @@ void ChannelData::setGroupCall( data.vaccess_hash().v, scheduleDate, rtmp, - false); // conference + Data::GroupCallOrigin::Group); owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 288e0807c9..d453643732 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -235,7 +235,7 @@ void ChatData::setGroupCall( data.vaccess_hash().v, scheduleDate, rtmp, - false); // conference + Data::GroupCallOrigin::Group); owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 6ac868018a..9b2f123555 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -63,7 +63,7 @@ GroupCall::GroupCall( uint64 accessHash, TimeId scheduleDate, bool rtmp, - bool conference) + GroupCallOrigin origin) : _id(id) , _accessHash(accessHash) , _peer(peer) @@ -71,7 +71,8 @@ GroupCall::GroupCall( , _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) , _scheduleDate(scheduleDate) , _rtmp(rtmp) -, _conference(conference) +, _conference(origin == GroupCallOrigin::Conference) +, _videoStream(origin == GroupCallOrigin::VideoStream) , _listenersHidden(rtmp) { if (_conference) { session().data().registerGroupCall(this); @@ -99,11 +100,13 @@ GroupCall::GroupCall( requestParticipants(); } }, _checkStaleLifetime); + } else if (_videoStream) { + session().data().registerGroupCall(this); } } GroupCall::~GroupCall() { - if (_conference) { + if (_conference || _videoStream) { session().data().unregisterGroupCall(this); } api().request(_unknownParticipantPeersRequestId).cancel(); @@ -127,6 +130,14 @@ bool GroupCall::rtmp() const { return _rtmp; } +GroupCallOrigin GroupCall::origin() const { + return _conference + ? GroupCallOrigin::Conference + : _videoStream + ? GroupCallOrigin::VideoStream + : GroupCallOrigin::Group; +} + bool GroupCall::canManage() const { return _conference ? _creator : _peer->canManageGroupCall(); } diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 23e347f174..27615f83c9 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -60,6 +60,12 @@ struct GroupCallParticipant { [[nodiscard]] bool screenPaused() const; }; +enum class GroupCallOrigin : uchar { + Group, + Conference, + VideoStream, +}; + class GroupCall final { public: GroupCall( @@ -68,7 +74,7 @@ public: uint64 accessHash, TimeId scheduleDate, bool rtmp, - bool conference); + GroupCallOrigin origin); ~GroupCall(); [[nodiscard]] Main::Session &session() const; @@ -76,6 +82,7 @@ public: [[nodiscard]] CallId id() const; [[nodiscard]] bool loaded() const; [[nodiscard]] bool rtmp() const; + [[nodiscard]] GroupCallOrigin origin() const; [[nodiscard]] bool canManage() const; [[nodiscard]] bool listenersHidden() const; [[nodiscard]] bool blockchainMayBeEmpty() const; @@ -292,6 +299,7 @@ private: bool _applyingQueuedUpdates : 1 = false; bool _rtmp : 1 = false; bool _conference : 1 = false; + bool _videoStream : 1 = false; bool _listenersHidden : 1 = false; }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 2325202eb8..34f946f9ac 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1250,7 +1250,7 @@ std::shared_ptr Session::sharedConferenceCall( accessHash, TimeId(), // scheduledDate false, // rtmp - true); // conference + GroupCallOrigin::Conference); if (i != end(_conferenceCalls)) { i->second = result; } else { diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 5d7c335194..31ed05c806 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_document.h" +#include "data/data_group_call.h" #include "data/data_folder.h" #include "data/data_photo.h" #include "data/data_user.h" @@ -49,12 +50,13 @@ constexpr auto kPollingViewsPerPage = Story::kRecentViewersMax; using UpdateFlag = StoryUpdate::Flag; [[nodiscard]] std::optional ParseMedia( - not_null owner, - const MTPMessageMedia &media) { + not_null peer, + const MTPMessageMedia &media, + const std::shared_ptr &existingCall) { return media.match([&](const MTPDmessageMediaPhoto &data) -> std::optional { if (const auto photo = data.vphoto()) { - const auto result = owner->processPhoto(*photo); + const auto result = peer->owner().processPhoto(*photo); if (!result->isNull()) { return StoryMedia{ result }; } @@ -63,7 +65,7 @@ using UpdateFlag = StoryUpdate::Flag; }, [&](const MTPDmessageMediaDocument &data) -> std::optional { if (const auto document = data.vdocument()) { - const auto result = owner->processDocument( + const auto result = peer->owner().processDocument( *document, data.valt_documents()); if (!result->isNull() @@ -73,6 +75,24 @@ using UpdateFlag = StoryUpdate::Flag; } } return {}; + }, [&](const MTPDmessageMediaVideoStream &data) { + return std::make_optional(data.vcall().match([&]( + const MTPDinputGroupCall &data) { + auto call = (existingCall + && existingCall->peer() == peer + && existingCall->id() == data.vid().v) + ? existingCall + : std::make_shared( + peer, + data.vid().v, + data.vaccess_hash().v, + TimeId(), + false, // rtmp + GroupCallOrigin::VideoStream); + return StoryMedia{ std::move(call) }; + }, [](const auto &) { + return StoryMedia(); + })); }, [&](const MTPDmessageMediaUnsupported &data) { return std::make_optional(StoryMedia{ v::null }); }, [](const auto &) { return std::optional(); }); @@ -494,7 +514,14 @@ Story *Stories::parseAndApply( not_null peer, const MTPDstoryItem &data, TimeId now) { - const auto media = ParseMedia(_owner, data.vmedia()); + const auto id = data.vid().v; + const auto fullId = FullStoryId{ peer->id, id }; + auto &stories = _stories[peer->id]; + const auto i = stories.find(id); + const auto &existingCall = (i != end(stories)) + ? i->second->call() + : nullptr; + const auto media = ParseMedia(peer, data.vmedia(), existingCall); if (!media) { return nullptr; } @@ -503,7 +530,6 @@ Story *Stories::parseAndApply( if (expired && !data.is_pinned() && !hasArchive(peer)) { return nullptr; } - const auto id = data.vid().v; auto albumInfo = (Albums*)nullptr; auto list = std::optional>(); if (const auto albums = data.valbums()) { @@ -517,9 +543,6 @@ Story *Stories::parseAndApply( } } } - const auto fullId = FullStoryId{ peer->id, id }; - auto &stories = _stories[peer->id]; - const auto i = stories.find(id); if (i != end(stories)) { const auto result = i->second.get(); const auto mediaChanged = (result->media() != *media); diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index c8ea41036b..ecef1f9de5 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" #include "data/data_story.h" +namespace Data { +class GroupCall; +} // namespace Data + namespace Main { class Session; } // namespace Main diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 1c92ecaf0d..c90b2418a2 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -316,11 +316,20 @@ DocumentData *Story::document() const { return result ? result->get() : nullptr; } +const std::shared_ptr &Story::call() const { + const auto result = std::get_if>( + &_media.data); + static const auto empty = std::shared_ptr(); + return result ? *result : empty; +} + bool Story::hasReplyPreview() const { return v::match(_media.data, [](not_null photo) { return !photo->isNull(); }, [](not_null document) { return document->hasThumbnail(); + }, [](const std::shared_ptr &call) { + return false; }, [](v::null_t) { return false; }); @@ -331,6 +340,8 @@ Image *Story::replyPreview() const { return photo->getReplyPreview(fullId(), _peer, false); }, [&](not_null document) { return document->getReplyPreview(fullId(), _peer, false); + }, [](const std::shared_ptr &call) { + return (Image*)nullptr; }, [](v::null_t) { return (Image*)nullptr; }); diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index bcb425c2ca..7836838882 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -21,6 +21,7 @@ class Session; namespace Data { +class GroupCall; class Session; class Thread; class MediaPreload; @@ -54,7 +55,8 @@ struct StoryMedia { std::variant< v::null_t, not_null, - not_null> data; + not_null, + std::shared_ptr> data; friend inline bool operator==(StoryMedia, StoryMedia) = default; }; @@ -170,6 +172,7 @@ public: [[nodiscard]] const StoryMedia &media() const; [[nodiscard]] PhotoData *photo() const; [[nodiscard]] DocumentData *document() const; + [[nodiscard]] const std::shared_ptr &call() const; [[nodiscard]] bool hasReplyPreview() const; [[nodiscard]] Image *replyPreview() const; diff --git a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp index 6e4947ece5..5739b4c0b8 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp @@ -275,6 +275,8 @@ void Sibling::checkStory() { _loader = std::make_unique(document, origin, [=] { check(); }); + }, [&](const std::shared_ptr &call) { + _loader = nullptr; }, [&](v::null_t) { _loader = nullptr; }); diff --git a/Telegram/SourceFiles/media/view/media_view_open_common.h b/Telegram/SourceFiles/media/view/media_view_open_common.h index f512ca45b3..c65b52838d 100644 --- a/Telegram/SourceFiles/media/view/media_view_open_common.h +++ b/Telegram/SourceFiles/media/view/media_view_open_common.h @@ -81,6 +81,17 @@ public: , _storiesContext(context) { } + OpenRequest( + Window::SessionController *controller, + std::shared_ptr call, + QString linkSlug, + MsgId joinMessageId) + : _controller(controller) + , _call(std::move(call)) + , _callLinkSlug(std::move(linkSlug)) + , _callJoinMessageId(joinMessageId) { + } + [[nodiscard]] PeerData *peer() const { return _peer; } @@ -111,6 +122,16 @@ public: return _storiesContext; } + [[nodiscard]] const std::shared_ptr &call() const { + return _call; + } + [[nodiscard]] const QString &callLinkSlug() const { + return _callLinkSlug; + } + [[nodiscard]] MsgId callJoinMessageId() const { + return _callJoinMessageId; + } + [[nodiscard]] std::optional cloudTheme() const { return _cloudTheme; } @@ -141,6 +162,10 @@ private: bool _continueStreaming = false; crl::time _startTime = 0; + std::shared_ptr _call; + QString _callLinkSlug; + MsgId _callJoinMessageId = 0; + }; [[nodiscard]] TimeId ExtractVideoTimestamp(not_null item); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 2fe3cabb16..bc0c354eec 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "menu/menu_sponsored.h" #include "boxes/premium_preview_box.h" +#include "calls/calls_instance.h" #include "core/application.h" #include "core/click_handler_types.h" #include "core/file_utilities.h" @@ -52,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_overlay_raster.h" #include "media/view/media_view_overlay_opengl.h" #include "media/view/media_view_playback_sponsored.h" +#include "media/view/media_view_video_stream.h" #include "media/stories/media_stories_share.h" #include "media/stories/media_stories_view.h" #include "media/streaming/media_streaming_document.h" @@ -62,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "data/components/sponsored_messages.h" +#include "data/data_group_call.h" #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_channel.h" @@ -90,7 +93,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_overlay_widget.h" #include "storage/file_download.h" #include "storage/storage_account.h" -#include "calls/calls_instance.h" #include "styles/style_media_view.h" #include "styles/style_calls.h" #include "styles/style_chat.h" @@ -2095,6 +2097,16 @@ void OverlayWidget::resizeContentByScreenSize() { _h = content.height(); _zoom = 0; updateNavigationControlsGeometry(); + if (_videoStream) { + _videoStream->updateGeometry(_x, _y, _w, _h); + } + return; + } else if (_videoStream) { + _w = _body->width() / 2; + _h = _body->height() / 2; + _x = (_body->width() - _w) / 2; + _y = (_body->height() - _h) / 2; + _videoStream->updateGeometry(_x, _y, _w, _h); return; } recountSkipTop(); @@ -2333,6 +2345,7 @@ void OverlayWidget::assignMediaPointer(DocumentData *document) { _flip = {}; _photo = nullptr; _photoMedia = nullptr; + _videoStream = nullptr; if (_document != document) { _streamedQualityChangeFrame = QImage(); _streamedQualityChangeFinished = false; @@ -2371,6 +2384,7 @@ void OverlayWidget::assignMediaPointer(not_null photo) { _documentLoadingTo = QString(); _videoCover = nullptr; _videoCoverMedia = nullptr; + _videoStream = nullptr; if (_photo != photo) { _flip = {}; _photo = photo; @@ -2382,6 +2396,32 @@ void OverlayWidget::assignMediaPointer(not_null photo) { } } +void OverlayWidget::assignMediaPointer( + std::shared_ptr call) { + _savePhotoVideoWhenLoaded = SavePhotoVideo::None; + _chosenQuality = nullptr; + _streamedQualityChangeFrame = QImage(); + _streamedQualityChangeFinished = false; + _document = nullptr; + _documentMedia = nullptr; + _documentLoadingTo = QString(); + _videoCover = nullptr; + _videoCoverMedia = nullptr; + _flip = {}; + _photo = nullptr; + _photoMedia = nullptr; + + _videoStream = nullptr; + _videoStream = std::make_unique( + _body, + _wrap->backend(), + uiShow(), + std::move(call), + _callLinkSlug, + _callJoinMessageId); + _widget->lower(); +} + void OverlayWidget::clickHandlerActiveChanged( const ClickHandlerPtr &p, bool active) { @@ -3550,6 +3590,7 @@ void OverlayWidget::show(OpenRequest request) { const auto story = request.story(); const auto document = story ? story->document() : request.document(); const auto photo = story ? story->photo() : request.photo(); + const auto call = story ? story->call() : request.call(); const auto contextItem = request.item(); const auto contextPeer = request.peer(); const auto contextTopicRootId = request.topicRootId(); @@ -3600,8 +3641,12 @@ void OverlayWidget::show(OpenRequest request) { displayPhoto(photo); preloadData(0); activateControls(); - } else if (story || document) { - setSession(document ? &document->session() : &story->session()); + } else if (story || document || call) { + setSession(document + ? &document->session() + : story + ? &story->session() + : &call->session()); if (story) { setContext(StoriesContext{ @@ -3618,13 +3663,19 @@ void OverlayWidget::show(OpenRequest request) { clearControlsState(); _streamingStartPaused = false; - displayDocument( - document, - anim::activation::normal, - request.cloudTheme() - ? *request.cloudTheme() - : Data::CloudTheme(), - { request.continueStreaming(), request.startTime() }); + if (call) { + _callLinkSlug = request.callLinkSlug(); + _callJoinMessageId = request.callJoinMessageId(); + displayVideoStream(call, anim::activation::normal); + } else { + displayDocument( + document, + anim::activation::normal, + (request.cloudTheme() + ? *request.cloudTheme() + : Data::CloudTheme()), + { request.continueStreaming(), request.startTime() }); + } if (!isHidden()) { preloadData(0); activateControls(); @@ -3840,6 +3891,26 @@ void OverlayWidget::displayDocument( } } +// Empty messages shown as docs: doc can be nullptr. +void OverlayWidget::displayVideoStream( + const std::shared_ptr &call, + anim::activation activation) { + _fullScreenVideo = false; + _staticContent = QImage(); + clearStreaming(true); + destroyThemePreview(); + assignMediaPointer(call); + + _rotation = 0; + _radial.stop(); + + _touchbarDisplay.fire(TouchBarItemType::None); + + contentSizeChanged(); + _blurred = false; + displayFinished(activation); +} + void OverlayWidget::initSponsoredButton() { const auto has = _message && _message->isSponsored() && _session; if (has && _sponsoredButton) { @@ -4754,6 +4825,8 @@ void OverlayWidget::storiesJumpTo( displayPhoto(photo, anim::activation::background); }, [&](not_null document) { displayDocument(document, anim::activation::background); + }, [&](const std::shared_ptr &call) { + displayVideoStream(call, anim::activation::background); }, [&](v::null_t) { displayDocument(nullptr, anim::activation::background); }); @@ -4768,6 +4841,8 @@ void OverlayWidget::storiesRedisplay(not_null story) { displayPhoto(photo, anim::activation::background); }, [&](not_null document) { displayDocument(document, anim::activation::background); + }, [&](const std::shared_ptr &call) { + displayVideoStream(call, anim::activation::background); }, [&](v::null_t) { displayDocument(nullptr, anim::activation::background); }); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 2ec83512be..f9996cecd4 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -27,7 +27,12 @@ namespace anim { enum class activation : uchar; } // namespace anim +namespace Calls { +class GroupCall; +} // namespace Calls + namespace Data { +class GroupCall; class PhotoMedia; class DocumentMedia; struct StoriesContext; @@ -78,6 +83,7 @@ struct ContentLayout; namespace Media::View { +class VideoStream; class PlaybackSponsored; class GroupThumbs; class Pip; @@ -299,6 +305,7 @@ private: void assignMediaPointer(DocumentData *document); void assignMediaPointer(not_null photo); + void assignMediaPointer(std::shared_ptr call); void updateOver(QPoint mpos); void initFullScreen(); @@ -385,6 +392,9 @@ private: anim::activation activation = anim::activation::normal, const Data::CloudTheme &cloud = Data::CloudTheme(), const StartStreaming &startStreaming = StartStreaming()); + void displayVideoStream( + const std::shared_ptr &call, + anim::activation activation = anim::activation::normal); void displayFinished(anim::activation activation); void redisplayContent(); void findCurrent(); @@ -647,6 +657,10 @@ private: rpl::event_stream _storiesStickerOrEmojiChosen; std::unique_ptr _layerBg; + std::unique_ptr _videoStream; + QString _callLinkSlug; + MsgId _callJoinMessageId; + const style::icon *_docIcon = nullptr; style::color _docIconColor; QString _docName, _docSize, _docExt; diff --git a/Telegram/SourceFiles/media/view/media_view_video_stream.cpp b/Telegram/SourceFiles/media/view/media_view_video_stream.cpp new file mode 100644 index 0000000000..0dea56058f --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_video_stream.cpp @@ -0,0 +1,180 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "media/view/media_view_video_stream.h" + +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_common.h" +#include "calls/group/calls_group_members.h" +#include "calls/group/calls_group_viewport.h" +#include "calls/calls_instance.h" +#include "chat_helpers/compose/compose_show.h" +#include "core/application.h" + +namespace Media::View { + +class VideoStream::Delegate final : public Calls::GroupCall::Delegate { +public: + Delegate(); + + void groupCallFinished(not_null call) override; + void groupCallFailed(not_null call) override; + void groupCallRequestPermissionsOrFail(Fn onSuccess) override; + void groupCallPlaySound(GroupCallSound sound) override; + auto groupCallGetVideoCapture(const QString &deviceId) + -> std::shared_ptr override; + FnMut groupCallAddAsyncWaiter() override; + +private: + +}; + +VideoStream::Delegate::Delegate() { +} + +void VideoStream::Delegate::groupCallFinished( + not_null call) { + // #TODO videostreams +} + +void VideoStream::Delegate::groupCallFailed(not_null call) { + // #TODO videostreams +} + +void VideoStream::Delegate::groupCallRequestPermissionsOrFail( + Fn onSuccess) { +} + +void VideoStream::Delegate::groupCallPlaySound(GroupCallSound sound) { +} + +auto VideoStream::Delegate::groupCallGetVideoCapture(const QString &deviceId) +-> std::shared_ptr { + return nullptr; +} + +FnMut VideoStream::Delegate::groupCallAddAsyncWaiter() { + // #TODO videostreams + return [] {}; +} + +VideoStream::VideoStream( + not_null parent, + Ui::GL::Backend backend, + std::shared_ptr show, + std::shared_ptr call, + QString callLinkSlug, + MsgId callJoinMessageId) +: _show(std::move(show)) +, _delegate(std::make_unique()) +, _call(std::make_unique( + _delegate.get(), + Calls::StartConferenceInfo{ + .show = _show, + .call = std::move(call), + .linkSlug = callLinkSlug, + .joinMessageId = callJoinMessageId, + })) +, _members( + std::make_unique( + parent, + _call.get(), + Calls::Group::PanelMode::VideoStream, + backend)) +, _viewport( + std::make_unique( + parent, + Calls::Group::PanelMode::VideoStream, + backend)) { + Core::App().calls().registerVideoStream(_call.get()); + setupMembers(); + setupVideo(); +} + +VideoStream::~VideoStream() { +} + +void VideoStream::updateGeometry(int x, int y, int width, int height) { + _viewport->setGeometry(false, { x, y, width, height }); +} + +void VideoStream::setupMembers() { +} + +void VideoStream::setupVideo() { + const auto setupTile = [=]( + const Calls::VideoEndpoint &endpoint, + const std::unique_ptr &track) { + using namespace rpl::mappers; + const auto row = endpoint.rtmp() + ? _members->rtmpFakeRow(Calls::GroupCall::TrackPeer(track)).get() + : _members->lookupRow(Calls::GroupCall::TrackPeer(track)); + Assert(row != nullptr); + + auto pinned = rpl::combine( + _call->videoEndpointLargeValue(), + _call->videoEndpointPinnedValue() + ) | rpl::map(_1 == endpoint && _2); + const auto self = (endpoint.peer == _call->joinAs()); + _viewport->add( + endpoint, + Calls::Group::VideoTileTrack{ + Calls::GroupCall::TrackPointer(track), + row, + }, + Calls::GroupCall::TrackSizeValue(track), + std::move(pinned), + self); + }; + for (const auto &[endpoint, track] : _call->activeVideoTracks()) { + setupTile(endpoint, track); + } + _call->videoStreamActiveUpdates( + ) | rpl::start_with_next([=](const Calls::VideoStateToggle &update) { + if (update.value) { + // Add async (=> the participant row is definitely in Members). + const auto endpoint = update.endpoint; + crl::on_main(_viewport->widget(), [=] { + const auto &tracks = _call->activeVideoTracks(); + const auto i = tracks.find(endpoint); + if (i != end(tracks)) { + setupTile(endpoint, i->second); + } + }); + } else { + // Remove sync. + _viewport->remove(update.endpoint); + } + }, _viewport->lifetime()); + + _viewport->pinToggled( + ) | rpl::start_with_next([=](bool pinned) { + _call->pinVideoEndpoint(pinned + ? _call->videoEndpointLarge() + : Calls::VideoEndpoint{}); + }, _viewport->lifetime()); + + _viewport->clicks( + ) | rpl::start_with_next([=](Calls::VideoEndpoint &&endpoint) { + if (_call->videoEndpointLarge() == endpoint) { + _call->showVideoEndpointLarge({}); + } else if (_call->videoEndpointPinned()) { + _call->pinVideoEndpoint(std::move(endpoint)); + } else { + _call->showVideoEndpointLarge(std::move(endpoint)); + } + }, _viewport->lifetime()); + + _viewport->qualityRequests( + ) | rpl::start_with_next([=](const Calls::VideoQualityRequest &request) { + _call->requestVideoQuality(request.endpoint, request.quality); + }, _viewport->lifetime()); + + _viewport->widget()->lower(); +} + +} // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_video_stream.h b/Telegram/SourceFiles/media/view/media_view_video_stream.h new file mode 100644 index 0000000000..64aed212ea --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_video_stream.h @@ -0,0 +1,60 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Calls { +class GroupCall; +} // namespace Calls + +namespace Calls::Group { +class Members; +class Viewport; +} // namespace Calls::Group + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Data { +class GroupCall; +} // namespace Data + +namespace Ui::GL { +enum class Backend; +} // namespace Ui::GL + +namespace Media::View { + +class VideoStream final { +public: + VideoStream( + not_null parent, + Ui::GL::Backend backend, + std::shared_ptr show, + std::shared_ptr call, + QString callLinkSlug, + MsgId callJoinMessageId); + ~VideoStream(); + + void updateGeometry(int x, int y, int width, int height); + +private: + class Delegate; + + void setupVideo(); + void setupMembers(); + + std::shared_ptr _show; + std::unique_ptr _delegate; + std::unique_ptr _call; + std::unique_ptr _members; + std::unique_ptr _viewport; + +}; + +} // namespace Media::View diff --git a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp index 32f164776f..4f2a35043c 100644 --- a/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp +++ b/Telegram/SourceFiles/ui/dynamic_thumbnails.cpp @@ -661,6 +661,8 @@ std::shared_ptr MakeStoryThumbnail( const auto id = story->fullId(); return v::match(story->media().data, [](v::null_t) -> Result { return std::make_shared(); + }, [](const std::shared_ptr &call) -> Result { + return std::make_shared(); }, [&](not_null photo) -> Result { return std::make_shared(photo, id, true); }, [&](not_null video) -> Result { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index d1956026d2..aaa5e390ab 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -981,11 +981,18 @@ void SessionNavigation::resolveConferenceCall( if (call->fullCount() >= conferenceLimit) { showToast(tr::lng_confcall_participants_limit(tr::now)); } else { - Core::App().calls().startOrJoinConferenceCall({ - .call = call, - .linkSlug = slug, - .joinMessageId = inviteMsgId, - }); + parentController()->window().openInMediaView( + Media::View::OpenRequest( + parentController(), + call, + slug, + inviteMsgId)); + AssertIsDebug(); + //Core::App().calls().startOrJoinConferenceCall({ + // .call = call, + // .linkSlug = slug, + // .joinMessageId = inviteMsgId, + //}); close(); } };