Skip to content

Commit 3ff9b7b

Browse files
authored
Merge pull request #3687 from pipecat-ai/mb/rtvi-mute-events
Emit RTVI events for user mute/unmute
2 parents 91c8122 + 00ec6c7 commit 3ff9b7b

4 files changed

Lines changed: 60 additions & 0 deletions

File tree

changelog/3687.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

src/pipecat/frames/frames.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,28 @@ class UserStoppedSpeakingFrame(SystemFrame):
12091209
emulated: bool = False
12101210

12111211

1212+
@dataclass
1213+
class UserMuteStartedFrame(SystemFrame):
1214+
"""Frame indicating that the user has been muted.
1215+
1216+
Emitted when a mute strategy activates, suppressing user frames (audio,
1217+
transcription, interruption) from propagating through the pipeline.
1218+
"""
1219+
1220+
pass
1221+
1222+
1223+
@dataclass
1224+
class UserMuteStoppedFrame(SystemFrame):
1225+
"""Frame indicating that the user has been unmuted.
1226+
1227+
Emitted when a mute strategy deactivates, allowing user frames to
1228+
propagate through the pipeline again.
1229+
"""
1230+
1231+
pass
1232+
1233+
12121234
@dataclass
12131235
class UserSpeakingFrame(SystemFrame):
12141236
"""Frame indicating the user is speaking.

src/pipecat/processors/aggregators/llm_response_universal.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
TextFrame,
5454
TranscriptionFrame,
5555
UserImageRawFrame,
56+
UserMuteStartedFrame,
57+
UserMuteStoppedFrame,
5658
UserSpeakingFrame,
5759
UserStartedSpeakingFrame,
5860
UserStoppedSpeakingFrame,
@@ -569,8 +571,10 @@ async def _maybe_mute_frame(self, frame: Frame):
569571
# Emit mute state change events
570572
if self._user_is_muted:
571573
await self._call_event_handler("on_user_mute_started")
574+
await self.broadcast_frame(UserMuteStartedFrame)
572575
else:
573576
await self._call_event_handler("on_user_mute_stopped")
577+
await self.broadcast_frame(UserMuteStoppedFrame)
574578

575579
return should_mute_frame
576580

src/pipecat/processors/frameworks/rtvi.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
TTSStartedFrame,
6868
TTSStoppedFrame,
6969
TTSTextFrame,
70+
UserMuteStartedFrame,
71+
UserMuteStoppedFrame,
7072
UserStartedSpeakingFrame,
7173
UserStoppedSpeakingFrame,
7274
)
@@ -891,6 +893,20 @@ class RTVIUserStoppedSpeakingMessage(BaseModel):
891893
type: Literal["user-stopped-speaking"] = "user-stopped-speaking"
892894

893895

896+
class RTVIUserMuteStartedMessage(BaseModel):
897+
"""Message indicating user has been muted."""
898+
899+
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
900+
type: Literal["user-mute-started"] = "user-mute-started"
901+
902+
903+
class RTVIUserMuteStoppedMessage(BaseModel):
904+
"""Message indicating user has been unmuted."""
905+
906+
label: RTVIMessageLiteral = RTVI_MESSAGE_LABEL
907+
type: Literal["user-mute-stopped"] = "user-mute-stopped"
908+
909+
894910
class RTVIBotStartedSpeakingMessage(BaseModel):
895911
"""Message indicating bot has started speaking."""
896912

@@ -1043,6 +1059,7 @@ class RTVIObserverParams:
10431059
bot_audio_level_enabled: bool = False
10441060
user_llm_enabled: bool = True
10451061
user_speaking_enabled: bool = True
1062+
user_mute_enabled: bool = True
10461063
user_transcription_enabled: bool = True
10471064
user_audio_level_enabled: bool = False
10481065
metrics_enabled: bool = True
@@ -1218,6 +1235,11 @@ async def on_push_frame(self, data: FramePushed):
12181235
and self._params.user_speaking_enabled
12191236
):
12201237
await self._handle_interruptions(frame)
1238+
elif (
1239+
isinstance(frame, (UserMuteStartedFrame, UserMuteStoppedFrame))
1240+
and self._params.user_mute_enabled
1241+
):
1242+
await self._handle_user_mute(frame)
12211243
elif (
12221244
isinstance(frame, (BotStartedSpeakingFrame, BotStoppedSpeakingFrame))
12231245
and self._params.bot_speaking_enabled
@@ -1349,6 +1371,17 @@ async def _handle_interruptions(self, frame: Frame):
13491371
if message:
13501372
await self.send_rtvi_message(message)
13511373

1374+
async def _handle_user_mute(self, frame: Frame):
1375+
"""Handle user mute/unmute frames."""
1376+
message = None
1377+
if isinstance(frame, UserMuteStartedFrame):
1378+
message = RTVIUserMuteStartedMessage()
1379+
elif isinstance(frame, UserMuteStoppedFrame):
1380+
message = RTVIUserMuteStoppedMessage()
1381+
1382+
if message:
1383+
await self.send_rtvi_message(message)
1384+
13521385
async def _handle_bot_speaking(self, frame: Frame):
13531386
"""Handle bot speaking event frames."""
13541387
message = None

0 commit comments

Comments
 (0)