mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-07-23 06:24:09 -04:00
552 lines
20 KiB
C++
552 lines
20 KiB
C++
/*
|
|
* @file ServiceAudio.cpp
|
|
* @author Mateusz Piesta (mateusz.piesta@mudita.com)
|
|
* @date 29.07.19
|
|
* @brief
|
|
* @copyright Copyright (C) 2019 mudita.com
|
|
* @details
|
|
*/
|
|
|
|
#include "ServiceAudio.hpp"
|
|
#include "messages/AudioMessage.hpp"
|
|
#include "Audio/Operation/IdleOperation.hpp"
|
|
#include "Audio/Operation/PlaybackOperation.hpp"
|
|
|
|
#include <type_traits>
|
|
|
|
const char *ServiceAudio::serviceName = "ServiceAudio";
|
|
|
|
using namespace audio;
|
|
|
|
ServiceAudio::ServiceAudio()
|
|
: sys::Service(serviceName, "", 4096 * 2, sys::ServicePriority::Idle),
|
|
audioMux([this](auto... params) { return this->AsyncCallback(params...); },
|
|
[this](auto... params) { return this->DbCallback(params...); })
|
|
{
|
|
busChannels.push_back(sys::BusChannels::ServiceAudioNotifications);
|
|
LOG_INFO("[ServiceAudio] Initializing");
|
|
}
|
|
|
|
ServiceAudio::~ServiceAudio()
|
|
{
|
|
LOG_INFO("[ServiceAudio] Cleaning resources");
|
|
}
|
|
|
|
sys::ReturnCodes ServiceAudio::InitHandler()
|
|
{
|
|
return sys::ReturnCodes::Success;
|
|
}
|
|
|
|
sys::ReturnCodes ServiceAudio::DeinitHandler()
|
|
{
|
|
return sys::ReturnCodes::Success;
|
|
}
|
|
|
|
int32_t ServiceAudio::AsyncCallback(PlaybackEvent e)
|
|
{
|
|
switch (e.event) {
|
|
case audio::PlaybackEventType::EndOfFile: {
|
|
auto msg = std::make_shared<AudioNotificationMessage>(
|
|
static_cast<AudioNotificationMessage::Type>(AudioNotificationMessage::Type::EndOfFile), e.token);
|
|
sys::Bus::SendMulticast(msg, sys::BusChannels::ServiceAudioNotifications, this);
|
|
} break;
|
|
case audio::PlaybackEventType::FileSystemNoSpace:
|
|
case audio::PlaybackEventType::Empty:
|
|
break;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
uint32_t ServiceAudio::DbCallback(const std::string &path, const uint32_t &defaultValue)
|
|
{
|
|
this->addOrIgnoreEntry(path, std::to_string(defaultValue));
|
|
return this->fetchAudioSettingFromDb(path, defaultValue);
|
|
};
|
|
|
|
sys::ReturnCodes ServiceAudio::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
|
|
{
|
|
LOG_FATAL("[ServiceAudio] PowerModeHandler: %s", c_str(mode));
|
|
return sys::ReturnCodes::Success;
|
|
}
|
|
|
|
|
|
constexpr bool ServiceAudio::IsResumable(const audio::PlaybackType &type) const
|
|
{
|
|
return type == audio::PlaybackType::Multimedia;
|
|
}
|
|
|
|
constexpr bool ServiceAudio::ShouldLoop(const std::optional<audio::PlaybackType> &type) const
|
|
{
|
|
|
|
return type.value_or(audio::PlaybackType::None) == audio::PlaybackType::CallRingtone;
|
|
}
|
|
|
|
// below static methods will be replaced by final vibration API
|
|
static void ExtVibrateOnce()
|
|
{
|
|
LOG_ERROR("[Vibration] - Unimplemented - vibration one shot");
|
|
}
|
|
static void ExtVibrationStart()
|
|
{
|
|
LOG_ERROR("[Vibration] - Unimplemented - vibration start");
|
|
}
|
|
static void ExtVibrationStop()
|
|
{
|
|
LOG_ERROR("[Vibration] - Unimplemented - vibration stop");
|
|
}
|
|
// below static members will be replaced by accessors to DB entries
|
|
static bool IsVibrationEnabled(const audio::PlaybackType &type)
|
|
{
|
|
return true;
|
|
}
|
|
static bool IsPlaybackEnabled(const audio::PlaybackType &type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::optional<ServiceAudio::VibrationType> ServiceAudio::GetVibrationType(const audio::PlaybackType &type)
|
|
{
|
|
if (!IsVibrationEnabled(type)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (type == PlaybackType::CallRingtone) {
|
|
return VibrationType::Continuous;
|
|
}
|
|
else if (type == PlaybackType::Notifications || type == PlaybackType::TextMessageRingtone) {
|
|
return VibrationType::OneShot;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
void ServiceAudio::VibrationStart(const audio::PlaybackType &type, std::shared_ptr<AudioResponseMessage> &resp)
|
|
{
|
|
auto vibType = GetVibrationType(type);
|
|
if (!vibType || vibrationToken.IsValid()) {
|
|
return;
|
|
}
|
|
|
|
if (vibType == VibrationType::OneShot) {
|
|
vibrationToken = Token();
|
|
ExtVibrateOnce();
|
|
}
|
|
else if (vibType == VibrationType::Continuous) {
|
|
if (resp && resp->token.IsValid()) {
|
|
// audio has started
|
|
vibrationToken = resp->token;
|
|
}
|
|
else {
|
|
// audio did not start but we still want vibration
|
|
vibrationToken = audioMux.IncrementToken();
|
|
resp = std::make_unique<AudioResponseMessage>(RetCode::Success, Tags(), vibrationToken);
|
|
}
|
|
ExtVibrationStart();
|
|
}
|
|
}
|
|
|
|
void ServiceAudio::VibrationStop(const Token &token)
|
|
{
|
|
if (vibrationToken.IsValid() && vibrationToken == token) {
|
|
ExtVibrationStop();
|
|
vibrationToken = Token();
|
|
}
|
|
}
|
|
|
|
void ServiceAudio::addOrIgnoreEntry(const std::string &profilePath, const std::string &defaultValue)
|
|
{
|
|
auto [code, msg] = DBServiceAPI::GetQueryWithReply(
|
|
this,
|
|
db::Interface::Name::Settings_v2,
|
|
std::make_unique<db::query::settings::AddOrIgnoreQuery>(
|
|
SettingsRecord_v2{SettingsTableRow_v2{{.ID = DB_ID_NONE}, .path = profilePath, .value = defaultValue}}),
|
|
audio::audioOperationTimeout);
|
|
|
|
if (code == sys::ReturnCodes::Success && msg != nullptr) {
|
|
auto queryResponse = dynamic_cast<db::QueryResponse *>(msg.get());
|
|
assert(queryResponse != nullptr);
|
|
|
|
auto settingsResultResponse = queryResponse->getResult();
|
|
assert(dynamic_cast<db::query::settings::AddOrIgnoreResult *>(settingsResultResponse.get()) != nullptr);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AudioResponseMessage> ServiceAudio::HandlePause(std::optional<AudioRequestMessage *> msg)
|
|
{
|
|
|
|
audio::RetCode retCode = audio::RetCode::Failed;
|
|
audio::Token retToken = Token::MakeBadToken();
|
|
|
|
auto pauseInput = [this](AudioMux::Input &audioInput) {
|
|
auto playbackType = audioInput.audio->GetCurrentOperationPlaybackType();
|
|
if (playbackType && IsResumable(*playbackType)) {
|
|
audioInput.audio->Pause();
|
|
return audioInput.token;
|
|
}
|
|
else {
|
|
audioInput.audio->Stop();
|
|
auto broadMsg = std::make_shared<AudioNotificationMessage>(
|
|
static_cast<AudioNotificationMessage::Type>(AudioNotificationMessage::Type::Stop), audioInput.token);
|
|
sys::Bus::SendMulticast(broadMsg, sys::BusChannels::ServiceAudioNotifications, this);
|
|
audioMux.IncrementToken(&audioInput);
|
|
return Token::MakeBadToken();
|
|
}
|
|
};
|
|
|
|
if (msg) {
|
|
std::optional<AudioMux::Input *> input = audioMux.GetInput((*msg)->token);
|
|
if (!input) {
|
|
retToken = pauseInput(**input);
|
|
}
|
|
}
|
|
else {
|
|
for (auto &audioInput : audioMux.GetAllInputs()) {
|
|
if (audioInput.audio->GetCurrentOperation()->GetState() == Operation::State::Active) {
|
|
pauseInput(audioInput);
|
|
}
|
|
}
|
|
return std::make_unique<AudioResponseMessage>(audio::RetCode::Success, Tags(), retToken);
|
|
}
|
|
|
|
return std::make_unique<AudioResponseMessage>(retCode, Tags(), retToken);
|
|
}
|
|
|
|
template <typename... Args>
|
|
std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleStart(std::optional<AudioMux::Input *> input,
|
|
Operation::Type opType,
|
|
Args... args)
|
|
{
|
|
audio::RetCode retCode = audio::RetCode::Failed;
|
|
audio::Token retToken = Token::MakeBadToken();
|
|
|
|
if (input) {
|
|
|
|
HandlePause();
|
|
retToken = audioMux.IncrementToken(input);
|
|
retCode = (*input)->audio->Start(opType, retToken, args...);
|
|
}
|
|
if (retCode != audio::RetCode::Success) {
|
|
retToken = Token::MakeBadToken();
|
|
}
|
|
return std::make_unique<AudioResponseMessage>(retCode, Tags(), retToken);
|
|
}
|
|
|
|
std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleStop(AudioStopMessage *msg)
|
|
{
|
|
const auto playbackTypesToStop = msg->stopVec;
|
|
std::vector<audio::RetCode> retCodes = {audio::RetCode::Success};
|
|
|
|
auto stopInput = [this](auto inp) {
|
|
if (inp->audio->GetCurrentState() == Audio::State::Idle) {
|
|
return audio::RetCode::Success;
|
|
}
|
|
auto rCode = inp->audio->Stop();
|
|
// Send notification that audio file was stopped
|
|
auto msgStop = std::make_shared<AudioNotificationMessage>(
|
|
static_cast<AudioNotificationMessage::Type>(AudioNotificationMessage::Type::Stop), inp->token);
|
|
sys::Bus::SendMulticast(msgStop, sys::BusChannels::ServiceAudioNotifications, this);
|
|
audioMux.IncrementToken(inp);
|
|
return rCode;
|
|
};
|
|
|
|
// stop by token
|
|
if (auto tokenInput = audioMux.GetInput(msg->token)) {
|
|
retCodes.emplace_back(stopInput(tokenInput.value()));
|
|
}
|
|
// stop with vector of playback types
|
|
else if (!playbackTypesToStop.empty()) {
|
|
for (auto &input : audioMux.GetAllInputs()) {
|
|
const auto currentOperation = input.audio->GetCurrentOperation();
|
|
if (currentOperation && (std::find(playbackTypesToStop.begin(),
|
|
playbackTypesToStop.end(),
|
|
currentOperation->GetPlaybackType()) != playbackTypesToStop.end())) {
|
|
retCodes.emplace_back(stopInput(&input));
|
|
}
|
|
}
|
|
}
|
|
// stop all audio
|
|
else if (!msg->token.IsUninitialized()) {
|
|
for (auto &input : audioMux.GetAllInputs()) {
|
|
retCodes.emplace_back(stopInput(&input));
|
|
}
|
|
}
|
|
|
|
// on failure return first false code
|
|
auto it = std::find_if_not(retCodes.begin(), retCodes.end(), [](auto p) { return p == audio::RetCode::Success; });
|
|
if (it != retCodes.end()) {
|
|
return std::make_unique<AudioResponseMessage>(*it);
|
|
}
|
|
return std::make_unique<AudioResponseMessage>(audio::RetCode::Success);
|
|
}
|
|
|
|
sys::Message_t ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
|
|
{
|
|
std::shared_ptr<AudioResponseMessage> responseMsg;
|
|
|
|
auto msgType = static_cast<int>(msgl->messageType);
|
|
LOG_DEBUG("msgType %d", msgType);
|
|
|
|
if (auto *msg = dynamic_cast<AudioNotificationMessage *>(msgl)) {
|
|
switch (msg->type) {
|
|
case AudioNotificationMessage::Type::EndOfFile: {
|
|
auto input = audioMux.GetInput(msg->token);
|
|
if (input && ShouldLoop((*input)->audio->GetCurrentOperationPlaybackType())) {
|
|
(*input)->audio->Start();
|
|
}
|
|
else {
|
|
auto newMsg = std::make_shared<AudioStopMessage>(msg->token);
|
|
sys::Bus::SendUnicast(newMsg, ServiceAudio::serviceName, this);
|
|
}
|
|
} break;
|
|
|
|
case AudioNotificationMessage::Type::Stop: {
|
|
|
|
} break;
|
|
default:
|
|
LOG_DEBUG("Unhandled AudioNotificationMessage");
|
|
break;
|
|
}
|
|
}
|
|
else if (auto *msg = dynamic_cast<AudioGetSetting *>(msgl)) {
|
|
auto value = getSetting(msg->setting, msg->profileType, msg->playbackType);
|
|
responseMsg = std::make_shared<AudioResponseMessage>(RetCode::Success, utils::getValue<float>(value));
|
|
}
|
|
else if (auto *msg = dynamic_cast<AudioSetSetting *>(msgl)) {
|
|
setSetting(msg->setting, msg->val, msg->profileType, msg->playbackType);
|
|
responseMsg = std::make_shared<AudioResponseMessage>(RetCode::Success);
|
|
}
|
|
else if (auto *msg = dynamic_cast<AudioStopMessage *>(msgl)) {
|
|
VibrationStop(msg->token);
|
|
responseMsg = HandleStop(msg);
|
|
}
|
|
else if (auto *msg = dynamic_cast<AudioStartMessage *>(msgl)) {
|
|
if (IsPlaybackEnabled(msg->playbackType)) {
|
|
if (auto input = audioMux.GetPlaybackInput(msg->token, msg->playbackType)) {
|
|
responseMsg = HandleStart(input, Operation::Type::Playback, msg->fileName.c_str(), msg->playbackType);
|
|
}
|
|
}
|
|
VibrationStart(msg->playbackType, responseMsg);
|
|
}
|
|
else if (auto *msg = dynamic_cast<AudioRequestMessage *>(msgl)) {
|
|
switch (msg->type) {
|
|
case MessageType::AudioRecorderStart: {
|
|
if (auto input = audioMux.GetRecordingInput()) {
|
|
responseMsg = std::make_shared<AudioResponseMessage>(
|
|
(*input)->audio->Start(Operation::Type::Recorder, (*input)->token, msg->fileName.c_str()));
|
|
}
|
|
} break;
|
|
|
|
case MessageType::AudioRoutingStart: {
|
|
LOG_DEBUG("AudioRoutingStart");
|
|
if (auto input = audioMux.GetRoutingInput(true)) {
|
|
responseMsg = HandleStart(input, Operation::Type::Router);
|
|
}
|
|
} break;
|
|
|
|
case MessageType::AudioPause: {
|
|
responseMsg = HandlePause(msg);
|
|
} break;
|
|
|
|
case MessageType::AudioResume: {
|
|
if (auto input = audioMux.GetInput(msg->token)) {
|
|
responseMsg = std::make_shared<AudioResponseMessage>((*input)->audio->Resume());
|
|
}
|
|
} break;
|
|
|
|
case MessageType::AudioGetFileTags: {
|
|
if (auto input = audioMux.GetAvailableInput()) {
|
|
auto tag = (*input)->audio->GetFileTags(msg->fileName.c_str());
|
|
if (tag) {
|
|
responseMsg = std::make_shared<AudioResponseMessage>(RetCode::Success, tag.value());
|
|
}
|
|
else {
|
|
responseMsg = std::make_shared<AudioResponseMessage>(RetCode::FileDoesntExist);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case MessageType::AudioRoutingRecordCtrl: {
|
|
if (auto input = audioMux.GetRecordingInput()) {
|
|
responseMsg = std::make_shared<AudioResponseMessage>((*input)->audio->SendEvent(
|
|
msg->enable ? Operation::Event::StartCallRecording : Operation::Event::StopCallRecording));
|
|
}
|
|
} break;
|
|
|
|
case MessageType::AudioRoutingMute: {
|
|
if (auto input = audioMux.GetRecordingInput()) {
|
|
responseMsg = std::make_shared<AudioResponseMessage>((*input)->audio->SendEvent(
|
|
msg->enable ? Operation::Event::CallMute : Operation::Event::CallUnmute));
|
|
}
|
|
} break;
|
|
|
|
case MessageType::AudioRoutingSpeakerhone: {
|
|
if (auto input = audioMux.GetRecordingInput()) {
|
|
responseMsg = std::make_shared<AudioResponseMessage>((*input)->audio->SendEvent(
|
|
msg->enable ? Operation::Event::CallSpeakerphoneOn : Operation::Event::CallSpeakerphoneOff));
|
|
}
|
|
} break;
|
|
case MessageType::AudioRoutingHeadset: {
|
|
if (auto input = audioMux.GetRecordingInput()) {
|
|
responseMsg = std::make_shared<AudioResponseMessage>((*input)->audio->SendEvent(
|
|
msg->enable ? Operation::Event::HeadphonesPlugin : Operation::Event::HeadphonesUnplug));
|
|
}
|
|
} break;
|
|
|
|
default:
|
|
LOG_ERROR("Unhandled AudioRequestMessage");
|
|
responseMsg = std::make_shared<AudioResponseMessage>(RetCode::Failed);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
LOG_DEBUG("Unhandled message");
|
|
}
|
|
|
|
if (responseMsg) {
|
|
return responseMsg;
|
|
}
|
|
else {
|
|
return std::make_shared<AudioResponseMessage>(RetCode::Failed, Tags{}, Token::MakeBadToken());
|
|
}
|
|
}
|
|
|
|
void ServiceAudio::updateDbValue(const std::string &path, const audio::Setting &setting, const std::string &value)
|
|
{
|
|
if (path.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto query = std::make_unique<db::query::settings::UpdateQuery>(
|
|
SettingsRecord_v2({{.ID = DB_ID_NONE}, .path = path, .value = value}));
|
|
|
|
DBServiceAPI::GetQuery(this, db::Interface::Name::Settings_v2, std::move(query));
|
|
}
|
|
|
|
void ServiceAudio::updateDbValue(const audio::Operation *currentOperation,
|
|
const audio::Setting &setting,
|
|
const uint32_t &value)
|
|
{
|
|
if (currentOperation == nullptr) {
|
|
return;
|
|
}
|
|
const auto currentProfile = currentOperation->GetProfile();
|
|
|
|
auto dbPath = audio::str(setting, currentOperation->GetPlaybackType(), currentProfile->GetType());
|
|
updateDbValue(dbPath, setting, std::to_string(value));
|
|
}
|
|
|
|
void ServiceAudio::updateDbValue(const audio::Operation *currentOperation,
|
|
const audio::Setting &setting,
|
|
const bool &value)
|
|
{
|
|
if (currentOperation == nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto dbPath = audio::str(setting, currentOperation->GetPlaybackType());
|
|
updateDbValue(dbPath, setting, std::to_string(value));
|
|
}
|
|
|
|
void ServiceAudio::setSetting(const audio::Setting &setting,
|
|
const std::string &value,
|
|
const audio::Profile::Type &profileType,
|
|
const audio::PlaybackType &playbackType)
|
|
{
|
|
if (setting == audio::Setting::EnableVibration || setting == audio::Setting::EnableSound) {
|
|
const auto path = audio::str(setting, playbackType);
|
|
addOrIgnoreEntry(path, value);
|
|
updateDbValue(path, setting, value);
|
|
}
|
|
else {
|
|
if (profileType == Profile::Type::Idle) {
|
|
setCurrentSetting(setting, value);
|
|
}
|
|
const auto path = audio::str(setting, playbackType, profileType);
|
|
updateDbValue(path, setting, value);
|
|
}
|
|
}
|
|
|
|
std::string ServiceAudio::getSetting(const audio::Setting &setting,
|
|
const audio::Profile::Type &profileType,
|
|
const audio::PlaybackType &playbackType)
|
|
{
|
|
const std::string defaultValue = "";
|
|
if (profileType == audio::Profile::Type::Idle) {
|
|
if (const auto optSetting = getCurrentSetting(setting); optSetting.has_value()) {
|
|
return optSetting.value();
|
|
}
|
|
return defaultValue;
|
|
}
|
|
const auto path = audio::str(setting, playbackType, profileType);
|
|
return fetchAudioSettingFromDb(path, defaultValue);
|
|
}
|
|
|
|
std::optional<std::string> ServiceAudio::getCurrentSetting(const audio::Setting &setting)
|
|
{
|
|
const auto activeInput = audioMux.GetActiveInput();
|
|
|
|
if (!activeInput.has_value()) {
|
|
const auto idleInput = audioMux.GetIdleInput();
|
|
if (idleInput.has_value() && setting == audio::Setting::Volume) {
|
|
const auto path = audio::str(audio::Setting::Volume,
|
|
audio::PlaybackType::CallRingtone,
|
|
(*idleInput)->audio->GetHeadphonesInserted());
|
|
return fetchAudioSettingFromDb<std::string>(path, "");
|
|
}
|
|
return {};
|
|
}
|
|
|
|
const auto currentOperation = (*activeInput)->audio->GetCurrentOperation();
|
|
const auto path = [&setting, ¤tOperation]() -> std::string {
|
|
if (setting == audio::Setting::EnableVibration || setting == audio::Setting::EnableSound) {
|
|
return audio::str(setting, currentOperation->GetPlaybackType());
|
|
}
|
|
else {
|
|
return audio::str(setting, currentOperation->GetPlaybackType(), currentOperation->GetProfile()->GetType());
|
|
}
|
|
}();
|
|
return fetchAudioSettingFromDb<std::string>(path, "");
|
|
}
|
|
|
|
void ServiceAudio::setCurrentSetting(const audio::Setting &setting, const std::string &value)
|
|
{
|
|
const auto activeInput = audioMux.GetActiveInput();
|
|
|
|
if (!activeInput.has_value()) {
|
|
const auto idleInput = audioMux.GetIdleInput();
|
|
if (idleInput.has_value() && setting == audio::Setting::Volume) {
|
|
const auto path = audio::str(audio::Setting::Volume,
|
|
audio::PlaybackType::CallRingtone,
|
|
(*idleInput)->audio->GetHeadphonesInserted());
|
|
addOrIgnoreEntry(path, std::to_string(audio::playbackDefaults::defaultLoudspeakerVolume));
|
|
const auto valueToSet = std::clamp(utils::getValue<audio::Volume>(value), minVolume, maxVolume);
|
|
updateDbValue(path, Setting::Volume, std::to_string(valueToSet));
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto &audio = (*activeInput)->audio;
|
|
const auto currentOperation = audio->GetCurrentOperation();
|
|
switch (setting) {
|
|
case audio::Setting::Volume: {
|
|
const auto valueToSet = std::clamp(utils::getValue<audio::Volume>(value), minVolume, maxVolume);
|
|
if (audio->SetOutputVolume(valueToSet) == audio::RetCode::Success) {
|
|
updateDbValue(currentOperation, setting, valueToSet);
|
|
}
|
|
} break;
|
|
case audio::Setting::Gain: {
|
|
const auto valueToSet = std::clamp(utils::getValue<audio::Gain>(value), minGain, maxGain);
|
|
if (audio->SetInputGain(valueToSet) == audio::RetCode::Success) {
|
|
updateDbValue(currentOperation, setting, valueToSet);
|
|
}
|
|
} break;
|
|
case audio::Setting::EnableVibration: {
|
|
const auto valueToSet = utils::getValue<audio::Vibrate>(value);
|
|
updateDbValue(currentOperation, setting, valueToSet);
|
|
} break;
|
|
case audio::Setting::EnableSound: {
|
|
const auto valueToSet = utils::getValue<audio::EnableSound>(value);
|
|
updateDbValue(currentOperation, setting, valueToSet);
|
|
}
|
|
}
|
|
}
|