Skip to content

Commit

Permalink
Merge pull request #52 from GetStream/feature/implement-sorted-partic…
Browse files Browse the repository at this point in the history
…ipants

Implement IStreamCall.SortedParticipants
  • Loading branch information
sierpinskid authored Jan 10, 2024
2 parents 27c64a2 + 0037cfd commit 9b5c210
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 27 deletions.
50 changes: 50 additions & 0 deletions Packages/StreamVideo/Runtime/Core/CallParticipantComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using StreamVideo.Core.StatefulModels;

namespace Core
{
/// <summary>
/// User for sorting of <see cref="IStreamCall.SortedParticipants"/>
/// </summary>
internal class CallParticipantComparer : IComparer<IStreamVideoCallParticipant>
{
public bool OrderChanged { get; private set; }

public int Compare(IStreamVideoCallParticipant x, IStreamVideoCallParticipant y)
{
var result = InternalCompare(x, y);
if (result != 0)
{
OrderChanged = true;
}

return result;
}

public void Reset() => OrderChanged = false;

private int InternalCompare(IStreamVideoCallParticipant x, IStreamVideoCallParticipant y)
{
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;

if (x.IsPinned && !y.IsPinned) return -1;
if (!x.IsPinned && y.IsPinned) return 1;

if (x.IsScreenSharing && !y.IsScreenSharing) return -1;
if (!x.IsScreenSharing && y.IsScreenSharing) return 1;

if (x.IsDominantSpeaker && !y.IsDominantSpeaker) return -1;
if (!x.IsDominantSpeaker && y.IsDominantSpeaker) return 1;

if (x.IsVideoEnabled && !y.IsVideoEnabled) return -1;
if (!x.IsVideoEnabled && y.IsVideoEnabled) return 1;

if (x.IsAudioEnabled && !y.IsAudioEnabled) return -1;
if (!x.IsAudioEnabled && y.IsAudioEnabled) return 1;

return x.JoinedAt.CompareTo(y.JoinedAt); // Earlier joiners first
}
}
}

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

18 changes: 10 additions & 8 deletions Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,13 @@ private void OnSfuTrackUnpublished(TrackUnpublished trackUnpublished)
var cause = trackUnpublished.Cause;

// Optionally available. Read TrackUnpublished.participant comment in events.proto
var participant = trackUnpublished.Participant;
var participantSfuDto = trackUnpublished.Participant;

UpdateParticipantTracksState(userId, sessionId, type, isEnabled: false, out var streamParticipant);
UpdateParticipantTracksState(userId, sessionId, type, isEnabled: false, out var participant);

if (participant != null && streamParticipant != null)
if (participantSfuDto != null && participant != null)
{
streamParticipant.UpdateFromSfu(participant);
participant.UpdateFromSfu(participantSfuDto);
}

//StreamTodo: raise an event so user can react to track unpublished? Otherwise the video will just freeze
Expand All @@ -542,13 +542,13 @@ private void OnSfuTrackPublished(TrackPublished trackPublished)
var type = trackPublished.Type.ToPublicEnum();

// Optionally available. Read TrackUnpublished.participant comment in events.proto
var participant = trackPublished.Participant;
var participantSfuDto = trackPublished.Participant;

UpdateParticipantTracksState(userId, sessionId, type, isEnabled: true, out var streamParticipant);
UpdateParticipantTracksState(userId, sessionId, type, isEnabled: true, out var participant);

if (participant != null && streamParticipant != null)
if (participantSfuDto != null && participant != null)
{
streamParticipant.UpdateFromSfu(participant);
participant.UpdateFromSfu(participantSfuDto);
}

//StreamTodo: fixes the case when joining a call where other participant starts with no video and activates video track after we've joined -
Expand All @@ -573,6 +573,8 @@ private void UpdateParticipantTracksState(string userId, string sessionId, Track
}

participant.SetTrackEnabled(trackType, isEnabled);

ActiveCall.NotifyTrackStateChanged(participant, trackType, isEnabled);
}

private void OnSfuParticipantJoined(ParticipantJoined participantJoined)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public interface IStreamCall : IStreamStatefulModel
/// <summary>
/// Notifies that the <see cref="SortedParticipants"/> collection was updated
/// </summary>
//event Action SortedParticipantsUpdated;
event Action SortedParticipantsUpdated;

Credentials Credentials { get; }

Expand Down Expand Up @@ -73,11 +73,11 @@ public interface IStreamCall : IStreamStatefulModel
/// - anyone who is pinned (locally pinned first, then remotely pinned)
/// - anyone who is screen-sharing
/// - dominant speaker
/// - all other video participants by when they joined
/// - audio only participants by when they joined
/// - all other video participants
/// - audio only participants
/// Any update to this collection will trigger the <see cref="SortedParticipantsUpdated"/> event.
/// </summary>
//IEnumerable<IStreamVideoCallParticipant> SortedParticipants { get; }
IEnumerable<IStreamVideoCallParticipant> SortedParticipants { get; }

