Merge branch 'Tryibion-audio-device-change'
This commit is contained in:
@@ -41,6 +41,8 @@ Array<AudioSource*> Audio::Sources;
|
||||
Array<AudioDevice> Audio::Devices;
|
||||
Action Audio::DevicesChanged;
|
||||
Action Audio::ActiveDeviceChanged;
|
||||
Action Audio::DeviceAdded;
|
||||
Action Audio::DeviceRemoved;
|
||||
AudioBackend* AudioBackend::Instance = nullptr;
|
||||
|
||||
namespace
|
||||
@@ -50,6 +52,8 @@ namespace
|
||||
int32 ActiveDeviceIndex = -1;
|
||||
bool MuteOnFocusLoss = true;
|
||||
bool EnableHRTF = true;
|
||||
StringAnsi ExplicitDeviceName;
|
||||
StringAnsi SystemDefaultDeviceName;
|
||||
}
|
||||
|
||||
class AudioService : public EngineService
|
||||
@@ -93,7 +97,22 @@ void AudioSettings::Apply()
|
||||
|
||||
AudioDevice* Audio::GetActiveDevice()
|
||||
{
|
||||
return &Devices[ActiveDeviceIndex];
|
||||
if (ActiveDeviceIndex >= 0 && ActiveDeviceIndex < Devices.Count())
|
||||
return &Devices[ActiveDeviceIndex];
|
||||
|
||||
if (!SystemDefaultDeviceName.IsEmpty() && Devices.HasItems())
|
||||
{
|
||||
for (int32 i = 0; i < Devices.Count(); i++)
|
||||
{
|
||||
if (Devices[i].InternalName == SystemDefaultDeviceName)
|
||||
return &Devices[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (Devices.HasItems())
|
||||
return &Devices[0];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int32 Audio::GetActiveDeviceIndex()
|
||||
@@ -101,9 +120,28 @@ int32 Audio::GetActiveDeviceIndex()
|
||||
return ActiveDeviceIndex;
|
||||
}
|
||||
|
||||
int32 Audio::GetActiveDeviceResolvedIndex()
|
||||
{
|
||||
AudioDevice* active = GetActiveDevice();
|
||||
if (active != nullptr)
|
||||
{
|
||||
for (int32 i = 0; i < Devices.Count(); i++)
|
||||
{
|
||||
if (&Devices[i] == active)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Audio::SetActiveDeviceIndex(int32 index)
|
||||
{
|
||||
index = Math::Clamp(index, -1, Devices.Count() - 1);
|
||||
if (index >= 0 && index < Devices.Count())
|
||||
ExplicitDeviceName = Devices[index].InternalName;
|
||||
else
|
||||
ExplicitDeviceName.Clear();
|
||||
|
||||
if (ActiveDeviceIndex == index)
|
||||
return;
|
||||
|
||||
@@ -114,6 +152,21 @@ void Audio::SetActiveDeviceIndex(int32 index)
|
||||
ActiveDeviceChanged();
|
||||
}
|
||||
|
||||
void Audio::SetActiveDeviceIndexSilent(int32 index)
|
||||
{
|
||||
ActiveDeviceIndex = Math::Clamp(index, -1, Devices.Count() - 1);
|
||||
}
|
||||
|
||||
const StringAnsi& Audio::GetExplicitDeviceName()
|
||||
{
|
||||
return ExplicitDeviceName;
|
||||
}
|
||||
|
||||
void Audio::SetSystemDefaultDeviceName(const StringAnsi& name)
|
||||
{
|
||||
SystemDefaultDeviceName = name;
|
||||
}
|
||||
|
||||
float Audio::GetMasterVolume()
|
||||
{
|
||||
return MasterVolume;
|
||||
|
||||
@@ -43,6 +43,16 @@ public:
|
||||
/// </summary>
|
||||
API_EVENT() static Action ActiveDeviceChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event called when a new audio device is connected to the system.
|
||||
/// </summary>
|
||||
API_EVENT() static Action DeviceAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Event called when an audio device is disconnected from the system.
|
||||
/// </summary>
|
||||
API_EVENT() static Action DeviceRemoved;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the active device.
|
||||
@@ -51,17 +61,39 @@ public:
|
||||
API_PROPERTY() static AudioDevice* GetActiveDevice();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the active device.
|
||||
/// Gets the index of the active device. Returns -1 if set to system default mode.
|
||||
/// </summary>
|
||||
/// <returns>The active device index.</returns>
|
||||
API_PROPERTY() static int32 GetActiveDeviceIndex();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resolved index in Devices array of the device currently playing (resolves system default mode to the actual device index).
|
||||
/// </summary>
|
||||
/// <returns>The resolved active device index.</returns>
|
||||
API_PROPERTY() static int32 GetActiveDeviceResolvedIndex();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index of the active device.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
API_PROPERTY() static void SetActiveDeviceIndex(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index of the active device without notifying backend.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
static void SetActiveDeviceIndexSilent(int32 index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal device name explicitly selected by the user (empty if using dynamic system default device).
|
||||
/// </summary>
|
||||
static const StringAnsi& GetExplicitDeviceName();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the system default internal device name.
|
||||
/// </summary>
|
||||
static void SetSystemDefaultDeviceName(const StringAnsi& name);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the master volume applied to all the audio sources (normalized to range 0-1).
|
||||
|
||||
@@ -102,6 +102,9 @@ public:
|
||||
class Source
|
||||
{
|
||||
public:
|
||||
// Helper accessors for Audio Source playback restoring after audio device change
|
||||
static void SetStreamingFirstChunk(AudioSource* source, int32 chunkIndex);
|
||||
|
||||
FORCE_INLINE static uint32 Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
|
||||
{
|
||||
return Instance->Source_Add(format, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler);
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include "AudioBackend.h"
|
||||
#include "Audio.h"
|
||||
|
||||
void AudioBackend::Source::SetStreamingFirstChunk(AudioSource* source, int32 chunkIndex)
|
||||
{
|
||||
source->_streamingFirstChunk = chunkIndex;
|
||||
}
|
||||
|
||||
AudioSource::AudioSource(const SpawnParams& params)
|
||||
: Actor(params)
|
||||
, _velocity(Vector3::Zero)
|
||||
@@ -195,13 +200,14 @@ void AudioSource::Stop()
|
||||
_state = States::Stopped;
|
||||
_isActuallyPlayingSth = false;
|
||||
_streamingFirstChunk = 0;
|
||||
_savedTime = 0.0f;
|
||||
if (SourceID)
|
||||
AudioBackend::Source::Stop(SourceID);
|
||||
}
|
||||
|
||||
float AudioSource::GetTime() const
|
||||
{
|
||||
if (_state == States::Stopped || SourceID == 0 || !Clip->IsLoaded())
|
||||
if (_state == States::Stopped || SourceID == 0 || Clip == nullptr || !Clip->IsLoaded())
|
||||
return 0.0f;
|
||||
|
||||
float time = AudioBackend::Source::GetCurrentBufferTime(SourceID);
|
||||
@@ -216,6 +222,11 @@ float AudioSource::GetTime() const
|
||||
|
||||
time = Math::Clamp(time, 0.0f, Clip->GetLength());
|
||||
|
||||
if (time <= 0.0001f && _savedTime > 0.0001f)
|
||||
{
|
||||
time = _savedTime;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
@@ -233,6 +244,9 @@ void AudioSource::SetTime(float time)
|
||||
Stop();
|
||||
}
|
||||
|
||||
// Restore _savedTime after Stop() resets it
|
||||
_savedTime = time;
|
||||
|
||||
if (UseStreaming())
|
||||
{
|
||||
// Update the first audio clip chunk to use for streaming and peek the relative time offset from the chunk start
|
||||
@@ -402,16 +416,24 @@ void AudioSource::Update()
|
||||
AudioBackend::Source::VelocityChanged(SourceID, _velocity);
|
||||
}
|
||||
|
||||
float curTime = 0.0f;
|
||||
if (_state != States::Stopped && SourceID && Clip && Clip->IsLoaded())
|
||||
{
|
||||
curTime = GetTime();
|
||||
if (curTime > 0.0001f)
|
||||
_savedTime = curTime;
|
||||
}
|
||||
|
||||
// Reset starting to play value once time is greater than zero
|
||||
if (_startingToPlay && GetTime() > 0.0f)
|
||||
if (_startingToPlay && curTime > 0.0f)
|
||||
{
|
||||
_startingToPlay = false;
|
||||
}
|
||||
|
||||
if (Math::NearEqual(GetTime(), _startTime) && _isActuallyPlayingSth && _startingToPlay)
|
||||
if (Math::NearEqual(curTime, _startTime) && _isActuallyPlayingSth && _startingToPlay)
|
||||
ClipStarted();
|
||||
|
||||
if (!UseStreaming() && Math::NearEqual(GetTime(), 0.0f) && _isActuallyPlayingSth && !_startingToPlay)
|
||||
if (!UseStreaming() && Math::NearEqual(curTime, 0.0f) && _isActuallyPlayingSth && !_startingToPlay)
|
||||
{
|
||||
int32 queuedBuffers;
|
||||
AudioBackend::Source::GetQueuedBuffersCount(SourceID, queuedBuffers);
|
||||
|
||||
@@ -18,6 +18,7 @@ class FLAXENGINE_API AudioSource : public Actor, IAssetReference
|
||||
DECLARE_SCENE_OBJECT(AudioSource);
|
||||
friend class AudioStreamingHandler;
|
||||
friend class AudioClip;
|
||||
friend class AudioBackend;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
#if AUDIO_API_OPENAL
|
||||
|
||||
#include "AudioBackendOAL.h"
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#include "Engine/Platform/StringUtils.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Tools/AudioTool/AudioTool.h"
|
||||
#include "Engine/Engine/Units.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Audio/Audio.h"
|
||||
@@ -204,23 +206,57 @@ namespace ALC
|
||||
Listener::Rebuild(listener);
|
||||
}
|
||||
|
||||
void RebuildSources(const Array<AudioSourceState>& states)
|
||||
void RebuildSources(const Array<AudioSourceState>& states, float elapsedTime = 0.0f)
|
||||
{
|
||||
for (int32 i = 0; i < states.Count(); i++)
|
||||
{
|
||||
AudioSource* source = Audio::Sources[i];
|
||||
Source::Rebuild(source->SourceID, source->GetPosition(), source->GetOrientation(), source->GetVolume(), source->GetPitch(), source->GetPan(), source->GetIsLooping() && !source->UseStreaming(), source->Is3D(), source->GetAttenuation(), source->GetMinDistance(), source->GetDopplerFactor());
|
||||
|
||||
if (states.HasItems() && source->SourceID)
|
||||
if (source->SourceID)
|
||||
{
|
||||
// Restore playback state
|
||||
// Restore playback state and time position without restarting or pausing
|
||||
auto& state = states[i];
|
||||
if (state.State != AudioSource::States::Stopped)
|
||||
source->Play();
|
||||
if (state.State == AudioSource::States::Paused)
|
||||
source->Pause();
|
||||
if (state.State != AudioSource::States::Stopped)
|
||||
source->SetTime(state.Time);
|
||||
{
|
||||
if (source->Clip && source->Clip->IsLoaded())
|
||||
{
|
||||
float targetTime = state.Time;
|
||||
if (state.State == AudioSource::States::Playing && elapsedTime > 0.0f)
|
||||
{
|
||||
targetTime += elapsedTime;
|
||||
const float clipLength = source->Clip->GetLength();
|
||||
if (clipLength > 0.0001f)
|
||||
{
|
||||
if (source->GetIsLooping())
|
||||
targetTime = fmodf(targetTime, clipLength);
|
||||
else
|
||||
targetTime = Math::Min(targetTime, clipLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (source->UseStreaming())
|
||||
{
|
||||
float relativeTime = 0;
|
||||
const int32 chunkIndex = source->Clip->GetFirstBufferIndex(targetTime, relativeTime);
|
||||
AudioBackend::Source::SetStreamingFirstChunk(source, chunkIndex);
|
||||
source->RequestStreamingBuffersUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (source->Clip->Buffers.HasItems() && source->Clip->Buffers[0] != 0)
|
||||
{
|
||||
AudioBackend::Source::SetNonStreamingBuffer(source->SourceID, source->Clip->Buffers[0]);
|
||||
AudioBackend::Source::SetCurrentBufferTime(source->SourceID, targetTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.State == AudioSource::States::Playing)
|
||||
source->Play();
|
||||
else if (state.State == AudioSource::States::Paused)
|
||||
source->Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -658,7 +694,6 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Audio);
|
||||
|
||||
// Fast-path on startup
|
||||
if (!ALC::Inited && ALC::Device)
|
||||
{
|
||||
@@ -666,6 +701,8 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
return;
|
||||
}
|
||||
|
||||
const double startTime = Platform::GetTimeSeconds();
|
||||
|
||||
// Cleanup
|
||||
Array<ALC::AudioSourceState> states;
|
||||
states.EnsureCapacity(Audio::Sources.Count());
|
||||
@@ -687,15 +724,41 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
}
|
||||
|
||||
// Open device
|
||||
const StringAnsi& name = Audio::GetActiveDevice()->InternalName;
|
||||
ALC::Device = alcOpenDevice(name.Get());
|
||||
const StringAnsi* namePtr = (Audio::Devices.HasItems() && Audio::GetActiveDeviceIndex() >= 0 && Audio::GetActiveDeviceIndex() < Audio::Devices.Count()) ? &Audio::GetActiveDevice()->InternalName : nullptr;
|
||||
if (namePtr)
|
||||
{
|
||||
ALC::Device = alcOpenDevice(namePtr->Get());
|
||||
if (ALC::Device == nullptr)
|
||||
{
|
||||
LOG(Warning, "Failed to open active OpenAL device ({0}). Trying default device...", String(*namePtr));
|
||||
ALC::Device = alcOpenDevice(nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ALC::Device = alcOpenDevice(nullptr);
|
||||
}
|
||||
|
||||
if (ALC::Device == nullptr && Audio::Devices.HasItems())
|
||||
{
|
||||
for (int32 i = 0; i < Audio::Devices.Count(); i++)
|
||||
{
|
||||
ALC::Device = alcOpenDevice(Audio::Devices[i].InternalName.Get());
|
||||
if (ALC::Device != nullptr)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ALC::Device == nullptr)
|
||||
{
|
||||
LOG(Fatal, "Failed to open OpenAL device ({0}).", String(name));
|
||||
LOG(Warning, "Failed to open any OpenAL audio device.");
|
||||
return;
|
||||
}
|
||||
if (ALC::Inited)
|
||||
LOG(Info, "Changed audio device to: {}", String(Audio::GetActiveDevice()->Name));
|
||||
{
|
||||
const AudioDevice* activeDev = Audio::GetActiveDevice();
|
||||
LOG(Info, "Changed audio device to: {}", activeDev ? String(activeDev->Name) : TEXT("Default"));
|
||||
}
|
||||
|
||||
// Rebuild context
|
||||
ALC::RebuildContext();
|
||||
@@ -719,11 +782,16 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
else
|
||||
{
|
||||
// Reload audio clip
|
||||
auto assetLock = audioClip->Storage->Lock();
|
||||
audioClip->LoadChunk(0);
|
||||
audioClip->Buffers[0] = AudioBackend::Buffer::Create();
|
||||
audioClip->WriteBuffer(0);
|
||||
|
||||
if (audioClip->Storage)
|
||||
{
|
||||
auto assetLock = audioClip->Storage->Lock();
|
||||
audioClip->LoadChunk(0);
|
||||
}
|
||||
if (audioClip->Buffers.HasItems())
|
||||
{
|
||||
audioClip->Buffers[0] = AudioBackend::Buffer::Create();
|
||||
audioClip->WriteBuffer(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,8 +807,9 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
player.AudioSource = 0;
|
||||
}
|
||||
}
|
||||
const float elapsedTime = static_cast<float>(Platform::GetTimeSeconds() - startTime);
|
||||
ALC::RebuildListeners();
|
||||
ALC::RebuildSources(states);
|
||||
ALC::RebuildSources(states, elapsedTime);
|
||||
}
|
||||
|
||||
void AudioBackendOAL::Base_SetDopplerFactor(float value)
|
||||
@@ -786,6 +855,7 @@ bool AudioBackendOAL::Base_Init()
|
||||
const ALCchar* devicesStr = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||
|
||||
const StringAnsi defaultDeviceName(defaultDevice);
|
||||
Audio::SetSystemDefaultDeviceName(defaultDeviceName);
|
||||
|
||||
devices.Clear();
|
||||
devices.EnsureCapacity(8);
|
||||
@@ -848,13 +918,9 @@ bool AudioBackendOAL::Base_Init()
|
||||
}
|
||||
|
||||
// Init
|
||||
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
|
||||
if (Audio::GetActiveDeviceIndex() == Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1))
|
||||
{
|
||||
// Manually create context if SetActiveDeviceIndex won't call it
|
||||
Base_OnActiveDeviceChanged();
|
||||
}
|
||||
Audio::SetActiveDeviceIndex(activeDeviceIndex);
|
||||
// Create context and open initial device
|
||||
Base_OnActiveDeviceChanged();
|
||||
Audio::SetActiveDeviceIndexSilent(activeDeviceIndex);
|
||||
#ifdef AL_SOFT_source_spatialize
|
||||
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
|
||||
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
|
||||
@@ -875,8 +941,204 @@ bool AudioBackendOAL::Base_Init()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef ALC_CONNECTED
|
||||
#define ALC_CONNECTED 0x313
|
||||
#endif
|
||||
|
||||
void AudioBackendOAL::Base_Update()
|
||||
{
|
||||
// Check if active audio device got disconnected or invalidated (WASAPI AUDCLNT_E_DEVICE_INVALIDATED 0x88890004)
|
||||
bool forceCheck = false;
|
||||
if (ALC::Device != nullptr)
|
||||
{
|
||||
ALCint connected = ALC_TRUE;
|
||||
alcGetIntegerv(ALC::Device, ALC_CONNECTED, 1, &connected);
|
||||
if (connected == ALC_FALSE || alcGetError(ALC::Device) != ALC_NO_ERROR)
|
||||
{
|
||||
LOG(Warning, "Active audio device disconnected. Swapping active audio device...");
|
||||
forceCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if ALC_ENUMERATE_ALL_EXT
|
||||
|
||||
// Update audio devices list periodically (throttled every 0.25 seconds / 250ms, or immediately if disconnected)
|
||||
static float deviceCheckTimer = 0.0f;
|
||||
static StringAnsi lastDefaultDeviceName;
|
||||
deviceCheckTimer += Time::Update.UnscaledDeltaTime.GetTotalSeconds();
|
||||
if (deviceCheckTimer >= 0.25f || forceCheck)
|
||||
{
|
||||
deviceCheckTimer = 0.0f;
|
||||
if (ALC::IsExtensionSupported("ALC_ENUMERATE_ALL_EXT"))
|
||||
{
|
||||
const ALCchar* defaultDevice = alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
|
||||
const StringAnsi defaultDeviceName(defaultDevice);
|
||||
Audio::SetSystemDefaultDeviceName(defaultDeviceName);
|
||||
const ALCchar* devicesStr = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||
Array<AudioDevice> newDevices;
|
||||
int32 defaultDeviceIndex = -1;
|
||||
|
||||
while (devicesStr && *devicesStr)
|
||||
{
|
||||
const int32 i = newDevices.Count();
|
||||
auto& device = newDevices.AddOne();
|
||||
device.InternalName = devicesStr;
|
||||
device.Name = String(device.InternalName).TrimTrailing();
|
||||
device.Name.Replace(TEXT("OpenAL Soft on "), TEXT(""));
|
||||
|
||||
if (device.InternalName == defaultDeviceName)
|
||||
{
|
||||
defaultDeviceIndex = i;
|
||||
}
|
||||
|
||||
devicesStr += (device.InternalName.Length() + 1) * sizeof(ALCchar);
|
||||
}
|
||||
|
||||
if (defaultDeviceIndex == -1 && !newDevices.IsEmpty())
|
||||
defaultDeviceIndex = 0;
|
||||
|
||||
bool devicesListChanged = newDevices.Count() != Audio::Devices.Count();
|
||||
if (!devicesListChanged)
|
||||
{
|
||||
for (int32 i = 0; i < newDevices.Count(); i++)
|
||||
{
|
||||
if (newDevices[i].InternalName != Audio::Devices[i].InternalName)
|
||||
{
|
||||
devicesListChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int32 currentActiveIndex = Audio::GetActiveDeviceIndex();
|
||||
const StringAnsi& explicitDeviceName = Audio::GetExplicitDeviceName();
|
||||
|
||||
// Determine the internal name of the currently active device.
|
||||
// When in System Default Mode (index -1), fall back to the OS default name so
|
||||
// comparisons correctly identify "no change" and avoid spurious device swaps.
|
||||
StringAnsi currentActiveInternalName;
|
||||
if (currentActiveIndex >= 0 && currentActiveIndex < Audio::Devices.Count())
|
||||
currentActiveInternalName = Audio::Devices[currentActiveIndex].InternalName;
|
||||
else if (explicitDeviceName.IsEmpty())
|
||||
currentActiveInternalName = defaultDeviceName; // System Default Mode: treat current as OS default
|
||||
|
||||
int32 currentActiveNewIndex = -1;
|
||||
int32 explicitDeviceNewIndex = -1;
|
||||
|
||||
for (int32 i = 0; i < newDevices.Count(); i++)
|
||||
{
|
||||
if (!currentActiveInternalName.IsEmpty() && newDevices[i].InternalName == currentActiveInternalName)
|
||||
{
|
||||
currentActiveNewIndex = i;
|
||||
}
|
||||
if (!explicitDeviceName.IsEmpty() && newDevices[i].InternalName == explicitDeviceName)
|
||||
{
|
||||
explicitDeviceNewIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
bool defaultDeviceChanged = (!lastDefaultDeviceName.IsEmpty() && lastDefaultDeviceName != defaultDeviceName);
|
||||
lastDefaultDeviceName = defaultDeviceName;
|
||||
|
||||
int32 targetActiveIndex = -1;
|
||||
if (!explicitDeviceName.IsEmpty())
|
||||
{
|
||||
// Explicit Device Lock Mode: User selected a specific audio device
|
||||
if (explicitDeviceNewIndex != -1)
|
||||
{
|
||||
// Explicit device is available (stay locked or re-claim explicit device)
|
||||
targetActiveIndex = explicitDeviceNewIndex;
|
||||
}
|
||||
else if (forceCheck || currentActiveNewIndex == -1)
|
||||
{
|
||||
// Explicit device disconnected -> Fallback to system default
|
||||
targetActiveIndex = (defaultDeviceIndex != -1) ? defaultDeviceIndex : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetActiveIndex = currentActiveNewIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dynamic System Default Mode: Automatically follow OS default device changes
|
||||
if (forceCheck)
|
||||
{
|
||||
targetActiveIndex = (defaultDeviceIndex != -1) ? defaultDeviceIndex : 0;
|
||||
}
|
||||
else if (defaultDeviceChanged && defaultDeviceIndex != -1)
|
||||
{
|
||||
targetActiveIndex = defaultDeviceIndex;
|
||||
}
|
||||
else if (currentActiveNewIndex != -1)
|
||||
{
|
||||
targetActiveIndex = currentActiveNewIndex;
|
||||
}
|
||||
else if (defaultDeviceIndex != -1)
|
||||
{
|
||||
targetActiveIndex = defaultDeviceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (devicesListChanged)
|
||||
{
|
||||
// Fire DeviceAdded for each device in newDevices not present in old list
|
||||
for (int32 i = 0; i < newDevices.Count(); i++)
|
||||
{
|
||||
bool found = false;
|
||||
for (int32 j = 0; j < Audio::Devices.Count(); j++)
|
||||
{
|
||||
if (Audio::Devices[j].InternalName == newDevices[i].InternalName)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
Audio::DeviceAdded();
|
||||
}
|
||||
|
||||
// Fire DeviceRemoved for each device in old list not present in newDevices
|
||||
for (int32 i = 0; i < Audio::Devices.Count(); i++)
|
||||
{
|
||||
bool found = false;
|
||||
for (int32 j = 0; j < newDevices.Count(); j++)
|
||||
{
|
||||
if (newDevices[j].InternalName == Audio::Devices[i].InternalName)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
Audio::DeviceRemoved();
|
||||
}
|
||||
|
||||
Audio::Devices = newDevices;
|
||||
Audio::DevicesChanged();
|
||||
}
|
||||
|
||||
if (targetActiveIndex != -1)
|
||||
{
|
||||
StringAnsi targetActiveInternalName = newDevices[targetActiveIndex].InternalName;
|
||||
if (forceCheck)
|
||||
{
|
||||
Audio::SetActiveDeviceIndexSilent(targetActiveIndex);
|
||||
Base_OnActiveDeviceChanged();
|
||||
}
|
||||
else if (targetActiveInternalName != currentActiveInternalName)
|
||||
{
|
||||
Audio::SetActiveDeviceIndexSilent(targetActiveIndex);
|
||||
Base_OnActiveDeviceChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Audio::SetActiveDeviceIndexSilent(targetActiveIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioBackendOAL::Base_Dispose()
|
||||
|
||||
Reference in New Issue
Block a user