diff --git a/.gitignore b/.gitignore index bac4de2..199ab27 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ demo/scratch .vscode .DS_Store test/ -.venv* \ No newline at end of file +.venv* +.env \ No newline at end of file diff --git a/backend/fastrtc/stream.py b/backend/fastrtc/stream.py index 52e3227..96966b0 100644 --- a/backend/fastrtc/stream.py +++ b/backend/fastrtc/stream.py @@ -42,6 +42,8 @@ class UIArgs(TypedDict): """Color of the icon button. Default is var(--color-accent) of the demo theme.""" pulse_color: NotRequired[str] """Color of the pulse animation. Default is var(--color-accent) of the demo theme.""" + icon_radius: NotRequired[int] + """Border radius of the icon button expressed as a percentage of the button size. Default is 50%.""" class Stream(WebRTCConnectionMixin): @@ -322,6 +324,7 @@ class Stream(WebRTCConnectionMixin): icon=ui_args.get("icon"), icon_button_color=ui_args.get("icon_button_color"), pulse_color=ui_args.get("pulse_color"), + icon_radius=ui_args.get("icon_radius"), ) for component in additional_output_components: if component not in same_components: @@ -358,6 +361,10 @@ class Stream(WebRTCConnectionMixin): rtc_configuration=self.rtc_configuration, mode="send-receive", modality="audio", + icon=ui_args.get("icon"), + icon_button_color=ui_args.get("icon_button_color"), + pulse_color=ui_args.get("pulse_color"), + icon_radius=ui_args.get("icon_radius"), ) for component in additional_input_components: if component not in same_components: @@ -400,6 +407,7 @@ class Stream(WebRTCConnectionMixin): icon=ui_args.get("icon"), icon_button_color=ui_args.get("icon_button_color"), pulse_color=ui_args.get("pulse_color"), + icon_radius=ui_args.get("icon_radius"), ) for component in additional_input_components: if component not in same_components: @@ -443,6 +451,7 @@ class Stream(WebRTCConnectionMixin): icon=ui_args.get("icon"), icon_button_color=ui_args.get("icon_button_color"), pulse_color=ui_args.get("pulse_color"), + icon_radius=ui_args.get("icon_radius"), ) for component in additional_input_components: if component not in same_components: @@ -552,6 +561,7 @@ class Stream(WebRTCConnectionMixin): port: int = 8000, **kwargs, ): + import atexit import secrets import threading import time @@ -563,7 +573,6 @@ class Stream(WebRTCConnectionMixin): from gradio.networking import setup_tunnel from gradio.tunneling import CURRENT_TUNNELS from huggingface_hub import get_token - import atexit app = FastAPI() diff --git a/backend/fastrtc/utils.py b/backend/fastrtc/utils.py index 1788574..e11a8cc 100644 --- a/backend/fastrtc/utils.py +++ b/backend/fastrtc/utils.py @@ -1,14 +1,15 @@ import asyncio import fractions +import functools +import inspect import io import json import logging import tempfile +import traceback from contextvars import ContextVar from typing import Any, Callable, Literal, Protocol, TypedDict, cast -import functools -import traceback -import inspect + import av import numpy as np from numpy.typing import NDArray diff --git a/backend/fastrtc/webrtc.py b/backend/fastrtc/webrtc.py index a678e87..0129c92 100644 --- a/backend/fastrtc/webrtc.py +++ b/backend/fastrtc/webrtc.py @@ -87,6 +87,7 @@ class WebRTC(Component, WebRTCConnectionMixin): icon: str | None = None, icon_button_color: str | None = None, pulse_color: str | None = None, + icon_radius: int | None = None, button_labels: dict | None = None, ): """ @@ -119,6 +120,7 @@ class WebRTC(Component, WebRTCConnectionMixin): icon_button_color: Color of the icon button. Default is var(--color-accent) of the demo theme. pulse_color: Color of the pulse animation. Default is var(--color-accent) of the demo theme. button_labels: Text to display on the audio or video start, stop, waiting buttons. Dict with keys "start", "stop", "waiting" mapping to the text to display on the buttons. + icon_radius: Border radius of the icon button expressed as a percentage of the button size. Default is 50% """ self.time_limit = time_limit self.height = height @@ -129,6 +131,7 @@ class WebRTC(Component, WebRTCConnectionMixin): self.mode = mode self.modality = modality self.icon_button_color = icon_button_color + self.icon_radius = icon_radius self.pulse_color = pulse_color self.rtp_params = rtp_params or {} self.button_labels = { diff --git a/backend/fastrtc/webrtc_connection_mixin.py b/backend/fastrtc/webrtc_connection_mixin.py index 8407bf0..7e03e46 100644 --- a/backend/fastrtc/webrtc_connection_mixin.py +++ b/backend/fastrtc/webrtc_connection_mixin.py @@ -149,14 +149,14 @@ class WebRTCConnectionMixin: if isinstance(self.event_handler, StreamHandlerBase): handler = self.event_handler.copy() - handler.emit = webrtc_error_handler(handler.emit) - handler.receive = webrtc_error_handler(handler.receive) - handler.start_up = webrtc_error_handler(handler.start_up) - handler.shutdown = webrtc_error_handler(handler.shutdown) + handler.emit = webrtc_error_handler(handler.emit) # type: ignore + handler.receive = webrtc_error_handler(handler.receive) # type: ignore + handler.start_up = webrtc_error_handler(handler.start_up) # type: ignore + handler.shutdown = webrtc_error_handler(handler.shutdown) # type: ignore if hasattr(handler, "video_receive"): - handler.video_receive = webrtc_error_handler(handler.video_receive) + handler.video_receive = webrtc_error_handler(handler.video_receive) # type: ignore if hasattr(handler, "video_emit"): - handler.video_emit = webrtc_error_handler(handler.video_emit) + handler.video_emit = webrtc_error_handler(handler.video_emit) # type: ignore else: handler = webrtc_error_handler(cast(Callable, self.event_handler)) diff --git a/demo/gemini_audio_video/app.py b/demo/gemini_audio_video/app.py index 4a9a457..f8e01a7 100644 --- a/demo/gemini_audio_video/app.py +++ b/demo/gemini_audio_video/app.py @@ -5,16 +5,16 @@ import time from io import BytesIO import gradio as gr -from gradio.utils import get_space import numpy as np -from google import genai from dotenv import load_dotenv from fastrtc import ( AsyncAudioVideoStreamHandler, Stream, - get_twilio_turn_credentials, WebRTC, + get_twilio_turn_credentials, ) +from google import genai +from gradio.utils import get_space from PIL import Image load_dotenv() diff --git a/frontend/Index.svelte b/frontend/Index.svelte index a28ee42..e458cb1 100644 --- a/frontend/Index.svelte +++ b/frontend/Index.svelte @@ -38,6 +38,7 @@ export let icon: string | undefined = undefined; export let icon_button_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)"; + export let icon_radius: number = 50; const on_change_cb = (msg: "change" | "tick" | any) => { if ( @@ -124,6 +125,7 @@ {icon} {icon_button_color} {pulse_color} + {icon_radius} i18n={gradio.i18n} on:tick={() => gradio.dispatch("tick")} on:error={({ detail }) => gradio.dispatch("error", detail)} @@ -178,6 +180,7 @@ {icon} {reject_cb} {icon_button_color} + {icon_radius} {pulse_color} {button_labels} on:tick={() => gradio.dispatch("tick")} diff --git a/frontend/shared/AudioWave.svelte b/frontend/shared/AudioWave.svelte index 8372fb9..9f99dcd 100644 --- a/frontend/shared/AudioWave.svelte +++ b/frontend/shared/AudioWave.svelte @@ -11,6 +11,7 @@ export let icon_button_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)"; export let pending: boolean = false; + export let icon_radius: number = 50; let audioContext: AudioContext; let analyser: AnalyserNode; @@ -34,6 +35,7 @@ }); function setupAudioContext() { + // @ts-ignore audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); const source = audioContext.createMediaStreamSource( @@ -58,6 +60,7 @@ ); for (let i = 0; i < bars.length; i++) { const barHeight = (dataArray[i] / 255) * 2; + // @ts-ignore bars[i].style.transform = `scaleY(${Math.max(0.1, barHeight)})`; } @@ -78,6 +81,7 @@ {pulse_color} {icon} {icon_button_color} + {icon_radius} {audio_source_callback} /> diff --git a/frontend/shared/InteractiveAudio.svelte b/frontend/shared/InteractiveAudio.svelte index 2a4f011..2611f6a 100644 --- a/frontend/shared/InteractiveAudio.svelte +++ b/frontend/shared/InteractiveAudio.svelte @@ -33,6 +33,7 @@ export let icon: string | undefined = undefined; export let icon_button_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)"; + export let icon_radius: number = 50; export let button_labels: { start: string; stop: string; waiting: string }; let pending = false; @@ -291,6 +292,7 @@ {icon_button_color} {pulse_color} {pending} + {icon_radius} />
diff --git a/frontend/shared/InteractiveVideo.svelte b/frontend/shared/InteractiveVideo.svelte index 53de457..f223acc 100644 --- a/frontend/shared/InteractiveVideo.svelte +++ b/frontend/shared/InteractiveVideo.svelte @@ -63,6 +63,7 @@ {icon_button_color} {pulse_color} {button_labels} + {icon_radius} on:error on:start_recording on:stop_recording diff --git a/frontend/shared/PulsingIcon.svelte b/frontend/shared/PulsingIcon.svelte index 2fab0b4..041a503 100644 --- a/frontend/shared/PulsingIcon.svelte +++ b/frontend/shared/PulsingIcon.svelte @@ -7,6 +7,7 @@ export let icon: string | ComponentType = undefined; export let icon_button_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)"; + export let icon_radius: number = 50; let audioContext: AudioContext; let analyser: AnalyserNode; @@ -27,6 +28,7 @@ }); function setupAudioContext() { + // @ts-ignore audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); const source = audioContext.createMediaStreamSource( @@ -77,7 +79,12 @@ style:background={icon_button_color} > {#if typeof icon === "string"} - Audio visualization icon + Audio visualization icon {:else if icon === undefined}
{:else} @@ -123,7 +130,6 @@ width: 100%; height: 100%; object-fit: contain; - border-radius: 50%; } .pulse-ring { diff --git a/frontend/shared/StaticAudio.svelte b/frontend/shared/StaticAudio.svelte index 69ea37e..8ef347d 100644 --- a/frontend/shared/StaticAudio.svelte +++ b/frontend/shared/StaticAudio.svelte @@ -18,6 +18,7 @@ export let icon: string | undefined = undefined; export let icon_button_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)"; + export let icon_radius: number = 50; export let server: { offer: (body: any) => Promise; @@ -65,6 +66,7 @@ }); let stream = null; const timeoutId = setTimeout(() => { + // @ts-ignore on_change_cb({ type: "connection_timeout" }); }, 5000); @@ -120,6 +122,7 @@ {icon} {icon_button_color} {pulse_color} + {icon_radius} />
{/if} diff --git a/pyproject.toml b/pyproject.toml index 039cc77..f67925b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "hatchling.build" [project] name = "fastrtc" -version = "0.0.5.post2" +version = "0.0.6" description = "The realtime communication library for Python" readme = "README.md" license = "apache-2.0" @@ -83,3 +83,4 @@ packages = ["/backend/fastrtc"] [tool.ruff] target-version = "py310" +extend-exclude = ["demo/phonic_chat"]