From cbc83e67f8578d3b51aa51485ca1f2af052ecd6f Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:52:11 +0200 Subject: [PATCH 1/4] Remove some example usage --- Runtime/Scripts/PlatformAudio.cs | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/Runtime/Scripts/PlatformAudio.cs b/Runtime/Scripts/PlatformAudio.cs index 1525eb42..7df9c5f6 100644 --- a/Runtime/Scripts/PlatformAudio.cs +++ b/Runtime/Scripts/PlatformAudio.cs @@ -67,34 +67,7 @@ public struct AudioDevice /// 2. Optionally enumerate and select devices /// 3. Create audio tracks using PlatformAudioSource /// 4. Remote audio automatically plays through speakers - /// - /// - /// - /// // Create PlatformAudio (enables ADM) - /// var platformAudio = new PlatformAudio(); - /// - /// // Enumerate devices - /// var (recording, playout) = platformAudio.GetDevices(); - /// foreach (var device in recording) - /// Debug.Log($"Mic {device.Index}: {device.Name}"); - /// - /// // Select devices (no-op on Android/iOS; routing there is governed by the OS). - /// // Use the uint overload for quick index-based selection, or the string overload - /// // with a GUID from GetDevices() to persist a stable selection across hot-plug. - /// platformAudio.SetRecordingDevice(0); - /// platformAudio.SetPlayoutDevice(0); - /// - /// // Create audio source and track - /// var source = new PlatformAudioSource(platformAudio); - /// var track = LocalAudioTrack.CreateAudioTrack("microphone", source, room); - /// - /// // Publish track - /// await room.LocalParticipant.PublishTrack(track, options); - /// - /// // Dispose when done - /// platformAudio.Dispose(); - /// - /// + public sealed class PlatformAudio : IDisposable { internal readonly FfiHandle Handle; From 18a83aebd42da39c9e095ad31b32ea01b994a988 Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:40:28 +0200 Subject: [PATCH 2/4] PlatformAudio: expose IsDeviceSelectionSupported; bump FFI submodule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumes the device-selection capability added in rust-sdks#1156. - Bump client-sdk-rust~ to the device-selection commit (regen proto includes PlatformAudioInfo.device_selection_supported). - Regenerate Runtime/Scripts/Proto/AudioFrame.cs. - PlatformAudio.IsDeviceSelectionSupported — false on iOS/Android, true on desktop; lets clients hide device-picker UI where selection has no effect. - Meet sample: guard SetRecordingDevice/SetPlayoutDevice behind the flag. - Add a PlayMode test asserting the flag is true on desktop (editor). Note: submodule points at the rust-sdks PR branch; re-point to the merged SHA once that PR lands. Native binaries are produced by CI from the submodule. Co-Authored-By: Claude Opus 4.8 (1M context) --- Runtime/Scripts/PlatformAudio.cs | 12 ++ Runtime/Scripts/Proto/AudioFrame.cs | 128 ++++++++++++++------ Samples~/Meet/Assets/Runtime/MeetManager.cs | 17 ++- Tests/PlayMode/PlatformAudioTests.cs | 13 ++ client-sdk-rust~ | 2 +- 5 files changed, 132 insertions(+), 40 deletions(-) diff --git a/Runtime/Scripts/PlatformAudio.cs b/Runtime/Scripts/PlatformAudio.cs index 7df9c5f6..f9d903ee 100644 --- a/Runtime/Scripts/PlatformAudio.cs +++ b/Runtime/Scripts/PlatformAudio.cs @@ -84,6 +84,18 @@ public sealed class PlatformAudio : IDisposable /// public int PlayoutDeviceCount => _info.PlayoutDeviceCount; + /// + /// Whether per-device selection is honored on this platform. + /// + /// True on desktop (Windows, macOS, Linux), where + /// and select the device. False on iOS/Android, + /// where audio routing is controlled by the OS (AVAudioSession / AudioManager); the + /// selection methods are accepted but have no effect there. + /// + /// Use this to decide whether to present a device-picker UI. + /// + public bool IsDeviceSelectionSupported => _info.DeviceSelectionSupported; + /// /// Creates a new PlatformAudio instance, enabling the platform ADM. /// diff --git a/Runtime/Scripts/Proto/AudioFrame.cs b/Runtime/Scripts/Proto/AudioFrame.cs index d73a6af1..bc296d99 100644 --- a/Runtime/Scripts/Proto/AudioFrame.cs +++ b/Runtime/Scripts/Proto/AudioFrame.cs @@ -126,41 +126,42 @@ static AudioFrameReflection() { "YXRoGAEgAigJEhQKDGRlcGVuZGVuY2llcxgCIAMoCRIRCgltb2R1bGVfaWQY", "AyACKAkiLgodTG9hZEF1ZGlvRmlsdGVyUGx1Z2luUmVzcG9uc2USDQoFZXJy", "b3IYASABKAkiPAoPQXVkaW9EZXZpY2VJbmZvEg0KBWluZGV4GAEgAigNEgwK", - "BG5hbWUYAiACKAkSDAoEZ3VpZBgDIAEoCSJRChFQbGF0Zm9ybUF1ZGlvSW5m", + "BG5hbWUYAiACKAkSDAoEZ3VpZBgDIAEoCSJ1ChFQbGF0Zm9ybUF1ZGlvSW5m", "bxIeChZyZWNvcmRpbmdfZGV2aWNlX2NvdW50GAEgAigFEhwKFHBsYXlvdXRf", - "ZGV2aWNlX2NvdW50GAIgAigFInMKEk93bmVkUGxhdGZvcm1BdWRpbxItCgZo", - "YW5kbGUYASACKAsyHS5saXZla2l0LnByb3RvLkZmaU93bmVkSGFuZGxlEi4K", - "BGluZm8YAiACKAsyIC5saXZla2l0LnByb3RvLlBsYXRmb3JtQXVkaW9JbmZv", - "IhkKF05ld1BsYXRmb3JtQXVkaW9SZXF1ZXN0InMKGE5ld1BsYXRmb3JtQXVk", - "aW9SZXNwb25zZRI7Cg5wbGF0Zm9ybV9hdWRpbxgBIAEoCzIhLmxpdmVraXQu", - "cHJvdG8uT3duZWRQbGF0Zm9ybUF1ZGlvSAASDwoFZXJyb3IYAiABKAlIAEIJ", - "CgdtZXNzYWdlIjcKFkdldEF1ZGlvRGV2aWNlc1JlcXVlc3QSHQoVcGxhdGZv", - "cm1fYXVkaW9faGFuZGxlGAEgAigEIpwBChdHZXRBdWRpb0RldmljZXNSZXNw", - "b25zZRI3Cg9wbGF5b3V0X2RldmljZXMYASADKAsyHi5saXZla2l0LnByb3Rv", - "LkF1ZGlvRGV2aWNlSW5mbxI5ChFyZWNvcmRpbmdfZGV2aWNlcxgCIAMoCzIe", - "LmxpdmVraXQucHJvdG8uQXVkaW9EZXZpY2VJbmZvEg0KBWVycm9yGAMgASgJ", - "Ik0KGVNldFJlY29yZGluZ0RldmljZVJlcXVlc3QSHQoVcGxhdGZvcm1fYXVk", - "aW9faGFuZGxlGAEgAigEEhEKCWRldmljZV9pZBgCIAIoCSIrChpTZXRSZWNv", - "cmRpbmdEZXZpY2VSZXNwb25zZRINCgVlcnJvchgBIAEoCSJLChdTZXRQbGF5", - "b3V0RGV2aWNlUmVxdWVzdBIdChVwbGF0Zm9ybV9hdWRpb19oYW5kbGUYASAC", - "KAQSEQoJZGV2aWNlX2lkGAIgAigJIikKGFNldFBsYXlvdXREZXZpY2VSZXNw", - "b25zZRINCgVlcnJvchgBIAEoCSI2ChVTdGFydFJlY29yZGluZ1JlcXVlc3QS", - "HQoVcGxhdGZvcm1fYXVkaW9faGFuZGxlGAEgAigEIicKFlN0YXJ0UmVjb3Jk", - "aW5nUmVzcG9uc2USDQoFZXJyb3IYASABKAkiNQoUU3RvcFJlY29yZGluZ1Jl", - "cXVlc3QSHQoVcGxhdGZvcm1fYXVkaW9faGFuZGxlGAEgAigEIiYKFVN0b3BS", - "ZWNvcmRpbmdSZXNwb25zZRINCgVlcnJvchgBIAEoCSpKChRTb3hSZXNhbXBs", - "ZXJEYXRhVHlwZRIYChRTT1hSX0RBVEFUWVBFX0lOVDE2SRAAEhgKFFNPWFJf", - "REFUQVRZUEVfSU5UMTZTEAEqiwEKEFNveFF1YWxpdHlSZWNpcGUSFgoSU09Y", - "Ul9RVUFMSVRZX1FVSUNLEAASFAoQU09YUl9RVUFMSVRZX0xPVxABEhcKE1NP", - "WFJfUVVBTElUWV9NRURJVU0QAhIVChFTT1hSX1FVQUxJVFlfSElHSBADEhkK", - "FVNPWFJfUVVBTElUWV9WRVJZSElHSBAEKpcBCgtTb3hGbGFnQml0cxIWChJT", - "T1hSX1JPTExPRkZfU01BTEwQABIXChNTT1hSX1JPTExPRkZfTUVESVVNEAES", - "FQoRU09YUl9ST0xMT0ZGX05PTkUQAhIYChRTT1hSX0hJR0hfUFJFQ19DTE9D", - "SxADEhkKFVNPWFJfRE9VQkxFX1BSRUNJU0lPThAEEgsKB1NPWFJfVlIQBSpB", - "Cg9BdWRpb1N0cmVhbVR5cGUSFwoTQVVESU9fU1RSRUFNX05BVElWRRAAEhUK", - "EUFVRElPX1NUUkVBTV9IVE1MEAEqRQoPQXVkaW9Tb3VyY2VUeXBlEhcKE0FV", - "RElPX1NPVVJDRV9OQVRJVkUQABIZChVBVURJT19TT1VSQ0VfUExBVEZPUk0Q", - "AUIQqgINTGl2ZUtpdC5Qcm90bw==")); + "ZGV2aWNlX2NvdW50GAIgAigFEiIKGmRldmljZV9zZWxlY3Rpb25fc3VwcG9y", + "dGVkGAMgAigIInMKEk93bmVkUGxhdGZvcm1BdWRpbxItCgZoYW5kbGUYASAC", + "KAsyHS5saXZla2l0LnByb3RvLkZmaU93bmVkSGFuZGxlEi4KBGluZm8YAiAC", + "KAsyIC5saXZla2l0LnByb3RvLlBsYXRmb3JtQXVkaW9JbmZvIhkKF05ld1Bs", + "YXRmb3JtQXVkaW9SZXF1ZXN0InMKGE5ld1BsYXRmb3JtQXVkaW9SZXNwb25z", + "ZRI7Cg5wbGF0Zm9ybV9hdWRpbxgBIAEoCzIhLmxpdmVraXQucHJvdG8uT3du", + "ZWRQbGF0Zm9ybUF1ZGlvSAASDwoFZXJyb3IYAiABKAlIAEIJCgdtZXNzYWdl", + "IjcKFkdldEF1ZGlvRGV2aWNlc1JlcXVlc3QSHQoVcGxhdGZvcm1fYXVkaW9f", + "aGFuZGxlGAEgAigEIpwBChdHZXRBdWRpb0RldmljZXNSZXNwb25zZRI3Cg9w", + "bGF5b3V0X2RldmljZXMYASADKAsyHi5saXZla2l0LnByb3RvLkF1ZGlvRGV2", + "aWNlSW5mbxI5ChFyZWNvcmRpbmdfZGV2aWNlcxgCIAMoCzIeLmxpdmVraXQu", + "cHJvdG8uQXVkaW9EZXZpY2VJbmZvEg0KBWVycm9yGAMgASgJIk0KGVNldFJl", + "Y29yZGluZ0RldmljZVJlcXVlc3QSHQoVcGxhdGZvcm1fYXVkaW9faGFuZGxl", + "GAEgAigEEhEKCWRldmljZV9pZBgCIAIoCSIrChpTZXRSZWNvcmRpbmdEZXZp", + "Y2VSZXNwb25zZRINCgVlcnJvchgBIAEoCSJLChdTZXRQbGF5b3V0RGV2aWNl", + "UmVxdWVzdBIdChVwbGF0Zm9ybV9hdWRpb19oYW5kbGUYASACKAQSEQoJZGV2", + "aWNlX2lkGAIgAigJIikKGFNldFBsYXlvdXREZXZpY2VSZXNwb25zZRINCgVl", + "cnJvchgBIAEoCSI2ChVTdGFydFJlY29yZGluZ1JlcXVlc3QSHQoVcGxhdGZv", + "cm1fYXVkaW9faGFuZGxlGAEgAigEIicKFlN0YXJ0UmVjb3JkaW5nUmVzcG9u", + "c2USDQoFZXJyb3IYASABKAkiNQoUU3RvcFJlY29yZGluZ1JlcXVlc3QSHQoV", + "cGxhdGZvcm1fYXVkaW9faGFuZGxlGAEgAigEIiYKFVN0b3BSZWNvcmRpbmdS", + "ZXNwb25zZRINCgVlcnJvchgBIAEoCSpKChRTb3hSZXNhbXBsZXJEYXRhVHlw", + "ZRIYChRTT1hSX0RBVEFUWVBFX0lOVDE2SRAAEhgKFFNPWFJfREFUQVRZUEVf", + "SU5UMTZTEAEqiwEKEFNveFF1YWxpdHlSZWNpcGUSFgoSU09YUl9RVUFMSVRZ", + "X1FVSUNLEAASFAoQU09YUl9RVUFMSVRZX0xPVxABEhcKE1NPWFJfUVVBTElU", + "WV9NRURJVU0QAhIVChFTT1hSX1FVQUxJVFlfSElHSBADEhkKFVNPWFJfUVVB", + "TElUWV9WRVJZSElHSBAEKpcBCgtTb3hGbGFnQml0cxIWChJTT1hSX1JPTExP", + "RkZfU01BTEwQABIXChNTT1hSX1JPTExPRkZfTUVESVVNEAESFQoRU09YUl9S", + "T0xMT0ZGX05PTkUQAhIYChRTT1hSX0hJR0hfUFJFQ19DTE9DSxADEhkKFVNP", + "WFJfRE9VQkxFX1BSRUNJU0lPThAEEgsKB1NPWFJfVlIQBSpBCg9BdWRpb1N0", + "cmVhbVR5cGUSFwoTQVVESU9fU1RSRUFNX05BVElWRRAAEhUKEUFVRElPX1NU", + "UkVBTV9IVE1MEAEqRQoPQXVkaW9Tb3VyY2VUeXBlEhcKE0FVRElPX1NPVVJD", + "RV9OQVRJVkUQABIZChVBVURJT19TT1VSQ0VfUExBVEZPUk0QAUIQqgINTGl2", + "ZUtpdC5Qcm90bw==")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { global::LiveKit.Proto.HandleReflection.Descriptor, global::LiveKit.Proto.TrackReflection.Descriptor, }, new pbr::GeneratedClrTypeInfo(new[] {typeof(global::LiveKit.Proto.SoxResamplerDataType), typeof(global::LiveKit.Proto.SoxQualityRecipe), typeof(global::LiveKit.Proto.SoxFlagBits), typeof(global::LiveKit.Proto.AudioStreamType), typeof(global::LiveKit.Proto.AudioSourceType), }, null, new pbr::GeneratedClrTypeInfo[] { @@ -211,7 +212,7 @@ static AudioFrameReflection() { new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.LoadAudioFilterPluginRequest), global::LiveKit.Proto.LoadAudioFilterPluginRequest.Parser, new[]{ "PluginPath", "Dependencies", "ModuleId" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.LoadAudioFilterPluginResponse), global::LiveKit.Proto.LoadAudioFilterPluginResponse.Parser, new[]{ "Error" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.AudioDeviceInfo), global::LiveKit.Proto.AudioDeviceInfo.Parser, new[]{ "Index", "Name", "Guid" }, null, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.PlatformAudioInfo), global::LiveKit.Proto.PlatformAudioInfo.Parser, new[]{ "RecordingDeviceCount", "PlayoutDeviceCount" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.PlatformAudioInfo), global::LiveKit.Proto.PlatformAudioInfo.Parser, new[]{ "RecordingDeviceCount", "PlayoutDeviceCount", "DeviceSelectionSupported" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.OwnedPlatformAudio), global::LiveKit.Proto.OwnedPlatformAudio.Parser, new[]{ "Handle", "Info" }, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.NewPlatformAudioRequest), global::LiveKit.Proto.NewPlatformAudioRequest.Parser, null, null, null, null, null), new pbr::GeneratedClrTypeInfo(typeof(global::LiveKit.Proto.NewPlatformAudioResponse), global::LiveKit.Proto.NewPlatformAudioResponse.Parser, new[]{ "PlatformAudio", "Error" }, new[]{ "Message" }, null, null, null), @@ -13683,6 +13684,7 @@ public PlatformAudioInfo(PlatformAudioInfo other) : this() { _hasBits0 = other._hasBits0; recordingDeviceCount_ = other.recordingDeviceCount_; playoutDeviceCount_ = other.playoutDeviceCount_; + deviceSelectionSupported_ = other.deviceSelectionSupported_; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } @@ -13752,6 +13754,38 @@ public void ClearPlayoutDeviceCount() { _hasBits0 &= ~2; } + /// Field number for the "device_selection_supported" field. + public const int DeviceSelectionSupportedFieldNumber = 3; + private readonly static bool DeviceSelectionSupportedDefaultValue = false; + + private bool deviceSelectionSupported_; + /// + /// Whether per-device selection is honored on this platform. False on iOS/Android, + /// where audio routing is controlled by the OS (AVAudioSession / AudioManager) rather + /// than through WebRTC device selection. + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool DeviceSelectionSupported { + get { if ((_hasBits0 & 4) != 0) { return deviceSelectionSupported_; } else { return DeviceSelectionSupportedDefaultValue; } } + set { + _hasBits0 |= 4; + deviceSelectionSupported_ = value; + } + } + /// Gets whether the "device_selection_supported" field is set + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool HasDeviceSelectionSupported { + get { return (_hasBits0 & 4) != 0; } + } + /// Clears the value of the "device_selection_supported" field + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void ClearDeviceSelectionSupported() { + _hasBits0 &= ~4; + } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public override bool Equals(object other) { @@ -13769,6 +13803,7 @@ public bool Equals(PlatformAudioInfo other) { } if (RecordingDeviceCount != other.RecordingDeviceCount) return false; if (PlayoutDeviceCount != other.PlayoutDeviceCount) return false; + if (DeviceSelectionSupported != other.DeviceSelectionSupported) return false; return Equals(_unknownFields, other._unknownFields); } @@ -13778,6 +13813,7 @@ public override int GetHashCode() { int hash = 1; if (HasRecordingDeviceCount) hash ^= RecordingDeviceCount.GetHashCode(); if (HasPlayoutDeviceCount) hash ^= PlayoutDeviceCount.GetHashCode(); + if (HasDeviceSelectionSupported) hash ^= DeviceSelectionSupported.GetHashCode(); if (_unknownFields != null) { hash ^= _unknownFields.GetHashCode(); } @@ -13804,6 +13840,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(16); output.WriteInt32(PlayoutDeviceCount); } + if (HasDeviceSelectionSupported) { + output.WriteRawTag(24); + output.WriteBool(DeviceSelectionSupported); + } if (_unknownFields != null) { _unknownFields.WriteTo(output); } @@ -13822,6 +13862,10 @@ public void WriteTo(pb::CodedOutputStream output) { output.WriteRawTag(16); output.WriteInt32(PlayoutDeviceCount); } + if (HasDeviceSelectionSupported) { + output.WriteRawTag(24); + output.WriteBool(DeviceSelectionSupported); + } if (_unknownFields != null) { _unknownFields.WriteTo(ref output); } @@ -13838,6 +13882,9 @@ public int CalculateSize() { if (HasPlayoutDeviceCount) { size += 1 + pb::CodedOutputStream.ComputeInt32Size(PlayoutDeviceCount); } + if (HasDeviceSelectionSupported) { + size += 1 + 1; + } if (_unknownFields != null) { size += _unknownFields.CalculateSize(); } @@ -13856,6 +13903,9 @@ public void MergeFrom(PlatformAudioInfo other) { if (other.HasPlayoutDeviceCount) { PlayoutDeviceCount = other.PlayoutDeviceCount; } + if (other.HasDeviceSelectionSupported) { + DeviceSelectionSupported = other.DeviceSelectionSupported; + } _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); } @@ -13883,6 +13933,10 @@ public void MergeFrom(pb::CodedInputStream input) { PlayoutDeviceCount = input.ReadInt32(); break; } + case 24: { + DeviceSelectionSupported = input.ReadBool(); + break; + } } } #endif @@ -13910,6 +13964,10 @@ public void MergeFrom(pb::CodedInputStream input) { PlayoutDeviceCount = input.ReadInt32(); break; } + case 24: { + DeviceSelectionSupported = input.ReadBool(); + break; + } } } } diff --git a/Samples~/Meet/Assets/Runtime/MeetManager.cs b/Samples~/Meet/Assets/Runtime/MeetManager.cs index b7afbfbf..4a9b173f 100644 --- a/Samples~/Meet/Assets/Runtime/MeetManager.cs +++ b/Samples~/Meet/Assets/Runtime/MeetManager.cs @@ -108,10 +108,19 @@ private void InitializePlatformAudio() foreach (var device in playout) Debug.Log($" [{device.Index}] {device.Name}"); - if (_platformAudio.RecordingDeviceCount > 0) - _platformAudio.SetRecordingDevice(0); - if (_platformAudio.PlayoutDeviceCount > 0) - _platformAudio.SetPlayoutDevice(0); + // Device selection only does something on desktop. On iOS/Android the OS owns + // audio routing, so we skip it there rather than show a picker that has no effect. + if (_platformAudio.IsDeviceSelectionSupported) + { + if (_platformAudio.RecordingDeviceCount > 0) + _platformAudio.SetRecordingDevice(0); + if (_platformAudio.PlayoutDeviceCount > 0) + _platformAudio.SetPlayoutDevice(0); + } + else + { + Debug.Log("PlatformAudio: device selection not supported on this platform; OS controls routing"); + } Debug.Log($"PlatformAudio ready. AEC={echoCancellation}, NS={noiseSuppression}, AGC={autoGainControl}, HW={preferHardwareProcessing}"); } diff --git a/Tests/PlayMode/PlatformAudioTests.cs b/Tests/PlayMode/PlatformAudioTests.cs index 28b0ed7b..6db34f64 100644 --- a/Tests/PlayMode/PlatformAudioTests.cs +++ b/Tests/PlayMode/PlatformAudioTests.cs @@ -33,6 +33,19 @@ public IEnumerator CreateSourceAndTrack_WhenAvailable() yield break; } + [UnityTest] + public IEnumerator IsDeviceSelectionSupported_TrueOnDesktop() + { + using var platformAudio = PlatformAudioTestHelper.TryCreateOrIgnore(); + + // Tests run in the editor, which reports a desktop platform, so per-device + // selection is supported. On iOS/Android this would be false. + Assert.IsTrue(platformAudio.IsDeviceSelectionSupported, + "device selection should be supported on desktop (editor)"); + + yield break; + } + [UnityTest] public IEnumerator CreateSource_WithCustomOptions() { diff --git a/client-sdk-rust~ b/client-sdk-rust~ index a4f41cdc..b1cc2049 160000 --- a/client-sdk-rust~ +++ b/client-sdk-rust~ @@ -1 +1 @@ -Subproject commit a4f41cdcae5214986ab4ac5a8cb8507e5cc7ee6e +Subproject commit b1cc20495a57863ad71dc7c63ad2bd58f27e6860 From 330f25c0e618fb150e8fc88990ffb639b967f922 Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:23:48 +0200 Subject: [PATCH 3/4] Move device print inside if selection supported --- Samples~/Meet/Assets/Runtime/MeetManager.cs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Samples~/Meet/Assets/Runtime/MeetManager.cs b/Samples~/Meet/Assets/Runtime/MeetManager.cs index 4a9b173f..5c83eb5b 100644 --- a/Samples~/Meet/Assets/Runtime/MeetManager.cs +++ b/Samples~/Meet/Assets/Runtime/MeetManager.cs @@ -96,22 +96,21 @@ private void InitializePlatformAudio() try { _platformAudio = new PlatformAudio(); - Debug.Log($"PlatformAudio initialized: {_platformAudio.RecordingDeviceCount} mics, " + - $"{_platformAudio.PlayoutDeviceCount} speakers"); - - var (recording, playout) = _platformAudio.GetDevices(); - Debug.Log("Recording devices:"); - foreach (var device in recording) - Debug.Log($" [{device.Index}] {device.Name}"); - - Debug.Log("Playout devices:"); - foreach (var device in playout) - Debug.Log($" [{device.Index}] {device.Name}"); + Debug.Log($"PlatformAudio initialized"); // Device selection only does something on desktop. On iOS/Android the OS owns // audio routing, so we skip it there rather than show a picker that has no effect. if (_platformAudio.IsDeviceSelectionSupported) { + var (recording, playout) = _platformAudio.GetDevices(); + Debug.Log("Recording devices:"); + foreach (var device in recording) + Debug.Log($" [{device.Index}] {device.Name}"); + + Debug.Log("Playout devices:"); + foreach (var device in playout) + Debug.Log($" [{device.Index}] {device.Name}"); + if (_platformAudio.RecordingDeviceCount > 0) _platformAudio.SetRecordingDevice(0); if (_platformAudio.PlayoutDeviceCount > 0) From 5fa9af73b0d5e38bec2ae376ea2109975cafe7d5 Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:47:03 +0200 Subject: [PATCH 4/4] PlatformAudio: document mobile no-op on GetDevices and index setters Cross-link GetDevices(), the uint-index setters, and the GUID setters to IsDeviceSelectionSupported so the mobile (iOS/Android) no-op behavior is discoverable from every device-related member, not just the string overloads. Doc-comment only. Co-Authored-By: Claude Opus 4.8 (1M context) --- Runtime/Scripts/PlatformAudio.cs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Runtime/Scripts/PlatformAudio.cs b/Runtime/Scripts/PlatformAudio.cs index f9d903ee..cb65281b 100644 --- a/Runtime/Scripts/PlatformAudio.cs +++ b/Runtime/Scripts/PlatformAudio.cs @@ -134,6 +134,11 @@ public PlatformAudio() /// /// Gets the lists of available recording and playout devices. + /// + /// On Android and iOS this typically reports a single "default" device, and + /// selecting a device has no effect (see ): + /// audio routing is governed by the OS. The list is informational there; prefer + /// gating any device-picker UI on . /// /// /// A tuple containing: @@ -183,6 +188,9 @@ public PlatformAudio() /// Convenience wrapper around that looks /// up the GUID from . Prefer the GUID overload for code /// that persists a selection — indices can shift when devices are added/removed. + /// + /// No-op on Android/iOS, same as + /// (see ). /// /// Device index from GetDevices().Recording /// @@ -200,10 +208,10 @@ public void SetRecordingDevice(uint index) /// /// Sets the recording device (microphone) by device ID (GUID). /// - /// On Android and iOS this is a no-op in the native ADM: input routing is - /// governed by the OS (AVAudioSession on iOS, AudioManager on Android) and - /// the call is acknowledged but ignored. The method is still safe to call, - /// and the response carries no error. + /// On Android and iOS this is a no-op (see ): + /// input routing is governed by the OS (AVAudioSession on iOS, AudioManager on + /// Android) and the call is acknowledged but ignored. The method is still safe to + /// call, and the response carries no error. /// /// Device ID/GUID from GetDevices().Recording[i].Guid /// @@ -230,6 +238,9 @@ public void SetRecordingDevice(string deviceId) /// Convenience wrapper around that looks /// up the GUID from . Prefer the GUID overload for code /// that persists a selection — indices can shift when devices are added/removed. + /// + /// No-op on Android/iOS, same as + /// (see ). /// /// Device index from GetDevices().Playout /// @@ -247,10 +258,10 @@ public void SetPlayoutDevice(uint index) /// /// Sets the playout device (speaker/headphones) by device ID (GUID). /// - /// On Android and iOS this is a no-op in the native ADM: output routing is - /// governed by the OS (AVAudioSession on iOS, AudioManager on Android) and - /// the call is acknowledged but ignored. The method is still safe to call, - /// and the response carries no error. + /// On Android and iOS this is a no-op (see ): + /// output routing is governed by the OS (AVAudioSession on iOS, AudioManager on + /// Android) and the call is acknowledged but ignored. The method is still safe to + /// call, and the response carries no error. /// /// Device ID/GUID from GetDevices().Playout[i].Guid ///