|
| 1 | +# Headset Button PTT (Push-to-Talk) Implementation |
| 2 | + |
| 3 | +This document describes the implementation of AirPods/Bluetooth earbuds button support for Push-to-Talk (PTT) functionality with LiveKit. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +AirPods and standard Bluetooth earbuds use AVRCP (Audio/Video Remote Control Profile) to communicate media button events. Unlike specialized PTT devices (Aina, B01 Inrico, HYS) that use BLE characteristics, standard Bluetooth audio devices send button events through the system's audio session. |
| 8 | + |
| 9 | +This implementation adds support for using the play/pause button on AirPods and other Bluetooth earbuds to mute/unmute the microphone during a LiveKit voice call. |
| 10 | + |
| 11 | +## Files Modified/Created |
| 12 | + |
| 13 | +### New Files |
| 14 | + |
| 15 | +1. **`src/services/headset-button.service.ts`** |
| 16 | + - Core service that handles media button events from AirPods and Bluetooth earbuds |
| 17 | + - Listens for `DeviceEventEmitter` events: `HeadsetButtonEvent`, `AudioRouteChange`, `RemoteControlEvent`, `MediaButtonEvent` |
| 18 | + - Provides methods for toggling microphone, starting/stopping monitoring |
| 19 | + - Supports configurable PTT modes: toggle, push-to-talk, disabled |
| 20 | + |
| 21 | +2. **`src/lib/hooks/use-headset-button-ptt.ts`** |
| 22 | + - Custom React hook for easy integration with components |
| 23 | + - Auto-starts/stops monitoring based on LiveKit connection status |
| 24 | + - Provides `toggleMicrophone`, `startMonitoring`, `stopMonitoring` functions |
| 25 | + |
| 26 | +3. **`src/services/__tests__/headset-button.service.test.ts`** |
| 27 | + - Comprehensive unit tests for the headset button service |
| 28 | + |
| 29 | +### Modified Files |
| 30 | + |
| 31 | +1. **`src/stores/app/bluetooth-audio-store.ts`** |
| 32 | + - Added `PttMode` type and `HeadsetButtonConfig` interface |
| 33 | + - Added `isHeadsetButtonMonitoring` state |
| 34 | + - Added `headsetButtonConfig` state |
| 35 | + - Added `setIsHeadsetButtonMonitoring` and `setHeadsetButtonConfig` actions |
| 36 | + |
| 37 | +2. **`src/stores/app/livekit-store.ts`** |
| 38 | + - Added import for `headsetButtonService` |
| 39 | + - Added `toggleMicrophone` and `setMicrophoneEnabled` actions |
| 40 | + - Added `startHeadsetButtonMonitoring` and `stopHeadsetButtonMonitoring` actions |
| 41 | + - Modified `connectToRoom` to auto-start headset button monitoring |
| 42 | + - Modified `disconnectFromRoom` to auto-stop headset button monitoring |
| 43 | + |
| 44 | +3. **`src/translations/en.json`** |
| 45 | + - Added translations for headset button PTT feature |
| 46 | + |
| 47 | +4. **`src/translations/es.json`** |
| 48 | + - Added Spanish translations for headset button PTT feature |
| 49 | + |
| 50 | +5. **`src/translations/ar.json`** |
| 51 | + - Added Arabic translations for headset button PTT feature |
| 52 | + |
| 53 | +## Usage |
| 54 | + |
| 55 | +### Basic Usage with the Hook |
| 56 | + |
| 57 | +```typescript |
| 58 | +import { useHeadsetButtonPTT } from '@/lib/hooks/use-headset-button-ptt'; |
| 59 | + |
| 60 | +function MyVoiceComponent() { |
| 61 | + const { |
| 62 | + isMonitoring, |
| 63 | + isConnected, |
| 64 | + isMuted, |
| 65 | + toggleMicrophone, |
| 66 | + startMonitoring, |
| 67 | + stopMonitoring, |
| 68 | + } = useHeadsetButtonPTT(); |
| 69 | + |
| 70 | + return ( |
| 71 | + <View> |
| 72 | + <Text>Monitoring: {isMonitoring ? 'Active' : 'Inactive'}</Text> |
| 73 | + <Text>Muted: {isMuted ? 'Yes' : 'No'}</Text> |
| 74 | + <Button onPress={toggleMicrophone} title="Toggle Mute" /> |
| 75 | + </View> |
| 76 | + ); |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +### Configuration Options |
| 81 | + |
| 82 | +```typescript |
| 83 | +const { updateConfig } = useHeadsetButtonPTT({ |
| 84 | + autoStartOnConnect: true, // Auto-start when LiveKit connects |
| 85 | + autoStopOnDisconnect: true, // Auto-stop when LiveKit disconnects |
| 86 | + pttMode: 'toggle', // 'toggle', 'push_to_talk', or 'disabled' |
| 87 | + soundFeedback: true, // Play sounds when muting/unmuting |
| 88 | +}); |
| 89 | + |
| 90 | +// Update configuration at runtime |
| 91 | +updateConfig({ |
| 92 | + playPauseAction: 'toggle_mute', // What to do on play/pause press |
| 93 | + doubleClickAction: 'none', // What to do on double click |
| 94 | + longPressAction: 'none', // What to do on long press |
| 95 | +}); |
| 96 | +``` |
| 97 | + |
| 98 | +### Using the Service Directly |
| 99 | + |
| 100 | +```typescript |
| 101 | +import { headsetButtonService } from '@/services/headset-button.service'; |
| 102 | + |
| 103 | +// Initialize the service |
| 104 | +await headsetButtonService.initialize(); |
| 105 | + |
| 106 | +// Start monitoring |
| 107 | +headsetButtonService.startMonitoring(); |
| 108 | + |
| 109 | +// Toggle microphone |
| 110 | +await headsetButtonService.toggleMicrophone(); |
| 111 | + |
| 112 | +// Enable microphone |
| 113 | +await headsetButtonService.enableMicrophone(); |
| 114 | + |
| 115 | +// Disable microphone |
| 116 | +await headsetButtonService.disableMicrophone(); |
| 117 | + |
| 118 | +// Stop monitoring |
| 119 | +headsetButtonService.stopMonitoring(); |
| 120 | +``` |
| 121 | + |
| 122 | +## How It Works |
| 123 | + |
| 124 | +1. When a user connects to a LiveKit room, the headset button monitoring is automatically started. |
| 125 | + |
| 126 | +2. The service listens for media button events from the system: |
| 127 | + - On iOS: Remote control events via `AVAudioSession` |
| 128 | + - On Android: Media button events via `AudioManager` |
| 129 | + |
| 130 | +3. When the play/pause button is pressed on AirPods or Bluetooth earbuds: |
| 131 | + - Single tap: Toggle microphone mute state |
| 132 | + - Double tap: Configurable action (default: none) |
| 133 | + - Long press: Configurable action (default: none) |
| 134 | + |
| 135 | +4. Sound feedback is played when muting/unmuting (configurable). |
| 136 | + |
| 137 | +5. When the user disconnects from the LiveKit room, monitoring is automatically stopped. |
| 138 | + |
| 139 | +## Supported Button Types |
| 140 | + |
| 141 | +| Button Type | Description | |
| 142 | +|------------|-------------| |
| 143 | +| `play_pause` | Play/Pause media button (main action button) | |
| 144 | +| `hook` | Headset hook button (answer/end call) | |
| 145 | +| `next` | Skip to next track | |
| 146 | +| `previous` | Skip to previous track | |
| 147 | +| `stop` | Stop media playback | |
| 148 | + |
| 149 | +## PTT Modes |
| 150 | + |
| 151 | +| Mode | Description | |
| 152 | +|------|-------------| |
| 153 | +| `toggle` | Tap to toggle between muted and unmuted | |
| 154 | +| `push_to_talk` | Hold to talk, release to mute (not yet implemented) | |
| 155 | +| `disabled` | Headset buttons don't affect microphone | |
| 156 | + |
| 157 | +## Native Module Requirements |
| 158 | + |
| 159 | +For full functionality, native modules need to be implemented to: |
| 160 | + |
| 161 | +1. **iOS**: Register for remote control events via `MPRemoteCommandCenter` |
| 162 | +2. **Android**: Register a `MediaSession` and handle `MediaButtonReceiver` |
| 163 | + |
| 164 | +The service is designed to receive events from these native modules via `DeviceEventEmitter`. The expected event format: |
| 165 | + |
| 166 | +```typescript |
| 167 | +// HeadsetButtonEvent |
| 168 | +{ |
| 169 | + type: 'play_pause' | 'hook' | 'next' | 'previous' | 'stop', |
| 170 | + source: 'airpods' | 'bluetooth_headset' | 'wired_headset', |
| 171 | + deviceName: string |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +## Testing |
| 176 | + |
| 177 | +Run the tests with: |
| 178 | + |
| 179 | +```bash |
| 180 | +yarn test -- --testPathPattern="headset-button" --no-coverage |
| 181 | +``` |
| 182 | + |
| 183 | +## Future Enhancements |
| 184 | + |
| 185 | +1. Implement native modules for iOS and Android to capture media button events |
| 186 | +2. Add push-to-talk (hold to talk) mode |
| 187 | +3. Add support for volume button controls |
| 188 | +4. Add support for custom button mappings |
| 189 | +5. Integrate with iOS CallKit for better system integration |
0 commit comments