Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Packages/StreamVideo/Runtime/Core/IStreamVideoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public interface IStreamVideoClient : IStreamVideoClientEventsListener, IDisposa
/// <summary>
/// Currently ongoing call session. This will be NULL if there's no call active.
/// You can subscribe to <see cref="CallStarted"/> and <see cref="CallEnded"/> events to get notified when a call is started/ended.
/// The client can only be in a single call at a time.
/// </summary>
IStreamCall ActiveCall { get; }

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

/// <summary>
/// Will return null if the call doesn't exist
/// Gets call information without joining it. Will return null if the call doesn't exist
/// </summary>
Task<IStreamCall> GetCallAsync(StreamCallType callType, string callId);

Expand Down
10 changes: 8 additions & 2 deletions Packages/StreamVideo/Runtime/Core/Models/CallSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,14 @@ void IStateLoadableFrom<SfuCallState, CallSession>.LoadFromDto(SfuCallState dto,
}

// dto.CallState.Participants may not contain all participants
UpdateExtensions<StreamVideoCallParticipant, SfuParticipant>.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<SfuParticipantCount, ParticipantCount>)ParticipantCount).LoadFromDto(
dto.ParticipantCount, cache);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,5 +337,10 @@ Task<QueryMembersResult> QueryMembersAsync(IEnumerable<IFieldFilterRule> filters
/// </summary>
/// <returns>True if participant is pinned remotely</returns>
bool IsPinned(IStreamVideoCallParticipant participant);

/// <summary>
/// Helper function to get the local participant object for the current user in this call
/// </summary>
IStreamVideoCallParticipant GetLocalParticipant();
}
}
88 changes: 88 additions & 0 deletions Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CallResponseInternalDTO, StreamCall>.UpdateFromDto(CallResponseInternalDTO dto,
ICache cache)
{
Expand All @@ -463,6 +506,29 @@ void IUpdateableFrom<CallResponseInternalDTO, StreamCall>.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);
}
Expand Down Expand Up @@ -531,6 +597,26 @@ internal void UpdateFromSfu(JoinResponse joinResponse)
{
((IStateLoadableFrom<CallState, CallSession>)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)
Expand Down Expand Up @@ -750,6 +836,8 @@ private readonly List<IStreamVideoCallParticipant>

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

private readonly DebugLogBuffer _tempLogs = new DebugLogBuffer();

#endregion

private readonly StreamCallType _type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal sealed class StreamVideoCallParticipant : StreamStatefulModelBase<Strea
public event ParticipantTrackChangedHandler TrackAdded;
public event ParticipantTrackChangedHandler TrackIsEnabledChanged;

public bool IsLocalParticipant => UserSessionId == Client.InternalLowLevelClient.RtcSession.SessionId;
public bool IsLocalParticipant => SessionId == Client.InternalLowLevelClient.RtcSession.SessionId;

public bool IsPinned { get; private set; }

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

public IStreamVideoUser User { get; set; }

//StreamTODO: investigate why we have UserSessionID and SessionId. On a user that was joining the call I had null SessionId while UserSessionId had value
// They probably represent the same thing and one is set by coordinator and the other by SFU but let's verify
public string UserSessionId
{
get => _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
Expand Down Expand Up @@ -155,18 +135,13 @@ void IUpdateableFrom<CallParticipantResponseInternalDTO, StreamVideoCallParticip
JoinedAt = dto.JoinedAt;
Role = dto.Role;
User = cache.TryCreateOrUpdate(dto.User);
UserSessionId = dto.UserSessionId;
SessionId = dto.UserSessionId;

if (string.IsNullOrEmpty(UserId) && User != null)
{
UserId = User.Id;
}

//StreamTodo: investigate why we either update UserId or User object
//Depending on the update source we'll end up with one being empty
//We can take UserId from user obj, but we can't easily get User obj give UserId in dto
//Ideally, we'd only expose the User object -> check if this is possible

//StreamTodo: verify if we're using every piece of data received from API/SFU responses to update this object
}

Expand Down Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs
Original file line number Diff line number Diff line change
@@ -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<string> GetLogs()
{
for (var i = 0; i < _count; i++)
{
var idx = (_index - _count + i + MaxSize) % MaxSize;
yield return _buffer[idx];
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPool.cs
Original file line number Diff line number Diff line change
@@ -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<StringBuilder> Pool = new Stack<StringBuilder>();
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions Packages/StreamVideo/Runtime/Core/Utils/StringBuilderPoolScope.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading