comparison libervia/backend/plugins/plugin_xep_0167/__init__.py @ 4116:23fa52acf72c

plugin XEP-0167, XEP-0176: transport-info and ICE candidate sending are delayed if session is not active yet
author Goffi <goffi@goffi.org>
date Mon, 21 Aug 2023 15:19:45 +0200
parents 0da563780ffc
children 07370d2a9bde
comparison
equal deleted inserted replaced
4115:0da563780ffc 4116:23fa52acf72c
113 async_=True, 113 async_=True,
114 ) 114 )
115 115
116 # args: session_id, serialised setup data (dict with keys "role" and "sdp"), 116 # args: session_id, serialised setup data (dict with keys "role" and "sdp"),
117 # profile 117 # profile
118 host.bridge.add_signal( 118 host.bridge.add_signal("call_setup", ".plugin", signature="sss")
119 "call_setup", ".plugin", signature="sss"
120 )
121 119
122 # args: session_id, data, profile 120 # args: session_id, data, profile
123 host.bridge.add_signal( 121 host.bridge.add_signal("call_ended", ".plugin", signature="sss")
124 "call_ended", ".plugin", signature="sss"
125 )
126 122
127 # args: session_id, info_type, extra, profile 123 # args: session_id, info_type, extra, profile
128 host.bridge.add_signal( 124 host.bridge.add_signal("call_info", ".plugin", signature="ssss")
129 "call_info", ".plugin", signature="ssss"
130 )
131 125
132 def get_handler(self, client): 126 def get_handler(self, client):
133 return XEP_0167_handler() 127 return XEP_0167_handler()
134 128
135 # bridge methods 129 # bridge methods
192 try: 186 try:
193 call_data[media_type]["id"] = media_data["id"] 187 call_data[media_type]["id"] = media_data["id"]
194 except KeyError: 188 except KeyError:
195 log.warning(f"no media ID found for {media_type}: {media_data}") 189 log.warning(f"no media ID found for {media_type}: {media_data}")
196 try: 190 try:
197 call_data[media_type]["ice-candidates"] = transport_data["candidates"] 191 call_data[media_type]["ice-candidates"] = transport_data.get(
192 "candidates", []
193 )
198 metadata["ice-ufrag"] = transport_data["ufrag"] 194 metadata["ice-ufrag"] = transport_data["ufrag"]
199 metadata["ice-pwd"] = transport_data["pwd"] 195 metadata["ice-pwd"] = transport_data["pwd"]
200 except KeyError: 196 except KeyError:
201 log.warning("ICE data are missing from SDP") 197 log.warning("ICE data are missing from SDP")
202 continue 198 continue
248 peer_metadata={}, 244 peer_metadata={},
249 ) 245 )
250 ) 246 )
251 return sid 247 return sid
252 248
253 def _call_answer_sdp( 249 def _call_answer_sdp(self, session_id: str, answer_sdp: str, profile: str) -> None:
254 self,
255 session_id: str,
256 answer_sdp: str,
257 profile: str
258 ) -> None:
259 client = self.host.get_client(profile) 250 client = self.host.get_client(profile)
260 session = self._j.get_session(client, session_id) 251 session = self._j.get_session(client, session_id)
261 try: 252 try:
262 answer_sdp_d = session.pop("answer_sdp_d") 253 answer_sdp_d = session.pop("answer_sdp_d")
263 except KeyError: 254 except KeyError:
272 data_s: str, 263 data_s: str,
273 profile_key: str, 264 profile_key: str,
274 ): 265 ):
275 client = self.host.get_client(profile_key) 266 client = self.host.get_client(profile_key)
276 return defer.ensureDeferred( 267 return defer.ensureDeferred(
277 self.call_end( 268 self.call_end(client, session_id, data_format.deserialise(data_s))
278 client, session_id, data_format.deserialise(data_s)
279 )
280 ) 269 )
281 270
282 async def call_end( 271 async def call_end(
283 self, 272 self,
284 client: SatXMPPEntity, 273 client: SatXMPPEntity,
295 await self._j.terminate(client, self._j.REASON_SUCCESS, session) 284 await self._j.terminate(client, self._j.REASON_SUCCESS, session)
296 285
297 # jingle callbacks 286 # jingle callbacks
298 287
299 async def confirm_incoming_call( 288 async def confirm_incoming_call(
300 self, 289 self, client: SatXMPPEntity, session: dict, call_type: str
301 client: SatXMPPEntity,
302 session: dict,
303 call_type: str
304 ) -> bool: 290 ) -> bool:
305 """Prompt the user for a call confirmation. 291 """Prompt the user for a call confirmation.
306 292
307 @param client: The client entity. 293 @param client: The client entity.
308 @param session: The Jingle session. 294 @param session: The Jingle session.
339 session["call_accepted"] = True 325 session["call_accepted"] = True
340 326
341 return accepted 327 return accepted
342 328
343 async def jingle_preflight( 329 async def jingle_preflight(
344 self, 330 self, client: SatXMPPEntity, session: dict, description_elt: domish.Element
345 client: SatXMPPEntity,
346 session: dict,
347 description_elt: domish.Element
348 ) -> None: 331 ) -> None:
349 """Perform preflight checks for an incoming call session. 332 """Perform preflight checks for an incoming call session.
350 333
351 Check if the calls is audio only or audio/video, then, prompts the user for 334 Check if the calls is audio only or audio/video, then, prompts the user for
352 confirmation. 335 confirmation.
378 except defer.CancelledError as e: 361 except defer.CancelledError as e:
379 # raised when call is retracted before user has answered or rejected 362 # raised when call is retracted before user has answered or rejected
380 self.host.bridge.call_ended( 363 self.host.bridge.call_ended(
381 session["id"], 364 session["id"],
382 data_format.serialise({"reason": "retracted"}), 365 data_format.serialise({"reason": "retracted"}),
383 client.profile 366 client.profile,
384 ) 367 )
385 raise e 368 raise e
386 369
387 if not accepted: 370 if not accepted:
388 raise exceptions.CancelError("User declined the incoming call.") 371 raise exceptions.CancelError("User declined the incoming call.")
390 async def jingle_preflight_info( 373 async def jingle_preflight_info(
391 self, 374 self,
392 client: SatXMPPEntity, 375 client: SatXMPPEntity,
393 session: dict, 376 session: dict,
394 info_type: str, 377 info_type: str,
395 info_data: dict|None = None 378 info_data: dict | None = None,
396 ) -> None: 379 ) -> None:
397 if info_type == "ringing": 380 if info_type == "ringing":
398 if not session.get("ringing", False): 381 if not session.get("ringing", False):
399 self.host.bridge.call_info( 382 self.host.bridge.call_info(session["id"], "ringing", "", client.profile)
400 session["id"], "ringing", "", client.profile
401 )
402 # we indicate that the ringing has started, to avoid sending several times 383 # we indicate that the ringing has started, to avoid sending several times
403 # the signal 384 # the signal
404 session["ringing"] = True 385 session["ringing"] = True
405 else: 386 else:
406 log.warning(f"Unknown preflight info type: {info_type!r}") 387 log.warning(f"Unknown preflight info type: {info_type!r}")
407 388
408 async def jingle_preflight_cancel( 389 async def jingle_preflight_cancel(
409 self, 390 self, client: SatXMPPEntity, session: dict, cancel_error: exceptions.CancelError
410 client: SatXMPPEntity,
411 session: dict,
412 cancel_error: exceptions.CancelError
413 ) -> None: 391 ) -> None:
414 """The call has been rejected""" 392 """The call has been rejected"""
415 # call_ended is use to send the signal only once even if there are audio and video 393 # call_ended is use to send the signal only once even if there are audio and video
416 # contents 394 # contents
417 call_ended = session.get("call_ended", False) 395 call_ended = session.get("call_ended", False)
418 if call_ended: 396 if call_ended:
419 return 397 return
420 data = { 398 data = {"reason": getattr(cancel_error, "reason", "cancelled")}
421 "reason": getattr(cancel_error, "reason", "cancelled")
422 }
423 text = getattr(cancel_error, "text", None) 399 text = getattr(cancel_error, "text", None)
424 if text: 400 if text:
425 data["text"] = text 401 data["text"] = text
426 self.host.bridge.call_ended( 402 self.host.bridge.call_ended(
427 session["id"], 403 session["id"], data_format.serialise(data), client.profile
428 data_format.serialise(data),
429 client.profile
430 ) 404 )
431 session["call_ended"] = True 405 session["call_ended"] = True
432
433 406
434 def jingle_session_init( 407 def jingle_session_init(
435 self, 408 self,
436 client: SatXMPPEntity, 409 client: SatXMPPEntity,
437 session: dict, 410 session: dict,
505 # we should have the answer long before 2 min 478 # we should have the answer long before 2 min
506 answer_sdp_d.addTimeout(2 * 60, reactor) 479 answer_sdp_d.addTimeout(2 * 60, reactor)
507 480
508 self.host.bridge.call_setup( 481 self.host.bridge.call_setup(
509 session["id"], 482 session["id"],
510 data_format.serialise({ 483 data_format.serialise(
511 "role": session["role"], 484 {
512 "sdp": sdp, 485 "role": session["role"],
513 }), 486 "sdp": sdp,
514 client.profile 487 }
488 ),
489 client.profile,
515 ) 490 )
516 491
517 answer_sdp = await answer_sdp_d 492 answer_sdp = await answer_sdp_d
518 493
519 parsed_answer = mapping.parse_sdp(answer_sdp) 494 parsed_answer = mapping.parse_sdp(answer_sdp)
557 # we only send the signal for first content, as it means that the whole 532 # we only send the signal for first content, as it means that the whole
558 # session is accepted 533 # session is accepted
559 answer_sdp = mapping.generate_sdp_from_session(session) 534 answer_sdp = mapping.generate_sdp_from_session(session)
560 self.host.bridge.call_setup( 535 self.host.bridge.call_setup(
561 session["id"], 536 session["id"],
562 data_format.serialise({ 537 data_format.serialise(
563 "role": session["role"], 538 {
564 "sdp": answer_sdp, 539 "role": session["role"],
565 }), 540 "sdp": answer_sdp,
566 client.profile 541 }
542 ),
543 client.profile,
567 ) 544 )
568 else: 545 else:
569 log.warning(f"FIXME: unmanaged action {action}") 546 log.warning(f"FIXME: unmanaged action {action}")
570 547
571 self.host.trigger.point( 548 self.host.trigger.point(
633 session: dict, 610 session: dict,
634 content_name: str, 611 content_name: str,
635 reason_elt: domish.Element, 612 reason_elt: domish.Element,
636 ) -> None: 613 ) -> None:
637 reason, text = self._j.parse_reason_elt(reason_elt) 614 reason, text = self._j.parse_reason_elt(reason_elt)
638 data = { 615 data = {"reason": reason}
639 "reason": reason
640 }
641 if text: 616 if text:
642 data["text"] = text 617 data["text"] = text
643 self.host.bridge.call_ended( 618 self.host.bridge.call_ended(
644 session["id"], data_format.serialise(data), client.profile 619 session["id"], data_format.serialise(data), client.profile
645 ) 620 )