@@ -97,6 +97,12 @@ final class GroupCallController implements CallControllerInterface, GroupConnect
9797 private array $ participants = [];
9898 /** @var array<int, int> Signed source ID => bot API peer ID. */
9999 private array $ sourceToPeer = [];
100+ /**
101+ * @var array<int, array<int, int>> The retransmission (RTX) SSRC of each video and screen-share SSRC of each
102+ * participant that has one (the second source of its FID group), signed, by SSRC,
103+ * by bot API peer ID: the SFU resends the lost packets of the stream on it.
104+ */
105+ private array $ retransmission = [];
100106
101107 /** Our own audio source ID, in the signed form used by the API. */
102108 private int $ source = 0 ;
@@ -526,6 +532,7 @@ public function applyGroupCall(array $result): void
526532 }
527533 $ this ->participants = [];
528534 $ this ->sourceToPeer = [];
535+ $ this ->retransmission = [];
529536 foreach ($ result ['participants ' ] as $ participant ) {
530537 $ this ->applyParticipant ($ participant );
531538 }
@@ -604,6 +611,35 @@ public function onParticipantsUpdate(array $participants, int $version): void
604611 $ this ->syncSources ();
605612 }
606613
614+ /**
615+ * The retransmission SSRC of each video and screen-share SSRC of a participant that has one: from the source
616+ * groups of the update, or kept from the previous one for a stream a min update doesn't carry.
617+ *
618+ * @param array<int, int> $previous
619+ *
620+ * @return array<int, int> Signed SSRCs, by signed SSRC.
621+ *
622+ * @psalm-pure
623+ */
624+ private static function retransmissionSources (array $ participant , AbstractGroupCallParticipant $ parsed , array $ previous ): array
625+ {
626+ $ sources = [];
627+ foreach (['video ' , 'presentation ' ] as $ key ) {
628+ $ video = $ participant [$ key ] ?? null ;
629+ if (!\is_array ($ video )) {
630+ continue ;
631+ }
632+ foreach ($ video ['source_groups ' ] ?? [] as $ group ) {
633+ if (($ group ['semantics ' ] ?? '' ) === 'FID ' && isset ($ group ['sources ' ][0 ], $ group ['sources ' ][1 ])) {
634+ $ sources [$ group ['sources ' ][0 ]] = $ group ['sources ' ][1 ];
635+ }
636+ }
637+ }
638+ // Only the streams the participant sends (not a paused one, say).
639+ $ current = array_flip ([...$ parsed ->videoSources , ...$ parsed ->presentationSources ]);
640+ return array_intersect_key ($ sources + $ previous , $ current );
641+ }
642+
607643 private function applyParticipant (array $ participant ): void
608644 {
609645 $ peerId = $ this ->API ->getIdInternal ($ participant ['peer ' ]);
@@ -615,6 +651,7 @@ private function applyParticipant(array $participant): void
615651 if ($ old !== null ) {
616652 unset($ this ->sourceToPeer [$ old ->source ], $ this ->participants [$ peerId ]);
617653 }
654+ unset($ this ->retransmission [$ peerId ]);
618655 if (($ participant ['self ' ] ?? false )
619656 && $ this ->callState === GroupCallState::JOINED
620657 && (int ) ($ participant ['source ' ] ?? 0 ) === $ this ->source
@@ -630,6 +667,7 @@ private function applyParticipant(array $participant): void
630667 ? LiveStoryParticipant ::fromRaw ($ participant , $ peerId , $ this ->participants [$ peerId ] ?? null )
631668 : GroupCallParticipant ::fromRaw ($ participant , $ peerId , $ this ->participants [$ peerId ] ?? null );
632669 $ this ->participants [$ peerId ] = $ parsed ;
670+ $ this ->retransmission [$ peerId ] = self ::retransmissionSources ($ participant , $ parsed , $ this ->retransmission [$ peerId ] ?? []);
633671 if ($ parsed ->source !== 0 ) {
634672 $ this ->sourceToPeer [$ parsed ->source ] = $ peerId ;
635673 $ this ->sourcePeers [$ parsed ->source ] = $ peerId ;
@@ -650,7 +688,7 @@ private function syncSources(): void
650688 return ;
651689 }
652690 $ sources = [];
653- foreach ($ this ->participants as $ participant ) {
691+ foreach ($ this ->participants as $ peerId => $ participant ) {
654692 if ($ participant ->source !== 0 && $ participant ->source !== $ this ->source ) {
655693 $ sources [] = [
656694 'audio ' => $ participant ->source ,
@@ -659,8 +697,7 @@ private function syncSources(): void
659697 'presentation ' => $ participant ->presentationSources ,
660698 'videoEndpoint ' => $ participant ->videoEndpoint ,
661699 'presentationEndpoint ' => $ participant ->presentationEndpoint ,
662- // Participants saved by versions that didn't keep the retransmission sources have none.
663- 'rtx ' => $ participant ->retransmissionSources ?? [],
700+ 'rtx ' => $ this ->retransmission [$ peerId ] ?? [],
664701 ];
665702 }
666703 }
0 commit comments