diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..2ff91a8a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF/webrtc filter=lfs diff=lfs merge=lfs -text +Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/**/*.yml filter=lfs diff=lfs merge=lfs -text diff --git a/Assets/Samples/Stream Video & Audio Chat SDK/0.9.0/Video & Audio Chat Example Project/Scripts/StreamVideoManager.cs b/Assets/Samples/Stream Video & Audio Chat SDK/0.9.0/Video & Audio Chat Example Project/Scripts/StreamVideoManager.cs index ce016658..6e7021c7 100644 --- a/Assets/Samples/Stream Video & Audio Chat SDK/0.9.0/Video & Audio Chat Example Project/Scripts/StreamVideoManager.cs +++ b/Assets/Samples/Stream Video & Audio Chat SDK/0.9.0/Video & Audio Chat Example Project/Scripts/StreamVideoManager.cs @@ -216,7 +216,7 @@ protected void OnApplicationPause(bool pauseStatus) if (pauseStatus) { // App is going to background - Client.PauseAndroidAudioPlayback(); + Client.PauseMobileAudioPlayback(); _wasAudioPublishEnabledOnPause = Client.AudioDeviceManager.IsEnabled; _wasVideoPublishEnabledOnPause = Client.VideoDeviceManager.IsEnabled; @@ -226,7 +226,7 @@ protected void OnApplicationPause(bool pauseStatus) else { // App is coming to foreground - Client.ResumeAndroidAudioPlayback(); + Client.ResumeMobileAudioPlayback(); if (_wasAudioPublishEnabledOnPause) { diff --git a/Packages/StreamVideo/Runtime/Core/DeviceManagers/StreamAudioDeviceManager.cs b/Packages/StreamVideo/Runtime/Core/DeviceManagers/StreamAudioDeviceManager.cs index ae18889d..10823b09 100644 --- a/Packages/StreamVideo/Runtime/Core/DeviceManagers/StreamAudioDeviceManager.cs +++ b/Packages/StreamVideo/Runtime/Core/DeviceManagers/StreamAudioDeviceManager.cs @@ -31,6 +31,7 @@ public override IEnumerable EnumerateDevices() { } +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR if (RtcSession.UseNativeAudioBindings) { NativeAudioDeviceManager.GetAudioInputDevices(ref _inputDevicesBuffer); @@ -43,13 +44,13 @@ public override IEnumerable EnumerateDevices() yield return new MicrophoneDeviceInfo(device.Id, device.Name); } + yield break; } - else +#endif + + foreach (var deviceName in Microphone.devices) { - foreach (var deviceName in Microphone.devices) - { - yield return new MicrophoneDeviceInfo(deviceName); - } + yield return new MicrophoneDeviceInfo(deviceName); } } @@ -129,10 +130,12 @@ public void SelectDevice(MicrophoneDeviceInfo device, bool enable) IsEnabled = enable; +#if UNITY_ANDROID && !UNITY_EDITOR if (RtcSession.UseNativeAudioBindings) { SetAudioRoutingAsync((NativeAudioDeviceManager.AudioRouting)SelectedDevice.IntId.Value).LogIfFailed(); } +#endif } //StreamTodo: https://docs.unity3d.com/ScriptReference/AudioSource-ignoreListenerPause.html perhaps this should be enabled so that AudioListener doesn't affect recorded audio diff --git a/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs b/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs index 6dfa06a2..c827a8c0 100644 --- a/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs +++ b/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs @@ -31,6 +31,11 @@ public interface IStreamVideoClient : IStreamVideoClientEventsListener, IDisposa /// event CallHandler CallStarted; + /// + /// Event fired when a call is about to be left. You can still access full call data because the leaving process is just starting. + /// + event CallHandler CallLeaving; + /// /// Event fired when a call ended /// @@ -129,16 +134,17 @@ Task GetCallAsync(StreamCallType callType, string callId, /// - /// Temporary method (can be removed in the future) to pause audio playback on Android. - /// This will completely suspend playback of any audio coming from the StreamVideo SDK on the Android platform. + /// Mutes native SDK audio playback (silences remote participants on the device's + /// speakers). Call from app lifecycle events such as OnApplicationPause(true). + /// The native audio device stays open so resume is instant. No-op on platforms + /// that don't use the native audio pipeline (Editor, Standalone). /// - void PauseAndroidAudioPlayback(); + void PauseMobileAudioPlayback(); /// - /// Temporary method (can be removed in the future) to resume audio playback on Android. - /// Call this resume audio playback if it was previously paused using . + /// Resumes playback previously muted via . /// - void ResumeAndroidAudioPlayback(); + void ResumeMobileAudioPlayback(); /// /// Set Android Audio usage mode diff --git a/Packages/StreamVideo/Runtime/Core/LowLevelClient/PeerConnectionBase.cs b/Packages/StreamVideo/Runtime/Core/LowLevelClient/PeerConnectionBase.cs index 50dec3ee..1620de3e 100644 --- a/Packages/StreamVideo/Runtime/Core/LowLevelClient/PeerConnectionBase.cs +++ b/Packages/StreamVideo/Runtime/Core/LowLevelClient/PeerConnectionBase.cs @@ -1,6 +1,6 @@ //StreamTodo: duplicated declaration of STREAM_NATIVE_AUDIO (also in RtcSession.cs) easy to get out of sync. -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR #define STREAM_NATIVE_AUDIO //Defined in multiple files #endif using System; diff --git a/Packages/StreamVideo/Runtime/Core/LowLevelClient/PublisherPeerConnection.cs b/Packages/StreamVideo/Runtime/Core/LowLevelClient/PublisherPeerConnection.cs index 0821f9bc..1df5a791 100644 --- a/Packages/StreamVideo/Runtime/Core/LowLevelClient/PublisherPeerConnection.cs +++ b/Packages/StreamVideo/Runtime/Core/LowLevelClient/PublisherPeerConnection.cs @@ -1,4 +1,4 @@ -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR #define STREAM_NATIVE_AUDIO //Defined in multiple files #endif using System; diff --git a/Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs b/Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs index 3833d043..61f0abf8 100644 --- a/Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs +++ b/Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs @@ -1,4 +1,4 @@ -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR #define STREAM_NATIVE_AUDIO //Defined in multiple files #endif using System; @@ -67,7 +67,6 @@ internal class RtcSession : IMediaInputProvider, ISfuClient, IDisposable // Some sources claim the 48kHz is the most optimal sample rate for WebRTC, other cause internal resampling public const int AudioOutputSampleRate = 48_000; - public const int AudioOutputChannels = 2; #if STREAM_NATIVE_AUDIO public const bool UseNativeAudioBindings = true; @@ -588,7 +587,16 @@ public async Task DoJoin(JoinCallData joinCallData, CancellationToken cancellati //StreamTODO: if we try to rejoin a call with no other participants we'll get error from SFU not call FOUND // What should we do then? - + + if (ActiveCall == null) + { + throw new Exception("ActiveCall should never be null here."); + } + + if (joinResponse == null) + { + throw new Exception("joinResponse was null"); + } ActiveCall.UpdateFromSfu(joinResponse); _logs.WarningIfDebug($"{nameof(DoJoin)} - SFU Sending join response received. startNewPeerConnections: {startNewPeerConnections}"); @@ -660,7 +668,10 @@ public async Task DoJoin(JoinCallData joinCallData, CancellationToken cancellati { //StreamTODO: Either use UseNativeAudioBindings const or STREAM_NATIVE_AUDIO flag but not both. Once we replace the webRTC package we could remove STREAM_NATIVE_AUDIO #if STREAM_NATIVE_AUDIO - WebRTC.StartAudioPlayback(AudioOutputSampleRate, AudioOutputChannels); + // iOS VPIO requires PlayAndRecord before the audio unit opens (no automatic retry on '!rec'). + EnsureIOSAudioSessionReadyForVPIO($"{nameof(DoJoin)} StartAudioPlayback"); + + WebRTC.StartAudioPlayback(AudioOutputSampleRate); #endif } @@ -675,6 +686,15 @@ public async Task DoJoin(JoinCallData joinCallData, CancellationToken cancellati { CallState = prevCallState; } + + try + { + await ClearSessionAsync(); + } + catch (Exception cleanupEx) + { + _logs.Warning($"{nameof(DoJoin)} session cleanup after failure encountered an error: {cleanupEx.Message}"); + } throw; } @@ -731,7 +751,18 @@ public Task StopAsync(string reason = "") if (UseNativeAudioBindings) { #if STREAM_NATIVE_AUDIO + // iOS order: stop playback + capture (closes the duplex VPIO unit), + // THEN deactivate AVAudioSession. Reverse order returns IsBusy. WebRTC.StopAudioPlayback(); + +#if UNITY_IOS && !UNITY_EDITOR + if (Publisher?.PublisherAudioTrack != null) + { + Publisher.PublisherAudioTrack.StopLocalAudioCapture(); + } + + Libs.iOSAudioManagers.IOSAudioManager.DeconfigureAudioSession(); +#endif #endif } @@ -882,37 +913,30 @@ public void SetAudioRecordingDevice(MicrophoneDeviceInfo device) public void TryRestartAudioPlayback() { - if (!UseNativeAudioBindings) + if (UseNativeAudioBindings) { - return; - } #if STREAM_NATIVE_AUDIO - WebRTC.StopAudioPlayback(); - WebRTC.StartAudioPlayback(AudioOutputSampleRate, AudioOutputChannels); + WebRTC.StopAudioPlayback(); + EnsureIOSAudioSessionReadyForVPIO(nameof(TryRestartAudioPlayback)); + WebRTC.StartAudioPlayback(AudioOutputSampleRate); #endif + } } - //StreamTODO: temp solution to allow stopping the audio when app is minimized. User tried disabling the AudioSource but the audio is handled natively so it has no effect - public void PauseAndroidAudioPlayback() + // Mutes the AudioMixer so the playback callback writes silence to the speakers. + // The native audio device keeps running; on iOS this also keeps the VPIO audio + // unit alive so unmuting is instant and does not need a session reconfigure. + public void PauseMobileAudioPlayback() { #if STREAM_NATIVE_AUDIO - WebRTC.MuteAndroidAudioPlayback(); - _logs.Warning("Audio Playback is paused. This stops all audio coming from StreamVideo SDK on Android platform."); -#else - throw new NotSupportedException( - $"{nameof(PauseAndroidAudioPlayback)} is only supported on Android platform."); + WebRTC.MuteAudioPlayback(); #endif } - //StreamTODO: temp solution to allow stopping the audio when app is minimized. User tried disabling the AudioSource but the audio is handled natively so it has no effect - public void ResumeAndroidAudioPlayback() + public void ResumeMobileAudioPlayback() { #if STREAM_NATIVE_AUDIO - WebRTC.UnmuteAndroidAudioPlayback(); - _logs.Warning("Audio Playback is resumed. This resumes audio coming from StreamVideo SDK on Android platform."); -#else - throw new NotSupportedException( - $"{nameof(ResumeAndroidAudioPlayback)} is only supported on Android platform."); + WebRTC.UnmuteAudioPlayback(); #endif } @@ -1416,13 +1440,53 @@ private void UpdateAudioRecording() //StreamTODO: implement proper passing deviceID -> for Android and IOS we're skipping the deviceID //because they operate on audio routing instead of actual devices. The underlying native implementation for Android let's OS pick the preferred device + // Configure session BEFORE capture so VPIO opens with HW AEC/NS/AGC from sample 0. + EnsureIOSAudioSessionReadyForVPIO($"{nameof(UpdateAudioRecording)} StartLocalAudioCapture"); + _logs.WarningIfDebug("RtcSession.UpdateAudioRecording -> START local audio capture"); Publisher.PublisherAudioTrack.StartLocalAudioCapture(-1, AudioInputSampleRate); + +#if UNITY_IOS && !UNITY_EDITOR + if (!Libs.iOSAudioManagers.IOSAudioManager.IsHardwareNoiseCancellationActive) + { + _logs.Warning( + "RtcSession.UpdateAudioRecording -> iOS hardware noise cancellation (VoiceProcessingIO) NOT active. " + + "AEC/NS/AGC will not be applied. Check that no other plugin overrode AVAudioSession mode."); + } +#endif } else { _logs.WarningIfDebug("RtcSession.UpdateAudioRecording -> STOP local audio capture"); Publisher.PublisherAudioTrack.StopLocalAudioCapture(); + + // Do NOT deactivate the AVAudioSession on mute: the duplex VPIO unit is + // shared with playback and would also stop, killing remote audio. The + // session is deactivated in StopAsync after both directions are torn down. + } +#endif + } + + /// + /// Configure the iOS AVAudioSession for VPIO (PlayAndRecord + VideoChat) before + /// opening the duplex audio unit. Verifies the result and retries once to catch + /// races with other components reasserting the session. No-op on non-iOS targets. + /// + private void EnsureIOSAudioSessionReadyForVPIO(string callerContext) + { +#if UNITY_IOS && !UNITY_EDITOR + var configureOk = Libs.iOSAudioManagers.IOSAudioManager.ConfigureForWebRTC(); + if (configureOk && Libs.iOSAudioManagers.IOSAudioManager.IsHardwareNoiseCancellationActive) + { + return; + } + + configureOk = Libs.iOSAudioManagers.IOSAudioManager.ConfigureForWebRTC(); + if (!configureOk || !Libs.iOSAudioManagers.IOSAudioManager.IsHardwareNoiseCancellationActive) + { + _logs.Error( + $"RtcSession.{nameof(EnsureIOSAudioSessionReadyForVPIO)} ({callerContext}): session not VPIO-ready after retry. " + + "Next StartAudio call may fail with '!rec'. See [StreamVideo iOS Audio] device log for NSError details."); } #endif } diff --git a/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs b/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs index 6696a11c..f2df9e01 100644 --- a/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs +++ b/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs @@ -62,13 +62,13 @@ void IStateLoadableFrom.LoadFromDto _acceptedBy.TryReplaceValuesFromDto(dto.AcceptedBy); EndedAt = dto.EndedAt; Id = dto.Id; - + // CallSessionResponseInternalDTO usually (or always?) contains no participants. Participants are updated from the SFU join response // But SFU response can arrive before API response, so we can't override participants here because this clears the list - + //StreamTODO: temp remove this. This seems to be only messing up the participants list. We're testing updating the participants only based on SFU data. // But we need to check how this will work with GetCall where there's not SFU connection - + // foreach (var dtoParticipant in dto.Participants) // { // var participant = cache.TryCreateOrUpdate(dtoParticipant); @@ -77,19 +77,34 @@ void IStateLoadableFrom.LoadFromDto // _participants.Add(participant); // } // } - + // StreamTODO: figure out how to best handle this. Should we update it from coordinator or only the SFU //_participantsCountByRole.TryReplaceValuesFromDto(dto.ParticipantsCountByRole); _rejectedBy.TryReplaceValuesFromDto(dto.RejectedBy); StartedAt = dto.StartedAt; LiveEndedAt = dto.LiveEndedAt; LiveStartedAt = dto.LiveStartedAt; - + UpdateParticipantCountFromSessionInternal(dto.AnonymousParticipantCount, dto.ParticipantsCountByRole); } void IStateLoadableFrom.LoadFromDto(SfuCallState dto, ICache cache) { +#if STREAM_DEBUG_ENABLED + if (dto == null || cache == null || dto.Participants == null || dto.ParticipantCount == null) + { + throw new ArgumentNullException( + nameof(dto), + $"{nameof(CallSession)}.LoadFromDto(SfuCallState) precondition failed. " + + $"dto: {dto != null}, " + + $"cache: {cache != null}, " + + $"dto.Participants: {dto?.Participants != null} (count: {dto?.Participants?.Count ?? 0}), " + + $"dto.ParticipantCount: {dto?.ParticipantCount != null}, " + + $"dto.StartedAt: {dto?.StartedAt != null}, " + + $"dto.Pins: {dto?.Pins != null}"); + } +#endif + if (dto.StartedAt != null) { StartedAt = dto.StartedAt.ToDateTimeOffset(); @@ -103,7 +118,7 @@ void IStateLoadableFrom.LoadFromDto(SfuCallState dto, { tempPrevSessionIds.Add(p.SessionId); } - + // Treat SFU as the most updated source of truth for participants _participants.Clear(); @@ -164,7 +179,7 @@ internal void UpdateFromSfu(HealthCheckResponse healthCheckResponse, ICache cach ((IStateLoadableFrom)ParticipantCount).LoadFromDto( healthCheckResponse.ParticipantCount, cache); } - + internal void UpdateFromSfu(ParticipantLeft participantLeft, ICache cache) { var participant = cache.TryCreateOrUpdate(participantLeft.Participant); @@ -176,16 +191,16 @@ internal void UpdateFromSfu(ParticipantLeft participantLeft, ICache cache) cache.CallParticipants.TryRemove(participantLeft.Participant.SessionId); } } - + internal void UpdateFromCoordinator( InternalDTO.Events.CallSessionParticipantCountsUpdatedEventInternalDTO participantCountsUpdated, LowLevelClient.CallingState callingState) { _participantsCountByRole.TryReplaceValuesFromDto(participantCountsUpdated.ParticipantsCountByRole); - UpdateParticipantCountFromCoordinator(participantCountsUpdated.AnonymousParticipantCount, + UpdateParticipantCountFromCoordinator(participantCountsUpdated.AnonymousParticipantCount, participantCountsUpdated.ParticipantsCountByRole, callingState); } - + internal void UpdateFromCoordinator( InternalDTO.Events.CallSessionParticipantJoinedEventInternalDTO participantJoined, ICache cache, LowLevelClient.CallingState callingState) @@ -197,7 +212,7 @@ internal void UpdateFromCoordinator( { _participants.Add(participant); } - + var role = participantJoined.Participant.Role; if (_participantsCountByRole.ContainsKey(role)) { @@ -207,7 +222,7 @@ internal void UpdateFromCoordinator( { _participantsCountByRole[role] = 1; } - + var anonymousCount = ParticipantCount != null ? (int)ParticipantCount.Anonymous : 0; UpdateParticipantCountFromCoordinator(anonymousCount, _participantsCountByRole, callingState); @@ -216,7 +231,7 @@ internal void UpdateFromCoordinator( ParticipantAdded?.Invoke(participant); } } - + //StreamTODO: double-check this logic internal void UpdateFromCoordinator( InternalDTO.Events.CallSessionParticipantLeftEventInternalDTO participantLeft, ICache cache, @@ -224,18 +239,18 @@ internal void UpdateFromCoordinator( { var participant = cache.TryCreateOrUpdate(participantLeft.Participant); var wasRemoved = _participants.Remove(participant); - + var role = participantLeft.Participant.Role; if (_participantsCountByRole.ContainsKey(role)) { _participantsCountByRole[role] = Math.Max(0, _participantsCountByRole[role] - 1); - + if (_participantsCountByRole[role] == 0) { _participantsCountByRole.Remove(role); } } - + var anonymousCount = ParticipantCount != null ? (int)ParticipantCount.Anonymous : 0; UpdateParticipantCountFromCoordinator(anonymousCount, _participantsCountByRole, callingState); @@ -244,16 +259,16 @@ internal void UpdateFromCoordinator( ParticipantRemoved?.Invoke(participant.SessionId, participant.UserId); } } - + private readonly Dictionary _acceptedBy = new Dictionary(); private readonly List _participants = new List(); private readonly Dictionary _participantsCountByRole = new Dictionary(); private readonly Dictionary _rejectedBy = new Dictionary(); - + /// /// Updates the ParticipantCount based on session data (used when NOT connected to SFU) /// - private void UpdateParticipantCountFromCoordinator(int anonymousParticipantCount, + private void UpdateParticipantCountFromCoordinator(int anonymousParticipantCount, IReadOnlyDictionary participantsCountByRole, LowLevelClient.CallingState callingState) { // When in JOINED state, we should use the participant count coming through @@ -262,14 +277,14 @@ private void UpdateParticipantCountFromCoordinator(int anonymousParticipantCount { return; } - + UpdateParticipantCountFromSessionInternal(anonymousParticipantCount, participantsCountByRole); } - + /// /// Updates the ParticipantCount based on session data /// - private void UpdateParticipantCountFromSessionInternal(int anonymousParticipantCount, + private void UpdateParticipantCountFromSessionInternal(int anonymousParticipantCount, IReadOnlyDictionary participantsCountByRole) { var byRoleCount = 0; @@ -277,15 +292,15 @@ private void UpdateParticipantCountFromSessionInternal(int anonymousParticipantC { byRoleCount += count; } - + var total = Math.Max(byRoleCount, _participants.Count); - + var dto = new SfuParticipantCount { Total = (uint)total, Anonymous = (uint)anonymousParticipantCount }; - + ((IStateLoadableFrom)ParticipantCount) .LoadFromDto(dto, null); } diff --git a/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs b/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs index 02732d0a..5ec24e0e 100644 --- a/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs +++ b/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs @@ -637,11 +637,50 @@ internal StreamCall(string uniqueId, ICacheRepository repository, //StreamTodo: solve with a generic interface and best to be handled by cache layer internal void UpdateFromSfu(JoinResponse joinResponse) { +#if STREAM_DEBUG_ENABLED var wasBefore = IsLocalParticipantIncluded(); + + var callState = joinResponse?.CallState; + var preconditionFailed = joinResponse == null + || callState == null + || Cache == null + || Session == null + || callState.Pins == null + || callState.Participants == null + || callState.ParticipantCount == null; + + if (preconditionFailed) + { + using (new StringBuilderPoolScope(out var tempSb)) + { + tempSb.Append($"{nameof(StreamCall)}.{nameof(UpdateFromSfu)}(JoinResponse) precondition failed. "); + tempSb.Append($"joinResponse: {joinResponse != null}, "); + tempSb.Append($"Session: {Session != null}, "); + tempSb.Append($"Cache: {Cache != null}"); + + if (joinResponse != null) + { + tempSb.Append($", joinResponse.CallState: {callState != null}"); + + if (callState != null) + { + tempSb.Append($", CallState.Pins: {callState.Pins != null}"); + tempSb.Append( + $", CallState.Participants: {callState.Participants != null} (count: {callState.Participants?.Count ?? 0})"); + tempSb.Append($", CallState.ParticipantCount: {callState.ParticipantCount != null}"); + tempSb.Append($", CallState.StartedAt: {callState.StartedAt != null}"); + } + } + + Logs.Error(tempSb.ToString()); + } + } +#endif ((IStateLoadableFrom)Session).LoadFromDto(joinResponse.CallState, Cache); - UpdateServerPins(joinResponse.CallState.Pins); + UpdateServerPins(joinResponse.CallState?.Pins); +#if STREAM_DEBUG_ENABLED var isAfter = IsLocalParticipantIncluded(); try @@ -677,6 +716,7 @@ internal void UpdateFromSfu(JoinResponse joinResponse) { Logs.Exception(e); } +#endif } internal void UpdateFromSfu(ParticipantJoined participantJoined, ICache cache) @@ -724,7 +764,7 @@ internal void UpdateFromSfu(HealthCheckResponse healthCheckResponse, ICache cach { Session?.UpdateFromSfu(healthCheckResponse, cache); } - + internal void UpdateFromSfu(AudioLevelChanged audioLevelChanged) { Session?.UpdateFromSfu(audioLevelChanged); @@ -972,6 +1012,11 @@ private void UpdateServerPins(IEnumerable pins) { _serverPinsSessionIds.Clear(); + if (pins == null) + { + return; + } + foreach (var pin in pins) { _serverPinsSessionIds.Add(pin.SessionId); diff --git a/Packages/StreamVideo/Runtime/Core/StreamVideoClient.cs b/Packages/StreamVideo/Runtime/Core/StreamVideoClient.cs index b0c014e0..8ba5060e 100644 --- a/Packages/StreamVideo/Runtime/Core/StreamVideoClient.cs +++ b/Packages/StreamVideo/Runtime/Core/StreamVideoClient.cs @@ -1,4 +1,4 @@ -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR #define STREAM_NATIVE_AUDIO //Defined in multiple files #endif using System; @@ -82,6 +82,7 @@ public class StreamVideoClient : IStreamVideoClient, IInternalStreamVideoClient public event DisconnectedHandler Disconnected; public event CallHandler CallStarted; + public event CallHandler CallLeaving; //StreamTodo: not sure if this should pass instance because we want to destroy call instance when the call is over?? public event CallHandler CallEnded; @@ -301,13 +302,13 @@ public void SetAudioProcessingModule(bool enabled, bool echoCancellationEnabled, #endif } - public void PauseAndroidAudioPlayback() => InternalLowLevelClient.RtcSession.PauseAndroidAudioPlayback(); + public void PauseMobileAudioPlayback() => InternalLowLevelClient.RtcSession.PauseMobileAudioPlayback(); - public void ResumeAndroidAudioPlayback() => InternalLowLevelClient.RtcSession.ResumeAndroidAudioPlayback(); + public void ResumeMobileAudioPlayback() => InternalLowLevelClient.RtcSession.ResumeMobileAudioPlayback(); public void SetAndroidAudioUsageMode(AndroidAudioUsageMode usageMode) { -#if STREAM_NATIVE_AUDIO +#if UNITY_ANDROID && !UNITY_EDITOR WebRTC.SetAndroidAudioUsageMode((Unity.WebRTC.AndroidAudioUsageMode)usageMode); _logs.Warning("Set audio usage mode to " + Enum.GetName(typeof(AndroidAudioUsageMode), usageMode)); #else @@ -837,6 +838,10 @@ private void OnRtcPeerConnectionDisconnectedDuringSession() private void OnRtcCallStateChanged(CallingState previousState, CallingState newState) { + if (newState == CallingState.Leaving) + { + CallLeaving?.Invoke(ActiveCall); + } if (newState == CallingState.Left) { CallEnded?.Invoke(ActiveCall); diff --git a/Packages/StreamVideo/Runtime/Libs/DeviceManagers/IOSAudioDeviceManager.cs b/Packages/StreamVideo/Runtime/Libs/DeviceManagers/IOSAudioDeviceManager.cs new file mode 100644 index 00000000..4e370183 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/DeviceManagers/IOSAudioDeviceManager.cs @@ -0,0 +1,29 @@ +using Libs.DeviceManagers; + +namespace StreamVideo.Libs.DeviceManagers +{ + /// + /// iOS audio input device manager. Exposes a single synthetic "Default Microphone" + /// entry; the actual mic is picked by the system based on the AVAudioSession route. + /// The synthetic id is not consumed by the native plugin (miniaudio uses the OS-default device). + /// StreamTODO: expose real routing options (Built-in / Bluetooth / Wired / AirPods) + /// by querying AVAudioSession.availableInputs and applying via setPreferredInput:. + /// + internal static class IOSAudioDeviceManager + { + private const int DefaultMicrophoneId = 0; + private const string DefaultMicrophoneName = "Default Microphone"; + + public static void GetAudioInputDevices(ref NativeAudioDeviceManager.AudioDeviceInfo[] result) + { + AudioDeviceManagerHelper.ClearBuffer(ref result); + + if (result == null || result.Length < 1) + { + result = new NativeAudioDeviceManager.AudioDeviceInfo[1]; + } + + result[0] = new NativeAudioDeviceManager.AudioDeviceInfo(DefaultMicrophoneId, DefaultMicrophoneName); + } + } +} diff --git a/Packages/StreamVideo/Runtime/Libs/DeviceManagers/IOSAudioDeviceManager.cs.meta b/Packages/StreamVideo/Runtime/Libs/DeviceManagers/IOSAudioDeviceManager.cs.meta new file mode 100644 index 00000000..94b6fe11 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/DeviceManagers/IOSAudioDeviceManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d43302dee6cb4d00a8b3a36f8a7bfb0a +timeCreated: 1745432400 diff --git a/Packages/StreamVideo/Runtime/Libs/DeviceManagers/NativeAudioDeviceManager.cs b/Packages/StreamVideo/Runtime/Libs/DeviceManagers/NativeAudioDeviceManager.cs index 04ddddd6..6ae8444b 100644 --- a/Packages/StreamVideo/Runtime/Libs/DeviceManagers/NativeAudioDeviceManager.cs +++ b/Packages/StreamVideo/Runtime/Libs/DeviceManagers/NativeAudioDeviceManager.cs @@ -65,8 +65,10 @@ public static void SetPreferredAudioRoute(AudioRouting audioRoute) { #if UNITY_ANDROID AndroidAudioDeviceManager.SetPreferredAudioRoute(audioRoute); +#elif UNITY_IOS + // StreamTODO: route via AVAudioSession (setPreferredInput / overrideOutputAudioPort). #else - UnityEngine.Debug.LogWarning($"{nameof(GetAudioInputDevices)} is not supported on this platform: " + UnityEngine.Application.platform); + UnityEngine.Debug.LogWarning($"{nameof(SetPreferredAudioRoute)} is not supported on this platform: " + UnityEngine.Application.platform); #endif } @@ -74,6 +76,8 @@ public static void GetAudioInputDevices(ref AudioDeviceInfo[] result) { #if UNITY_ANDROID AndroidAudioDeviceManager.GetAudioInputDevices(ref result); +#elif UNITY_IOS + IOSAudioDeviceManager.GetAudioInputDevices(ref result); #else UnityEngine.Debug.LogWarning($"{nameof(GetAudioInputDevices)} is not supported on this platform: " + UnityEngine.Application.platform); #endif @@ -86,6 +90,8 @@ public static void GetAudioRoute() { #if UNITY_ANDROID AndroidAudioDeviceManager.GetAudioRoute(); +#elif UNITY_IOS + // StreamTODO: query AVAudioSession.currentRoute. #else UnityEngine.Debug.LogWarning($"{nameof(GetAudioRoute)} is not supported on this platform: " + UnityEngine.Application.platform); #endif diff --git a/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/IOSAudioManager.cs b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/IOSAudioManager.cs new file mode 100644 index 00000000..e83b71e2 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/IOSAudioManager.cs @@ -0,0 +1,204 @@ +using System; +using System.Runtime.InteropServices; + +namespace StreamVideo.Libs.iOSAudioManagers +{ + /// + /// Manages iOS AVAudioSession configuration for WebRTC calls. + /// Sets category = PlayAndRecord and mode = VideoChat with options + /// DefaultToSpeaker | AllowBluetooth | AllowBluetoothA2DP | AllowAirPlay, + /// which engages the VoiceProcessingIO audio unit (hardware AEC, NS, AGC) + /// and the same media-volume / loudspeaker-default routing used by Zoom, Meet + /// and Teams. + /// + /// + /// All members except are iOS-only and throw + /// on every other build target. + /// Use to gate calls without #if directives. + /// + /// + public static class IOSAudioManager + { +#if UNITY_IOS && !UNITY_EDITOR + [DllImport("__Internal")] + private static extern void _StreamForceOutputToSpeaker(); + + [DllImport("__Internal")] + private static extern void _StreamClearOutputOverride(); + + [DllImport("__Internal")] + private static extern void _StreamMaximizeInputGain(); + + [DllImport("__Internal")] + private static extern int _StreamConfigureAudioSessionForWebRTC(); + + [DllImport("__Internal")] + private static extern void _StreamDeconfigureAudioSession(); + + [DllImport("__Internal")] + private static extern int _StreamIsHardwareNoiseCancellationActive(); + + [DllImport("__Internal")] + private static extern IntPtr _StreamGetAudioSessionInfo(); +#endif + + /// + /// true on iOS device builds (the underlying native plugin is reachable); + /// false elsewhere. When false, every other member throws. + /// + public static bool IsSupported + { + get + { +#if UNITY_IOS && !UNITY_EDITOR + return true; +#else + return false; +#endif + } + } + + /// + /// Configure AVAudioSession for a WebRTC call (PlayAndRecord + VideoChat, + /// with interruption / media-services-reset observers). Idempotent. Must be + /// called before the native audio engine opens the VoiceProcessingIO audio + /// unit; the SDK handles this automatically at the relevant call sites. + /// + /// + /// true when the session ended up VPIO-compatible and was activated; + /// false otherwise (details under [StreamVideo iOS Audio] in the + /// device log). + /// + /// Thrown on non-iOS targets. + public static bool ConfigureForWebRTC() + { +#if UNITY_IOS && !UNITY_EDITOR + return _StreamConfigureAudioSessionForWebRTC() == 1; +#else + throw NotSupported(nameof(ConfigureForWebRTC)); +#endif + } + + /// + /// Deactivate the AVAudioSession set up by and + /// notify other apps so background music / navigation can resume. + /// + /// Thrown on non-iOS targets. + public static void DeconfigureAudioSession() + { +#if UNITY_IOS && !UNITY_EDITOR + _StreamDeconfigureAudioSession(); +#else + throw NotSupported(nameof(DeconfigureAudioSession)); +#endif + } + + /// + /// Hard-override the output to the built-in loudspeaker even if a wired or + /// Bluetooth headset is connected. Use sparingly: this overrides the user's + /// headphone choice. already defaults to the + /// loudspeaker while still letting headphones take over automatically; only + /// call this for an explicit speakerphone toggle. + /// Use to undo. + /// + /// Thrown on non-iOS targets. + public static void ForceLoudspeaker() + { +#if UNITY_IOS && !UNITY_EDITOR + _StreamForceOutputToSpeaker(); +#else + throw NotSupported(nameof(ForceLoudspeaker)); +#endif + } + + /// + /// Removes a previous override so iOS picks the + /// route normally (headphones if connected, otherwise the built-in loudspeaker). + /// + /// Thrown on non-iOS targets. + public static void ClearOutputOverride() + { +#if UNITY_IOS && !UNITY_EDITOR + _StreamClearOutputOverride(); +#else + throw NotSupported(nameof(ClearOutputOverride)); +#endif + } + + /// + /// Set microphone input gain to 1.0. iOS rarely allows this for the built-in + /// mic; the call is best-effort and silently ignored when the system disallows + /// it. already invokes this. + /// + /// Thrown on non-iOS targets. + public static void MaximizeInputGain() + { +#if UNITY_IOS && !UNITY_EDITOR + _StreamMaximizeInputGain(); +#else + throw NotSupported(nameof(MaximizeInputGain)); +#endif + } + + /// + /// true when the AVAudioSession is currently configured for the + /// VoiceProcessingIO audio unit (i.e. category == PlayAndRecord and + /// mode == VoiceChat or VideoChat), which gives us hardware + /// AEC, noise suppression and automatic gain control. + /// + /// + /// This checks the session configuration only; it does NOT verify that the + /// IO buffer duration is small enough for VPIO AEC to actually converge. See + /// the [StreamVideo iOS Audio] Configured: ... IOBuffer=... device log + /// line for the authoritative status. + /// + /// Thrown on non-iOS targets. + public static bool IsHardwareNoiseCancellationActive + { + get + { +#if UNITY_IOS && !UNITY_EDITOR + return _StreamIsHardwareNoiseCancellationActive() == 1; +#else + throw NotSupported(nameof(IsHardwareNoiseCancellationActive)); +#endif + } + } + + /// + /// Returns a human-readable dump of the current iOS audio session state + /// (category, mode, options, route, sample rate, IO buffer, latencies). + /// Useful for diagnostics. + /// + /// Thrown on non-iOS targets. + public static string GetCurrentSettings() + { +#if UNITY_IOS && !UNITY_EDITOR + try + { + IntPtr ptr = _StreamGetAudioSessionInfo(); + if (ptr == IntPtr.Zero) + return "Failed to get audio session info"; + + string info = Marshal.PtrToStringAnsi(ptr); + Marshal.FreeHGlobal(ptr); + return info; + } + catch (Exception e) + { + return $"Error getting audio info: {e.Message}"; + } +#else + throw NotSupported(nameof(GetCurrentSettings)); +#endif + } + +#if !(UNITY_IOS && !UNITY_EDITOR) + private static PlatformNotSupportedException NotSupported(string member) + => new PlatformNotSupportedException( + $"{nameof(IOSAudioManager)}.{member} is only available on iOS device builds. " + + $"Gate the call with `if ({nameof(IOSAudioManager)}.{nameof(IsSupported)})` " + + "or `#if UNITY_IOS && !UNITY_EDITOR`."); +#endif + } +} diff --git a/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/IOSAudioManager.cs.meta b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/IOSAudioManager.cs.meta new file mode 100644 index 00000000..b42764e2 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/IOSAudioManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS.meta b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS.meta new file mode 100644 index 00000000..a37b6626 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a3b9c7d1e2f4a5b6c7d8e9f0a1b2c3d4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS/iosAudioConfig.mm b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS/iosAudioConfig.mm new file mode 100644 index 00000000..b2c37541 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS/iosAudioConfig.mm @@ -0,0 +1,269 @@ +#import + +// Single source of truth for AVAudioSession. The native WebRTC plugin (miniaudio) +// is told not to touch the session. Configures PlayAndRecord + VideoChat (-> VPIO +// HW AEC/NS/AGC, loudspeaker default) on call start; deactivates on call end. +// Also installs (once) observers for interruption-ended and media-services-reset +// to reapply the session while a call is still in progress. + +static BOOL s_streamSessionConfigured = NO; + +extern "C" { + +// Hard-override the output to the built-in loudspeaker even if a wired/Bluetooth +// headset is connected. Not called from Configure; invoke explicitly for an +// explicit speakerphone toggle. +void _StreamForceOutputToSpeaker() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + BOOL success = [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error]; + if (!success || error) { + NSLog(@"[StreamVideo iOS Audio] Error forcing speaker: success=%d, error=%@", success, error); + } +} + +void _StreamClearOutputOverride() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + BOOL success = [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error]; + if (!success || error) { + NSLog(@"[StreamVideo iOS Audio] Failed to clear speaker override: success=%d, error=%@", success, error); + } +} + +void _StreamMaximizeInputGain() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + if (audioSession.isInputGainSettable) { + [audioSession setInputGain:1.0 error:&error]; + if (error) { + NSLog(@"[StreamVideo iOS Audio] Could not set input gain: %@", error); + } + } +} + +static BOOL StreamApplyVideoChatCategory() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + + AVAudioSessionCategoryOptions options = + AVAudioSessionCategoryOptionDefaultToSpeaker | + AVAudioSessionCategoryOptionAllowBluetooth | + AVAudioSessionCategoryOptionAllowBluetoothA2DP | + AVAudioSessionCategoryOptionAllowAirPlay; + + BOOL ok = [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord + mode:AVAudioSessionModeVideoChat + options:options + error:&error]; + if (!ok || error) { + NSLog(@"[StreamVideo iOS Audio] Failed to set PlayAndRecord/VideoChat: %@", error); + return NO; + } + return YES; +} + +static BOOL StreamActivateAudioSession() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + BOOL ok = [audioSession setActive:YES error:&error]; + if (!ok) { + NSLog(@"[StreamVideo iOS Audio] Failed to activate audio session: %@", error); + } + return ok; +} + +// VPIO HW AEC needs ~5-10 ms IO buffers to converge. iOS may snap to a larger +// value if another component locked the session first (see Configure log line). +static const double kStreamPreferredIOBufferDuration = 0.010; +static const double kStreamPreferredSampleRate = 48000.0; + +static void StreamApplyPreferredIOParams() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + + if (![audioSession setPreferredSampleRate:kStreamPreferredSampleRate error:&error]) { + NSLog(@"[StreamVideo iOS Audio] setPreferredSampleRate(%.0f) failed: %@", + kStreamPreferredSampleRate, error); + } + + error = nil; + if (![audioSession setPreferredIOBufferDuration:kStreamPreferredIOBufferDuration error:&error]) { + NSLog(@"[StreamVideo iOS Audio] setPreferredIOBufferDuration(%.3f) failed: %@", + kStreamPreferredIOBufferDuration, error); + } +} + +// Returns YES iff the session ended up VPIO-compatible (PlayAndRecord + VideoChat/VoiceChat). +static BOOL StreamReapplySession() { + BOOL categoryOk = StreamApplyVideoChatCategory(); + BOOL activateOk = StreamActivateAudioSession(); + // Apply preferred I/O params AFTER activation; otherwise iOS evaluates them + // against the wrong hardware route and silently drops them. + StreamApplyPreferredIOParams(); + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + BOOL playAndRecord = [audioSession.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]; + BOOL voiceProcessing = [audioSession.mode isEqualToString:AVAudioSessionModeVideoChat] || + [audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]; + return categoryOk && activateOk && playAndRecord && voiceProcessing; +} + +// Reapply the session after system events (interruption-ended, media-services-reset). +// Note: media-services-reset also tears down miniaudio's ma_device; recovering audio +// fully would require RtcSession.TryRestartAudioPlayback / TryRestartAudioRecording +// from C#. Today this only reapplies the session. +static void StreamEnsureNotificationObserversInstalled() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + + [center addObserverForName:AVAudioSessionInterruptionNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * _Nonnull note) { + NSNumber *typeValue = note.userInfo[AVAudioSessionInterruptionTypeKey]; + if (typeValue == nil) return; + AVAudioSessionInterruptionType type = (AVAudioSessionInterruptionType)typeValue.unsignedIntegerValue; + + if (type == AVAudioSessionInterruptionTypeEnded && s_streamSessionConfigured) { + BOOL ok = StreamReapplySession(); + if (!ok) { + NSLog(@"[StreamVideo iOS Audio] Failed to reapply session after interruption"); + } + } + }]; + + [center addObserverForName:AVAudioSessionMediaServicesWereResetNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * _Nonnull note) { + NSLog(@"[StreamVideo iOS Audio] mediaServicesWereReset (sessionConfigured=%@)", + s_streamSessionConfigured ? @"YES" : @"NO"); + if (s_streamSessionConfigured) { + BOOL ok = StreamReapplySession(); + if (!ok) { + NSLog(@"[StreamVideo iOS Audio] Failed to reapply session after media services reset"); + } + } + }]; + }); +} + +// Returns 1 on success (PlayAndRecord + VideoChat/VoiceChat, active), 0 on failure. +int _StreamConfigureAudioSessionForWebRTC() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + + StreamEnsureNotificationObserversInstalled(); + + // Mark intent BEFORE applying so observers triggered by a concurrent system + // event reapply rather than skip. + s_streamSessionConfigured = YES; + + BOOL ok = StreamReapplySession(); + + BOOL voiceProcessing = [audioSession.mode isEqualToString:AVAudioSessionModeVideoChat] || + [audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]; + + double ioBufferMs = audioSession.IOBufferDuration * 1000.0; + double preferredIoBufferMs = kStreamPreferredIOBufferDuration * 1000.0; + BOOL ioBufferOk = ioBufferMs <= preferredIoBufferMs * 2.0; + + NSLog(@"[StreamVideo iOS Audio] Configured: category=%@ mode=%@ sampleRate=%.0f IOBuffer=%.1fms (preferred %.1fms) %@", + audioSession.category, audioSession.mode, + audioSession.sampleRate, + ioBufferMs, preferredIoBufferMs, + ioBufferOk ? @"OK" : @"TOO LARGE - VPIO AEC will be ineffective"); + + if (!voiceProcessing) { + NSLog(@"[StreamVideo iOS Audio] WARNING: hardware noise cancellation is NOT active. " + "AVAudioSession.mode must be VideoChat or VoiceChat. Current mode=%@", audioSession.mode); + } + + if (!ioBufferOk) { + // Most common cause on Unity: the engine locked the session with a 2048-frame + // buffer before the call started; iOS won't shrink it for our secondary client. + NSLog(@"[StreamVideo iOS Audio] WARNING: IOBufferDuration is %.1f ms, exceeds the %.1f ms " + "preferred for VPIO AEC. Hardware echo cancellation will likely be ineffective. " + "Try Unity Project Settings -> Audio -> DSP Buffer Size = 'Best Latency' or 'Good Latency'.", + ioBufferMs, preferredIoBufferMs); + } + + _StreamMaximizeInputGain(); + + return ok ? 1 : 0; +} + +// Tear down the session so other apps can resume audio. Safe to call when no +// session was configured. +void _StreamDeconfigureAudioSession() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + + // Clear intent first so observers don't reapply during teardown. + s_streamSessionConfigured = NO; + + BOOL ok = [audioSession setActive:NO + withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation + error:&error]; + if (!ok || error) { + NSLog(@"[StreamVideo iOS Audio] Failed to deactivate audio session: %@", error); + } +} + +// Returns 1 if the session is configured for VoiceProcessingIO +// (PlayAndRecord + VideoChat/VoiceChat), 0 otherwise. Configuration only; +// does not verify IOBufferDuration is small enough for AEC to converge. +int _StreamIsHardwareNoiseCancellationActive() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + BOOL voiceProcessing = [audioSession.mode isEqualToString:AVAudioSessionModeVideoChat] || + [audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]; + BOOL playAndRecord = [audioSession.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]; + return (voiceProcessing && playAndRecord) ? 1 : 0; +} + +const char* _StreamGetAudioSessionInfo() { + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + AVAudioSessionRouteDescription *route = audioSession.currentRoute; + AVAudioSessionCategoryOptions options = audioSession.categoryOptions; + + NSMutableString *info = [NSMutableString string]; + [info appendString:@"=== iOS Audio Session ===\n"]; + [info appendFormat:@"Category: %@\n", audioSession.category]; + [info appendFormat:@"Mode: %@\n", audioSession.mode]; + + BOOL voiceProcessing = [audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat] || + [audioSession.mode isEqualToString:AVAudioSessionModeVideoChat]; + [info appendFormat:@"Hardware AEC/NS/AGC (VoiceProcessingIO): %@\n", voiceProcessing ? @"ENABLED" : @"DISABLED"]; + + [info appendString:@"Options:"]; + if (options & AVAudioSessionCategoryOptionMixWithOthers) [info appendString:@" MixWithOthers"]; + if (options & AVAudioSessionCategoryOptionDuckOthers) [info appendString:@" DuckOthers"]; + if (options & AVAudioSessionCategoryOptionAllowBluetooth) [info appendString:@" AllowBluetooth"]; + if (options & AVAudioSessionCategoryOptionDefaultToSpeaker) [info appendString:@" DefaultToSpeaker"]; + if (options & AVAudioSessionCategoryOptionAllowBluetoothA2DP) [info appendString:@" AllowBluetoothA2DP"]; + if (options & AVAudioSessionCategoryOptionAllowAirPlay) [info appendString:@" AllowAirPlay"]; + if (options == 0) [info appendString:@" (None)"]; + [info appendString:@"\n"]; + + for (AVAudioSessionPortDescription *output in route.outputs) { + [info appendFormat:@"Output: %@ (%@)\n", output.portName, output.portType]; + } + for (AVAudioSessionPortDescription *input in route.inputs) { + [info appendFormat:@"Input: %@ (%@)\n", input.portName, input.portType]; + } + + [info appendFormat:@"Volume: %.0f%%\n", audioSession.outputVolume * 100.0]; + if (audioSession.isInputGainSettable) { + [info appendFormat:@"Input Gain: %.0f%% (settable)\n", audioSession.inputGain * 100.0]; + } else { + [info appendFormat:@"Input Gain: %.0f%% (not settable)\n", audioSession.inputGain * 100.0]; + } + [info appendFormat:@"Sample Rate: %.0f Hz\n", audioSession.sampleRate]; + [info appendFormat:@"IO Buffer: %.1f ms\n", audioSession.IOBufferDuration * 1000.0]; + [info appendFormat:@"Input Latency: %.1f ms\n", audioSession.inputLatency * 1000.0]; + [info appendFormat:@"Output Latency: %.1f ms\n", audioSession.outputLatency * 1000.0]; + + return strdup([info UTF8String]); +} + +} diff --git a/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS/iosAudioConfig.mm.meta b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS/iosAudioConfig.mm.meta new file mode 100644 index 00000000..3575334e --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/iOSAudioManagers/Plugins/iOS/iosAudioConfig.mm.meta @@ -0,0 +1,34 @@ +fileFormatVersion: 2 +guid: d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + iPhone: iOS + second: + enabled: 1 + settings: + CompileFlags: + FrameworkDependencies: AVFoundation + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/Android/libwebrtc.aar b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/Android/libwebrtc.aar index 8e81750d..85739686 100644 Binary files a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/Android/libwebrtc.aar and b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/Android/libwebrtc.aar differ diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM.meta new file mode 100644 index 00000000..e6534731 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0923e5ab5ce4649ea9f8cf661056e837 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents.meta new file mode 100644 index 00000000..7d8c99cc --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d36091c2bf2f4213a46883d0f013ebf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Info.plist b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Info.plist new file mode 100644 index 00000000..4ca2ee09 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.com.unity.webrtc + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 2.4.0 + CFBundleVersion + 2.4.0 + + diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Info.plist.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Info.plist.meta new file mode 100644 index 00000000..2767e532 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Info.plist.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1b24c774906c14a3ab540c884922adc5 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources.meta new file mode 100644 index 00000000..e17189b2 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4dd68111a5d854dd3b1c9bfe123ec28b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF.meta new file mode 100644 index 00000000..0dc57b7b --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fa8a63d716b854dfb8472df618351c7c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF/webrtc b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF/webrtc new file mode 100644 index 00000000..bbbac8a1 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF/webrtc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c80c30ce69cbba4d95dacd1557765bb47da27f5d18aee5902c5cf2abe4814961 +size 172626164 diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF/webrtc.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF/webrtc.meta new file mode 100644 index 00000000..f78052b3 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/DWARF/webrtc.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8fa65869da6ed4409af11b126627fa48 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations.meta new file mode 100644 index 00000000..0ca2f3f7 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1a526a03474004ed2a85625d0dc7bdfd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64.meta new file mode 100644 index 00000000..e4e27abf --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9a5c6a7212ef46e6aa3183036759ce6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64/webrtc.yml b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64/webrtc.yml new file mode 100644 index 00000000..71381799 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64/webrtc.yml @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aa6d948374aa799a0fd0f2444b06e9366e6bd55e367c29fc7623d44d4ba3ecf +size 52398094 diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64/webrtc.yml.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64/webrtc.yml.meta new file mode 100644 index 00000000..e9041201 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/aarch64/webrtc.yml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 15a57c8b275624ceebd73398ff0ee19c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64.meta new file mode 100644 index 00000000..70c76dc9 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ede26ef0490048a3abac60158fd8c05 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64/webrtc.yml b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64/webrtc.yml new file mode 100644 index 00000000..fdddba35 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64/webrtc.yml @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b4b606d1e35077f50c0760782935c712c395e49d77ae1dc0a55b6f21229650c +size 52771777 diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64/webrtc.yml.meta b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64/webrtc.yml.meta new file mode 100644 index 00000000..2ce5bf97 --- /dev/null +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework.dSYM/Contents/Resources/Relocations/x86_64/webrtc.yml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c232c8808c71b4584aade3cdd99da67d +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/Info.plist b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/Info.plist index 556c754d..04714044 100644 Binary files a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/Info.plist and b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/Info.plist differ diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/webrtc b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/webrtc old mode 100644 new mode 100755 index 083a0f77..f8205db7 Binary files a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/webrtc and b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/iOS/webrtc.framework/webrtc differ diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/macOS/libwebrtc.dylib b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/macOS/libwebrtc.dylib old mode 100644 new mode 100755 index 7b2faae7..11450676 Binary files a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/macOS/libwebrtc.dylib and b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/macOS/libwebrtc.dylib differ diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/libwebrtc.so b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/libwebrtc.so old mode 100644 new mode 100755 index fe31c6fb..67f39340 Binary files a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/libwebrtc.so and b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/libwebrtc.so differ diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/webrtc.dll b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/webrtc.dll index aebcf909..8c6a0c87 100644 Binary files a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/webrtc.dll and b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Plugins/x86_64/webrtc.dll differ diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/AudioStreamTrack.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/AudioStreamTrack.cs index 8d568c6d..d6cf4b5b 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/AudioStreamTrack.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/AudioStreamTrack.cs @@ -26,15 +26,15 @@ namespace Unity.WebRTC public delegate void AudioReadEventHandler(float[] data, int channels, int sampleRate); /// - /// + /// Provides extension methods for the class to facilitate integration with . /// public static class AudioSourceExtension { /// - /// + /// Sets the for the . /// - /// - /// + /// The to set the track for. + /// The to set. public static void SetTrack(this AudioSource source, AudioStreamTrack track) { track._streamRenderer.Source = source; @@ -42,7 +42,7 @@ public static void SetTrack(this AudioSource source, AudioStreamTrack track) } /// - /// Represents a single audio track within a stream. + /// Represents a single audio track within a stream. /// /// /// `AudioStreamTrack` is a `MediaStreamTrack` that represents a single audio track within a stream. @@ -289,10 +289,10 @@ internal void RemoveSink(AudioStreamRenderer renderer) NativeMethods.AudioTrackRemoveSink( GetSelfOrThrow(), renderer.self); } +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR -#if UNITY_ANDROID && !UNITY_EDITOR /// - /// Mutes this audio track locally without affecting other users. + /// Mutes this audio track locally. /// /// /// `MuteLocally` mutes this audio track on the local device only. @@ -311,7 +311,7 @@ public void MuteLocally() { if (_streamRenderer == null) throw new InvalidOperationException("MuteLocally is only available for receiver side audio tracks."); - + NativeMethods.AudioTrackSinkMute(_streamRenderer.self); } @@ -333,7 +333,7 @@ public void UnmuteLocally() { if (_streamRenderer == null) throw new InvalidOperationException("UnmuteLocally is only available for receiver side audio tracks."); - + NativeMethods.AudioTrackSinkUnmute(_streamRenderer.self); } @@ -359,16 +359,16 @@ public bool IsLocallyMuted() { if (_streamRenderer == null) throw new InvalidOperationException("IsLocallyMuted is only available for receiver side audio tracks."); - + return NativeMethods.AudioTrackSinkIsMuted(_streamRenderer.self); } #endif /// - /// Disposes of AudioStreamTrack. + /// Disposes of AudioStreamTrack. /// /// - /// `Dispose` method disposes of the `AudioStreamTrack` and releases the associated resources. + /// `Dispose` method disposes of the `AudioStreamTrack` and releases the associated resources. /// /// /// - /// Provides the audio data to the track. + /// Provides the audio data to the track. /// /// - /// `SetData` method provides the audio data to the track. + /// `SetData` method provides the audio data to the track. /// - /// `NativeArray` containing audio data samples. + /// NativeArray containing audio data samples. /// Number of audio channels. /// Sample rate of the audio data /// @@ -425,12 +425,12 @@ public void SetData(NativeArray.ReadOnly nativeArray, int channels, int s } /// - /// Provides the audio data to the track. + /// Provides the audio data to the track. /// /// - /// `SetData` method provides the audio data to the track. + /// `SetData` method provides the audio data to the track. /// - /// `NativeSlice` containing audio data samples. + /// NativeSlice containing audio data samples. /// Number of audio channels. /// Sample rate of the audio data /// @@ -542,7 +542,7 @@ public event AudioReadEventHandler onReceived _streamRenderer.onReceived -= value; } } -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR public void StartLocalAudioCapture(int deviceId, int sampleRate) { diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/CameraExtension.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/CameraExtension.cs index b58a3279..9ef11185 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/CameraExtension.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/CameraExtension.cs @@ -23,7 +23,7 @@ public static class CameraExtension /// An optional to facilitate texture copying. Default is null /// A instance that can be used to stream video. /// - /// Creates a GameObject with a Camera component and a VideoStreamTrack capturing video from the camera. + /// Creates a GameObject with a Camera component and a VideoStreamTrack capturing video from the camera. /// The depth buffer format for the render texture. Default is /// A containing the video track captured from the camera. /// - /// Creates a MediaStream with a VideoStreamTrack capturing video from the camera. + /// Creates a MediaStream with a VideoStreamTrack capturing video from the camera. /// - /// + /// Delegate invoked when a is added to the . /// - /// + /// Event containing the added track information. public delegate void DelegateOnAddTrack(MediaStreamTrackEvent e); /// - /// + /// Delegate invoked when a is removed from the . /// - /// + /// Event containing the removed track information. public delegate void DelegateOnRemoveTrack(MediaStreamTrackEvent e); /// /// Represents a stream of media content. /// /// - /// `MediaStream` represents a stream of media content. + /// represents a stream of media content. /// A stream consists of several tracks, such as video or audio tracks. - /// Each track is specified as an instance of `MediaStreamTrack`. + /// Each track is specified as an instance of . /// /// /// - /// Finalizer for MediaStream. + /// Finalizer for . /// /// /// Ensures that resources are released by calling the `Dispose` method. @@ -80,10 +80,10 @@ public override void Dispose() } /// - /// Delegate to be called when a new MediaStreamTrack object has been added. + /// Delegate to be called when a new object has been added. /// - /// todo:(kazuki) Rename to "onAddTrack" - /// todo:(kazuki) Should we change the API to use UnityEvent or Action class? + // todo:(kazuki) Rename to "onAddTrack" + // todo:(kazuki) Should we change the API to use UnityEvent or Action class? public DelegateOnAddTrack OnAddTrack { get => onAddTrack; @@ -94,10 +94,10 @@ public DelegateOnAddTrack OnAddTrack } /// - /// Delegate to be called when a new MediaStreamTrack object has been removed. + /// Delegate to be called when a new object has been removed. /// - /// todo:(kazuki) Rename to "onAddTrack" - /// todo:(kazuki) Should we change the API to use UnityEvent or Action class? + // todo:(kazuki) Rename to "onAddTrack" + // todo:(kazuki) Should we change the API to use UnityEvent or Action class? public DelegateOnRemoveTrack OnRemoveTrack { get => onRemoveTrack; @@ -111,9 +111,9 @@ public DelegateOnRemoveTrack OnRemoveTrack /// Returns a list of VideoStreamTrack objects in the stream. /// /// - /// `GetVideoTracks` method returns a sequence that represents all the `VideoStreamTrack` objects in this stream's track set. + /// `GetVideoTracks` method returns a sequence that represents all the objects in this stream's track set. /// - /// List of `MediaStreamTrack` objects, one for each video track contained in the media stream. + /// List of objects, one for each video track contained in the media stream. /// /// videoTracks = mediaStream.GetVideoTracks(); @@ -129,9 +129,9 @@ public IEnumerable GetVideoTracks() /// Returns a list of AudioStreamTrack objects in the stream. /// /// - /// `GetAudioTracks` method returns a sequence that represents all the `AudioStreamTrack` objects in this stream's track set. + /// `GetAudioTracks` method returns a sequence that represents all the objects in this stream's track set. /// - /// List of `AudioStreamTrack` objects, one for each audio track contained in the stream. + /// List of objects, one for each audio track contained in the stream. /// /// audioTracks = mediaStream.GetAudioTracks(); @@ -147,9 +147,9 @@ public IEnumerable GetAudioTracks() /// Returns a list of MediaStreamTrack objects in the stream. /// /// - /// `GetTracks` method returns a sequence that represents all the `MediaStreamTrack` objects in this stream's track set. + /// `GetTracks` method returns a sequence that represents all the objects in this stream's track set. /// - /// List of `MediaStreamTrack` objects. + /// List of objects. /// /// tracks = mediaStream.GetTracks(); diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/MediaStreamTrack.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/MediaStreamTrack.cs index 3bf49047..4de18632 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/MediaStreamTrack.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/MediaStreamTrack.cs @@ -124,49 +124,49 @@ internal static MediaStreamTrack Create(IntPtr ptr) } /// - /// + /// Specifies the type of media for a track. /// public enum TrackKind { /// - /// + /// Represents an audio track. /// Audio, /// - /// + /// Represents a video track. /// Video } /// - /// + /// Indicates the current state of a media track. /// public enum TrackState { /// - /// + /// The track is active and live. /// Live, /// - /// + /// The track has ended. /// Ended } /// - /// + /// Represents an event triggered when a track is added to a peer connection. /// public class RTCTrackEvent { /// - /// + /// The transceiver associated with the track event. /// public RTCRtpTransceiver Transceiver { get; } /// - /// + /// The receiver associated with the track event. /// public RTCRtpReceiver Receiver { @@ -177,7 +177,7 @@ public RTCRtpReceiver Receiver } /// - /// + /// The media track associated with the event. /// public MediaStreamTrack Track { @@ -188,7 +188,7 @@ public MediaStreamTrack Track } /// - /// + /// The media streams associated with the track event. /// public IEnumerable Streams { @@ -206,12 +206,12 @@ internal RTCTrackEvent(IntPtr ptrTransceiver, RTCPeerConnection peer) } /// - /// + /// Represents an event triggered when a media stream track is added or removed. /// public class MediaStreamTrackEvent { /// - /// + /// The media stream track associated with the event. /// public MediaStreamTrack Track { get; } diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCDataChannel.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCDataChannel.cs index 0f6a7370..49770f03 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCDataChannel.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCDataChannel.cs @@ -801,7 +801,7 @@ internal RTCDataChannel(IntPtr ptr, RTCPeerConnection peerConnection) } /// - /// + /// Finalizer for . /// ~RTCDataChannel() { diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCIceCandidate.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCIceCandidate.cs index ce002325..bcf5ee70 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCIceCandidate.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCIceCandidate.cs @@ -4,20 +4,20 @@ namespace Unity.WebRTC { /// - /// + /// Initialization options for creating an ICE candidate. /// public class RTCIceCandidateInit { /// - /// + /// SDP string describing the candidate. /// public string candidate; /// - /// + /// The media stream identification for the candidate. /// public string sdpMid; /// - /// + /// The index of the m-line in SDP. /// public int? sdpMLineIndex; } @@ -29,68 +29,68 @@ public class RTCIceCandidateInit public enum RTCIceComponent : int { /// - /// + /// RTP component. /// Rtp = 1, /// - /// + /// RTCP component. /// Rtcp = 2, } /// - /// + /// Indicates the transport protocol used by the ICE candidate. /// public enum RTCIceProtocol : int { /// - /// + /// UDP protocol. /// Udp = 1, /// - /// + /// TCP protocol. /// Tcp = 2 } /// - /// + /// Specifies the type of ICE candidate. /// public enum RTCIceCandidateType { /// - /// + /// Host candidate type. /// Host, /// - /// + /// Server reflexive candidate type. /// Srflx, /// - /// + /// Peer reflexive candidate type. /// Prflx, /// - /// + /// Relay candidate type. /// Relay } /// - /// + /// Describes the TCP type for ICE candidates. /// public enum RTCIceTcpCandidateType { /// - /// + /// Active TCP candidate. /// Active, /// - /// + /// Passive TCP candidate. /// Passive, /// - /// + /// Simultaneous open TCP candidate. /// So } @@ -147,64 +147,64 @@ public static RTCIceCandidateType ParseRTCIceCandidateType(this string src) } /// - /// + /// Represents an ICE candidate used for network connectivity checks. /// public class RTCIceCandidate : IDisposable { /// - /// + /// Returns the SDP string for this candidate. /// public string Candidate => NativeMethods.IceCandidateGetSdp(self); /// - /// + /// Returns the media stream identification. /// public string SdpMid => NativeMethods.IceCandidateGetSdpMid(self); /// - /// + /// Returns the index of the m-line in SDP. /// public int? SdpMLineIndex => NativeMethods.IceCandidateGetSdpLineIndex(self); /// - /// + /// Returns a string which uniquely identifies the candidate. /// public string Foundation => _candidate.foundation; /// - /// + /// Returns the ICE component type. /// public RTCIceComponent? Component => _candidate.component; /// - /// + /// Returns the priority value for this candidate. /// public uint Priority => _candidate.priority; /// - /// + /// Returns the IP address for this candidate. /// public string Address => _candidate.address; /// - /// + /// Returns the transport protocol for this candidate. /// public RTCIceProtocol? Protocol => _candidate.protocol.ParseRTCIceProtocol(); /// - /// + /// Returns the port number for this candidate. /// public ushort? Port => _candidate.port; /// - /// + /// Returns the candidate type. /// public RTCIceCandidateType? Type => _candidate.type.ParseRTCIceCandidateType(); /// - /// + /// Returns the TCP type for this candidate, if applicable. /// public RTCIceTcpCandidateType? TcpType => _candidate.tcpType.ParseRTCIceTcpCandidateType(); /// - /// + /// Returns the related IP address for this candidate. /// public string RelatedAddress => _candidate.relatedAddress; /// - /// + /// Returns the related port number for this candidate. /// public ushort? RelatedPort => _candidate.relatedPort; /// - /// + /// Returns the username fragment for this candidate. /// public string UserNameFragment => _candidate.usernameFragment; @@ -214,7 +214,7 @@ public class RTCIceCandidate : IDisposable private bool disposed; /// - /// + /// Finalizer for RTCIceCandidate to release resources. /// ~RTCIceCandidate() { @@ -222,7 +222,7 @@ public class RTCIceCandidate : IDisposable } /// - /// + /// Releases resources used by the ICE candidate. /// public void Dispose() { @@ -242,9 +242,9 @@ public void Dispose() } /// - /// + /// Creates a new RTCIceCandidate instance from initialization data. /// - /// + /// Initialization data for the candidate. public RTCIceCandidate(RTCIceCandidateInit candidateInfo = null) { candidateInfo = candidateInfo ?? new RTCIceCandidateInit(); diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCPeerConnection.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCPeerConnection.cs index 48071fb5..14313246 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCPeerConnection.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCPeerConnection.cs @@ -50,12 +50,12 @@ namespace Unity.WebRTC public delegate void DelegateOnIceConnectionChange(RTCIceConnectionState state); /// - /// Delegate to be called after a new track has been added to an RTCRtpReceiver which is part of the connection. + /// Delegate to be called after a new track has been added to an RTCRtpReceiver which is part of the connection. /// /// - /// This delegate is called after a new track has been added to an `RTCRtpReceiver` which is part of the connection. + /// This delegate is called after a new track has been added to an `RTCRtpReceiver` which is part of the connection. /// - /// + /// New connection state. /// /// @@ -73,7 +73,7 @@ namespace Unity.WebRTC /// /// This delegate is called when the state of the ICE candidate gathering process changes. /// - /// + /// New ICE gathering state. /// /// @@ -361,7 +361,7 @@ RTCRtpTransceiver CreateTransceiver(IntPtr ptr) /// /// Delegate to be called when the IceConnectionState is changed. /// - /// A delegate containing . + /// A delegate containing . /// /// @@ -619,7 +619,7 @@ public RTCPeerConnection() /// /// /// `RTCPeerConnection` constructor creates an instance of peer connection with a default configuration. - /// An object providing options to configure the new connection. + /// An object providing options to configure the new connection. /// /// /// - /// + /// Represents a contributing source for RTP streams. /// public class RTCRtpContributingSource { /// - /// This value is in the range 0.0 to 1.0 + /// The audio level of the source, ranging from 0.0 to 1.0. /// public double? audioLevel { get; private set; } /// - /// + /// The RTP timestamp of the source. /// public long? rtpTimestamp { get; private set; } /// - /// + /// The SSRC of the source. /// public uint? source { get; private set; } /// - /// + /// The timestamp of the source. /// public long? timestamp { get; private set; } @@ -56,7 +56,7 @@ internal struct RTCRtpContributingSourceInternal } /// - /// + /// Represents a receiver for RTP streams. /// public class RTCRtpReceiver : RefCountedObject { @@ -70,7 +70,7 @@ internal RTCRtpReceiver(IntPtr ptr, RTCPeerConnection peer) : base(ptr) } /// - /// + /// Finalizer for RTCRtpReceiver. /// ~RTCRtpReceiver() { @@ -78,7 +78,7 @@ internal RTCRtpReceiver(IntPtr ptr, RTCPeerConnection peer) : base(ptr) } /// - /// + /// Releases resources used by the object. /// public override void Dispose() { @@ -94,10 +94,10 @@ public override void Dispose() } /// - /// + /// Gets the capabilities of the RTP receiver. /// - /// - /// + /// The type of media track (audio or video). + /// Capabilities supported by the receiver. public static RTCRtpCapabilities GetCapabilities(TrackKind kind) { WebRTC.Context.GetReceiverCapabilities(kind, out IntPtr ptr); @@ -109,17 +109,18 @@ public static RTCRtpCapabilities GetCapabilities(TrackKind kind) } /// - /// + /// Gets the statistics report for the receiver. /// - /// + /// Returns an asynchronous operation for retrieving receiver statistics. public RTCStatsReportAsyncOperation GetStats() { return peer.GetStats(this); } /// - /// + /// Gets the contributing sources for the receiver. /// + /// Returns an array of contributing sources. public RTCRtpContributingSource[] GetContributingSources() { RTCRtpContributingSourceInternal[] array = NativeMethods.ReceiverGetSources(self, out var length).AsArray((int)length); @@ -133,8 +134,9 @@ public RTCRtpContributingSource[] GetContributingSources() } /// - /// + /// Gets the synchronization sources for the receiver. /// + /// Returns an array of synchronization sources. public RTCRtpContributingSource[] GetSynchronizationSources() { RTCRtpContributingSourceInternal[] array = NativeMethods.ReceiverGetSources(self, out var length).AsArray((int)length); @@ -147,6 +149,9 @@ public RTCRtpContributingSource[] GetSynchronizationSources() return sources; } + /// + /// Gets the media stream track associated with the receiver. + /// public MediaStreamTrack Track { get @@ -159,7 +164,7 @@ public MediaStreamTrack Track } /// - /// + /// Gets or sets the RTP transform for the receiver. /// public RTCRtpTransform Transform { @@ -179,7 +184,7 @@ public RTCRtpTransform Transform } /// - /// + /// Gets the media streams associated with the receiver. /// public IEnumerable Streams { diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpSender.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpSender.cs index 4a47fc3a..202793da 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpSender.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpSender.cs @@ -15,6 +15,7 @@ namespace Unity.WebRTC /// + /// /// public class RTCRtpSender : RefCountedObject { @@ -122,7 +123,7 @@ public RTCStatsReportAsyncOperation GetStats() } /// - /// MediaStreamTrack managed by RTCRtpSender. If it is null, no transmission occurs. + /// managed by RTCRtpSender. If it is null, no transmission occurs. /// public MediaStreamTrack Track { @@ -136,7 +137,7 @@ public MediaStreamTrack Track } /// - /// RTCRtpScriptTransform used to insert a transform stream in a worker thread into the sender pipeline, + /// used to insert a transform stream in a worker thread into the sender pipeline, /// enabling transformations on encoded video and audio frames after output by a codec but before transmission. /// public RTCRtpTransform Transform diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpTransform.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpTransform.cs index 55cd0380..a4c1f907 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpTransform.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCRtpTransform.cs @@ -7,58 +7,58 @@ namespace Unity.WebRTC { /// - /// + /// Types of encoded video frames. /// public enum RTCEncodedVideoFrameType { /// - /// + /// No frame data. /// Empty, /// - /// + /// Key frame for video decoding. /// Key, /// - /// + /// Delta frame containing changes. /// Delta } /// - /// + /// Metadata for an encoded video frame. /// public class RTCEncodedVideoFrameMetadata { /// - /// + /// Unique identifier for the frame. /// public readonly long? frameId; /// - /// + /// Frame width in pixels. /// public readonly ushort width; /// - /// + /// Frame height in pixels. /// public readonly ushort height; /// - /// + /// Simulcast stream index. /// public readonly int simulcastIndex; /// - /// + /// Temporal layer index. /// public readonly long temporalIndex; /// - /// + /// An Array of positive integers indicating the frameIds of frames on which this frame depends. /// public readonly long[] dependencies; @@ -91,25 +91,25 @@ public void Dispose() } /// - /// + /// Represents an encoded RTP frame. /// public class RTCEncodedFrame { internal IntPtr self; /// - /// + /// Timestamp of the frame. /// public uint Timestamp => NativeMethods.FrameGetTimestamp(self); /// - /// + /// SSRC identifier for the frame. /// public uint Ssrc => NativeMethods.FrameGetSsrc(self); /// - /// + /// Gets the encoded frame data as a read-only array. /// - /// + /// Read-only byte array of frame data. #if UNITY_ANDROID // todo: Optimizing for Android platform leads a crash issue. [MethodImpl(MethodImplOptions.NoOptimization)] @@ -131,9 +131,9 @@ public NativeArray.ReadOnly GetData() } /// - /// + /// Sets the frame data from a read-only array. /// - /// + /// Read-only byte array. public void SetData(NativeArray.ReadOnly data) { unsafe @@ -143,11 +143,11 @@ public void SetData(NativeArray.ReadOnly data) } /// - /// + /// Sets a portion of the frame data. /// - /// - /// - /// + /// Read-only byte array. + /// Start index in array. + /// Number of bytes to set. public void SetData(NativeArray.ReadOnly data, int startIndex, int length) { unsafe @@ -157,9 +157,9 @@ public void SetData(NativeArray.ReadOnly data, int startIndex, int length) } /// - /// + /// Sets the frame data from a native slice. /// - /// + /// Native slice of bytes. public void SetData(NativeSlice data) { unsafe @@ -175,7 +175,7 @@ internal RTCEncodedFrame(IntPtr ptr) } /// - /// + /// Encoded audio frame for RTP transform. /// public class RTCEncodedAudioFrame : RTCEncodedFrame { @@ -183,12 +183,12 @@ internal RTCEncodedAudioFrame(IntPtr ptr) : base(ptr) { } } /// - /// + /// Encoded video frame for RTP transform. /// public class RTCEncodedVideoFrame : RTCEncodedFrame { /// - /// + /// Type of the encoded video frame. /// public RTCEncodedVideoFrameType Type { @@ -200,9 +200,9 @@ public RTCEncodedVideoFrameType Type } /// - /// + /// Gets metadata for the encoded video frame. /// - /// + /// Metadata object. public RTCEncodedVideoFrameMetadata GetMetadata() { IntPtr ptr = NativeMethods.VideoFrameGetMetadata(self); @@ -216,12 +216,12 @@ internal RTCEncodedVideoFrame(IntPtr ptr) : base(ptr) { } }; /// - /// + /// RTP transform for encoded frames. /// public class RTCRtpTransform : RefCountedObject { /// - /// + /// Track kind for the transform. /// public TrackKind Kind { get; } @@ -236,7 +236,7 @@ internal RTCRtpTransform(TrackKind kind, TransformedFrameCallback callback) } /// - /// + /// Releases resources used by the transform. /// ~RTCRtpTransform() { @@ -244,16 +244,16 @@ internal RTCRtpTransform(TrackKind kind, TransformedFrameCallback callback) } /// - /// + /// Writes an encoded frame to the transform sink. /// - /// + /// Encoded frame to write. public void Write(RTCEncodedFrame frame) { NativeMethods.FrameTransformerSendFrameToSink(self, frame.self); } /// - /// + /// Releases resources used by the transform. /// public override void Dispose() { @@ -270,12 +270,12 @@ public override void Dispose() } /// - /// + /// Event for transformed RTP frames. /// public class RTCTransformEvent { /// - /// + /// Transformed encoded frame. /// public RTCEncodedFrame Frame { get; } @@ -286,21 +286,21 @@ internal RTCTransformEvent(RTCEncodedFrame frame) } /// - /// + /// Callback for transformed RTP frames. /// - /// + /// Transform event argument. public delegate void TransformedFrameCallback(RTCTransformEvent e); /// - /// + /// Script-based RTP transform for encoded frames. /// public class RTCRtpScriptTransform : RTCRtpTransform { /// - /// + /// Constructor for RTCRtpScriptTransform. /// - /// - /// + /// Track kind for the transform. + /// Callback to invoke for transformed frames. public RTCRtpScriptTransform(TrackKind kind, TransformedFrameCallback callback) : base(kind, callback) { diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCStats.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCStats.cs index 8dea5c11..f93b8652 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCStats.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RTCStats.cs @@ -5,19 +5,19 @@ namespace Unity.WebRTC { /// - /// + /// Associates a string value with an enum field for RTCStatsType. /// public class StringValueAttribute : Attribute { /// - /// + /// String value associated with the enum field. /// public string StringValue { get; protected set; } /// - /// + /// Constructor for StringValueAttribute. /// - /// + /// String value to associate with the enum field. public StringValueAttribute(string value) { this.StringValue = value; @@ -25,90 +25,90 @@ public StringValueAttribute(string value) } /// - /// + /// Identifies the type of RTC statistics object. /// public enum RTCStatsType { /// - /// + /// Identifies the type of codec. /// [StringValue("codec")] Codec = 0, /// - /// + /// Identifies inbound RTP stream statistics. /// [StringValue("inbound-rtp")] InboundRtp = 1, /// - /// + /// Identifies outbound RTP stream statistics. /// [StringValue("outbound-rtp")] OutboundRtp = 2, /// - /// + /// Identifies statistics related to remote inbound RTP streams. /// [StringValue("remote-inbound-rtp")] RemoteInboundRtp = 3, /// - /// + /// Identifies statistics related to remote outbound RTP streams. /// [StringValue("remote-outbound-rtp")] RemoteOutboundRtp = 4, /// - /// + /// Identifies statistics related to media sources. /// [StringValue("media-source")] MediaSource = 5, /// - /// + /// Identifies statistics related to media playout. /// [StringValue("media-playout")] MediaPlayOut = 6, /// - /// + /// Identifies statistics related to a peer connection. /// [StringValue("peer-connection")] PeerConnection = 7, /// - /// + /// Identifies statistics related to a data channel. /// [StringValue("data-channel")] DataChannel = 8, /// - /// + /// Identifies statistics related to a transport. /// [StringValue("transport")] Transport = 9, /// - /// + /// Identifies statistics related to a candidate pair. /// [StringValue("candidate-pair")] CandidatePair = 10, /// - /// + /// Identifies statistics related to a local ICE candidate. /// [StringValue("local-candidate")] LocalCandidate = 11, /// - /// + /// Identifies statistics related to a remote ICE candidate. /// [StringValue("remote-candidate")] RemoteCandidate = 12, /// - /// + /// Identifies statistics related to a certificate. /// [StringValue("certificate")] Certificate = 13, @@ -207,7 +207,7 @@ internal object GetValue() } /// - /// + /// Base class for all RTC statistics objects. /// public class RTCStats { @@ -216,7 +216,7 @@ public class RTCStats internal Dictionary m_dict; /// - /// + /// Gets the type of this statistics object. /// public RTCStatsType Type { @@ -227,7 +227,7 @@ public RTCStatsType Type } /// - /// + /// Returns the unique identifier for this stats entry. /// public string Id { @@ -243,7 +243,7 @@ public long Timestamp } /// - /// + /// Converts the timestamp to a UTC DateTime value. /// public DateTime UtcTimeStamp { @@ -251,7 +251,7 @@ public DateTime UtcTimeStamp } /// - /// + /// Returns a dictionary of all stats members and their values. /// public IDictionary Dict { @@ -450,9 +450,9 @@ RTCStatsMember[] GetMembers() } /// - /// + /// Serializes the stats object to a JSON string. /// - /// + /// JSON representation of the stats. public string ToJson() { return NativeMethods.StatsGetJson(self).AsAnsiStringWithFreeMem(); @@ -460,27 +460,27 @@ public string ToJson() } /// - /// + /// Contains certificate-related statistics for a peer connection. /// public class RTCCertificateStats : RTCStats { /// - /// + /// The fingerprint of the certificate. /// public string fingerprint { get { return GetString("fingerprint"); } } /// - /// + /// The algorithm used to generate the fingerprint. /// public string fingerprintAlgorithm { get { return GetString("fingerprintAlgorithm"); } } /// - /// + /// The base64-encoded certificate. /// public string base64Certificate { get { return GetString("base64Certificate"); } } /// - /// + /// The ID of the certificate that issued this certificate, if any. /// public string issuerCertificateId { get { return GetString("issuerCertificateId"); } } @@ -490,37 +490,37 @@ internal RTCCertificateStats(IntPtr ptr) : base(ptr) } /// - /// + /// Provides codec-specific statistics for RTP streams. /// public class RTCCodecStats : RTCStats { /// - /// + /// Identifies the transport used by this codec. /// public string transportId { get { return GetString("transportId"); } } /// - /// + /// Identifies the payload type for this codec. /// public uint payloadType { get { return GetUnsignedInt("payloadType"); } } /// - /// + /// Identifies the codec's MIME type and subtype. /// public string mimeType { get { return GetString("mimeType"); } } /// - /// + /// Identifies the codec's clock rate. /// public uint clockRate { get { return GetUnsignedInt("clockRate"); } } /// - /// + /// Identifies the codec's number of channels. /// public uint channels { get { return GetUnsignedInt("channels"); } } /// - /// + /// The SDP format parameters associated with this codec. /// public string sdpFmtpLine { get { return GetString("sdpFmtpLine"); } } @@ -530,47 +530,47 @@ internal RTCCodecStats(IntPtr ptr) : base(ptr) } /// - /// + /// Reports statistics for a data channel. /// public class RTCDataChannelStats : RTCStats { /// - /// + /// The label of the data channel. /// public string label { get { return GetString("label"); } } /// - /// + /// The protocol used by the data channel. /// public string protocol { get { return GetString("protocol"); } } /// - /// + /// The identifier for the data channel. /// public int dataChannelIdentifier { get { return GetInt("dataChannelIdentifier"); } } /// - /// + /// The state of the data channel. /// public string state { get { return GetString("state"); } } /// - /// + /// The number of messages sent over the data channel. /// public uint messagesSent { get { return GetUnsignedInt("messagesSent"); } } /// - /// + /// The number of bytes sent over the data channel. /// public ulong bytesSent { get { return GetUnsignedLong("bytesSent"); } } /// - /// + /// The number of messages received over the data channel. /// public uint messagesReceived { get { return GetUnsignedInt("messagesReceived"); } } /// - /// + /// The number of bytes received over the data channel. /// public ulong bytesReceived { get { return GetUnsignedLong("bytesReceived"); } } @@ -580,117 +580,117 @@ internal RTCDataChannelStats(IntPtr ptr) : base(ptr) } /// - /// + /// Details statistics for a pair of ICE candidates. /// public class RTCIceCandidatePairStats : RTCStats { /// - /// + /// Identifier for the transport used by this candidate pair. /// public string transportId { get { return GetString("transportId"); } } /// - /// + /// ID of the local ICE candidate in this pair. /// public string localCandidateId { get { return GetString("localCandidateId"); } } /// - /// + /// ID of the remote ICE candidate in this pair. /// public string remoteCandidateId { get { return GetString("remoteCandidateId"); } } /// - /// + /// Current state of the candidate pair (e.g., succeeded, failed). /// public string state { get { return GetString("state"); } } /// - /// + /// Indicates if this pair is nominated for use. /// public bool nominated { get { return GetBool("nominated"); } } /// - /// + /// Number of packets sent over this candidate pair. /// public ulong packetsSent { get { return GetUnsignedLong("packetsSent"); } } /// - /// + /// Number of packets received over this candidate pair. /// public ulong packetsReceived { get { return GetUnsignedLong("packetsReceived"); } } /// - /// + /// Total bytes sent over this candidate pair. /// public ulong bytesSent { get { return GetUnsignedLong("bytesSent"); } } /// - /// + /// Total bytes received over this candidate pair. /// public ulong bytesReceived { get { return GetUnsignedLong("bytesReceived"); } } /// - /// + /// Timestamp of the last packet sent. /// public double lastPacketSentTimestamp { get { return GetDouble("lastPacketSentTimestamp"); } } /// - /// + /// Timestamp of the last packet received. /// public double lastPacketReceivedTimestamp { get { return GetDouble("lastPacketReceivedTimestamp"); } } /// - /// + /// Round-trip time for this candidate pair. /// public double totalRoundTripTime { get { return GetDouble("totalRoundTripTime"); } } /// - /// + /// Most recent round-trip time measurement. /// public double currentRoundTripTime { get { return GetDouble("currentRoundTripTime"); } } /// - /// + /// Estimated available outgoing bitrate. /// public double availableOutgoingBitrate { get { return GetDouble("availableOutgoingBitrate"); } } /// - /// + /// Estimated available incoming bitrate. /// public double availableIncomingBitrate { get { return GetDouble("availableIncomingBitrate"); } } /// - /// + /// Number of STUN requests received. /// public ulong requestsReceived { get { return GetUnsignedLong("requestsReceived"); } } /// - /// + /// Number of STUN requests sent. /// public ulong requestsSent { get { return GetUnsignedLong("requestsSent"); } } /// - /// + /// Number of STUN responses received. /// public ulong responsesReceived { get { return GetUnsignedLong("responsesReceived"); } } /// - /// + /// Number of STUN responses sent. /// public ulong responsesSent { get { return GetUnsignedLong("responsesSent"); } } /// - /// + /// Number of consent requests sent. /// public ulong consentRequestsSent { get { return GetUnsignedLong("consentRequestsSent"); } } /// - /// + /// Number of packets discarded during sending. /// public ulong packetsDiscardedOnSend { get { return GetUnsignedLong("packetsDiscardedOnSend"); } } /// - /// + /// Number of bytes discarded during sending. /// public ulong bytesDiscardedOnSend { get { return GetUnsignedLong("bytesDiscardedOnSend"); } } @@ -700,98 +700,98 @@ internal RTCIceCandidatePairStats(IntPtr ptr) : base(ptr) } /// - /// + /// Contains statistics for a single ICE candidate. /// public class RTCIceCandidateStats : RTCStats { /// - /// + /// Identifier for the transport associated with this candidate. /// public string transportId { get { return GetString("transportId"); } } /// - /// + /// Indicates if this is a remote candidate. /// [Obsolete] public bool isRemote { get { return GetBool("isRemote"); } } /// - /// + /// The network type of the candidate (e.g., "wifi", "ethernet"). /// public string networkType { get { return GetString("networkType"); } } /// - /// + /// The IP address of the candidate. /// public string ip { get { return GetString("ip"); } } /// - /// + /// The port number of the candidate. + /// + public int port { get { return GetInt("port"); } } + + /// + /// The address of the candidate. /// public string address { get { return GetString("address"); } } /// - /// + /// The candidate's priority. /// - public int port { get { return GetInt("port"); } } + public int priority { get { return GetInt("priority"); } } /// - /// + /// The protocol used by the candidate (e.g., "udp", "tcp"). /// public string protocol { get { return GetString("protocol"); } } /// - /// + /// The transport protocol used by the relay candidate (e.g., "udp", "tcp", "tls"). /// public string relayProtocol { get { return GetString("relayProtocol"); } } /// - /// + /// The type of the candidate (e.g., "host", "srflx", "prflx", "relay"). /// public string candidateType { get { return GetString("candidateType"); } } /// - /// - /// - public int priority { get { return GetInt("priority"); } } - - /// - /// + /// The URL of the TURN server used by the candidate, if applicable. /// public string url { get { return GetString("url"); } } /// - /// + /// The string which uniquely identifies the candidate. /// public string foundation { get { return GetString("foundation"); } } /// - /// + /// The related address of the candidate, if applicable. /// public string relatedAddress { get { return GetString("relatedAddress"); } } /// - /// + /// The related port of the candidate, if applicable. /// public int relatedPort { get { return GetInt("relatedPort"); } } /// - /// + /// The username fragment used in ICE negotiation. /// public string usernameFragment { get { return GetString("usernameFragment"); } } /// - /// + /// The TCP type of the candidate (e.g., "active", "passive", "so"). /// public string tcpType { get { return GetString("tcpType"); } } /// - /// + /// Indicates if the candidate is on a VPN. /// public bool vpn { get { return GetBool("vpn"); } } /// - /// + /// The type of network adapter used by the candidate (e.g., "ethernet", "wifi"). /// public string networkAdapterType { get { return GetString("networkAdapterType"); } } @@ -801,18 +801,18 @@ internal RTCIceCandidateStats(IntPtr ptr) : base(ptr) } /// - /// + /// Provides statistics for the peer connection itself. /// public class RTCPeerConnectionStats : RTCStats { /// - /// + /// The number of data channels opened by the peer connection. /// public uint dataChannelsOpened { get { return GetUnsignedInt("dataChannelsOpened"); } } /// - /// + /// The number of data channels closed by the peer connection. /// public uint dataChannelsClosed { get { return GetUnsignedInt("dataChannelsClosed"); } } @@ -822,28 +822,28 @@ internal RTCPeerConnectionStats(IntPtr ptr) : base(ptr) } /// - /// + /// Base class for RTP stream statistics. /// public class RTCRTPStreamStats : RTCStats { /// - /// + /// The SSRC identifier for the RTP stream. /// public uint ssrc { get { return GetUnsignedInt("ssrc"); } } /// - /// + /// The media type of the RTP stream (e.g., "audio", "video"). /// public string kind { get { return GetString("kind"); } } /// - /// + /// The identifier for the transport used by this RTP stream. /// public string transportId { get { return GetString("transportId"); } } /// - /// + /// The identifier for the RTCCodecStats used by this RTP stream. /// public string codecId { get { return GetString("codecId"); } } @@ -853,17 +853,17 @@ internal RTCRTPStreamStats(IntPtr ptr) : base(ptr) } /// - /// + /// Statistics for received RTP streams. /// public class RTCReceivedRtpStreamStats : RTCRTPStreamStats { /// - /// + /// The amount of jitter experienced by the RTP stream. /// public double jitter { get { return GetDouble("jitter"); } } /// - /// + /// The number of packets lost in the RTP stream. /// public int packetsLost { get { return GetInt("packetsLost"); } } @@ -873,17 +873,17 @@ internal RTCReceivedRtpStreamStats(IntPtr ptr) : base(ptr) } /// - /// + /// Statistics for sent RTP streams. /// public class RTCSentRtpStreamStats : RTCRTPStreamStats { /// - /// + /// The number of packets sent in the RTP stream. /// public ulong packetsSent { get { return GetUnsignedLong("packetsSent"); } } /// - /// + /// The total number of bytes sent in the RTP stream. /// public ulong bytesSent { get { return GetUnsignedLong("bytesSent"); } } @@ -893,297 +893,297 @@ internal RTCSentRtpStreamStats(IntPtr ptr) : base(ptr) } /// - /// + /// Reports inbound RTP stream statistics. /// public class RTCInboundRTPStreamStats : RTCReceivedRtpStreamStats { /// - /// + /// Identifier for the media source of this RTP stream. /// public string playoutId { get { return GetString("playoutId"); } } /// - /// + /// Identifier for the id value of the of this RTP stream. /// public string trackIdentifier { get { return GetString("trackIdentifier"); } } /// - /// + /// The type of media being transmitted (e.g., "audio", "video"). /// public string mid { get { return GetString("mid"); } } /// - /// + /// Identifier for the remote media source of this RTP stream. /// public string remoteId { get { return GetString("remoteId"); } } /// - /// + /// The total number of RTP packets received. /// public uint packetsReceived { get { return GetUnsignedInt("packetsReceived"); } } /// - /// + /// The total number of RTP packets discarded. /// public ulong packetsDiscarded { get { return GetUnsignedLong("packetsDiscarded"); } } /// - /// + /// The total number of received Forward Error Correction (FEC) packets. /// public ulong fecPacketsReceived { get { return GetUnsignedLong("fecPacketsReceived"); } } /// - /// + /// The total number of discarded Forward Error Correction (FEC) packets. /// public ulong fecPacketsDiscarded { get { return GetUnsignedLong("fecPacketsDiscarded"); } } /// - /// + /// The total number of bytes received in the RTP stream. /// public ulong bytesReceived { get { return GetUnsignedLong("bytesReceived"); } } /// - /// + /// The total number of header bytes received in the RTP stream. /// public ulong headerBytesReceived { get { return GetUnsignedLong("headerBytesReceived"); } } /// - /// + /// The total number of payload bytes received in the RTP stream. /// public ulong retransmittedPacketsReceived { get { return GetUnsignedLong("retransmittedPacketsReceived"); } } /// - /// + /// The total number of retransmitted bytes received in the RTP stream. /// public ulong retransmittedBytesReceived { get { return GetUnsignedLong("retransmittedBytesReceived"); } } /// - /// + /// Timestamp of the last packet received. /// public double lastPacketReceivedTimestamp { get { return GetDouble("lastPacketReceivedTimestamp"); } } /// - /// + /// The total number of packets that arrived late and were discarded. /// public double jitterBufferDelay { get { return GetDouble("jitterBufferDelay"); } } /// - /// + /// The target delay for the jitter buffer. /// public double jitterBufferTargetDelay { get { return GetDouble("jitterBufferTargetDelay"); } } /// - /// + /// The minimum delay experienced by the jitter buffer. /// public double jitterBufferMinimumDelay { get { return GetDouble("jitterBufferMinimumDelay"); } } /// - /// + /// The total number of packets emitted from the jitter buffer. /// public ulong jitterBufferEmittedCount { get { return GetUnsignedLong("jitterBufferEmittedCount"); } } /// - /// + /// The total number of samples received on this stream, including concealedSamples. /// public ulong totalSamplesReceived { get { return GetUnsignedLong("totalSamplesReceived"); } } /// - /// + /// The total number of concealed samples for the received audio track /// public ulong concealedSamples { get { return GetUnsignedLong("concealedSamples"); } } /// - /// + /// The total number of silent concealed samples for the received audio track /// public ulong silentConcealedSamples { get { return GetUnsignedLong("silentConcealedSamples"); } } /// - /// + /// The total number of concealment events for the received audio track /// public ulong concealmentEvents { get { return GetUnsignedLong("concealmentEvents"); } } /// - /// + /// The total number of inserted samples for the received audio track /// public ulong insertedSamplesForDeceleration { get { return GetUnsignedLong("insertedSamplesForDeceleration"); } } /// - /// + /// The total number of removed samples for the received audio track /// public ulong removedSamplesForAcceleration { get { return GetUnsignedLong("removedSamplesForAcceleration"); } } /// - /// + /// The audio level for the received audio track, between 0.0 and 1.0. /// public double audioLevel { get { return GetDouble("audioLevel"); } } /// - /// + /// The total audio energy for the received audio track. /// public double totalAudioEnergy { get { return GetDouble("totalAudioEnergy"); } } /// - /// + /// The total duration of audio samples received on this stream. /// public double totalSamplesDuration { get { return GetDouble("totalSamplesDuration"); } } /// - /// + /// The total number of video frames received on this stream. /// public uint framesReceived { get { return GetUnsignedInt("framesReceived"); } } /// - /// + /// The width of the last decoded frame. /// public uint frameWidth { get { return GetUnsignedInt("frameWidth"); } } /// - /// + /// The height of the last decoded frame. /// public uint frameHeight { get { return GetUnsignedInt("frameHeight"); } } /// - /// + /// The frame rate of the received video stream. /// public double framesPerSecond { get { return GetDouble("framesPerSecond"); } } /// - /// + /// The total number of frames dropped for the received video stream. /// public uint framesDecoded { get { return GetUnsignedInt("framesDecoded"); } } /// - /// + /// The total number of key frames decoded for the received video stream. /// public uint keyFramesDecoded { get { return GetUnsignedInt("keyFramesDecoded"); } } /// - /// + /// The total number of frames dropped for the received video stream. /// public uint framesDropped { get { return GetUnsignedInt("framesDropped"); } } /// - /// + /// The total time spent decoding frames for the received video stream. /// public double totalDecodeTime { get { return GetDouble("totalDecodeTime"); } } /// - /// + /// The total time spent processing frames for the received video stream. /// public double totalProcessingDelay { get { return GetDouble("totalProcessingDelay"); } } /// - /// + /// The total time spent assembling frames for the received video stream. /// public double totalAssemblyTime { get { return GetDouble("totalAssemblyTime"); } } /// - /// + /// The total number of frames that were assembled from multiple packets. /// public uint framesAssembledFromMultiplePackets { get { return GetUnsignedInt("framesAssembledFromMultiplePackets"); } } /// - /// + /// The total number of frames that were decoded from multiple packets. /// public double totalInterFrameDelay { get { return GetDouble("totalInterFrameDelay"); } } /// - /// + /// The total squared inter-frame delay for the received video stream. /// public double totalSquaredInterFrameDelay { get { return GetDouble("totalSquaredInterFrameDelay"); } } /// - /// + /// The total number of pauses in the received video stream. /// public uint pauseCount { get { return GetUnsignedInt("pauseCount"); } } /// - /// + /// The total duration of pauses in the received video stream. /// public double totalPausesDuration { get { return GetDouble("totalPausesDuration"); } } /// - /// + /// The total number of freezes in the received video stream. /// public uint freezeCount { get { return GetUnsignedInt("freezeCount"); } } /// - /// + /// The total duration of freezes in the received video stream. /// public double totalFreezesDuration { get { return GetDouble("totalFreezesDuration"); } } /// - /// + /// the video-content-type of the last key frame sent. /// public string contentType { get { return GetString("contentType"); } } /// - /// + /// The estimated playout timestamp for the last frame. /// public double estimatedPlayoutTimestamp { get { return GetDouble("estimatedPlayoutTimestamp"); } } /// - /// + /// Identifies the decoder implementation used. /// public string decoderImplementation { get { return GetString("decoderImplementation"); } } /// - /// + /// The total number of Full Intra Request (FIR) packets sent by this receiver. /// public uint firCount { get { return GetUnsignedInt("firCount"); } } /// - /// + /// The total number of Picture Loss Indication (PLI) packets sent by this receiver. /// public uint pliCount { get { return GetUnsignedInt("pliCount"); } } /// - /// + /// The total number of Negative Acknowledgement (NACK) packets sent by this receiver. /// public uint nackCount { get { return GetUnsignedInt("nackCount"); } } /// - /// + /// The sum of the QP values of frames decoded by this receiver. /// public ulong qpSum { get { return GetUnsignedLong("qpSum"); } } /// - /// + /// Google-specific timing frame information. /// public string googTimingFrameInfo { get { return GetString("googTimingFrameInfo"); } } /// - /// + /// Indicates whether the decoder is power efficient. /// public bool powerEfficientDecoder { get { return GetBool("powerEfficientDecoder"); } } /// - /// + /// The total number of times the jitter buffer was flushed. /// public ulong jitterBufferFlushes { get { return GetUnsignedLong("jitterBufferFlushes"); } } /// - /// + /// The total number of samples that were lost due to a delayed packet. /// public ulong delayedPacketOutageSamples { get { return GetUnsignedLong("delayedPacketOutageSamples"); } } /// - /// + /// The total number of samples that were lost due to a delayed packet. /// public double relativePacketArrivalDelay { get { return GetDouble("relativePacketArrivalDelay"); } } /// - /// + /// The total number of interruptions in the RTP stream. /// public ulong interruptionCount { get { return GetUnsignedLong("interruptionCount"); } } /// - /// + /// The total duration of interruptions in the RTP stream. /// public double totalInterruptionDuration { get { return GetDouble("totalInterruptionDuration"); } } /// - /// + /// The minimum playout delay for the RTP stream. /// public double minPlayoutDelay { get { return GetDouble("minPlayoutDelay"); } } @@ -1194,7 +1194,7 @@ internal RTCInboundRTPStreamStats(IntPtr ptr) : base(ptr) } /// - /// + /// Reports outbound RTP stream statistics. /// public class RTCOutboundRTPStreamStats : RTCSentRtpStreamStats { @@ -1356,7 +1356,7 @@ internal RTCOutboundRTPStreamStats(IntPtr ptr) : base(ptr) } /// - /// + /// Statistics for remote inbound RTP streams. /// public class RTCRemoteInboundRtpStreamStats : RTCReceivedRtpStreamStats { @@ -1391,7 +1391,7 @@ internal RTCRemoteInboundRtpStreamStats(IntPtr ptr) : base(ptr) } /// - /// + /// Statistics for remote outbound RTP streams. /// public class RTCRemoteOutboundRtpStreamStats : RTCSentRtpStreamStats { @@ -1431,7 +1431,7 @@ internal RTCRemoteOutboundRtpStreamStats(IntPtr ptr) : base(ptr) } /// - /// + /// Base class for media source statistics. /// public class RTCMediaSourceStats : RTCStats { @@ -1451,7 +1451,7 @@ internal RTCMediaSourceStats(IntPtr ptr) : base(ptr) } /// - /// + /// Audio source-specific statistics. /// public class RTCAudioSourceStats : RTCMediaSourceStats { @@ -1486,7 +1486,7 @@ internal RTCAudioSourceStats(IntPtr ptr) : base(ptr) } /// - /// + /// Video source-specific statistics. /// public class RTCVideoSourceStats : RTCMediaSourceStats { @@ -1520,7 +1520,7 @@ internal RTCVideoSourceStats(IntPtr ptr) : base(ptr) } /// - /// + /// Reports audio playout statistics. /// public class RTCAudioPlayoutStats : RTCStats { @@ -1560,7 +1560,7 @@ internal RTCAudioPlayoutStats(IntPtr ptr) : base(ptr) } /// - /// + /// Provides transport-level statistics for a connection. /// public class RTCTransportStats : RTCStats { @@ -1697,7 +1697,7 @@ public static RTCStats Create(RTCStatsType type, IntPtr ptr) } /// - /// + /// Represents a report containing multiple RTCStats objects. /// public class RTCStatsReport : IDisposable { @@ -1733,7 +1733,7 @@ internal RTCStatsReport(IntPtr ptr) } /// - /// + /// Finalizer to ensure resources are released. /// ~RTCStatsReport() { @@ -1741,7 +1741,7 @@ internal RTCStatsReport(IntPtr ptr) } /// - /// + /// Releases resources held by this report. /// public void Dispose() { @@ -1762,28 +1762,28 @@ public void Dispose() } /// - /// + /// Retrieves a stats object by its identifier. /// - /// - /// + /// Stats identifier. + /// The corresponding RTCStats object. public RTCStats Get(string id) { return m_dictStats[id]; } /// - /// + /// Attempts to get a stats object by its identifier. /// - /// - /// - /// + /// Stats identifier. + /// Output parameter for the stats object. + /// True if found, otherwise false. public bool TryGetValue(string id, out RTCStats stats) { return m_dictStats.TryGetValue(id, out stats); } /// - /// + /// Returns all stats objects in this report. /// public IDictionary Stats { diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RefCountedObject.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RefCountedObject.cs index 3f5722cf..efc2fa4d 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RefCountedObject.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RefCountedObject.cs @@ -3,11 +3,15 @@ namespace Unity.WebRTC { /// - /// + /// Base class for objects managed with reference and disposal. /// - public class RefCountedObject : IDisposable + public abstract class RefCountedObject : IDisposable { internal IntPtr self; + + /// + /// Indicates whether the object has been disposed. + /// internal protected bool disposed; internal RefCountedObject(IntPtr ptr) @@ -27,7 +31,7 @@ internal IntPtr GetSelfOrThrow() } /// - /// + /// Releases resources used by the object. /// public virtual void Dispose() { diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RenderTextureDepth.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RenderTextureDepth.cs index 280d8638..6c8beac0 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RenderTextureDepth.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/RenderTextureDepth.cs @@ -1,22 +1,22 @@ namespace Unity.WebRTC { /// - /// + /// Specifies the bit depth for a render texture's depth buffer. /// public enum RenderTextureDepth { /// - /// + /// 16-bit depth buffer. /// Depth16 = 16, /// - /// + /// 24-bit depth buffer. /// Depth24 = 24, /// - /// + /// 32-bit depth buffer. /// Depth32 = 32, } diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/VideoStreamTrack.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/VideoStreamTrack.cs index d036a6f5..6c2d800f 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/VideoStreamTrack.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/VideoStreamTrack.cs @@ -40,6 +40,7 @@ namespace Unity.WebRTC /// + /// /// /// public delegate void CopyTexture(Texture source, RenderTexture dest); @@ -502,7 +503,7 @@ static void OnVideoFrameResize(IntPtr ptrRenderer, int width, int height) } } - public static class CopyTextureHelper + internal static class CopyTextureHelper { // Blit parameter to flip vertically private static readonly Vector2 s_verticalScale = new Vector2(1f, -1f); diff --git a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/WebRTC.cs b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/WebRTC.cs index 3c81c1ad..e8028e95 100644 --- a/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/WebRTC.cs +++ b/Packages/StreamVideo/Runtime/Libs/io.stream.unity.webrtc/Runtime/Scripts/WebRTC.cs @@ -10,333 +10,333 @@ namespace Unity.WebRTC { /// - /// + /// Indicates a specific error detail encountered during WebRTC operations. /// public enum RTCErrorDetailType { /// - /// + /// Failure occurred in the data channel. /// DataChannelFailure, /// - /// + /// DTLS handshake or encryption failed. /// DtlsFailure, /// - /// + /// Fingerprint verification failed. /// FingerprintFailure, /// - /// + /// Identity provider script error. /// IdpBadScriptFailure, /// - /// + /// Identity provider execution error. /// IdpExecutionFailure, /// - /// + /// Identity provider could not be loaded. /// IdpLoadFailure, /// - /// + /// Identity provider requires user login. /// IdpNeedLogin, /// - /// + /// Identity provider operation timed out. /// IdpTimeout, /// - /// + /// TLS error occurred with identity provider. /// IdpTlsFailure, /// - /// + /// Identity provider token expired. /// IdpTokenExpired, /// - /// + /// Identity provider token is invalid. /// IdpTokenInvalid, /// - /// + /// SCTP transport failure. /// SctpFailure, /// - /// + /// SDP syntax is incorrect. /// SdpSyntaxError, /// - /// + /// Hardware encoder is not available. /// HardwareEncoderNotAvailable, /// - /// + /// Hardware encoder encountered an error. /// HardwareEncoderError } /// - /// + /// Represents an error returned from a WebRTC operation. /// public struct RTCError { /// - /// + /// Type of error encountered. /// public RTCErrorType errorType; /// - /// + /// Detailed error message. /// public string message; } /// - /// + /// Represents the state of a peer connection. /// /// public enum RTCPeerConnectionState : int { /// - /// + /// The initial state of the peer connection. /// New = 0, /// - /// + /// The peer connection is in the process of connecting. /// Connecting = 1, /// - /// + /// The peer connection has been established. /// Connected = 2, /// - /// + /// The peer connection has been disconnected. /// Disconnected = 3, /// - /// + /// The peer connection has failed. /// Failed = 4, /// - /// + /// The peer connection has been closed. /// Closed = 5 } /// - /// + /// Represents the ICE connection state for a peer connection. /// /// public enum RTCIceConnectionState : int { /// - /// + /// The ICE connection is in the initial state. /// New = 0, /// - /// + /// The ICE connection is in the process of checking candidates. /// Checking = 1, /// - /// + /// The ICE connection has been established. /// Connected = 2, /// - /// + /// The ICE connection has been completed. /// Completed = 3, /// - /// + /// The ICE connection has failed. /// Failed = 4, /// - /// + /// The ICE connection has been disconnected. /// Disconnected = 5, /// - /// + /// The ICE connection has been closed. /// Closed = 6, /// - /// + /// This is a sentinel value used for bounds checking. /// Max = 7 } /// - /// + /// Represents the ICE gathering state for a peer connection. /// /// public enum RTCIceGatheringState : int { /// - /// + /// The ICE gathering state is in the initial state. /// New = 0, /// - /// + /// The ICE gathering state is in the process of gathering candidates. /// Gathering = 1, /// - /// + /// The ICE gathering state has completed gathering candidates. /// Complete = 2 } /// - /// + /// Represents the signaling state for a peer connection. /// /// public enum RTCSignalingState : int { /// - /// + /// The signaling state is stable. /// Stable = 0, /// - /// + /// The signaling state has a local offer. /// HaveLocalOffer = 1, /// - /// + /// The signaling state has a local provisional answer. /// HaveLocalPrAnswer = 2, /// - /// + /// The signaling state has a remote offer. /// HaveRemoteOffer = 3, /// - /// + /// The signaling state has a remote provisional answer. /// HaveRemotePrAnswer = 4, /// - /// + /// The signaling state is closed. /// Closed = 5, } /// - /// + /// Represents the type of error returned from a WebRTC operation. /// public enum RTCErrorType { /// - /// + /// No error occurred. /// None, /// - /// + /// The operation is not supported. /// UnsupportedOperation, /// - /// + /// The parameter is not supported. /// UnsupportedParameter, /// - /// + /// The parameter is invalid. /// InvalidParameter, /// - /// + /// The object is in an invalid state for the requested operation. /// InvalidRange, /// - /// + /// The syntax of the operation is incorrect. /// SyntaxError, /// - /// + /// The object is in an invalid state for the requested operation. /// InvalidState, /// - /// + /// The operation is not allowed in the current context. /// InvalidModification, /// - /// + /// The network connection has failed. /// NetworkError, /// - /// + /// The operation was aborted. /// ResourceExhausted, /// - /// + /// An internal error occurred. /// InternalError, /// - /// + /// The operation failed. /// OperationErrorWithData } /// - /// + /// Represents the type of event for a peer connection. /// public enum RTCPeerConnectionEventType { /// - /// + /// The connection state has changed. /// ConnectionStateChange, /// - /// + /// A new data channel has been added. /// DataChannel, /// - /// + /// /// IceCandidate, /// - /// + /// /// IceConnectionStateChange, @@ -347,7 +347,7 @@ public enum RTCPeerConnectionEventType } /// - /// + /// Represents the type of SDP message. /// public enum RTCSdpType { @@ -511,7 +511,7 @@ public struct RTCSessionDescription } /// - /// + /// Options for creating an offer or answer. /// public struct RTCOfferAnswerOptions { @@ -555,7 +555,7 @@ public enum RTCIceCredentialType } /// - /// Represents a configuration for an ICE server used within WebRTC connections. + /// Represents the configuration for an ICE server used within WebRTC connections. /// /// /// Represents a configuration for an ICE server used within WebRTC connections, @@ -567,7 +567,7 @@ public enum RTCIceCredentialType /// configuration.iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } }; /// ]]> /// - /// /// + /// [Serializable] public struct RTCIceServer { @@ -685,32 +685,32 @@ struct RTCConfigurationInternal } /// - /// + /// Represents the severity level for native logging output. /// public enum NativeLoggingSeverity { /// - /// + /// Verbose logging, includes all details. /// Verbose, /// - /// + /// Informational messages only. /// Info, /// - /// + /// Warnings about potential issues. /// Warning, /// - /// + /// Error messages indicating failures. /// Error, /// - /// + /// Disables native logging output. /// None, }; @@ -916,7 +916,7 @@ public static void ConfigureNativeLogging(bool enableNativeLogging, NativeLoggin NativeMethods.RegisterDebugLog(null, false, nativeLoggingSeverity); } } -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR /// /// Sets the graphics sync timeout. @@ -1129,7 +1129,7 @@ public static TextureFormat GetSupportedTextureFormat(GraphicsDeviceType type) var graphicsFormat = GetSupportedGraphicsFormat(type); return GraphicsFormatUtility.GetTextureFormat(graphicsFormat); } -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR public static void SetAudioProcessingModule(bool enabled, bool echoCancellationEnabled, bool autoGainEnabled, bool noiseSuppressionEnabled, int noiseSuppressionLevel) { @@ -1141,9 +1141,13 @@ public static void GetAudioProcessingModuleConfig(out bool enabled, out bool ech WebRTC.Context.GetAudioProcessingModuleConfig(out enabled, out echoCancellationEnabled, out autoGainEnabled, out noiseSuppressionEnabled, out noiseSuppressionLevel); } - public static void StartAudioPlayback(int sampleRate, int numChannels) + // Channel count is intentionally not a parameter. WebRTC's voice pipeline (APM, mixer, + // sinks) is mono/stereo only, and this SDK's playback path is hard-pinned to stereo + // end-to-end. Exposing channels as a public knob would create the illusion of + // configurability while breaking audio for any value other than 2. + public static void StartAudioPlayback(int sampleRate) { - Context.StartAudioPlayback(sampleRate, numChannels); + Context.StartAudioPlayback(sampleRate); } public static void StopAudioPlayback() @@ -1151,14 +1155,14 @@ public static void StopAudioPlayback() Context.StopAudioPlayback(); } - public static void MuteAndroidAudioPlayback() + public static void MuteAudioPlayback() { - Context.MuteAndroidAudioPlayback(); + Context.MuteAudioPlayback(); } - public static void UnmuteAndroidAudioPlayback() + public static void UnmuteAudioPlayback() { - Context.UnmuteAndroidAudioPlayback(); + Context.UnmuteAudioPlayback(); } /// @@ -1736,7 +1740,7 @@ public static extern void RegisterDebugLog(DelegateDebugLog func, [MarshalAs(Unm [DllImport(WebRTC.Lib)] public static extern void AudioTrackSinkProcessAudio( IntPtr sink, float[] data, int length, int channels, int sampleRate); -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR [DllImport(WebRTC.Lib)] public static extern void AudioTrackSinkMute(IntPtr sink); [DllImport(WebRTC.Lib)] @@ -1864,7 +1868,7 @@ public static extern IntPtr CreateVideoRenderer( [DllImport(WebRTC.Lib)] public static extern void FrameTransformerSendFrameToSink(IntPtr transform, IntPtr frame); -#if UNITY_ANDROID && !UNITY_EDITOR +#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR [DllImport(WebRTC.Lib)] public static extern void SetGraphicsSyncTimeout(uint nSecTimeout); @@ -1878,16 +1882,16 @@ public static extern IntPtr CreateVideoRenderer( public static extern void StopAudioCapture(IntPtr context, IntPtr track); [DllImport(WebRTC.Lib)] - public static extern void StartAudioPlayback(IntPtr context, int sampleRate, int numChannels); + public static extern void StartAudioPlayback(IntPtr context, int sampleRate); [DllImport(WebRTC.Lib)] public static extern void StopAudioPlayback(IntPtr context); [DllImport(WebRTC.Lib)] - public static extern void MuteAndroidAudioPlayback(IntPtr context); + public static extern void MuteAudioPlayback(IntPtr context); [DllImport(WebRTC.Lib)] - public static extern void UnmuteAndroidAudioPlayback(IntPtr context); + public static extern void UnmuteAudioPlayback(IntPtr context); [DllImport(WebRTC.Lib)] public static extern void SetAudioProcessingModule(IntPtr context, bool enabled, bool echoCancellationEnabled, bool autoGainEnabled, bool noiseSuppressionEnabled, int noiseSuppressionLevel); diff --git a/Packages/StreamVideo/Samples~/VideoChat/Scripts/StreamVideoManager.cs b/Packages/StreamVideo/Samples~/VideoChat/Scripts/StreamVideoManager.cs index ce016658..6e7021c7 100644 --- a/Packages/StreamVideo/Samples~/VideoChat/Scripts/StreamVideoManager.cs +++ b/Packages/StreamVideo/Samples~/VideoChat/Scripts/StreamVideoManager.cs @@ -216,7 +216,7 @@ protected void OnApplicationPause(bool pauseStatus) if (pauseStatus) { // App is going to background - Client.PauseAndroidAudioPlayback(); + Client.PauseMobileAudioPlayback(); _wasAudioPublishEnabledOnPause = Client.AudioDeviceManager.IsEnabled; _wasVideoPublishEnabledOnPause = Client.VideoDeviceManager.IsEnabled; @@ -226,7 +226,7 @@ protected void OnApplicationPause(bool pauseStatus) else { // App is coming to foreground - Client.ResumeAndroidAudioPlayback(); + Client.ResumeMobileAudioPlayback(); if (_wasAudioPublishEnabledOnPause) {