@@ -62,7 +62,7 @@ internal sealed class StreamCall : StreamStatefulModelBase<StreamCall>,
6262
6363 //StreamTodo: Maybe add OtherParticipants -> All participants except for the local participant?
6464 public IReadOnlyList < IStreamVideoCallParticipant > Participants => Session ? . Participants ;
65-
65+
6666 public ParticipantCount ParticipantCount => Session . ParticipantCount ;
6767
6868 public bool IsLocalUserOwner
@@ -453,31 +453,41 @@ public IStreamVideoCallParticipant GetLocalParticipant()
453453 {
454454 tempSb . AppendLine ( log ) ;
455455 }
456-
456+
457457 Logs . Error ( tempSb . ToString ( ) ) ;
458458 }
459- throw new InvalidOperationException ( "No participants in the call." ) ;
459+
460+ return null ;
460461 }
461-
462+
462463 var localParticipant = Participants . FirstOrDefault ( p => p . IsLocalParticipant ) ;
463464 if ( localParticipant == null )
464465 {
465- using ( new StringBuilderPoolScope ( out var sb ) )
466+ try
466467 {
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 )
468+ using ( new StringBuilderPoolScope ( out var sb ) )
470469 {
471- sb . AppendLine ( $ " - UserId: { p . UserId } , SessionId: { p . SessionId } , IsLocalParticipant: { p . IsLocalParticipant } ") ;
472- }
470+ var currentSessionId = LowLevelClient . RtcSession . SessionId ;
471+ sb . AppendLine (
472+ $ "Local participant not found. Local Session ID: { currentSessionId } . Participants in the call:") ;
473+ foreach ( var p in Participants )
474+ {
475+ sb . AppendLine (
476+ $ " - UserId: { p . UserId } , SessionId: { p . SessionId } , IsLocalParticipant: { p . IsLocalParticipant } ") ;
477+ }
473478
474- sb . AppendLine ( "Last operations leading to this state:" ) ;
475- foreach ( var log in _tempLogs . GetLogs ( ) )
476- {
477- sb . AppendLine ( log ) ;
479+ sb . AppendLine ( "Last operations leading to this state:" ) ;
480+ foreach ( var log in _tempLogs . GetLogs ( ) )
481+ {
482+ sb . AppendLine ( log ) ;
483+ }
484+
485+ Logs . Error ( sb . ToString ( ) ) ;
478486 }
479-
480- Logs . Error ( sb . ToString ( ) ) ;
487+ }
488+ catch ( Exception e )
489+ {
490+ Logs . Warning ( $ "Error while generating log for { nameof ( GetLocalParticipant ) } : " + e . Message ) ;
481491 }
482492 }
483493
@@ -487,6 +497,8 @@ public IStreamVideoCallParticipant GetLocalParticipant()
487497 void IUpdateableFrom < CallResponseInternalDTO , StreamCall > . UpdateFromDto ( CallResponseInternalDTO dto ,
488498 ICache cache )
489499 {
500+ var wasBefore = IsLocalParticipantIncluded ( ) ;
501+
490502 Backstage = dto . Backstage ;
491503 _blockedUserIds . TryReplaceValuesFromDto ( dto . BlockedUserIds ) ;
492504 Cid = dto . Cid ;
@@ -506,27 +518,33 @@ void IUpdateableFrom<CallResponseInternalDTO, StreamCall>.UpdateFromDto(CallResp
506518 Type = new StreamCallType ( dto . Type ) ;
507519 UpdatedAt = dto . UpdatedAt ;
508520
521+ var isAfter = IsLocalParticipantIncluded ( ) ;
522+
509523 try
510524 {
525+ var localParticipantId = LowLevelClient . RtcSession . SessionId ;
511526 // Ignore the IDE warning, this can be null
512527 if ( dto . Session != null )
513528 {
514529 using ( new StringBuilderPoolScope ( out var tempSb ) )
515530 {
516- tempSb . Append ( $ "`UpdateFromDto(CallResponseInternalDTO dto` - dto participants: { dto . Session . Participants ? . Count } , call participants: { Session . Participants . Count } . Dto participants: ") ;
531+ tempSb . Append (
532+ $ "`UpdateFromDto(CallResponseInternalDTO dto` - dto participants: { dto . Session . Participants ? . Count } , call participants: { Session . Participants . Count } . ") ;
533+ tempSb . Append (
534+ $ "IsLocalParticipantIncluded ({ localParticipantId } ) before: { wasBefore } , after: { isAfter } . ") ;
535+ tempSb . Append ( "Dto participants:" ) ;
517536 foreach ( var p in dto . Session . Participants )
518537 {
519- tempSb . Append ( $ "[UserSessionId: { p . UserSessionId } , SessionId: { p . User ? . Id } ") ;
538+ tempSb . Append ( $ "[UserSessionId: { p . UserSessionId } , SessionId: { p . User ? . Id } , ") ;
520539 }
521-
540+
522541 _tempLogs . Add ( tempSb . ToString ( ) ) ;
523542 }
524543 }
525-
526544 }
527545 catch ( Exception e )
528546 {
529- Logs . Exception ( e ) ;
547+ Logs . Warning ( "Failed to log participants in UpdateFromDto: " + e . Message ) ;
530548 }
531549
532550 // Depends on Session.Participants so load as last
@@ -595,21 +613,39 @@ internal StreamCall(string uniqueId, ICacheRepository<StreamCall> repository,
595613 //StreamTodo: solve with a generic interface and best to be handled by cache layer
596614 internal void UpdateFromSfu ( JoinResponse joinResponse )
597615 {
616+ var wasBefore = IsLocalParticipantIncluded ( ) ;
617+
598618 ( ( IStateLoadableFrom < CallState , CallSession > ) Session ) . LoadFromDto ( joinResponse . CallState , Cache ) ;
599619 UpdateServerPins ( joinResponse . CallState . Pins ) ;
600620
621+ var isAfter = IsLocalParticipantIncluded ( ) ;
622+
601623 try
602624 {
625+ var localParticipantId = LowLevelClient . RtcSession . SessionId ;
603626 using ( new StringBuilderPoolScope ( out var tempSb ) )
604627 {
628+ tempSb . Append ( "`UpdateFromSfu(JoinResponse joinResponse)` - " ) ;
629+ tempSb . Append (
630+ $ "IsLocalParticipantIncluded ({ localParticipantId } ) before: { wasBefore } , after: { isAfter } . ") ;
605631 tempSb . Append ( "`UpdateFromSfu(JoinResponse joinResponse)` - joinResponse participants: " ) ;
606- if ( joinResponse . CallState != null && joinResponse . CallState . Participants != null )
632+ if ( joinResponse . CallState != null && joinResponse . CallState . Participants != null )
607633 {
608634 foreach ( var p in joinResponse . CallState . Participants )
609635 {
610636 tempSb . Append ( $ "[UserId: { p . UserId } , SessionId: { p . SessionId } , ") ;
611637 }
612638 }
639+ else
640+ {
641+ tempSb . Append ( "joinResponse.CallState not null:" ) ;
642+ tempSb . Append ( joinResponse . CallState != null ) ;
643+ tempSb . Append ( "joinResponse.CallState.Participants not null: " ) ;
644+ tempSb . Append ( joinResponse . CallState ? . Participants != null ) ;
645+ tempSb . Append ( "count: " ) ;
646+ tempSb . Append ( joinResponse . CallState ? . Participants ? . Count ) ;
647+ }
648+
613649 _tempLogs . Add ( tempSb . ToString ( ) ) ;
614650 }
615651 }
@@ -628,6 +664,20 @@ internal void UpdateFromSfu(ParticipantJoined participantJoined, ICache cache)
628664
629665 internal void UpdateFromSfu ( ParticipantLeft participantLeft , ICache cache )
630666 {
667+ try
668+ {
669+ var p = cache . TryCreateOrUpdate ( participantLeft . Participant ) ;
670+ if ( p . IsLocalParticipant )
671+ {
672+ _tempLogs . Add (
673+ "`UpdateFromSfu(ParticipantLeft participantLeft)` - ERROR - local participant is leaving the call." ) ;
674+ }
675+ }
676+ catch ( Exception e )
677+ {
678+ Logs . Warning ( "Error when generating debug log: " + e . Message ) ;
679+ }
680+
631681 var participant = Session . UpdateFromSfu ( participantLeft , cache ) ;
632682
633683 _localPinsSessionIds . RemoveAll ( participant . sessionId ) ;
@@ -666,23 +716,23 @@ internal void UpdateFromSfu(HealthCheckResponse healthCheckResponse, ICache cach
666716 {
667717 Session ? . UpdateFromSfu ( healthCheckResponse , cache ) ;
668718 }
669-
719+
670720 internal void UpdateFromCoordinator ( CallSessionParticipantCountsUpdatedEventInternalDTO eventData )
671721 {
672722 Session ? . UpdateFromCoordinator ( eventData , Client . InternalLowLevelClient . RtcSession . CallState ) ;
673723 }
674-
724+
675725 internal void UpdateFromCoordinator ( CallSessionParticipantJoinedEventInternalDTO eventData , ICache cache )
676726 {
677727 Session ? . UpdateFromCoordinator ( eventData , cache , Client . InternalLowLevelClient . RtcSession . CallState ) ;
678-
728+
679729 //StreamTodo: we should extract AddParticipant logic from SFU and whatever is received first (SFU or Coordinator) should handle it
680730 }
681-
731+
682732 internal void UpdateFromCoordinator ( CallSessionParticipantLeftEventInternalDTO eventData , ICache cache )
683733 {
684734 Session ? . UpdateFromCoordinator ( eventData , cache , Client . InternalLowLevelClient . RtcSession . CallState ) ;
685-
735+
686736 //StreamTodo: we should extract RemoveParticipant logic from SFU and whatever is received first (SFU or Coordinator) should handle it
687737 }
688738
@@ -759,12 +809,14 @@ internal void InternalHandleCallRecordingStartedEvent(CallReactionEventInternalD
759809
760810 //StreamTodo: NullReferenceException here because _client is never set
761811 var participant
762- = Client . InternalLowLevelClient . RtcSession . ActiveCall . Participants . FirstOrDefault ( p => p . UserId == reaction . User . Id ) ;
812+ = Client . InternalLowLevelClient . RtcSession . ActiveCall . Participants . FirstOrDefault ( p
813+ => p . UserId == reaction . User . Id ) ;
763814 if ( participant == null )
764815 {
765816 Logs . ErrorIfDebug (
766817 $ "Failed to find participant for reaction. UserId: { reaction . User . Id } , Participants: " +
767- string . Join ( ", " , Client . InternalLowLevelClient . RtcSession . ActiveCall . Participants . Select ( p => p . UserId ) ) ) ;
818+ string . Join ( ", " ,
819+ Client . InternalLowLevelClient . RtcSession . ActiveCall . Participants . Select ( p => p . UserId ) ) ) ;
768820 return ;
769821 }
770822
@@ -928,7 +980,7 @@ private void UpdateCapabilitiesByRole(Dictionary<string, List<string>> capabilit
928980 tempRolesToRemove . Add ( role ) ;
929981 }
930982 }
931-
983+
932984 foreach ( var role in tempRolesToRemove )
933985 {
934986 _capabilitiesByRole . Remove ( role ) ;
@@ -987,5 +1039,15 @@ private void GetOrCreateParticipantsCustomDataSection(IStreamVideoCallParticipan
9871039
9881040 participantCustomData = allParticipantsCustomData [ participant . SessionId ] ;
9891041 }
1042+
1043+ private bool IsLocalParticipantIncluded ( )
1044+ {
1045+ if ( Session == null || Session . Participants == null || Session . Participants . Count == 0 )
1046+ {
1047+ return false ;
1048+ }
1049+
1050+ return Session . Participants . FirstOrDefault ( p => p . IsLocalParticipant ) != null ;
1051+ }
9901052 }
9911053}
0 commit comments