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
397 lines
11 KiB
C++
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
|