https://github.com/devmaan707/omi_sdk
A comprehensive Flutter SDK for connecting to and streaming audio from Omi devices, enabling real-time speech transcription and AI interactions.
https://github.com/devmaan707/omi_sdk
hardware omi sdk
Last synced: 4 months ago
JSON representation
A comprehensive Flutter SDK for connecting to and streaming audio from Omi devices, enabling real-time speech transcription and AI interactions.
- Host: GitHub
- URL: https://github.com/devmaan707/omi_sdk
- Owner: DevMaan707
- License: other
- Created: 2025-06-23T07:52:44.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2025-06-25T17:06:43.000Z (4 months ago)
- Last Synced: 2025-06-25T18:21:49.516Z (4 months ago)
- Topics: hardware, omi, sdk
- Language: Dart
- Homepage:
- Size: 124 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Omi SDK for Flutter
A comprehensive Flutter SDK for connecting to and streaming audio from Omi devices, with built-in support for real-time transcription, audio recording, and WebSocket streaming.
## Features
- 🎧 **Audio Streaming**: Real-time audio streaming from Omi devices
- 🎙️ **Recording**: Record and save audio sessions to local storage
- 📝 **Transcription**: Real-time speech-to-text via WebSocket (Deepgram compatible)
- 🔊 **Playback**: Play recorded audio files
- 📱 **Device Management**: Scan, connect, and manage Omi devices via Bluetooth
- 🔋 **Battery Monitoring**: Get battery levels from connected devices
- 🎵 **Multiple Codecs**: Support for PCM8, PCM16, Opus, and OpusFS320
- 🔄 **Auto-Reconnection**: Automatic reconnection for both Bluetooth and WebSocket
- 📊 **Audio Processing**: Real-time audio processing and format conversion## Installation
Add this to your package's `pubspec.yaml` file:
```yaml
dependencies:
omi_sdk: ^1.0.0
```### Platform-specific Setup
#### Android
Add the following permissions to your `android/app/src/main/AndroidManifest.xml`:```xml
```
#### iOS
Add the following to your `ios/Runner/Info.plist`:```xml
NSBluetoothAlwaysUsageDescription
This app uses Bluetooth to connect to Omi devices
NSMicrophoneUsageDescription
This app needs microphone access to process audio
```## Quick Start
### 1. Initialize the SDK
```dart
import 'package:omi_sdk/omi_sdk.dart';void main() async {
WidgetsFlutterBinding.ensureInitialized();// Initialize the SDK with your configuration
final sdk = await OmiSDK.initialize(OmiConfig(
apiKey: 'your-deepgram-api-key', // Optional: For transcription
apiBaseUrl: 'wss://api.deepgram.com', // Optional: Custom WebSocket URL
connectionTimeout: Duration(seconds: 15),
autoReconnect: true,
maxReconnectAttempts: 3,
));runApp(MyApp());
}
```### 2. Scan for Devices
```dart
// Start scanning for Omi devices
await OmiSDK.instance.device.startScan();// Listen for discovered devices
OmiSDK.instance.device.devicesStream.listen((devices) {
print('Found ${devices.length} devices');
for (final device in devices) {
print('Device: ${device.name} (${device.id})');
}
});// Stop scanning after 10 seconds
await Future.delayed(Duration(seconds: 10));
await OmiSDK.instance.device.stopScan();
```### 3. Connect to a Device
```dart
// Connect to a specific device
final deviceId = 'your-device-id';
await OmiSDK.instance.device.connectToDevice(deviceId);// Listen for connection state changes
OmiSDK.instance.device.connectionStateStream.listen((state) {
switch (state) {
case DeviceConnectionState.connected:
print('Device connected!');
break;
case DeviceConnectionState.disconnected:
print('Device disconnected');
break;
case DeviceConnectionState.connecting:
print('Connecting...');
break;
case DeviceConnectionState.error:
print('Connection error');
break;
}
});
```## Usage Examples
### Audio Recording
```dart
class AudioRecordingExample extends StatefulWidget {
@override
_AudioRecordingExampleState createState() => _AudioRecordingExampleState();
}class _AudioRecordingExampleState extends State {
bool _isRecording = false;
RecordingSession? _currentSession;@override
Widget build(BuildContext context) {
return Column(
children: [
// Recording controls
ElevatedButton(
onPressed: _isRecording ? _stopRecording : _startRecording,
child: Text(_isRecording ? 'Stop Recording' : 'Start Recording'),
),// Recording duration display
StreamBuilder(
stream: OmiSDK.instance.recording.recordingDurationStream,
builder: (context, snapshot) {
final duration = snapshot.data ?? Duration.zero;
return Text('Duration: ${_formatDuration(duration)}');
},
),// Recording state display
StreamBuilder(
stream: OmiSDK.instance.recording.stateStream,
builder: (context, snapshot) {
final state = snapshot.data ?? RecordingState.idle;
return Text('State: ${state.toString()}');
},
),
],
);
}Future _startRecording() async {
try {
final filePath = await OmiSDK.instance.startRecording(
customFileName: 'my_recording_${DateTime.now().millisecondsSinceEpoch}.wav',
);
print('Recording started: $filePath');
setState(() => _isRecording = true);
} catch (e) {
print('Failed to start recording: $e');
}
}Future _stopRecording() async {
try {
final session = await OmiSDK.instance.stopRecording();
if (session != null) {
print('Recording saved: ${session.filePath}');
setState(() {
_isRecording = false;
_currentSession = session;
});
}
} catch (e) {
print('Failed to stop recording: $e');
}
}String _formatDuration(Duration duration) {
final minutes = duration.inMinutes;
final seconds = duration.inSeconds % 60;
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
}
```### Real-time Transcription
```dart
class TranscriptionExample extends StatefulWidget {
@override
_TranscriptionExampleState createState() => _TranscriptionExampleState();
}class _TranscriptionExampleState extends State {
String _currentTranscript = '';
bool _isStreaming = false;@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: _isStreaming ? _stopTranscription : _startTranscription,
child: Text(_isStreaming ? 'Stop Transcription' : 'Start Transcription'),
),// WebSocket state
StreamBuilder(
stream: OmiSDK.instance.websocket.stateStream,
builder: (context, snapshot) {
final state = snapshot.data ?? WebSocketState.disconnected;
return Text('WebSocket: ${state.toString()}');
},
),// Transcription results
StreamBuilder>(
stream: OmiSDK.instance.websocket.messageStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final message = snapshot.data!;
if (message['type'] == 'Results') {
_updateTranscript(message);
}
}
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: Text(
_currentTranscript.isEmpty
? 'Transcription will appear here...'
: _currentTranscript,
style: TextStyle(fontSize: 16),
),
);
},
),
],
);
}Future _startTranscription() async {
try {
await OmiSDK.instance.startTranscriptionStreaming(
language: 'en-US',
userId: 'user123',
includeSpeechProfile: true,
);
setState(() => _isStreaming = true);
} catch (e) {
print('Failed to start transcription: $e');
}
}Future _stopTranscription() async {
try {
await OmiSDK.instance.stopAudioStreaming();
setState(() => _isStreaming = false);
} catch (e) {
print('Failed to stop transcription: $e');
}
}void _updateTranscript(Map message) {
// Extract transcript from Deepgram response
String? transcript;
final isFinal = message['is_final'] as bool? ?? false;if (message['channel'] != null) {
final channel = message['channel'] as Map?;
if (channel?['alternatives'] is List) {
final alternatives = channel!['alternatives'] as List;
if (alternatives.isNotEmpty && alternatives[0] is Map) {
final firstAlt = alternatives[0] as Map;
transcript = firstAlt['transcript'] as String?;
}
}
}if (transcript != null && transcript.isNotEmpty && isFinal) {
setState(() {
_currentTranscript += ' $transcript';
});
}
}
}
```### Audio Playback
```dart
class PlaybackExample extends StatefulWidget {
@override
_PlaybackExampleState createState() => _PlaybackExampleState();
}class _PlaybackExampleState extends State {
List _recordings = [];@override
void initState() {
super.initState();
_loadRecordings();
}Future _loadRecordings() async {
final recordings = await OmiSDK.instance.getRecordings();
setState(() => _recordings = recordings);
}@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _recordings.length,
itemBuilder: (context, index) {
final recording = _recordings[index];
return ListTile(
title: Text('Recording ${recording.id}'),
subtitle: Text('Duration: ${_formatDuration(recording.duration)}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () => _playRecording(recording.filePath),
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () => _deleteRecording(recording),
),
],
),
);
},
);
}Future _playRecording(String filePath) async {
try {
await OmiSDK.instance.playRecording(filePath);
} catch (e) {
print('Failed to play recording: $e');
}
}Future _deleteRecording(RecordingSession recording) async {
try {
await OmiSDK.instance.deleteRecording(recording.filePath);
await _loadRecordings(); // Refresh list
} catch (e) {
print('Failed to delete recording: $e');
}
}String _formatDuration(Duration duration) {
final minutes = duration.inMinutes;
final seconds = duration.inSeconds % 60;
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
}
```### Dual Streaming (Audio + Transcription)
```dart
// Stream both audio data and transcription simultaneously
await OmiSDK.instance.startDualStreaming(
language: 'en-US',
userId: 'user123',
);// Listen to audio data
OmiSDK.instance.audio.audioDataStream.listen((audioData) {
// Process raw audio data
print('Received ${audioData.length} bytes of audio');
});// Listen to transcription
OmiSDK.instance.websocket.messageStream.listen((message) {
if (message['type'] == 'Results') {
// Handle transcription results
print('Transcription: ${message}');
}
});
```## Advanced Features
### Custom WebSocket Parameters
```dart
await OmiSDK.instance.startTranscriptionStreaming(
websocketUrl: 'wss://your-custom-server.com/v1/listen',
apiKey: 'your-api-key',
language: 'en-US',
customHeaders: {
'X-Custom-Header': 'value',
},
customParams: {
'model': 'nova-2',
'smart_format': 'true',
'diarize': 'true',
},
);
```### Audio Codec Detection
```dart
// The SDK automatically detects the audio codec from the device
final codec = await OmiSDK.instance.device.getAudioCodec();
print('Device codec: $codec');
print('Sample rate: ${codec.sampleRate} Hz');
```### Battery Monitoring
```dart
// Get current battery level
final batteryLevel = await OmiSDK.instance.device.getBatteryLevel();
print('Battery: $batteryLevel%');
```### Debug Logging
```dart
// Get current audio debug log path
final logPath = OmiSDK.instance.getCurrentAudioLogPath();
print('Audio debug log: $logPath');// Get all log files
final allLogs = OmiSDK.instance.getAllAudioLogFiles();
for (final log in allLogs) {
print('Log file: $log');
}
```## Supported Audio Codecs
- **PCM 8-bit**: 8kHz sample rate, 1 byte per sample
- **PCM 16-bit**: 16kHz sample rate, 2 bytes per sample
- **Opus**: 16kHz sample rate, compressed audio
- **Opus FS320**: 16kHz sample rate, compressed audio with 320ms frame sizeThe SDK automatically detects the codec from your Omi device and handles the appropriate decoding.
## Error Handling
```dart
try {
await OmiSDK.instance.device.connectToDevice(deviceId);
} catch (e) {
if (e.toString().contains('Device not found')) {
// Handle device not found
} else if (e.toString().contains('Connection timeout')) {
// Handle connection timeout
} else {
// Handle other errors
}
}
```## Best Practices
1. **Always initialize the SDK** before using any features
2. **Handle connection states** properly in your UI
3. **Dispose resources** when done:
```dart
@override
void dispose() {
OmiSDK.instance.dispose();
super.dispose();
}
```
4. **Check connection status** before starting audio operations
5. **Use StreamBuilder** widgets for real-time updates
6. **Handle permissions** gracefully on both Android and iOS## Troubleshooting
### Common Issues
1. **Bluetooth Permission Denied**
- Ensure all required permissions are added to your manifest
- Request permissions at runtime on Android 6+2. **WebSocket Connection Failed**
- Check your API key and endpoint URL
- Verify network connectivity
- Check firewall settings3. **Audio Codec Issues**
- The SDK automatically handles codec detection
- Check debug logs for codec information4. **No Devices Found**
- Ensure Bluetooth is enabled
- Check if the device is in pairing mode
- Verify the device supports Omi service UUIDs### Debug Information
Enable debug logging to get detailed information:
```dart
// Audio debug logs are automatically created
final logPath = OmiSDK.instance.getCurrentAudioLogPath();
// Check the log file for detailed audio processing information
```## API Reference
### OmiSDK Core
- `initialize(OmiConfig)` - Initialize the SDK
- `startRecording()` - Start audio recording
- `stopRecording()` - Stop recording and get session
- `startTranscriptionStreaming()` - Start real-time transcription
- `startDualStreaming()` - Start audio + transcription
- `dispose()` - Clean up resources### Device Manager
- `startScan()` - Scan for devices
- `connectToDevice(String)` - Connect to device
- `getAudioCodec()` - Get device audio codec
- `getBatteryLevel()` - Get battery level### Audio Manager
- `audioDataStream` - Raw audio data stream
- `processedAudioStream` - Processed audio stream
- `isStreaming` - Current streaming state### WebSocket Manager
- `connect()` - Connect to WebSocket
- `messageStream` - Incoming message stream
- `sendAudio()` - Send audio data### Recording Manager
- `startRecording()` - Start recording session
- `pauseRecording()` - Pause current recording
- `getRecordings()` - Get all recordings
- `playRecording()` - Play audio file## Attribution
This SDK is based on [OMI by BasedHardware](https://github.com/BasedHardware/omi),
originally licensed under the MIT License.
All original work belongs to its respective authors.Special thanks to the [BasedHardware team](https://github.com/BasedHardware/omi) for creating the Omi platform and making it open source.
## License
MIT License - see LICENSE file for details.
## Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
## Support
For issues and questions:
- Check the troubleshooting section above
- Review debug logs for detailed error information
- Open an issue on our GitHub repository