Skip to content

Commit a6ebc84

Browse files
Fix screenshare audio on mic mute and add incoming volume control
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. Reuses the existing MenuVolumeItem from group calls. Always wrap the ADM with MixingAudioDeviceModule so playback volume control is available regardless of loopback capture support.
1 parent 2f80db3 commit a6ebc84

5 files changed

Lines changed: 207 additions & 16 deletions

File tree

Telegram/SourceFiles/calls/calls_call.cpp

Lines changed: 78 additions & 5 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,42 @@ 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 (_screenAudioControl) {
1401+
_screenAudioControl->setPlaybackVolume(level);
1402+
}
1403+
}
1404+
1405+
int Call::playbackVolume() const {
1406+
return _playbackVolume.current();
1407+
}
1408+
1409+
void Call::setPlaybackMuted(bool muted) {
1410+
_playbackMuted = muted;
1411+
const auto level = muted
1412+
? 0.f
1413+
: (_playbackVolume.current() / 10000.f);
1414+
if (_screenAudioControl) {
1415+
_screenAudioControl->setPlaybackVolume(level);
1416+
}
1417+
}
1418+
1419+
bool Call::playbackMuted() const {
1420+
return _playbackMuted.current();
1421+
}
1422+
1423+
rpl::producer<int> Call::playbackVolumeValue() const {
1424+
return _playbackVolume.value();
1425+
}
1426+
1427+
rpl::producer<bool> Call::playbackMutedValue() const {
1428+
return _playbackMuted.value();
1429+
}
1430+
13841431
void Call::setAudioDuckingEnabled(bool enabled) {
13851432
if (_instance) {
13861433
_instance->setAudioOutputDuckingEnabled(enabled);
@@ -1399,6 +1446,10 @@ bool Call::isSharingScreen() const {
13991446
return _videoCaptureIsScreencast && isSharingVideo();
14001447
}
14011448

1449+
bool Call::screenSharingWithAudio() const {
1450+
return isSharingScreen() && _screenWithAudio;
1451+
}
1452+
14021453
QString Call::cameraSharingDeviceId() const {
14031454
return isSharingCamera() ? _videoCaptureDeviceId : QString();
14041455
}
@@ -1433,7 +1484,9 @@ void Call::toggleCameraSharing(bool enabled) {
14331484
}), true);
14341485
}
14351486

1436-
void Call::toggleScreenSharing(std::optional<QString> uniqueId) {
1487+
void Call::toggleScreenSharing(
1488+
std::optional<QString> uniqueId,
1489+
bool withAudio) {
14371490
if (!uniqueId) {
14381491
if (isSharingScreen()) {
14391492
if (_videoCapture) {
@@ -1443,13 +1496,32 @@ void Call::toggleScreenSharing(std::optional<QString> uniqueId) {
14431496
}
14441497
_videoCaptureDeviceId = QString();
14451498
_videoCaptureIsScreencast = false;
1499+
if (_screenWithAudio && _screenAudioControl) {
1500+
_screenAudioControl->setMicrophoneMuted(false);
1501+
_screenAudioControl->setLoopbackEnabled(false);
1502+
if (_muted.current() && _instance) {
1503+
_instance->setMuteMicrophone(true);
1504+
}
1505+
}
1506+
_screenWithAudio = false;
14461507
return;
1447-
} else if (screenSharingDeviceId() == *uniqueId) {
1508+
} else if (screenSharingDeviceId() == *uniqueId
1509+
&& _screenWithAudio == withAudio) {
14481510
return;
14491511
}
14501512
toggleCameraSharing(false);
14511513
_videoCaptureIsScreencast = true;
14521514
_videoCaptureDeviceId = *uniqueId;
1515+
_screenWithAudio = withAudio;
1516+
if (_screenAudioControl) {
1517+
_screenAudioControl->setLoopbackEnabled(withAudio);
1518+
if (withAudio && _muted.current()) {
1519+
_screenAudioControl->setMicrophoneMuted(true);
1520+
if (_instance) {
1521+
_instance->setMuteMicrophone(false);
1522+
}
1523+
}
1524+
}
14531525
if (_videoCapture) {
14541526
_videoCapture->switchToDevice(uniqueId->toStdString(), true);
14551527
if (_instance) {
@@ -1615,6 +1687,7 @@ void Call::handleControllerError(const QString &error) {
16151687
}
16161688

16171689
void Call::destroyController() {
1690+
_screenAudioControl = nullptr;
16181691
_instanceLifetime.destroy();
16191692
Core::App().mediaDevices().setCaptureMuteTracker(this, false);
16201693

Telegram/SourceFiles/calls/calls_call.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ namespace Webrtc {
3636
enum class VideoState;
3737
class VideoTrack;
3838
struct DeviceResolvedId;
39+
class MixingAudioControl;
3940
} // namespace Webrtc
4041

4142
namespace Calls {
42-
4343
struct StartConferenceInfo;
4444

4545
struct DhConfig {
@@ -242,17 +242,27 @@ class Call final
242242
//void setAudioVolume(bool input, float level);
243243
void setAudioDuckingEnabled(bool enabled);
244244

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

249256
[[nodiscard]] bool isSharingVideo() const;
250257
[[nodiscard]] bool isSharingCamera() const;
251258
[[nodiscard]] bool isSharingScreen() const;
259+
[[nodiscard]] bool screenSharingWithAudio() const;
252260
[[nodiscard]] QString cameraSharingDeviceId() const;
253261
[[nodiscard]] QString screenSharingDeviceId() const;
254262
void toggleCameraSharing(bool enabled);
255-
void toggleScreenSharing(std::optional<QString> uniqueId);
263+
void toggleScreenSharing(
264+
std::optional<QString> uniqueId,
265+
bool withAudio = false);
256266
[[nodiscard]] auto peekVideoCapture() const
257267
-> std::shared_ptr<tgcalls::VideoCaptureInterface>;
258268

@@ -363,9 +373,13 @@ class Call final
363373
std::vector<not_null<PeerData*>> _conferenceParticipants;
364374

365375
std::unique_ptr<tgcalls::Instance> _instance;
376+
std::shared_ptr<Webrtc::MixingAudioControl> _screenAudioControl;
366377
std::shared_ptr<tgcalls::VideoCaptureInterface> _videoCapture;
367378
QString _videoCaptureDeviceId;
368379
bool _videoCaptureIsScreencast = false;
380+
bool _screenWithAudio = false;
381+
rpl::variable<int> _playbackVolume = 10000;
382+
rpl::variable<bool> _playbackMuted = false;
369383
const std::unique_ptr<Webrtc::VideoTrack> _videoIncoming;
370384
const std::unique_ptr<Webrtc::VideoTrack> _videoOutgoing;
371385

Telegram/SourceFiles/calls/calls_panel.cpp

Lines changed: 110 additions & 8 deletions
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"
@@ -375,6 +379,21 @@ void Panel::initWidget() {
375379
) | rpl::skip(1) | rpl::on_next([=] {
376380
updateControlsGeometry();
377381
}, lifetime());
382+
383+
base::install_event_filter(widget(), widget(), [=](
384+
not_null<QEvent*> e) {
385+
if (e->type() == QEvent::ContextMenu) {
386+
const auto event = static_cast<QContextMenuEvent*>(e.get());
387+
const auto pos = event->pos();
388+
if (_incoming
389+
&& _incoming->widget()->isVisible()
390+
&& incomingFrameGeometry().contains(pos)) {
391+
showIncomingVolumeMenu(event->globalPos());
392+
return base::EventFilterResult::Cancel;
393+
}
394+
}
395+
return base::EventFilterResult::Continue;
396+
});
378397
}
379398

380399
void Panel::initControls() {
@@ -395,6 +414,28 @@ void Panel::initControls() {
395414
} else if (const auto source = env->uniqueDesktopCaptureSource()) {
396415
if (!chooseSourceActiveDeviceId().isEmpty()) {
397416
chooseSourceStop();
417+
} else if (Webrtc::LoopbackAudioCaptureSupported()) {
418+
const auto sourceId = *source;
419+
uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> box) {
420+
box->setTitle(
421+
tr::lng_group_call_screen_share_start());
422+
const auto withAudio = box->addRow(
423+
object_ptr<Ui::Checkbox>(
424+
box,
425+
tr::lng_group_call_screen_share_audio(
426+
tr::now),
427+
false));
428+
box->addButton(
429+
tr::lng_group_call_screen_share_start(),
430+
[=] {
431+
const auto audio = withAudio->checked();
432+
box->closeBox();
433+
chooseSourceAccepted(sourceId, audio);
434+
});
435+
box->addButton(
436+
tr::lng_cancel(),
437+
[=] { box->closeBox(); });
438+
}));
398439
} else {
399440
chooseSourceAccepted(*source, false);
400441
}
@@ -564,15 +605,11 @@ QString Panel::chooseSourceActiveDeviceId() {
564605
}
565606

566607
bool Panel::chooseSourceActiveWithAudio() {
567-
return false;// _call->screenSharingWithAudio();
608+
return _call->screenSharingWithAudio();
568609
}
569610

570611
bool Panel::chooseSourceWithAudioSupported() {
571-
//#ifdef Q_OS_WIN
572-
// return true;
573-
//#else // Q_OS_WIN
574-
return false;
575-
//#endif // Q_OS_WIN
612+
return Webrtc::LoopbackAudioCaptureSupported();
576613
}
577614

578615
rpl::lifetime &Panel::chooseSourceInstanceLifetime() {
@@ -589,7 +626,7 @@ rpl::producer<bool> Panel::startOutgoingRequests() const {
589626
void Panel::chooseSourceAccepted(
590627
const QString &deviceId,
591628
bool withAudio) {
592-
_call->toggleScreenSharing(deviceId/*, withAudio*/);
629+
_call->toggleScreenSharing(deviceId, withAudio);
593630
}
594631

595632
void Panel::chooseSourceStop() {
@@ -1038,6 +1075,71 @@ void Panel::initMediaDeviceToggles() {
10381075
});
10391076
}
10401077

1078+
void Panel::showIncomingVolumeMenu(QPoint globalPos) {
1079+
if (!_call || _incomingVolumeMenu) {
1080+
return;
1081+
}
1082+
1083+
_incomingVolumeMenu = base::make_unique_q<Ui::PopupMenu>(
1084+
widget(),
1085+
st::groupCallPopupMenuWithVolume);
1086+
1087+
const auto menu = _incomingVolumeMenu.get();
1088+
const auto call = _call;
1089+
const auto peer = _user;
1090+
const auto startVolume = call->playbackVolume();
1091+
const auto startMuted = call->playbackMuted();
1092+
1093+
auto stateProducer = rpl::combine(
1094+
call->playbackVolumeValue(),
1095+
call->playbackMutedValue()
1096+
) | rpl::map([peer](int volume, bool muted) {
1097+
return Group::ParticipantState{
1098+
.peer = peer,
1099+
.volume = volume,
1100+
.mutedByMe = muted,
1101+
};
1102+
});
1103+
1104+
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
1105+
menu->menu(),
1106+
st::groupCallPopupVolumeMenu,
1107+
st::groupCallMenuVolumeSlider,
1108+
std::move(stateProducer),
1109+
startVolume,
1110+
Group::kMaxVolume,
1111+
startMuted,
1112+
st::groupCallMenuVolumePadding);
1113+
1114+
volumeItem->toggleMuteRequests(
1115+
) | rpl::on_next([=](bool muted) {
1116+
call->setPlaybackMuted(muted);
1117+
if (muted) {
1118+
crl::on_main(menu, [=] {
1119+
menu->hideMenu();
1120+
});
1121+
}
1122+
}, volumeItem->lifetime());
1123+
1124+
volumeItem->toggleMuteLocallyRequests(
1125+
) | rpl::on_next([=](bool muted) {
1126+
call->setPlaybackMuted(muted);
1127+
}, volumeItem->lifetime());
1128+
1129+
volumeItem->changeVolumeRequests(
1130+
) | rpl::on_next([=](int volume) {
1131+
call->setPlaybackVolume(volume);
1132+
}, volumeItem->lifetime());
1133+
1134+
volumeItem->changeVolumeLocallyRequests(
1135+
) | rpl::on_next([=](int volume) {
1136+
call->setPlaybackVolume(volume);
1137+
}, volumeItem->lifetime());
1138+
1139+
menu->addAction(std::move(volumeItem));
1140+
_incomingVolumeMenu->popup(globalPos);
1141+
}
1142+
10411143
void Panel::showDevicesMenu(
10421144
not_null<QWidget*> button,
10431145
std::vector<DeviceSelection> types) {

Telegram/SourceFiles/calls/calls_panel.h

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

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

214215
base::unique_qptr<Ui::PopupMenu> _devicesMenu;
216+
base::unique_qptr<Ui::PopupMenu> _incomingVolumeMenu;
215217

216218
base::Timer _updateDurationTimer;
217219
base::Timer _updateOuterRippleTimer;

0 commit comments

Comments
 (0)