From 9ea6a5e3a617705da41b66b70333679e04387292 Mon Sep 17 00:00:00 2001
From: grabowski
Date: Fri, 24 Oct 2025 15:45:30 +0700
Subject: [PATCH] Fix extra button sound playback and add status indicator
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
**Button Sound Playback Fix:**
- Use separate PyAudio instance for button sound during recording
- Allows simultaneous audio input (recording) and output (button sound)
- Button sound plays through speaker/handset for guest to hear
- Button sound is NOT captured in the recording file
- Only works during recording phase (not during greeting)
**Button Status Indicator:**
- Added button status display in Phone Status card
- Shows "Button ready" or "Button sound playing..."
- Yellow highlight when button sound is playing
- Status updates dynamically via refresh
- Only visible when extra button is enabled
**Technical Details:**
- Separate audio_playback instance prevents stream conflicts
- Recording uses input stream, button uses output stream
- Both operate on same HiFiBerry device simultaneously
- Status API now includes extra_button_playing flag
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
rotary_phone_web.py | 90 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 84 insertions(+), 6 deletions(-)
diff --git a/rotary_phone_web.py b/rotary_phone_web.py
index 7a5f301..1eb4377 100644
--- a/rotary_phone_web.py
+++ b/rotary_phone_web.py
@@ -315,7 +315,8 @@ class RotaryPhone:
return {
"status": self.phone_status,
"recording": self.recording,
- "current_recording": self.current_recording
+ "current_recording": self.current_recording,
+ "extra_button_playing": self.extra_button_playing if EXTRA_BUTTON_ENABLED else False
}
def play_extra_button_sound(self):
@@ -335,9 +336,49 @@ class RotaryPhone:
print(f"\n=== Extra button pressed ===")
self.extra_button_playing = True
- # Play without checking hook status - play entire sound
- self.play_sound_file(button_sound, check_hook_status=False)
- self.extra_button_playing = False
+
+ try:
+ # Use a separate PyAudio instance for playback during recording
+ # This allows simultaneous input (recording) and output (button sound)
+ audio_playback = pyaudio.PyAudio()
+
+ wf = wave.open(button_sound, 'rb')
+
+ stream = audio_playback.open(
+ format=audio_playback.get_format_from_width(wf.getsampwidth()),
+ channels=wf.getnchannels(),
+ rate=wf.getframerate(),
+ output=True,
+ output_device_index=AUDIO_DEVICE_INDEX,
+ frames_per_buffer=CHUNK
+ )
+
+ # Get volume multiplier
+ volume = self.get_volume() / 100.0
+
+ # Play the sound
+ data = wf.readframes(CHUNK)
+ while data:
+ # Apply volume
+ if volume < 1.0:
+ audio_data = np.frombuffer(data, dtype=np.int16)
+ audio_data = (audio_data * volume).astype(np.int16)
+ data = audio_data.tobytes()
+
+ stream.write(data)
+ data = wf.readframes(CHUNK)
+
+ stream.stop_stream()
+ stream.close()
+ wf.close()
+ audio_playback.terminate()
+
+ print("Extra button sound playback finished")
+
+ except Exception as e:
+ print(f"Error playing button sound: {e}")
+ finally:
+ self.extra_button_playing = False
def phone_loop(self):
"""Main phone handling loop"""
@@ -1090,6 +1131,17 @@ if __name__ == "__main__":
Recording to: {{ status.current_recording.split('/')[-1] }}
{% endif %}
+ {% if extra_button_enabled %}
+
+
+ {% if status.extra_button_playing %}
+ 🔘 Button sound playing...
+ {% else %}
+ 🔘 Button ready
+ {% endif %}
+
+
+ {% endif %}
@@ -1532,8 +1584,34 @@ if __name__ == "__main__":
fetch('/api/status')
.then(response => response.json())
.then(data => {
- location.reload();
- });
+ // Update status text
+ const statusText = document.getElementById('status-text');
+ const statusDot = document.getElementById('status-dot');
+
+ if (data.recording) {
+ statusText.textContent = '🔴 Recording in progress...';
+ statusDot.className = 'status-dot recording';
+ } else if (data.status === 'off_hook') {
+ statusText.textContent = '📞 Handset off hook';
+ statusDot.className = 'status-dot off-hook';
+ } else {
+ statusText.textContent = '✅ Ready (handset on hook)';
+ statusDot.className = 'status-dot on-hook';
+ }
+
+ // Update extra button status
+ const buttonStatus = document.getElementById('button-status');
+ if (buttonStatus) {
+ if (data.extra_button_playing) {
+ buttonStatus.style.background = '#fef3c7';
+ buttonStatus.querySelector('span').textContent = '🔘 Button sound playing...';
+ } else {
+ buttonStatus.style.background = '#f3f4f6';
+ buttonStatus.querySelector('span').textContent = '🔘 Button ready';
+ }
+ }
+ })
+ .catch(error => console.error('Error refreshing status:', error));
}
function showAlert(message, type) {