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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/3687.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added `UserMuteStartedFrame` and `UserMuteStoppedFrame` system frames, and corresponding `user-mute-started` / `user-mute-stopped` RTVI messages, so clients can observe when mute strategies activate or deactivate.
22 changes: 22 additions & 0 deletions src/pipecat/frames/frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,28 @@ class UserStoppedSpeakingFrame(SystemFrame):
emulated: bool = False


@dataclass
class UserMuteStartedFrame(SystemFrame):
"""Frame indicating that the user has been muted.

Emitted when a mute strategy activates, suppressing user frames (audio,
transcription, interruption) from propagating through the pipeline.
"""

pass


@dataclass
class UserMuteStoppedFrame(SystemFrame):
"""Frame indicating that the user has been unmuted.

Emitted when a mute strategy deactivates, allowing user frames to
propagate through the pipeline again.
"""

pass


@dataclass
class UserSpeakingFrame(SystemFrame):
"""Frame indicating the user is speaking.
Expand Down
4 changes: 4 additions & 0 deletions src/pipecat/processors/aggregators/llm_response_universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
TextFrame,
TranscriptionFrame,
UserImageRawFrame,
UserMuteStartedFrame,
UserMuteStoppedFrame,
UserSpeakingFrame,
UserStartedSpeakingFrame,
UserStoppedSpeakingFrame,
Expand Down Expand Up @@ -569,8 +571,10 @@ async def _maybe_mute_frame(self, frame: Frame):
# Emit mute state change events
if self._user_is_muted:
await self._call_event_handler("on_user_mute_started")
await self.broadcast_frame(UserMuteStartedFrame)
else:
await self._call_event_handler("on_user_mute_stopped")
await self.broadcast_frame(UserMuteStoppedFrame)

return should_mute_frame

Expand Down
33 changes: 33 additions & 0 deletions src/pipecat/processors/frameworks/rtvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
TTSStartedFrame,
TTSStoppedFrame,
TTSTextFrame,
UserMuteStartedFrame,
UserMuteStoppedFrame,
UserStartedSpeakingFrame,
UserStoppedSpeakingFrame,
)
Expand Down Expand Up @@ -891,6 +893,20 @@ class RTVIUserStoppedSpeakingMessage(BaseModel):
type: Literal["user-stopped-speaking"] = "user-stopped-speaking"


class RTVIUserMuteStartedMessage(BaseModel):
"""Message indicating user has been muted."""

label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["user-mute-started"] = "user-mute-started"


class RTVIUserMuteStoppedMessage(BaseModel):
"""Message indicating user has been unmuted."""

label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
type: Literal["user-mute-stopped"] = "user-mute-stopped"


class RTVIBotStartedSpeakingMessage(BaseModel):
"""Message indicating bot has started speaking."""

Expand Down Expand Up @@ -1043,6 +1059,7 @@ class RTVIObserverParams:
bot_audio_level_enabled: bool = False
user_llm_enabled: bool = True
user_speaking_enabled: bool = True
user_mute_enabled: bool = True
user_transcription_enabled: bool = True
user_audio_level_enabled: bool = False
metrics_enabled: bool = True
Expand Down Expand Up @@ -1212,6 +1229,11 @@ async def on_push_frame(self, data: FramePushed):
and self._params.user_speaking_enabled
):
await self._handle_interruptions(frame)
elif (
isinstance(frame, (UserMuteStartedFrame, UserMuteStoppedFrame))
and self._params.user_mute_enabled
):
await self._handle_user_mute(frame)
elif (
isinstance(frame, (BotStartedSpeakingFrame, BotStoppedSpeakingFrame))
and self._params.bot_speaking_enabled
Expand Down Expand Up @@ -1343,6 +1365,17 @@ async def _handle_interruptions(self, frame: Frame):
if message:
await self.send_rtvi_message(message)

async def _handle_user_mute(self, frame: Frame):
"""Handle user mute/unmute frames."""
message = None
if isinstance(frame, UserMuteStartedFrame):
message = RTVIUserMuteStartedMessage()
elif isinstance(frame, UserMuteStoppedFrame):
message = RTVIUserMuteStoppedMessage()

if message:
await self.send_rtvi_message(message)

async def _handle_bot_speaking(self, frame: Frame):
"""Handle bot speaking event frames."""
message = None
Expand Down
Loading