just_waveform 0.0.5 just_waveform: ^0.0.5 copied to clipboard
Extracts waveform data from an audio file suitable for visually rendering the waveform.
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
_AudioWaveformState 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;
}
}
}