Thanks to visit codestin.com
Credit goes to pub.dev

audio_decoder 0.7.3 copy "audio_decoder: ^0.7.3" to clipboard
audio_decoder: ^0.7.3 copied to clipboard

Decode MP3, M4A, AAC, FLAC, OGG & more to WAV/PCM using native platform APIs. Convert, trim, and analyze audio — no FFmpeg required.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:audio_decoder/audio_decoder.dart';

enum _StatusType { ready, loading, success, error }

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _status = 'Tap a button to test audio operations.';
  _StatusType _statusType = _StatusType.ready;
  bool _busy = false;
  List<double>? _waveform;

  // ---------------------------------------------------------------------------
  // Business logic
  // ---------------------------------------------------------------------------

  /// Helper to copy Flutter assets to temp directory for file-based API
  Future<String> _copyAssetToTemp(String assetPath) async {
    final data = await rootBundle.load(assetPath);
    final dir = Directory.systemTemp;
    final file = File('${dir.path}/${assetPath.split('/').last}');
    await file.writeAsBytes(data.buffer.asUint8List());
    return file.path;
  }

  Future<void> _convertToWav(String assetPath) async {
    if (_busy) return;

    final ext = assetPath.split('.').last.toUpperCase();

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Converting $ext to WAV...';
    });

    try {
      final inputPath = await _copyAssetToTemp(assetPath);
      final inputSize = File(inputPath).lengthSync();
      final baseName = assetPath.split('/').last.replaceAll(RegExp(r'\.[^.]+$'), '');

      final outputPath = '${Directory.systemTemp.path}/${baseName}_converted.wav';
      // Convert any audio format to WAV (lossless PCM)
      final result = await AudioDecoder.convertToWav(inputPath, outputPath);
      final outputSize = await File(result).length();

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Converted $ext → WAV\n\n'
            'Input: ${assetPath.split('/').last} ($inputSize bytes)\n'
            'Output: ${result.split('/').last}\n'
            'Size: ${(outputSize / 1024).toStringAsFixed(1)} KB';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Conversion failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _convertToM4a(String assetPath) async {
    if (_busy) return;

    final ext = assetPath.split('.').last.toUpperCase();

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Converting $ext to M4A...';
    });

    try {
      final inputPath = await _copyAssetToTemp(assetPath);
      final inputSize = File(inputPath).lengthSync();
      final baseName = assetPath.split('/').last.replaceAll(RegExp(r'\.[^.]+$'), '');

      final outputPath = '${Directory.systemTemp.path}/${baseName}_converted.m4a';
      // Convert any audio format to M4A (compressed AAC)
      final result = await AudioDecoder.convertToM4a(inputPath, outputPath);
      final outputSize = await File(result).length();

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Converted $ext → M4A\n\n'
            'Input: ${assetPath.split('/').last} ($inputSize bytes)\n'
            'Output: ${result.split('/').last}\n'
            'Size: ${(outputSize / 1024).toStringAsFixed(1)} KB';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Conversion failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _getAudioInfo(String assetPath) async {
    if (_busy) return;

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Getting audio info...';
    });

    try {
      final inputPath = await _copyAssetToTemp(assetPath);
      // Get metadata: duration, sample rate, channels, bit rate, format
      final info = await AudioDecoder.getAudioInfo(inputPath);

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Audio Info: ${assetPath.split('/').last}\n\n'
            'Duration: ${info.duration.inMilliseconds} ms\n'
            'Sample rate: ${info.sampleRate} Hz\n'
            'Channels: ${info.channels}\n'
            'Bit rate: ${info.bitRate} bps\n'
            'Format: ${info.format}';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Get info failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _trimAudio(String assetPath) async {
    if (_busy) return;

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Trimming audio (0.2s - 0.8s)...';
    });

    try {
      final inputPath = await _copyAssetToTemp(assetPath);
      final inputSize = File(inputPath).lengthSync();
      final outputPath = '${Directory.systemTemp.path}/trimmed.wav';
      // Extract a time range from audio file
      final result = await AudioDecoder.trimAudio(
        inputPath,
        outputPath,
        const Duration(milliseconds: 200),
        const Duration(milliseconds: 800),
      );
      final outputSize = await File(result).length();

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Trimmed ${assetPath.split('/').last} (0.2s-0.8s)\n\n'
            'Input: $inputSize bytes\n'
            'Output: ${result.split('/').last}\n'
            'Size: ${(outputSize / 1024).toStringAsFixed(1)} KB';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Trim failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _getWaveform(String assetPath) async {
    if (_busy) return;

    setState(() {
      _busy = true;
      _waveform = null;
      _statusType = _StatusType.loading;
      _status = 'Extracting waveform...';
    });

    try {
      final inputPath = await _copyAssetToTemp(assetPath);
      // Extract normalized amplitude data (0.0-1.0) for waveform visualization
      final waveform = await AudioDecoder.getWaveform(inputPath, numberOfSamples: 100);

      setState(() {
        _waveform = waveform;
        _statusType = _StatusType.success;
        _status = 'Waveform (${waveform.length} samples)';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Waveform failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  /// Helper to load Flutter assets as bytes
  Future<Uint8List> _loadAssetBytes(String assetPath) async {
    final data = await rootBundle.load(assetPath);
    return data.buffer.asUint8List();
  }

  // ---------------------------------------------------------------------------
  // Bytes API (in-memory) - Use when working with network responses, assets,
  // or any in-memory audio data without file I/O
  // ---------------------------------------------------------------------------

  Future<void> _convertToWavBytes(String assetPath) async {
    if (_busy) return;

    final ext = assetPath.split('.').last;

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Converting ${ext.toUpperCase()} → WAV (bytes API)...';
    });

    try {
      final inputBytes = await _loadAssetBytes(assetPath);
      // Convert in-memory audio bytes to WAV format (requires formatHint)
      final wavBytes = await AudioDecoder.convertToWavBytes(inputBytes, formatHint: ext);

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Bytes API: ${ext.toUpperCase()} → WAV\n\n'
            'Input: ${assetPath.split('/').last} (${inputBytes.length} bytes)\n'
            'Output: ${wavBytes.length} bytes\n'
            'Size: ${(wavBytes.length / 1024).toStringAsFixed(1)} KB';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Bytes conversion failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _convertToRawPcmBytes(String assetPath) async {
    if (_busy) return;

    final ext = assetPath.split('.').last;

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Converting ${ext.toUpperCase()} → raw PCM (bytes API)...';
    });

    try {
      final inputBytes = await _loadAssetBytes(assetPath);
      final wavBytes = await AudioDecoder.convertToWavBytes(inputBytes, formatHint: ext);
      // Get raw PCM data without WAV header (set includeHeader: false)
      final pcmBytes = await AudioDecoder.convertToWavBytes(inputBytes, formatHint: ext, includeHeader: false);

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Bytes API: ${ext.toUpperCase()} → raw PCM\n\n'
            'Input: ${assetPath.split('/').last} (${inputBytes.length} bytes)\n'
            'WAV output: ${wavBytes.length} bytes (with header)\n'
            'PCM output: ${pcmBytes.length} bytes (headerless)\n'
            'Header stripped: ${wavBytes.length - pcmBytes.length} bytes';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Raw PCM conversion failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _getAudioInfoBytes(String assetPath) async {
    if (_busy) return;

    final ext = assetPath.split('.').last;

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Getting audio info (bytes API)...';
    });

    try {
      final inputBytes = await _loadAssetBytes(assetPath);
      final info = await AudioDecoder.getAudioInfoBytes(inputBytes, formatHint: ext);

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Bytes API Info: ${assetPath.split('/').last}\n\n'
            'Duration: ${info.duration.inMilliseconds} ms\n'
            'Sample rate: ${info.sampleRate} Hz\n'
            'Channels: ${info.channels}\n'
            'Bit rate: ${info.bitRate} bps\n'
            'Format: ${info.format}';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Bytes info failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _trimAudioBytes(String assetPath) async {
    if (_busy) return;

    final ext = assetPath.split('.').last;

    setState(() {
      _busy = true;
      _statusType = _StatusType.loading;
      _status = 'Trimming audio (0.2s - 0.8s, bytes API)...';
    });

    try {
      final inputBytes = await _loadAssetBytes(assetPath);
      final trimmedBytes = await AudioDecoder.trimAudioBytes(
        inputBytes,
        formatHint: ext,
        start: const Duration(milliseconds: 200),
        end: const Duration(milliseconds: 800),
      );

      setState(() {
        _statusType = _StatusType.success;
        _status =
            'Bytes API: Trimmed (0.2s-0.8s)\n\n'
            'Input: ${assetPath.split('/').last} (${inputBytes.length} bytes)\n'
            'Output: ${trimmedBytes.length} bytes\n'
            'Size: ${(trimmedBytes.length / 1024).toStringAsFixed(1)} KB';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Bytes trim failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  Future<void> _getWaveformBytes(String assetPath) async {
    if (_busy) return;

    final ext = assetPath.split('.').last;

    setState(() {
      _busy = true;
      _waveform = null;
      _statusType = _StatusType.loading;
      _status = 'Extracting waveform (bytes API)...';
    });

    try {
      final inputBytes = await _loadAssetBytes(assetPath);
      // Extract waveform data from in-memory audio bytes
      final waveform = await AudioDecoder.getWaveformBytes(inputBytes, formatHint: ext, numberOfSamples: 100);

      setState(() {
        _waveform = waveform;
        _statusType = _StatusType.success;
        _status = 'Bytes API: Waveform (${waveform.length} samples)';
      });
    } on AudioConversionException catch (e) {
      setState(() {
        _statusType = _StatusType.error;
        _status = 'Bytes waveform failed: $e';
      });
    } finally {
      setState(() => _busy = false);
    }
  }

  // ---------------------------------------------------------------------------
  // UI helpers
  // ---------------------------------------------------------------------------

  Widget _sectionCard({required String title, required IconData icon, required List<Widget> children}) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
                const SizedBox(width: 8),
                Text(title, style: Theme.of(context).textTheme.titleSmall),
              ],
            ),
            const SizedBox(height: 12),
            ...children,
          ],
        ),
      ),
    );
  }

  Widget _actionButton({required String label, required IconData icon, required VoidCallback? onPressed}) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: SizedBox(
        width: double.infinity,
        child: FilledButton.tonalIcon(onPressed: onPressed, icon: Icon(icon, size: 18), label: Text(label)),
      ),
    );
  }

  // ---------------------------------------------------------------------------
  // Build
  // ---------------------------------------------------------------------------

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;

    final statusColor = switch (_statusType) {
      _StatusType.ready => colorScheme.surfaceContainerLow,
      _StatusType.loading => colorScheme.surfaceContainerLow,
      _StatusType.success => colorScheme.primaryContainer,
      _StatusType.error => colorScheme.errorContainer,
    };

    final statusIcon = switch (_statusType) {
      _StatusType.ready => Icon(Icons.audio_file, color: colorScheme.onSurfaceVariant),
      _StatusType.loading => SizedBox(
        width: 20,
        height: 20,
        child: CircularProgressIndicator(strokeWidth: 2.5, color: colorScheme.primary),
      ),
      _StatusType.success => Icon(Icons.check_circle_outline, color: colorScheme.primary),
      _StatusType.error => Icon(Icons.error_outline, color: colorScheme.error),
    };

    return MaterialApp(
      theme: ThemeData(colorSchemeSeed: Colors.deepPurple, useMaterial3: true),
      home: Scaffold(
        appBar: AppBar(title: const Text('Audio Decoder'), centerTitle: true),
        body: Column(
          children: [
            // Status area
            Padding(
              padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
              child: Card(
                color: statusColor,
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    children: [
                      Row(
                        children: [
                          statusIcon,
                          const SizedBox(width: 12),
                          Expanded(
                            child: Text(
                              _status,
                              style: Theme.of(context).textTheme.bodySmall?.copyWith(fontFamily: 'monospace'),
                            ),
                          ),
                        ],
                      ),
                      if (_waveform != null) ...[
                        const SizedBox(height: 12),
                        Container(
                          height: 150,
                          decoration: BoxDecoration(
                            color: colorScheme.surfaceContainerLowest,
                            borderRadius: BorderRadius.circular(12),
                            border: Border.all(color: colorScheme.outlineVariant),
                          ),
                          padding: const EdgeInsets.symmetric(vertical: 2),
                          clipBehavior: Clip.antiAlias,
                          child: CustomPaint(
                            size: const Size(double.infinity, 120),
                            painter: _WaveformPainter(
                              _waveform!,
                              color: colorScheme.primary,
                              accentColor: colorScheme.tertiary,
                            ),
                          ),
                        ),
                      ],
                    ],
                  ),
                ),
              ),
            ),
            // Action buttons
            Expanded(
              child: SingleChildScrollView(
                padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    _sectionCard(
                      title: 'Conversion',
                      icon: Icons.swap_horiz,
                      children: [
                        _actionButton(
                          label: 'MP3 → WAV',
                          icon: Icons.audio_file,
                          onPressed: _busy ? null : () => _convertToWav('assets/test_tone.mp3'),
                        ),
                        _actionButton(
                          label: 'M4A → WAV',
                          icon: Icons.audio_file,
                          onPressed: _busy ? null : () => _convertToWav('assets/test_tone.m4a'),
                        ),
                        _actionButton(
                          label: 'WAV → M4A',
                          icon: Icons.audio_file,
                          onPressed: _busy ? null : () => _convertToM4a('assets/test_tone.wav'),
                        ),
                      ],
                    ),
                    _sectionCard(
                      title: 'Info & Analysis',
                      icon: Icons.analytics_outlined,
                      children: [
                        _actionButton(
                          label: 'Get Audio Info (MP3)',
                          icon: Icons.info_outline,
                          onPressed: _busy ? null : () => _getAudioInfo('assets/test_tone.mp3'),
                        ),
                        _actionButton(
                          label: 'Get Waveform (MP3)',
                          icon: Icons.graphic_eq,
                          onPressed: _busy ? null : () => _getWaveform('assets/test_tone.mp3'),
                        ),
                      ],
                    ),
                    _sectionCard(
                      title: 'Trim',
                      icon: Icons.content_cut,
                      children: [
                        _actionButton(
                          label: 'Trim MP3 (0.2s – 0.8s) → WAV',
                          icon: Icons.content_cut,
                          onPressed: _busy ? null : () => _trimAudio('assets/test_tone.mp3'),
                        ),
                      ],
                    ),
                    _sectionCard(
                      title: 'Bytes API (in-memory)',
                      icon: Icons.memory,
                      children: [
                        _actionButton(
                          label: 'MP3 → WAV (bytes)',
                          icon: Icons.swap_horiz,
                          onPressed: _busy ? null : () => _convertToWavBytes('assets/test_tone.mp3'),
                        ),
                        _actionButton(
                          label: 'MP3 → raw PCM (bytes)',
                          icon: Icons.data_array,
                          onPressed: _busy ? null : () => _convertToRawPcmBytes('assets/test_tone.mp3'),
                        ),
                        _actionButton(
                          label: 'Get Audio Info (bytes)',
                          icon: Icons.info_outline,
                          onPressed: _busy ? null : () => _getAudioInfoBytes('assets/test_tone.mp3'),
                        ),
                        _actionButton(
                          label: 'Trim MP3 (0.2s – 0.8s, bytes)',
                          icon: Icons.content_cut,
                          onPressed: _busy ? null : () => _trimAudioBytes('assets/test_tone.mp3'),
                        ),
                        _actionButton(
                          label: 'Get Waveform (bytes)',
                          icon: Icons.graphic_eq,
                          onPressed: _busy ? null : () => _getWaveformBytes('assets/test_tone.mp3'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// CustomPainter for rendering audio waveform with gradient color effect
class _WaveformPainter extends CustomPainter {
  final List<double> waveform;
  final Color color;
  final Color accentColor;

  _WaveformPainter(this.waveform, {required this.color, required this.accentColor});

  @override
  void paint(Canvas canvas, Size size) {
    if (waveform.isEmpty) return;

    final barWidth = size.width / waveform.length;
    final midY = size.height / 2;
    final radius = Radius.circular(barWidth * 0.4);

    for (int i = 0; i < waveform.length; i++) {
      final t = i / waveform.length;
      final barColor = Color.lerp(color, accentColor, t)!;
      final paint = Paint()..color = barColor;

      final barHeight = waveform[i] * midY;
      final x = i * barWidth;
      canvas.drawRRect(
        RRect.fromRectAndRadius(
          Rect.fromCenter(center: Offset(x + barWidth / 2, midY), width: barWidth * 0.7, height: barHeight * 2),
          radius,
        ),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(_WaveformPainter old) =>
      old.waveform != waveform || old.color != color || old.accentColor != accentColor;
}
1
likes
150
points
983
downloads
screenshot

Publisher

unverified uploader

Weekly Downloads

Decode MP3, M4A, AAC, FLAC, OGG & more to WAV/PCM using native platform APIs. Convert, trim, and analyze audio — no FFmpeg required.

Homepage
Repository (GitHub)
View/report issues

Topics

#audio #audio-processing #decoder #pcm #flutter-plugin

Documentation

API reference

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, flutter_web_plugins, plugin_platform_interface, web

More

Packages that depend on audio_decoder

Packages that implement audio_decoder