diff libervia/cli/call_webrtc.py @ 4210:9218d4331bb2

cli (call): `tui` output implementation: - Moved original UI to a separated class, and use if with the `simple` output - By default, best output is automatically selected. For now `gui` is selected if possible, and `simple` is used as fallback. - The new `tui` output can be used to have the videos directly embedded in the terminal, either with real videos for compatible terminal emulators, or with Unicode blocks. - Text contrôls are used for both `simple` and `tui` outputs - several options can be used with `--oo` (will be documented in next commit). rel 428
author Goffi <goffi@goffi.org>
date Fri, 16 Feb 2024 18:46:06 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/cli/call_webrtc.py	Fri Feb 16 18:46:06 2024 +0100
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+
+# Libervia CLI
+# Copyright (C) 2009-2024 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from dataclasses import dataclass
+
+from libervia.backend.tools.common import data_format
+from libervia.frontends.tools import aio, jid
+
+
+@dataclass
+class CallData:
+    callee: jid.JID
+    sid: str | None = None
+    action_id: str | None = None
+
+
+class WebRTCCall:
+    def __init__(self, host, profile: str, callee: jid.JID, **kwargs):
+        """Create and setup a webRTC instance
+
+        @param profile: profile making or receiving the call
+        @param callee: peer jid
+        @param kwargs: extra kw args to use when instantiating WebRTC
+        """
+        from libervia.frontends.tools import webrtc
+
+        aio.install_glib_asyncio_iteration()
+        self.host = host
+        self.profile = profile
+        self.webrtc = webrtc.WebRTC(host.bridge, profile, **kwargs)
+        self.webrtc.callee = callee
+        host.bridge.register_signal(
+            "ice_candidates_new", self.on_ice_candidates_new, "plugin"
+        )
+        host.bridge.register_signal("call_setup", self.on_call_setup, "plugin")
+        host.bridge.register_signal("call_ended", self.on_call_ended, "plugin")
+
+    @classmethod
+    async def make_webrtc_call(
+        cls,
+        host,
+        profile: str,
+        call_data: CallData,
+        **kwargs
+    ) -> "WebRTCCall":
+        """Create the webrtc_call instance
+
+        @param call_data: Call data of the command
+        @param kwargs: extra args used to instanciate WebRTCCall
+
+        """
+        webrtc_call = cls(host, profile, call_data.callee, **kwargs)
+        if call_data.sid is None:
+            # we are making the call
+            await webrtc_call.start()
+        else:
+            # we are receiving the call
+            webrtc_call.sid = call_data.sid
+            if call_data.action_id is not None:
+                await host.bridge.action_launch(
+                    call_data.action_id,
+                    data_format.serialise({"cancelled": False}),
+                    profile
+                )
+        return webrtc_call
+
+    @property
+    def sid(self) -> str | None:
+        return self.webrtc.sid
+
+    @sid.setter
+    def sid(self, new_sid: str | None) -> None:
+        self.webrtc.sid = new_sid
+
+    async def on_ice_candidates_new(
+        self, sid: str, candidates_s: str, profile: str
+    ) -> None:
+        if sid != self.webrtc.sid or profile != self.profile:
+            return
+        self.webrtc.on_ice_candidates_new(
+            data_format.deserialise(candidates_s),
+        )
+
+    async def on_call_setup(self, sid: str, setup_data_s: str, profile: str) -> None:
+        if sid != self.webrtc.sid or profile != self.profile:
+            return
+        setup_data = data_format.deserialise(setup_data_s)
+        try:
+            role = setup_data["role"]
+            sdp = setup_data["sdp"]
+        except KeyError:
+            self.host.disp(f"Invalid setup data received: {setup_data}", error=True)
+            return
+        if role == "initiator":
+            self.webrtc.on_accepted_call(sdp, profile)
+        elif role == "responder":
+            await self.webrtc.answer_call(sdp, profile)
+        else:
+            self.host.disp(
+                f"Invalid role received during setup: {setup_data}", error=True
+            )
+        # we want to be sure that call is ended if user presses `Ctrl + c` or anything
+        # else stops the session.
+        self.host.add_on_quit_callback(
+            lambda: self.host.bridge.call_end(sid, "", profile)
+        )
+
+    async def on_call_ended(self, sid: str, data_s: str, profile: str) -> None:
+        if sid != self.webrtc.sid or profile != self.profile:
+            return
+        await self.webrtc.end_call()
+        await self.host.a_quit()
+
+    async def start(self):
+        """Start a call.
+
+        To be used only if we are initiator
+        """
+        await self.webrtc.setup_call("initiator")
+        self.webrtc.start_pipeline()