Skip to content

Commit a02fdac

Browse files
authored
Merge pull request #180 from GetStream/feature/uni-134-cleanup-user-session-id-handling
Feature/uni 134 cleanup user session id handling
2 parents 6a51a40 + 5dec5d4 commit a02fdac

File tree

11 files changed

+207
-32
lines changed

11 files changed

+207
-32
lines changed

Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public interface IStreamVideoClient : IStreamVideoClientEventsListener, IDisposa
3838
/// <summary>
3939
/// Currently ongoing call session. This will be NULL if there's no call active.
4040
/// You can subscribe to <see cref="CallStarted"/> and <see cref="CallEnded"/> events to get notified when a call is started/ended.
41+
/// The client can only be in a single call at a time.
4142
/// </summary>
4243
IStreamCall ActiveCall { get; }
4344

@@ -76,7 +77,7 @@ Task<IStreamCall> JoinCallAsync(StreamCallType callType, string callId, bool cre
7677
bool notify);
7778

7879
/// <summary>
79-
/// Will return null if the call doesn't exist
80+
/// Gets call information without joining it. Will return null if the call doesn't exist
8081
/// </summary>
8182
Task<IStreamCall> GetCallAsync(StreamCallType callType, string callId);
8283

Packages/StreamVideo/Runtime/Core/Models/CallSession.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,14 @@ void IStateLoadableFrom<SfuCallState, CallSession>.LoadFromDto(SfuCallState dto,
7171
}
7272

7373
// dto.CallState.Participants may not contain all participants
74-
UpdateExtensions<StreamVideoCallParticipant, SfuParticipant>.TryAddUniqueTrackedObjects(_participants,
75-
dto.Participants, cache.CallParticipants);
74+
foreach (var dtoParticipant in dto.Participants)
75+
{
76+
var participant = cache.TryCreateOrUpdate(dtoParticipant);
77+
if (!_participants.Contains(participant))
78+
{
79+
_participants.Add(participant);
80+
}
81+
}
7682

7783
((IStateLoadableFrom<SfuParticipantCount, ParticipantCount>)ParticipantCount).LoadFromDto(
7884
dto.ParticipantCount, cache);

Packages/StreamVideo/Runtime/Core/StatefulModels/IStreamCall.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,5 +337,10 @@ Task<QueryMembersResult> QueryMembersAsync(IEnumerable<IFieldFilterRule> filters
337337
/// </summary>
338338
/// <returns>True if participant is pinned remotely</returns>
339339
bool IsPinned(IStreamVideoCallParticipant participant);
340+
341+
/// <summary>
342+
/// Helper function to get the local participant object for the current user in this call
343+
/// </summary>
344+
IStreamVideoCallParticipant GetLocalParticipant();
340345
}
341346
}

Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,49 @@ public Task UploadParticipantCustomDataAsync(IStreamVideoCallParticipant partici
441441
return UploadCustomDataAsync();
442442
}
443443

