Riprodurre e registrare audio in Flutter con just_audio e record
GuideIntermedio40 min Flutter 3.x

Riprodurre e registrare audio in Flutter con just_audio e record

Audio in Flutter

Molte applicazioni moderne (podcast, note vocali, lettori musicali) richiedono la gestione dell'audio. In questo tutorial vedremo come usare just_audio per la riproduzione e record per la registrazione vocale.

Alla fine avrai una schermata capace di registrare un memo vocale, salvarlo su disco e riprodurlo con una barra di avanzamento interattiva.

Cosa useremo

  • just_audio per il playback
  • record per registrare dal microfono
  • path_provider per ottenere una cartella dove salvare i file
  • permission_handler per il permesso microfono

Assicurati di testare su un dispositivo reale: la registrazione audio spesso non funziona correttamente sugli emulatori.

  1. 1

    Aggiungere le dipendenze

    Aggiungi i pacchetti necessari al file pubspec.yaml. just_audio gestisce la riproduzione, record la cattura dal microfono, path_provider fornisce le directory di sistema e permission_handler gestisce i permessi runtime.

    Dopo la modifica esegui flutter pub get.

    dependencies:
      flutter:
        sdk: flutter
      just_audio: ^0.9.40
      record: ^5.1.2
      path_provider: ^2.1.4
      permission_handler: ^11.3.1

    Risultato atteso

    I pacchetti vengono scaricati senza errori e sono disponibili per l'import.

  2. 2

    Configurare i permessi nativi

    La registrazione richiede l'accesso al microfono su entrambe le piattaforme.

    Android — in android/app/src/main/AndroidManifest.xml aggiungi il permesso:

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    

    iOS — in ios/Runner/Info.plist aggiungi la descrizione d'uso del microfono, altrimenti l'app verrà rifiutata dallo store.

    <key>NSMicrophoneUsageDescription</key>
    <string>L'app ha bisogno del microfono per registrare i memo vocali.</string>

    Risultato atteso

    Le piattaforme sono configurate per richiedere il permesso microfono a runtime.

  3. 3

    Creare il servizio di registrazione

    Incapsuliamo la logica di registrazione in una classe dedicata. Prima di registrare verifichiamo il permesso con record, poi definiamo un percorso di output nella cartella documenti dell'app usando path_provider.

    Il metodo stop() restituisce il percorso del file registrato.

    import 'package:record/record.dart';
    import 'package:path_provider/path_provider.dart';
    
    class RecorderService {
      final AudioRecorder _recorder = AudioRecorder();
    
      Future<void> start() async {
        if (await _recorder.hasPermission()) {
          final dir = await getApplicationDocumentsDirectory();
          final path =
              '${dir.path}/memo_${DateTime.now().millisecondsSinceEpoch}.m4a';
          await _recorder.start(
            const RecordConfig(encoder: AudioEncoder.aacLc),
            path: path,
          );
        }
      }
    
      Future<String?> stop() async {
        return await _recorder.stop();
      }
    
      Future<bool> get isRecording => _recorder.isRecording();
    
      void dispose() => _recorder.dispose();
    }

    Risultato atteso

    Hai un servizio riutilizzabile per avviare e fermare la registrazione, che salva un file .m4a.

  4. 4

    Costruire la UI di registrazione

    Creiamo uno StatefulWidget con un pulsante che alterna tra avvio e stop della registrazione. Al termine memorizziamo il percorso del file per poterlo riprodurre successivamente.

    class AudioScreen extends StatefulWidget {
      const AudioScreen({super.key});
      @override
      State<AudioScreen> createState() => _AudioScreenState();
    }
    
    class _AudioScreenState extends State<AudioScreen> {
      final RecorderService _recorder = RecorderService();
      bool _isRecording = false;
      String? _filePath;
    
      Future<void> _toggleRecording() async {
        if (_isRecording) {
          final path = await _recorder.stop();
          setState(() {
            _isRecording = false;
            _filePath = path;
          });
        } else {
          await _recorder.start();
          setState(() => _isRecording = true);
        }
      }
    
      @override
      void dispose() {
        _recorder.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Memo vocale')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: _toggleRecording,
                  icon: Icon(_isRecording ? Icons.stop : Icons.mic),
                  label: Text(_isRecording ? 'Ferma' : 'Registra'),
                ),
                if (_filePath != null) ...[
                  const SizedBox(height: 24),
                  Text('File: ${_filePath!.split('/').last}'),
                ],
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Puoi registrare un memo vocale e vedere il nome del file salvato in schermata.

  5. 5

    Riprodurre l'audio con just_audio

    Aggiungiamo un AudioPlayer di just_audio. Carichiamo il file registrato tramite setFilePath e gestiamo play/pausa. Ricorda che just_audio espone comodi Stream per lo stato del player e la posizione corrente.

    import 'package:just_audio/just_audio.dart';
    
    // Aggiungi nel State:
    final AudioPlayer _player = AudioPlayer();
    
    Future<void> _play() async {
      if (_filePath == null) return;
      await _player.setFilePath(_filePath!);
      await _player.play();
    }
    
    // Nel dispose:
    // _player.dispose();
    
    // Bottone nella UI:
    StreamBuilder<PlayerState>(
      stream: _player.playerStateStream,
      builder: (context, snapshot) {
        final playing = snapshot.data?.playing ?? false;
        return IconButton(
          iconSize: 48,
          icon: Icon(playing ? Icons.pause_circle : Icons.play_circle),
          onPressed: () async {
            if (playing) {
              await _player.pause();
            } else {
              await _play();
            }
          },
        );
      },
    )

    Risultato atteso

    Premendo play il memo registrato viene riprodotto e l'icona cambia tra play e pausa.

  6. 6

    Aggiungere una barra di avanzamento con seek

    Usiamo gli stream positionStream e durationStream di just_audio per mostrare uno Slider che riflette la posizione di riproduzione. Trascinando lo slider chiamiamo seek per spostarci nel brano.

    Combiniamo i due stream con un piccolo record per aggiornare la UI in modo reattivo.

    StreamBuilder<Duration?>(
      stream: _player.durationStream,
      builder: (context, durationSnap) {
        final duration = durationSnap.data ?? Duration.zero;
        return StreamBuilder<Duration>(
          stream: _player.positionStream,
          builder: (context, posSnap) {
            var position = posSnap.data ?? Duration.zero;
            if (position > duration) position = duration;
            return Column(
              children: [
                Slider(
                  min: 0,
                  max: duration.inMilliseconds.toDouble(),
                  value: position.inMilliseconds
                      .clamp(0, duration.inMilliseconds)
                      .toDouble(),
                  onChanged: (value) {
                    _player.seek(Duration(milliseconds: value.toInt()));
                  },
                ),
                Text(
                  '${_format(position)} / ${_format(duration)}',
                ),
              ],
            );
          },
        );
      },
    )
    
    String _format(Duration d) {
      final m = d.inMinutes.remainder(60).toString().padLeft(2, '0');
      final s = d.inSeconds.remainder(60).toString().padLeft(2, '0');
      return '$m:$s';
    }

    Risultato atteso

    Vedi una barra di avanzamento che si muove durante la riproduzione e puoi trascinarla per spostarti nel file audio.

  7. 7

    Rilasciare le risorse e testare

    È fondamentale liberare le risorse audio nel metodo dispose per evitare memory leak e blocchi del microfono. Assicurati di richiamare dispose sia su AudioPlayer sia sul RecorderService.

    Testa il flusso completo su un dispositivo fisico: registra un memo, verifica la creazione del file, riproducilo e usa lo slider. Su iOS accetta il popup dei permessi al primo avvio.

    @override
    void dispose() {
      _player.dispose();
      _recorder.dispose();
      super.dispose();
    }

    Risultato atteso

    L'app registra, salva e riproduce audio senza leak; il microfono viene rilasciato correttamente quando esci dalla schermata.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!