Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions joinly/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Protocol

from joinly.types import (
ActionAnimation,
AudioChunk,
AudioFormat,
MeetingChatHistory,
Expand Down Expand Up @@ -248,6 +249,10 @@ async def stop_sharing(self) -> None:
"""Stop sharing screen in the meeting."""
...

async def set_animation(self, animation: ActionAnimation | None) -> None:
"""Set an action animation on the camera feed."""
...

async def update_ui(self, update: UIUpdate) -> None:
"""Update the UI on the meeting provider.

Expand Down
4 changes: 4 additions & 0 deletions joinly/providers/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from joinly.core import MeetingProvider
from joinly.types import (
ActionAnimation,
MeetingChatHistory,
MeetingParticipant,
ProviderNotSupportedError,
Expand Down Expand Up @@ -60,5 +61,8 @@ async def stop_sharing(self) -> None:
msg = "Provider does not support stopping screen share."
raise ProviderNotSupportedError(msg)

async def set_animation(self, animation: ActionAnimation | None) -> None:
"""Set an action animation on the camera feed."""

async def update_ui(self, update: UIUpdate) -> None:
"""Update the UI on the meeting provider."""
7 changes: 3 additions & 4 deletions joinly/providers/browser/camera_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,16 +309,15 @@
{fx_busy}

const FX = {{
send_chat_message: fxTyping,
get_chat_history: fxReading,
get_participants: fxReading,
typing: fxTyping,
reading: fxReading,
interrupted: fxInterrupted,
busy: fxBusy,
}};

const FX_BG = {{
thinking: fxThinking,
share_screen: fxShare,
sharing: fxShare,
}};

function _initCanvas() {{
Expand Down
19 changes: 5 additions & 14 deletions joinly/providers/browser/meeting_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
from joinly.providers.browser.screen_share import remove_overlay, setup_content_stream
from joinly.settings import get_settings
from joinly.types import (
ActionAnimation,
AudioChunk,
MeetingChatHistory,
MeetingParticipant,
ProviderNotSupportedError,
SpeechInterruptedError,
UIAnimationContent,
UIHtmlContent,
UIUpdate,
Expand Down Expand Up @@ -197,7 +197,6 @@ async def _action_guard(
raise RuntimeError(msg)

async with self._lock:
self._camera_feed.set_effect(action)
try:
yield self._page, self._platform_controller
except Exception as e:
Expand All @@ -208,18 +207,6 @@ async def _action_guard(
raise RuntimeError(msg) from None
else:
logger.info("Successfully performed '%s'.", action)
finally:
self._camera_feed.set_effect(None)

@asynccontextmanager
async def speech_guard(self) -> AsyncIterator[None]:
"""Context manager that sets the interrupted camera status on interruption."""
try:
yield
except SpeechInterruptedError:
self._camera_feed.set_effect("interrupted")
self._camera_feed.set_effect(None)
raise

async def _get_platform_controller(self, url: str) -> BrowserPlatformController:
"""Get the platform-specific meeting controller based on the URL.
Expand Down Expand Up @@ -414,6 +401,10 @@ async def stop_sharing(self) -> None:
finally:
await self._cleanup_content_page()

async def set_animation(self, animation: ActionAnimation | None) -> None:
"""Set an action animation on the camera feed."""
self._camera_feed.set_effect(animation)

async def update_ui(self, update: UIUpdate) -> None:
"""Update the UI on the camera feed."""
if isinstance(update.content, UIAnimationContent):
Expand Down
40 changes: 30 additions & 10 deletions joinly/session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import contextlib
import logging
from collections.abc import Callable, Coroutine
from collections.abc import AsyncIterator, Callable, Coroutine
from contextlib import asynccontextmanager

from joinly.core import (
MeetingProvider,
Expand All @@ -9,8 +10,10 @@
VideoReader,
)
from joinly.types import (
ActionAnimation,
MeetingChatHistory,
MeetingParticipant,
SpeechInterruptedError,
Transcript,
UIUpdate,
VideoSnapshot,
Expand Down Expand Up @@ -129,36 +132,39 @@ async def speak_text(self, text: str) -> None:
Args:
text (str): The text to be spoken.
"""
guard = getattr(self._meeting_provider, "speech_guard", None)
if guard is not None:
async with guard():
await self._speech_controller.speak_text(text)
else:
try:
await self._speech_controller.speak_text(text)
except SpeechInterruptedError:
await self.set_animation("interrupted")
await self.set_animation(None)
raise

async def send_chat_message(self, message: str) -> None:
"""Send a chat message in the meeting.

Args:
message (str): The message to be sent.
"""
await self._meeting_provider.send_chat_message(message)
async with self.animation("typing"):
await self._meeting_provider.send_chat_message(message)

async def get_chat_history(self) -> MeetingChatHistory:
"""Get the chat history from the meeting.

Returns:
MeetingChatHistory: The chat history of the meeting.
"""
return await self._meeting_provider.get_chat_history()
async with self.animation("reading"):
return await self._meeting_provider.get_chat_history()

async def get_participants(self) -> list[MeetingParticipant]:
"""Get the list of participants in the meeting.

Returns:
list[MeetingParticipant]: A list of participants in the meeting.
"""
return await self._meeting_provider.get_participants()
async with self.animation("reading"):
return await self._meeting_provider.get_participants()

async def get_video_snapshot(self) -> VideoSnapshot:
"""Get a snapshot of the current video feed.
Expand All @@ -174,7 +180,8 @@ async def share_screen(self, url: str) -> None:
Args:
url: URL to display while sharing.
"""
await self._meeting_provider.share_screen(url)
async with self.animation("sharing"):
await self._meeting_provider.share_screen(url)

async def stop_sharing(self) -> None:
"""Stop sharing screen in the meeting."""
Expand All @@ -188,6 +195,19 @@ async def unmute(self) -> None:
"""Unmute yourself in the meeting."""
await self._meeting_provider.unmute()

async def set_animation(self, animation: ActionAnimation | None) -> None:
"""Set an action animation on the meeting provider."""
await self._meeting_provider.set_animation(animation)

@asynccontextmanager
async def animation(self, name: ActionAnimation) -> AsyncIterator[None]:
"""Show an action animation for the duration of the block."""
await self.set_animation(name)
try:
yield
finally:
await self.set_animation(None)

async def update_ui(self, update: UIUpdate) -> None:
"""Update the UI on the meeting provider.

Expand Down
4 changes: 4 additions & 0 deletions joinly/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from typing import Literal

from joinly_common.types import (
MeetingChatHistory,
Expand All @@ -16,7 +17,10 @@
VideoSnapshot,
)

ActionAnimation = Literal["typing", "reading", "interrupted", "sharing"]

__all__ = [
"ActionAnimation",
"MeetingChatHistory",
"MeetingChatMessage",
"MeetingParticipant",
Expand Down
Loading