444+
public IStreamVideoCallParticipant GetLocalParticipant()
445+
{
446+
if (Participants.Count == 0)
447+
{
448+
using (new StringBuilderPoolScope(out var tempSb))
449+
{
450+
tempSb.AppendLine($"{nameof(GetLocalParticipant)} - no participants in the call.");
451+
tempSb.AppendLine("Last operations leading to this state:");
452+
foreach (var log in _tempLogs.GetLogs())
453+
{
454+
tempSb.AppendLine(log);
455+
}
456+
457+
Logs.Error(tempSb.ToString());
458+
}
459+
throw new InvalidOperationException("No participants in the call.");
460+
}
461+
462+
var localParticipant = Participants.FirstOrDefault(p => p.IsLocalParticipant);
463+
if (localParticipant == null)
464+
{
465+
using (new StringBuilderPoolScope(out var sb))
466+
{
467+
var currentSessionId = LowLevelClient.RtcSession.SessionId;
468+
sb.AppendLine($"Local participant not found. Local Session ID: {currentSessionId}. Participants in the call:");
469+
foreach (var p in Participants)
470+
{
471+
sb.AppendLine($" - UserId: {p.UserId}, SessionId: {p.SessionId}, IsLocalParticipant: {p.IsLocalParticipant}");
472+
}
473+
474+
sb.AppendLine("Last operations leading to this state:");
475+
foreach (var log in _tempLogs.GetLogs())
476+
{
477+
sb.AppendLine(log);
478+
}
479+
480+
Logs.Error(sb.ToString());
481+
}
482+
}
483+
484+
return localParticipant;
485+
}
486+
444487
void IUpdateableFrom<CallResponseInternalDTO, StreamCall>.UpdateFromDto(CallResponseInternalDTO dto,
445488
ICache cache)
446489
{
@@ -463,6 +506,29 @@ void IUpdateableFrom<CallResponseInternalDTO, StreamCall>.UpdateFromDto(CallResp
463506
Type = new StreamCallType(dto.Type);
464507
UpdatedAt = dto.UpdatedAt;
465508

509+
try
510+
{
511+
// Ignore the IDE warning, this can be null
512+
if (dto.Session != null)
513+
{
514+
using (new StringBuilderPoolScope(out var tempSb))
515+
{
516+
tempSb.Append($"`UpdateFromDto(CallResponseInternalDTO dto` - dto participants: {dto.Session.Participants?.Count}, call participants: {Session.Participants.Count}. Dto participants: ");
517+
foreach (var p in dto.Session.Participants)
518+
{
519+
tempSb.Append($"[UserSessionId: {p.UserSessionId}, SessionId: {p.User?.Id}");
520+
}
521+
522+
_tempLogs.Add(tempSb.ToString());
523+
}
524+
}
525+
526+
}
527+
catch (Exception e)
528+
{
529+
Logs.Exception(e);
530+
}
531+
466532
// Depends on Session.Participants so load as last
467533
LoadCustomData(dto.Custom);
468534
}
@@ -531,6 +597,26 @@ internal void UpdateFromSfu(JoinResponse joinResponse)
531597
{
532598
((IStateLoadableFrom<CallState, CallSession>)Session).LoadFromDto(joinResponse.CallState, Cache);
533599
UpdateServerPins(joinResponse.CallState.Pins);
600+
601+
try
602+
{
603+
using (new StringBuilderPoolScope(out var tempSb))
604+
{
605+
tempSb.Append("`UpdateFromSfu(JoinResponse joinResponse)` - joinResponse participants: ");
606+
if(joinResponse.CallState !=null && joinResponse.CallState.Participants != null)
607+
{
608+
foreach (var p in joinResponse.CallState.Participants)
609+
{
610+
tempSb.Append($"[UserId: {p.UserId}, SessionId: {p.SessionId}, ");
611+
}
612+
}
613+
_tempLogs.Add(tempSb.ToString());
614+
}
615+
}
616+
catch (Exception e)
617+
{
618+
Logs.Exception(e);
619+
}
534620
}
535621

536622
internal void UpdateFromSfu(ParticipantJoined participantJoined, ICache cache)
@@ -750,6 +836,8 @@ private readonly List<IStreamVideoCallParticipant>
750836

751837
private readonly Dictionary<string, List<string>> _capabilitiesByRole = new Dictionary<string, List<string>>();
752838

839+
private readonly DebugLogBuffer _tempLogs = new DebugLogBuffer();
840+
753841
#endregion
754842

755843
private readonly StreamCallType _type;

Packages/StreamVideo/Runtime/Core/StatefulModels/StreamVideoCallParticipant.cs

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal sealed class StreamVideoCallParticipant : StreamStatefulModelBase<Strea
2323
public event ParticipantTrackChangedHandler TrackAdded;
2424
public event ParticipantTrackChangedHandler TrackIsEnabledChanged;
2525

26-
public bool IsLocalParticipant => UserSessionId == Client.InternalLowLevelClient.RtcSession.SessionId;
26+
public bool IsLocalParticipant => SessionId == Client.InternalLowLevelClient.RtcSession.SessionId;
2727

2828
public bool IsPinned { get; private set; }
2929

@@ -49,26 +49,6 @@ internal sealed class StreamVideoCallParticipant : StreamStatefulModelBase<Strea
4949

5050
public IStreamVideoUser User { get; set; }
5151

52-
//StreamTODO: investigate why we have UserSessionID and SessionId. On a user that was joining the call I had null SessionId while UserSessionId had value
53-
// They probably represent the same thing and one is set by coordinator and the other by SFU but let's verify
54-
public string UserSessionId
55-
{
56-
get => _userSessionId;
57-
private set
58-
{
59-
Logs.WarningIfDebug(
60-
$"UserSessionId set to: {value} for participant: {UserId} and Session ID: {SessionId}");
61-
_userSessionId = value;
62-
63-
if (string.IsNullOrEmpty(SessionId))
64-
{
65-
SessionId = value;
66-
}
67-
}
68-
}
69-
70-
private string _userSessionId;
71-
7252
#endregion
7353

7454
#region Sfu State
@@ -155,18 +135,13 @@ void IUpdateableFrom<CallParticipantResponseInternalDTO, StreamVideoCallParticip
155135
JoinedAt = dto.JoinedAt;
156136
Role = dto.Role;
157137
User = cache.TryCreateOrUpdate(dto.User);
158-
UserSessionId = dto.UserSessionId;
138+
SessionId = dto.UserSessionId;
159139

160140
if (string.IsNullOrEmpty(UserId) && User != null)
161141
{
162142
UserId = User.Id;
163143
}
164144

165-
//StreamTodo: investigate why we either update UserId or User object
166-
//Depending on the update source we'll end up with one being empty
167-
//We can take UserId from user obj, but we can't easily get User obj give UserId in dto
168-
//Ideally, we'd only expose the User object -> check if this is possible
169-
170145
//StreamTodo: verify if we're using every piece of data received from API/SFU responses to update this object
171146
}
172147

