Mercurial > libervia-backend
diff libervia/backend/plugins/plugin_xep_0167/__init__.py @ 4240:79c8a70e1813
backend, frontend: prepare remote control:
This is a series of changes necessary to prepare the implementation of remote control
feature:
- XEP-0166: add a `priority` attribute to `ApplicationData`: this is needed when several
applications are working in a same session, to know which one must be handled first.
Will be used to make Remote Control have precedence over Call content.
- XEP-0166: `_call_plugins` is now async and is not used with `DeferredList` anymore: the
benefit to have methods called in parallels is very low, and it cause a lot of trouble
as we can't predict order. Methods are now called sequentially so workflow can be
predicted.
- XEP-0167: fix `senders` XMPP attribute <=> SDP mapping
- XEP-0234: preflight acceptance key is now `pre-accepted` instead of `file-accepted`, so
the same key can be used with other jingle applications.
- XEP-0167, XEP-0343: move some method to XEP-0167
- XEP-0353: use new `priority` feature to call preflight methods of applications according
to it.
- frontend (webrtc): refactor the sources/sink handling with a more flexible mechanism
based on Pydantic models. It is now possible to have has many Data Channel as necessary,
to have them in addition to A/V streams, to specify manually GStreamer sources and
sinks, etc.
- frontend (webrtc): rework of the pipeline to reduce latency.
- frontend: new `portal_desktop` method. Screenshare portal handling has been moved there,
and RemoteDesktop portal has been added.
- frontend (webrtc): fix `extract_ufrag_pwd` method.
rel 436
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 11 May 2024 13:52:41 +0200 |
parents | e11b13418ba6 |
children | a7d4007a8fa5 |
line wrap: on
line diff
--- a/libervia/backend/plugins/plugin_xep_0167/__init__.py Sat May 11 13:25:45 2024 +0200 +++ b/libervia/backend/plugins/plugin_xep_0167/__init__.py Sat May 11 13:52:41 2024 +0200 @@ -71,9 +71,12 @@ "unmute", "ringing", ) +ANSWER_SDP_SENT_KEY = "answer_sdp_sent" class XEP_0167(BaseApplicationHandler): + namespace = NS_JINGLE_RTP + def __init__(self, host): log.info(f'Plugin "{PLUGIN_INFO[C.PI_NAME]}" initialization') self.host = host @@ -147,7 +150,7 @@ metadata = call_data.get("metadata") or {} if "sdp" in call_data: - sdp_data = mapping.parse_sdp(call_data["sdp"]) + sdp_data = mapping.parse_sdp(call_data["sdp"], self._j.ROLE_INITIATOR) to_delete = set() for media, data in sdp_data.items(): if media not in ("audio", "video", "application"): @@ -186,6 +189,44 @@ return metadata + def get_contents(self, call_data: dict, metadata: dict) -> list[dict]: + """Generate call related contents. + + @param call_data: Call data after being parsed by [parse_call_data] + @param metadata: Metadata as returned by [parse_call_data] + @return: List of contents to be used with [jingle.initiate]. + + """ + contents = [] + seen_names = set() + + for media, media_data in call_data.items(): + if media not in ("audio", "video"): + continue + content = { + "app_ns": NS_JINGLE_RTP, + "senders": media_data["senders"], + "transport_type": self._j.TRANSPORT_DATAGRAM, + "app_kwargs": {"media": media, "media_data": media_data}, + "transport_data": { + "local_ice_data": { + "ufrag": metadata["ice-ufrag"], + "pwd": metadata["ice-pwd"], + "candidates": media_data.pop("ice-candidates"), + "fingerprint": media_data.pop("fingerprint", {}), + } + }, + } + if "id" in media_data: + name = media_data.pop("id") + if name in seen_names: + raise exceptions.DataError( + f"Content name (mid) seen multiple times: {name}" + ) + content["name"] = name + contents.append(content) + return contents + async def call_start( self, client: SatXMPPEntity, @@ -211,36 +252,8 @@ @raises exceptions.DataError: If media data is invalid or duplicate content name (mid) is found. """ - sid = str(uuid.uuid4()) metadata = self.parse_call_data(call_data) - contents = [] - seen_names = set() - - for media, media_data in call_data.items(): - if media not in ("audio", "video"): - continue - content = { - "app_ns": NS_JINGLE_RTP, - "senders": "both", - "transport_type": self._j.TRANSPORT_DATAGRAM, - "app_kwargs": {"media": media, "media_data": media_data}, - "transport_data": { - "local_ice_data": { - "ufrag": metadata["ice-ufrag"], - "pwd": metadata["ice-pwd"], - "candidates": media_data.pop("ice-candidates"), - "fingerprint": media_data.pop("fingerprint", {}), - } - }, - } - if "id" in media_data: - name = media_data.pop("id") - if name in seen_names: - raise exceptions.DataError( - f"Content name (mid) seen multiple times: {name}" - ) - content["name"] = name - contents.append(content) + contents = self.get_contents(call_data, metadata) if not contents: raise exceptions.DataError("no valid media data found: {call_data}") @@ -249,17 +262,14 @@ else C.META_SUBTYPE_CALL_AUDIO ) - defer.ensureDeferred( - self._j.initiate( + sid = await self._j.initiate( client, peer_jid, contents, - sid=sid, call_type=call_type, metadata=metadata, peer_metadata={}, ) - ) return sid def _call_answer_sdp(self, session_id: str, answer_sdp: str, profile: str) -> None: @@ -338,7 +348,7 @@ accepted = not resp_data.get("cancelled", False) if accepted: - session["call_accepted"] = True + session["pre_accepted"] = True return accepted @@ -357,7 +367,7 @@ @raises exceptions.CancelError: If the user doesn't accept the incoming call. """ - if session.get("call_accepted", False): + if session.get("pre_accepted", False): # the call is already accepted, nothing to do return @@ -474,7 +484,7 @@ # is requested only once for audio and video contents. return True - if not session.get("call_accepted", False): + if not session.get("pre_accepted", False): if any( c["desc_elt"].getAttribute("media") == "video" for c in session["contents"].values() @@ -505,20 +515,47 @@ answer_sdp = await answer_sdp_d - parsed_answer = mapping.parse_sdp(answer_sdp) + parsed_answer = mapping.parse_sdp(answer_sdp, session["role"]) session["metadata"].update(parsed_answer["metadata"]) - for media in ("audio", "video"): - for content in session["contents"].values(): - if content["desc_elt"].getAttribute("media") == media: - media_data = parsed_answer[media] - application_data = content["application_data"] - application_data["local_data"] = media_data["application_data"] - transport_data = content["transport_data"] - local_ice_data = media_data["transport_data"] - transport_data["local_ice_data"] = local_ice_data + self.propagate_data(session, parsed_answer) return True + def propagate_data(self, session: dict, parsed_answer: dict) -> None: + """Propagate local SDP data to other contents""" + for media in ("audio", "video", "application"): + for content in session["contents"].values(): + try: + application_data = content["application_data"] + content_media = application_data["media"] + except KeyError: + pass + else: + if content_media == media: + media_data = parsed_answer[media] + application_data["local_data"] = media_data["application_data"] + transport_data = content["transport_data"] + local_ice_data = media_data["transport_data"] + transport_data["local_ice_data"] = local_ice_data + + def send_answer_sdp(self, client: SatXMPPEntity, session: dict) -> None: + """Send answer SDP to frontend""" + if not session.get(ANSWER_SDP_SENT_KEY, False): + # we only send the signal once, as it means that the whole session is + # accepted + answer_sdp = mapping.generate_sdp_from_session(session) + self.host.bridge.call_setup( + session["id"], + data_format.serialise( + { + "role": session["role"], + "sdp": answer_sdp, + } + ), + client.profile, + ) + session[ANSWER_SDP_SENT_KEY] = True + async def jingle_handler(self, client, action, session, content_name, desc_elt): content_data = session["contents"][content_name] application_data = content_data["application_data"] @@ -542,20 +579,7 @@ elif action == self._j.A_PREPARE_INITIATOR: application_data["peer_data"] = mapping.parse_description(desc_elt) elif action == self._j.A_SESSION_ACCEPT: - if content_name == next(iter(session["contents"])): - # we only send the signal for first content, as it means that the whole - # session is accepted - answer_sdp = mapping.generate_sdp_from_session(session) - self.host.bridge.call_setup( - session["id"], - data_format.serialise( - { - "role": session["role"], - "sdp": answer_sdp, - } - ), - client.profile, - ) + pass # self.send_answer_sdp(client, session) else: log.warning(f"FIXME: unmanaged action {action}")