flutter_realtime_voice_ai 0.1.1 copy "flutter_realtime_voice_ai: ^0.1.1" to clipboard
flutter_realtime_voice_ai: ^0.1.1 copied to clipboard

A Flutter package for streaming voice recording, and audio playback with focus on real-time voice interactions.

example/lib/main.dart

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_realtime_voice_ai/flutter_realtime_voice_ai.dart';

void main() {
  runApp(const VoiceAIExampleApp());
}

class VoiceAIExampleApp extends StatelessWidget {
  const VoiceAIExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Realtime Voice AI Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const LocalVoicePage(),
    );
  }
}

class LocalVoicePage extends StatefulWidget {
  const LocalVoicePage({super.key});

  @override
  State<LocalVoicePage> createState() => _LocalVoicePageState();
}

class _LocalVoicePageState extends State<LocalVoicePage> {
  late VoiceRecorderService _recorderService;
  late VoicePlayerService _playerService;

  // State variables
  VoiceRecorderState _recorderState = VoiceRecorderState.idle;
  VoicePlayerState _playerState = VoicePlayerState.idle;

  bool _isInitialized = false;
  String _statusMessage = 'Not initialized';

  // Recorded audio data
  final List<Uint8List> _recordedAudioChunks = [];
  bool _isRecording = false;

  @override
  void initState() {
    super.initState();
    _initializeServices();
  }

  Future<void> _initializeServices() async {
    try {
      // Request microphone permission
      final micStatus = await Permission.microphone.request();
      if (!micStatus.isGranted) {
        setState(() {
          _statusMessage = 'Microphone permission denied';
        });
        return;
      }

      // Initialize services
      _recorderService = VoiceRecorderService();
      _playerService = VoicePlayerService();

      // Setup state listeners
      _setupStateListeners();

      setState(() {
        _isInitialized = true;
        _statusMessage = 'Ready to record';
      });
    } catch (e) {
      setState(() {
        _statusMessage = 'Initialization failed: $e';
      });
    }
  }

  void _setupStateListeners() {
    _recorderService.stateStream.listen((state) {
      setState(() {
        _recorderState = state;
        _updateStatusMessage();
      });
    });

    _playerService.stateStream.listen((state) {
      setState(() {
        _playerState = state;
        _updateStatusMessage();
      });
    });
  }

  void _updateStatusMessage() {
    String message = '';

    if (_recorderState == VoiceRecorderState.recording) {
      message = 'Recording...';
    } else if (_playerState == VoicePlayerState.playing) {
      message = 'Playing...';
    } else if (_playerState == VoicePlayerState.buffering) {
      message = 'Buffering...';
    } else if (_playerState == VoicePlayerState.paused) {
      message = 'Paused';
    } else {
      message = 'Ready';
    }

    if (_recordedAudioChunks.isNotEmpty) {
      message += ' (${_recordedAudioChunks.length} chunks)';
    }

    setState(() {
      _statusMessage = message;
    });
  }

  Future<void> _startRecording() async {
    try {
      _recordedAudioChunks.clear();
      _isRecording = true;

      await _recorderService.startRecording(
        config: StreamConfig.lowLatency(),
        onAudioData: (data) {
          if (_isRecording) {
            _recordedAudioChunks.add(data);
          }
        },
      );
    } catch (e) {
      _showErrorDialog('Failed to start recording: $e');
    }
  }

  Future<void> _stopRecording() async {
    try {
      _isRecording = false;
      await _recorderService.stopRecording();
    } catch (e) {
      _showErrorDialog('Failed to stop recording: $e');
    }
  }

  Future<void> _playRecordedAudio() async {
    if (_recordedAudioChunks.isEmpty) {
      _showErrorDialog('No recorded audio to play');
      return;
    }

    try {
      // Start buffering
      _playerService.startBuffering('local-recording');

      // Add all recorded chunks
      for (final chunk in _recordedAudioChunks) {
        _playerService.addAudioData(chunk);
      }

      // Finalize and play
      await _playerService.finalizeBufferAndPlay();
    } catch (e) {
      _showErrorDialog('Failed to play audio: $e');
    }
  }

