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) {