diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 15ecfd33c090d8..2c5c32e738b68a 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1008,6 +1008,7 @@ groupCallScreenShareSmall: CallButton(groupCallSettingsSmall) { rippleAreaPosition: point(8px, 12px); } } + groupCallMenuToggleSmall: CallButton(groupCallSettingsSmall) { button: IconButton(groupCallSettingsInner) { icon: icon {{ "calls/calls_more", groupCallIconFg }}; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 91b46b066554f7..44b5a5b414a4c2 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -520,7 +520,12 @@ rpl::producer Call::captureMuteDeviceId() { void Call::setMuted(bool mute) { _muted = mute; - if (_instance) { + if (_screenWithAudio && _screenAudioControl) { + _screenAudioControl->setMicrophoneMuted(mute); + if (_instance) { + _instance->setMuteMicrophone(false); + } + } else if (_instance) { _instance->setMuteMicrophone(mute); } } @@ -1080,6 +1085,13 @@ void Call::createAndStartController(const MTPDphoneCall &call) { }); }; + _screenAudioControl = std::make_shared(); + auto admCreator = Webrtc::AudioDeviceModuleCreator( + saveSetDeviceIdCallback); + auto audioDeviceModuleCreator = Webrtc::MixingAudioDeviceModuleCreator( + std::move(admCreator), + _screenAudioControl); + tgcalls::Descriptor descriptor = { .version = versionString, .config = tgcalls::Config{ @@ -1139,8 +1151,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) { sendSignalingData(bytes); }); }, - .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( - saveSetDeviceIdCallback), + .createAudioDeviceModule = std::move(audioDeviceModuleCreator), }; if (Logs::DebugEnabled()) { const auto callLogFolder = cWorkingDir() + u"DebugLogs"_q; @@ -1381,6 +1392,48 @@ void Call::setState(State state) { // } //} +void Call::setPlaybackVolume(int volume) { + _playbackVolume = std::clamp(volume, 0, 20000); + const auto level = _playbackMuted.current() + ? 0.f + : (_playbackVolume.current() / 10000.f); + if (_instance) { + _instance->setOutputVolume(level); + } + if (_screenAudioControl) { + _screenAudioControl->setPlaybackVolume(level); + } +} + +int Call::playbackVolume() const { + return _playbackVolume.current(); +} + +void Call::setPlaybackMuted(bool muted) { + _playbackMuted = muted; + const auto level = muted + ? 0.f + : (_playbackVolume.current() / 10000.f); + if (_instance) { + _instance->setOutputVolume(level); + } + if (_screenAudioControl) { + _screenAudioControl->setPlaybackVolume(level); + } +} + +bool Call::playbackMuted() const { + return _playbackMuted.current(); +} + +rpl::producer Call::playbackVolumeValue() const { + return _playbackVolume.value(); +} + +rpl::producer Call::playbackMutedValue() const { + return _playbackMuted.value(); +} + void Call::setAudioDuckingEnabled(bool enabled) { if (_instance) { _instance->setAudioOutputDuckingEnabled(enabled); @@ -1399,6 +1452,10 @@ bool Call::isSharingScreen() const { return _videoCaptureIsScreencast && isSharingVideo(); } +bool Call::screenSharingWithAudio() const { + return isSharingScreen() && _screenWithAudio; +} + QString Call::cameraSharingDeviceId() const { return isSharingCamera() ? _videoCaptureDeviceId : QString(); } @@ -1445,6 +1502,13 @@ void Call::toggleScreenSharing( } _videoCaptureDeviceId = QString(); _videoCaptureIsScreencast = false; + if (_screenWithAudio && _screenAudioControl) { + _screenAudioControl->setMicrophoneMuted(false); + _screenAudioControl->setLoopbackEnabled(false); + if (_muted.current() && _instance) { + _instance->setMuteMicrophone(true); + } + } _screenWithAudio = false; if (_systemAudioCapture) { _systemAudioCapture->stop(); @@ -1459,6 +1523,15 @@ void Call::toggleScreenSharing( _videoCaptureIsScreencast = true; _videoCaptureDeviceId = *uniqueId; _screenWithAudio = withAudio; + if (_screenAudioControl) { + _screenAudioControl->setLoopbackEnabled(withAudio); + if (withAudio && _muted.current()) { + _screenAudioControl->setMicrophoneMuted(true); + if (_instance) { + _instance->setMuteMicrophone(false); + } + } + } if (_videoCapture) { _videoCapture->switchToDevice(uniqueId->toStdString(), true); if (_instance) { @@ -1647,6 +1720,7 @@ void Call::handleControllerError(const QString &error) { } void Call::destroyController() { + _screenAudioControl = nullptr; _instanceLifetime.destroy(); Core::App().mediaDevices().setCaptureMuteTracker(this, false); if (_systemAudioCapture) { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index d40d0794c40e69..abba5cbb3849f8 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -37,10 +37,10 @@ namespace Webrtc { enum class VideoState; class VideoTrack; struct DeviceResolvedId; +class MixingAudioControl; } // namespace Webrtc namespace Calls { - struct StartConferenceInfo; struct DhConfig { @@ -243,6 +243,13 @@ class Call final //void setAudioVolume(bool input, float level); void setAudioDuckingEnabled(bool enabled); + void setPlaybackVolume(int volume); + [[nodiscard]] int playbackVolume() const; + void setPlaybackMuted(bool muted); + [[nodiscard]] bool playbackMuted() const; + [[nodiscard]] rpl::producer playbackVolumeValue() const; + [[nodiscard]] rpl::producer playbackMutedValue() const; + [[nodiscard]] QString videoDeviceId() const { return _videoCaptureDeviceId; } @@ -250,15 +257,14 @@ class Call final [[nodiscard]] bool isSharingVideo() const; [[nodiscard]] bool isSharingCamera() const; [[nodiscard]] bool isSharingScreen() const; + [[nodiscard]] bool screenSharingWithAudio() const; [[nodiscard]] QString cameraSharingDeviceId() const; [[nodiscard]] QString screenSharingDeviceId() const; void toggleCameraSharing(bool enabled); void toggleScreenSharing( std::optional uniqueId, bool withAudio = false); - [[nodiscard]] bool screenSharingWithAudio() const { - return _screenWithAudio; - } + [[nodiscard]] auto peekVideoCapture() const -> std::shared_ptr; @@ -369,11 +375,14 @@ class Call final std::vector> _conferenceParticipants; std::unique_ptr _instance; + std::shared_ptr _screenAudioControl; std::shared_ptr _videoCapture; QString _videoCaptureDeviceId; bool _videoCaptureIsScreencast = false; bool _screenWithAudio = false; std::unique_ptr _systemAudioCapture; + rpl::variable _playbackVolume = 10000; + rpl::variable _playbackMuted = false; const std::unique_ptr _videoIncoming; const std::unique_ptr _videoOutgoing; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 082cedb1b6c0a5..892303386c0e49 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -7,7 +7,12 @@ For license and copyright information please follow this link: */ #include "calls/calls_panel.h" +#include "ui/widgets/checkbox.h" +#include "webrtc/webrtc_create_adm.h" + #include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge +#include "calls/group/calls_volume_item.h" +#include "calls/group/calls_group_common.h" #include "calls/calls_panel_background.h" #include "data/data_photo.h" #include "data/data_session.h" @@ -16,7 +21,6 @@ For license and copyright information please follow this link: #include "data/data_photo_media.h" #include "data/data_cloud_file.h" #include "data/data_changes.h" -#include "calls/group/calls_group_common.h" #include "calls/group/calls_group_invite_controller.h" #include "calls/ui/calls_device_menu.h" #include "calls/calls_emoji_fingerprint.h" @@ -382,6 +386,21 @@ void Panel::initWidget() { ) | rpl::skip(1) | rpl::on_next([=] { updateControlsGeometry(); }, lifetime()); + + base::install_event_filter(widget(), widget(), [=]( + not_null e) { + if (e->type() == QEvent::ContextMenu) { + const auto event = static_cast(e.get()); + const auto pos = event->pos(); + if (_incoming + && _incoming->widget()->isVisible() + && incomingFrameGeometry().contains(pos)) { + showIncomingVolumeMenu(event->globalPos()); + return base::EventFilterResult::Cancel; + } + } + return base::EventFilterResult::Continue; + }); } void Panel::initControls() { @@ -1056,6 +1075,71 @@ void Panel::initMediaDeviceToggles() { _audioDeviceToggle->setAccessibleName(tr::lng_settings_call_section_output(tr::now)); } +void Panel::showIncomingVolumeMenu(QPoint globalPos) { + if (!_call || _incomingVolumeMenu) { + return; + } + + _incomingVolumeMenu = base::make_unique_q( + widget(), + st::groupCallPopupMenuWithVolume); + + const auto menu = _incomingVolumeMenu.get(); + const auto call = _call; + const auto peer = _user; + const auto startVolume = call->playbackVolume(); + const auto startMuted = call->playbackMuted(); + + auto stateProducer = rpl::combine( + call->playbackVolumeValue(), + call->playbackMutedValue() + ) | rpl::map([peer](int volume, bool muted) { + return Group::ParticipantState{ + .peer = peer, + .volume = volume, + .mutedByMe = muted, + }; + }); + + auto volumeItem = base::make_unique_q( + menu->menu(), + st::groupCallPopupVolumeMenu, + st::groupCallMenuVolumeSlider, + std::move(stateProducer), + startVolume, + Group::kMaxVolume, + startMuted, + st::groupCallMenuVolumePadding); + + volumeItem->toggleMuteRequests( + ) | rpl::on_next([=](bool muted) { + call->setPlaybackMuted(muted); + if (muted) { + crl::on_main(menu, [=] { + menu->hideMenu(); + }); + } + }, volumeItem->lifetime()); + + volumeItem->toggleMuteLocallyRequests( + ) | rpl::on_next([=](bool muted) { + call->setPlaybackMuted(muted); + }, volumeItem->lifetime()); + + volumeItem->changeVolumeRequests( + ) | rpl::on_next([=](int volume) { + call->setPlaybackVolume(volume); + }, volumeItem->lifetime()); + + volumeItem->changeVolumeLocallyRequests( + ) | rpl::on_next([=](int volume) { + call->setPlaybackVolume(volume); + }, volumeItem->lifetime()); + + menu->addAction(std::move(volumeItem)); + _incomingVolumeMenu->popup(globalPos); +} + void Panel::showDevicesMenu( not_null button, std::vector types) { diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index aaed848f7fd2a7..df39c55d86eccc 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -158,6 +158,7 @@ class Panel final void showDevicesMenu( not_null button, std::vector types); + void showIncomingVolumeMenu(QPoint globalPos); [[nodiscard]] QRect incomingFrameGeometry() const; [[nodiscard]] QRect outgoingFrameGeometry() const; @@ -212,6 +213,7 @@ class Panel final bool _mouseInside = false; base::unique_qptr _devicesMenu; + base::unique_qptr _incomingVolumeMenu; base::Timer _updateDurationTimer; base::Timer _updateOuterRippleTimer; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index f002f3cd220a44..d540c62b6d8094 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -3570,6 +3570,24 @@ float64 GroupCall::singleSourceVolumeValue() const { return _singleSourceVolume / float64(Group::kDefaultVolume); } +float64 GroupCall::effectiveParticipantVolume( + const Data::GroupCallParticipant &participant) const { + return participant.mutedByMe + ? 0. + : (participant.volume / float64(Group::kDefaultVolume)); +} + +float64 GroupCall::effectiveParticipantScreenVolume( + const Data::GroupCallParticipant &participant) const { + const auto i = _screenVolumesByPeer.find(participant.peer); + if (i != end(_screenVolumesByPeer)) { + return i->second.muted + ? 0. + : (i->second.volume / float64(Group::kDefaultVolume)); + } + return effectiveParticipantVolume(participant); +} + void GroupCall::updateInstanceVolumes() { const auto real = lookupReal(); if (!real) { @@ -3606,14 +3624,13 @@ void GroupCall::updateInstanceVolume( && (volumeChanged || (was && (GetAdditionalAudioSsrc(was->videoParams) != additionalSsrc))); - const auto localVolume = now.mutedByMe - ? 0. - : (now.volume / float64(Group::kDefaultVolume)); + const auto localVolume = effectiveParticipantVolume(now); + const auto localScreenVolume = effectiveParticipantScreenVolume(now); if (set) { _instance->setVolume(now.ssrc, localVolume); } if (additionalSet) { - _instance->setVolume(additionalSsrc, localVolume); + _instance->setVolume(additionalSsrc, localScreenVolume); } } @@ -3996,6 +4013,42 @@ void GroupCall::changeVolume(const Group::VolumeRequest &data) { } } +void GroupCall::toggleScreenMute(const Group::MuteRequest &data) { + if (_rtmp || videoStream() || data.peer->isSelf()) { + return; + } + auto &state = _screenVolumesByPeer[data.peer]; + state.muted = data.mute; + _otherParticipantScreenStateValue.fire(participantScreenState(data.peer)); + applyScreenVolume(data.peer); +} + +void GroupCall::changeScreenVolume(const Group::VolumeRequest &data) { + if (_rtmp || videoStream() || data.peer->isSelf()) { + return; + } + auto &state = _screenVolumesByPeer[data.peer]; + state.volume = std::clamp(data.volume, 1, Group::kMaxVolume); + state.muted = false; + _otherParticipantScreenStateValue.fire(participantScreenState(data.peer)); + applyScreenVolume(data.peer); +} + +void GroupCall::applyScreenVolume(not_null participantPeer) { + if (!_instance) { + return; + } + const auto participant = LookupParticipant(this, participantPeer); + if (!participant) { + return; + } + const auto ssrc = GetAdditionalAudioSsrc(participant->videoParams); + if (!ssrc) { + return; + } + _instance->setVolume(ssrc, effectiveParticipantScreenVolume(*participant)); +} + void GroupCall::editParticipant( not_null participantPeer, bool mute, @@ -4222,6 +4275,40 @@ auto GroupCall::otherParticipantStateValue() const return _otherParticipantStateValue.events(); } +auto GroupCall::otherParticipantScreenStateValue() const +-> rpl::producer { + return _otherParticipantScreenStateValue.events(); +} + +bool GroupCall::hasScreenShareAudio( + not_null participantPeer) { + const auto participant = LookupParticipant(this, participantPeer); + return participant && GetAdditionalAudioSsrc(participant->videoParams); +} + +Group::ParticipantState GroupCall::participantScreenState( + not_null participantPeer) { + const auto participant = LookupParticipant(this, participantPeer); + const auto i = _screenVolumesByPeer.find(participantPeer); + const auto fallbackVolume = participant + ? participant->volume + : Group::kDefaultVolume; + const auto fallbackMuted = participant ? participant->mutedByMe : false; + return Group::ParticipantState{ + .peer = participantPeer, + .volume = std::clamp( + (i != end(_screenVolumesByPeer)) + ? i->second.volume + : fallbackVolume, + 1, + Group::kMaxVolume), + .mutedByMe = (i != end(_screenVolumesByPeer)) + ? i->second.muted + : fallbackMuted, + .locallyOnly = true, + }; +} + MTPInputGroupCall GroupCall::inputCall() const { Expects(_id != 0); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index c2820cd8b7d0e3..c6d97364b29754 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -10,6 +10,7 @@ For license and copyright information please follow this link: #include "base/weak_ptr.h" #include "base/timer.h" #include "base/bytes.h" +#include "calls/group/calls_group_common.h" #include "mtproto/sender.h" #include "mtproto/mtproto_auth_key.h" #include "webrtc/webrtc_device_common.h" @@ -338,6 +339,12 @@ class GroupCall final [[nodiscard]] auto otherParticipantStateValue() const -> rpl::producer; + [[nodiscard]] auto otherParticipantScreenStateValue() const + -> rpl::producer; + [[nodiscard]] bool hasScreenShareAudio( + not_null participantPeer); + [[nodiscard]] Group::ParticipantState participantScreenState( + not_null participantPeer); enum State { Creating, @@ -454,6 +461,8 @@ class GroupCall final void toggleMute(const Group::MuteRequest &data); void changeVolume(const Group::VolumeRequest &data); + void toggleScreenMute(const Group::MuteRequest &data); + void changeScreenVolume(const Group::VolumeRequest &data); void inviteUsers( const std::vector &requests, @@ -594,6 +603,11 @@ class GroupCall final void updateInstanceVolume( const std::optional &was, const Data::GroupCallParticipant &now); + [[nodiscard]] float64 effectiveParticipantVolume( + const Data::GroupCallParticipant &participant) const; + [[nodiscard]] float64 effectiveParticipantScreenVolume( + const Data::GroupCallParticipant &participant) const; + void applyScreenVolume(not_null participantPeer); void applyMeInCallLocally(); void startRejoin(); void rejoin(); @@ -730,7 +744,13 @@ class GroupCall final bool _acceptFields = false; rpl::event_stream _otherParticipantStateValue; + rpl::event_stream _otherParticipantScreenStateValue; std::vector _queuedSelfUpdates; + struct ScreenVolumeState { + int volume = Group::kDefaultVolume; + bool muted = false; + }; + base::flat_map, ScreenVolumeState> _screenVolumesByPeer; CallId _id = 0; CallId _accessHash = 0; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index c3e4328e90063f..d0d3dc807759ac 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -80,6 +80,8 @@ class Members::Controller final } [[nodiscard]] rpl::producer toggleMuteRequests() const; [[nodiscard]] rpl::producer changeVolumeRequests() const; + [[nodiscard]] rpl::producer toggleScreenMuteRequests() const; + [[nodiscard]] rpl::producer changeScreenVolumeRequests() const; [[nodiscard]] auto kickParticipantRequests() const -> rpl::producer>; @@ -188,6 +190,8 @@ class Members::Controller final rpl::event_stream _toggleMuteRequests; rpl::event_stream _changeVolumeRequests; + rpl::event_stream _toggleScreenMuteRequests; + rpl::event_stream _changeScreenVolumeRequests; rpl::event_stream> _kickParticipantRequests; rpl::variable _fullCount = 1; @@ -1047,6 +1051,16 @@ auto Members::Controller::changeVolumeRequests() const return _changeVolumeRequests.events(); } +auto Members::Controller::toggleScreenMuteRequests() const +-> rpl::producer { + return _toggleScreenMuteRequests.events(); +} + +auto Members::Controller::changeScreenVolumeRequests() const +-> rpl::producer { + return _changeScreenVolumeRequests.events(); +} + bool Members::Controller::rowIsMe(not_null participantPeer) { return isMe(participantPeer); } @@ -1581,11 +1595,40 @@ void Members::Controller::addMuteActionsToContextMenu( .locallyOnly = local, }); }); + const auto toggleScreenMute = crl::guard(this, [=](bool mute) { + _toggleScreenMuteRequests.fire(Group::MuteRequest{ + .peer = participantPeer, + .mute = mute, + .locallyOnly = true, + }); + }); + const auto changeScreenVolume = crl::guard(this, [=](int volume) { + _changeScreenVolumeRequests.fire(Group::VolumeRequest{ + .peer = participantPeer, + .volume = std::clamp(volume, 1, Group::kMaxVolume), + .locallyOnly = true, + }); + }); + const auto addSectionLabel = [=](const QString &text) { + const auto action = menu->addAction(text, [] {}); + action->setEnabled(false); + }; + const auto screenMuteActionText = [=](bool muted) { + return tr::lng_group_call_screen_share_audio(tr::now) + + u": "_q + + (muted + ? tr::lng_call_unmute_audio(tr::now) + : tr::lng_call_mute_audio(tr::now)); + }; const auto muteState = row->state(); const auto muted = (muteState == Row::State::Muted) || (muteState == Row::State::RaisedHand); const auto mutedByMe = row->mutedByMe(); + const auto addScreenVolumeItem = !_call->rtmp() + && !_call->videoStream() + && !isMe(participantPeer) + && _call->hasScreenShareAudio(participantPeer); auto mutesFromVolume = rpl::never() | rpl::type_erased; @@ -1643,16 +1686,70 @@ void Members::Controller::addMuteActionsToContextMenu( if (menu->actions().size() > 1) { // First - cover. menu->addSeparator(); } + if (addScreenVolumeItem) { + addSectionLabel(tr::lng_group_call_microphone(tr::now)); + } menu->addAction(std::move(volumeItem)); - if (!_call->rtmp() - && !_call->videoStream() - && !isMe(participantPeer)) { + if (!addScreenVolumeItem && !isMe(participantPeer)) { menu->addSeparator(); } }; + if (addScreenVolumeItem) { + auto otherParticipantScreenStateValue = rpl::merge( + _call->otherParticipantScreenStateValue( + ) | rpl::filter([=](const Group::ParticipantState &data) { + return data.peer == participantPeer; + }), + _call->otherParticipantStateValue( + ) | rpl::filter([=](const Group::ParticipantState &data) { + return data.peer == participantPeer; + }) | rpl::map([=](const Group::ParticipantState &) { + return _call->participantScreenState(participantPeer); + })); + + const auto state = _call->participantScreenState(participantPeer); + auto volumeItem = base::make_unique_q( + menu->menu(), + st::groupCallPopupVolumeMenu, + st::groupCallMenuVolumeSlider, + std::move(otherParticipantScreenStateValue), + state.volume.value_or(Group::kDefaultVolume), + Group::kMaxVolume, + state.mutedByMe, + st::groupCallMenuVolumePadding); + + volumeItem->toggleMuteRequests( + ) | rpl::on_next([=](bool muted) { + toggleScreenMute(muted); + }, volumeItem->lifetime()); + + volumeItem->toggleMuteLocallyRequests( + ) | rpl::on_next([=](bool muted) { + toggleScreenMute(muted); + }, volumeItem->lifetime()); + + volumeItem->changeVolumeRequests( + ) | rpl::on_next([=](int volume) { + changeScreenVolume(volume); + }, volumeItem->lifetime()); + + volumeItem->changeVolumeLocallyRequests( + ) | rpl::on_next([=](int volume) { + changeScreenVolume(volume); + }, volumeItem->lifetime()); + + if (menu->actions().size() > 1) { + menu->addSeparator(); + } + addSectionLabel(tr::lng_group_call_screen_share_audio(tr::now)); + + menu->addAction(std::move(volumeItem)); + menu->addSeparator(); + } + const auto muteAction = [&]() -> QAction* { if (muteState == Row::State::Invited || muteState == Row::State::Calling @@ -1696,6 +1793,29 @@ void Members::Controller::addMuteActionsToContextMenu( muteAction->setText(muteUnmuteString(muted, mutedByMe)); }, menu->lifetime()); } + if (addScreenVolumeItem) { + const auto initial = _call->participantScreenState(participantPeer); + const auto screenMuteAction = menu->addAction( + screenMuteActionText(initial.mutedByMe), + [=] { + const auto state = _call->participantScreenState(participantPeer); + toggleScreenMute(!state.mutedByMe); + }); + rpl::merge( + _call->otherParticipantScreenStateValue( + ) | rpl::filter([=](const Group::ParticipantState &data) { + return data.peer == participantPeer; + }), + _call->otherParticipantStateValue( + ) | rpl::filter([=](const Group::ParticipantState &data) { + return data.peer == participantPeer; + }) | rpl::map([=](const Group::ParticipantState &) { + return _call->participantScreenState(participantPeer); + }) + ) | rpl::on_next([=](const Group::ParticipantState &state) { + screenMuteAction->setText(screenMuteActionText(state.mutedByMe)); + }, menu->lifetime()); + } } std::unique_ptr Members::Controller::createRowForMe() { @@ -1782,6 +1902,16 @@ auto Members::changeVolumeRequests() const return _listController->changeVolumeRequests(); } +auto Members::toggleScreenMuteRequests() const +-> rpl::producer { + return _listController->toggleScreenMuteRequests(); +} + +auto Members::changeScreenVolumeRequests() const +-> rpl::producer { + return _listController->changeScreenVolumeRequests(); +} + auto Members::kickParticipantRequests() const -> rpl::producer> { return _listController->kickParticipantRequests(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 739cccc990300d..656c48672d1e48 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -55,6 +55,10 @@ class Members final -> rpl::producer; [[nodiscard]] auto changeVolumeRequests() const -> rpl::producer; + [[nodiscard]] auto toggleScreenMuteRequests() const + -> rpl::producer; + [[nodiscard]] auto changeScreenVolumeRequests() const + -> rpl::producer; [[nodiscard]] auto kickParticipantRequests() const -> rpl::producer>; [[nodiscard]] rpl::producer<> addMembersRequests() const { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index fc6112da01b684..59da5c42c4539d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -254,7 +254,11 @@ Panel::Panel(not_null call, ConferencePanelMigration info) result.setAlphaF(kControlsBackgroundOpacity); return result; }) -, _hideControlsTimer([=] { toggleWideControls(false); }) { +, _hideControlsTimer([=] { + if (_rtmpFull || fullscreenForScreencastOnly()) { + toggleWideControls(false); + } +}) { _viewport->widget()->hide(); if (!_viewport->requireARGB32()) { _call->setNotRequireARGB32(); @@ -387,28 +391,48 @@ void Panel::initWindow() { subscribeToPeerChanges(); } + const auto wasFullscreen = std::make_shared( + (window()->windowState() & Qt::WindowFullScreen) != 0); const auto updateFullScreen = [=] { const auto state = window()->windowState(); - const auto full = (state & Qt::WindowFullScreen) - || (state & Qt::WindowMaximized); + const auto fullscreen = ((state & Qt::WindowFullScreen) != 0); + const auto maximized = ((state & Qt::WindowMaximized) != 0); + const auto full = (fullscreen || maximized); + if (!fullscreen) { + _screencastOnlyInFullscreen = false; + } else if (!*wasFullscreen && fullscreenForScreencast()) { + _screencastOnlyInFullscreen = true; + } + *wasFullscreen = fullscreen; _rtmpFull = _call->rtmp() && full; _fullScreenOrMaximized = full; + if (_subtitle && fullscreenForScreencastOnly()) { + _subtitle.destroy(); + } + updateControlsGeometry(); + if (fullscreenForScreencastOnly()) { + _hideControlsTimer.callOnce(kHideControlsTimeout); + toggleWideControls(true); + } else if (!_rtmpFull) { + _hideControlsTimer.cancel(); + toggleWideControls(_viewport->widget()->underMouse()); + } }; base::install_event_filter(window().get(), [=](not_null e) { const auto type = e->type(); if (type == QEvent::Close && handleClose()) { e->ignore(); return base::EventFilterResult::Cancel; - } else if (_call->rtmp() - && (type == QEvent::KeyPress || type == QEvent::KeyRelease)) { + } else if (type == QEvent::KeyPress || type == QEvent::KeyRelease) { const auto key = static_cast(e.get())->key(); - if (key == Qt::Key_Space) { + if (_call->rtmp() && key == Qt::Key_Space) { _call->pushToTalk( e->type() == QEvent::KeyPress, kSpacePushToTalkDelay); - } else if (key == Qt::Key_Escape - && _fullScreenOrMaximized.current()) { - toggleFullScreen(); + } else if ((type == QEvent::KeyPress) + && (key == Qt::Key_Escape) + && window()->isFullScreen()) { + toggleFullScreen(false); } } else if (type == QEvent::WindowStateChange) { updateFullScreen(); @@ -423,6 +447,12 @@ void Panel::initWindow() { if (!guard) { return (Flag::None | Flag(0)); } + if (fullscreenForScreencast() + && _viewport->widget()->geometry().contains(widgetPoint)) { + return window()->isFullScreen() + ? (Flag::None | Flag(0)) + : (Flag::FullScreen | Flag(0)); + } const auto titleRect = QRect( 0, 0, @@ -440,7 +470,11 @@ void Panel::initWindow() { } const auto shown = _window->topShownLayer(); return (!shown || !shown->geometry().contains(widgetPoint)) - ? (Flag::Move | Flag::Menu | Flag::Maximize) + ? (Flag::Move + | Flag::Menu + | (fullscreenForScreencast() + ? Flag::FullScreen + : Flag::Maximize)) : Flag::None; }); @@ -450,7 +484,8 @@ void Panel::initWindow() { }, lifetime()); _window->maximizeRequests() | rpl::on_next([=](bool maximized) { - if (_call->rtmp()) { + if (_call->rtmp() || fullscreenForScreencast()) { + _screencastOnlyInFullscreen = false; toggleFullScreen(maximized); } else { window()->setWindowState(maximized @@ -681,6 +716,9 @@ void Panel::toggleFullScreen() { } void Panel::toggleFullScreen(bool fullscreen) { + if (!fullscreen) { + _screencastOnlyInFullscreen = false; + } if (fullscreen) { window()->showFullScreen(); } else { @@ -1027,7 +1065,28 @@ void Panel::setupMembers() { ) | rpl::filter([=] { return !_rtmpFull; }) | rpl::on_next([=](bool inside) { - toggleWideControls(inside); + if (fullscreenForScreencastOnly()) { + if (inside) { + _hideControlsTimer.callOnce(kHideControlsTimeout); + toggleWideControls(true); + } else { + _hideControlsTimer.cancel(); + toggleWideControls(false); + } + } else { + _hideControlsTimer.cancel(); + toggleWideControls(inside); + } + }, _viewport->lifetime()); + + _viewport->rp()->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::MouseMove); + }) | rpl::on_next([=] { + if (fullscreenForScreencastOnly()) { + _hideControlsTimer.callOnce(kHideControlsTimeout); + toggleWideControls(true); + } }, _viewport->lifetime()); _members->show(); @@ -1051,6 +1110,16 @@ void Panel::setupMembers() { _call->changeVolume(request); }, _callLifetime); + _members->toggleScreenMuteRequests( + ) | rpl::on_next([=](MuteRequest request) { + _call->toggleScreenMute(request); + }, _callLifetime); + + _members->changeScreenVolumeRequests( + ) | rpl::on_next([=](VolumeRequest request) { + _call->changeScreenVolume(request); + }, _callLifetime); + _members->kickParticipantRequests( ) | rpl::on_next([=](not_null participantPeer) { kickParticipant(participantPeer); @@ -1080,6 +1149,7 @@ void Panel::setupMembers() { enlargeVideo(); } _viewport->showLarge(large); + updateControlsGeometry(); }, _callLifetime); } @@ -1261,6 +1331,34 @@ void Panel::setupVideo(not_null viewport) { } }, viewport->lifetime()); + if (viewport == _viewport.get()) { + viewport->doubleClicks( + ) | rpl::on_next([=](const VideoEndpoint &endpoint) { + if (endpoint.type != VideoEndpointType::Screen) { + return; + } + if (_call->videoEndpointLarge() != endpoint) { + if (_call->videoEndpointPinned()) { + _call->pinVideoEndpoint(endpoint); + } else { + _call->showVideoEndpointLarge(endpoint); + } + } + if (window()->isFullScreen()) { + _screencastOnlyInFullscreen = !_screencastOnlyInFullscreen; + if (fullscreenForScreencastOnly()) { + _hideControlsTimer.callOnce(kHideControlsTimeout); + } else { + _hideControlsTimer.cancel(); + } + updateControlsGeometry(); + } else { + _screencastOnlyInFullscreen = true; + toggleFullScreen(true); + } + }, viewport->lifetime()); + } + viewport->qualityRequests( ) | rpl::on_next([=](const VideoQualityRequest &request) { _call->requestVideoQuality(request.endpoint, request.quality); @@ -1284,7 +1382,8 @@ void Panel::updateWideControlsVisibility() { if (_wideControlsShown == shown) { return; } - _viewport->setCursorShown(!_rtmpFull || shown); + _viewport->setCursorShown( + (!_rtmpFull && !fullscreenForScreencastOnly()) || shown); _wideControlsShown = shown; _wideControlsAnimation.start( [=] { updateButtonsGeometry(); }, @@ -1889,9 +1988,9 @@ void Panel::updateButtonsStyles() { (wide ? &st::groupCallMessageActiveSmall : &st::groupCallMessageActive)); - _message->setText(wide - ? rpl::single(QString()) - : tr::lng_group_call_message()); + _message->setText(wide + ? rpl::single(QString()) + : tr::lng_group_call_message()); } if (_settings) { _settings->setText(wide @@ -2144,6 +2243,9 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) { } }); toggleWideControls(true); + if (_rtmpFull || fullscreenForScreencastOnly()) { + _hideControlsTimer.cancel(); + } } else if (type == QEvent::Leave) { *over = false; crl::on_main(widget, [=] { @@ -2152,6 +2254,9 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) { } }); toggleWideControls(false); + if (_rtmpFull || fullscreenForScreencastOnly()) { + _hideControlsTimer.callOnce(kHideControlsTimeout); + } } }, lifetime); } @@ -2468,13 +2573,16 @@ void Panel::updateButtonsGeometry() { + (_settings->width() + skip) + _hangup->width(); const auto membersSkip = st::groupCallNarrowSkip; - const auto membersWidth = rtmp + const auto screencastOnly = fullscreenForScreencastOnly(); + const auto membersWidth = (rtmp || screencastOnly) ? membersSkip : (st::groupCallNarrowMembersWidth + 2 * membersSkip); - auto left = membersSkip + (widget()->width() - - membersWidth - - membersSkip - - fullWidth) / 2; + auto left = screencastOnly + ? (widget()->width() - fullWidth) / 2 + : membersSkip + (widget()->width() + - membersWidth + - membersSkip + - fullWidth) / 2; const auto forMessagesLeft = left - st::groupCallControlsBackMargin.left(); @@ -2685,21 +2793,28 @@ void Panel::updateMembersGeometry() { if (!_members) { return; } - _members->setVisible(!_call->rtmp()); + _members->setVisible(!_call->rtmp() && !fullscreenForScreencastOnly()); const auto desiredHeight = _members->desiredHeight(); if (mode() == PanelMode::Wide) { - const auto skip = _rtmpFull ? 0 : st::groupCallNarrowSkip; - const auto membersWidth = st::groupCallNarrowMembersWidth; - const auto top = _rtmpFull ? 0 : st::groupCallWideVideoTop; + const auto screencastOnly = fullscreenForScreencastOnly(); + const auto skip = (_rtmpFull || screencastOnly) + ? 0 + : st::groupCallNarrowSkip; + const auto membersWidth = screencastOnly + ? 0 + : st::groupCallNarrowMembersWidth; + const auto top = (_rtmpFull || screencastOnly) + ? 0 + : st::groupCallWideVideoTop; _members->setGeometry( widget()->width() - skip - membersWidth, top, membersWidth, std::min(desiredHeight, widget()->height() - top - skip)); - const auto viewportSkip = _call->rtmp() + const auto viewportSkip = (_call->rtmp() || screencastOnly) ? 0 : (skip + membersWidth); - _viewport->setGeometry(_rtmpFull, { + _viewport->setGeometry(_rtmpFull || screencastOnly, { skip, top, widget()->width() - viewportSkip - 2 * skip, @@ -2729,6 +2844,17 @@ void Panel::updateMembersGeometry() { } } +bool Panel::fullscreenForScreencast() const { + const auto &endpoint = _call->videoEndpointLarge(); + return endpoint && (endpoint.type == VideoEndpointType::Screen); +} + +bool Panel::fullscreenForScreencastOnly() const { + return _screencastOnlyInFullscreen + && window()->isFullScreen() + && fullscreenForScreencast(); +} + rpl::producer Panel::titleText() { if (_call->conference()) { return tr::lng_confcall_join_title(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 6f2fa17533d7f7..1e83a2e1911c0f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -153,6 +153,8 @@ class Panel final void updateControlsGeometry(); void updateButtonsGeometry(); void updateTooltipGeometry(); + [[nodiscard]] bool fullscreenForScreencast() const; + [[nodiscard]] bool fullscreenForScreencastOnly() const; void updateButtonsStyles(); void updateMembersGeometry(); void refreshControlsBackground(); @@ -210,6 +212,7 @@ class Panel final std::shared_ptr _window; rpl::variable _mode; rpl::variable _fullScreenOrMaximized = false; + bool _screencastOnlyInFullscreen = false; bool _unpinnedMaximized = false; bool _rtmpFull = false; diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp index 1db9eaf97afe1b..2a332681f19249 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp @@ -120,6 +120,10 @@ void Viewport::setup() { handleMousePress( static_cast(e.get())->pos(), static_cast(e.get())->button()); + } else if (type == QEvent::MouseButtonDblClick) { + handleMouseDoubleClick( + static_cast(e.get())->pos(), + static_cast(e.get())->button()); } else if (type == QEvent::MouseButtonRelease) { handleMouseRelease( static_cast(e.get())->pos(), @@ -202,10 +206,29 @@ void Viewport::handleMousePress(QPoint position, Qt::MouseButton button) { setPressed(_selected); } +void Viewport::handleMouseDoubleClick( + QPoint position, + Qt::MouseButton button) { + handleMouseMove(position); + if (button != Qt::LeftButton || videoStream()) { + return; + } + const auto tile = _selected.tile; + if (!tile) { + return; + } + _skipNextMouseRelease = true; + _doubleClicks.fire_copy(tile->endpoint()); +} + void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) { handleMouseMove(position); const auto pressed = _pressed; setPressed({}); + if (_skipNextMouseRelease && button == Qt::LeftButton) { + _skipNextMouseRelease = false; + return; + } if (const auto tile = pressed.tile) { if (pressed == _selected) { if (videoStream()) { @@ -919,6 +942,10 @@ rpl::producer Viewport::clicks() const { return _clicks.events(); } +rpl::producer Viewport::doubleClicks() const { + return _doubleClicks.events(); +} + rpl::producer Viewport::qualityRequests() const { return _qualityRequests.events(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.h b/Telegram/SourceFiles/calls/group/calls_group_viewport.h index 7024c164be7230..12e687a1bed328 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.h @@ -97,6 +97,7 @@ class Viewport final { [[nodiscard]] rpl::producer fullHeightValue() const; [[nodiscard]] rpl::producer pinToggled() const; [[nodiscard]] rpl::producer clicks() const; + [[nodiscard]] rpl::producer doubleClicks() const; [[nodiscard]] rpl::producer qualityRequests() const; [[nodiscard]] rpl::producer mouseInsideValue() const; @@ -179,6 +180,7 @@ class Viewport final { void setPressed(Selection value); void handleMousePress(QPoint position, Qt::MouseButton button); + void handleMouseDoubleClick(QPoint position, Qt::MouseButton button); void handleMouseRelease(QPoint position, Qt::MouseButton button); void handleMouseMove(QPoint position); void updateSelected(QPoint position); @@ -201,6 +203,7 @@ class Viewport final { int _scrollTop = 0; QImage _shadow; rpl::event_stream _clicks; + rpl::event_stream _doubleClicks; rpl::event_stream _pinToggles; rpl::event_stream _qualityRequests; float64 _controlsShownRatio = 1.; @@ -211,6 +214,7 @@ class Viewport final { Layout _finishTilesLayout; Selection _selected; Selection _pressed; + bool _skipNextMouseRelease = false; rpl::variable _mouseInside = false; Ui::RpWidgetWrap * const _borrowed = nullptr; diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp index 94ce5cded80ef0..1133d65b037633 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp @@ -1450,7 +1450,7 @@ void Viewport::RendererGL::validateNoiseTexture( void Viewport::RendererGL::validateOutlineAnimation( not_null tile, TileData &data) { - const auto outline = tile->row()->speaking(); + const auto outline = !_owner->_fullscreen && tile->row()->speaking(); if (data.outline == outline) { return; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp index 9f006fa06442c0..fbef9987b46d12 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp @@ -186,7 +186,7 @@ void Viewport::RendererSW::paintTileOutline( int width, int height, not_null tile) { - if (!tile->row()->speaking()) { + if (_owner->_fullscreen || !tile->row()->speaking()) { return; } const auto outline = st::groupCallOutline; diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 24876ebca7da10..cd5e5eedb42e04 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 24876ebca7da10f92dc972225734337f9e793054 +Subproject commit cd5e5eedb42e046132d5af863da38cad84234ee4 diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 01e157532633b5..1088f5eb764408 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 01e157532633b5a56398f0d974c199a5769fcc39 +Subproject commit 1088f5eb764408e779942f409b72de0983336ff6