  Future<void> _stopPlayback() async {
    try {
      await _playerService.stopPlayback();
    } catch (e) {
      _showErrorDialog('Failed to stop playback: $e');
    }
  }

  Future<void> _pausePlayback() async {
    try {
      await _playerService.pausePlayback();
    } catch (e) {
      _showErrorDialog('Failed to pause playback: $e');
    }
  }

  Future<void> _resumePlayback() async {
    try {
      await _playerService.resumePlayback();
    } catch (e) {
      _showErrorDialog('Failed to resume playback: $e');
    }
  }

  void _clearRecording() {
    setState(() {
      _recordedAudioChunks.clear();
      _updateStatusMessage();
    });
  }

  void _showErrorDialog(String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Error'),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Local Voice Recording & Playback'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status display
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Status: $_statusMessage',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Text('Recorder: ${_recorderState.name}'),
                    Text('Player: ${_playerState.name}'),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 24),

            // Recording controls
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Recording Controls',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 16),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _isInitialized && !_isRecording
                                ? _startRecording
                                : null,
                            icon: const Icon(Icons.mic),
                            label: const Text('Start Recording'),
                            style: ElevatedButton.styleFrom(
                              backgroundColor: Colors.red,
                              foregroundColor: Colors.white,
                            ),
                          ),
                        ),
                        const SizedBox(width: 16),
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _isRecording ? _stopRecording : null,
                            icon: const Icon(Icons.stop),
                            label: const Text('Stop Recording'),
                            style: ElevatedButton.styleFrom(
                              backgroundColor: Colors.grey,
                              foregroundColor: Colors.white,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Playback controls
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Playback Controls',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 16),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _recordedAudioChunks.isNotEmpty &&
                                    _playerState != VoicePlayerState.playing
                                ? _playRecordedAudio
                                : null,
                            icon: const Icon(Icons.play_arrow),
                            label: const Text('Play Recording'),
                            style: ElevatedButton.styleFrom(
                              backgroundColor: Colors.green,
                              foregroundColor: Colors.white,
                            ),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _playerState == VoicePlayerState.playing
                                ? _pausePlayback
                                : _playerState == VoicePlayerState.paused
                                    ? _resumePlayback
                                    : null,
                            icon: Icon(_playerState == VoicePlayerState.paused
                                ? Icons.play_arrow
                                : Icons.pause),
                            label: Text(_playerState == VoicePlayerState.paused
                                ? 'Resume'
                                : 'Pause'),
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed:
                                _playerState == VoicePlayerState.playing ||
                                        _playerState == VoicePlayerState.paused
                                    ? _stopPlayback
                                    : null,
                            icon: const Icon(Icons.stop),
                            label: const Text('Stop Playback'),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _recordedAudioChunks.isNotEmpty
                                ? _clearRecording
                                : null,
                            icon: const Icon(Icons.clear),
                            label: const Text('Clear Recording'),
                            style: ElevatedButton.styleFrom(
                              backgroundColor: Colors.orange,
                              foregroundColor: Colors.white,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),

            const Spacer(),

            // Instructions
            Card(
              color: Colors.blue.shade50,
              child: const Padding(
                padding: EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Instructions:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    SizedBox(height: 8),
                    Text('1. Click "Start Recording" to begin recording audio'),
                    Text('2. Click "Stop Recording" when finished'),
                    Text(
                        '3. Click "Play Recording" to playback what you recorded'),
                    Text('4. Use Pause/Resume to control playback'),
                    Text('5. Use "Clear Recording" to start over'),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    if (_isInitialized) {
      _recorderService.dispose();
      _playerService.dispose();
    }
    super.dispose();
  }
}
0
likes
140
points
188
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for streaming voice recording, and audio playback with focus on real-time voice interactions.

Homepage

Topics

#voice #audio #streaming #recording #playback

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_sound, http, logger, path_provider, record, stacked, stacked_services, uuid, web_socket_channel

More

Packages that depend on flutter_realtime_voice_ai