dv-pipecat-ai 0.0.74.dev770__py3-none-any.whl → 0.0.82.dev776__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dv-pipecat-ai might be problematic. Click here for more details.
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/METADATA +137 -93
- dv_pipecat_ai-0.0.82.dev776.dist-info/RECORD +340 -0
- pipecat/__init__.py +17 -0
- pipecat/adapters/base_llm_adapter.py +36 -1
- pipecat/adapters/schemas/direct_function.py +296 -0
- pipecat/adapters/schemas/function_schema.py +15 -6
- pipecat/adapters/schemas/tools_schema.py +55 -7
- pipecat/adapters/services/anthropic_adapter.py +22 -3
- pipecat/adapters/services/aws_nova_sonic_adapter.py +23 -3
- pipecat/adapters/services/bedrock_adapter.py +22 -3
- pipecat/adapters/services/gemini_adapter.py +16 -3
- pipecat/adapters/services/open_ai_adapter.py +17 -2
- pipecat/adapters/services/open_ai_realtime_adapter.py +23 -3
- pipecat/audio/filters/base_audio_filter.py +30 -6
- pipecat/audio/filters/koala_filter.py +37 -2
- pipecat/audio/filters/krisp_filter.py +59 -6
- pipecat/audio/filters/noisereduce_filter.py +37 -0
- pipecat/audio/interruptions/base_interruption_strategy.py +25 -5
- pipecat/audio/interruptions/min_words_interruption_strategy.py +21 -4
- pipecat/audio/mixers/base_audio_mixer.py +30 -7
- pipecat/audio/mixers/soundfile_mixer.py +53 -6
- pipecat/audio/resamplers/base_audio_resampler.py +17 -9
- pipecat/audio/resamplers/resampy_resampler.py +26 -1
- pipecat/audio/resamplers/soxr_resampler.py +32 -1
- pipecat/audio/resamplers/soxr_stream_resampler.py +101 -0
- pipecat/audio/utils.py +194 -1
- pipecat/audio/vad/silero.py +60 -3
- pipecat/audio/vad/vad_analyzer.py +114 -30
- pipecat/clocks/base_clock.py +19 -0
- pipecat/clocks/system_clock.py +25 -0
- pipecat/extensions/voicemail/__init__.py +0 -0
- pipecat/extensions/voicemail/voicemail_detector.py +707 -0
- pipecat/frames/frames.py +590 -156
- pipecat/metrics/metrics.py +64 -1
- pipecat/observers/base_observer.py +58 -19
- pipecat/observers/loggers/debug_log_observer.py +56 -64
- pipecat/observers/loggers/llm_log_observer.py +8 -1
- pipecat/observers/loggers/transcription_log_observer.py +19 -7
- pipecat/observers/loggers/user_bot_latency_log_observer.py +32 -5
- pipecat/observers/turn_tracking_observer.py +26 -1
- pipecat/pipeline/base_pipeline.py +5 -7
- pipecat/pipeline/base_task.py +52 -9
- pipecat/pipeline/parallel_pipeline.py +121 -177
- pipecat/pipeline/pipeline.py +129 -20
- pipecat/pipeline/runner.py +50 -1
- pipecat/pipeline/sync_parallel_pipeline.py +132 -32
- pipecat/pipeline/task.py +263 -280
- pipecat/pipeline/task_observer.py +85 -34
- pipecat/pipeline/to_be_updated/merge_pipeline.py +32 -2
- pipecat/processors/aggregators/dtmf_aggregator.py +29 -22
- pipecat/processors/aggregators/gated.py +25 -24
- pipecat/processors/aggregators/gated_openai_llm_context.py +22 -2
- pipecat/processors/aggregators/llm_response.py +398 -89
- pipecat/processors/aggregators/openai_llm_context.py +161 -13
- pipecat/processors/aggregators/sentence.py +25 -14
- pipecat/processors/aggregators/user_response.py +28 -3
- pipecat/processors/aggregators/vision_image_frame.py +24 -14
- pipecat/processors/async_generator.py +28 -0
- pipecat/processors/audio/audio_buffer_processor.py +78 -37
- pipecat/processors/consumer_processor.py +25 -6
- pipecat/processors/filters/frame_filter.py +23 -0
- pipecat/processors/filters/function_filter.py +30 -0
- pipecat/processors/filters/identity_filter.py +17 -2
- pipecat/processors/filters/null_filter.py +24 -1
- pipecat/processors/filters/stt_mute_filter.py +56 -21
- pipecat/processors/filters/wake_check_filter.py +46 -3
- pipecat/processors/filters/wake_notifier_filter.py +21 -3
- pipecat/processors/frame_processor.py +488 -131
- pipecat/processors/frameworks/langchain.py +38 -3
- pipecat/processors/frameworks/rtvi.py +719 -34
- pipecat/processors/gstreamer/pipeline_source.py +41 -0
- pipecat/processors/idle_frame_processor.py +26 -3
- pipecat/processors/logger.py +23 -0
- pipecat/processors/metrics/frame_processor_metrics.py +77 -4
- pipecat/processors/metrics/sentry.py +42 -4
- pipecat/processors/producer_processor.py +34 -14
- pipecat/processors/text_transformer.py +22 -10
- pipecat/processors/transcript_processor.py +48 -29
- pipecat/processors/user_idle_processor.py +31 -21
- pipecat/runner/__init__.py +1 -0
- pipecat/runner/daily.py +132 -0
- pipecat/runner/livekit.py +148 -0
- pipecat/runner/run.py +543 -0
- pipecat/runner/types.py +67 -0
- pipecat/runner/utils.py +515 -0
- pipecat/serializers/base_serializer.py +42 -0
- pipecat/serializers/exotel.py +17 -6
- pipecat/serializers/genesys.py +95 -0
- pipecat/serializers/livekit.py +33 -0
- pipecat/serializers/plivo.py +16 -15
- pipecat/serializers/protobuf.py +37 -1
- pipecat/serializers/telnyx.py +18 -17
- pipecat/serializers/twilio.py +32 -16
- pipecat/services/ai_service.py +5 -3
- pipecat/services/anthropic/llm.py +113 -43
- pipecat/services/assemblyai/models.py +63 -5
- pipecat/services/assemblyai/stt.py +64 -11
- pipecat/services/asyncai/__init__.py +0 -0
- pipecat/services/asyncai/tts.py +501 -0
- pipecat/services/aws/llm.py +185 -111
- pipecat/services/aws/stt.py +217 -23
- pipecat/services/aws/tts.py +118 -52
- pipecat/services/aws/utils.py +101 -5
- pipecat/services/aws_nova_sonic/aws.py +82 -64
- pipecat/services/aws_nova_sonic/context.py +15 -6
- pipecat/services/azure/common.py +10 -2
- pipecat/services/azure/image.py +32 -0
- pipecat/services/azure/llm.py +9 -7
- pipecat/services/azure/stt.py +65 -2
- pipecat/services/azure/tts.py +154 -23
- pipecat/services/cartesia/stt.py +125 -8
- pipecat/services/cartesia/tts.py +102 -38
- pipecat/services/cerebras/llm.py +15 -23
- pipecat/services/deepgram/stt.py +19 -11
- pipecat/services/deepgram/tts.py +36 -0
- pipecat/services/deepseek/llm.py +14 -23
- pipecat/services/elevenlabs/tts.py +330 -64
- pipecat/services/fal/image.py +43 -0
- pipecat/services/fal/stt.py +48 -10
- pipecat/services/fireworks/llm.py +14 -21
- pipecat/services/fish/tts.py +109 -9
- pipecat/services/gemini_multimodal_live/__init__.py +1 -0
- pipecat/services/gemini_multimodal_live/events.py +83 -2
- pipecat/services/gemini_multimodal_live/file_api.py +189 -0
- pipecat/services/gemini_multimodal_live/gemini.py +218 -21
- pipecat/services/gladia/config.py +17 -10
- pipecat/services/gladia/stt.py +82 -36
- pipecat/services/google/frames.py +40 -0
- pipecat/services/google/google.py +2 -0
- pipecat/services/google/image.py +39 -2
- pipecat/services/google/llm.py +176 -58
- pipecat/services/google/llm_openai.py +26 -4
- pipecat/services/google/llm_vertex.py +37 -15
- pipecat/services/google/rtvi.py +41 -0
- pipecat/services/google/stt.py +65 -17
- pipecat/services/google/test-google-chirp.py +45 -0
- pipecat/services/google/tts.py +390 -19
- pipecat/services/grok/llm.py +8 -6
- pipecat/services/groq/llm.py +8 -6
- pipecat/services/groq/stt.py +13 -9
- pipecat/services/groq/tts.py +40 -0
- pipecat/services/hamsa/__init__.py +9 -0
- pipecat/services/hamsa/stt.py +241 -0
- pipecat/services/heygen/__init__.py +5 -0
- pipecat/services/heygen/api.py +281 -0
- pipecat/services/heygen/client.py +620 -0
- pipecat/services/heygen/video.py +338 -0
- pipecat/services/image_service.py +5 -3
- pipecat/services/inworld/__init__.py +1 -0
- pipecat/services/inworld/tts.py +592 -0
- pipecat/services/llm_service.py +127 -45
- pipecat/services/lmnt/tts.py +80 -7
- pipecat/services/mcp_service.py +85 -44
- pipecat/services/mem0/memory.py +42 -13
- pipecat/services/minimax/tts.py +74 -15
- pipecat/services/mistral/__init__.py +0 -0
- pipecat/services/mistral/llm.py +185 -0
- pipecat/services/moondream/vision.py +55 -10
- pipecat/services/neuphonic/tts.py +275 -48
- pipecat/services/nim/llm.py +8 -6
- pipecat/services/ollama/llm.py +27 -7
- pipecat/services/openai/base_llm.py +54 -16
- pipecat/services/openai/image.py +30 -0
- pipecat/services/openai/llm.py +7 -5
- pipecat/services/openai/stt.py +13 -9
- pipecat/services/openai/tts.py +42 -10
- pipecat/services/openai_realtime_beta/azure.py +11 -9
- pipecat/services/openai_realtime_beta/context.py +7 -5
- pipecat/services/openai_realtime_beta/events.py +10 -7
- pipecat/services/openai_realtime_beta/openai.py +37 -18
- pipecat/services/openpipe/llm.py +30 -24
- pipecat/services/openrouter/llm.py +9 -7
- pipecat/services/perplexity/llm.py +15 -19
- pipecat/services/piper/tts.py +26 -12
- pipecat/services/playht/tts.py +227 -65
- pipecat/services/qwen/llm.py +8 -6
- pipecat/services/rime/tts.py +128 -17
- pipecat/services/riva/stt.py +160 -22
- pipecat/services/riva/tts.py +67 -2
- pipecat/services/sambanova/llm.py +19 -17
- pipecat/services/sambanova/stt.py +14 -8
- pipecat/services/sarvam/tts.py +60 -13
- pipecat/services/simli/video.py +82 -21
- pipecat/services/soniox/__init__.py +0 -0
- pipecat/services/soniox/stt.py +398 -0
- pipecat/services/speechmatics/stt.py +29 -17
- pipecat/services/stt_service.py +47 -11
- pipecat/services/tavus/video.py +94 -25
- pipecat/services/together/llm.py +8 -6
- pipecat/services/tts_service.py +77 -53
- pipecat/services/ultravox/stt.py +46 -43
- pipecat/services/vision_service.py +5 -3
- pipecat/services/websocket_service.py +12 -11
- pipecat/services/whisper/base_stt.py +58 -12
- pipecat/services/whisper/stt.py +69 -58
- pipecat/services/xtts/tts.py +59 -2
- pipecat/sync/base_notifier.py +19 -0
- pipecat/sync/event_notifier.py +24 -0
- pipecat/tests/utils.py +73 -5
- pipecat/transcriptions/language.py +24 -0
- pipecat/transports/base_input.py +112 -8
- pipecat/transports/base_output.py +235 -13
- pipecat/transports/base_transport.py +119 -0
- pipecat/transports/local/audio.py +76 -0
- pipecat/transports/local/tk.py +84 -0
- pipecat/transports/network/fastapi_websocket.py +174 -15
- pipecat/transports/network/small_webrtc.py +383 -39
- pipecat/transports/network/webrtc_connection.py +214 -8
- pipecat/transports/network/websocket_client.py +171 -1
- pipecat/transports/network/websocket_server.py +147 -9
- pipecat/transports/services/daily.py +792 -70
- pipecat/transports/services/helpers/daily_rest.py +122 -129
- pipecat/transports/services/livekit.py +339 -4
- pipecat/transports/services/tavus.py +273 -38
- pipecat/utils/asyncio/task_manager.py +92 -186
- pipecat/utils/base_object.py +83 -1
- pipecat/utils/network.py +2 -0
- pipecat/utils/string.py +114 -58
- pipecat/utils/text/base_text_aggregator.py +44 -13
- pipecat/utils/text/base_text_filter.py +46 -0
- pipecat/utils/text/markdown_text_filter.py +70 -14
- pipecat/utils/text/pattern_pair_aggregator.py +18 -14
- pipecat/utils/text/simple_text_aggregator.py +43 -2
- pipecat/utils/text/skip_tags_aggregator.py +21 -13
- pipecat/utils/time.py +36 -0
- pipecat/utils/tracing/class_decorators.py +32 -7
- pipecat/utils/tracing/conversation_context_provider.py +12 -2
- pipecat/utils/tracing/service_attributes.py +80 -64
- pipecat/utils/tracing/service_decorators.py +48 -21
- pipecat/utils/tracing/setup.py +13 -7
- pipecat/utils/tracing/turn_context_provider.py +12 -2
- pipecat/utils/tracing/turn_trace_observer.py +27 -0
- pipecat/utils/utils.py +14 -14
- dv_pipecat_ai-0.0.74.dev770.dist-info/RECORD +0 -319
- pipecat/examples/daily_runner.py +0 -64
- pipecat/examples/run.py +0 -265
- pipecat/utils/asyncio/watchdog_async_iterator.py +0 -72
- pipecat/utils/asyncio/watchdog_event.py +0 -42
- pipecat/utils/asyncio/watchdog_priority_queue.py +0 -48
- pipecat/utils/asyncio/watchdog_queue.py +0 -48
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/top_level.txt +0 -0
- /pipecat/{examples → extensions}/__init__.py +0 -0
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
+
"""Daily transport implementation for Pipecat.
|
|
8
|
+
|
|
9
|
+
This module provides comprehensive Daily video conferencing integration including
|
|
10
|
+
audio/video streaming, transcription, recording, dial-in/out functionality, and
|
|
11
|
+
real-time communication features.
|
|
12
|
+
"""
|
|
13
|
+
|
|
7
14
|
import asyncio
|
|
8
15
|
import time
|
|
16
|
+
from concurrent.futures import CancelledError as FuturesCancelledError
|
|
9
17
|
from concurrent.futures import ThreadPoolExecutor
|
|
10
18
|
from dataclasses import dataclass
|
|
11
19
|
from typing import Any, Awaitable, Callable, Dict, Mapping, Optional
|
|
@@ -20,6 +28,7 @@ from pipecat.frames.frames import (
|
|
|
20
28
|
EndFrame,
|
|
21
29
|
ErrorFrame,
|
|
22
30
|
Frame,
|
|
31
|
+
InputAudioRawFrame,
|
|
23
32
|
InterimTranscriptionFrame,
|
|
24
33
|
OutputAudioRawFrame,
|
|
25
34
|
OutputDTMFFrame,
|
|
@@ -40,7 +49,6 @@ from pipecat.transports.base_input import BaseInputTransport
|
|
|
40
49
|
from pipecat.transports.base_output import BaseOutputTransport
|
|
41
50
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
|
42
51
|
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
|
43
|
-
from pipecat.utils.asyncio.watchdog_queue import WatchdogQueue
|
|
44
52
|
|
|
45
53
|
try:
|
|
46
54
|
from daily import (
|
|
@@ -52,6 +60,10 @@ try:
|
|
|
52
60
|
EventHandler,
|
|
53
61
|
VideoFrame,
|
|
54
62
|
VirtualCameraDevice,
|
|
63
|
+
VirtualSpeakerDevice,
|
|
64
|
+
)
|
|
65
|
+
from daily import (
|
|
66
|
+
LogLevel as DailyLogLevel,
|
|
55
67
|
)
|
|
56
68
|
except ModuleNotFoundError as e:
|
|
57
69
|
logger.error(f"Exception: {e}")
|
|
@@ -67,7 +79,7 @@ VAD_RESET_PERIOD_MS = 2000
|
|
|
67
79
|
class DailyTransportMessageFrame(TransportMessageFrame):
|
|
68
80
|
"""Frame for transport messages in Daily calls.
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
Parameters:
|
|
71
83
|
participant_id: Optional ID of the participant this message is for/from.
|
|
72
84
|
"""
|
|
73
85
|
|
|
@@ -78,7 +90,7 @@ class DailyTransportMessageFrame(TransportMessageFrame):
|
|
|
78
90
|
class DailyTransportMessageUrgentFrame(TransportMessageUrgentFrame):
|
|
79
91
|
"""Frame for urgent transport messages in Daily calls.
|
|
80
92
|
|
|
81
|
-
|
|
93
|
+
Parameters:
|
|
82
94
|
participant_id: Optional ID of the participant this message is for/from.
|
|
83
95
|
"""
|
|
84
96
|
|
|
@@ -89,13 +101,15 @@ class WebRTCVADAnalyzer(VADAnalyzer):
|
|
|
89
101
|
"""Voice Activity Detection analyzer using WebRTC.
|
|
90
102
|
|
|
91
103
|
Implements voice activity detection using Daily's native WebRTC VAD.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
sample_rate: Audio sample rate in Hz.
|
|
95
|
-
params: VAD configuration parameters (VADParams).
|
|
96
104
|
"""
|
|
97
105
|
|
|
98
106
|
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None):
|
|
107
|
+
"""Initialize the WebRTC VAD analyzer.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
sample_rate: Audio sample rate in Hz.
|
|
111
|
+
params: VAD configuration parameters.
|
|
112
|
+
"""
|
|
99
113
|
super().__init__(sample_rate=sample_rate, params=params)
|
|
100
114
|
|
|
101
115
|
self._webrtc_vad = Daily.create_native_vad(
|
|
@@ -104,9 +118,22 @@ class WebRTCVADAnalyzer(VADAnalyzer):
|
|
|
104
118
|
logger.debug("Loaded native WebRTC VAD")
|
|
105
119
|
|
|
106
120
|
def num_frames_required(self) -> int:
|
|
121
|
+
"""Get the number of audio frames required for VAD analysis.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The number of frames needed (equivalent to 10ms of audio).
|
|
125
|
+
"""
|
|
107
126
|
return int(self.sample_rate / 100.0)
|
|
108
127
|
|
|
109
128
|
def voice_confidence(self, buffer) -> float:
|
|
129
|
+
"""Analyze audio buffer and return voice confidence score.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
buffer: Audio buffer to analyze.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Voice confidence score between 0.0 and 1.0.
|
|
136
|
+
"""
|
|
110
137
|
confidence = 0
|
|
111
138
|
if len(buffer) > 0:
|
|
112
139
|
confidence = self._webrtc_vad.analyze_frames(buffer)
|
|
@@ -116,7 +143,7 @@ class WebRTCVADAnalyzer(VADAnalyzer):
|
|
|
116
143
|
class DailyDialinSettings(BaseModel):
|
|
117
144
|
"""Settings for Daily's dial-in functionality.
|
|
118
145
|
|
|
119
|
-
|
|
146
|
+
Parameters:
|
|
120
147
|
call_id: CallId is represented by UUID and represents the sessionId in the SIP Network.
|
|
121
148
|
call_domain: Call Domain is represented by UUID and represents your Daily Domain on the SIP Network.
|
|
122
149
|
"""
|
|
@@ -128,7 +155,7 @@ class DailyDialinSettings(BaseModel):
|
|
|
128
155
|
class DailyTranscriptionSettings(BaseModel):
|
|
129
156
|
"""Configuration settings for Daily's transcription service.
|
|
130
157
|
|
|
131
|
-
|
|
158
|
+
Parameters:
|
|
132
159
|
language: ISO language code for transcription (e.g. "en").
|
|
133
160
|
model: Transcription model to use (e.g. "nova-2-general").
|
|
134
161
|
profanity_filter: Whether to filter profanity from transcripts.
|
|
@@ -152,18 +179,20 @@ class DailyTranscriptionSettings(BaseModel):
|
|
|
152
179
|
class DailyParams(TransportParams):
|
|
153
180
|
"""Configuration parameters for Daily transport.
|
|
154
181
|
|
|
155
|
-
|
|
156
|
-
api_url: Daily API base URL
|
|
157
|
-
api_key: Daily API authentication key
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
182
|
+
Parameters:
|
|
183
|
+
api_url: Daily API base URL.
|
|
184
|
+
api_key: Daily API authentication key.
|
|
185
|
+
audio_in_user_tracks: Receive users' audio in separate tracks
|
|
186
|
+
dialin_settings: Optional settings for dial-in functionality.
|
|
187
|
+
camera_out_enabled: Whether to enable the main camera output track.
|
|
188
|
+
microphone_out_enabled: Whether to enable the main microphone track.
|
|
189
|
+
transcription_enabled: Whether to enable speech transcription.
|
|
190
|
+
transcription_settings: Configuration for transcription service.
|
|
163
191
|
"""
|
|
164
192
|
|
|
165
193
|
api_url: str = "https://api.daily.co/v1"
|
|
166
194
|
api_key: str = ""
|
|
195
|
+
audio_in_user_tracks: bool = True
|
|
167
196
|
dialin_settings: Optional[DailyDialinSettings] = None
|
|
168
197
|
camera_out_enabled: bool = True
|
|
169
198
|
microphone_out_enabled: bool = True
|
|
@@ -174,7 +203,7 @@ class DailyParams(TransportParams):
|
|
|
174
203
|
class DailyCallbacks(BaseModel):
|
|
175
204
|
"""Callback handlers for Daily events.
|
|
176
205
|
|
|
177
|
-
|
|
206
|
+
Parameters:
|
|
178
207
|
on_active_speaker_changed: Called when the active speaker of the call has changed.
|
|
179
208
|
on_joined: Called when bot successfully joined a room.
|
|
180
209
|
on_left: Called when bot left a room.
|
|
@@ -197,6 +226,8 @@ class DailyCallbacks(BaseModel):
|
|
|
197
226
|
on_participant_left: Called when a participant leaves.
|
|
198
227
|
on_participant_updated: Called when participant info is updated.
|
|
199
228
|
on_transcription_message: Called when receiving transcription.
|
|
229
|
+
on_transcription_stopped: Called when transcription is stopped.
|
|
230
|
+
on_transcription_error: Called when transcription encounters an error.
|
|
200
231
|
on_recording_started: Called when recording starts.
|
|
201
232
|
on_recording_stopped: Called when recording stops.
|
|
202
233
|
on_recording_error: Called when recording encounters an error.
|
|
@@ -224,12 +255,23 @@ class DailyCallbacks(BaseModel):
|
|
|
224
255
|
on_participant_left: Callable[[Mapping[str, Any], str], Awaitable[None]]
|
|
225
256
|
on_participant_updated: Callable[[Mapping[str, Any]], Awaitable[None]]
|
|
226
257
|
on_transcription_message: Callable[[Mapping[str, Any]], Awaitable[None]]
|
|
258
|
+
on_transcription_stopped: Callable[[str, bool], Awaitable[None]]
|
|
259
|
+
on_transcription_error: Callable[[str], Awaitable[None]]
|
|
227
260
|
on_recording_started: Callable[[Mapping[str, Any]], Awaitable[None]]
|
|
228
261
|
on_recording_stopped: Callable[[str], Awaitable[None]]
|
|
229
262
|
on_recording_error: Callable[[str, str], Awaitable[None]]
|
|
230
263
|
|
|
231
264
|
|
|
232
265
|
def completion_callback(future):
|
|
266
|
+
"""Create a completion callback for Daily API calls.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
future: The asyncio Future to set the result on.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
A callback function that sets the future result.
|
|
273
|
+
"""
|
|
274
|
+
|
|
233
275
|
def _callback(*args):
|
|
234
276
|
def set_result(future, *args):
|
|
235
277
|
try:
|
|
@@ -247,6 +289,13 @@ def completion_callback(future):
|
|
|
247
289
|
|
|
248
290
|
@dataclass
|
|
249
291
|
class DailyAudioTrack:
|
|
292
|
+
"""Container for Daily audio track components.
|
|
293
|
+
|
|
294
|
+
Parameters:
|
|
295
|
+
source: The custom audio source for the track.
|
|
296
|
+
track: The custom audio track instance.
|
|
297
|
+
"""
|
|
298
|
+
|
|
250
299
|
source: CustomAudioSource
|
|
251
300
|
track: CustomAudioTrack
|
|
252
301
|
|
|
@@ -254,21 +303,14 @@ class DailyAudioTrack:
|
|
|
254
303
|
class DailyTransportClient(EventHandler):
|
|
255
304
|
"""Core client for interacting with Daily's API.
|
|
256
305
|
|
|
257
|
-
Manages the connection to Daily rooms and handles all low-level API interactions
|
|
258
|
-
|
|
259
|
-
Args:
|
|
260
|
-
room_url: URL of the Daily room to connect to.
|
|
261
|
-
token: Optional authentication token for the room.
|
|
262
|
-
bot_name: Display name for the bot in the call.
|
|
263
|
-
params: Configuration parameters (DailyParams).
|
|
264
|
-
callbacks: Event callback handlers (DailyCallbacks).
|
|
265
|
-
transport_name: Name identifier for the transport.
|
|
306
|
+
Manages the connection to Daily rooms and handles all low-level API interactions
|
|
307
|
+
including room management, media streaming, transcription, and event handling.
|
|
266
308
|
"""
|
|
267
309
|
|
|
268
310
|
_daily_initialized: bool = False
|
|
269
311
|
|
|
270
|
-
# This is necessary to override EventHandler's __new__ method.
|
|
271
312
|
def __new__(cls, *args, **kwargs):
|
|
313
|
+
"""Override EventHandler's __new__ method to ensure Daily is initialized only once."""
|
|
272
314
|
return super().__new__(cls)
|
|
273
315
|
|
|
274
316
|
def __init__(
|
|
@@ -280,6 +322,16 @@ class DailyTransportClient(EventHandler):
|
|
|
280
322
|
callbacks: DailyCallbacks,
|
|
281
323
|
transport_name: str,
|
|
282
324
|
):
|
|
325
|
+
"""Initialize the Daily transport client.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
room_url: URL of the Daily room to connect to.
|
|
329
|
+
token: Optional authentication token for the room.
|
|
330
|
+
bot_name: Display name for the bot in the call.
|
|
331
|
+
params: Configuration parameters for the transport.
|
|
332
|
+
callbacks: Event callback handlers.
|
|
333
|
+
transport_name: Name identifier for the transport.
|
|
334
|
+
"""
|
|
283
335
|
super().__init__()
|
|
284
336
|
|
|
285
337
|
if not DailyTransportClient._daily_initialized:
|
|
@@ -305,7 +357,6 @@ class DailyTransportClient(EventHandler):
|
|
|
305
357
|
self._leave_counter = 0
|
|
306
358
|
|
|
307
359
|
self._task_manager: Optional[BaseTaskManager] = None
|
|
308
|
-
self._watchdog_timers_enabled = False
|
|
309
360
|
|
|
310
361
|
# We use the executor to cleanup the client. We just do it from one
|
|
311
362
|
# place, so only one thread is really needed.
|
|
@@ -331,29 +382,60 @@ class DailyTransportClient(EventHandler):
|
|
|
331
382
|
self._out_sample_rate = 0
|
|
332
383
|
|
|
333
384
|
self._camera: Optional[VirtualCameraDevice] = None
|
|
385
|
+
self._speaker: Optional[VirtualSpeakerDevice] = None
|
|
334
386
|
self._microphone_track: Optional[DailyAudioTrack] = None
|
|
335
387
|
self._custom_audio_tracks: Dict[str, DailyAudioTrack] = {}
|
|
336
388
|
|
|
337
389
|
def _camera_name(self):
|
|
390
|
+
"""Generate a unique virtual camera name for this client instance."""
|
|
338
391
|
return f"camera-{self}"
|
|
339
392
|
|
|
393
|
+
def _speaker_name(self):
|
|
394
|
+
"""Generate a unique virtual speaker name for this client instance."""
|
|
395
|
+
return f"speaker-{self}"
|
|
396
|
+
|
|
340
397
|
@property
|
|
341
398
|
def room_url(self) -> str:
|
|
399
|
+
"""Get the Daily room URL.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
The room URL this client is connected to.
|
|
403
|
+
"""
|
|
342
404
|
return self._room_url
|
|
343
405
|
|
|
344
406
|
@property
|
|
345
407
|
def participant_id(self) -> str:
|
|
408
|
+
"""Get the participant ID for this client.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
The participant ID assigned by Daily.
|
|
412
|
+
"""
|
|
346
413
|
return self._participant_id
|
|
347
414
|
|
|
348
415
|
@property
|
|
349
416
|
def in_sample_rate(self) -> int:
|
|
417
|
+
"""Get the input audio sample rate.
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
The input sample rate in Hz.
|
|
421
|
+
"""
|
|
350
422
|
return self._in_sample_rate
|
|
351
423
|
|
|
352
424
|
@property
|
|
353
425
|
def out_sample_rate(self) -> int:
|
|
426
|
+
"""Get the output audio sample rate.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
The output sample rate in Hz.
|
|
430
|
+
"""
|
|
354
431
|
return self._out_sample_rate
|
|
355
432
|
|
|
356
433
|
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
|
434
|
+
"""Send an application message to participants.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
frame: The message frame to send.
|
|
438
|
+
"""
|
|
357
439
|
if not self._joined:
|
|
358
440
|
return
|
|
359
441
|
|
|
@@ -367,11 +449,45 @@ class DailyTransportClient(EventHandler):
|
|
|
367
449
|
)
|
|
368
450
|
await future
|
|
369
451
|
|
|
452
|
+
async def read_next_audio_frame(self) -> Optional[InputAudioRawFrame]:
|
|
453
|
+
"""Reads the next 20ms audio frame from the virtual speaker."""
|
|
454
|
+
if not self._speaker:
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
sample_rate = self._in_sample_rate
|
|
458
|
+
num_channels = self._params.audio_in_channels
|
|
459
|
+
num_frames = int(sample_rate / 100) * 2 # 20ms of audio
|
|
460
|
+
|
|
461
|
+
future = self._get_event_loop().create_future()
|
|
462
|
+
self._speaker.read_frames(num_frames, completion=completion_callback(future))
|
|
463
|
+
audio = await future
|
|
464
|
+
|
|
465
|
+
if len(audio) > 0:
|
|
466
|
+
return InputAudioRawFrame(
|
|
467
|
+
audio=audio, sample_rate=sample_rate, num_channels=num_channels
|
|
468
|
+
)
|
|
469
|
+
else:
|
|
470
|
+
# If we don't read any audio it could be there's no participant
|
|
471
|
+
# connected. daily-python will return immediately if that's the
|
|
472
|
+
# case, so let's sleep for a little bit (i.e. busy wait).
|
|
473
|
+
await asyncio.sleep(0.01)
|
|
474
|
+
return None
|
|
475
|
+
|
|
370
476
|
async def register_audio_destination(self, destination: str):
|
|
477
|
+
"""Register a custom audio destination for multi-track output.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
destination: The destination identifier to register.
|
|
481
|
+
"""
|
|
371
482
|
self._custom_audio_tracks[destination] = await self.add_custom_audio_track(destination)
|
|
372
483
|
self._client.update_publishing({"customAudio": {destination: True}})
|
|
373
484
|
|
|
374
485
|
async def write_audio_frame(self, frame: OutputAudioRawFrame):
|
|
486
|
+
"""Write an audio frame to the appropriate audio track.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
frame: The audio frame to write.
|
|
490
|
+
"""
|
|
375
491
|
future = self._get_event_loop().create_future()
|
|
376
492
|
|
|
377
493
|
destination = frame.transport_destination
|
|
@@ -391,23 +507,33 @@ class DailyTransportClient(EventHandler):
|
|
|
391
507
|
await future
|
|
392
508
|
|
|
393
509
|
async def write_video_frame(self, frame: OutputImageRawFrame):
|
|
510
|
+
"""Write a video frame to the camera device.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
frame: The image frame to write.
|
|
514
|
+
"""
|
|
394
515
|
if not frame.transport_destination and self._camera:
|
|
395
516
|
self._camera.write_frame(frame.image)
|
|
396
517
|
|
|
397
518
|
async def setup(self, setup: FrameProcessorSetup):
|
|
519
|
+
"""Setup the client with task manager and event queues.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
setup: The frame processor setup configuration.
|
|
523
|
+
"""
|
|
398
524
|
if self._task_manager:
|
|
399
525
|
return
|
|
400
526
|
|
|
401
527
|
self._task_manager = setup.task_manager
|
|
402
|
-
self._watchdog_timers_enabled = setup.watchdog_timers_enabled
|
|
403
528
|
|
|
404
|
-
self._event_queue =
|
|
529
|
+
self._event_queue = asyncio.Queue()
|
|
405
530
|
self._event_task = self._task_manager.create_task(
|
|
406
531
|
self._callback_task_handler(self._event_queue),
|
|
407
532
|
f"{self}::event_callback_task",
|
|
408
533
|
)
|
|
409
534
|
|
|
410
535
|
async def cleanup(self):
|
|
536
|
+
"""Cleanup client resources and cancel tasks."""
|
|
411
537
|
if self._event_task and self._task_manager:
|
|
412
538
|
await self._task_manager.cancel_task(self._event_task)
|
|
413
539
|
self._event_task = None
|
|
@@ -422,18 +548,32 @@ class DailyTransportClient(EventHandler):
|
|
|
422
548
|
await self._get_event_loop().run_in_executor(self._executor, self._cleanup)
|
|
423
549
|
|
|
424
550
|
async def start(self, frame: StartFrame):
|
|
551
|
+
"""Start the client and initialize audio/video components.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
frame: The start frame containing initialization parameters.
|
|
555
|
+
"""
|
|
425
556
|
self._in_sample_rate = self._params.audio_in_sample_rate or frame.audio_in_sample_rate
|
|
426
557
|
self._out_sample_rate = self._params.audio_out_sample_rate or frame.audio_out_sample_rate
|
|
427
558
|
|
|
428
|
-
if self._params.audio_in_enabled
|
|
429
|
-
self.
|
|
430
|
-
|
|
431
|
-
self.
|
|
432
|
-
|
|
433
|
-
|
|
559
|
+
if self._params.audio_in_enabled:
|
|
560
|
+
if self._params.audio_in_user_tracks and not self._audio_task and self._task_manager:
|
|
561
|
+
self._audio_queue = asyncio.Queue()
|
|
562
|
+
self._audio_task = self._task_manager.create_task(
|
|
563
|
+
self._callback_task_handler(self._audio_queue),
|
|
564
|
+
f"{self}::audio_callback_task",
|
|
565
|
+
)
|
|
566
|
+
elif not self._speaker:
|
|
567
|
+
self._speaker = Daily.create_speaker_device(
|
|
568
|
+
self._speaker_name(),
|
|
569
|
+
sample_rate=self._in_sample_rate,
|
|
570
|
+
channels=self._params.audio_in_channels,
|
|
571
|
+
non_blocking=True,
|
|
572
|
+
)
|
|
573
|
+
Daily.select_speaker_device(self._speaker_name())
|
|
434
574
|
|
|
435
575
|
if self._params.video_in_enabled and not self._video_task and self._task_manager:
|
|
436
|
-
self._video_queue =
|
|
576
|
+
self._video_queue = asyncio.Queue()
|
|
437
577
|
self._video_task = self._task_manager.create_task(
|
|
438
578
|
self._callback_task_handler(self._video_queue),
|
|
439
579
|
f"{self}::video_callback_task",
|
|
@@ -452,6 +592,7 @@ class DailyTransportClient(EventHandler):
|
|
|
452
592
|
self._microphone_track = DailyAudioTrack(source=audio_source, track=audio_track)
|
|
453
593
|
|
|
454
594
|
async def join(self):
|
|
595
|
+
"""Join the Daily room with configured settings."""
|
|
455
596
|
# Transport already joined or joining, ignore.
|
|
456
597
|
if self._joined or self._joining:
|
|
457
598
|
# Increment leave counter if we already joined.
|
|
@@ -497,6 +638,7 @@ class DailyTransportClient(EventHandler):
|
|
|
497
638
|
await self._callbacks.on_error(error_msg)
|
|
498
639
|
|
|
499
640
|
async def _join(self):
|
|
641
|
+
"""Execute the actual room join operation."""
|
|
500
642
|
future = self._get_event_loop().create_future()
|
|
501
643
|
|
|
502
644
|
camera_enabled = self._params.video_out_enabled and self._params.camera_out_enabled
|
|
@@ -552,6 +694,7 @@ class DailyTransportClient(EventHandler):
|
|
|
552
694
|
return await asyncio.wait_for(future, timeout=10)
|
|
553
695
|
|
|
554
696
|
async def leave(self):
|
|
697
|
+
"""Leave the Daily room and cleanup resources."""
|
|
555
698
|
# Decrement leave counter when leaving.
|
|
556
699
|
self._leave_counter -= 1
|
|
557
700
|
|
|
@@ -586,22 +729,39 @@ class DailyTransportClient(EventHandler):
|
|
|
586
729
|
await self._callbacks.on_error(error_msg)
|
|
587
730
|
|
|
588
731
|
async def _leave(self):
|
|
732
|
+
"""Execute the actual room leave operation."""
|
|
589
733
|
future = self._get_event_loop().create_future()
|
|
590
734
|
self._client.leave(completion=completion_callback(future))
|
|
591
735
|
return await asyncio.wait_for(future, timeout=10)
|
|
592
736
|
|
|
593
737
|
def _cleanup(self):
|
|
738
|
+
"""Cleanup the Daily client instance."""
|
|
594
739
|
if self._client:
|
|
595
740
|
self._client.release()
|
|
596
741
|
self._client = None
|
|
597
742
|
|
|
598
743
|
def participants(self):
|
|
744
|
+
"""Get current participants in the room.
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
Dictionary of participants keyed by participant ID.
|
|
748
|
+
"""
|
|
599
749
|
return self._client.participants()
|
|
600
750
|
|
|
601
751
|
def participant_counts(self):
|
|
752
|
+
"""Get participant count information.
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
Dictionary with participant count details.
|
|
756
|
+
"""
|
|
602
757
|
return self._client.participant_counts()
|
|
603
758
|
|
|
604
759
|
async def start_dialout(self, settings):
|
|
760
|
+
"""Start a dial-out call to a phone number.
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
settings: Dial-out configuration settings.
|
|
764
|
+
"""
|
|
605
765
|
logger.debug(f"Starting dialout: settings={settings}")
|
|
606
766
|
|
|
607
767
|
future = self._get_event_loop().create_future()
|
|
@@ -611,6 +771,11 @@ class DailyTransportClient(EventHandler):
|
|
|
611
771
|
logger.error(f"Unable to start dialout: {error}")
|
|
612
772
|
|
|
613
773
|
async def stop_dialout(self, participant_id):
|
|
774
|
+
"""Stop a dial-out call for a specific participant.
|
|
775
|
+
|
|
776
|
+
Args:
|
|
777
|
+
participant_id: ID of the participant to stop dial-out for.
|
|
778
|
+
"""
|
|
614
779
|
logger.debug(f"Stopping dialout: participant_id={participant_id}")
|
|
615
780
|
|
|
616
781
|
future = self._get_event_loop().create_future()
|
|
@@ -620,21 +785,43 @@ class DailyTransportClient(EventHandler):
|
|
|
620
785
|
logger.error(f"Unable to stop dialout: {error}")
|
|
621
786
|
|
|
622
787
|
async def send_dtmf(self, settings):
|
|
788
|
+
"""Send DTMF tones during a call.
|
|
789
|
+
|
|
790
|
+
Args:
|
|
791
|
+
settings: DTMF settings including tones and target session.
|
|
792
|
+
"""
|
|
623
793
|
future = self._get_event_loop().create_future()
|
|
624
794
|
self._client.send_dtmf(settings, completion=completion_callback(future))
|
|
625
795
|
await future
|
|
626
796
|
|
|
627
797
|
async def sip_call_transfer(self, settings):
|
|
798
|
+
"""Transfer a SIP call to another destination.
|
|
799
|
+
|
|
800
|
+
Args:
|
|
801
|
+
settings: SIP call transfer settings.
|
|
802
|
+
"""
|
|
628
803
|
future = self._get_event_loop().create_future()
|
|
629
804
|
self._client.sip_call_transfer(settings, completion=completion_callback(future))
|
|
630
805
|
await future
|
|
631
806
|
|
|
632
807
|
async def sip_refer(self, settings):
|
|
808
|
+
"""Send a SIP REFER request.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
settings: SIP REFER settings.
|
|
812
|
+
"""
|
|
633
813
|
future = self._get_event_loop().create_future()
|
|
634
814
|
self._client.sip_refer(settings, completion=completion_callback(future))
|
|
635
815
|
await future
|
|
636
816
|
|
|
637
817
|
async def start_recording(self, streaming_settings, stream_id, force_new):
|
|
818
|
+
"""Start recording the call.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
streaming_settings: Recording configuration settings.
|
|
822
|
+
stream_id: Unique identifier for the recording stream.
|
|
823
|
+
force_new: Whether to force a new recording session.
|
|
824
|
+
"""
|
|
638
825
|
logger.debug(
|
|
639
826
|
f"Starting recording: stream_id={stream_id} force_new={force_new} settings={streaming_settings}"
|
|
640
827
|
)
|
|
@@ -648,6 +835,11 @@ class DailyTransportClient(EventHandler):
|
|
|
648
835
|
logger.error(f"Unable to start recording: {error}")
|
|
649
836
|
|
|
650
837
|
async def stop_recording(self, stream_id):
|
|
838
|
+
"""Stop recording the call.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
stream_id: Unique identifier for the recording stream to stop.
|
|
842
|
+
"""
|
|
651
843
|
logger.debug(f"Stopping recording: stream_id={stream_id}")
|
|
652
844
|
|
|
653
845
|
future = self._get_event_loop().create_future()
|
|
@@ -657,6 +849,11 @@ class DailyTransportClient(EventHandler):
|
|
|
657
849
|
logger.error(f"Unable to stop recording: {error}")
|
|
658
850
|
|
|
659
851
|
async def start_transcription(self, settings):
|
|
852
|
+
"""Start transcription for the call.
|
|
853
|
+
|
|
854
|
+
Args:
|
|
855
|
+
settings: Transcription configuration settings.
|
|
856
|
+
"""
|
|
660
857
|
if not self._token:
|
|
661
858
|
logger.warning("Transcription can't be started without a room token")
|
|
662
859
|
return
|
|
@@ -673,6 +870,7 @@ class DailyTransportClient(EventHandler):
|
|
|
673
870
|
logger.error(f"Unable to start transcription: {error}")
|
|
674
871
|
|
|
675
872
|
async def stop_transcription(self):
|
|
873
|
+
"""Stop transcription for the call."""
|
|
676
874
|
if not self._token:
|
|
677
875
|
return
|
|
678
876
|
|
|
@@ -685,6 +883,12 @@ class DailyTransportClient(EventHandler):
|
|
|
685
883
|
logger.error(f"Unable to stop transcription: {error}")
|
|
686
884
|
|
|
687
885
|
async def send_prebuilt_chat_message(self, message: str, user_name: Optional[str] = None):
|
|
886
|
+
"""Send a chat message to Daily's Prebuilt main room.
|
|
887
|
+
|
|
888
|
+
Args:
|
|
889
|
+
message: The chat message to send.
|
|
890
|
+
user_name: Optional user name that will appear as sender of the message.
|
|
891
|
+
"""
|
|
688
892
|
if not self._joined:
|
|
689
893
|
return
|
|
690
894
|
|
|
@@ -695,6 +899,11 @@ class DailyTransportClient(EventHandler):
|
|
|
695
899
|
await future
|
|
696
900
|
|
|
697
901
|
async def capture_participant_transcription(self, participant_id: str):
|
|
902
|
+
"""Enable transcription capture for a specific participant.
|
|
903
|
+
|
|
904
|
+
Args:
|
|
905
|
+
participant_id: ID of the participant to capture transcription for.
|
|
906
|
+
"""
|
|
698
907
|
if not self._params.transcription_enabled:
|
|
699
908
|
return
|
|
700
909
|
|
|
@@ -710,6 +919,15 @@ class DailyTransportClient(EventHandler):
|
|
|
710
919
|
sample_rate: int = 16000,
|
|
711
920
|
callback_interval_ms: int = 20,
|
|
712
921
|
):
|
|
922
|
+
"""Capture audio from a specific participant.
|
|
923
|
+
|
|
924
|
+
Args:
|
|
925
|
+
participant_id: ID of the participant to capture audio from.
|
|
926
|
+
callback: Callback function to handle audio data.
|
|
927
|
+
audio_source: Audio source to capture (microphone, screenAudio, or custom).
|
|
928
|
+
sample_rate: Desired sample rate for audio capture.
|
|
929
|
+
callback_interval_ms: Interval between audio callbacks in milliseconds.
|
|
930
|
+
"""
|
|
713
931
|
# Only enable the desired audio source subscription on this participant.
|
|
714
932
|
if audio_source in ("microphone", "screenAudio"):
|
|
715
933
|
media = {"media": {audio_source: "subscribed"}}
|
|
@@ -740,6 +958,15 @@ class DailyTransportClient(EventHandler):
|
|
|
740
958
|
video_source: str = "camera",
|
|
741
959
|
color_format: str = "RGB",
|
|
742
960
|
):
|
|
961
|
+
"""Capture video from a specific participant.
|
|
962
|
+
|
|
963
|
+
Args:
|
|
964
|
+
participant_id: ID of the participant to capture video from.
|
|
965
|
+
callback: Callback function to handle video frames.
|
|
966
|
+
framerate: Desired framerate for video capture.
|
|
967
|
+
video_source: Video source to capture (camera, screenVideo, or custom).
|
|
968
|
+
color_format: Color format for video frames.
|
|
969
|
+
"""
|
|
743
970
|
# Only enable the desired audio source subscription on this participant.
|
|
744
971
|
if video_source in ("camera", "screenVideo"):
|
|
745
972
|
media = {"media": {video_source: "subscribed"}}
|
|
@@ -762,6 +989,14 @@ class DailyTransportClient(EventHandler):
|
|
|
762
989
|
)
|
|
763
990
|
|
|
764
991
|
async def add_custom_audio_track(self, track_name: str) -> DailyAudioTrack:
|
|
992
|
+
"""Add a custom audio track for multi-stream output.
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
track_name: Name for the custom audio track.
|
|
996
|
+
|
|
997
|
+
Returns:
|
|
998
|
+
The created DailyAudioTrack instance.
|
|
999
|
+
"""
|
|
765
1000
|
future = self._get_event_loop().create_future()
|
|
766
1001
|
|
|
767
1002
|
audio_source = CustomAudioSource(self._out_sample_rate, 1)
|
|
@@ -782,6 +1017,11 @@ class DailyTransportClient(EventHandler):
|
|
|
782
1017
|
return track
|
|
783
1018
|
|
|
784
1019
|
async def remove_custom_audio_track(self, track_name: str):
|
|
1020
|
+
"""Remove a custom audio track.
|
|
1021
|
+
|
|
1022
|
+
Args:
|
|
1023
|
+
track_name: Name of the custom audio track to remove.
|
|
1024
|
+
"""
|
|
785
1025
|
future = self._get_event_loop().create_future()
|
|
786
1026
|
self._client.remove_custom_audio_track(
|
|
787
1027
|
track_name=track_name,
|
|
@@ -790,6 +1030,12 @@ class DailyTransportClient(EventHandler):
|
|
|
790
1030
|
await future
|
|
791
1031
|
|
|
792
1032
|
async def update_transcription(self, participants=None, instance_id=None):
|
|
1033
|
+
"""Update transcription settings for specific participants.
|
|
1034
|
+
|
|
1035
|
+
Args:
|
|
1036
|
+
participants: List of participant IDs to enable transcription for.
|
|
1037
|
+
instance_id: Optional transcription instance ID.
|
|
1038
|
+
"""
|
|
793
1039
|
future = self._get_event_loop().create_future()
|
|
794
1040
|
self._client.update_transcription(
|
|
795
1041
|
participants, instance_id, completion=completion_callback(future)
|
|
@@ -797,6 +1043,12 @@ class DailyTransportClient(EventHandler):
|
|
|
797
1043
|
await future
|
|
798
1044
|
|
|
799
1045
|
async def update_subscriptions(self, participant_settings=None, profile_settings=None):
|
|
1046
|
+
"""Update media subscription settings.
|
|
1047
|
+
|
|
1048
|
+
Args:
|
|
1049
|
+
participant_settings: Per-participant subscription settings.
|
|
1050
|
+
profile_settings: Global subscription profile settings.
|
|
1051
|
+
"""
|
|
800
1052
|
future = self._get_event_loop().create_future()
|
|
801
1053
|
self._client.update_subscriptions(
|
|
802
1054
|
participant_settings=participant_settings,
|
|
@@ -806,6 +1058,11 @@ class DailyTransportClient(EventHandler):
|
|
|
806
1058
|
await future
|
|
807
1059
|
|
|
808
1060
|
async def update_publishing(self, publishing_settings: Mapping[str, Any]):
|
|
1061
|
+
"""Update media publishing settings.
|
|
1062
|
+
|
|
1063
|
+
Args:
|
|
1064
|
+
publishing_settings: Publishing configuration settings.
|
|
1065
|
+
"""
|
|
809
1066
|
future = self._get_event_loop().create_future()
|
|
810
1067
|
self._client.update_publishing(
|
|
811
1068
|
publishing_settings=publishing_settings,
|
|
@@ -814,6 +1071,11 @@ class DailyTransportClient(EventHandler):
|
|
|
814
1071
|
await future
|
|
815
1072
|
|
|
816
1073
|
async def update_remote_participants(self, remote_participants: Mapping[str, Any]):
|
|
1074
|
+
"""Update settings for remote participants.
|
|
1075
|
+
|
|
1076
|
+
Args:
|
|
1077
|
+
remote_participants: Remote participant configuration settings.
|
|
1078
|
+
"""
|
|
817
1079
|
future = self._get_event_loop().create_future()
|
|
818
1080
|
self._client.update_remote_participants(
|
|
819
1081
|
remote_participants=remote_participants, completion=completion_callback(future)
|
|
@@ -826,76 +1088,199 @@ class DailyTransportClient(EventHandler):
|
|
|
826
1088
|
#
|
|
827
1089
|
|
|
828
1090
|
def on_active_speaker_changed(self, participant):
|
|
1091
|
+
"""Handle active speaker change events.
|
|
1092
|
+
|
|
1093
|
+
Args:
|
|
1094
|
+
participant: The new active speaker participant info.
|
|
1095
|
+
"""
|
|
829
1096
|
self._call_event_callback(self._callbacks.on_active_speaker_changed, participant)
|
|
830
1097
|
|
|
831
1098
|
def on_app_message(self, message: Any, sender: str):
|
|
1099
|
+
"""Handle application message events.
|
|
1100
|
+
|
|
1101
|
+
Args:
|
|
1102
|
+
message: The received message data.
|
|
1103
|
+
sender: ID of the message sender.
|
|
1104
|
+
"""
|
|
832
1105
|
self._call_event_callback(self._callbacks.on_app_message, message, sender)
|
|
833
1106
|
|
|
834
1107
|
def on_call_state_updated(self, state: str):
|
|
1108
|
+
"""Handle call state update events.
|
|
1109
|
+
|
|
1110
|
+
Args:
|
|
1111
|
+
state: The new call state.
|
|
1112
|
+
"""
|
|
835
1113
|
self._call_event_callback(self._callbacks.on_call_state_updated, state)
|
|
836
1114
|
|
|
837
1115
|
def on_dialin_connected(self, data: Any):
|
|
1116
|
+
"""Handle dial-in connected events.
|
|
1117
|
+
|
|
1118
|
+
Args:
|
|
1119
|
+
data: Dial-in connection data.
|
|
1120
|
+
"""
|
|
838
1121
|
self._call_event_callback(self._callbacks.on_dialin_connected, data)
|
|
839
1122
|
|
|
840
1123
|
def on_dialin_ready(self, sip_endpoint: str):
|
|
1124
|
+
"""Handle dial-in ready events.
|
|
1125
|
+
|
|
1126
|
+
Args:
|
|
1127
|
+
sip_endpoint: The SIP endpoint for dial-in.
|
|
1128
|
+
"""
|
|
841
1129
|
self._call_event_callback(self._callbacks.on_dialin_ready, sip_endpoint)
|
|
842
1130
|
|
|
843
1131
|
def on_dialin_stopped(self, data: Any):
|
|
1132
|
+
"""Handle dial-in stopped events.
|
|
1133
|
+
|
|
1134
|
+
Args:
|
|
1135
|
+
data: Dial-in stop data.
|
|
1136
|
+
"""
|
|
844
1137
|
self._call_event_callback(self._callbacks.on_dialin_stopped, data)
|
|
845
1138
|
|
|
846
1139
|
def on_dialin_error(self, data: Any):
|
|
1140
|
+
"""Handle dial-in error events.
|
|
1141
|
+
|
|
1142
|
+
Args:
|
|
1143
|
+
data: Dial-in error data.
|
|
1144
|
+
"""
|
|
847
1145
|
self._call_event_callback(self._callbacks.on_dialin_error, data)
|
|
848
1146
|
|
|
849
1147
|
def on_dialin_warning(self, data: Any):
|
|
1148
|
+
"""Handle dial-in warning events.
|
|
1149
|
+
|
|
1150
|
+
Args:
|
|
1151
|
+
data: Dial-in warning data.
|
|
1152
|
+
"""
|
|
850
1153
|
self._call_event_callback(self._callbacks.on_dialin_warning, data)
|
|
851
1154
|
|
|
852
1155
|
def on_dialout_answered(self, data: Any):
|
|
1156
|
+
"""Handle dial-out answered events.
|
|
1157
|
+
|
|
1158
|
+
Args:
|
|
1159
|
+
data: Dial-out answered data.
|
|
1160
|
+
"""
|
|
853
1161
|
self._call_event_callback(self._callbacks.on_dialout_answered, data)
|
|
854
1162
|
|
|
855
1163
|
def on_dialout_connected(self, data: Any):
|
|
1164
|
+
"""Handle dial-out connected events.
|
|
1165
|
+
|
|
1166
|
+
Args:
|
|
1167
|
+
data: Dial-out connection data.
|
|
1168
|
+
"""
|
|
856
1169
|
self._call_event_callback(self._callbacks.on_dialout_connected, data)
|
|
857
1170
|
|
|
858
1171
|
def on_dialout_stopped(self, data: Any):
|
|
1172
|
+
"""Handle dial-out stopped events.
|
|
1173
|
+
|
|
1174
|
+
Args:
|
|
1175
|
+
data: Dial-out stop data.
|
|
1176
|
+
"""
|
|
859
1177
|
self._call_event_callback(self._callbacks.on_dialout_stopped, data)
|
|
860
1178
|
|
|
861
1179
|
def on_dialout_error(self, data: Any):
|
|
1180
|
+
"""Handle dial-out error events.
|
|
1181
|
+
|
|
1182
|
+
Args:
|
|
1183
|
+
data: Dial-out error data.
|
|
1184
|
+
"""
|
|
862
1185
|
self._call_event_callback(self._callbacks.on_dialout_error, data)
|
|
863
1186
|
|
|
864
1187
|
def on_dialout_warning(self, data: Any):
|
|
1188
|
+
"""Handle dial-out warning events.
|
|
1189
|
+
|
|
1190
|
+
Args:
|
|
1191
|
+
data: Dial-out warning data.
|
|
1192
|
+
"""
|
|
865
1193
|
self._call_event_callback(self._callbacks.on_dialout_warning, data)
|
|
866
1194
|
|
|
867
1195
|
def on_participant_joined(self, participant):
|
|
1196
|
+
"""Handle participant joined events.
|
|
1197
|
+
|
|
1198
|
+
Args:
|
|
1199
|
+
participant: The participant that joined.
|
|
1200
|
+
"""
|
|
868
1201
|
self._call_event_callback(self._callbacks.on_participant_joined, participant)
|
|
869
1202
|
|
|
870
1203
|
def on_participant_left(self, participant, reason):
|
|
1204
|
+
"""Handle participant left events.
|
|
1205
|
+
|
|
1206
|
+
Args:
|
|
1207
|
+
participant: The participant that left.
|
|
1208
|
+
reason: Reason for leaving.
|
|
1209
|
+
"""
|
|
871
1210
|
self._call_event_callback(self._callbacks.on_participant_left, participant, reason)
|
|
872
1211
|
|
|
873
1212
|
def on_participant_updated(self, participant):
|
|
1213
|
+
"""Handle participant updated events.
|
|
1214
|
+
|
|
1215
|
+
Args:
|
|
1216
|
+
participant: The updated participant info.
|
|
1217
|
+
"""
|
|
874
1218
|
self._call_event_callback(self._callbacks.on_participant_updated, participant)
|
|
875
1219
|
|
|
876
1220
|
def on_transcription_started(self, status):
|
|
1221
|
+
"""Handle transcription started events.
|
|
1222
|
+
|
|
1223
|
+
Args:
|
|
1224
|
+
status: Transcription start status.
|
|
1225
|
+
"""
|
|
877
1226
|
logger.debug(f"Transcription started: {status}")
|
|
878
1227
|
self._transcription_status = status
|
|
879
1228
|
self._call_event_callback(self.update_transcription, self._transcription_ids)
|
|
880
1229
|
|
|
881
1230
|
def on_transcription_stopped(self, stopped_by, stopped_by_error):
|
|
1231
|
+
"""Handle transcription stopped events.
|
|
1232
|
+
|
|
1233
|
+
Args:
|
|
1234
|
+
stopped_by: Who stopped the transcription.
|
|
1235
|
+
stopped_by_error: Whether stopped due to error.
|
|
1236
|
+
"""
|
|
882
1237
|
logger.debug("Transcription stopped")
|
|
1238
|
+
self._call_event_callback(
|
|
1239
|
+
self._callbacks.on_transcription_stopped, stopped_by, stopped_by_error
|
|
1240
|
+
)
|
|
883
1241
|
|
|
884
1242
|
def on_transcription_error(self, message):
|
|
1243
|
+
"""Handle transcription error events.
|
|
1244
|
+
|
|
1245
|
+
Args:
|
|
1246
|
+
message: Error message.
|
|
1247
|
+
"""
|
|
885
1248
|
logger.error(f"Transcription error: {message}")
|
|
1249
|
+
self._call_event_callback(self._callbacks.on_transcription_error, message)
|
|
886
1250
|
|
|
887
1251
|
def on_transcription_message(self, message):
|
|
1252
|
+
"""Handle transcription message events.
|
|
1253
|
+
|
|
1254
|
+
Args:
|
|
1255
|
+
message: The transcription message data.
|
|
1256
|
+
"""
|
|
888
1257
|
self._call_event_callback(self._callbacks.on_transcription_message, message)
|
|
889
1258
|
|
|
890
1259
|
def on_recording_started(self, status):
|
|
1260
|
+
"""Handle recording started events.
|
|
1261
|
+
|
|
1262
|
+
Args:
|
|
1263
|
+
status: Recording start status.
|
|
1264
|
+
"""
|
|
891
1265
|
logger.debug(f"Recording started: {status}")
|
|
892
1266
|
self._call_event_callback(self._callbacks.on_recording_started, status)
|
|
893
1267
|
|
|
894
1268
|
def on_recording_stopped(self, stream_id):
|
|
1269
|
+
"""Handle recording stopped events.
|
|
1270
|
+
|
|
1271
|
+
Args:
|
|
1272
|
+
stream_id: ID of the stopped recording stream.
|
|
1273
|
+
"""
|
|
895
1274
|
logger.debug(f"Recording stopped: {stream_id}")
|
|
896
1275
|
self._call_event_callback(self._callbacks.on_recording_stopped, stream_id)
|
|
897
1276
|
|
|
898
1277
|
def on_recording_error(self, stream_id, message):
|
|
1278
|
+
"""Handle recording error events.
|
|
1279
|
+
|
|
1280
|
+
Args:
|
|
1281
|
+
stream_id: ID of the recording stream with error.
|
|
1282
|
+
message: Error message.
|
|
1283
|
+
"""
|
|
899
1284
|
logger.error(f"Recording error for {stream_id}: {message}")
|
|
900
1285
|
self._call_event_callback(self._callbacks.on_recording_error, stream_id, message)
|
|
901
1286
|
|
|
@@ -904,12 +1289,14 @@ class DailyTransportClient(EventHandler):
|
|
|
904
1289
|
#
|
|
905
1290
|
|
|
906
1291
|
def _audio_data_received(self, participant_id: str, audio_data: AudioData, audio_source: str):
|
|
1292
|
+
"""Handle received audio data from participants."""
|
|
907
1293
|
callback = self._audio_renderers[participant_id][audio_source]
|
|
908
1294
|
self._call_audio_callback(callback, participant_id, audio_data, audio_source)
|
|
909
1295
|
|
|
910
1296
|
def _video_frame_received(
|
|
911
1297
|
self, participant_id: str, video_frame: VideoFrame, video_source: str
|
|
912
1298
|
):
|
|
1299
|
+
"""Handle received video frames from participants."""
|
|
913
1300
|
callback = self._video_renderers[participant_id][video_source]
|
|
914
1301
|
self._call_video_callback(callback, participant_id, video_frame, video_source)
|
|
915
1302
|
|
|
@@ -918,21 +1305,29 @@ class DailyTransportClient(EventHandler):
|
|
|
918
1305
|
#
|
|
919
1306
|
|
|
920
1307
|
def _call_audio_callback(self, callback, *args):
|
|
1308
|
+
"""Queue an audio callback for async execution."""
|
|
921
1309
|
self._call_async_callback(self._audio_queue, callback, *args)
|
|
922
1310
|
|
|
923
1311
|
def _call_video_callback(self, callback, *args):
|
|
1312
|
+
"""Queue a video callback for async execution."""
|
|
924
1313
|
self._call_async_callback(self._video_queue, callback, *args)
|
|
925
1314
|
|
|
926
1315
|
def _call_event_callback(self, callback, *args):
|
|
1316
|
+
"""Queue an event callback for async execution."""
|
|
927
1317
|
self._call_async_callback(self._event_queue, callback, *args)
|
|
928
1318
|
|
|
929
1319
|
def _call_async_callback(self, queue: asyncio.Queue, callback, *args):
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1320
|
+
"""Queue a callback for async execution on the event loop."""
|
|
1321
|
+
try:
|
|
1322
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
1323
|
+
queue.put((callback, *args)), self._get_event_loop()
|
|
1324
|
+
)
|
|
1325
|
+
future.result()
|
|
1326
|
+
except FuturesCancelledError:
|
|
1327
|
+
pass
|
|
934
1328
|
|
|
935
1329
|
async def _callback_task_handler(self, queue: asyncio.Queue):
|
|
1330
|
+
"""Handle queued callbacks from the specified queue."""
|
|
936
1331
|
while True:
|
|
937
1332
|
# Wait to process any callback until we are joined.
|
|
938
1333
|
await self._joined_event.wait()
|
|
@@ -941,22 +1336,21 @@ class DailyTransportClient(EventHandler):
|
|
|
941
1336
|
queue.task_done()
|
|
942
1337
|
|
|
943
1338
|
def _get_event_loop(self) -> asyncio.AbstractEventLoop:
|
|
1339
|
+
"""Get the event loop from the task manager."""
|
|
944
1340
|
if not self._task_manager:
|
|
945
1341
|
raise Exception(f"{self}: missing task manager (pipeline not started?)")
|
|
946
1342
|
return self._task_manager.get_event_loop()
|
|
947
1343
|
|
|
948
1344
|
def __str__(self):
|
|
1345
|
+
"""String representation of the DailyTransportClient."""
|
|
949
1346
|
return f"{self._transport_name}::DailyTransportClient"
|
|
950
1347
|
|
|
951
1348
|
|
|
952
1349
|
class DailyInputTransport(BaseInputTransport):
|
|
953
1350
|
"""Handles incoming media streams and events from Daily calls.
|
|
954
1351
|
|
|
955
|
-
Processes incoming audio, video, transcriptions and other events from Daily
|
|
956
|
-
|
|
957
|
-
Args:
|
|
958
|
-
client: DailyTransportClient instance.
|
|
959
|
-
params: Configuration parameters.
|
|
1352
|
+
Processes incoming audio, video, transcriptions and other events from Daily
|
|
1353
|
+
room participants, including participant media capture and event forwarding.
|
|
960
1354
|
"""
|
|
961
1355
|
|
|
962
1356
|
def __init__(
|
|
@@ -966,6 +1360,14 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
966
1360
|
params: DailyParams,
|
|
967
1361
|
**kwargs,
|
|
968
1362
|
):
|
|
1363
|
+
"""Initialize the Daily input transport.
|
|
1364
|
+
|
|
1365
|
+
Args:
|
|
1366
|
+
transport: The parent transport instance.
|
|
1367
|
+
client: DailyTransportClient instance.
|
|
1368
|
+
params: Configuration parameters.
|
|
1369
|
+
**kwargs: Additional arguments passed to parent class.
|
|
1370
|
+
"""
|
|
969
1371
|
super().__init__(params, **kwargs)
|
|
970
1372
|
|
|
971
1373
|
self._transport = transport
|
|
@@ -984,34 +1386,62 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
984
1386
|
# case we don't start streaming right away.
|
|
985
1387
|
self._capture_participant_audio = []
|
|
986
1388
|
|
|
1389
|
+
# Audio task when using a virtual speaker (i.e. no user tracks).
|
|
1390
|
+
self._audio_in_task: Optional[asyncio.Task] = None
|
|
1391
|
+
|
|
987
1392
|
self._vad_analyzer: Optional[VADAnalyzer] = params.vad_analyzer
|
|
988
1393
|
|
|
989
1394
|
@property
|
|
990
1395
|
def vad_analyzer(self) -> Optional[VADAnalyzer]:
|
|
1396
|
+
"""Get the Voice Activity Detection analyzer.
|
|
1397
|
+
|
|
1398
|
+
Returns:
|
|
1399
|
+
The VAD analyzer instance if configured.
|
|
1400
|
+
"""
|
|
991
1401
|
return self._vad_analyzer
|
|
992
1402
|
|
|
993
1403
|
async def start_audio_in_streaming(self):
|
|
1404
|
+
"""Start receiving audio from participants."""
|
|
994
1405
|
if not self._params.audio_in_enabled:
|
|
995
1406
|
return
|
|
996
1407
|
|
|
997
1408
|
logger.debug(f"Start receiving audio")
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1409
|
+
|
|
1410
|
+
if self._params.audio_in_enabled:
|
|
1411
|
+
if self._params.audio_in_user_tracks:
|
|
1412
|
+
# Capture invididual participant tracks.
|
|
1413
|
+
for participant_id, audio_source, sample_rate in self._capture_participant_audio:
|
|
1414
|
+
await self._client.capture_participant_audio(
|
|
1415
|
+
participant_id, self._on_participant_audio_data, audio_source, sample_rate
|
|
1416
|
+
)
|
|
1417
|
+
elif not self._audio_in_task:
|
|
1418
|
+
# Create audio task. It reads audio frames from a single room
|
|
1419
|
+
# track and pushes them internally for VAD processing.
|
|
1420
|
+
self._audio_in_task = self.create_task(self._audio_in_task_handler())
|
|
1002
1421
|
|
|
1003
1422
|
self._streaming_started = True
|
|
1004
1423
|
|
|
1005
1424
|
async def setup(self, setup: FrameProcessorSetup):
|
|
1425
|
+
"""Setup the input transport with shared client setup.
|
|
1426
|
+
|
|
1427
|
+
Args:
|
|
1428
|
+
setup: The frame processor setup configuration.
|
|
1429
|
+
"""
|
|
1006
1430
|
await super().setup(setup)
|
|
1007
1431
|
await self._client.setup(setup)
|
|
1008
1432
|
|
|
1009
1433
|
async def cleanup(self):
|
|
1434
|
+
"""Cleanup input transport and shared resources."""
|
|
1010
1435
|
await super().cleanup()
|
|
1011
1436
|
await self._client.cleanup()
|
|
1012
1437
|
await self._transport.cleanup()
|
|
1013
1438
|
|
|
1014
1439
|
async def start(self, frame: StartFrame):
|
|
1440
|
+
"""Start the input transport and join the Daily room.
|
|
1441
|
+
|
|
1442
|
+
Args:
|
|
1443
|
+
frame: The start frame containing initialization parameters.
|
|
1444
|
+
"""
|
|
1015
1445
|
# Parent start.
|
|
1016
1446
|
await super().start(frame)
|
|
1017
1447
|
|
|
@@ -1033,22 +1463,46 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1033
1463
|
await self.start_audio_in_streaming()
|
|
1034
1464
|
|
|
1035
1465
|
async def stop(self, frame: EndFrame):
|
|
1466
|
+
"""Stop the input transport and leave the Daily room.
|
|
1467
|
+
|
|
1468
|
+
Args:
|
|
1469
|
+
frame: The end frame signaling transport shutdown.
|
|
1470
|
+
"""
|
|
1036
1471
|
# Parent stop.
|
|
1037
1472
|
await super().stop(frame)
|
|
1038
1473
|
# Leave the room.
|
|
1039
1474
|
await self._client.leave()
|
|
1475
|
+
# Stop audio thread.
|
|
1476
|
+
if self._audio_in_task:
|
|
1477
|
+
await self.cancel_task(self._audio_in_task)
|
|
1478
|
+
self._audio_in_task = None
|
|
1040
1479
|
|
|
1041
1480
|
async def cancel(self, frame: CancelFrame):
|
|
1481
|
+
"""Cancel the input transport and leave the Daily room.
|
|
1482
|
+
|
|
1483
|
+
Args:
|
|
1484
|
+
frame: The cancel frame signaling immediate cancellation.
|
|
1485
|
+
"""
|
|
1042
1486
|
# Parent stop.
|
|
1043
1487
|
await super().cancel(frame)
|
|
1044
1488
|
# Leave the room.
|
|
1045
1489
|
await self._client.leave()
|
|
1490
|
+
# Stop audio thread.
|
|
1491
|
+
if self._audio_in_task:
|
|
1492
|
+
await self.cancel_task(self._audio_in_task)
|
|
1493
|
+
self._audio_in_task = None
|
|
1046
1494
|
|
|
1047
1495
|
#
|
|
1048
1496
|
# FrameProcessor
|
|
1049
1497
|
#
|
|
1050
1498
|
|
|
1051
1499
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
1500
|
+
"""Process incoming frames, including user image requests.
|
|
1501
|
+
|
|
1502
|
+
Args:
|
|
1503
|
+
frame: The frame to process.
|
|
1504
|
+
direction: The direction of frame flow in the pipeline.
|
|
1505
|
+
"""
|
|
1052
1506
|
await super().process_frame(frame, direction)
|
|
1053
1507
|
|
|
1054
1508
|
if isinstance(frame, UserImageRequestFrame):
|
|
@@ -1059,9 +1513,20 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1059
1513
|
#
|
|
1060
1514
|
|
|
1061
1515
|
async def push_transcription_frame(self, frame: TranscriptionFrame | InterimTranscriptionFrame):
|
|
1516
|
+
"""Push a transcription frame downstream.
|
|
1517
|
+
|
|
1518
|
+
Args:
|
|
1519
|
+
frame: The transcription frame to push.
|
|
1520
|
+
"""
|
|
1062
1521
|
await self.push_frame(frame)
|
|
1063
1522
|
|
|
1064
1523
|
async def push_app_message(self, message: Any, sender: str):
|
|
1524
|
+
"""Push an application message as an urgent transport frame.
|
|
1525
|
+
|
|
1526
|
+
Args:
|
|
1527
|
+
message: The message data to send.
|
|
1528
|
+
sender: ID of the message sender.
|
|
1529
|
+
"""
|
|
1065
1530
|
frame = DailyTransportMessageUrgentFrame(message=message, participant_id=sender)
|
|
1066
1531
|
await self.push_frame(frame)
|
|
1067
1532
|
|
|
@@ -1075,6 +1540,13 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1075
1540
|
audio_source: str = "microphone",
|
|
1076
1541
|
sample_rate: int = 16000,
|
|
1077
1542
|
):
|
|
1543
|
+
"""Capture audio from a specific participant.
|
|
1544
|
+
|
|
1545
|
+
Args:
|
|
1546
|
+
participant_id: ID of the participant to capture audio from.
|
|
1547
|
+
audio_source: Audio source to capture from.
|
|
1548
|
+
sample_rate: Desired sample rate for audio capture.
|
|
1549
|
+
"""
|
|
1078
1550
|
if self._streaming_started:
|
|
1079
1551
|
await self._client.capture_participant_audio(
|
|
1080
1552
|
participant_id, self._on_participant_audio_data, audio_source, sample_rate
|
|
@@ -1085,6 +1557,7 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1085
1557
|
async def _on_participant_audio_data(
|
|
1086
1558
|
self, participant_id: str, audio: AudioData, audio_source: str
|
|
1087
1559
|
):
|
|
1560
|
+
"""Handle received participant audio data."""
|
|
1088
1561
|
frame = UserAudioRawFrame(
|
|
1089
1562
|
user_id=participant_id,
|
|
1090
1563
|
audio=audio.audio_frames,
|
|
@@ -1094,6 +1567,12 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1094
1567
|
frame.transport_source = audio_source
|
|
1095
1568
|
await self.push_audio_frame(frame)
|
|
1096
1569
|
|
|
1570
|
+
async def _audio_in_task_handler(self):
|
|
1571
|
+
while True:
|
|
1572
|
+
frame = await self._client.read_next_audio_frame()
|
|
1573
|
+
if frame:
|
|
1574
|
+
await self.push_audio_frame(frame)
|
|
1575
|
+
|
|
1097
1576
|
#
|
|
1098
1577
|
# Camera in
|
|
1099
1578
|
#
|
|
@@ -1105,6 +1584,14 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1105
1584
|
video_source: str = "camera",
|
|
1106
1585
|
color_format: str = "RGB",
|
|
1107
1586
|
):
|
|
1587
|
+
"""Capture video from a specific participant.
|
|
1588
|
+
|
|
1589
|
+
Args:
|
|
1590
|
+
participant_id: ID of the participant to capture video from.
|
|
1591
|
+
framerate: Desired framerate for video capture.
|
|
1592
|
+
video_source: Video source to capture from.
|
|
1593
|
+
color_format: Color format for video frames.
|
|
1594
|
+
"""
|
|
1108
1595
|
if participant_id not in self._video_renderers:
|
|
1109
1596
|
self._video_renderers[participant_id] = {}
|
|
1110
1597
|
|
|
@@ -1119,6 +1606,11 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1119
1606
|
)
|
|
1120
1607
|
|
|
1121
1608
|
async def request_participant_image(self, frame: UserImageRequestFrame):
|
|
1609
|
+
"""Request a video frame from a specific participant.
|
|
1610
|
+
|
|
1611
|
+
Args:
|
|
1612
|
+
frame: The user image request frame.
|
|
1613
|
+
"""
|
|
1122
1614
|
if frame.user_id in self._video_renderers:
|
|
1123
1615
|
video_source = frame.video_source if frame.video_source else "camera"
|
|
1124
1616
|
self._video_renderers[frame.user_id][video_source]["render_next_frame"].append(frame)
|
|
@@ -1126,6 +1618,7 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1126
1618
|
async def _on_participant_video_frame(
|
|
1127
1619
|
self, participant_id: str, video_frame: VideoFrame, video_source: str
|
|
1128
1620
|
):
|
|
1621
|
+
"""Handle received participant video frames."""
|
|
1129
1622
|
render_frame = False
|
|
1130
1623
|
|
|
1131
1624
|
curr_time = time.time()
|
|
@@ -1161,16 +1654,21 @@ class DailyInputTransport(BaseInputTransport):
|
|
|
1161
1654
|
class DailyOutputTransport(BaseOutputTransport):
|
|
1162
1655
|
"""Handles outgoing media streams and events to Daily calls.
|
|
1163
1656
|
|
|
1164
|
-
Manages sending audio, video and other data to Daily calls
|
|
1165
|
-
|
|
1166
|
-
Args:
|
|
1167
|
-
client: DailyTransportClient instance.
|
|
1168
|
-
params: Configuration parameters.
|
|
1657
|
+
Manages sending audio, video, DTMF tones, and other data to Daily calls,
|
|
1658
|
+
including audio destination registration and message transmission.
|
|
1169
1659
|
"""
|
|
1170
1660
|
|
|
1171
1661
|
def __init__(
|
|
1172
1662
|
self, transport: BaseTransport, client: DailyTransportClient, params: DailyParams, **kwargs
|
|
1173
1663
|
):
|
|
1664
|
+
"""Initialize the Daily output transport.
|
|
1665
|
+
|
|
1666
|
+
Args:
|
|
1667
|
+
transport: The parent transport instance.
|
|
1668
|
+
client: DailyTransportClient instance.
|
|
1669
|
+
params: Configuration parameters.
|
|
1670
|
+
**kwargs: Additional arguments passed to parent class.
|
|
1671
|
+
"""
|
|
1174
1672
|
super().__init__(params, **kwargs)
|
|
1175
1673
|
|
|
1176
1674
|
self._transport = transport
|
|
@@ -1180,15 +1678,26 @@ class DailyOutputTransport(BaseOutputTransport):
|
|
|
1180
1678
|
self._initialized = False
|
|
1181
1679
|
|
|
1182
1680
|
async def setup(self, setup: FrameProcessorSetup):
|
|
1681
|
+
"""Setup the output transport with shared client setup.
|
|
1682
|
+
|
|
1683
|
+
Args:
|
|
1684
|
+
setup: The frame processor setup configuration.
|
|
1685
|
+
"""
|
|
1183
1686
|
await super().setup(setup)
|
|
1184
1687
|
await self._client.setup(setup)
|
|
1185
1688
|
|
|
1186
1689
|
async def cleanup(self):
|
|
1690
|
+
"""Cleanup output transport and shared resources."""
|
|
1187
1691
|
await super().cleanup()
|
|
1188
1692
|
await self._client.cleanup()
|
|
1189
1693
|
await self._transport.cleanup()
|
|
1190
1694
|
|
|
1191
1695
|
async def start(self, frame: StartFrame):
|
|
1696
|
+
"""Start the output transport and join the Daily room.
|
|
1697
|
+
|
|
1698
|
+
Args:
|
|
1699
|
+
frame: The start frame containing initialization parameters.
|
|
1700
|
+
"""
|
|
1192
1701
|
# Parent start.
|
|
1193
1702
|
await super().start(frame)
|
|
1194
1703
|
|
|
@@ -1207,27 +1716,57 @@ class DailyOutputTransport(BaseOutputTransport):
|
|
|
1207
1716
|
await self.set_transport_ready(frame)
|
|
1208
1717
|
|
|
1209
1718
|
async def stop(self, frame: EndFrame):
|
|
1719
|
+
"""Stop the output transport and leave the Daily room.
|
|
1720
|
+
|
|
1721
|
+
Args:
|
|
1722
|
+
frame: The end frame signaling transport shutdown.
|
|
1723
|
+
"""
|
|
1210
1724
|
# Parent stop.
|
|
1211
1725
|
await super().stop(frame)
|
|
1212
1726
|
# Leave the room.
|
|
1213
1727
|
await self._client.leave()
|
|
1214
1728
|
|
|
1215
1729
|
async def cancel(self, frame: CancelFrame):
|
|
1730
|
+
"""Cancel the output transport and leave the Daily room.
|
|
1731
|
+
|
|
1732
|
+
Args:
|
|
1733
|
+
frame: The cancel frame signaling immediate cancellation.
|
|
1734
|
+
"""
|
|
1216
1735
|
# Parent stop.
|
|
1217
1736
|
await super().cancel(frame)
|
|
1218
1737
|
# Leave the room.
|
|
1219
1738
|
await self._client.leave()
|
|
1220
1739
|
|
|
1221
1740
|
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
|
1741
|
+
"""Send a transport message to participants.
|
|
1742
|
+
|
|
1743
|
+
Args:
|
|
1744
|
+
frame: The transport message frame to send.
|
|
1745
|
+
"""
|
|
1222
1746
|
await self._client.send_message(frame)
|
|
1223
1747
|
|
|
1224
1748
|
async def register_video_destination(self, destination: str):
|
|
1749
|
+
"""Register a video output destination.
|
|
1750
|
+
|
|
1751
|
+
Args:
|
|
1752
|
+
destination: The destination identifier to register.
|
|
1753
|
+
"""
|
|
1225
1754
|
logger.warning(f"{self} registering video destinations is not supported yet")
|
|
1226
1755
|
|
|
1227
1756
|
async def register_audio_destination(self, destination: str):
|
|
1757
|
+
"""Register an audio output destination.
|
|
1758
|
+
|
|
1759
|
+
Args:
|
|
1760
|
+
destination: The destination identifier to register.
|
|
1761
|
+
"""
|
|
1228
1762
|
await self._client.register_audio_destination(destination)
|
|
1229
1763
|
|
|
1230
1764
|
async def write_dtmf(self, frame: OutputDTMFFrame | OutputDTMFUrgentFrame):
|
|
1765
|
+
"""Write DTMF tones to the call.
|
|
1766
|
+
|
|
1767
|
+
Args:
|
|
1768
|
+
frame: The DTMF frame containing tone information.
|
|
1769
|
+
"""
|
|
1231
1770
|
await self._client.send_dtmf(
|
|
1232
1771
|
{
|
|
1233
1772
|
"sessionId": frame.transport_destination,
|
|
@@ -1236,25 +1775,28 @@ class DailyOutputTransport(BaseOutputTransport):
|
|
|
1236
1775
|
)
|
|
1237
1776
|
|
|
1238
1777
|
async def write_audio_frame(self, frame: OutputAudioRawFrame):
|
|
1778
|
+
"""Write an audio frame to the Daily call.
|
|
1779
|
+
|
|
1780
|
+
Args:
|
|
1781
|
+
frame: The audio frame to write.
|
|
1782
|
+
"""
|
|
1239
1783
|
await self._client.write_audio_frame(frame)
|
|
1240
1784
|
|
|
1241
1785
|
async def write_video_frame(self, frame: OutputImageRawFrame):
|
|
1786
|
+
"""Write a video frame to the Daily call.
|
|
1787
|
+
|
|
1788
|
+
Args:
|
|
1789
|
+
frame: The video frame to write.
|
|
1790
|
+
"""
|
|
1242
1791
|
await self._client.write_video_frame(frame)
|
|
1243
1792
|
|
|
1244
1793
|
|
|
1245
1794
|
class DailyTransport(BaseTransport):
|
|
1246
1795
|
"""Transport implementation for Daily audio and video calls.
|
|
1247
1796
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
Args:
|
|
1252
|
-
room_url: URL of the Daily room to connect to.
|
|
1253
|
-
token: Optional authentication token for the room.
|
|
1254
|
-
bot_name: Display name for the bot in the call.
|
|
1255
|
-
params: Configuration parameters (DailyParams) for the transport.
|
|
1256
|
-
input_name: Optional name for the input transport.
|
|
1257
|
-
output_name: Optional name for the output transport.
|
|
1797
|
+
Provides comprehensive Daily integration including audio/video streaming,
|
|
1798
|
+
transcription, recording, dial-in/out functionality, and real-time communication
|
|
1799
|
+
features for conversational AI applications.
|
|
1258
1800
|
"""
|
|
1259
1801
|
|
|
1260
1802
|
def __init__(
|
|
@@ -1266,6 +1808,16 @@ class DailyTransport(BaseTransport):
|
|
|
1266
1808
|
input_name: Optional[str] = None,
|
|
1267
1809
|
output_name: Optional[str] = None,
|
|
1268
1810
|
):
|
|
1811
|
+
"""Initialize the Daily transport.
|
|
1812
|
+
|
|
1813
|
+
Args:
|
|
1814
|
+
room_url: URL of the Daily room to connect to.
|
|
1815
|
+
token: Optional authentication token for the room.
|
|
1816
|
+
bot_name: Display name for the bot in the call.
|
|
1817
|
+
params: Configuration parameters for the transport.
|
|
1818
|
+
input_name: Optional name for the input transport.
|
|
1819
|
+
output_name: Optional name for the output transport.
|
|
1820
|
+
"""
|
|
1269
1821
|
super().__init__(input_name=input_name, output_name=output_name)
|
|
1270
1822
|
|
|
1271
1823
|
callbacks = DailyCallbacks(
|
|
@@ -1291,6 +1843,8 @@ class DailyTransport(BaseTransport):
|
|
|
1291
1843
|
on_participant_left=self._on_participant_left,
|
|
1292
1844
|
on_participant_updated=self._on_participant_updated,
|
|
1293
1845
|
on_transcription_message=self._on_transcription_message,
|
|
1846
|
+
on_transcription_stopped=self._on_transcription_stopped,
|
|
1847
|
+
on_transcription_error=self._on_transcription_error,
|
|
1294
1848
|
on_recording_started=self._on_recording_started,
|
|
1295
1849
|
on_recording_stopped=self._on_recording_stopped,
|
|
1296
1850
|
on_recording_error=self._on_recording_error,
|
|
@@ -1339,6 +1893,11 @@ class DailyTransport(BaseTransport):
|
|
|
1339
1893
|
#
|
|
1340
1894
|
|
|
1341
1895
|
def input(self) -> DailyInputTransport:
|
|
1896
|
+
"""Get the input transport for receiving media and events.
|
|
1897
|
+
|
|
1898
|
+
Returns:
|
|
1899
|
+
The Daily input transport instance.
|
|
1900
|
+
"""
|
|
1342
1901
|
if not self._input:
|
|
1343
1902
|
self._input = DailyInputTransport(
|
|
1344
1903
|
self, self._client, self._params, name=self._input_name
|
|
@@ -1346,6 +1905,11 @@ class DailyTransport(BaseTransport):
|
|
|
1346
1905
|
return self._input
|
|
1347
1906
|
|
|
1348
1907
|
def output(self) -> DailyOutputTransport:
|
|
1908
|
+
"""Get the output transport for sending media and events.
|
|
1909
|
+
|
|
1910
|
+
Returns:
|
|
1911
|
+
The Daily output transport instance.
|
|
1912
|
+
"""
|
|
1349
1913
|
if not self._output:
|
|
1350
1914
|
self._output = DailyOutputTransport(
|
|
1351
1915
|
self, self._client, self._params, name=self._output_name
|
|
@@ -1358,33 +1922,93 @@ class DailyTransport(BaseTransport):
|
|
|
1358
1922
|
|
|
1359
1923
|
@property
|
|
1360
1924
|
def room_url(self) -> str:
|
|
1925
|
+
"""Get the Daily room URL.
|
|
1926
|
+
|
|
1927
|
+
Returns:
|
|
1928
|
+
The room URL this transport is connected to.
|
|
1929
|
+
"""
|
|
1361
1930
|
return self._client.room_url
|
|
1362
1931
|
|
|
1363
1932
|
@property
|
|
1364
1933
|
def participant_id(self) -> str:
|
|
1934
|
+
"""Get the participant ID for this transport.
|
|
1935
|
+
|
|
1936
|
+
Returns:
|
|
1937
|
+
The participant ID assigned by Daily.
|
|
1938
|
+
"""
|
|
1365
1939
|
return self._client.participant_id
|
|
1366
1940
|
|
|
1941
|
+
def set_log_level(self, level: DailyLogLevel):
|
|
1942
|
+
"""Set the logging level for Daily's internal logging system.
|
|
1943
|
+
|
|
1944
|
+
Args:
|
|
1945
|
+
level: The log level to set. Should be a member of the DailyLogLevel enum,
|
|
1946
|
+
such as DailyLogLevel.Info, DailyLogLevel.Debug, etc.
|
|
1947
|
+
|
|
1948
|
+
Example:
|
|
1949
|
+
transport.set_log_level(DailyLogLevel.Info)
|
|
1950
|
+
"""
|
|
1951
|
+
Daily.set_log_level(level)
|
|
1952
|
+
|
|
1367
1953
|
async def send_image(self, frame: OutputImageRawFrame | SpriteFrame):
|
|
1954
|
+
"""Send an image frame to the Daily call.
|
|
1955
|
+
|
|
1956
|
+
Args:
|
|
1957
|
+
frame: The image frame to send.
|
|
1958
|
+
"""
|
|
1368
1959
|
if self._output:
|
|
1369
1960
|
await self._output.queue_frame(frame, FrameDirection.DOWNSTREAM)
|
|
1370
1961
|
|
|
1371
1962
|
async def send_audio(self, frame: OutputAudioRawFrame):
|
|
1963
|
+
"""Send an audio frame to the Daily call.
|
|
1964
|
+
|
|
1965
|
+
Args:
|
|
1966
|
+
frame: The audio frame to send.
|
|
1967
|
+
"""
|
|
1372
1968
|
if self._output:
|
|
1373
1969
|
await self._output.queue_frame(frame, FrameDirection.DOWNSTREAM)
|
|
1374
1970
|
|
|
1375
1971
|
def participants(self):
|
|
1972
|
+
"""Get current participants in the room.
|
|
1973
|
+
|
|
1974
|
+
Returns:
|
|
1975
|
+
Dictionary of participants keyed by participant ID.
|
|
1976
|
+
"""
|
|
1376
1977
|
return self._client.participants()
|
|
1377
1978
|
|
|
1378
1979
|
def participant_counts(self):
|
|
1980
|
+
"""Get participant count information.
|
|
1981
|
+
|
|
1982
|
+
Returns:
|
|
1983
|
+
Dictionary with participant count details.
|
|
1984
|
+
"""
|
|
1379
1985
|
return self._client.participant_counts()
|
|
1380
1986
|
|
|
1381
1987
|
async def start_dialout(self, settings=None):
|
|
1988
|
+
"""Start a dial-out call to a phone number.
|
|
1989
|
+
|
|
1990
|
+
Args:
|
|
1991
|
+
settings: Dial-out configuration settings.
|
|
1992
|
+
"""
|
|
1382
1993
|
await self._client.start_dialout(settings)
|
|
1383
1994
|
|
|
1384
1995
|
async def stop_dialout(self, participant_id):
|
|
1996
|
+
"""Stop a dial-out call for a specific participant.
|
|
1997
|
+
|
|
1998
|
+
Args:
|
|
1999
|
+
participant_id: ID of the participant to stop dial-out for.
|
|
2000
|
+
"""
|
|
1385
2001
|
await self._client.stop_dialout(participant_id)
|
|
1386
2002
|
|
|
1387
2003
|
async def send_dtmf(self, settings):
|
|
2004
|
+
"""Send DTMF tones during a call (deprecated).
|
|
2005
|
+
|
|
2006
|
+
.. deprecated:: 0.0.69
|
|
2007
|
+
Push an `OutputDTMFFrame` or an `OutputDTMFUrgentFrame` instead.
|
|
2008
|
+
|
|
2009
|
+
Args:
|
|
2010
|
+
settings: DTMF settings including tones and target session.
|
|
2011
|
+
"""
|
|
1388
2012
|
import warnings
|
|
1389
2013
|
|
|
1390
2014
|
with warnings.catch_warnings():
|
|
@@ -1396,33 +2020,66 @@ class DailyTransport(BaseTransport):
|
|
|
1396
2020
|
await self._client.send_dtmf(settings)
|
|
1397
2021
|
|
|
1398
2022
|
async def sip_call_transfer(self, settings):
|
|
2023
|
+
"""Transfer a SIP call to another destination.
|
|
2024
|
+
|
|
2025
|
+
Args:
|
|
2026
|
+
settings: SIP call transfer settings.
|
|
2027
|
+
"""
|
|
1399
2028
|
await self._client.sip_call_transfer(settings)
|
|
1400
2029
|
|
|
1401
2030
|
async def sip_refer(self, settings):
|
|
2031
|
+
"""Send a SIP REFER request.
|
|
2032
|
+
|
|
2033
|
+
Args:
|
|
2034
|
+
settings: SIP REFER settings.
|
|
2035
|
+
"""
|
|
1402
2036
|
await self._client.sip_refer(settings)
|
|
1403
2037
|
|
|
1404
2038
|
async def start_recording(self, streaming_settings=None, stream_id=None, force_new=None):
|
|
2039
|
+
"""Start recording the call.
|
|
2040
|
+
|
|
2041
|
+
Args:
|
|
2042
|
+
streaming_settings: Recording configuration settings.
|
|
2043
|
+
stream_id: Unique identifier for the recording stream.
|
|
2044
|
+
force_new: Whether to force a new recording session.
|
|
2045
|
+
"""
|
|
1405
2046
|
await self._client.start_recording(streaming_settings, stream_id, force_new)
|
|
1406
2047
|
|
|
1407
2048
|
async def stop_recording(self, stream_id=None):
|
|
2049
|
+
"""Stop recording the call.
|
|
2050
|
+
|
|
2051
|
+
Args:
|
|
2052
|
+
stream_id: Unique identifier for the recording stream to stop.
|
|
2053
|
+
"""
|
|
1408
2054
|
await self._client.stop_recording(stream_id)
|
|
1409
2055
|
|
|
1410
2056
|
async def start_transcription(self, settings=None):
|
|
2057
|
+
"""Start transcription for the call.
|
|
2058
|
+
|
|
2059
|
+
Args:
|
|
2060
|
+
settings: Transcription configuration settings.
|
|
2061
|
+
"""
|
|
1411
2062
|
await self._client.start_transcription(settings)
|
|
1412
2063
|
|
|
1413
2064
|
async def stop_transcription(self):
|
|
2065
|
+
"""Stop transcription for the call."""
|
|
1414
2066
|
await self._client.stop_transcription()
|
|
1415
2067
|
|
|
1416
2068
|
async def send_prebuilt_chat_message(self, message: str, user_name: Optional[str] = None):
|
|
1417
|
-
"""
|
|
2069
|
+
"""Send a chat message to Daily's Prebuilt main room.
|
|
1418
2070
|
|
|
1419
2071
|
Args:
|
|
1420
|
-
|
|
1421
|
-
|
|
2072
|
+
message: The chat message to send.
|
|
2073
|
+
user_name: Optional user name that will appear as sender of the message.
|
|
1422
2074
|
"""
|
|
1423
2075
|
await self._client.send_prebuilt_chat_message(message, user_name)
|
|
1424
2076
|
|
|
1425
2077
|
async def capture_participant_transcription(self, participant_id: str):
|
|
2078
|
+
"""Enable transcription capture for a specific participant.
|
|
2079
|
+
|
|
2080
|
+
Args:
|
|
2081
|
+
participant_id: ID of the participant to capture transcription for.
|
|
2082
|
+
"""
|
|
1426
2083
|
await self._client.capture_participant_transcription(participant_id)
|
|
1427
2084
|
|
|
1428
2085
|
async def capture_participant_audio(
|
|
@@ -1431,6 +2088,13 @@ class DailyTransport(BaseTransport):
|
|
|
1431
2088
|
audio_source: str = "microphone",
|
|
1432
2089
|
sample_rate: int = 16000,
|
|
1433
2090
|
):
|
|
2091
|
+
"""Capture audio from a specific participant.
|
|
2092
|
+
|
|
2093
|
+
Args:
|
|
2094
|
+
participant_id: ID of the participant to capture audio from.
|
|
2095
|
+
audio_source: Audio source to capture from.
|
|
2096
|
+
sample_rate: Desired sample rate for audio capture.
|
|
2097
|
+
"""
|
|
1434
2098
|
if self._input:
|
|
1435
2099
|
await self._input.capture_participant_audio(participant_id, audio_source, sample_rate)
|
|
1436
2100
|
|
|
@@ -1441,32 +2105,60 @@ class DailyTransport(BaseTransport):
|
|
|
1441
2105
|
video_source: str = "camera",
|
|
1442
2106
|
color_format: str = "RGB",
|
|
1443
2107
|
):
|
|
2108
|
+
"""Capture video from a specific participant.
|
|
2109
|
+
|
|
2110
|
+
Args:
|
|
2111
|
+
participant_id: ID of the participant to capture video from.
|
|
2112
|
+
framerate: Desired framerate for video capture.
|
|
2113
|
+
video_source: Video source to capture from.
|
|
2114
|
+
color_format: Color format for video frames.
|
|
2115
|
+
"""
|
|
1444
2116
|
if self._input:
|
|
1445
2117
|
await self._input.capture_participant_video(
|
|
1446
2118
|
participant_id, framerate, video_source, color_format
|
|
1447
2119
|
)
|
|
1448
2120
|
|
|
1449
2121
|
async def update_publishing(self, publishing_settings: Mapping[str, Any]):
|
|
2122
|
+
"""Update media publishing settings.
|
|
2123
|
+
|
|
2124
|
+
Args:
|
|
2125
|
+
publishing_settings: Publishing configuration settings.
|
|
2126
|
+
"""
|
|
1450
2127
|
await self._client.update_publishing(publishing_settings=publishing_settings)
|
|
1451
2128
|
|
|
1452
2129
|
async def update_subscriptions(self, participant_settings=None, profile_settings=None):
|
|
2130
|
+
"""Update media subscription settings.
|
|
2131
|
+
|
|
2132
|
+
Args:
|
|
2133
|
+
participant_settings: Per-participant subscription settings.
|
|
2134
|
+
profile_settings: Global subscription profile settings.
|
|
2135
|
+
"""
|
|
1453
2136
|
await self._client.update_subscriptions(
|
|
1454
2137
|
participant_settings=participant_settings, profile_settings=profile_settings
|
|
1455
2138
|
)
|
|
1456
2139
|
|
|
1457
2140
|
async def update_remote_participants(self, remote_participants: Mapping[str, Any]):
|
|
2141
|
+
"""Update settings for remote participants.
|
|
2142
|
+
|
|
2143
|
+
Args:
|
|
2144
|
+
remote_participants: Remote participant configuration settings.
|
|
2145
|
+
"""
|
|
1458
2146
|
await self._client.update_remote_participants(remote_participants=remote_participants)
|
|
1459
2147
|
|
|
1460
2148
|
async def _on_active_speaker_changed(self, participant: Any):
|
|
2149
|
+
"""Handle active speaker change events."""
|
|
1461
2150
|
await self._call_event_handler("on_active_speaker_changed", participant)
|
|
1462
2151
|
|
|
1463
2152
|
async def _on_joined(self, data):
|
|
2153
|
+
"""Handle room joined events."""
|
|
1464
2154
|
await self._call_event_handler("on_joined", data)
|
|
1465
2155
|
|
|
1466
2156
|
async def _on_left(self):
|
|
2157
|
+
"""Handle room left events."""
|
|
1467
2158
|
await self._call_event_handler("on_left")
|
|
1468
2159
|
|
|
1469
2160
|
async def _on_error(self, error):
|
|
2161
|
+
"""Handle error events and push error frames."""
|
|
1470
2162
|
await self._call_event_handler("on_error", error)
|
|
1471
2163
|
# Push error frame to notify the pipeline
|
|
1472
2164
|
error_frame = ErrorFrame(error)
|
|
@@ -1480,20 +2172,25 @@ class DailyTransport(BaseTransport):
|
|
|
1480
2172
|
raise Exception("No valid input or output channel to push error")
|
|
1481
2173
|
|
|
1482
2174
|
async def _on_app_message(self, message: Any, sender: str):
|
|
2175
|
+
"""Handle application message events."""
|
|
1483
2176
|
if self._input:
|
|
1484
2177
|
await self._input.push_app_message(message, sender)
|
|
1485
2178
|
await self._call_event_handler("on_app_message", message, sender)
|
|
1486
2179
|
|
|
1487
2180
|
async def _on_call_state_updated(self, state: str):
|
|
2181
|
+
"""Handle call state update events."""
|
|
1488
2182
|
await self._call_event_handler("on_call_state_updated", state)
|
|
1489
2183
|
|
|
1490
2184
|
async def _on_client_connected(self, participant: Any):
|
|
2185
|
+
"""Handle client connected events."""
|
|
1491
2186
|
await self._call_event_handler("on_client_connected", participant)
|
|
1492
2187
|
|
|
1493
2188
|
async def _on_client_disconnected(self, participant: Any):
|
|
2189
|
+
"""Handle client disconnected events."""
|
|
1494
2190
|
await self._call_event_handler("on_client_disconnected", participant)
|
|
1495
2191
|
|
|
1496
2192
|
async def _handle_dialin_ready(self, sip_endpoint: str):
|
|
2193
|
+
"""Handle dial-in ready events by updating SIP configuration."""
|
|
1497
2194
|
if not self._params.dialin_settings:
|
|
1498
2195
|
return
|
|
1499
2196
|
|
|
@@ -1528,42 +2225,53 @@ class DailyTransport(BaseTransport):
|
|
|
1528
2225
|
logger.exception(f"Error handling dialin-ready event ({url}): {e}")
|
|
1529
2226
|
|
|
1530
2227
|
async def _on_dialin_connected(self, data):
|
|
2228
|
+
"""Handle dial-in connected events."""
|
|
1531
2229
|
await self._call_event_handler("on_dialin_connected", data)
|
|
1532
2230
|
|
|
1533
2231
|
async def _on_dialin_ready(self, sip_endpoint):
|
|
2232
|
+
"""Handle dial-in ready events."""
|
|
1534
2233
|
if self._params.dialin_settings:
|
|
1535
2234
|
await self._handle_dialin_ready(sip_endpoint)
|
|
1536
2235
|
await self._call_event_handler("on_dialin_ready", sip_endpoint)
|
|
1537
2236
|
|
|
1538
2237
|
async def _on_dialin_stopped(self, data):
|
|
2238
|
+
"""Handle dial-in stopped events."""
|
|
1539
2239
|
await self._call_event_handler("on_dialin_stopped", data)
|
|
1540
2240
|
|
|
1541
2241
|
async def _on_dialin_error(self, data):
|
|
2242
|
+
"""Handle dial-in error events."""
|
|
1542
2243
|
await self._call_event_handler("on_dialin_error", data)
|
|
1543
2244
|
|
|
1544
2245
|
async def _on_dialin_warning(self, data):
|
|
2246
|
+
"""Handle dial-in warning events."""
|
|
1545
2247
|
await self._call_event_handler("on_dialin_warning", data)
|
|
1546
2248
|
|
|
1547
2249
|
async def _on_dialout_answered(self, data):
|
|
2250
|
+
"""Handle dial-out answered events."""
|
|
1548
2251
|
await self._call_event_handler("on_dialout_answered", data)
|
|
1549
2252
|
|
|
1550
2253
|
async def _on_dialout_connected(self, data):
|
|
2254
|
+
"""Handle dial-out connected events."""
|
|
1551
2255
|
await self._call_event_handler("on_dialout_connected", data)
|
|
1552
2256
|
|
|
1553
2257
|
async def _on_dialout_stopped(self, data):
|
|
2258
|
+
"""Handle dial-out stopped events."""
|
|
1554
2259
|
await self._call_event_handler("on_dialout_stopped", data)
|
|
1555
2260
|
|
|
1556
2261
|
async def _on_dialout_error(self, data):
|
|
2262
|
+
"""Handle dial-out error events."""
|
|
1557
2263
|
await self._call_event_handler("on_dialout_error", data)
|
|
1558
2264
|
|
|
1559
2265
|
async def _on_dialout_warning(self, data):
|
|
2266
|
+
"""Handle dial-out warning events."""
|
|
1560
2267
|
await self._call_event_handler("on_dialout_warning", data)
|
|
1561
2268
|
|
|
1562
2269
|
async def _on_participant_joined(self, participant):
|
|
2270
|
+
"""Handle participant joined events."""
|
|
1563
2271
|
id = participant["id"]
|
|
1564
2272
|
logger.info(f"Participant joined {id}")
|
|
1565
2273
|
|
|
1566
|
-
if self._input and self._params.audio_in_enabled:
|
|
2274
|
+
if self._input and self._params.audio_in_enabled and self._params.audio_in_user_tracks:
|
|
1567
2275
|
await self._input.capture_participant_audio(
|
|
1568
2276
|
id, "microphone", self._client.in_sample_rate
|
|
1569
2277
|
)
|
|
@@ -1577,6 +2285,7 @@ class DailyTransport(BaseTransport):
|
|
|
1577
2285
|
await self._call_event_handler("on_client_connected", participant)
|
|
1578
2286
|
|
|
1579
2287
|
async def _on_participant_left(self, participant, reason):
|
|
2288
|
+
"""Handle participant left events."""
|
|
1580
2289
|
id = participant["id"]
|
|
1581
2290
|
logger.info(f"Participant left {id}")
|
|
1582
2291
|
await self._call_event_handler("on_participant_left", participant, reason)
|
|
@@ -1584,9 +2293,11 @@ class DailyTransport(BaseTransport):
|
|
|
1584
2293
|
await self._call_event_handler("on_client_disconnected", participant)
|
|
1585
2294
|
|
|
1586
2295
|
async def _on_participant_updated(self, participant):
|
|
2296
|
+
"""Handle participant updated events."""
|
|
1587
2297
|
await self._call_event_handler("on_participant_updated", participant)
|
|
1588
2298
|
|
|
1589
2299
|
async def _on_transcription_message(self, message):
|
|
2300
|
+
"""Handle transcription message events."""
|
|
1590
2301
|
await self._call_event_handler("on_transcription_message", message)
|
|
1591
2302
|
|
|
1592
2303
|
participant_id = ""
|
|
@@ -1618,11 +2329,22 @@ class DailyTransport(BaseTransport):
|
|
|
1618
2329
|
if self._input:
|
|
1619
2330
|
await self._input.push_transcription_frame(frame)
|
|
1620
2331
|
|
|
2332
|
+
async def _on_transcription_stopped(self, stopped_by, stopped_by_error):
|
|
2333
|
+
"""Handle transcription stopped events."""
|
|
2334
|
+
await self._call_event_handler("on_transcription_stopped", stopped_by, stopped_by_error)
|
|
2335
|
+
|
|
2336
|
+
async def _on_transcription_error(self, message):
|
|
2337
|
+
"""Handle transcription error events."""
|
|
2338
|
+
await self._call_event_handler("on_transcription_error", message)
|
|
2339
|
+
|
|
1621
2340
|
async def _on_recording_started(self, status):
|
|
2341
|
+
"""Handle recording started events."""
|
|
1622
2342
|
await self._call_event_handler("on_recording_started", status)
|
|
1623
2343
|
|
|
1624
2344
|
async def _on_recording_stopped(self, stream_id):
|
|
2345
|
+
"""Handle recording stopped events."""
|
|
1625
2346
|
await self._call_event_handler("on_recording_stopped", stream_id)
|
|
1626
2347
|
|
|
1627
2348
|
async def _on_recording_error(self, stream_id, message):
|
|
2349
|
+
"""Handle recording error events."""
|
|
1628
2350
|
await self._call_event_handler("on_recording_error", stream_id, message)
|