Skip to content

Commit 3116cd5

Browse files
Calls: screenshare audio fixes, volume control and fullscreen mode
Fix system audio stopping when microphone is muted during 1-to-1 screen sharing. Previously setMuteMicrophone() muted the entire outgoing audio channel, killing both mic and system audio. Now when screen audio is active, mic is muted at the ADM mixing level while the channel stays unmuted so system audio continues flowing. Add right-click context menu on the incoming video in 1-to-1 calls with a volume slider and mute toggle for controlling the remote user's audio volume locally. Implements setOutputVolume via the WebRTC receive channel (same mechanism as group calls) so the slider actually affects playback. Always wrap the ADM with MixingAudioDeviceModule so playback volume control is available regardless of loopback capture support. Add separate controls for participant voice and screen-share audio in group call member menus, and expose a dedicated screen-share mute action when screen audio is available. Add screencast-only fullscreen mode for group call screenshare. When a screen endpoint is focused, fullscreen switches to a layout that hides side panels. Controls auto-hide on inactivity and reappear on mouse movement. Also disable speaking outline rendering in fullscreen to avoid full-frame green highlighting.
1 parent cc85a4a commit 3116cd5

17 files changed

Lines changed: 617 additions & 46 deletions

Telegram/SourceFiles/calls/calls.style

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,7 @@ groupCallScreenShareSmall: CallButton(groupCallSettingsSmall) {
10081008
rippleAreaPosition: point(8px, 12px);
10091009
}
10101010
}
1011+
10111012
groupCallMenuToggleSmall: CallButton(groupCallSettingsSmall) {
10121013
button: IconButton(groupCallSettingsInner) {
10131014
icon: icon {{ "calls/calls_more", groupCallIconFg }};

Telegram/SourceFiles/calls/calls_call.cpp

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,12 @@ rpl::producer<Webrtc::DeviceResolvedId> Call::captureMuteDeviceId() {
520520

521521
void Call::setMuted(bool mute) {
522522
_muted = mute;
523-
if (_instance) {
523+
if (_screenWithAudio && _screenAudioControl) {
524+
_screenAudioControl->setMicrophoneMuted(mute);
525+
if (_instance) {
526+
_instance->setMuteMicrophone(false);
527+
}
528+
} else if (_instance) {
524529
_instance->setMuteMicrophone(mute);
525530
}
526531
}
@@ -1080,6 +1085,13 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
10801085
});
10811086
};
10821087

1088+
_screenAudioControl = std::make_shared<Webrtc::MixingAudioControl>();
1089+
auto admCreator = Webrtc::AudioDeviceModuleCreator(
1090+
saveSetDeviceIdCallback);
1091+
auto audioDeviceModuleCreator = Webrtc::MixingAudioDeviceModuleCreator(
1092+
std::move(admCreator),
1093+
_screenAudioControl);
1094+
10831095
tgcalls::Descriptor descriptor = {
10841096
.version = versionString,
10851097
.config = tgcalls::Config{
@@ -1139,8 +1151,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
11391151
sendSignalingData(bytes);
11401152
});
11411153
},
1142-
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
1143-
saveSetDeviceIdCallback),
1154+
.createAudioDeviceModule = std::move(audioDeviceModuleCreator),
11441155
};
11451156
if (Logs::DebugEnabled()) {
11461157
const auto callLogFolder = cWorkingDir() + u"DebugLogs"_q;
@@ -1381,6 +1392,48 @@ void Call::setState(State state) {
13811392
// }
13821393
//}
13831394

1395+
void Call::setPlaybackVolume(int volume) {
1396+
_playbackVolume = std::clamp(volume, 0, 20000);
1397+
const auto level = _playbackMuted.current()
1398+
? 0.f
1399+
: (_playbackVolume.current() / 10000.f);
1400+
if (_instance) {
1401+
_instance->setOutputVolume(level);
1402+
}
1403+
if (_screenAudioControl) {
1404+
_screenAudioControl->setPlaybackVolume(level);
1405+
}
1406+
}
1407+
1408+
int Call::playbackVolume() const {
1409+
return _playbackVolume.current();
1410+
}
1411+
1412+
void Call::setPlaybackMuted(bool muted) {
1413+
_playbackMuted = muted;
1414+
const auto level = muted
1415+
? 0.f
1416+
: (_playbackVolume.current() / 10000.f);
1417+
if (_instance) {
1418+
_instance->setOutputVolume(level);
1419+
}
1420+
if (_screenAudioControl) {
1421+
_screenAudioControl->setPlaybackVolume(level);
1422+
}
1423+
}
1424+
1425+
bool Call::playbackMuted() const {
1426+
return _playbackMuted.current();
1427+
}
1428+
1429+
rpl::producer<int> Call::playbackVolumeValue() const {
1430+
return _playbackVolume.value();
1431+
}
1432+
1433+
rpl::producer<bool> Call::playbackMutedValue() const {
1434+
return _playbackMuted.value();
1435+
}
1436+
13841437
void Call::setAudioDuckingEnabled(bool enabled) {
13851438
if (_instance) {
13861439
_instance->setAudioOutputDuckingEnabled(enabled);
@@ -1399,6 +1452,10 @@ bool Call::isSharingScreen() const {
13991452
return _videoCaptureIsScreencast && isSharingVideo();
14001453
}
14011454

1455+
bool Call::screenSharingWithAudio() const {
1456+
return isSharingScreen() && _screenWithAudio;
1457+
}
1458+
14021459
QString Call::cameraSharingDeviceId() const {
14031460
return isSharingCamera() ? _videoCaptureDeviceId : QString();
14041461
}
@@ -1445,6 +1502,13 @@ void Call::toggleScreenSharing(
14451502
}
14461503
_videoCaptureDeviceId = QString();
14471504
_videoCaptureIsScreencast = false;
1505+
if (_screenWithAudio && _screenAudioControl) {
1506+
_screenAudioControl->setMicrophoneMuted(false);
1507+
_screenAudioControl->setLoopbackEnabled(false);
1508+
if (_muted.current() && _instance) {
1509+
_instance->setMuteMicrophone(true);
1510+
}
1511+
}
14481512
_screenWithAudio = false;
14491513
if (_systemAudioCapture) {
14501514
_systemAudioCapture->stop();
@@ -1459,6 +1523,15 @@ void Call::toggleScreenSharing(
14591523
_videoCaptureIsScreencast = true;
14601524
_videoCaptureDeviceId = *uniqueId;
14611525
_screenWithAudio = withAudio;
1526+
if (_screenAudioControl) {
1527+
_screenAudioControl->setLoopbackEnabled(withAudio);
1528+
if (withAudio && _muted.current()) {
1529+
_screenAudioControl->setMicrophoneMuted(true);
1530+
if (_instance) {
1531+
_instance->setMuteMicrophone(false);
1532+
}
1533+
}
1534+
}
14621535
if (_videoCapture) {
14631536
_videoCapture->switchToDevice(uniqueId->toStdString(), true);
14641537
if (_instance) {
@@ -1647,6 +1720,7 @@ void Call::handleControllerError(const QString &error) {
16471720
}
16481721

16491722
void Call::destroyController() {
1723+
_screenAudioControl = nullptr;
16501724
_instanceLifetime.destroy();
16511725
Core::App().mediaDevices().setCaptureMuteTracker(this, false);
16521726
if (_systemAudioCapture) {

Telegram/SourceFiles/calls/calls_call.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ namespace Webrtc {
3737
enum class VideoState;
3838
class VideoTrack;
3939
struct DeviceResolvedId;
40+
class MixingAudioControl;
4041
} // namespace Webrtc
4142

4243
namespace Calls {
43-
4444
struct StartConferenceInfo;
4545

4646
struct DhConfig {
@@ -243,22 +243,28 @@ class Call final
243243
//void setAudioVolume(bool input, float level);
244244
void setAudioDuckingEnabled(bool enabled);
245245

246+
void setPlaybackVolume(int volume);
247+
[[nodiscard]] int playbackVolume() const;
248+
void setPlaybackMuted(bool muted);
249+
[[nodiscard]] bool playbackMuted() const;
250+
[[nodiscard]] rpl::producer<int> playbackVolumeValue() const;
251+
[[nodiscard]] rpl::producer<bool> playbackMutedValue() const;
252+
246253
[[nodiscard]] QString videoDeviceId() const {
247254
return _videoCaptureDeviceId;
248255
}
249256

250257
[[nodiscard]] bool isSharingVideo() const;
251258
[[nodiscard]] bool isSharingCamera() const;
252259
[[nodiscard]] bool isSharingScreen() const;
260+
[[nodiscard]] bool screenSharingWithAudio() const;
253261
[[nodiscard]] QString cameraSharingDeviceId() const;
254262
[[nodiscard]] QString screenSharingDeviceId() const;
255263
void toggleCameraSharing(bool enabled);
256264
void toggleScreenSharing(
257265
std::optional<QString> uniqueId,
258266
bool withAudio = false);
259-
[[nodiscard]] bool screenSharingWithAudio() const {
260-
return _screenWithAudio;
261-
}
267+
262268
[[nodiscard]] auto peekVideoCapture() const
263269
-> std::shared_ptr<tgcalls::VideoCaptureInterface>;
264270

@@ -369,11 +375,14 @@ class Call final
369375
std::vector<not_null<PeerData*>> _conferenceParticipants;
370376

371377
std::unique_ptr<tgcalls::Instance> _instance;
378+
std::shared_ptr<Webrtc::MixingAudioControl> _screenAudioControl;
372379
std::shared_ptr<tgcalls::VideoCaptureInterface> _videoCapture;
373380
QString _videoCaptureDeviceId;
374381
bool _videoCaptureIsScreencast = false;
375382
bool _screenWithAudio = false;
376383
std::unique_ptr<Webrtc::SystemAudioCapture> _systemAudioCapture;
384+
rpl::variable<int> _playbackVolume = 10000;
385+
rpl::variable<bool> _playbackMuted = false;
377386
const std::unique_ptr<Webrtc::VideoTrack> _videoIncoming;
378387
const std::unique_ptr<Webrtc::VideoTrack> _videoOutgoing;
379388

Telegram/SourceFiles/calls/calls_panel.cpp

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ For license and copyright information please follow this link:
77
*/
88
#include "calls/calls_panel.h"
99

10+
#include "ui/widgets/checkbox.h"
11+
#include "webrtc/webrtc_create_adm.h"
12+
1013
#include "boxes/peers/replace_boost_box.h" // CreateUserpicsWithMoreBadge
14+
#include "calls/group/calls_volume_item.h"
15+
#include "calls/group/calls_group_common.h"
1116
#include "calls/calls_panel_background.h"
1217
#include "data/data_photo.h"
1318
#include "data/data_session.h"
@@ -16,7 +21,6 @@ For license and copyright information please follow this link:
1621
#include "data/data_photo_media.h"
1722
#include "data/data_cloud_file.h"
1823
#include "data/data_changes.h"
19-
#include "calls/group/calls_group_common.h"
2024
#include "calls/group/calls_group_invite_controller.h"
2125
#include "calls/ui/calls_device_menu.h"
2226
#include "calls/calls_emoji_fingerprint.h"
@@ -376,6 +380,21 @@ void Panel::initWidget() {
376380
) | rpl::skip(1) | rpl::on_next([=] {
377381
updateControlsGeometry();
378382
}, lifetime());
383+
384+
base::install_event_filter(widget(), widget(), [=](
385+
not_null<QEvent*> e) {
386+
if (e->type() == QEvent::ContextMenu) {
387+
const auto event = static_cast<QContextMenuEvent*>(e.get());
388+
const auto pos = event->pos();
389+
if (_incoming
390+
&& _incoming->widget()->isVisible()
391+
&& incomingFrameGeometry().contains(pos)) {
392+
showIncomingVolumeMenu(event->globalPos());
393+
return base::EventFilterResult::Cancel;
394+
}
395+
}
396+
return base::EventFilterResult::Continue;
397+
});
379398
}
380399

381400
void Panel::initControls() {
@@ -1041,6 +1060,71 @@ void Panel::initMediaDeviceToggles() {
10411060
});
10421061
}
10431062

1063+
void Panel::showIncomingVolumeMenu(QPoint globalPos) {
1064+
if (!_call || _incomingVolumeMenu) {
1065+
return;
1066+
}
1067+
1068+
_incomingVolumeMenu = base::make_unique_q<Ui::PopupMenu>(
1069+
widget(),
1070+
st::groupCallPopupMenuWithVolume);
1071+
1072+
const auto menu = _incomingVolumeMenu.get();
1073+
const auto call = _call;
1074+
const auto peer = _user;
1075+
const auto startVolume = call->playbackVolume();
1076+
const auto startMuted = call->playbackMuted();
1077+
1078+
auto stateProducer = rpl::combine(
1079+
call->playbackVolumeValue(),
1080+
call->playbackMutedValue()
1081+
) | rpl::map([peer](int volume, bool muted) {
1082+
return Group::ParticipantState{
1083+
.peer = peer,
1084+
.volume = volume,
1085+
.mutedByMe = muted,
1086+
};
1087+
});
1088+
1089+
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
1090+
menu->menu(),
1091+
st::groupCallPopupVolumeMenu,
1092+
st::groupCallMenuVolumeSlider,
1093+
std::move(stateProducer),
1094+
startVolume,
1095+
Group::kMaxVolume,
1096+
startMuted,
1097+
st::groupCallMenuVolumePadding);
1098+
1099+
volumeItem->toggleMuteRequests(
1100+
) | rpl::on_next([=](bool muted) {
1101+
call->setPlaybackMuted(muted);
1102+
if (muted) {
1103+
crl::on_main(menu, [=] {
1104+
menu->hideMenu();
1105+
});
1106+
}
1107+
}, volumeItem->lifetime());
1108+
1109+
volumeItem->toggleMuteLocallyRequests(
1110+
) | rpl::on_next([=](bool muted) {
1111+
call->setPlaybackMuted(muted);
1112+
}, volumeItem->lifetime());
1113+
1114+
volumeItem->changeVolumeRequests(
1115+
) | rpl::on_next([=](int volume) {
1116+
call->setPlaybackVolume(volume);
1117+
}, volumeItem->lifetime());
1118+
1119+
volumeItem->changeVolumeLocallyRequests(
1120+
) | rpl::on_next([=](int volume) {
1121+
call->setPlaybackVolume(volume);
1122+
}, volumeItem->lifetime());
1123+
1124+
menu->addAction(std::move(volumeItem));
1125+
_incomingVolumeMenu->popup(globalPos);
1126+
}
1127+
10441128
void Panel::showDevicesMenu(
10451129
not_null<QWidget*> button,
10461130
std::vector<DeviceSelection> types) {

Telegram/SourceFiles/calls/calls_panel.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ class Panel final
159159
void showDevicesMenu(
160160
not_null<QWidget*> button,
161161
std::vector<DeviceSelection> types);
162+
void showIncomingVolumeMenu(QPoint globalPos);
162163

163164
[[nodiscard]] QRect incomingFrameGeometry() const;
164165
[[nodiscard]] QRect outgoingFrameGeometry() const;
@@ -213,6 +214,7 @@ class Panel final
213214
bool _mouseInside = false;
214215

215216
base::unique_qptr<Ui::PopupMenu> _devicesMenu;
217+
base::unique_qptr<Ui::PopupMenu> _incomingVolumeMenu;
216218

217219
base::Timer _updateDurationTimer;
218220
base::Timer _updateOuterRippleTimer;

0 commit comments

Comments
 (0)