IReadOnlyList<OwnCapability> OwnCapabilities { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,44 @@ namespace StreamVideo.Core.StatefulModels
public interface IStreamVideoCallParticipant : IStreamStatefulModel
{
event ParticipantTrackChangedHandler TrackAdded;


/// <summary>
/// Is this participant "pinned" in the call meaning it will have precedence in <see cref="IStreamCall.SortedParticipants"/> list
/// </summary>
bool IsPinned { get; }

/// <summary>
/// Is this participant currently streaming a screen share track
/// </summary>
bool IsScreenSharing { get; }

/// <summary>
/// Is this participant currently streaming a video track
/// </summary>
bool IsVideoEnabled { get; }

/// <summary>
/// Is this participant currently streaming an audio track
/// </summary>
bool IsAudioEnabled { get; }

/// <summary>
/// Is this participant currently the most actively speaking participant.
/// </summary>
bool IsDominantSpeaker { get; }
string UserId { get; }

/// <summary>
/// Session ID is a unique identifier for a <see cref="IStreamVideoUser"/> in a <see cref="IStreamCall"/>.
/// A single user can join a call through multiple devices therefore a single call can have multiple participants with the same <see cref="UserId"/>.
/// </summary>
string SessionId { get; }
string TrackLookupPrefix { get; }
string Name { get; }

/// <summary>
/// Is this the participant from this device
/// </summary>
bool IsLocalParticipant { get; }
IStreamVideoUser User { get; set; }
IStreamTrack VideoTrack { get; }
Expand All @@ -27,7 +60,6 @@ public interface IStreamVideoCallParticipant : IStreamStatefulModel
float AudioLevel { get; }
bool IsSpeaking { get; }
ConnectionQuality ConnectionQuality { get; }
bool IsDominantSpeaker { get; }

IEnumerable<IStreamTrack> GetTracks();
}
Expand Down
75 changes: 62 additions & 13 deletions Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Core;
using Core.Utils;
using Stream.Video.v1.Sfu.Events;
using Stream.Video.v1.Sfu.Models;
Expand All @@ -18,6 +19,7 @@
using StreamVideo.Core.StatefulModels;
using StreamVideo.Core.StatefulModels.Tracks;
using StreamVideo.Core.Utils;
using TrackType = StreamVideo.Core.Models.Sfu.TrackType;

namespace StreamVideo.Core
{
Expand Down Expand Up @@ -46,7 +48,7 @@ internal sealed class StreamCall : StreamStatefulModelBase<StreamCall>,

public event Action PinnedParticipantsUpdated;

//public event Action SortedParticipantsUpdated; //StreamTodo: implement
public event Action SortedParticipantsUpdated;

public event CallReactionAddedHandler ReactionAdded;

Expand Down Expand Up @@ -74,6 +76,7 @@ private set

if (prev != value)
{
UpdateSortedParticipants();
PreviousDominantSpeaker = prev;
DominantSpeakerChanged?.Invoke(value, prev);
}
Expand All @@ -83,7 +86,7 @@ private set
public IStreamVideoCallParticipant PreviousDominantSpeaker { get; private set; }

public IReadOnlyList<IStreamVideoCallParticipant> PinnedParticipants => _pinnedParticipants;
//public IEnumerable<IStreamVideoCallParticipant> SortedParticipants => _sortedParticipants;
public IEnumerable<IStreamVideoCallParticipant> SortedParticipants => _sortedParticipants;

#region State

Expand Down Expand Up @@ -365,16 +368,14 @@ public void PinLocally(IStreamVideoCallParticipant participant)
_localPinsSessionIds.Remove(participant.SessionId);
_localPinsSessionIds.AddFirst(participant.SessionId);

UpdatePinnedParticipants();
UpdateSortedParticipants();
UpdatePinnedParticipants(out _);
}

public void UnpinLocally(IStreamVideoCallParticipant participant)
{
_localPinsSessionIds.Remove(participant.SessionId);

UpdatePinnedParticipants();
UpdateSortedParticipants();
UpdatePinnedParticipants(out _);
}

public bool IsPinnedLocally(IStreamVideoCallParticipant participant)
Expand Down Expand Up @@ -488,6 +489,7 @@ internal void UpdateFromSfu(JoinResponse joinResponse)
internal void UpdateFromSfu(ParticipantJoined participantJoined, ICache cache)
{
var participant = Session.UpdateFromSfu(participantJoined, cache);
UpdateSortedParticipants();
ParticipantJoined?.Invoke(participant);
}

Expand All @@ -497,8 +499,12 @@ internal void UpdateFromSfu(ParticipantLeft participantLeft, ICache cache)

_localPinsSessionIds.RemoveAll(participant.sessionId);
_serverPinsSessionIds.RemoveAll(pin => pin == participant.sessionId);
UpdatePinnedParticipants();
UpdateSortedParticipants();

UpdatePinnedParticipants(out var updatedSortedParticipants);
if (!updatedSortedParticipants)
{
UpdateSortedParticipants();
}

cache.CallParticipants.TryRemove(participant.sessionId);

Expand All @@ -508,16 +514,22 @@ internal void UpdateFromSfu(ParticipantLeft participantLeft, ICache cache)

internal void UpdateFromSfu(DominantSpeakerChanged dominantSpeakerChanged, ICache cache)
{
var prev = DominantSpeaker;
DominantSpeaker = Participants.FirstOrDefault(p => p.SessionId == dominantSpeakerChanged.SessionId);

if (prev != DominantSpeaker)
{
UpdateSortedParticipants();
}
}

internal void UpdateFromSfu(PinsChanged pinsChanged, ICache cache)
{
UpdateServerPins(pinsChanged.Pins);
UpdatePinnedParticipants();
UpdateSortedParticipants();
UpdatePinnedParticipants(out _);
}

//StreamTodo: missing TrackRemoved or perhaps we should not care whether a track was added/removed but only published/unpublished -> enabled/disabled
internal void NotifyTrackAdded(IStreamVideoCallParticipant participant, IStreamTrack track)
=> TrackAdded?.Invoke(participant, track);

Expand Down Expand Up @@ -601,10 +613,14 @@ internal void InternalHandleCallRecordingStartedEvent(
CallRecordingStartedEventInternalDTO callRecordingStartedEvent)
=> RecordingStarted?.Invoke();

public void InternalHandleCallRecordingStoppedEvent(
internal void InternalHandleCallRecordingStoppedEvent(
CallRecordingStoppedEventInternalDTO callRecordingStoppedEvent)
=> RecordingStopped?.Invoke();

internal void NotifyTrackStateChanged(StreamVideoCallParticipant participant, TrackType trackType,
bool isEnabled)
=> UpdateSortedParticipants();

protected override string InternalUniqueId
{
get => Cid;
Expand Down Expand Up @@ -632,6 +648,11 @@ protected override string InternalUniqueId
private readonly List<IStreamVideoCallParticipant>
_pinnedParticipants = new List<IStreamVideoCallParticipant>();

private readonly List<IStreamVideoCallParticipant>
_sortedParticipants = new List<IStreamVideoCallParticipant>();

private readonly CallParticipantComparer _participantComparer = new CallParticipantComparer();

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

#endregion
Expand All @@ -652,7 +673,7 @@ private void UpdateServerPins(IEnumerable<Pin> pins)
}
}

private void UpdatePinnedParticipants()
private void UpdatePinnedParticipants(out bool sortedParticipants)
{
_pinnedParticipants.Clear();

Expand All @@ -673,12 +694,40 @@ private void UpdatePinnedParticipants()
}
}

