From d3778f92d250d498d8b3e815e01ba20fe7ecbdb5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 22 Jan 2024 20:40:55 +0400 Subject: [PATCH] Implement media devices tracking on Windows. --- Telegram/SourceFiles/calls/calls_call.cpp | 78 ++-- Telegram/SourceFiles/calls/calls_call.h | 8 +- Telegram/SourceFiles/calls/calls_instance.cpp | 18 +- Telegram/SourceFiles/calls/calls_instance.h | 2 - .../calls/group/calls_group_call.cpp | 78 ++-- .../calls/group/calls_group_call.h | 11 +- .../calls/group/calls_group_settings.cpp | 56 +-- Telegram/SourceFiles/core/application.cpp | 2 + Telegram/SourceFiles/core/application.h | 8 + Telegram/SourceFiles/core/core_settings.cpp | 71 +-- Telegram/SourceFiles/core/core_settings.h | 82 +++- .../SourceFiles/media/audio/media_audio.cpp | 7 +- .../media/audio/media_audio_track.cpp | 17 +- .../media/audio/media_audio_track.h | 10 +- .../SourceFiles/settings/settings_calls.cpp | 430 +++++++++++------- .../SourceFiles/settings/settings_calls.h | 21 +- .../details/storage_settings_scheme.cpp | 4 +- Telegram/lib_webrtc | 2 +- 18 files changed, 551 insertions(+), 354 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 471468ee3..7f34d9793 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -215,6 +215,22 @@ Call::Call( , _api(&_user->session().mtp()) , _type(type) , _discardByTimeoutTimer([=] { hangup(); }) +, _playbackDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Playback, + Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callPlaybackDeviceIdValue(), + Core::App().settings().playbackDeviceIdValue())) +, _captureDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Capture, + Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callCaptureDeviceIdValue(), + Core::App().settings().captureDeviceIdValue())) +, _cameraDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Camera, + Core::App().settings().cameraDeviceIdValue()) , _videoIncoming( std::make_unique( StartVideoState(video))) @@ -228,6 +244,7 @@ Call::Call( _discardByTimeoutTimer.callOnce(config.callRingTimeoutMs); startWaitingTrack(); } + setupMediaDevices(); setupOutgoingVideo(); } @@ -410,6 +427,20 @@ void Call::setMuted(bool mute) { } } +void Call::setupMediaDevices() { + _playbackDeviceId.changes() | rpl::filter([=] { + return _instance != nullptr; + }) | rpl::start_with_next([=](const QString &deviceId) { + _instance->setAudioOutputDevice(deviceId.toStdString()); + }, _lifetime); + + _captureDeviceId.changes() | rpl::filter([=] { + return _instance != nullptr; + }) | rpl::start_with_next([=](const QString &deviceId) { + _instance->setAudioInputDevice(deviceId.toStdString()); + }, _lifetime); +} + void Call::setupOutgoingVideo() { static const auto hasDevices = [] { return !Webrtc::GetVideoInputList().empty(); @@ -455,6 +486,19 @@ void Call::setupOutgoingVideo() { } } }, _lifetime); + + _cameraDeviceId.changes( + ) | rpl::filter([=] { + return !_videoCaptureIsScreencast; + }) | rpl::start_with_next([=](QString deviceId) { + _videoCaptureDeviceId = deviceId; + if (_videoCapture) { + _videoCapture->switchToDevice(deviceId.toStdString(), false); + if (_instance) { + _instance->sendVideoDeviceUpdated(); + } + } + }, _lifetime); } not_null Call::videoIncoming() const { @@ -866,8 +910,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) { std::move(encryptionKeyValue), (_type == Type::Outgoing)), .mediaDevicesConfig = tgcalls::MediaDevicesConfig{ - .audioInputId = settings.callInputDeviceId().toStdString(), - .audioOutputId = settings.callOutputDeviceId().toStdString(), + .audioInputId = _captureDeviceId.current().toStdString(), + .audioOutputId = _playbackDeviceId.current().toStdString(), .inputVolume = 1.f,//settings.callInputVolume() / 100.f, .outputVolume = 1.f,//settings.callOutputVolume() / 100.f, }, @@ -1096,29 +1140,6 @@ void Call::setState(State state) { } } -void Call::setCurrentAudioDevice(bool input, const QString &deviceId) { - if (_instance) { - const auto id = deviceId.toStdString(); - if (input) { - _instance->setAudioInputDevice(id); - } else { - _instance->setAudioOutputDevice(id); - } - } -} - -void Call::setCurrentCameraDevice(const QString &deviceId) { - if (!_videoCaptureIsScreencast) { - _videoCaptureDeviceId = deviceId; - if (_videoCapture) { - _videoCapture->switchToDevice(deviceId.toStdString(), false); - if (_instance) { - _instance->sendVideoDeviceUpdated(); - } - } - } -} - //void Call::setAudioVolume(bool input, float level) { // if (_instance) { // if (input) { @@ -1168,10 +1189,11 @@ void Call::toggleCameraSharing(bool enabled) { } _delegate->callRequestPermissionsOrFail(crl::guard(this, [=] { toggleScreenSharing(std::nullopt); - const auto deviceId = Core::App().settings().callVideoInputDeviceId(); - _videoCaptureDeviceId = deviceId; + _videoCaptureDeviceId = _cameraDeviceId.current(); if (_videoCapture) { - _videoCapture->switchToDevice(deviceId.toStdString(), false); + _videoCapture->switchToDevice( + _videoCaptureDeviceId.toStdString(), + false); if (_instance) { _instance->sendVideoDeviceUpdated(); } diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 1a72983e1..00900ff60 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/bytes.h" #include "mtproto/sender.h" #include "mtproto/mtproto_auth_key.h" +#include "webrtc/webrtc_device_id.h" namespace Media { namespace Audio { @@ -190,11 +191,9 @@ public: QString getDebugLog() const; - void setCurrentAudioDevice(bool input, const QString &deviceId); //void setAudioVolume(bool input, float level); void setAudioDuckingEnabled(bool enabled); - void setCurrentCameraDevice(const QString &deviceId); [[nodiscard]] QString videoDeviceId() const { return _videoCaptureDeviceId; } @@ -250,6 +249,7 @@ private: void setSignalBarCount(int count); void destroyController(); + void setupMediaDevices(); void setupOutgoingVideo(); void updateRemoteMediaState( tgcalls::AudioState audio, @@ -271,6 +271,10 @@ private: base::DelayedCallTimer _finishByTimeoutTimer; base::Timer _discardByTimeoutTimer; + Webrtc::DeviceId _playbackDeviceId; + Webrtc::DeviceId _captureDeviceId; + Webrtc::DeviceId _cameraDeviceId; + rpl::variable _muted = false; DhConfig _dhConfig; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index d26140916..118532d16 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -522,20 +522,6 @@ void Instance::showInfoPanel(not_null call) { } } -void Instance::setCurrentAudioDevice(bool input, const QString &deviceId) { - if (input) { - Core::App().settings().setCallInputDeviceId(deviceId); - } else { - Core::App().settings().setCallOutputDeviceId(deviceId); - } - Core::App().saveSettingsDelayed(); - if (const auto call = currentCall()) { - call->setCurrentAudioDevice(input, deviceId); - } else if (const auto group = currentGroupCall()) { - group->setCurrentAudioDevice(input, deviceId); - } -} - FnMut Instance::addAsyncWaiter() { auto semaphore = std::make_unique(); const auto raw = semaphore.get(); @@ -846,7 +832,7 @@ std::shared_ptr Instance::getVideoCapture( if (deviceId) { result->switchToDevice( (deviceId->isEmpty() - ? Core::App().settings().callVideoInputDeviceId() + ? Core::App().settings().cameraDeviceId() : *deviceId).toStdString(), isScreenCapture); } @@ -854,7 +840,7 @@ std::shared_ptr Instance::getVideoCapture( } const auto startDeviceId = (deviceId && !deviceId->isEmpty()) ? *deviceId - : Core::App().settings().callVideoInputDeviceId(); + : Core::App().settings().cameraDeviceId(); auto result = std::shared_ptr( tgcalls::VideoCaptureInterface::Create( tgcalls::StaticThreads::getThreads(), diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index e15d800be..d52a0e9a2 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -103,8 +103,6 @@ public: -> std::shared_ptr; void requestPermissionsOrFail(Fn onSuccess, bool video = true); - void setCurrentAudioDevice(bool input, const QString &deviceId); - [[nodiscard]] FnMut addAsyncWaiter(); [[nodiscard]] bool isSharingScreen() const; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 5ec38443d..1d40998c1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -29,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/global_shortcuts.h" #include "base/random.h" #include "webrtc/webrtc_video_track.h" -#include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_create_adm.h" #include @@ -52,14 +51,6 @@ constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000); constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums. constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. -[[nodiscard]] std::unique_ptr CreateMediaDevices() { - const auto &settings = Core::App().settings(); - return Webrtc::CreateMediaDevices( - settings.callInputDeviceId(), - settings.callOutputDeviceId(), - settings.callVideoInputDeviceId()); -} - [[nodiscard]] const Data::GroupCallParticipant *LookupParticipant( not_null peer, CallId id, @@ -590,12 +581,27 @@ GroupCall::GroupCall( , _scheduleDate(info.scheduleDate) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) +, _playbackDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Playback, + Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callPlaybackDeviceIdValue(), + Core::App().settings().playbackDeviceIdValue())) +, _captureDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Capture, + Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callCaptureDeviceIdValue(), + Core::App().settings().captureDeviceIdValue())) +, _cameraDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Camera, + Webrtc::DeviceIdOrDefault(Core::App().settings().cameraDeviceIdValue())) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) , _connectingSoundTimer([=] { playConnectingSoundOnce(); }) , _listenersHidden(info.rtmp) , _rtmp(info.rtmp) -, _rtmpVolume(Group::kDefaultVolume) -, _mediaDevices(CreateMediaDevices()) { +, _rtmpVolume(Group::kDefaultVolume) { _muted.value( ) | rpl::combine_previous( ) | rpl::start_with_next([=](MuteState previous, MuteState state) { @@ -2058,28 +2064,22 @@ void GroupCall::applyOtherParticipantUpdate( } void GroupCall::setupMediaDevices() { - _mediaDevices->audioInputId( - ) | rpl::start_with_next([=](QString id) { - _audioInputId = id; - if (_instance) { - _instance->setAudioInputDevice(id.toStdString()); - } + _playbackDeviceId.changes() | rpl::filter([=] { + return _instance != nullptr; + }) | rpl::start_with_next([=](const QString &deviceId) { + _instance->setAudioOutputDevice(deviceId.toStdString()); }, _lifetime); - _mediaDevices->audioOutputId( - ) | rpl::start_with_next([=](QString id) { - _audioOutputId = id; - if (_instance) { - _instance->setAudioOutputDevice(id.toStdString()); - } + _captureDeviceId.changes() | rpl::filter([=] { + return _instance != nullptr; + }) | rpl::start_with_next([=](const QString &deviceId) { + _instance->setAudioInputDevice(deviceId.toStdString()); }, _lifetime); - _mediaDevices->videoInputId( - ) | rpl::start_with_next([=](QString id) { - _cameraInputId = id; - if (_cameraCapture) { - _cameraCapture->switchToDevice(id.toStdString(), false); - } + _cameraDeviceId.changes() | rpl::filter([=] { + return _cameraCapture != nullptr; + }) | rpl::start_with_next([=](const QString &deviceId) { + _cameraCapture->switchToDevice(deviceId.toStdString(), false); }, _lifetime); } @@ -2117,7 +2117,7 @@ bool GroupCall::emitShareCameraError() { return emitError(Error::DisabledNoCamera); } else if (mutedByAdmin()) { return emitError(Error::MutedNoCamera); - } else if (Webrtc::GetVideoInputList().empty()) { + } else if (_cameraDeviceId.current().isEmpty()) { return emitError(Error::NoCamera); } return false; @@ -2126,7 +2126,7 @@ bool GroupCall::emitShareCameraError() { void GroupCall::emitShareCameraError(Error error) { _cameraState = Webrtc::VideoState::Inactive; if (error == Error::CameraFailed - && Webrtc::GetVideoInputList().empty()) { + && _cameraDeviceId.current().isEmpty()) { error = Error::NoCamera; } _errors.fire_copy(error); @@ -2180,7 +2180,7 @@ void GroupCall::setupOutgoingVideo() { return; } else if (!_cameraCapture) { _cameraCapture = _delegate->groupCallGetVideoCapture( - _cameraInputId); + _cameraDeviceId.current()); if (!_cameraCapture) { return emitShareCameraError(Error::CameraFailed); } @@ -2192,7 +2192,7 @@ void GroupCall::setupOutgoingVideo() { }); } else { _cameraCapture->switchToDevice( - _cameraInputId.toStdString(), + _cameraDeviceId.current().toStdString(), false); } if (_instance) { @@ -2360,8 +2360,8 @@ bool GroupCall::tryCreateController() { } crl::on_main(weak, [=] { audioLevelsUpdated(data); }); }, - .initialInputDeviceId = _audioInputId.toStdString(), - .initialOutputDeviceId = _audioOutputId.toStdString(), + .initialInputDeviceId = _captureDeviceId.current().toStdString(), + .initialOutputDeviceId = _playbackDeviceId.current().toStdString(), .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( settings.callAudioBackend()), .videoCapture = _cameraCapture, @@ -3290,14 +3290,6 @@ void GroupCall::requestVideoQuality( updateRequestedVideoChannelsDelayed(); } -void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { - if (input) { - _mediaDevices->switchToAudioInput(deviceId); - } else { - _mediaDevices->switchToAudioOutput(deviceId); - } -} - void GroupCall::toggleMute(const Group::MuteRequest &data) { if (_rtmp) { _rtmpVolume = data.mute ? 0 : Group::kDefaultVolume; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 8b8eb34ca..27ee36ba3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/bytes.h" #include "mtproto/sender.h" #include "mtproto/mtproto_auth_key.h" +#include "webrtc/webrtc_device_id.h" class History; @@ -381,7 +382,6 @@ public: return _videoIsWorking.value(); } - void setCurrentAudioDevice(bool input, const QString &deviceId); [[nodiscard]] bool isSharingScreen() const; [[nodiscard]] rpl::producer isSharingScreenValue() const; [[nodiscard]] bool isScreenPaused() const; @@ -667,6 +667,10 @@ private: crl::time _lastSendProgressUpdate = 0; + Webrtc::DeviceId _playbackDeviceId; + Webrtc::DeviceId _captureDeviceId; + Webrtc::DeviceId _cameraDeviceId; + std::shared_ptr _shortcutManager; std::shared_ptr _pushToTalk; base::Timer _pushToTalkCancelTimer; @@ -677,11 +681,6 @@ private: bool _reloadedStaleCall = false; int _rtmpVolume = 0; - std::unique_ptr _mediaDevices; - QString _audioInputId; - QString _audioOutputId; - QString _cameraInputId; - rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 4a7b50458..29e60da71 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -250,8 +250,6 @@ void SettingsBox( const auto weakBox = Ui::MakeWeak(box); struct State { - rpl::event_stream outputNameStream; - rpl::event_stream inputNameStream; std::unique_ptr micTester; Ui::LevelMeter *micTestLevel = nullptr; float micLevel = 0.; @@ -295,42 +293,46 @@ void SettingsBox( Ui::AddSkip(layout); } + auto playbackIdWithFallback = Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callPlaybackDeviceIdValue(), + Core::App().settings().playbackDeviceIdValue()); AddButtonWithLabel( layout, tr::lng_group_call_speakers(), - rpl::single( - CurrentAudioOutputName() - ) | rpl::then( - state->outputNameStream.events() - ), + PlaybackDeviceNameValue(rpl::duplicate(playbackIdWithFallback)), st::groupCallSettingsButton )->addClickHandler([=] { - box->getDelegate()->show(ChooseAudioOutputBox(crl::guard(box, [=]( - const QString &id, - const QString &name) { - state->outputNameStream.fire_copy(name); - }), &st::groupCallCheckbox, &st::groupCallRadio)); + box->getDelegate()->show(ChoosePlaybackDeviceBox( + rpl::duplicate(playbackIdWithFallback), + crl::guard(box, [=](const QString &id) { + Core::App().settings().setCallPlaybackDeviceId(id); + Core::App().saveSettingsDelayed(); + }), + &st::groupCallCheckbox, + &st::groupCallRadio)); }); if (!rtmp) { + auto captureIdWithFallback = Webrtc::DeviceIdValueWithFallback( + Core::App().settings().callCaptureDeviceIdValue(), + Core::App().settings().captureDeviceIdValue()); AddButtonWithLabel( layout, tr::lng_group_call_microphone(), - rpl::single( - CurrentAudioInputName() - ) | rpl::then( - state->inputNameStream.events() - ), + CaptureDeviceNameValue(rpl::duplicate(captureIdWithFallback)), 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)); + box->getDelegate()->show(ChooseCaptureDeviceBox( + rpl::duplicate(captureIdWithFallback), + crl::guard(box, [=](const QString &id) { + Core::App().settings().setCallCaptureDeviceId(id); + Core::App().saveSettingsDelayed(); + if (state->micTester) { + state->micTester->setDeviceId(id); + } + }), + &st::groupCallCheckbox, + &st::groupCallRadio)); }); state->micTestLevel = box->addRow( @@ -773,7 +775,7 @@ void SettingsBox( crl::on_main(box, [=] { state->micTester = std::make_unique( Core::App().settings().callAudioBackend(), - Core::App().settings().callInputDeviceId()); + Core::App().settings().callCaptureDeviceId()); state->levelUpdateTimer.callEach(kMicTestUpdateInterval); }); }); @@ -883,7 +885,7 @@ MicLevelTester::MicLevelTester(Fn show) , _tester( std::make_unique( Core::App().settings().callAudioBackend(), - Core::App().settings().callInputDeviceId())) { + Core::App().settings().callCaptureDeviceId())) { _timer.callEach(kMicrophoneTooltipCheckInterval); } diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 3ed87c893..7e7dadbd4 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -88,6 +88,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "payments/payments_checkout_process.h" #include "export/export_manager.h" +#include "webrtc/webrtc_environment.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "boxes/abstract_box.h" @@ -150,6 +151,7 @@ Application::Application() , _private(std::make_unique()) , _platformIntegration(Platform::Integration::Create()) , _batterySaving(std::make_unique()) +, _mediaDevices(std::make_unique()) , _databases(std::make_unique()) , _animationsManager(std::make_unique()) , _clearEmojiImageLoaderTimer([=] { clearEmojiSourceImages(); }) diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index f9059da8d..b06e02074 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -101,6 +101,10 @@ namespace Calls { class Instance; } // namespace Calls +namespace Webrtc { +class Environment; +} // namespace Webrtc + namespace Core { struct LocalUrlHandler; @@ -238,6 +242,9 @@ public: [[nodiscard]] Media::Audio::Instance &audio() { return *_audio; } + [[nodiscard]] Webrtc::Environment &mediaDevices() { + return *_mediaDevices; + } // Langpack and emoji keywords. [[nodiscard]] Lang::Instance &langpack() { @@ -383,6 +390,7 @@ private: const std::unique_ptr _private; const std::unique_ptr _platformIntegration; const std::unique_ptr _batterySaving; + const std::unique_ptr _mediaDevices; const std::unique_ptr _databases; const std::unique_ptr _animationsManager; diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 0ab441fe0..0db565716 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/gl/gl_detection.h" #include "ui/widgets/fields/input_field.h" #include "webrtc/webrtc_create_adm.h" +#include "webrtc/webrtc_device_common.h" #include "window/section_widget.h" namespace Core { @@ -159,8 +160,8 @@ QByteArray Settings::serialize() const { + Serialize::stringSize(_downloadPath.current()) + Serialize::bytearraySize(_downloadPathBookmark) + sizeof(qint32) * 9 - + Serialize::stringSize(_callOutputDeviceId) - + Serialize::stringSize(_callInputDeviceId) + + Serialize::stringSize(_callPlaybackDeviceId.current()) + + Serialize::stringSize(_callCaptureDeviceId.current()) + sizeof(qint32) * 5; for (const auto &[key, value] : _soundOverrides) { size += Serialize::stringSize(key) + Serialize::stringSize(value); @@ -170,7 +171,7 @@ QByteArray Settings::serialize() const { + sizeof(qint32) + (_dictionariesEnabled.current().size() * sizeof(quint64)) + sizeof(qint32) * 12 - + Serialize::stringSize(_callVideoInputDeviceId) + + Serialize::stringSize(_cameraDeviceId.current()) + sizeof(qint32) * 2 + Serialize::bytearraySize(_groupCallPushToTalkShortcut) + sizeof(qint64) @@ -194,7 +195,7 @@ QByteArray Settings::serialize() const { + (_accountsOrder.size() * sizeof(quint64)) + sizeof(qint32) * 7 + (skipLanguages.size() * sizeof(quint64)) - + sizeof(qint32) + + sizeof(qint32) * 2 + sizeof(quint64) + sizeof(qint32) * 3 + Serialize::bytearraySize(mediaViewPosition) @@ -204,6 +205,9 @@ QByteArray Settings::serialize() const { for (const auto &id : _recentEmojiSkip) { size += Serialize::stringSize(id); } + size += sizeof(qint32) * 2 + + Serialize::stringSize(_playbackDeviceId.current()) + + Serialize::stringSize(_captureDeviceId.current()); auto result = QByteArray(); result.reserve(size); @@ -228,8 +232,8 @@ QByteArray Settings::serialize() const { << qint32(_notificationsCount) << static_cast(_notificationsCorner) << qint32(_autoLock) - << _callOutputDeviceId - << _callInputDeviceId + << _callPlaybackDeviceId.current() + << _callCaptureDeviceId.current() << qint32(_callOutputVolume) << qint32(_callInputVolume) << qint32(_callAudioDuckingEnabled ? 1 : 0) @@ -273,7 +277,7 @@ QByteArray Settings::serialize() const { << qint32(_notifyFromAll ? 1 : 0) << qint32(_nativeWindowFrame.current() ? 1 : 0) << qint32(_systemDarkModeEnabled.current() ? 1 : 0) - << _callVideoInputDeviceId + << _cameraDeviceId.current() << qint32(_ipRevealWarning ? 1 : 0) << qint32(_groupCallPushToTalk ? 1 : 0) << _groupCallPushToTalkShortcut @@ -327,9 +331,7 @@ QByteArray Settings::serialize() const { } stream - << qint32(_rememberedDeleteMessageOnlyForYou ? 1 : 0); - - stream + << qint32(_rememberedDeleteMessageOnlyForYou ? 1 : 0) << qint32(_translateChatEnabled.current() ? 1 : 0) << quint64(QLocale::Language(_translateToRaw.current())) << qint32(_windowTitleContent.current().hideChatName ? 1 : 0) @@ -339,14 +341,18 @@ QByteArray Settings::serialize() const { << qint32(_ignoreBatterySaving.current() ? 1 : 0) << quint64(_macRoundIconDigest.value_or(0)) << qint32(_storiesClickTooltipHidden.current() ? 1 : 0) - << qint32(_recentEmojiSkip.size()) - << qint32(_ttlVoiceClickTooltipHidden.current() ? 1 : 0); + << qint32(_recentEmojiSkip.size()); for (const auto &id : _recentEmojiSkip) { stream << id; } stream - << qint32(_trayIconMonochrome.current() ? 1 : 0); + << qint32(_trayIconMonochrome.current() ? 1 : 0) + << qint32(_ttlVoiceClickTooltipHidden.current() ? 1 : 0) + << _playbackDeviceId.current() + << _captureDeviceId.current(); } + + Ensures(result.size() == size); return result; } @@ -375,9 +381,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 notificationsCount = _notificationsCount; qint32 notificationsCorner = static_cast(_notificationsCorner); qint32 autoLock = _autoLock; - QString callOutputDeviceId = _callOutputDeviceId; - QString callInputDeviceId = _callInputDeviceId; - QString callVideoInputDeviceId = _callVideoInputDeviceId; + QString playbackDeviceId = _playbackDeviceId.current(); + QString captureDeviceId = _captureDeviceId.current(); + QString cameraDeviceId = _cameraDeviceId.current(); + QString callPlaybackDeviceId = _callPlaybackDeviceId.current(); + QString callCaptureDeviceId = _callCaptureDeviceId.current(); qint32 callOutputVolume = _callOutputVolume; qint32 callInputVolume = _callInputVolume; qint32 callAudioDuckingEnabled = _callAudioDuckingEnabled ? 1 : 0; @@ -475,8 +483,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) { >> notificationsCount >> notificationsCorner >> autoLock - >> callOutputDeviceId - >> callInputDeviceId + >> callPlaybackDeviceId + >> callCaptureDeviceId >> callOutputVolume >> callInputVolume >> callAudioDuckingEnabled @@ -539,7 +547,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { stream >> systemDarkModeEnabled; } if (!stream.atEnd()) { - stream >> callVideoInputDeviceId; + stream >> cameraDeviceId; } if (!stream.atEnd()) { stream >> ipRevealWarning; @@ -666,7 +674,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) { }); } } - + } + if (!stream.atEnd()) { stream >> rememberedDeleteMessageOnlyForYou; } if (!stream.atEnd()) { @@ -714,6 +723,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> ttlVoiceClickTooltipHidden; } + if (!stream.atEnd()) { + stream + >> playbackDeviceId + >> captureDeviceId; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -757,9 +771,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _countUnreadMessages = (countUnreadMessages == 1); _notifyAboutPinned = (notifyAboutPinned == 1); _autoLock = autoLock; - _callOutputDeviceId = callOutputDeviceId; - _callInputDeviceId = callInputDeviceId; - _callVideoInputDeviceId = callVideoInputDeviceId; + _playbackDeviceId = playbackDeviceId; + _captureDeviceId = captureDeviceId; + const auto kOldDefault = u"default"_q; + _cameraDeviceId = cameraDeviceId; + _callPlaybackDeviceId = callPlaybackDeviceId; + _callCaptureDeviceId = callCaptureDeviceId; _callOutputVolume = callOutputVolume; _callInputVolume = callInputVolume; _callAudioDuckingEnabled = (callAudioDuckingEnabled == 1); @@ -1216,9 +1233,11 @@ void Settings::resetOnLastLogout() { _notifyAboutPinned = true; //_autoLock = 3600; - //_callOutputDeviceId = u"default"_q; - //_callInputDeviceId = u"default"_q; - //_callVideoInputDeviceId = u"default"_q; + //_playbackDeviceId = QString(); + //_captureDeviceId = QString(); + //_cameraDeviceId = QString(); + //_callPlaybackDeviceId = QString(); + //_callCaptureDeviceId = QString(); //_callOutputVolume = 100; //_callInputVolume = 100; //_callAudioDuckingEnabled = true; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 5afab71f9..6e3f53564 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -263,30 +263,68 @@ public: void setAutoLock(int value) { _autoLock = value; } - [[nodiscard]] QString callOutputDeviceId() const { - return _callOutputDeviceId.isEmpty() - ? u"default"_q - : _callOutputDeviceId; + + [[nodiscard]] QString playbackDeviceId() const { + return _playbackDeviceId.current(); } - void setCallOutputDeviceId(const QString &value) { - _callOutputDeviceId = value; + [[nodiscard]] rpl::producer playbackDeviceIdChanges() const { + return _playbackDeviceId.changes(); } - [[nodiscard]] QString callInputDeviceId() const { - return _callInputDeviceId.isEmpty() - ? u"default"_q - : _callInputDeviceId; + [[nodiscard]] rpl::producer playbackDeviceIdValue() const { + return _playbackDeviceId.value(); } - void setCallInputDeviceId(const QString &value) { - _callInputDeviceId = value; + void setPlaybackDeviceId(const QString &value) { + _playbackDeviceId = value; } - [[nodiscard]] QString callVideoInputDeviceId() const { - return _callVideoInputDeviceId.isEmpty() - ? u"default"_q - : _callVideoInputDeviceId; + [[nodiscard]] QString captureDeviceId() const { + return _captureDeviceId.current(); } - void setCallVideoInputDeviceId(const QString &value) { - _callVideoInputDeviceId = value; + [[nodiscard]] rpl::producer captureDeviceIdChanges() const { + return _captureDeviceId.changes(); } + [[nodiscard]] rpl::producer captureDeviceIdValue() const { + return _captureDeviceId.value(); + } + void setCaptureDeviceId(const QString &value) { + _captureDeviceId = value; + } + [[nodiscard]] QString cameraDeviceId() const { + return _cameraDeviceId.current(); + } + [[nodiscard]] rpl::producer cameraDeviceIdChanges() const { + return _cameraDeviceId.changes(); + } + [[nodiscard]] rpl::producer cameraDeviceIdValue() const { + return _cameraDeviceId.value(); + } + void setCameraDeviceId(const QString &value) { + _cameraDeviceId = value; + } + [[nodiscard]] QString callPlaybackDeviceId() const { + return _callPlaybackDeviceId.current(); + } + [[nodiscard]] rpl::producer callPlaybackDeviceIdChanges() const { + return _callPlaybackDeviceId.changes(); + } + [[nodiscard]] rpl::producer callPlaybackDeviceIdValue() const { + return _callPlaybackDeviceId.value(); + } + void setCallPlaybackDeviceId(const QString &value) { + _callPlaybackDeviceId = value; + } + [[nodiscard]] QString callCaptureDeviceId() const { + return _callCaptureDeviceId.current(); + } + [[nodiscard]] rpl::producer callCaptureDeviceIdChanges() const { + return _callCaptureDeviceId.changes(); + } + [[nodiscard]] rpl::producer callCaptureDeviceIdValue() const { + return _callCaptureDeviceId.value(); + } + void setCallCaptureDeviceId(const QString &value) { + _callCaptureDeviceId = value; + } + [[nodiscard]] int callOutputVolume() const { return _callOutputVolume; } @@ -875,9 +913,11 @@ private: bool _countUnreadMessages = true; rpl::variable _notifyAboutPinned = true; int _autoLock = 3600; - QString _callOutputDeviceId = u"default"_q; - QString _callInputDeviceId = u"default"_q; - QString _callVideoInputDeviceId = u"default"_q; + rpl::variable _playbackDeviceId; + rpl::variable _captureDeviceId; + rpl::variable _cameraDeviceId; + rpl::variable _callPlaybackDeviceId; + rpl::variable _callCaptureDeviceId; int _callOutputVolume = 100; int _callInputVolume = 100; bool _callAudioDuckingEnabled = true; diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index c882856f6..b93743d7f 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -141,7 +141,8 @@ void DestroyPlaybackDevice() { bool CreatePlaybackDevice() { if (AudioDevice) return true; - AudioDevice = alcOpenDevice(nullptr); + const auto id = Current().deviceId().toStdString(); + AudioDevice = alcOpenDevice(id.c_str()); if (!AudioDevice) { LOG(("Audio Error: Could not create default playback device, enumerating..")); EnumeratePlaybackDevices(); @@ -200,12 +201,12 @@ void Start(not_null instance) { MixerInstance = new Player::Mixer(instance); - Platform::Audio::Init(); + //Platform::Audio::Init(); } // Thread: Main. void Finish(not_null instance) { - Platform::Audio::DeInit(); + //Platform::Audio::DeInit(); // MixerInstance variable should be modified under AudioMutex protection. // So it is modified in the ~Mixer() destructor after all tracks are cleared. diff --git a/Telegram/SourceFiles/media/audio/media_audio_track.cpp b/Telegram/SourceFiles/media/audio/media_audio_track.cpp index 59836d310..6973b4a13 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_track.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_track.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio_ffmpeg_loader.h" #include "media/audio/media_audio.h" #include "core/application.h" +#include "core/core_settings.h" #include "core/file_location.h" #include @@ -242,7 +243,12 @@ Track::~Track() { _instance->unregisterTrack(this); } -Instance::Instance() { +Instance::Instance() +: _playbackDeviceId( + &Core::App().mediaDevices(), + Webrtc::DeviceType::Playback, + Webrtc::DeviceIdOrDefault( + Core::App().settings().playbackDeviceIdValue())) { _updateTimer.setCallback([this] { auto hasActive = false; for (auto track : _tracks) { @@ -260,6 +266,15 @@ Instance::Instance() { _detachFromDeviceForce = false; Player::internal::DetachFromDevice(this); }); + + _playbackDeviceId.changes() | rpl::start_with_next([=] { + _detachFromDeviceForce = false; + Player::internal::DetachFromDevice(this); + }, _lifetime); +} + +QString Instance::deviceId() const { + return _playbackDeviceId.current(); } std::unique_ptr Instance::createTrack() { diff --git a/Telegram/SourceFiles/media/audio/media_audio_track.h b/Telegram/SourceFiles/media/audio/media_audio_track.h index ca11ff7b0..bc6f74100 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_track.h +++ b/Telegram/SourceFiles/media/audio/media_audio_track.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "base/bytes.h" +#include "webrtc/webrtc_device_id.h" namespace Core { class FileLocation; @@ -94,7 +95,9 @@ public: // Thread: Main. Instance(); - std::unique_ptr createTrack(); + [[nodiscard]] QString deviceId() const; + + [[nodiscard]] std::unique_ptr createTrack(); void detachTracks(); void reattachTracks(); @@ -115,15 +118,18 @@ private: private: std::set _tracks; + Webrtc::DeviceId _playbackDeviceId; base::Timer _updateTimer; base::Timer _detachFromDeviceTimer; bool _detachFromDeviceForce = false; + rpl::lifetime _lifetime; + }; -Instance &Current(); +[[nodiscard]] Instance &Current(); } // namespace Audio } // namespace Media diff --git a/Telegram/SourceFiles/settings/settings_calls.cpp b/Telegram/SourceFiles/settings/settings_calls.cpp index efc73b04f..8f7f1d61f 100644 --- a/Telegram/SourceFiles/settings/settings_calls.cpp +++ b/Telegram/SourceFiles/settings/settings_calls.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_video_bubble.h" #include "apiwrap.h" #include "api/api_authorizations.h" +#include "webrtc/webrtc_environment.h" #include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_audio_input_tester.h" @@ -41,6 +42,18 @@ namespace { using namespace Webrtc; +[[nodiscard]] rpl::producer DeviceNameValue( + DeviceType type, + rpl::producer id) { + return std::move(id) | rpl::map([type](const QString &id) { + const auto list = Core::App().mediaDevices().devices(type); + const auto i = ranges::find(list, id, &DeviceInfo::id); + return (i != end(list)) + ? i->name + : tr::lng_settings_call_device_default(tr::now); + }); +} + } // namespace Calls::Calls( @@ -86,7 +99,7 @@ Webrtc::VideoTrack *Calls::AddCameraSubsection( const auto cameras = GetVideoInputList(); const auto i = ranges::find( cameras, - Core::App().settings().callVideoInputDeviceId(), + Core::App().settings().cameraDeviceId(), &VideoInput::id); return (i != end(cameras)) ? i->name @@ -111,7 +124,7 @@ Webrtc::VideoTrack *Calls::AddCameraSubsection( ) | ranges::to_vector; const auto i = ranges::find( devices, - Core::App().settings().callVideoInputDeviceId(), + Core::App().settings().cameraDeviceId(), &VideoInput::id); const auto currentOption = (i != end(devices)) ? int(i - begin(devices) + 1) @@ -120,14 +133,11 @@ Webrtc::VideoTrack *Calls::AddCameraSubsection( cameraNameStream->fire_copy(options[option]); const auto deviceId = option ? devices[option - 1].id - : "default"; + : kDefaultDeviceId; if (saveToSettings) { - Core::App().settings().setCallVideoInputDeviceId(deviceId); + Core::App().settings().setCameraDeviceId(deviceId); Core::App().saveSettingsDelayed(); } - if (const auto call = Core::App().calls().currentCall()) { - call->setCurrentCameraDevice(deviceId); - } if (*capturerOwner) { (*capturerOwner)->switchToDevice( deviceId.toStdString(), @@ -186,7 +196,7 @@ Webrtc::VideoTrack *Calls::AddCameraSubsection( return; } *capturerOwner = Core::App().calls().getVideoCapture( - Core::App().settings().callVideoInputDeviceId(), + Core::App().settings().cameraDeviceId(), false); (*capturerOwner)->setPreferredAspectRatio(0.); track->setState(VideoState::Active); @@ -220,54 +230,58 @@ void Calls::sectionSaveChanges(FnMut done) { void Calls::setupContent() { const auto content = Ui::CreateChild(this); - if (!GetVideoInputList().empty()) { - Ui::AddSkip(content); - Ui::AddSubsectionTitle(content, tr::lng_settings_call_camera()); - AddCameraSubsection(_controller->uiShow(), content, true); - Ui::AddSkip(content); - Ui::AddDivider(content); - } Ui::AddSkip(content); Ui::AddSubsectionTitle(content, tr::lng_settings_call_section_output()); + + //auto playbackIdWithFallback = DeviceIdValueWithFallback( + // Core::App().settings().callPlaybackDeviceIdValue(), + // Core::App().settings().playbackDeviceIdValue()); + auto playbackIdWithFallback = [] { + return DeviceIdOrDefault( + Core::App().settings().playbackDeviceIdValue()); + }; AddButtonWithLabel( content, tr::lng_settings_call_output_device(), - rpl::single( - CurrentAudioOutputName() - ) | rpl::then( - _outputNameStream.events() - ), + PlaybackDeviceNameValue(playbackIdWithFallback()), st::settingsButtonNoIcon )->addClickHandler([=] { - _controller->show(ChooseAudioOutputBox(crl::guard(this, [=]( - const QString &id, - const QString &name) { - _outputNameStream.fire_copy(name); - }))); + _controller->show(ChoosePlaybackDeviceBox( + playbackIdWithFallback(), + crl::guard(this, [=](const QString &id) { + //Core::App().settings().setCallPlaybackDeviceId(id); + Core::App().settings().setPlaybackDeviceId(id); + Core::App().saveSettingsDelayed(); + }))); }); Ui::AddSkip(content); Ui::AddDivider(content); Ui::AddSkip(content); Ui::AddSubsectionTitle(content, tr::lng_settings_call_section_input()); + //auto captureIdWithFallback = DeviceIdValueWithFallback( + // Core::App().settings().callCaptureDeviceIdValue(), + // Core::App().settings().captureDeviceIdValue()); + auto captureIdWithFallback = [] { + return DeviceIdOrDefault( + Core::App().settings().captureDeviceIdValue()); + }; AddButtonWithLabel( content, tr::lng_settings_call_input_device(), - rpl::single( - CurrentAudioInputName() - ) | rpl::then( - _inputNameStream.events() - ), + CaptureDeviceNameValue(captureIdWithFallback()), st::settingsButtonNoIcon )->addClickHandler([=] { - _controller->show(ChooseAudioInputBox(crl::guard(this, [=]( - const QString &id, - const QString &name) { - _inputNameStream.fire_copy(name); - if (_micTester) { - _micTester->setDeviceId(id); - } - }))); + _controller->show(ChooseCaptureDeviceBox( + captureIdWithFallback(), + crl::guard(this, [=](const QString &id) { + //Core::App().settings().setCallCaptureDeviceId(id); + Core::App().settings().setCaptureDeviceId(id); + Core::App().saveSettingsDelayed(); + if (_micTester) { + _micTester->setDeviceId(id); + } + }))); }); _micTestLevel = content->add( @@ -287,6 +301,15 @@ void Calls::setupContent() { Ui::AddSkip(content); Ui::AddDivider(content); + + if (!GetVideoInputList().empty()) { + Ui::AddSkip(content); + Ui::AddSubsectionTitle(content, tr::lng_settings_call_camera()); + AddCameraSubsection(_controller->uiShow(), content, true); + Ui::AddSkip(content); + Ui::AddDivider(content); + } + Ui::AddSkip(content); Ui::AddSubsectionTitle(content, tr::lng_settings_call_section_other()); @@ -310,8 +333,8 @@ void Calls::setupContent() { tr::lng_settings_call_open_system_prefs(), st::settingsButtonNoIcon ))->addClickHandler([=] { - const auto opened = Platform::OpenSystemSettings( - Platform::SystemSettingsType::Audio); + using namespace ::Platform; + const auto opened = OpenSystemSettings(SystemSettingsType::Audio); if (!opened) { _controller->show( Ui::MakeInformBox(tr::lng_linux_no_audio_prefs())); @@ -324,26 +347,27 @@ void Calls::setupContent() { } void Calls::requestPermissionAndStartTestingMicrophone() { - const auto status = Platform::GetPermissionStatus( - Platform::PermissionType::Microphone); - if (status == Platform::PermissionStatus::Granted) { + using namespace ::Platform; + const auto status = GetPermissionStatus( + PermissionType::Microphone); + if (status == PermissionStatus::Granted) { startTestingMicrophone(); - } else if (status == Platform::PermissionStatus::CanRequest) { + } else if (status == PermissionStatus::CanRequest) { const auto startTestingChecked = crl::guard(this, [=]( - Platform::PermissionStatus status) { - if (status == Platform::PermissionStatus::Granted) { + PermissionStatus status) { + if (status == PermissionStatus::Granted) { crl::on_main(crl::guard(this, [=] { startTestingMicrophone(); })); } }); - Platform::RequestPermission( - Platform::PermissionType::Microphone, + RequestPermission( + PermissionType::Microphone, startTestingChecked); } else { const auto showSystemSettings = [controller = _controller] { - Platform::OpenSystemSettingsForPermission( - Platform::PermissionType::Microphone); + OpenSystemSettingsForPermission( + PermissionType::Microphone); controller->hideLayer(); }; _controller->show(Ui::MakeConfirmBox({ @@ -358,135 +382,215 @@ void Calls::startTestingMicrophone() { _levelUpdateTimer.callEach(kMicTestUpdateInterval); _micTester = std::make_unique( Core::App().settings().callAudioBackend(), - Core::App().settings().callInputDeviceId()); + Core::App().settings().callCaptureDeviceId()); } -QString CurrentAudioOutputName() { - const auto &settings = Core::App().settings(); - const auto list = GetAudioOutputList(settings.callAudioBackend()); - const auto i = ranges::find( - list, - settings.callOutputDeviceId(), - &AudioOutput::id); - return (i != end(list)) - ? i->name - : tr::lng_settings_call_device_default(tr::now); +rpl::producer PlaybackDeviceNameValue(rpl::producer id) { + return DeviceNameValue(DeviceType::Playback, std::move(id)); } -QString CurrentAudioInputName() { - const auto &settings = Core::App().settings(); - const auto list = GetAudioInputList(settings.callAudioBackend()); - const auto i = ranges::find( - list, - settings.callInputDeviceId(), - &AudioInput::id); - return (i != end(list)) - ? i->name - : tr::lng_settings_call_device_default(tr::now); +rpl::producer CaptureDeviceNameValue(rpl::producer id) { + return DeviceNameValue(DeviceType::Capture, std::move(id)); } -object_ptr ChooseAudioOutputBox( - Fn chosen, +void ChooseAudioDeviceBox( + not_null box, + rpl::producer title, + rpl::producer> devicesValue, + rpl::producer currentId, + Fn chosen, const style::Checkbox *st, const style::Radio *radioSt) { - const auto &settings = Core::App().settings(); - const auto list = GetAudioOutputList(settings.callAudioBackend()); - const auto options = ranges::views::concat( - ranges::views::single(tr::lng_settings_call_device_default(tr::now)), - list | ranges::views::transform(&AudioOutput::name) - ) | ranges::to_vector; - const auto i = ranges::find( - list, - settings.callOutputDeviceId(), - &AudioOutput::id); - const auto currentOption = (i != end(list)) - ? int(i - begin(list) + 1) - : 0; - const auto save = [=](int option) { - const auto deviceId = option - ? list[option - 1].id - : "default"; - Core::App().calls().setCurrentAudioDevice(false, deviceId); - chosen(deviceId, options[option]); + box->setTitle(std::move(title)); + box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); + const auto layout = box->verticalLayout(); + const auto skip = st::boxOptionListPadding.top() + + st::defaultBoxCheckbox.margin.top(); + layout->add(object_ptr(layout, skip)); + + if (!st) { + st = &st::defaultBoxCheckbox; + } + if (!radioSt) { + radioSt = &st::defaultRadio; + } + + struct State { + std::vector list; + base::flat_map ids; + rpl::variable currentId; + QString currentName; + bool ignoreValueChange = false; }; - return Box([=](not_null box) { - SingleChoiceBox(box, { - .title = tr::lng_settings_call_output_device(), - .options = options, - .initialSelection = currentOption, - .callback = save, - .st = st, - .radioSt = radioSt, + const auto state = box->lifetime().make_state(); + state->currentId = std::move(currentId); + + const auto group = std::make_shared(); + const auto fake = std::make_shared(0); + const auto buttons = layout->add(object_ptr(layout)); + const auto other = layout->add(object_ptr(layout)); + const auto margins = QMargins( + st::boxPadding.left() + st::boxOptionListPadding.left(), + 0, + st::boxPadding.right(), + st::boxOptionListSkip); + const auto def = buttons->add( + object_ptr( + buttons, + group, + 0, + tr::lng_settings_call_device_default(tr::now), + *st, + *radioSt), + margins); + + const auto selectCurrent = [=](QString current) { + state->ignoreValueChange = true; + const auto guard = gsl::finally([&] { + state->ignoreValueChange = false; }); + if (current.isEmpty() || current == kDefaultDeviceId) { + group->setValue(0); + other->clear(); + } else { + auto found = false; + for (const auto &[index, id] : state->ids) { + if (id == current) { + group->setValue(index); + found = true; + break; + } + } + if (found) { + other->clear(); + } else { + group->setValue(0); + const auto i = ranges::find( + state->list, + current, + &DeviceInfo::id); + if (i != end(state->list)) { + const auto button = other->add( + object_ptr( + other, + fake, + 0, + i->name, + *st, + *radioSt), + margins); + button->show(); + button->setDisabled(true); + button->finishAnimating(); + button->setAttribute(Qt::WA_TransparentForMouseEvents); + while (other->count() > 1) { + delete other->widgetAt(1); + } + if (const auto width = box->width()) { + other->resizeToWidth(width); + } + } else { + other->clear(); + } + } + } + }; + + std::move( + devicesValue + ) | rpl::start_with_next([=](std::vector &&list) { + auto count = buttons->count(); + auto index = 1; + state->ids.clear(); + state->list = std::move(list); + + state->ignoreValueChange = true; + const auto guard = gsl::finally([&] { + state->ignoreValueChange = false; + }); + + const auto current = state->currentId.current(); + for (const auto &info : state->list) { + if (info.inactive) { + continue; + } else if (current == info.id) { + group->setValue(index); + } + const auto button = buttons->insert( + index, + object_ptr( + buttons, + group, + index, + info.name, + *st, + *radioSt), + margins); + button->show(); + button->finishAnimating(); + + state->ids.emplace(index, info.id); + if (index < count) { + delete buttons->widgetAt(index + 1); + } + ++index; + } + while (index < count) { + delete buttons->widgetAt(index); + --count; + } + if (const auto width = box->width()) { + buttons->resizeToWidth(width); + } + selectCurrent(current); + }, box->lifetime()); + + state->currentId.changes( + ) | rpl::start_with_next(selectCurrent, box->lifetime()); + + def->finishAnimating(); + + group->setChangedCallback([=](int value) { + if (state->ignoreValueChange) { + return; + } + const auto weak = Ui::MakeWeak(box); + chosen(state->ids.take(value).value_or(kDefaultDeviceId)); + if (weak) { + box->closeBox(); + } }); } -object_ptr ChooseAudioInputBox( - Fn chosen, +object_ptr ChoosePlaybackDeviceBox( + rpl::producer currentId, + Fn chosen, const style::Checkbox *st, const style::Radio *radioSt) { - const auto &settings = Core::App().settings(); - const auto list = GetAudioInputList(settings.callAudioBackend()); - const auto options = ranges::views::concat( - ranges::views::single(tr::lng_settings_call_device_default(tr::now)), - list | ranges::views::transform(&AudioInput::name) - ) | ranges::to_vector; - const auto i = ranges::find( - list, - Core::App().settings().callInputDeviceId(), - &AudioInput::id); - const auto currentOption = (i != end(list)) - ? int(i - begin(list) + 1) - : 0; - const auto save = [=](int option) { - const auto deviceId = option - ? list[option - 1].id - : "default"; - Core::App().calls().setCurrentAudioDevice(true, deviceId); - chosen(deviceId, options[option]); - }; - return Box([=](not_null box) { - SingleChoiceBox(box, { - .title = tr::lng_settings_call_input_device(), - .options = options, - .initialSelection = currentOption, - .callback = save, - .st = st, - .radioSt = radioSt, - }); - }); + return Box( + ChooseAudioDeviceBox, + tr::lng_settings_call_output_device(), + Core::App().mediaDevices().devicesValue(DeviceType::Playback), + std::move(currentId), + std::move(chosen), + st, + radioSt); +} + +object_ptr ChooseCaptureDeviceBox( + rpl::producer currentId, + Fn chosen, + const style::Checkbox *st, + const style::Radio *radioSt) { + return Box( + ChooseAudioDeviceBox, + tr::lng_settings_call_input_device(), + Core::App().mediaDevices().devicesValue(DeviceType::Capture), + std::move(currentId), + std::move(chosen), + st, + radioSt); } -// -//object_ptr ChooseAudioBackendBox( -// const style::Checkbox *st, -// const style::Radio *radioSt) { -// const auto &settings = Core::App().settings(); -// const auto list = GetAudioInputList(settings.callAudioBackend()); -// const auto options = std::vector{ -// "OpenAL", -// "Webrtc ADM", -//#ifdef Q_OS_WIN -// "Webrtc ADM2", -//#endif // Q_OS_WIN -// }; -// const auto currentOption = static_cast(settings.callAudioBackend()); -// const auto save = [=](int option) { -// Core::App().settings().setCallAudioBackend( -// static_cast(option)); -// Core::App().saveSettings(); -// Core::Restart(); -// }; -// return Box([=](not_null box) { -// SingleChoiceBox(box, { -// .title = rpl::single("Calls audio backend"), -// .options = options, -// .initialSelection = currentOption, -// .callback = save, -// .st = st, -// .radioSt = radioSt, -// }); -// }); -//} } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_calls.h b/Telegram/SourceFiles/settings/settings_calls.h index 1fc021aad..1345adda1 100644 --- a/Telegram/SourceFiles/settings/settings_calls.h +++ b/Telegram/SourceFiles/settings/settings_calls.h @@ -54,8 +54,6 @@ private: const not_null _controller; rpl::event_stream _cameraNameStream; - rpl::event_stream _outputNameStream; - rpl::event_stream _inputNameStream; std::unique_ptr _micTester; Ui::LevelMeter *_micTestLevel = nullptr; float _micLevel = 0.; @@ -67,19 +65,20 @@ private: inline constexpr auto kMicTestUpdateInterval = crl::time(100); inline constexpr auto kMicTestAnimationDuration = crl::time(200); -[[nodiscard]] QString CurrentAudioOutputName(); -[[nodiscard]] QString CurrentAudioInputName(); -[[nodiscard]] object_ptr ChooseAudioOutputBox( - Fn chosen, +[[nodiscard]] rpl::producer PlaybackDeviceNameValue( + rpl::producer id); +[[nodiscard]] rpl::producer CaptureDeviceNameValue( + rpl::producer id); +[[nodiscard]] object_ptr ChoosePlaybackDeviceBox( + rpl::producer currentId, + Fn chosen, const style::Checkbox *st = nullptr, const style::Radio *radioSt = nullptr); -[[nodiscard]] object_ptr ChooseAudioInputBox( - Fn chosen, +[[nodiscard]] object_ptr ChooseCaptureDeviceBox( + rpl::producer currentId, + Fn chosen, const style::Checkbox *st = nullptr, const style::Radio *radioSt = nullptr); -//[[nodiscard]] object_ptr ChooseAudioBackendBox( -// const style::Checkbox *st = nullptr, -// const style::Radio *radioSt = nullptr); } // namespace Settings diff --git a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp index 56e848252..da60290b4 100644 --- a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp +++ b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp @@ -1148,9 +1148,9 @@ bool ReadSetting( settingsStream >> duckingEnabled; if (CheckStreamStatus(settingsStream)) { auto &app = Core::App().settings(); - app.setCallOutputDeviceId(outputDeviceID); + app.setCallPlaybackDeviceId(outputDeviceID); + app.setCallCaptureDeviceId(inputDeviceID); app.setCallOutputVolume(outputVolume); - app.setCallInputDeviceId(inputDeviceID); app.setCallInputVolume(inputVolume); app.setCallAudioDuckingEnabled(duckingEnabled); } diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index b68a95ad4..5a8316978 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit b68a95ad4d1ae9a1827671100a7fd76cbe448c3f +Subproject commit 5a831697880967bbccbd45177fb6cf6b11759a22