Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement IStreamCall.SortedParticipants #52

Merged
merged 2 commits into from
Jan 10, 2024
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
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
Loading