-
Notifications
You must be signed in to change notification settings - Fork 4
Develop #199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughAdds Push-to-Talk (PTT) support for AirPods/Bluetooth earbuds via a new Expo config plugin that injects native iOS/Android modules, a MediaButtonService singleton for event handling and LiveKit microphone control, store extensions for PTT settings, and related docs, translations, tests, and minor image-permission error handling improvements. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User (Earbud Button)
participant Native as iOS/Android Native Module
participant Service as MediaButtonService
participant LiveKit as LiveKit Room
participant Store as Bluetooth Audio Store
User->>Native: press / double-press media button
Native->>Service: emit onMediaButtonEvent(type, timestamp, source)
Service->>Service: detect single vs double-tap (timer)
alt PTT enabled & action maps to PTT
Service->>Store: get mediaButtonPTTSettings
Service->>LiveKit: request setMicrophoneEnabled(true/false)
LiveKit-->>Service: confirm microphone state
Service->>Service: play start/stop transmitting sound
Service->>Store: log PTT action
else Non-PTT or logging-only
Service->>Store: log media button event
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (7)
.github/copilot-instructions.md (1)
51-51: Minor grammar improvement: hyphenate "Expo-managed".The static analysis tool flagged this, and using "Expo-managed" is grammatically correct when the compound modifier precedes the noun.
✏️ Suggested fix
-- This is an expo managed project that uses prebuild, do not make native code changes outside of expo prebuild capabilities. +- This is an Expo-managed project that uses prebuild, do not make native code changes outside of Expo prebuild capabilities.plugins/withMediaButtonModule.js (2)
485-491: Redundant branches: identical code in both if/else paths.Both branches of the
if (packagesMatch[1])condition produce the same replacement string. This appears to be copy-paste code where the "already has toMutableList()" case should differ from the "need to add toMutableList()" case, but currently they're identical, making the conditional check pointless.♻️ Suggested fix
Since both branches are identical, simplify to remove the unnecessary conditional:
if (packagesMatch) { - if (packagesMatch[1]) { - // Already has toMutableList() - mainApplication.contents = mainApplication.contents.replace(packagesPattern, `val packages = PackageList(this).packages.toMutableList()\n packages.add(MediaButtonPackage())`); - } else { - // Need to add toMutableList() - mainApplication.contents = mainApplication.contents.replace(packagesPattern, `val packages = PackageList(this).packages.toMutableList()\n packages.add(MediaButtonPackage())`); - } + mainApplication.contents = mainApplication.contents.replace( + packagesPattern, + `val packages = PackageList(this).packages.toMutableList()\n packages.add(MediaButtonPackage())` + ); console.log('[withMediaButtonModule] Registered MediaButtonPackage in MainApplication.kt'); }
299-305: Consider logging the exception during receiver unregistration.While catching the exception during cleanup is appropriate defensive coding, silently swallowing exceptions can make debugging harder. Consider logging at debug/warn level.
✏️ Suggested improvement
mediaButtonReceiver?.let { try { reactApplicationContext.unregisterReceiver(it) } catch (e: Exception) { - // Receiver may not be registered + // Receiver may not be registered - log for debugging + android.util.Log.d("MediaButtonModule", "Receiver unregistration skipped: \${e.message}") } }app.config.ts (1)
210-210: Align plugin filename with lowercase-hyphen conventionConsider renaming the plugin file to
with-media-button-module.jsand updating this reference to match the repo’s naming guideline. As per coding guidelines, ...♻️ Suggested update in this file (requires matching file rename)
- './plugins/withMediaButtonModule.js', + './plugins/with-media-button-module.js',src/services/media-button.service.ts (1)
1-40: Avoid duplicating PTT settings/types across modules
PTTModeandMediaButtonPTTSettingsduplicate definitions already present insrc/stores/app/bluetooth-audio-store.ts. Consider exporting shared types/defaults to prevent drift between modules.src/stores/app/bluetooth-audio-store.ts (2)
7-20: Consolidate PTT types to avoid drift.These definitions duplicate
src/services/media-button.service.tsLines 19-31, and the inline docs already diverge. Consider centralizingPTTModeandMediaButtonPTTSettingsin a shared types module and importing here to keep store/service aligned.
65-72: Avoid sharing a mutable default settings object.
DEFAULT_MEDIA_BUTTON_PTT_SETTINGSis exported and used directly as store state. If any consumer mutates it, the store state will mutate too. Consider cloning when initializing (and/or freezing the constant).♻️ Suggested change (clone on initialization)
- mediaButtonPTTSettings: DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, + mediaButtonPTTSettings: { ...DEFAULT_MEDIA_BUTTON_PTT_SETTINGS },Also applies to: 156-156
| private handleSingleTap(type: MediaButtonEventType): void { | ||
| // Only handle play/pause type events for PTT | ||
| if (!this.settings.usePlayPauseForPTT) { | ||
| logger.debug({ | ||
| message: 'Play/Pause PTT disabled, ignoring', | ||
| context: { type }, | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| if (type === 'playPause' || type === 'play' || type === 'pause' || type === 'togglePlayPause') { | ||
| this.handlePTTAction(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Handle double tap action | ||
| */ | ||
| private handleDoubleTap(): void { | ||
| if (this.settings.doubleTapAction === 'toggle_mute') { | ||
| this.handlePTTAction(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Execute the PTT action (toggle or push-to-talk based on mode) | ||
| */ | ||
| private async handlePTTAction(): Promise<void> { | ||
| const liveKitStore = getLiveKitStore().getState(); | ||
|
|
||
| if (!liveKitStore.currentRoom) { | ||
| logger.debug({ | ||
| message: 'No active LiveKit room, cannot handle PTT action', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const currentMicEnabled = liveKitStore.currentRoom.localParticipant.isMicrophoneEnabled; | ||
| const newMicEnabled = !currentMicEnabled; | ||
|
|
||
| await liveKitStore.currentRoom.localParticipant.setMicrophoneEnabled(newMicEnabled); | ||
|
|
||
| // Create button event for store | ||
| const buttonEvent: AudioButtonEvent = { | ||
| type: 'press', | ||
| button: newMicEnabled ? 'ptt_start' : 'ptt_stop', | ||
| timestamp: Date.now(), | ||
| }; | ||
|
|
||
| useBluetoothAudioStore.getState().addButtonEvent(buttonEvent); | ||
| useBluetoothAudioStore.getState().setLastButtonAction({ | ||
| action: newMicEnabled ? 'unmute' : 'mute', | ||
| timestamp: Date.now(), | ||
| }); | ||
|
|
||
| // Play audio feedback | ||
| if (newMicEnabled) { | ||
| await audioService.playStartTransmittingSound(); | ||
| } else { | ||
| await audioService.playStopTransmittingSound(); | ||
| } | ||
|
|
||
| logger.info({ | ||
| message: 'PTT action executed via media button (AirPods/earbuds)', | ||
| context: { | ||
| micEnabled: newMicEnabled, | ||
| pttMode: this.settings.pttMode, | ||
| }, | ||
| }); | ||
| } catch (error) { | ||
| logger.error({ | ||
| message: 'Failed to execute PTT action via media button', | ||
| context: { error }, | ||
| }); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pttMode is never honored (push-to-talk behaves like toggle)
pttMode is configured but not used—handlePTTAction always toggles mic state. This breaks push-to-talk semantics when users select that mode.
🛠️ Suggested fix: branch by pttMode in single-tap handling
private handleSingleTap(type: MediaButtonEventType): void {
// Only handle play/pause type events for PTT
if (!this.settings.usePlayPauseForPTT) {
logger.debug({
message: 'Play/Pause PTT disabled, ignoring',
context: { type },
});
return;
}
- if (type === 'playPause' || type === 'play' || type === 'pause' || type === 'togglePlayPause') {
- this.handlePTTAction();
- }
+ if (this.settings.pttMode === 'push_to_talk') {
+ if (type === 'play') {
+ void this.enableMicrophone();
+ } else if (type === 'pause') {
+ void this.disableMicrophone();
+ } else if (type === 'playPause' || type === 'togglePlayPause') {
+ void this.handlePTTAction(); // fallback when press/release isn’t available
+ }
+ return;
+ }
+
+ if (type === 'playPause' || type === 'play' || type === 'pause' || type === 'togglePlayPause') {
+ void this.handlePTTAction();
+ }
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/stores/app/bluetooth-audio-store.ts`:
- Around line 4-9: The multi-line import of createDefaultPTTSettings,
DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, MediaButtonPTTSettings, and PTTMode violates
the project's import formatting rule; update the import statement in
bluetooth-audio-store.ts to a formatter-compliant form (collapse into a single
line or run the project's formatter) so the imports for
createDefaultPTTSettings, DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, type
MediaButtonPTTSettings, and type PTTMode are formatted per lint rules.
♻️ Duplicate comments (1)
src/services/media-button.service.ts (1)
260-272:pttModestill not honored (push‑to‑talk behaves like toggle).
Single-tap handling ignorespttModeand always toggles, so press‑and‑hold behavior never happens. This is the same issue previously raised.🛠️ Suggested fix (branch by pttMode)
private handleSingleTap(type: MediaButtonEventType): void { // Only handle play/pause type events for PTT if (!this.settings.usePlayPauseForPTT) { logger.debug({ Same as before, review concisely with a diff or approach. </blockquote></details> </blockquote></details> <details> <summary>🧹 Nitpick comments (1)</summary><blockquote> <details> <summary>plugins/withMediaButtonModule.js (1)</summary><blockquote> `1-4`: **Filename doesn’t match the lowercase-hyphenated convention.** Consider renaming `plugins/withMediaButtonModule.js` → `plugins/with-media-button-module.js` and updating references to align with the project’s naming guidelines. </blockquote></details> </blockquote></details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| import { | ||
| createDefaultPTTSettings, | ||
| DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, | ||
| type MediaButtonPTTSettings, | ||
| type PTTMode, | ||
| } from '@/types/ptt'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lint warning: import formatting.
Static analysis flags the multi-line import here; please run the formatter or collapse the import into a single line per the lint rule.
🧹 Example formatter-compliant import
-import {
- createDefaultPTTSettings,
- DEFAULT_MEDIA_BUTTON_PTT_SETTINGS,
- type MediaButtonPTTSettings,
- type PTTMode,
-} from '@/types/ptt';
+import { createDefaultPTTSettings, DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, type MediaButtonPTTSettings, type PTTMode } from '@/types/ptt';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { | |
| createDefaultPTTSettings, | |
| DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, | |
| type MediaButtonPTTSettings, | |
| type PTTMode, | |
| } from '@/types/ptt'; | |
| import { createDefaultPTTSettings, DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, type MediaButtonPTTSettings, type PTTMode } from '@/types/ptt'; |
🧰 Tools
🪛 GitHub Check: test
[warning] 4-4:
Replace ⏎··createDefaultPTTSettings,⏎··DEFAULT_MEDIA_BUTTON_PTT_SETTINGS,⏎··type·MediaButtonPTTSettings,⏎··type·PTTMode,⏎ with ·createDefaultPTTSettings,·DEFAULT_MEDIA_BUTTON_PTT_SETTINGS,·type·MediaButtonPTTSettings,·type·PTTMode·
🤖 Prompt for AI Agents
In `@src/stores/app/bluetooth-audio-store.ts` around lines 4 - 9, The multi-line
import of createDefaultPTTSettings, DEFAULT_MEDIA_BUTTON_PTT_SETTINGS,
MediaButtonPTTSettings, and PTTMode violates the project's import formatting
rule; update the import statement in bluetooth-audio-store.ts to a
formatter-compliant form (collapse into a single line or run the project's
formatter) so the imports for createDefaultPTTSettings,
DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, type MediaButtonPTTSettings, and type PTTMode
are formatted per lint rules.
|
Approve |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is approved.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.