Mercurial > libervia-web
diff libervia/web/pages/calls/_browser/__init__.py @ 1602:6feac4a25e60
browser: Remote Control implementation:
- Add `cbor-x` JS dependency.
- In "Call" page, a Remote Control session can now be started. This is done by clicking on
a search item 3 dots menu. Libervia Web will act as a controlling device. The call box
is then adapted, and mouse/wheel and keyboard events are sent to remote, touch events
are converted to mouse one.
- Some Brython 3.12* related changes.
rel 436
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 11 May 2024 14:02:22 +0200 |
parents | 0a4433a343a3 |
children | 4a9679369856 |
line wrap: on
line diff
--- a/libervia/web/pages/calls/_browser/__init__.py Sat May 11 13:57:49 2024 +0200 +++ b/libervia/web/pages/calls/_browser/__init__.py Sat May 11 14:02:22 2024 +0200 @@ -4,7 +4,7 @@ from browser import aio, console as log, document, window from cache import cache import dialog -from javascript import JSObject +from javascript import JSObject, NULL from jid import JID from jid_search import JidSearch import loading @@ -27,7 +27,8 @@ ) AUDIO = "audio" VIDEO = "video" -ALLOWED_CALL_MODES = {AUDIO, VIDEO} +REMOTE = "remote-control" +ALLOWED_CALL_MODES = {AUDIO, VIDEO, REMOTE} INACTIVE_CLASS = "inactive" MUTED_CLASS = "muted" SCREEN_OFF_CLASS = "screen-off" @@ -107,6 +108,9 @@ ".click-to-audio": lambda evt, item: aio.run( self.on_entity_action(evt, AUDIO, item) ), + ".click-to-remote-control": lambda evt, item: aio.run( + self.on_entity_action(evt, REMOTE, item) + ), }, }, ) @@ -155,13 +159,28 @@ if mode in ALLOWED_CALL_MODES: if self._call_mode == mode: return + log.debug("Switching to {mode} call mode.") self._call_mode = mode - with_video = mode == VIDEO - for elt in self.call_box_elt.select(".is-video-only"): - if with_video: + selector = ".is-video-only, .is-not-remote" + for elt in self.call_box_elt.select(selector): + if mode == VIDEO: + # In video, all elements are visible. elt.classList.remove("is-hidden") + elif mode == AUDIO: + # In audio, we hide video-only elements. + if elt.classList.contains("is-video-only"): + elt.classList.add("is-hidden") + else: + elt.classList.remove("is-hidden") + elif mode == REMOTE: + # In remote, we show all video element, except if they are + # `is-not-remote` + if elt.classList.contains("is-not-remote"): + elt.classList.add("is-hidden") + else: + elt.classList.remove("is-hidden") else: - elt.classList.add("is-hidden") + raise Exception("This line should never be reached.") else: raise ValueError("Invalid call mode") @@ -190,7 +209,10 @@ @param profile: Profile associated with the action """ action_data = json.loads(action_data_s) - if action_data.get("type") == "confirm" and action_data.get("subtype") == "file": + if ( + action_data.get("type") in ("confirm", "not_in_roster_leak") + and action_data.get("subtype") == "file" + ): aio.run(self.on_file_preflight(action_data, action_id)) elif action_data.get("type") == "file": aio.run(self.on_file_proposal(action_data, action_id)) @@ -286,7 +308,7 @@ # TODO: Check if any other frontend is connected for this profile, and refuse # the file if none is. return - if action_data.get("file_accepted", False): + if action_data.get("pre_accepted", False): # File proposal has already been accepted in preflight. accepted = True else: @@ -472,13 +494,24 @@ btn_elt.classList.remove(INACTIVE_CLASS, MUTED_CLASS, "is-warning") btn_elt.classList.add("is-success") - async def make_call(self, audio: bool = True, video: bool = True) -> None: + async def make_call( + self, + audio: bool = True, + video: bool = True, + remote: bool = False + ) -> None: """Start a WebRTC call @param audio: True if an audio flux is required @param video: True if a video flux is required + @param remote: True if this is a Remote Control session. """ - self.call_mode = VIDEO if video else AUDIO + if remote: + self.call_mode = REMOTE + elif video: + self.call_mode = VIDEO + else: + self.call_mode = AUDIO try: callee_jid = JID(self.search_elt.value.strip()) if not callee_jid.is_valid: @@ -495,7 +528,12 @@ self.set_avatar(callee_jid) self.switch_mode("call") - await self.webrtc.make_call(callee_jid, audio, video) + if remote: + await self.webrtc.start_remote_control( + callee_jid, audio, video + ) + else: + await self.webrtc.make_call(callee_jid, audio, video) async def end_call(self, data: dict) -> None: """Stop streaming and clean instance""" @@ -612,18 +650,17 @@ @param fullscreen: if set, determine the fullscreen state; otherwise, the fullscreen mode will be toggled. """ - do_fullscreen = ( - document.fullscreenElement is None if fullscreen is None else fullscreen - ) + if fullscreen is None: + fullscreen = document.fullscreenElement is NULL try: - if do_fullscreen: - if document.fullscreenElement is None: + if fullscreen: + if document.fullscreenElement is NULL: self.call_box_elt.requestFullscreen() document["full_screen_btn"].classList.add("is-hidden") document["exit_full_screen_btn"].classList.remove("is-hidden") else: - if document.fullscreenElement is not None: + if document.fullscreenElement is not NULL: document.exitFullscreen() document["full_screen_btn"].classList.remove("is-hidden") document["exit_full_screen_btn"].classList.add("is-hidden") @@ -711,11 +748,15 @@ evt.stopPropagation() if action == "menu": evt.currentTarget.parent.classList.toggle("is-active") - elif action in (VIDEO, AUDIO): + elif action in (VIDEO, AUDIO, REMOTE): self.search_elt.value = item["entity"] # we want the dropdown to be inactive evt.currentTarget.closest(".dropdown").classList.remove("is-active") - await self.make_call(video=action == VIDEO) + if action == REMOTE: + await self.make_call(audio=False, video=True, remote=True) + + else: + await self.make_call(video=action == VIDEO) CallUI()