Mercurial > libervia-backend
comparison libervia/frontends/tools/webrtc.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 79c8a70e1813 |
children | 2992f9d1e039 |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
80 | 80 |
81 def __init__( | 81 def __init__( |
82 self, | 82 self, |
83 bridge, | 83 bridge, |
84 profile: str, | 84 profile: str, |
85 sources_data: SourcesData|None = None, | 85 sources_data: SourcesData | None = None, |
86 sinks_data: SinksData|None = None, | 86 sinks_data: SinksData | None = None, |
87 reset_cb: Callable | None = None, | 87 reset_cb: Callable | None = None, |
88 merge_pip: bool | None = None, | 88 merge_pip: bool | None = None, |
89 target_size: tuple[int, int] | None = None, | 89 target_size: tuple[int, int] | None = None, |
90 call_start_cb: Callable[[str, dict, str], Awaitable[str]] | None = None, | 90 call_start_cb: Callable[[str, dict, str], Awaitable[str]] | None = None, |
91 dc_data_list: list[SourcesDataChannel|SinksDataChannel]|None = None | 91 dc_data_list: list[SourcesDataChannel | SinksDataChannel] | None = None, |
92 ) -> None: | 92 ) -> None: |
93 """Initializes a new WebRTC instance. | 93 """Initializes a new WebRTC instance. |
94 | 94 |
95 @param bridge: An instance of backend bridge. | 95 @param bridge: An instance of backend bridge. |
96 @param profile: Libervia profile. | 96 @param profile: Libervia profile. |
384 """Retrieves ICE password and user fragment for SDP offer. | 384 """Retrieves ICE password and user fragment for SDP offer. |
385 | 385 |
386 @param sdp: The Session Description Protocol offer string. | 386 @param sdp: The Session Description Protocol offer string. |
387 """ | 387 """ |
388 lines = sdp.splitlines() | 388 lines = sdp.splitlines() |
389 media = '' | 389 media = "" |
390 mid_media_map = {} | 390 mid_media_map = {} |
391 bundle_media = set() | 391 bundle_media = set() |
392 bundle_ufrag = '' | 392 bundle_ufrag = "" |
393 bundle_pwd = '' | 393 bundle_pwd = "" |
394 in_bundle = False | 394 in_bundle = False |
395 | 395 |
396 for line in lines: | 396 for line in lines: |
397 if line.startswith('m='): | 397 if line.startswith("m="): |
398 media = line.split('=')[1].split()[0] | 398 media = line.split("=")[1].split()[0] |
399 elif line.startswith('a=mid:'): | 399 elif line.startswith("a=mid:"): |
400 mid = line.split(':')[1].strip() | 400 mid = line.split(":")[1].strip() |
401 mid_media_map[mid] = media | 401 mid_media_map[mid] = media |
402 elif line.startswith('a=group:BUNDLE'): | 402 elif line.startswith("a=group:BUNDLE"): |
403 in_bundle = True | 403 in_bundle = True |
404 bundle_media = set(line.split(':')[1].strip().split()) | 404 bundle_media = set(line.split(":")[1].strip().split()) |
405 elif line.startswith('a=ice-ufrag:'): | 405 elif line.startswith("a=ice-ufrag:"): |
406 if in_bundle: | 406 if in_bundle: |
407 bundle_ufrag = line.split(':')[1].strip() | 407 bundle_ufrag = line.split(":")[1].strip() |
408 else: | 408 else: |
409 self.ufrag[media] = line.split(':')[1].strip() | 409 self.ufrag[media] = line.split(":")[1].strip() |
410 elif line.startswith('a=ice-pwd:'): | 410 elif line.startswith("a=ice-pwd:"): |
411 if in_bundle: | 411 if in_bundle: |
412 bundle_pwd = line.split(':')[1].strip() | 412 bundle_pwd = line.split(":")[1].strip() |
413 else: | 413 else: |
414 self.pwd[media] = line.split(':')[1].strip() | 414 self.pwd[media] = line.split(":")[1].strip() |
415 else: | 415 else: |
416 in_bundle = False | 416 in_bundle = False |
417 | 417 |
418 if bundle_ufrag and bundle_pwd: | 418 if bundle_ufrag and bundle_pwd: |
419 for mid in bundle_media: | 419 for mid in bundle_media: |
485 @raises AssertionError: If the role is not 'initiator' or 'responder'. | 485 @raises AssertionError: If the role is not 'initiator' or 'responder'. |
486 """ | 486 """ |
487 assert role in ("initiator", "responder") | 487 assert role in ("initiator", "responder") |
488 self.role = role | 488 self.role = role |
489 | 489 |
490 | |
491 if isinstance(self.sources_data, SourcesPipeline): | 490 if isinstance(self.sources_data, SourcesPipeline): |
492 if self.sources_data.video_pipeline!= "" and video_pt is None: | 491 if self.sources_data.video_pipeline != "" and video_pt is None: |
493 raise NotImplementedError(NONE_NOT_IMPLEMENTED_MSG) | 492 raise NotImplementedError(NONE_NOT_IMPLEMENTED_MSG) |
494 if self.sources_data.audio_pipeline!= "" and audio_pt is None: | 493 if self.sources_data.audio_pipeline != "" and audio_pt is None: |
495 raise NotImplementedError(NONE_NOT_IMPLEMENTED_MSG) | 494 raise NotImplementedError(NONE_NOT_IMPLEMENTED_MSG) |
496 elif isinstance(self.sources_data, SourcesNone): | 495 elif isinstance(self.sources_data, SourcesNone): |
497 pass | 496 pass |
498 else: | 497 else: |
499 if audio_pt is None or video_pt is None: | 498 if audio_pt is None or video_pt is None: |
557 local_video_sink_elt = "compositor.sink_1" | 556 local_video_sink_elt = "compositor.sink_1" |
558 | 557 |
559 if video_source_elt: | 558 if video_source_elt: |
560 # Video source with an input-selector to switch between normal and video mute | 559 # Video source with an input-selector to switch between normal and video mute |
561 # (or desktop sharing). | 560 # (or desktop sharing). |
562 gst_pipe_elements.append(f""" | 561 gst_pipe_elements.append( |
562 f""" | |
563 input-selector name=video_selector | 563 input-selector name=video_selector |
564 ! videorate drop-only=1 max-rate=30 | 564 ! videorate drop-only=1 max-rate=30 |
565 ! video/x-raw,framerate=30/1 | 565 ! video/x-raw,framerate=30/1 |
566 ! tee name=t | 566 ! tee name=t |
567 | 567 |
579 ! videoconvert | 579 ! videoconvert |
580 ! vp8enc deadline=1 keyframe-max-dist=30 | 580 ! vp8enc deadline=1 keyframe-max-dist=30 |
581 ! rtpvp8pay picture-id-mode=15-bit | 581 ! rtpvp8pay picture-id-mode=15-bit |
582 ! application/x-rtp,media=video,encoding-name=VP8,payload={video_pt} | 582 ! application/x-rtp,media=video,encoding-name=VP8,payload={video_pt} |
583 ! sendrecv. | 583 ! sendrecv. |
584 """) | 584 """ |
585 ) | |
585 | 586 |
586 if local_video_sink_elt: | 587 if local_video_sink_elt: |
587 # Local video feedback. | 588 # Local video feedback. |
588 gst_pipe_elements.append(f""" | 589 gst_pipe_elements.append( |
590 f""" | |
589 t. | 591 t. |
590 ! queue max-size-buffers=1 max-size-time=0 max-size-bytes=0 leaky=downstream | 592 ! queue max-size-buffers=1 max-size-time=0 max-size-bytes=0 leaky=downstream |
591 ! videoconvert | 593 ! videoconvert |
592 ! {local_video_sink_elt} | 594 ! {local_video_sink_elt} |
593 """) | 595 """ |
596 ) | |
594 | 597 |
595 if audio_source_elt: | 598 if audio_source_elt: |
596 # Audio with a valve for muting. | 599 # Audio with a valve for muting. |
597 gst_pipe_elements.append(r""" | 600 gst_pipe_elements.append( |
601 r""" | |
598 {audio_source_elt} name=audio_src | 602 {audio_source_elt} name=audio_src |
599 ! valve | 603 ! valve |
600 ! queue max-size-buffers=10 max-size-time=0 max-size-bytes=0 leaky=downstream | 604 ! queue max-size-buffers=10 max-size-time=0 max-size-bytes=0 leaky=downstream |
601 ! audioconvert | 605 ! audioconvert |
602 ! audioresample | 606 ! audioresample |
603 ! opusenc audio-type=voice | 607 ! opusenc audio-type=voice |
604 ! rtpopuspay | 608 ! rtpopuspay |
605 ! application/x-rtp,media=audio,encoding-name=OPUS,payload={audio_pt} | 609 ! application/x-rtp,media=audio,encoding-name=OPUS,payload={audio_pt} |
606 ! sendrecv. | 610 ! sendrecv. |
607 """) | 611 """ |
612 ) | |
608 | 613 |
609 self.gst_pipe_desc = "\n\n".join(gst_pipe_elements) | 614 self.gst_pipe_desc = "\n\n".join(gst_pipe_elements) |
610 | 615 |
611 log.debug(f"Gstreamer pipeline: {self.gst_pipe_desc}") | 616 log.debug(f"Gstreamer pipeline: {self.gst_pipe_desc}") |
612 | 617 |
706 ) | 711 ) |
707 | 712 |
708 for dc_data in self.dc_data_list: | 713 for dc_data in self.dc_data_list: |
709 self.create_data_channel(dc_data) | 714 self.create_data_channel(dc_data) |
710 | 715 |
711 def create_data_channel(self, dc_data: SourcesDataChannel|SinksDataChannel) -> None: | 716 def create_data_channel(self, dc_data: SourcesDataChannel | SinksDataChannel) -> None: |
712 """Create a Data Channel and connect relevant callbacks.""" | 717 """Create a Data Channel and connect relevant callbacks.""" |
713 assert self.pipeline is not None | 718 assert self.pipeline is not None |
714 if isinstance(dc_data, SourcesDataChannel): | 719 if isinstance(dc_data, SourcesDataChannel): |
715 # Data channel configuration for compatibility with browser defaults | 720 # Data channel configuration for compatibility with browser defaults |
716 data_channel_options = Gst.Structure.new_empty("data-channel-options") | 721 data_channel_options = Gst.Structure.new_empty("data-channel-options") |
727 return | 732 return |
728 data_channel.connect("on-open", dc_data.dc_open_cb) | 733 data_channel.connect("on-open", dc_data.dc_open_cb) |
729 elif isinstance(dc_data, SinksDataChannel): | 734 elif isinstance(dc_data, SinksDataChannel): |
730 self.webrtcbin.connect("on-data-channel", dc_data.dc_on_data_channel) | 735 self.webrtcbin.connect("on-data-channel", dc_data.dc_on_data_channel) |
731 else: | 736 else: |
732 raise ValueError( | 737 raise ValueError("Only SourcesDataChannel or SinksDataChannel are allowed.") |
733 "Only SourcesDataChannel or SinksDataChannel are allowed." | |
734 ) | |
735 | 738 |
736 def start_pipeline(self) -> None: | 739 def start_pipeline(self) -> None: |
737 """Starts the GStreamer pipeline.""" | 740 """Starts the GStreamer pipeline.""" |
738 log.debug("starting the pipeline") | 741 log.debug("starting the pipeline") |
739 self.pipeline.set_state(Gst.State.PLAYING) | 742 self.pipeline.set_state(Gst.State.PLAYING) |
1098 assert sdp is not None | 1101 assert sdp is not None |
1099 self.extract_ufrag_pwd(sdp) | 1102 self.extract_ufrag_pwd(sdp) |
1100 ice_data = { | 1103 ice_data = { |
1101 "ufrag": self.ufrag[media_type], | 1104 "ufrag": self.ufrag[media_type], |
1102 "pwd": self.pwd[media_type], | 1105 "pwd": self.pwd[media_type], |
1103 "candidates": [parsed_candidate] | 1106 "candidates": [parsed_candidate], |
1104 } | 1107 } |
1105 self._a_call( | 1108 self._a_call( |
1106 self.bridge.ice_candidates_add, | 1109 self.bridge.ice_candidates_add, |
1107 self.sid, | 1110 self.sid, |
1108 data_format.serialise({media_type: ice_data}), | 1111 data_format.serialise({media_type: ice_data}), |