Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions evi/evi-react-native/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { HumeClient, type Hume } from "hume";
// The provided native module is a good starting place, but you should
// modify it to fit the audio recording needs of your specific app.
import NativeAudio, { AudioEventPayload } from "./modules/audio";
import VoiceIsolationModePrompt from "./VoiceIsolationModePrompt";

// Represents a chat message in the chat display.
interface ChatEntry {
Expand Down Expand Up @@ -55,6 +56,8 @@ const App = () => {
const [isConnected, setIsConnected] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [chatEntries, setChatEntries] = useState<ChatEntry[]>([]);
const [showVoiceIsolationPrompt, setShowVoiceIsolationPrompt] = useState(false);
const [currentMicMode, setCurrentMicMode] = useState("Standard");
const humeRef = useRef<HumeClient | null>(null);
const addChatEntry = (entry: ChatEntry) => {
setChatEntries((prev) => [...prev, entry]);
Expand Down Expand Up @@ -90,6 +93,15 @@ const App = () => {
console.error("Microphone permission denied");
return;
}

// Check microphone mode
const micMode = await NativeAudio.getMicrophoneMode();
setCurrentMicMode(micMode);

// Show prompt if not in Voice Isolation mode
if (micMode !== "Voice Isolation") {
setShowVoiceIsolationPrompt(true);
}
} catch (error) {
console.error("Failed to get permissions:", error);
return;
Expand Down Expand Up @@ -290,6 +302,12 @@ const App = () => {
/>
</View>
</SafeAreaView>

<VoiceIsolationModePrompt
isVisible={showVoiceIsolationPrompt}
currentMode={currentMicMode}
onDismiss={() => setShowVoiceIsolationPrompt(false)}
/>
</View>
);
};
Expand Down
64 changes: 64 additions & 0 deletions evi/evi-react-native/VoiceIsolationModePrompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import {
View,
Text,
Button,
Linking,
Platform,
} from 'react-native';
import Modal from 'react-native-modal';
import NativeAudio from './modules/audio';

interface VoiceIsolationModePromptProps {
isVisible: boolean;
currentMode: string;
onDismiss: () => void;
}

const VoiceIsolationModePrompt: React.FC<VoiceIsolationModePromptProps> = ({
isVisible,
currentMode,
onDismiss,
}) => {
const handleOpenSettings = async () => {
if (Platform.OS === 'ios') {
try {
await NativeAudio.showMicrophoneModes();
} catch (error) {
// Fallback to general settings if the API is not available
Linking.openSettings();
}
} else {
Linking.openSettings();
}
onDismiss();
};

const handleShowMeHow = () => {
const supportUrl = 'https://support.apple.com/guide/iphone/aside/iph45075b5b2/ios';
Linking.openURL(supportUrl);
};

return (
<Modal
isVisible={isVisible}
onBackdropPress={onDismiss}
>
<View style={{ backgroundColor: 'white', padding: 20 }}>
<Text>Enable voice isolation for the best experience</Text>

<Text>
Your device is currently using a {currentMode} microphone mode.
Enabling voice isolation will provide the best audio experience
in a noisy setting.
</Text>

<Button title="Open settings" onPress={handleOpenSettings} />
<Button title="Show me how" onPress={handleShowMeHow} />
<Button title="I'll do this later" onPress={onDismiss} />
</View>
</Modal>
);
};

export default VoiceIsolationModePrompt;
28 changes: 28 additions & 0 deletions evi/evi-react-native/modules/audio/ios/AudioModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,34 @@ public class AudioModule: Module {
AsyncFunction("stopPlayback") {
await _soundPlayer?.clearQueue()
}

AsyncFunction("showMicrophoneModes") {
if #available(iOS 15.0, *) {
AVCaptureDevice.showSystemUserInterface(.microphoneModes)
} else {
throw NSError(
domain: "AudioModule", code: 3,
userInfo: [NSLocalizedDescriptionKey: "Microphone modes are only available on iOS 15+"])
}
}

AsyncFunction("getMicrophoneMode") { () -> String in
if #available(iOS 15.0, *) {
let mode = AVCaptureDevice.preferredMicrophoneMode
switch mode {
case .standard:
return "Standard"
case .voiceIsolation:
return "Voice Isolation"
case .wideSpectrum:
return "Wide Spectrum"
default:
return "Unknown"
}
} else {
return "Standard"
}
}
}

private func getPermissions() async throws -> Bool {
Expand Down
2 changes: 2 additions & 0 deletions evi/evi-react-native/modules/audio/src/AudioModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ declare class AudioModule extends NativeModule<AudioModuleEvents> {
stopPlayback(): Promise<void>;
mute(): Promise<void>;
unmute(): Promise<void>;
showMicrophoneModes(): Promise<void>;
getMicrophoneMode(): Promise<string>;
}

// This call loads the native module object from the JSI.
Expand Down
Loading