//StreamTodo: optimize
var anyChanged = false;
foreach (var participant in Participants)
{
var prevIsPinned = participant.IsPinned;
var isPinned = IsPinned(participant);

if (prevIsPinned != isPinned)
{
((StreamVideoCallParticipant)participant).SetIsPinned(isPinned);
anyChanged = true;
}
}

if (!anyChanged)
{
sortedParticipants = false;
return;
}

sortedParticipants = true;
UpdateSortedParticipants();
PinnedParticipantsUpdated?.Invoke();
}

private void UpdateSortedParticipants()
{
//SortedParticipantsUpdated?.Invoke();
_participantComparer.Reset();
_sortedParticipants.Sort(_participantComparer);

if (_participantComparer.OrderChanged)
{
SortedParticipantsUpdated?.Invoke();
}
}

private void UpdateMembersFromDto(IEnumerable<MemberResponseInternalDTO> membersDtos)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ internal sealed class StreamVideoCallParticipant : StreamStatefulModelBase<Strea
public event ParticipantTrackChangedHandler TrackAdded;

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

public bool IsPinned { get; private set; }

public bool IsScreenSharing => ScreenShareTrack?.Enabled ?? false;

public bool IsVideoEnabled => VideoTrack?.Enabled ?? false;
public bool IsAudioEnabled => AudioTrack?.Enabled ?? false;

#region Tracks

Expand Down Expand Up @@ -47,6 +54,7 @@ internal sealed class StreamVideoCallParticipant : StreamStatefulModelBase<Strea

public string SessionId { get; private set; }
public IEnumerable<TrackType> PublishedTracks => _publishedTracks;

public string TrackLookupPrefix { get; private set; }
public ConnectionQuality ConnectionQuality { get; private set; }
public bool IsSpeaking { get; private set; }
Expand Down Expand Up @@ -168,6 +176,8 @@ internal void SetTrackEnabled(TrackType type, bool enabled)
//StreamTodo: we should trigger some event that track status changed
}

internal void SetIsPinned(bool isPinned) => IsPinned = isPinned;

protected override string InternalUniqueId
{
get => UserSessionId;
Expand Down

0 comments on commit 9b5c210

Please sign in to comment.