PR feedback and more explicit about only supporting exporting 1 video
This commit is contained in:
committed by
Mattie Ruth
parent
b987579d54
commit
bad9977e8c
@@ -228,7 +228,7 @@ class OutputImageRawFrame(DataFrame, ImageRawFrame):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
pts = format_pts(self.pts)
|
pts = format_pts(self.pts)
|
||||||
return f"{self.name}(pts: {pts}, size: {self.size}, format: {self.format})"
|
return f"{self.name}(pts: {pts}, destination: {self.transport_destination}, size: {self.size}, format: {self.format})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -233,15 +233,12 @@ async def maybe_capture_participant_camera(
|
|||||||
framerate: Video capture framerate. Defaults to 0 (auto).
|
framerate: Video capture framerate. Defaults to 0 (auto).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
|
||||||
from pipecat.transports.services.daily import DailyTransport
|
from pipecat.transports.services.daily import DailyTransport
|
||||||
|
|
||||||
if isinstance(transport, DailyTransport):
|
if isinstance(transport, DailyTransport):
|
||||||
await transport.capture_participant_video(
|
await transport.capture_participant_video(
|
||||||
client["id"], framerate=framerate, video_source="camera"
|
client["id"], framerate=framerate, video_source="camera"
|
||||||
)
|
)
|
||||||
elif isinstance(transport, SmallWebRTCTransport):
|
|
||||||
await transport.capture_participant_video(video_source="camera")
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -257,15 +254,12 @@ async def maybe_capture_participant_screen(
|
|||||||
framerate: Video capture framerate. Defaults to 0 (auto).
|
framerate: Video capture framerate. Defaults to 0 (auto).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from pipecat.transports.network.small_webrtc import SmallWebRTCTransport
|
|
||||||
from pipecat.transports.services.daily import DailyTransport
|
from pipecat.transports.services.daily import DailyTransport
|
||||||
|
|
||||||
if isinstance(transport, DailyTransport):
|
if isinstance(transport, DailyTransport):
|
||||||
await transport.capture_participant_video(
|
await transport.capture_participant_video(
|
||||||
client["id"], framerate=framerate, video_source="screenVideo"
|
client["id"], framerate=framerate, video_source="screenVideo"
|
||||||
)
|
)
|
||||||
elif isinstance(transport, SmallWebRTCTransport):
|
|
||||||
await transport.capture_participant_video(video_source="screenVideo")
|
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ except ModuleNotFoundError as e:
|
|||||||
logger.error("In order to use the SmallWebRTC, you need to `pip install pipecat-ai[webrtc]`.")
|
logger.error("In order to use the SmallWebRTC, you need to `pip install pipecat-ai[webrtc]`.")
|
||||||
raise Exception(f"Missing module: {e}")
|
raise Exception(f"Missing module: {e}")
|
||||||
|
|
||||||
|
CAM_VIDEO_SOURCE = "camera"
|
||||||
|
SCREEN_VIDEO_SOURCE = "screenVideo"
|
||||||
|
MIC_AUDIO_SOURCE = "microphone"
|
||||||
|
|
||||||
|
|
||||||
class SmallWebRTCCallbacks(BaseModel):
|
class SmallWebRTCCallbacks(BaseModel):
|
||||||
"""Callback handlers for SmallWebRTC events.
|
"""Callback handlers for SmallWebRTC events.
|
||||||
@@ -288,7 +292,9 @@ class SmallWebRTCClient:
|
|||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
video_track = (
|
video_track = (
|
||||||
self._video_input_track if video_source == "camera" else self._screen_video_track
|
self._video_input_track
|
||||||
|
if video_source == CAM_VIDEO_SOURCE
|
||||||
|
else self._screen_video_track
|
||||||
)
|
)
|
||||||
if video_track is None:
|
if video_track is None:
|
||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.01)
|
||||||
@@ -562,7 +568,7 @@ class SmallWebRTCInputTransport(BaseInputTransport):
|
|||||||
if not self._receive_audio_task and self._params.audio_in_enabled:
|
if not self._receive_audio_task and self._params.audio_in_enabled:
|
||||||
self._receive_audio_task = self.create_task(self._receive_audio())
|
self._receive_audio_task = self.create_task(self._receive_audio())
|
||||||
if not self._receive_video_task and self._params.video_in_enabled:
|
if not self._receive_video_task and self._params.video_in_enabled:
|
||||||
self._receive_video_task = self.create_task(self._receive_video("camera"))
|
self._receive_video_task = self.create_task(self._receive_video(CAM_VIDEO_SOURCE))
|
||||||
|
|
||||||
async def _stop_tasks(self):
|
async def _stop_tasks(self):
|
||||||
"""Stop all background tasks."""
|
"""Stop all background tasks."""
|
||||||
@@ -665,23 +671,27 @@ class SmallWebRTCInputTransport(BaseInputTransport):
|
|||||||
|
|
||||||
# If we're not already receiving video, try to get a frame now
|
# If we're not already receiving video, try to get a frame now
|
||||||
if (
|
if (
|
||||||
frame.video_source == "camera"
|
frame.video_source == CAM_VIDEO_SOURCE
|
||||||
and not self._receive_video_task
|
and not self._receive_video_task
|
||||||
and self._params.video_in_enabled
|
and self._params.video_in_enabled
|
||||||
):
|
):
|
||||||
# Start video reception if it's not already running
|
# Start video reception if it's not already running
|
||||||
self._receive_video_task = self.create_task(self._receive_video("camera"))
|
self._receive_video_task = self.create_task(self._receive_video(CAM_VIDEO_SOURCE))
|
||||||
elif (
|
elif (
|
||||||
frame.video_source == "screenVideo"
|
frame.video_source == SCREEN_VIDEO_SOURCE
|
||||||
and not self._receive_screen_video_task
|
and not self._receive_screen_video_task
|
||||||
and self._params.video_in_enabled
|
and self._params.video_in_enabled
|
||||||
):
|
):
|
||||||
|
print(f"Starting screen video task in request_participant_image")
|
||||||
|
|
||||||
# Start screen video reception if it's not already running
|
# Start screen video reception if it's not already running
|
||||||
self._receive_screen_video_task = self.create_task(self._receive_video("screenVideo"))
|
self._receive_screen_video_task = self.create_task(
|
||||||
|
self._receive_video(SCREEN_VIDEO_SOURCE)
|
||||||
|
)
|
||||||
|
|
||||||
async def capture_participant_media(
|
async def capture_participant_media(
|
||||||
self,
|
self,
|
||||||
source: str = "camera",
|
source: str = CAM_VIDEO_SOURCE,
|
||||||
):
|
):
|
||||||
"""Capture media from a specific participant.
|
"""Capture media from a specific participant.
|
||||||
|
|
||||||
@@ -690,22 +700,29 @@ class SmallWebRTCInputTransport(BaseInputTransport):
|
|||||||
"""
|
"""
|
||||||
# If we're not already receiving video, try to get a frame now
|
# If we're not already receiving video, try to get a frame now
|
||||||
if (
|
if (
|
||||||
source == "microphone"
|
source == MIC_AUDIO_SOURCE
|
||||||
and not self._receive_audio_task
|
and not self._receive_audio_task
|
||||||
and self._params.audio_in_enabled
|
and self._params.audio_in_enabled
|
||||||
):
|
):
|
||||||
# Start audio reception if it's not already running
|
# Start audio reception if it's not already running
|
||||||
self._receive_audio_task = self.create_task(self._receive_audio())
|
self._receive_audio_task = self.create_task(self._receive_audio())
|
||||||
elif source == "camera" and not self._receive_video_task and self._params.video_in_enabled:
|
|
||||||
# Start video reception if it's not already running
|
|
||||||
self._receive_video_task = self.create_task(self._receive_video("camera"))
|
|
||||||
elif (
|
elif (
|
||||||
source == "screenVideo"
|
source == CAM_VIDEO_SOURCE
|
||||||
|
and not self._receive_video_task
|
||||||
|
and self._params.video_in_enabled
|
||||||
|
):
|
||||||
|
# Start video reception if it's not already running
|
||||||
|
self._receive_video_task = self.create_task(self._receive_video(CAM_VIDEO_SOURCE))
|
||||||
|
elif (
|
||||||
|
source == SCREEN_VIDEO_SOURCE
|
||||||
and not self._receive_screen_video_task
|
and not self._receive_screen_video_task
|
||||||
and self._params.video_in_enabled
|
and self._params.video_in_enabled
|
||||||
):
|
):
|
||||||
# Start screen video reception if it's not already running
|
# Start screen video reception if it's not already running
|
||||||
self._receive_screen_video_task = self.create_task(self._receive_video("screenVideo"))
|
print(f"Starting screen video task in capture_participant_media")
|
||||||
|
self._receive_screen_video_task = self.create_task(
|
||||||
|
self._receive_video(SCREEN_VIDEO_SOURCE)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SmallWebRTCOutputTransport(BaseOutputTransport):
|
class SmallWebRTCOutputTransport(BaseOutputTransport):
|
||||||
@@ -895,34 +912,24 @@ class SmallWebRTCTransport(BaseTransport):
|
|||||||
|
|
||||||
async def capture_participant_video(
|
async def capture_participant_video(
|
||||||
self,
|
self,
|
||||||
participant_id: str = None,
|
video_source: str = CAM_VIDEO_SOURCE,
|
||||||
framerate: int = 30,
|
|
||||||
video_source: str = "camera",
|
|
||||||
color_format: str = "RGB",
|
|
||||||
):
|
):
|
||||||
"""Capture video from a specific participant.
|
"""Capture video from a specific participant.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
participant_id: Unused parameter, kept for compatibility.
|
video_source: Video source to capture from ("camera" or "screenVideo").
|
||||||
framerate: Unused parameter, kept for compatibility.
|
|
||||||
video_source: Video source to capture from.
|
|
||||||
color_format: Unused parameter, kept for compatibility.
|
|
||||||
"""
|
"""
|
||||||
if self._input:
|
if self._input:
|
||||||
await self._input.capture_participant_media(source=video_source)
|
await self._input.capture_participant_media(source=video_source)
|
||||||
|
|
||||||
async def capture_participant_audio(
|
async def capture_participant_audio(
|
||||||
self,
|
self,
|
||||||
participant_id: str = None,
|
audio_source: str = MIC_AUDIO_SOURCE,
|
||||||
audio_source: str = "microphone",
|
|
||||||
sample_rate: int = 16000,
|
|
||||||
):
|
):
|
||||||
"""Capture audio from a specific participant.
|
"""Capture audio from a specific participant.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
participant_id: Unused parameter, kept for compatibility.
|
|
||||||
audio_source: Audio source to capture from. (currently, "microphone" is the only supported option)
|
audio_source: Audio source to capture from. (currently, "microphone" is the only supported option)
|
||||||
sample_rate: Unused parameter, kept for compatibility.
|
|
||||||
"""
|
"""
|
||||||
if self._input:
|
if self._input:
|
||||||
await self._input.capture_participant_media(source=audio_source)
|
await self._input.capture_participant_media(source=audio_source)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class SmallWebRTCTrack:
|
|||||||
enable/disable control and frame discarding for audio and video streams.
|
enable/disable control and frame discarding for audio and video streams.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, track: MediaStreamTrack, index: int):
|
def __init__(self, track: MediaStreamTrack):
|
||||||
"""Initialize the WebRTC track wrapper.
|
"""Initialize the WebRTC track wrapper.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -104,7 +104,6 @@ class SmallWebRTCTrack:
|
|||||||
"""
|
"""
|
||||||
self._track = track
|
self._track = track
|
||||||
self._enabled = True
|
self._enabled = True
|
||||||
self.source_index = index
|
|
||||||
|
|
||||||
def set_enabled(self, enabled: bool) -> None:
|
def set_enabled(self, enabled: bool) -> None:
|
||||||
"""Enable or disable the track.
|
"""Enable or disable the track.
|
||||||
@@ -350,7 +349,11 @@ class SmallWebRTCConnection(BaseObject):
|
|||||||
screen_video_input_track = self.screen_video_input_track()
|
screen_video_input_track = self.screen_video_input_track()
|
||||||
if screen_video_input_track:
|
if screen_video_input_track:
|
||||||
await self.screen_video_input_track().discard_old_frames()
|
await self.screen_video_input_track().discard_old_frames()
|
||||||
self.ask_to_renegotiate()
|
if video_input_track or screen_video_input_track:
|
||||||
|
# This prevents an issue where sometimes the WebRTC connection can be established
|
||||||
|
# before the bot is ready to receive video. When that happens, we can lose a couple
|
||||||
|
# of seconds of video before we received a key frame to finally start displaying it.
|
||||||
|
self.ask_to_renegotiate()
|
||||||
|
|
||||||
async def renegotiate(self, sdp: str, type: str, restart_pc: bool = False):
|
async def renegotiate(self, sdp: str, type: str, restart_pc: bool = False):
|
||||||
"""Renegotiate the WebRTC connection with new parameters.
|
"""Renegotiate the WebRTC connection with new parameters.
|
||||||
@@ -385,7 +388,11 @@ class SmallWebRTCConnection(BaseObject):
|
|||||||
def force_transceivers_to_send_recv(self):
|
def force_transceivers_to_send_recv(self):
|
||||||
"""Force all transceivers to bidirectional send/receive mode."""
|
"""Force all transceivers to bidirectional send/receive mode."""
|
||||||
for transceiver in self._pc.getTransceivers():
|
for transceiver in self._pc.getTransceivers():
|
||||||
transceiver.direction = "sendrecv"
|
# For now, we only support sendrecv for camera audio and video (the first two transceivers)
|
||||||
|
if transceiver.mid == "0" or transceiver.mid == "1":
|
||||||
|
transceiver.direction = "sendrecv"
|
||||||
|
else:
|
||||||
|
transceiver.direction = "recvonly"
|
||||||
# logger.debug(
|
# logger.debug(
|
||||||
# f"Transceiver: {transceiver}, Mid: {transceiver.mid}, Direction: {transceiver.direction}"
|
# f"Transceiver: {transceiver}, Mid: {transceiver.mid}, Direction: {transceiver.direction}"
|
||||||
# )
|
# )
|
||||||
@@ -423,6 +430,22 @@ class SmallWebRTCConnection(BaseObject):
|
|||||||
else:
|
else:
|
||||||
logger.warning("Video transceiver not found. Cannot replace video track.")
|
logger.warning("Video transceiver not found. Cannot replace video track.")
|
||||||
|
|
||||||
|
def replace_screen_video_track(self, track):
|
||||||
|
"""Replace the screen video track in the second transceiver.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
track: The new screen video track to use for sending.
|
||||||
|
"""
|
||||||
|
logger.debug(f"Replacing screen video track {track.kind}")
|
||||||
|
# Transceivers always appear in creation-order for both peers
|
||||||
|
# For now we are only considering that we are going to have 02 transceivers,
|
||||||
|
# one for audio and one for video
|
||||||
|
transceivers = self._pc.getTransceivers()
|
||||||
|
if len(transceivers) > 2 and transceivers[2].sender:
|
||||||
|
transceivers[2].sender.replaceTrack(track)
|
||||||
|
else:
|
||||||
|
logger.warning("Screen video transceiver not found. Cannot replace screen video track.")
|
||||||
|
|
||||||
async def disconnect(self):
|
async def disconnect(self):
|
||||||
"""Disconnect from the WebRTC peer connection."""
|
"""Disconnect from the WebRTC peer connection."""
|
||||||
self.send_app_message({"type": SIGNALLING_TYPE, "message": PeerLeftMessage().model_dump()})
|
self.send_app_message({"type": SIGNALLING_TYPE, "message": PeerLeftMessage().model_dump()})
|
||||||
@@ -503,7 +526,7 @@ class SmallWebRTCConnection(BaseObject):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
track = transceivers[AUDIO_TRANSCEIVER_INDEX].receiver.track
|
track = transceivers[AUDIO_TRANSCEIVER_INDEX].receiver.track
|
||||||
audio_track = SmallWebRTCTrack(track, AUDIO_TRANSCEIVER_INDEX) if track else None
|
audio_track = SmallWebRTCTrack(track) if track else None
|
||||||
self._track_map[AUDIO_TRANSCEIVER_INDEX] = audio_track
|
self._track_map[AUDIO_TRANSCEIVER_INDEX] = audio_track
|
||||||
return audio_track
|
return audio_track
|
||||||
|
|
||||||
@@ -525,7 +548,7 @@ class SmallWebRTCConnection(BaseObject):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
track = transceivers[VIDEO_TRANSCEIVER_INDEX].receiver.track
|
track = transceivers[VIDEO_TRANSCEIVER_INDEX].receiver.track
|
||||||
video_track = SmallWebRTCTrack(track, VIDEO_TRANSCEIVER_INDEX) if track else None
|
video_track = SmallWebRTCTrack(track) if track else None
|
||||||
self._track_map[VIDEO_TRANSCEIVER_INDEX] = video_track
|
self._track_map[VIDEO_TRANSCEIVER_INDEX] = video_track
|
||||||
return video_track
|
return video_track
|
||||||
|
|
||||||
@@ -547,7 +570,7 @@ class SmallWebRTCConnection(BaseObject):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
track = transceivers[SCREEN_VIDEO_TRANSCEIVER_INDEX].receiver.track
|
track = transceivers[SCREEN_VIDEO_TRANSCEIVER_INDEX].receiver.track
|
||||||
video_track = SmallWebRTCTrack(track, SCREEN_VIDEO_TRANSCEIVER_INDEX) if track else None
|
video_track = SmallWebRTCTrack(track) if track else None
|
||||||
self._track_map[SCREEN_VIDEO_TRANSCEIVER_INDEX] = video_track
|
self._track_map[SCREEN_VIDEO_TRANSCEIVER_INDEX] = video_track
|
||||||
return video_track
|
return video_track
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user