@@ -275,8 +250,8 @@ internal void NotifyTrackEnabled(TrackType type, bool enabled)
275250

276251
protected override string InternalUniqueId
277252
{
278-
get => UserSessionId;
279-
set => UserSessionId = value;
253+
get => SessionId;
254+
set => SessionId = value;
280255
}
281256

282257
protected override StreamVideoCallParticipant Self => this;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Generic;
2+
3+
namespace StreamVideo.Core.Utils
4+
{
5+
internal class DebugLogBuffer
6+
{
7+
private const int MaxSize = 10;
8+
private readonly string[] _buffer = new string[MaxSize];
9+
private int _index;
10+
private int _count;
11+
12+
public void Add(string msg)
13+
{
14+
_buffer[_index] = msg;
15+
_index = (_index + 1) % MaxSize;
16+
if (_count < MaxSize) _count++;
17+
}
18+
19+
public IEnumerable<string> GetLogs()
20+
{
21+
for (var i = 0; i < _count; i++)
22+
{
23+
var idx = (_index - _count + i + MaxSize) % MaxSize;
24+
yield return _buffer[idx];
25+
}
26+
}
27+
}
28+
}

Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace StreamVideo.Core.Utils
6+
{
7+
internal static class StringBuilderPool
8+
{
9+
public static StringBuilder Rent()
10+
{
11+
if (Pool.Count > 0)
12+
{
13+
var sb = Pool.Pop();
14+
sb.Clear();
15+
return sb;
16+
}
17+
18+
return new StringBuilder();
19+
}
20+
21+
public static void Release(StringBuilder sb)
22+
{
23+
if (sb == null)
24+
{
25+
throw new ArgumentNullException(nameof(sb));
26+
}
27+
28+
sb.Clear();
29+
30+
if (Pool.Count < MaxPoolSize)
31+
{
32+
Pool.Push(sb);
33+
}
34+
}
35+
36+
private const int MaxPoolSize = 128;
37+
private static readonly Stack<StringBuilder> Pool = new Stack<StringBuilder>();
38+
}
39+
}

Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace StreamVideo.Core.Utils
5+
{
6+
internal readonly struct StringBuilderPoolScope : IDisposable
7+
{
8+
public StringBuilderPoolScope(out StringBuilder sb)
9+
{
10+
_sb = StringBuilderPool.Rent();
11+
sb = _sb;
12+
}
13+
14+
public void Dispose()
15+
{
16+
if (_sb != null)
17+
{
18+
StringBuilderPool.Release(_sb);
19+
}
20+
}
21+
22+
private readonly StringBuilder _sb;
23+
}
24+
}

0 commit comments

Comments
 (0)