diff --git a/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs b/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs
index a1d5f182..af8f0b3a 100644
--- a/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs
+++ b/Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs
@@ -38,6 +38,7 @@ public interface IStreamVideoClient : IStreamVideoClientEventsListener, IDisposa
///
/// Currently ongoing call session. This will be NULL if there's no call active.
/// You can subscribe to and events to get notified when a call is started/ended.
+ /// The client can only be in a single call at a time.
///
IStreamCall ActiveCall { get; }
@@ -76,7 +77,7 @@ Task JoinCallAsync(StreamCallType callType, string callId, bool cre
bool notify);
///
- /// Will return null if the call doesn't exist
+ /// Gets call information without joining it. Will return null if the call doesn't exist
///
Task GetCallAsync(StreamCallType callType, string callId);
diff --git a/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs b/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs
index d8c18439..8ed29a77 100644
--- a/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs
+++ b/Packages/StreamVideo/Runtime/Core/Models/CallSession.cs
@@ -71,8 +71,14 @@ void IStateLoadableFrom.LoadFromDto(SfuCallState dto,
}
// dto.CallState.Participants may not contain all participants
- UpdateExtensions.TryAddUniqueTrackedObjects(_participants,
- dto.Participants, cache.CallParticipants);
+ foreach (var dtoParticipant in dto.Participants)
+ {
+ var participant = cache.TryCreateOrUpdate(dtoParticipant);
+ if (!_participants.Contains(participant))
+ {
+ _participants.Add(participant);
+ }
+ }
((IStateLoadableFrom)ParticipantCount).LoadFromDto(
dto.ParticipantCount, cache);
diff --git a/Packages/StreamVideo/Runtime/Core/StatefulModels/IStreamCall.cs b/Packages/StreamVideo/Runtime/Core/StatefulModels/IStreamCall.cs
index b7c94a71..7d9adb24 100644
--- a/Packages/StreamVideo/Runtime/Core/StatefulModels/IStreamCall.cs
+++ b/Packages/StreamVideo/Runtime/Core/StatefulModels/IStreamCall.cs
@@ -337,5 +337,10 @@ Task QueryMembersAsync(IEnumerable filters
///
/// True if participant is pinned remotely
bool IsPinned(IStreamVideoCallParticipant participant);
+
+ ///
+ /// Helper function to get the local participant object for the current user in this call
+ ///
+ IStreamVideoCallParticipant GetLocalParticipant();
}
}
\ No newline at end of file
diff --git a/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs b/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs
index e45a7903..5f3282b2 100644
--- a/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs
+++ b/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs
@@ -441,6 +441,49 @@ public Task UploadParticipantCustomDataAsync(IStreamVideoCallParticipant partici
return UploadCustomDataAsync();
}
+ public IStreamVideoCallParticipant GetLocalParticipant()
+ {
+ if (Participants.Count == 0)
+ {
+ using (new StringBuilderPoolScope(out var tempSb))
+ {
+ tempSb.AppendLine($"{nameof(GetLocalParticipant)} - no participants in the call.");
+ tempSb.AppendLine("Last operations leading to this state:");
+ foreach (var log in _tempLogs.GetLogs())
+ {
+ tempSb.AppendLine(log);
+ }
+
+ Logs.Error(tempSb.ToString());
+ }
+ throw new InvalidOperationException("No participants in the call.");
+ }
+
+ var localParticipant = Participants.FirstOrDefault(p => p.IsLocalParticipant);
+ if (localParticipant == null)
+ {
+ using (new StringBuilderPoolScope(out var sb))
+ {
+ var currentSessionId = LowLevelClient.RtcSession.SessionId;
+ sb.AppendLine($"Local participant not found. Local Session ID: {currentSessionId}. Participants in the call:");
+ foreach (var p in Participants)
+ {
+ sb.AppendLine($" - UserId: {p.UserId}, SessionId: {p.SessionId}, IsLocalParticipant: {p.IsLocalParticipant}");
+ }
+
+ sb.AppendLine("Last operations leading to this state:");
+ foreach (var log in _tempLogs.GetLogs())
+ {
+ sb.AppendLine(log);
+ }
+
+ Logs.Error(sb.ToString());
+ }
+ }
+
+ return localParticipant;
+ }
+
void IUpdateableFrom.UpdateFromDto(CallResponseInternalDTO dto,
ICache cache)
{
@@ -463,6 +506,29 @@ void IUpdateableFrom.UpdateFromDto(CallResp
Type = new StreamCallType(dto.Type);
UpdatedAt = dto.UpdatedAt;
+ try
+ {
+ // Ignore the IDE warning, this can be null
+ if (dto.Session != null)
+ {
+ using (new StringBuilderPoolScope(out var tempSb))
+ {
+ tempSb.Append($"`UpdateFromDto(CallResponseInternalDTO dto` - dto participants: {dto.Session.Participants?.Count}, call participants: {Session.Participants.Count}. Dto participants: ");
+ foreach (var p in dto.Session.Participants)
+ {
+ tempSb.Append($"[UserSessionId: {p.UserSessionId}, SessionId: {p.User?.Id}");
+ }
+
+ _tempLogs.Add(tempSb.ToString());
+ }
+ }
+
+ }
+ catch (Exception e)
+ {
+ Logs.Exception(e);
+ }
+
// Depends on Session.Participants so load as last
LoadCustomData(dto.Custom);
}
@@ -531,6 +597,26 @@ internal void UpdateFromSfu(JoinResponse joinResponse)
{
((IStateLoadableFrom)Session).LoadFromDto(joinResponse.CallState, Cache);
UpdateServerPins(joinResponse.CallState.Pins);
+
+ try
+ {
+ using (new StringBuilderPoolScope(out var tempSb))
+ {
+ tempSb.Append("`UpdateFromSfu(JoinResponse joinResponse)` - joinResponse participants: ");
+ if(joinResponse.CallState !=null && joinResponse.CallState.Participants != null)
+ {
+ foreach (var p in joinResponse.CallState.Participants)
+ {
+ tempSb.Append($"[UserId: {p.UserId}, SessionId: {p.SessionId}, ");
+ }
+ }
+ _tempLogs.Add(tempSb.ToString());
+ }
+ }
+ catch (Exception e)
+ {
+ Logs.Exception(e);
+ }
}
internal void UpdateFromSfu(ParticipantJoined participantJoined, ICache cache)
@@ -750,6 +836,8 @@ private readonly List
private readonly Dictionary> _capabilitiesByRole = new Dictionary>();
+ private readonly DebugLogBuffer _tempLogs = new DebugLogBuffer();
+
#endregion
private readonly StreamCallType _type;
diff --git a/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamVideoCallParticipant.cs b/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamVideoCallParticipant.cs
index 56098acd..10641560 100644
--- a/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamVideoCallParticipant.cs
+++ b/Packages/StreamVideo/Runtime/Core/StatefulModels/StreamVideoCallParticipant.cs
@@ -23,7 +23,7 @@ internal sealed class StreamVideoCallParticipant : StreamStatefulModelBase UserSessionId == Client.InternalLowLevelClient.RtcSession.SessionId;
+ public bool IsLocalParticipant => SessionId == Client.InternalLowLevelClient.RtcSession.SessionId;
public bool IsPinned { get; private set; }
@@ -49,26 +49,6 @@ internal sealed class StreamVideoCallParticipant : StreamStatefulModelBase _userSessionId;
- private set
- {
- Logs.WarningIfDebug(
- $"UserSessionId set to: {value} for participant: {UserId} and Session ID: {SessionId}");
- _userSessionId = value;
-
- if (string.IsNullOrEmpty(SessionId))
- {
- SessionId = value;
- }
- }
- }
-
- private string _userSessionId;
-
#endregion
#region Sfu State
@@ -155,18 +135,13 @@ void IUpdateableFrom check if this is possible
-
//StreamTodo: verify if we're using every piece of data received from API/SFU responses to update this object
}
@@ -275,8 +250,8 @@ internal void NotifyTrackEnabled(TrackType type, bool enabled)
protected override string InternalUniqueId
{
- get => UserSessionId;
- set => UserSessionId = value;
+ get => SessionId;
+ set => SessionId = value;
}
protected override StreamVideoCallParticipant Self => this;
diff --git a/Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs b/Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs
new file mode 100644
index 00000000..49f07dc7
--- /dev/null
+++ b/Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+namespace StreamVideo.Core.Utils
+{
+ internal class DebugLogBuffer
+ {
+ private const int MaxSize = 10;
+ private readonly string[] _buffer = new string[MaxSize];
+ private int _index;
+ private int _count;
+
+ public void Add(string msg)
+ {
+ _buffer[_index] = msg;
+ _index = (_index + 1) % MaxSize;
+ if (_count < MaxSize) _count++;
+ }
+
+ public IEnumerable GetLogs()
+ {
+ for (var i = 0; i < _count; i++)
+ {
+ var idx = (_index - _count + i + MaxSize) % MaxSize;
+ yield return _buffer[idx];
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs.meta b/Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs.meta
new file mode 100644
index 00000000..6e737b95
--- /dev/null
+++ b/Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 739bff57312b4ac886f7e44d137822cc
+timeCreated: 1763907152
\ No newline at end of file
diff --git a/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs
new file mode 100644
index 00000000..cd6472ce
--- /dev/null
+++ b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace StreamVideo.Core.Utils
+{
+ internal static class StringBuilderPool
+ {
+ public static StringBuilder Rent()
+ {
+ if (Pool.Count > 0)
+ {
+ var sb = Pool.Pop();
+ sb.Clear();
+ return sb;
+ }
+
+ return new StringBuilder();
+ }
+
+ public static void Release(StringBuilder sb)
+ {
+ if (sb == null)
+ {
+ throw new ArgumentNullException(nameof(sb));
+ }
+
+ sb.Clear();
+
+ if (Pool.Count < MaxPoolSize)
+ {
+ Pool.Push(sb);
+ }
+ }
+
+ private const int MaxPoolSize = 128;
+ private static readonly Stack Pool = new Stack();
+ }
+}
\ No newline at end of file
diff --git a/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs.meta b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs.meta
new file mode 100644
index 00000000..e45a3199
--- /dev/null
+++ b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1232b18c26cb4f8aa805b41dec9e5009
+timeCreated: 1763909310
\ No newline at end of file
diff --git a/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPoolScope.cs b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPoolScope.cs
new file mode 100644
index 00000000..381d5f39
--- /dev/null
+++ b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPoolScope.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Text;
+
+namespace StreamVideo.Core.Utils
+{
+ internal readonly struct StringBuilderPoolScope : IDisposable
+ {
+ public StringBuilderPoolScope(out StringBuilder sb)
+ {
+ _sb = StringBuilderPool.Rent();
+ sb = _sb;
+ }
+
+ public void Dispose()
+ {
+ if (_sb != null)
+ {
+ StringBuilderPool.Release(_sb);
+ }
+ }
+
+ private readonly StringBuilder _sb;
+ }
+}
\ No newline at end of file
diff --git a/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPoolScope.cs.meta b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPoolScope.cs.meta
new file mode 100644
index 00000000..80e4e0ab
--- /dev/null
+++ b/Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPoolScope.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3aa3051826ba46978d388f6796567cf3
+timeCreated: 1763909332
\ No newline at end of file