content: import 'dart:io';
  import 'dart:math';
  
  import 'package:flutter/material.dart';
  import 'package:flutter/services.dart';
  import 'package:path/path.dart' as p;
  import 'package:path_provider/path_provider.dart';
  import 'package:rxdart/rxdart.dart';
  import 'package:just_waveform/just_waveform.dart';
  
  void main() {
    runApp(const MyApp());
  }
  
  class MyApp extends StatefulWidget {
    const MyApp({Key? key}) : super(key: key);
  
    @override
    State<MyApp> createState() => _MyAppState();
  }
  
  class _MyAppState extends State<MyApp> {
    final progressStream = BehaviorSubject<WaveformProgress>();
  
    @override
    void initState() {
      super.initState();
      _init();
    }
  
    Future<void> _init() async {
      final audioFile =
          File(p.join((await getTemporaryDirectory()).path, 'waveform.mp3'));
      try {
        await audioFile.writeAsBytes(
            (await rootBundle.load('audio/waveform.mp3')).buffer.asUint8List());
        final waveFile =
            File(p.join((await getTemporaryDirectory()).path, 'waveform.wave'));
        JustWaveform.extract(audioInFile: audioFile, waveOutFile: waveFile)
            .listen(progressStream.add, onError: progressStream.addError);
      } catch (e) {
        progressStream.addError(e);
      }
    }
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text('Plugin example app'),
          ),
          body: Container(
            alignment: Alignment.center,
            padding: const EdgeInsets.all(16.0),
            child: Container(
              height: 150.0,
              decoration: BoxDecoration(
                color: Colors.grey.shade200,
                borderRadius: const BorderRadius.all(Radius.circular(20.0)),
              ),
              padding: const EdgeInsets.all(16.0),
              width: double.maxFinite,
              child: StreamBuilder<WaveformProgress>(
                stream: progressStream,
                builder: (context, snapshot) {
                  if (snapshot.hasError) {
                    return Center(
                      child: Text(
                        'Error: ${snapshot.error}',
                        style: Theme.of(context).textTheme.titleLarge,
                        textAlign: TextAlign.center,
                      ),
                    );
                  }
                  final progress = snapshot.data?.progress ?? 0.0;
                  final waveform = snapshot.data?.waveform;
                  if (waveform == null) {
                    return Center(
                      child: Text(
                        '${(100 * progress).toInt()}%',
                        style: Theme.of(context).textTheme.titleLarge,
                      ),
                    );
                  }
                  return AudioWaveformWidget(
                    waveform: waveform,
                    start: Duration.zero,
                    duration: waveform.duration,
                  );
                },
              ),
            ),
          ),
        ),
      );
    }
  }
  
  class AudioWaveformWidget extends StatefulWidget {
    final Color waveColor;
    final double scale;
    final double strokeWidth;
    final double pixelsPerStep;
    final Waveform waveform;
    final Duration start;
    final Duration duration;
  
    const AudioWaveformWidget({
      Key? key,
      required this.waveform,
      required this.start,
      required this.duration,
      this.waveColor = Colors.blue,
      this.scale = 1.0,
      this.strokeWidth = 5.0,
      this.pixelsPerStep = 8.0,
    }) : super(key: key);
  
    @override
    State<AudioWaveformWidget> createState() => _AudioWaveformState();
  }
  
  class _AudioWaveformState extends State<AudioWaveformWidget> {
    @override
    Widget build(BuildContext context) {
      return ClipRect(
        child: CustomPaint(
          painter: AudioWaveformPainter(
            waveColor: widget.waveColor,
            waveform: widget.waveform,
            start: widget.start,
            duration: widget.duration,
            scale: widget.scale,
            strokeWidth: widget.strokeWidth,
            pixelsPerStep: widget.pixelsPerStep,
          ),
        ),
      );
    }
  }
  
  class AudioWaveformPainter extends CustomPainter {
    final double scale;
    final double strokeWidth;
    final double pixelsPerStep;
    final Paint wavePaint;
    final Waveform waveform;
    final Duration start;
    final Duration duration;
  
    AudioWaveformPainter({
      required this.waveform,
      required this.start,
      required this.duration,
      Color waveColor = Colors.blue,
      this.scale = 1.0,
      this.strokeWidth = 5.0,
      this.pixelsPerStep = 8.0,
    }) : wavePaint = Paint()
            ..style = PaintingStyle.stroke
            ..strokeWidth = strokeWidth
            ..strokeCap = StrokeCap.round
            ..color = waveColor;
  
    @override
    void paint(Canvas canvas, Size size) {
      if (duration == Duration.zero) return;
  
      double width = size.width;
      double height = size.height;
  
      final waveformPixelsPerWindow = waveform.positionToPixel(duration).toInt();
      final waveformPixelsPerDevicePixel = waveformPixelsPerWindow / width;
      final waveformPixelsPerStep = waveformPixelsPerDevicePixel * pixelsPerStep;
      final sampleOffset = waveform.positionToPixel(start);
      final sampleStart = -sampleOffset % waveformPixelsPerStep;
      for (var i = sampleStart.toDouble();
          i <= waveformPixelsPerWindow + 1.0;
          i += waveformPixelsPerStep) {
        final sampleIdx = (sampleOffset + i).toInt();
        final x = i / waveformPixelsPerDevicePixel;
        final minY = normalise(waveform.getPixelMin(sampleIdx), height);
        final maxY = normalise(waveform.getPixelMax(sampleIdx), height);
        canvas.drawLine(
          Offset(x + strokeWidth / 2, max(strokeWidth * 0.75, minY)),
          Offset(x + strokeWidth / 2, min(height - strokeWidth * 0.75, maxY)),
          wavePaint,
        );
      }
    }
  
    @override
    bool shouldRepaint(covariant AudioWaveformPainter oldDelegate) {
      return false;
    }
  
    double normalise(int s, double height) {
      if (waveform.flags == 0) {
        final y = 32768 + (scale * s).clamp(-32768.0, 32767.0).toDouble();
        return height - 1 - y * height / 65536;
      } else {
        final y = 128 + (scale * s).clamp(-128.0, 127.0).toDouble();
        return height - 1 - y * height / 256;
      }
    }
  }
  