diff libervia/web/pages/calls/_browser/webrtc.py @ 1616:6bfeb9f0fb84

browser (calls): conferences implementation: - Handle A/V conferences calls creation/joining by entering a conference room JID in the search box. - Group call box has been improved and is used both for group calls (small number of participants) and A/V conferences (larger number of participants). - Fullscreen button for group call is working. - Avatar/user nickname are shown in group call on peer user, as an overlay on video stream. - Use `user` metadata when present to display the right user avatar/name when receiving a stream from SFU (i.e. A/V conference). - Peer user have a new 3 dots menu with a `pin` item to (un)pin it (i.e. display it on full container with on top). - Updated webrtc to handle unidirectional streams correctly and to adapt to A/V conference specification. rel 448
author Goffi <goffi@goffi.org>
date Wed, 07 Aug 2024 00:01:57 +0200
parents 4a9679369856
children
line wrap: on
line diff
--- a/libervia/web/pages/calls/_browser/webrtc.py	Tue Aug 06 23:56:18 2024 +0200
+++ b/libervia/web/pages/calls/_browser/webrtc.py	Wed Aug 07 00:01:57 2024 +0200
@@ -648,12 +648,19 @@
         window.pc = self._peer_connection
         return peer_connection
 
-    async def _get_user_media(self, audio: bool = True, video: bool = True) -> None:
+    async def _get_user_media(
+        self,
+        audio: bool = True,
+        video: bool = True,
+        direction: str = "sendrecv"
+    ) -> None:
         """
         Gets user media (camera and microphone).
 
         @param audio: True if an audio flux is required.
         @param video: True if a video flux is required.
+        @param direction: The direction of the stream ('sendonly', 'recvonly', 'sendrecv',
+            or 'inactive')
         """
         media_constraints = {"audio": audio, "video": video}
         if self.local_stream is None:
@@ -668,8 +675,12 @@
         if self.local_video_elt is not None:
             self.local_video_elt.srcObject = self.local_stream
 
-        for track in self.local_stream.getTracks():
-            self._peer_connection.addTrack(track)
+        if direction != "recvonly":
+            for track in self.local_stream.getTracks():
+                sender = self._peer_connection.addTransceiver(track, {
+                    'direction': direction,
+                    'streams': [self.local_stream]
+                })
 
     async def _replace_user_video(
         self,
@@ -936,7 +947,7 @@
             self.remote_stream.addTrack(event.track)
 
     def on_negotiation_needed(self, event) -> None:
-        log.debug(f"on_negotiation_needed {event=}")
+        log.debug("on_negotiation_needed")
         # TODO
 
     def _on_data_channel(self, event) -> None:
@@ -947,7 +958,13 @@
         data_channel = event.channel
         self.file_receiver = FileReceiver(self.sid, data_channel, self.extra_data)
 
-    async def answer_call(self, sid: str, offer_sdp: str, profile: str):
+    async def answer_call(
+        self,
+        sid: str,
+        offer_sdp: str,
+        profile: str,
+        conference: bool = False
+    ):
         """We respond to the call"""
         log.debug("answering call")
         if sid != self.sid:
@@ -962,7 +979,9 @@
         if self.file_only:
             self._peer_connection.bind("datachannel", self._on_data_channel)
         else:
-            await self._get_user_media()
+            await self._get_user_media(
+                direction="recvonly" if conference else "sendrecv"
+            )
 
         # Gather local ICE candidates
         local_ice_data = await self._gather_ice_candidates(False)
@@ -998,7 +1017,8 @@
     async def prepare_call(
         self,
         audio: bool = True,
-        video: bool = True
+        video: bool = True,
+        direction: str = "sendrecv"
     ) -> dict:
         """Prepare a call.
 
@@ -1009,17 +1029,21 @@
         @return: Call Data
         """
         await self._create_peer_connection()
-        await self._get_user_media(audio, video)
+        await self._get_user_media(audio, video, direction)
         return await self._get_call_data()
 
     async def make_call(
-        self, callee_jid: jid.JID, audio: bool = True, video: bool = True
+        self,
+        callee_jid: jid.JID,
+        audio: bool = True,
+        video: bool = True,
+        direction: str = "sendrecv"
     ) -> None:
         """
         @param audio: True if an audio flux is required
         @param video: True if a video flux is required
         """
-        call_data = await self.prepare_call(audio, video)
+        call_data = await self.prepare_call(audio, video, direction)
         log.info(f"calling {callee_jid!r}")
         self.sid = await bridge.call_start(str(callee_jid), json.dumps(call_data))
         log.debug(f"Call SID: {self.sid}")