Toggle fullscreen by mute button in RTMP streams.

This commit is contained in:
John Preston 2022-02-28 16:14:28 +03:00
parent 25e29d3dd5
commit fc5ed46b40
11 changed files with 405 additions and 308 deletions

View file

@ -270,6 +270,9 @@ callMuteMajorBlobMinRadius: 67px;
callMuteMajorBlobMaxRadius: 77px;
callMuteBlobRadiusForDiameter: 100px;
callMuteToFullScreen: icon {{ "player/player_fullscreen", groupCallIconFg }};
callMuteFromFullScreen: icon {{ "player/player_minimize", groupCallIconFg }};
callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
color: lightButtonFg;
thickness: 4px;
@ -489,6 +492,7 @@ callErrorToast: Toast(defaultToast) {
groupCallWidth: 380px;
groupCallHeight: 580px;
groupCallHeightRtmpMin: 280px;
groupCallRipple: RippleAnimation(defaultRippleAnimation) {
color: groupCallMembersBgRipple;

View file

@ -990,9 +990,10 @@ void GroupCall::playConnectingSoundOnce() {
}
bool GroupCall::showChooseJoinAs() const {
return (_possibleJoinAs.size() > 1)
|| (_possibleJoinAs.size() == 1
&& !_possibleJoinAs.front()->isSelf());
return !_rtmp
&& ((_possibleJoinAs.size() > 1)
|| (_possibleJoinAs.size() == 1
&& !_possibleJoinAs.front()->isSelf()));
}
bool GroupCall::scheduleStartSubscribed() const {
@ -1010,6 +1011,14 @@ bool GroupCall::listenersHidden() const {
return _listenersHidden;
}
bool GroupCall::emptyRtmp() const {
return _emptyRtmp.current();
}
rpl::producer<bool> GroupCall::emptyRtmpValue() const {
return _emptyRtmp.value();
}
Data::GroupCall *GroupCall::lookupReal() const {
const auto real = _peer->groupCall();
return (real && real->id() == _id) ? real : nullptr;
@ -2601,7 +2610,8 @@ void GroupCall::requestCurrentTimeStart(
).done([=](const MTPphone_GroupCallStreamChannels &result) {
result.match([&](const MTPDphone_groupCallStreamChannels &data) {
const auto &list = data.vchannels().v;
if (!list.isEmpty()) {
const auto empty = list.isEmpty();
if (!empty) {
const auto &first = list.front();
first.match([&](const MTPDgroupCallStreamChannel &data) {
finish(data.vlast_timestamp_ms().v);
@ -2609,6 +2619,7 @@ void GroupCall::requestCurrentTimeStart(
} else {
finish(0);
}
_emptyRtmp = empty;
});
}).fail([=](const MTP::Error &error) {
finish(0);

View file

@ -233,6 +233,8 @@ public:
[[nodiscard]] bool scheduleStartSubscribed() const;
[[nodiscard]] bool rtmp() const;
[[nodiscard]] bool listenersHidden() const;
[[nodiscard]] bool emptyRtmp() const;
[[nodiscard]] rpl::producer<bool> emptyRtmpValue() const;
[[nodiscard]] Data::GroupCall *lookupReal() const;
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
@ -593,6 +595,7 @@ private:
rpl::variable<MuteState> _muted = MuteState::Muted;
rpl::variable<bool> _canManage = false;
rpl::variable<bool> _videoIsWorking = false;
rpl::variable<bool> _emptyRtmp = false;
bool _initialMuteStateSent = false;
bool _acceptFields = false;

View file

@ -1191,7 +1191,8 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
const auto muted = (muteState == Row::State::Muted)
|| (muteState == Row::State::RaisedHand);
const auto addCover = true;
const auto addVolumeItem = !muted || isMe(participantPeer);
const auto addVolumeItem = !_call->rtmp()
&& (!muted || isMe(participantPeer));
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
const auto session = &_peer->session();
const auto getCurrentWindow = [=]() -> Window::SessionController* {
@ -1430,7 +1431,8 @@ void Members::Controller::addMuteActionsToContextMenu(
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
const auto addVolumeItem = !muted || isMe(participantPeer);
const auto addVolumeItem = !_call->rtmp()
&& (!muted || isMe(participantPeer));
if (addVolumeItem) {
auto otherParticipantStateValue
= _call->otherParticipantStateValue(
@ -1492,6 +1494,7 @@ void Members::Controller::addMuteActionsToContextMenu(
const auto muteAction = [&]() -> QAction* {
if (muteState == Row::State::Invited
|| _call->rtmp()
|| isMe(participantPeer)
|| (muteState == Row::State::Inactive
&& participantIsCallAdmin

View file

@ -114,6 +114,9 @@ Panel::Panel(not_null<GroupCall*> call)
: _call->scheduleStartSubscribed()
? Ui::CallMuteButtonType::ScheduledNotify
: Ui::CallMuteButtonType::ScheduledSilent),
.expandType = ((_call->scheduleDate() || !_call->rtmp())
? Ui::CallMuteButtonExpandType::None
: Ui::CallMuteButtonExpandType::Normal),
}))
, _hangup(widget(), st::groupCallHangup)
, _stickedTooltipsShown(Core::App().settings().hiddenGroupCallTooltips()
@ -257,22 +260,34 @@ void Panel::initWindow() {
return base::EventFilterResult::Cancel;
} else if (e->type() == QEvent::KeyPress
|| e->type() == QEvent::KeyRelease) {
if (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Space) {
const auto key = static_cast<QKeyEvent*>(e.get())->key();
if (key == Qt::Key_Space) {
_call->pushToTalk(
e->type() == QEvent::KeyPress,
kSpacePushToTalkDelay);
} else if (key == Qt::Key_Escape && _fullScreen.current()) {
toggleFullScreen(false);
}
}
return base::EventFilterResult::Continue;
});
QObject::connect(
window()->windowHandle(),
&QWindow::windowStateChanged,
[=](Qt::WindowState state) {
_fullScreen = (state == Qt::WindowFullScreen);
});
window()->setBodyTitleArea([=](QPoint widgetPoint) {
using Flag = Ui::WindowTitleHitTestFlag;
const auto titleRect = QRect(
0,
0,
widget()->width(),
st::groupCallMembersTop);
(mode() == PanelMode::Wide
? st::groupCallWideVideoTop
: st::groupCallMembersTop));
const auto moveable = (titleRect.contains(widgetPoint)
&& (!_menuToggle || !_menuToggle->geometry().contains(widgetPoint))
&& (!_menu || !_menu->geometry().contains(widgetPoint))
@ -365,7 +380,11 @@ void Panel::initControls() {
!real->scheduleStartSubscribed());
}
return;
} else if (_call->rtmp()) {
toggleFullScreen(!_fullScreen.current());
return;
}
const auto oldState = _call->muted();
const auto newState = (oldState == MuteState::ForceMuted)
? MuteState::RaisedHand
@ -450,6 +469,14 @@ void Panel::initControls() {
refreshControlsBackground();
}
void Panel::toggleFullScreen(bool fullscreen) {
if (fullscreen) {
window()->showFullScreen();
} else {
window()->showNormal();
}
}
void Panel::refreshLeftButton() {
const auto share = _call->scheduleDate()
&& _peer->isBroadcast()
@ -467,6 +494,7 @@ void Panel::refreshLeftButton() {
_settings->setClickedCallback([=] {
showBox(Box(SettingsBox, _call));
});
trackControls(_trackControls, true);
}
const auto raw = _callShare ? _callShare.data() : _settings.data();
raw->show();
@ -615,7 +643,8 @@ void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
real->scheduleDateValue(),
real->scheduleStartSubscribedValue(),
_call->canManageValue(),
_mode.value()
_mode.value(),
_fullScreen.value()
) | rpl::distinct_until_changed(
) | rpl::filter(
_2 != GroupCall::InstanceState::TransitionToRtc
@ -625,9 +654,11 @@ void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
TimeId scheduleDate,
bool scheduleStartSubscribed,
bool canManage,
PanelMode mode) {
PanelMode mode,
bool fullScreen) {
const auto wide = (mode == PanelMode::Wide);
using Type = Ui::CallMuteButtonType;
using ExpandType = Ui::CallMuteButtonExpandType;
_mute->setState(Ui::CallMuteButtonState{
.text = (wide
? QString()
@ -664,6 +695,11 @@ void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
: mute == MuteState::Muted
? Type::Muted
: Type::Active),
.expandType = ((scheduleDate || !_call->rtmp())
? ExpandType::None
: fullScreen
? ExpandType::Expanded
: ExpandType::Normal),
});
}, _callLifetime);
}
@ -860,8 +896,10 @@ void Panel::minimizeVideo() {
}
const auto available = window()->screen()->availableGeometry();
const auto width = st::groupCallWidth;
const auto height = st::groupCallHeight;
auto geometry = QRect(
const auto height = _call->rtmp()
? st::groupCallHeightRtmpMin
: st::groupCallHeight;
const auto geometry = QRect(
window()->x() + (window()->width() - width) / 2,
window()->y() + (window()->height() - height) / 2,
width,
@ -1277,13 +1315,14 @@ void Panel::showMainMenu() {
if (wide) {
_wideMenu->installEventFilter(_menu);
trackControl(_menu, _trackControlsMenuLifetime);
const auto x = st::groupCallWideMenuPosition.x();
const auto y = st::groupCallWideMenuPosition.y();
_menu->moveToLeft(
_wideMenu->x() + x,
_wideMenu->y() - _menu->height() + y);
_menu->showAnimated(Ui::PanelAnimation::Origin::BottomLeft);
trackControl(_menu, _trackControlsMenuLifetime);
} else {
_menuToggle->installEventFilter(_menu);
const auto x = st::groupCallMenuPosition.x();
@ -1404,7 +1443,10 @@ rpl::lifetime &Panel::lifetime() {
void Panel::initGeometry() {
const auto center = Core::App().getPointForCallPanelCenter();
const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight);
const auto height = (_call->rtmp() && !_call->canManage())
? st::groupCallHeightRtmpMin
: st::groupCallHeight;
const auto rect = QRect(0, 0, st::groupCallWidth, height);
window()->setGeometry(rect.translated(center - rect.center()));
window()->setMinimumSize(rect.size());
window()->show();
@ -1854,15 +1896,14 @@ void Panel::updateTooltipGeometry() {
_niceTooltip->pointAt(geometry, RectPart::Top, countPosition);
}
void Panel::trackControls(bool track) {
if (_trackControls == track) {
void Panel::trackControls(bool track, bool force) {
if (!force && _trackControls == track) {
return;
}
_trackControls = track;
_trackControlsOverStateLifetime.destroy();
_trackControlsMenuLifetime.destroy();
if (!track) {
_trackControlsLifetime.destroy();
_trackControlsOverStateLifetime.destroy();
_trackControlsMenuLifetime.destroy();
toggleWideControls(true);
if (_wideControlsAnimation.animating()) {
_wideControlsAnimation.stop();
@ -1932,12 +1973,13 @@ void Panel::updateButtonsGeometry() {
Assert(_settings != nullptr);
Assert(_callShare == nullptr);
const auto rtmp = _call->rtmp();
const auto shown = _wideControlsAnimation.value(
_wideControlsShown ? 1. : 0.);
const auto hidden = (shown == 0.);
if (_viewport) {
_viewport->setControlsShown(_call->rtmp() ? 0. : shown);
_viewport->setControlsShown(rtmp ? 0. : shown);
}
const auto buttonsTop = widget()->height() - anim::interpolate(
@ -1947,10 +1989,10 @@ void Panel::updateButtonsGeometry() {
const auto addSkip = st::callMuteButtonSmall.active.outerRadius;
const auto muteSize = _mute->innerSize().width() + 2 * addSkip;
const auto skip = st::groupCallButtonSkipSmall;
const auto fullWidth = (_video->width() + skip)
+ (_screenShare->width() + skip)
const auto fullWidth = (rtmp ? 0 : (_video->width() + skip))
+ (rtmp ? 0 : (_screenShare->width() + skip))
+ (muteSize + skip)
+ (_settings ->width() + skip)
+ (_settings->width() + skip)
+ _hangup->width();
const auto membersSkip = st::groupCallNarrowSkip;
const auto membersWidth = _call->rtmp()
@ -1960,12 +2002,20 @@ void Panel::updateButtonsGeometry() {
- membersWidth
- membersSkip
- fullWidth) / 2;
toggle(_screenShare, !hidden);
_screenShare->moveToLeft(left, buttonsTop);
left += _screenShare->width() + skip;
toggle(_video, !hidden);
_video->moveToLeft(left, buttonsTop);
left += _video->width() + skip;
toggle(_screenShare, !hidden && !rtmp);
if (!rtmp) {
_screenShare->moveToLeft(left, buttonsTop);
left += _screenShare->width() + skip;
}
toggle(_video, !hidden && !rtmp);
if (!rtmp) {
_video->moveToLeft(left, buttonsTop);
left += _video->width() + skip;
} else {
_wideMenu->moveToLeft(left, buttonsTop);
_settings->moveToLeft(left, buttonsTop);
left += _settings->width() + skip;
}
toggle(_mute, !hidden);
_mute->moveInner({ left + addSkip, buttonsTop + addSkip });
left += muteSize + skip;
@ -1973,9 +2023,11 @@ void Panel::updateButtonsGeometry() {
|| _call->showChooseJoinAs();
toggle(_settings, !hidden && !wideMenuShown);
toggle(_wideMenu, !hidden && wideMenuShown);
_wideMenu->moveToLeft(left, buttonsTop);
_settings->moveToLeft(left, buttonsTop);
left += _settings->width() + skip;
if (!rtmp) {
_wideMenu->moveToLeft(left, buttonsTop);
_settings->moveToLeft(left, buttonsTop);
left += _settings->width() + skip;
}
toggle(_hangup, !hidden);
_hangup->moveToLeft(left, buttonsTop);
left += _hangup->width();

View file

@ -130,7 +130,8 @@ private:
bool handleClose();
void startScheduledNow();
void trackControls(bool track);
void toggleFullScreen(bool fullscreen);
void trackControls(bool track, bool force = false);
void raiseControls();
void enlargeVideo();
void minimizeVideo();
@ -197,6 +198,7 @@ private:
Ui::GL::Window _window;
const std::unique_ptr<Ui::LayerManager> _layerBg;
rpl::variable<PanelMode> _mode;
rpl::variable<bool> _fullScreen = false;
#ifndef Q_OS_MAC
std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
@ -215,7 +217,6 @@ private:
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
object_ptr<Members> _members = { nullptr };
std::unique_ptr<Viewport> _viewport;
rpl::lifetime _trackControlsLifetime;
rpl::lifetime _trackControlsOverStateLifetime;
rpl::lifetime _trackControlsMenuLifetime;
object_ptr<Ui::FlatLabel> _startsIn = { nullptr };

View file

@ -235,6 +235,7 @@ void SettingsBox(
const auto peer = call->peer();
const auto state = box->lifetime().make_state<State>();
const auto real = peer->groupCall();
const auto rtmp = call->rtmp();
const auto id = call->id();
const auto goodReal = (real && real->id() == id);
@ -274,280 +275,280 @@ void SettingsBox(
}), &st::groupCallCheckbox, &st::groupCallRadio));
});
AddButtonWithLabel(
layout,
tr::lng_group_call_microphone(),
rpl::single(
CurrentAudioInputName()
) | rpl::then(
state->inputNameStream.events()
),
st::groupCallSettingsButton
)->addClickHandler([=] {
box->getDelegate()->show(ChooseAudioInputBox(crl::guard(box, [=](
const QString &id,
const QString &name) {
state->inputNameStream.fire_copy(name);
if (state->micTester) {
state->micTester->setDeviceId(id);
}
}), &st::groupCallCheckbox, &st::groupCallRadio));
});
state->micTestLevel = box->addRow(
object_ptr<Ui::LevelMeter>(
box.get(),
st::groupCallLevelMeter),
st::settingsLevelMeterPadding);
state->micTestLevel->resize(QSize(0, st::defaultLevelMeter.height));
state->levelUpdateTimer.setCallback([=] {
const auto was = state->micLevel;
state->micLevel = state->micTester->getAndResetLevel();
state->micLevelAnimation.start([=] {
state->micTestLevel->setValue(
state->micLevelAnimation.value(state->micLevel));
}, was, state->micLevel, kMicTestAnimationDuration);
});
AddSkip(layout);
//AddDivider(layout);
//AddSkip(layout);
AddButton(
layout,
tr::lng_group_call_noise_suppression(),
st::groupCallSettingsButton
)->toggleOn(rpl::single(
settings.groupCallNoiseSuppression()
))->toggledChanges(
) | rpl::start_with_next([=](bool enabled) {
Core::App().settings().setGroupCallNoiseSuppression(enabled);
call->setNoiseSuppression(enabled);
Core::App().saveSettingsDelayed();
}, layout->lifetime());
using GlobalShortcut = base::GlobalShortcut;
struct PushToTalkState {
rpl::variable<QString> recordText = tr::lng_group_call_ptt_shortcut();
rpl::variable<QString> shortcutText;
rpl::event_stream<bool> pushToTalkToggles;
std::shared_ptr<base::GlobalShortcutManager> manager;
GlobalShortcut shortcut;
crl::time delay = 0;
bool recording = false;
};
if (base::GlobalShortcutsAvailable()) {
const auto state = box->lifetime().make_state<PushToTalkState>();
if (!base::GlobalShortcutsAllowed()) {
Core::App().settings().setGroupCallPushToTalk(false);
}
const auto tryFillFromManager = [=] {
state->shortcut = state->manager
? state->manager->shortcutFromSerialized(
Core::App().settings().groupCallPushToTalkShortcut())
: nullptr;
state->shortcutText = state->shortcut
? state->shortcut->toDisplayString()
: QString();
};
state->manager = settings.groupCallPushToTalk()
? call->ensureGlobalShortcutManager()
: nullptr;
tryFillFromManager();
state->delay = settings.groupCallPushToTalkDelay();
const auto pushToTalk = AddButton(
if (!rtmp) {
AddButtonWithLabel(
layout,
tr::lng_group_call_push_to_talk(),
tr::lng_group_call_microphone(),
rpl::single(
CurrentAudioInputName()
) | rpl::then(
state->inputNameStream.events()
),
st::groupCallSettingsButton
)->toggleOn(rpl::single(
settings.groupCallPushToTalk()
) | rpl::then(state->pushToTalkToggles.events()));
const auto pushToTalkWrap = layout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
layout,
object_ptr<Ui::VerticalLayout>(layout)));
const auto pushToTalkInner = pushToTalkWrap->entity();
const auto recording = AddButton(
pushToTalkInner,
state->recordText.value(),
st::groupCallSettingsButton);
CreateRightLabel(
recording,
state->shortcutText.value(),
st::groupCallSettingsButton,
state->recordText.value());
const auto applyAndSave = [=] {
call->applyGlobalShortcutChanges();
Core::App().saveSettingsDelayed();
};
const auto showPrivacyRequest = [=] {
#ifdef Q_OS_MAC
if (!Platform::IsMac10_14OrGreater()) {
return;
}
const auto requestInputMonitoring = Platform::IsMac10_15OrGreater();
box->getDelegate()->show(Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
rpl::combine(
tr::lng_group_call_mac_access(),
(requestInputMonitoring
? tr::lng_group_call_mac_input()
: tr::lng_group_call_mac_accessibility())
) | rpl::map([](QString a, QString b) {
auto result = Ui::Text::RichLangValue(a);
result.append("\n\n").append(Ui::Text::RichLangValue(b));
return result;
}),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
st::boxPadding.top(),
st::boxRowPadding.right(),
st::boxPadding.bottom()));
box->addButton(tr::lng_group_call_mac_settings(), [=] {
if (requestInputMonitoring) {
Platform::OpenInputMonitoringPrivacySettings();
} else {
Platform::OpenAccessibilityPrivacySettings();
}
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
if (!requestInputMonitoring) {
// Accessibility is enabled without app restart, so short-poll it.
base::timer_each(
kCheckAccessibilityInterval
) | rpl::filter([] {
return base::GlobalShortcutsAllowed();
}) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
)->addClickHandler([=] {
box->getDelegate()->show(ChooseAudioInputBox(crl::guard(box, [=](
const QString &id,
const QString &name) {
state->inputNameStream.fire_copy(name);
if (state->micTester) {
state->micTester->setDeviceId(id);
}
}));
#endif // Q_OS_MAC
};
const auto ensureManager = [=] {
if (state->manager) {
return true;
} else if (base::GlobalShortcutsAllowed()) {
state->manager = call->ensureGlobalShortcutManager();
tryFillFromManager();
return true;
}
showPrivacyRequest();
return false;
};
const auto stopRecording = [=] {
state->recording = false;
state->recordText = tr::lng_group_call_ptt_shortcut();
state->shortcutText = state->shortcut
? state->shortcut->toDisplayString()
: QString();
recording->setColorOverride(std::nullopt);
if (state->manager) {
state->manager->stopRecording();
}
};
const auto startRecording = [=] {
if (!ensureManager()) {
state->pushToTalkToggles.fire(false);
pushToTalkWrap->hide(anim::type::instant);
return;
}
state->recording = true;
state->recordText = tr::lng_group_call_ptt_recording();
recording->setColorOverride(
st::groupCallSettingsAttentionButton.textFg->c);
auto progress = crl::guard(box, [=](GlobalShortcut shortcut) {
state->shortcutText = shortcut->toDisplayString();
});
auto done = crl::guard(box, [=](GlobalShortcut shortcut) {
state->shortcut = shortcut;
Core::App().settings().setGroupCallPushToTalkShortcut(shortcut
? shortcut->serialize()
: QByteArray());
applyAndSave();
stopRecording();
});
state->manager->startRecording(std::move(progress), std::move(done));
};
recording->addClickHandler([=] {
if (state->recording) {
stopRecording();
} else {
startRecording();
}
}), &st::groupCallCheckbox, &st::groupCallRadio));
});
const auto label = pushToTalkInner->add(
object_ptr<Ui::LabelSimple>(
state->micTestLevel = box->addRow(
object_ptr<Ui::LevelMeter>(
box.get(),
st::groupCallLevelMeter),
st::settingsLevelMeterPadding);
state->micTestLevel->resize(QSize(0, st::defaultLevelMeter.height));
state->levelUpdateTimer.setCallback([=] {
const auto was = state->micLevel;
state->micLevel = state->micTester->getAndResetLevel();
state->micLevelAnimation.start([=] {
state->micTestLevel->setValue(
state->micLevelAnimation.value(state->micLevel));
}, was, state->micLevel, kMicTestAnimationDuration);
});
AddSkip(layout);
//AddDivider(layout);
//AddSkip(layout);
AddButton(
layout,
tr::lng_group_call_noise_suppression(),
st::groupCallSettingsButton
)->toggleOn(rpl::single(
settings.groupCallNoiseSuppression()
))->toggledChanges(
) | rpl::start_with_next([=](bool enabled) {
Core::App().settings().setGroupCallNoiseSuppression(enabled);
call->setNoiseSuppression(enabled);
Core::App().saveSettingsDelayed();
}, layout->lifetime());
using GlobalShortcut = base::GlobalShortcut;
struct PushToTalkState {
rpl::variable<QString> recordText = tr::lng_group_call_ptt_shortcut();
rpl::variable<QString> shortcutText;
rpl::event_stream<bool> pushToTalkToggles;
std::shared_ptr<base::GlobalShortcutManager> manager;
GlobalShortcut shortcut;
crl::time delay = 0;
bool recording = false;
};
if (base::GlobalShortcutsAvailable()) {
const auto state = box->lifetime().make_state<PushToTalkState>();
if (!base::GlobalShortcutsAllowed()) {
Core::App().settings().setGroupCallPushToTalk(false);
}
const auto tryFillFromManager = [=] {
state->shortcut = state->manager
? state->manager->shortcutFromSerialized(
Core::App().settings().groupCallPushToTalkShortcut())
: nullptr;
state->shortcutText = state->shortcut
? state->shortcut->toDisplayString()
: QString();
};
state->manager = settings.groupCallPushToTalk()
? call->ensureGlobalShortcutManager()
: nullptr;
tryFillFromManager();
state->delay = settings.groupCallPushToTalkDelay();
const auto pushToTalk = AddButton(
layout,
tr::lng_group_call_push_to_talk(),
st::groupCallSettingsButton
)->toggleOn(rpl::single(
settings.groupCallPushToTalk()
) | rpl::then(state->pushToTalkToggles.events()));
const auto pushToTalkWrap = layout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
layout,
object_ptr<Ui::VerticalLayout>(layout)));
const auto pushToTalkInner = pushToTalkWrap->entity();
const auto recording = AddButton(
pushToTalkInner,
st::groupCallDelayLabel),
st::groupCallDelayLabelMargin);
const auto value = std::clamp(
state->delay,
crl::time(0),
DelayByIndex(kDelaysCount - 1));
const auto callback = [=](crl::time delay) {
state->delay = delay;
label->setText(tr::lng_group_call_ptt_delay(
tr::now,
lt_delay,
FormatDelay(delay)));
if (Core::App().settings().groupCallPushToTalkDelay() != delay) {
Core::App().settings().setGroupCallPushToTalkDelay(delay);
state->recordText.value(),
st::groupCallSettingsButton);
CreateRightLabel(
recording,
state->shortcutText.value(),
st::groupCallSettingsButton,
state->recordText.value());
const auto applyAndSave = [=] {
call->applyGlobalShortcutChanges();
Core::App().saveSettingsDelayed();
};
const auto showPrivacyRequest = [=] {
#ifdef Q_OS_MAC
if (!Platform::IsMac10_14OrGreater()) {
return;
}
const auto requestInputMonitoring = Platform::IsMac10_15OrGreater();
box->getDelegate()->show(Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
rpl::combine(
tr::lng_group_call_mac_access(),
(requestInputMonitoring
? tr::lng_group_call_mac_input()
: tr::lng_group_call_mac_accessibility())
) | rpl::map([](QString a, QString b) {
auto result = Ui::Text::RichLangValue(a);
result.append("\n\n").append(Ui::Text::RichLangValue(b));
return result;
}),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
st::boxPadding.top(),
st::boxRowPadding.right(),
st::boxPadding.bottom()));
box->addButton(tr::lng_group_call_mac_settings(), [=] {
if (requestInputMonitoring) {
Platform::OpenInputMonitoringPrivacySettings();
} else {
Platform::OpenAccessibilityPrivacySettings();
}
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
if (!requestInputMonitoring) {
// Accessibility is enabled without app restart, so short-poll it.
base::timer_each(
kCheckAccessibilityInterval
) | rpl::filter([] {
return base::GlobalShortcutsAllowed();
}) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
}
}));
#endif // Q_OS_MAC
};
const auto ensureManager = [=] {
if (state->manager) {
return true;
} else if (base::GlobalShortcutsAllowed()) {
state->manager = call->ensureGlobalShortcutManager();
tryFillFromManager();
return true;
}
showPrivacyRequest();
return false;
};
const auto stopRecording = [=] {
state->recording = false;
state->recordText = tr::lng_group_call_ptt_shortcut();
state->shortcutText = state->shortcut
? state->shortcut->toDisplayString()
: QString();
recording->setColorOverride(std::nullopt);
if (state->manager) {
state->manager->stopRecording();
}
};
const auto startRecording = [=] {
if (!ensureManager()) {
state->pushToTalkToggles.fire(false);
pushToTalkWrap->hide(anim::type::instant);
return;
}
state->recording = true;
state->recordText = tr::lng_group_call_ptt_recording();
recording->setColorOverride(
st::groupCallSettingsAttentionButton.textFg->c);
auto progress = crl::guard(box, [=](GlobalShortcut shortcut) {
state->shortcutText = shortcut->toDisplayString();
});
auto done = crl::guard(box, [=](GlobalShortcut shortcut) {
state->shortcut = shortcut;
Core::App().settings().setGroupCallPushToTalkShortcut(shortcut
? shortcut->serialize()
: QByteArray());
applyAndSave();
stopRecording();
});
state->manager->startRecording(std::move(progress), std::move(done));
};
recording->addClickHandler([=] {
if (state->recording) {
stopRecording();
} else {
startRecording();
}
});
const auto label = pushToTalkInner->add(
object_ptr<Ui::LabelSimple>(
pushToTalkInner,
st::groupCallDelayLabel),
st::groupCallDelayLabelMargin);
const auto value = std::clamp(
state->delay,
crl::time(0),
DelayByIndex(kDelaysCount - 1));
const auto callback = [=](crl::time delay) {
state->delay = delay;
label->setText(tr::lng_group_call_ptt_delay(
tr::now,
lt_delay,
FormatDelay(delay)));
if (Core::App().settings().groupCallPushToTalkDelay() != delay) {
Core::App().settings().setGroupCallPushToTalkDelay(delay);
applyAndSave();
}
};
callback(value);
const auto slider = pushToTalkInner->add(
object_ptr<Ui::MediaSlider>(
pushToTalkInner,
st::groupCallDelaySlider),
st::groupCallDelayMargin);
slider->resize(st::groupCallDelaySlider.seekSize);
slider->setPseudoDiscrete(
kDelaysCount,
DelayByIndex,
value,
callback);
pushToTalkWrap->toggle(
settings.groupCallPushToTalk(),
anim::type::instant);
pushToTalk->toggledChanges(
) | rpl::start_with_next([=](bool toggled) {
if (!toggled) {
stopRecording();
} else if (!ensureManager()) {
state->pushToTalkToggles.fire(false);
pushToTalkWrap->hide(anim::type::instant);
return;
}
Core::App().settings().setGroupCallPushToTalk(toggled);
applyAndSave();
}
};
callback(value);
const auto slider = pushToTalkInner->add(
object_ptr<Ui::MediaSlider>(
pushToTalkInner,
st::groupCallDelaySlider),
st::groupCallDelayMargin);
slider->resize(st::groupCallDelaySlider.seekSize);
slider->setPseudoDiscrete(
kDelaysCount,
DelayByIndex,
value,
callback);
pushToTalkWrap->toggle(toggled, anim::type::normal);
}, pushToTalk->lifetime());
pushToTalkWrap->toggle(
settings.groupCallPushToTalk(),
anim::type::instant);
pushToTalk->toggledChanges(
) | rpl::start_with_next([=](bool toggled) {
if (!toggled) {
stopRecording();
} else if (!ensureManager()) {
state->pushToTalkToggles.fire(false);
pushToTalkWrap->hide(anim::type::instant);
return;
}
Core::App().settings().setGroupCallPushToTalk(toggled);
applyAndSave();
pushToTalkWrap->toggle(toggled, anim::type::normal);
}, pushToTalk->lifetime());
auto boxKeyFilter = [=](not_null<QEvent*> e) {
return (e->type() == QEvent::KeyPress && state->recording)
? base::EventFilterResult::Cancel
: base::EventFilterResult::Continue;
};
box->lifetime().make_state<base::unique_qptr<QObject>>(
base::install_event_filter(box, std::move(boxKeyFilter)));
}
auto boxKeyFilter = [=](not_null<QEvent*> e) {
return (e->type() == QEvent::KeyPress && state->recording)
? base::EventFilterResult::Cancel
: base::EventFilterResult::Continue;
};
box->lifetime().make_state<base::unique_qptr<QObject>>(
base::install_event_filter(box, std::move(boxKeyFilter)));
AddSkip(layout);
//AddDivider(layout);
//AddSkip(layout);
}
AddSkip(layout);
//AddDivider(layout);
//AddSkip(layout);
auto shareLink = Fn<void()>();
if (peer->isChannel()
&& peer->asChannel()->hasUsername()
@ -644,15 +645,17 @@ void SettingsBox(
});
}
box->setShowFinishedCallback([=] {
// Means we finished showing the box.
crl::on_main(box, [=] {
state->micTester = std::make_unique<Webrtc::AudioInputTester>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId());
state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
if (!rtmp) {
box->setShowFinishedCallback([=] {
// Means we finished showing the box.
crl::on_main(box, [=] {
state->micTester = std::make_unique<Webrtc::AudioInputTester>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId());
state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
});
});
});
}
box->setTitle(tr::lng_group_call_settings_title());
box->boxClosing(

View file

@ -876,6 +876,9 @@ rpl::producer<QString> MuteButtonTooltip(not_null<GroupCall*> call) {
// : tr::lng_group_call_set_reminder();
// }) | rpl::flatten_latest();
// }
if (call->rtmp()) {
return nullptr;
}
return call->mutedValue(
) | rpl::map([](MuteState muted) {
switch (muted) {

View file

@ -492,7 +492,7 @@ void GroupCall::processQueuedUpdates() {
}
void GroupCall::computeParticipantsCount() {
_fullCount = _allParticipantsLoaded
_fullCount = (_allParticipantsLoaded && !_listenersHidden)
? int(_participants.size())
: std::max(int(_participants.size()), _serverParticipantsCount);
}

View file

@ -825,7 +825,17 @@ void CallMuteButton::init() {
) | rpl::start_with_next([=](QRect clip) {
Painter p(_content);
_icons[_iconState.index]->paint(p, _muteIconRect.x(), _muteIconRect.y());
const auto expand = _state.current().expandType;
if (expand == CallMuteButtonExpandType::Expanded) {
st::callMuteFromFullScreen.paintInCenter(p, _muteIconRect);
} else if (expand == CallMuteButtonExpandType::Normal) {
st::callMuteToFullScreen.paintInCenter(p, _muteIconRect);
} else {
_icons[_iconState.index]->paint(
p,
_muteIconRect.x(),
_muteIconRect.y());
}
if (_radialInfo.state.has_value() && _switchAnimation.animating()) {
const auto radialProgress = _radialInfo.realShowProgress;

View file

@ -43,11 +43,18 @@ enum class CallMuteButtonType {
ScheduledNotify,
};
enum class CallMuteButtonExpandType {
None,
Normal,
Expanded,
};
struct CallMuteButtonState {
QString text;
QString subtext;
QString tooltip;
CallMuteButtonType type = CallMuteButtonType::Connecting;
CallMuteButtonExpandType expandType = CallMuteButtonExpandType::None;
};
class CallMuteButton final : private AbstractTooltipShower {