Files
tdesktop/Telegram/lib_webrtc/webrtc/webrtc_environment.cpp
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
init
2026-02-16 15:50:16 +03:00

397 lines
11 KiB
C++

// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "webrtc/webrtc_environment.h"
#include "base/debug_log.h"
#include "webrtc/platform/webrtc_platform_environment.h"
#include "webrtc/details/webrtc_environment_openal.h"
#include "webrtc/details/webrtc_environment_video_capture.h"
#ifdef WEBRTC_MAC
#include "webrtc/platform/mac/webrtc_environment_mac.h"
#endif // WEBRTC_MAC
#include <crl/crl_async.h>
namespace Webrtc {
namespace {
[[nodiscard]] QString SerializeDevices(const std::vector<DeviceInfo> &list) {
auto result = QStringList();
for (const auto &device : list) {
result.push_back('"' + device.name + "\" <" + device.id + ">");
}
return "{ " + result.join(", ") + " }";
}
[[nodiscard]] QString TypeToString(DeviceType type) {
switch (type) {
case DeviceType::Playback: return "Playback";
case DeviceType::Capture: return "Capture";
case DeviceType::Camera: return "Camera";
}
Unexpected("Type in TypeToString.");
}
} // namespace
Environment::Environment()
: _platform(
Platform::CreateEnvironment((Platform::EnvironmentDelegate*)this))
, _devices{
resolveDevices(DeviceType::Playback),
resolveDevices(DeviceType::Capture),
resolveDevices(DeviceType::Camera),
} {
using Type = DeviceType;
for (const auto type : { Type::Playback, Type::Capture, Type::Camera }) {
if (synced(type)) {
logState(type, LogType::Initial);
} else {
logSyncError(type);
}
}
}
Environment::~Environment() = default;
int Environment::TypeToIndex(DeviceType type) {
const auto result = int(type);
Ensures(result >= 0 && result < kTypeCount);
return result;
}
Environment::Devices Environment::resolveDevices(DeviceType type) const {
return {
.defaultId = _platform->defaultId(type),
.list = _platform->devices(type),
.refreshFullListOnChange = _platform->refreshFullListOnChange(type),
};
}
QString Environment::defaultId(DeviceType type) const {
validateDefaultId(type);
return _devices[TypeToIndex(type)].defaultId;
}
std::vector<DeviceInfo> Environment::devices(DeviceType type) const {
validateDevices(type);
return _devices[TypeToIndex(type)].list;
}
rpl::producer<DevicesChange> Environment::changes(
DeviceType type) const {
const auto devices = &_devices[TypeToIndex(type)];
return devices->changes.events(
) | rpl::map([=](DevicesChangeEvent &&event) -> DevicesChange {
return { std::move(event.defaultChange), devices->list };
});
}
rpl::producer<DeviceChange> Environment::defaultChanges(
DeviceType type) const {
return _devices[TypeToIndex(type)].changes.events(
) | rpl::filter([](const DevicesChangeEvent &event) {
return !!event.defaultChange;
}) | rpl::map([](DevicesChangeEvent &&event) {
return std::move(event.defaultChange);
});
}
rpl::producer<std::vector<DeviceInfo>> Environment::devicesValue(
DeviceType type) const {
validateDevices(type);
const auto devices = &_devices[TypeToIndex(type)];
return devices->changes.events_starting_with(
DevicesChangeEvent{ .listChanged = true }
) | rpl::filter([](const DevicesChangeEvent &event) {
return event.listChanged;
}) | rpl::map([=] {
return devices->list;
});
}
void Environment::forceRefresh(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
devices.defaultChangeFrom = std::exchange(
devices.defaultId,
_platform->defaultId(type));
const auto &old = *devices.defaultChangeFrom;
const auto newIsInOldList = ranges::contains(
devices.list,
devices.defaultId,
&DeviceInfo::id);
refreshDevices(type);
const auto oldIsInNewList = ranges::contains(
devices.list,
old,
&DeviceInfo::id);
if (devices.defaultId != old) {
devices.defaultChangeReason = !oldIsInNewList
? DeviceChangeReason::Disconnected
: !newIsInOldList
? DeviceChangeReason::Connected
: DeviceChangeReason::Manual;
}
maybeNotify(type);
}
bool Environment::desktopCaptureAllowed() const {
return _platform->desktopCaptureAllowed();
}
std::optional<QString> Environment::uniqueDesktopCaptureSource() const {
return _platform->uniqueDesktopCaptureSource();
}
DeviceResolvedId Environment::threadSafeResolveId(
const DeviceResolvedId &lastResolvedId,
const QString &savedId) const {
return _platform->threadSafeResolveId(lastResolvedId, savedId);
}
void Environment::setCaptureMuted(bool muted) {
_platform->setCaptureMuted(muted);
}
void Environment::setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) {
_platform->setCaptureMuteTracker(tracker, track);
}
RecordAvailability Environment::recordAvailability() const {
const_cast<Environment*>(this)->refreshRecordAvailability();
return _recordAvailability.current();
}
auto Environment::recordAvailabilityValue() const
->rpl::producer<RecordAvailability> {
const_cast<Environment*>(this)->refreshRecordAvailability();
return _recordAvailability.value();
}
void Environment::refreshRecordAvailability() {
if (_recordAvailabilityRefreshing) {
_recordAvailabilityRefreshPending = true;
return;
}
_recordAvailabilityRefreshing = true;
const auto strong = static_cast<base::has_weak_ptr*>(this);
const auto weak = base::make_weak(strong);
crl::async([weak] {
const auto type = DeviceType::Capture;
const auto audio = details::EnvironmentOpenAL::DefaultId(type);
#ifdef WEBRTC_MAC
const auto vtype = DeviceType::Camera;
const auto video = Platform::EnvironmentMac::DefaultId(vtype);
#else // WEBRTC_MAC
const auto video = details::EnvironmentVideoCapture::DefaultId();
#endif // WEBRTC_MAC
const auto availability = audio.isEmpty()
? RecordAvailability::None
: video.isEmpty()
? RecordAvailability::Audio
: RecordAvailability::VideoAndAudio;
crl::on_main([weak, availability] {
if (const auto strong = weak.get()) {
const auto that = static_cast<Environment*>(strong);
that->applyRecordAvailability(availability);
}
});
});
}
void Environment::applyRecordAvailability(RecordAvailability value) {
_recordAvailability = value;
_recordAvailabilityRefreshing = false;
if (base::take(_recordAvailabilityRefreshPending)) {
refreshRecordAvailability();
}
}
void Environment::defaultChanged(
DeviceType type,
DeviceChangeReason reason,
QString nowId) {
const auto guard = gsl::finally([&] {
validateAfterDefaultChange(type);
maybeNotify(type);
});
auto &devices = _devices[TypeToIndex(type)];
devices.defaultChangeFrom = std::exchange(
devices.defaultId,
std::move(nowId));
devices.defaultChangeReason = reason;
}
void Environment::deviceStateChanged(
DeviceType type,
QString id,
DeviceStateChange state) {
const auto guard = gsl::finally([&] {
validateAfterListChange(type);
maybeNotify(type);
});
auto &devices = _devices[TypeToIndex(type)];
if (devices.refreshFullListOnChange) {
refreshDevices(type);
}
const auto i = ranges::find(
devices.list,
id,
&DeviceInfo::id);
if (i == end(devices.list)) {
if (state == DeviceStateChange::Disconnected) {
return;
} else if (auto info = _platform->device(type, id)) {
devices.list.push_back(std::move(info));
devices.listChanged = true;
}
} else if (state == DeviceStateChange::Disconnected) {
const auto from = ranges::remove(
i,
end(devices.list),
id,
&DeviceInfo::id);
if (from != end(devices.list)) {
devices.list.erase(from, end(devices.list));
devices.listChanged = true;
}
} else if (i != end(devices.list)) {
const auto inactive = (state != DeviceStateChange::Active);
if (i->inactive != inactive) {
i->inactive = inactive;
devices.listChanged = true;
}
}
}
void Environment::devicesForceRefresh(DeviceType type) {
forceRefresh(type);
}
void Environment::validateDefaultId(DeviceType type) const {
_platform->defaultIdRequested(type);
}
void Environment::validateDevices(DeviceType type) const {
_platform->devicesRequested(type);
}
bool Environment::synced(DeviceType type) const {
const auto &devices = _devices[TypeToIndex(type)];
return ranges::contains(
devices.list,
devices.defaultId,
&DeviceInfo::id);
}
void Environment::validateAfterListChange(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
if (!devices.listChanged || synced(type)) {
return;
}
devices.defaultChangeFrom = std::exchange(
devices.defaultId,
_platform->defaultId(type));
devices.defaultChangeReason = DeviceChangeReason::Disconnected;
if (devices.defaultChangeFrom != devices.defaultId
&& synced(type)) {
return;
}
refreshDevices(type);
if (!devices.listChanged || !synced(type)) {
logSyncError(type);
}
}
void Environment::validateAfterDefaultChange(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
if (!devices.defaultChangeFrom
|| devices.defaultChangeFrom == devices.defaultId
|| synced(type)) {
return;
}
refreshDevices(type);
if (devices.listChanged && synced(type)) {
return;
}
auto changedOneMoreFromId = std::exchange(
devices.defaultId,
_platform->defaultId(type));
devices.defaultChangeReason = DeviceChangeReason::Disconnected;
if (devices.defaultId == changedOneMoreFromId || !synced(type)) {
logSyncError(type);
}
}
void Environment::maybeNotify(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
if (devices.defaultChangeFrom == devices.defaultId) {
devices.defaultChangeFrom = std::nullopt;
}
if (!devices.listChanged && !devices.defaultChangeFrom) {
return;
}
const auto listChanged = base::take(devices.listChanged);
const auto from = base::take(devices.defaultChangeFrom);
const auto reason = base::take(devices.defaultChangeReason);
devices.changes.fire({
.defaultChange = {
.wasId = from.value_or(QString()),
.nowId = from ? devices.defaultId : QString(),
.reason = (from ? reason : DeviceChangeReason::Manual),
},
.listChanged = listChanged,
});
}
void Environment::logSyncError(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
LOG(("Media Error: "
"Can't sync default device for type %1, default: %2, list: %3"
).arg(TypeToString(type)
).arg(devices.defaultId
).arg(SerializeDevices(devices.list)));
}
void Environment::logState(DeviceType type, LogType log) {
auto &devices = _devices[TypeToIndex(type)];
auto phrase = u"Media Info: Type %1, default: %2, list: %3"_q
.arg(TypeToString(type))
.arg(devices.defaultId)
.arg(SerializeDevices(devices.list));
if (log == LogType::Initial) {
phrase += u", full list refresh: %1"_q.arg(
devices.refreshFullListOnChange ? "true" : "false");
}
switch (log) {
case LogType::Initial:
case LogType::Always:
LOG((phrase));
break;
case LogType::Debug:
DEBUG_LOG((phrase));
break;
}
}
void Environment::refreshDevices(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
auto list = _platform->devices(type);
if (devices.list != list) {
devices.list = std::move(list);
devices.listChanged = true;
}
}
} // namespace Webrtc