mic_stream 0.2.0+1
mic_stream: ^0.2.0+1 copied to clipboard
Provides a tool to get the microphone input as PCM Stream [Android only]
example/lib/main.dart
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:mic_stream/mic_stream.dart';
enum Command {
start,
stop,
change,
}
void main() => runApp(MicStreamExampleApp());
class MicStreamExampleApp extends StatefulWidget {
@override
_MicStreamExampleAppState createState() => _MicStreamExampleAppState();
}
class _MicStreamExampleAppState extends State<MicStreamExampleApp> with SingleTickerProviderStateMixin, WidgetsBindingObserver {
Stream<List<int>> stream;
StreamSubscription<List<int>> listener;
List<int> currentSamples;
// Refreshes the Widget for every possible tick to force a rebuild of the sound wave
AnimationController controller;
Color _iconColor = Colors.white;
bool isRecording = false;
bool memRecordingState = false;
bool isActive;
DateTime startTime;
int page = 0;
List state = ["SoundWavePage", "InformationPage"];
@override
void initState() {
print("Init application");
super.initState();
WidgetsBinding.instance.addObserver(this);
setState(() {
initPlatformState();
});
}
void _controlPage(int index) => setState(() => page = index);
// Responsible for switching between recording / idle state
void _controlMicStream({Command command: Command.change}) async {
switch(command) {
case Command.change:
_changeListening();
break;
case Command.start:
_startListening();
break;
case Command.stop:
_stopListening();
break;
}
}
bool _changeListening() => !isRecording ? _startListening() : _stopListening();
bool _startListening() {
if (isRecording) return false;
stream = microphone(audioSource: AudioSource.DEFAULT, sampleRate: 16000, channelConfig: ChannelConfig.CHANNEL_IN_MONO, audioFormat: AudioFormat.ENCODING_PCM_16BIT);
setState(() {
isRecording = true;
startTime = DateTime.now();
});
print("Start Listening to the microphone");
listener = stream.listen((samples) => currentSamples = samples);
return true;
}
bool _stopListening() {
if (!isRecording) return false;
print("Stop Listening to the microphone");
listener.cancel();
setState(() {
isRecording = false;
currentSamples = null;
startTime = null;
});
return true;
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
if (!mounted) return;
isActive = true;
Statistics(false);
controller = AnimationController(duration: Duration(seconds: 1), vsync: this)
..addListener(() {
if (isRecording) setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) controller.reverse();
else if (status == AnimationStatus.dismissed) controller.forward();
})
..forward();
}
Color _getBgColor() => (isRecording) ? Colors.red : Colors.cyan;
Icon _getIcon() => (isRecording) ? Icon(Icons.stop) : Icon(Icons.keyboard_voice);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin: mic_stream :: Debug'),
),
floatingActionButton: FloatingActionButton(
onPressed: _controlMicStream,
child: _getIcon(),
foregroundColor: _iconColor,
backgroundColor: _getBgColor(),
tooltip: (isRecording) ? "Stop recording" : "Start recording",
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.broken_image),
title: Text("Sound Wave"),
),
BottomNavigationBarItem(
icon: Icon(Icons.view_list),
title: Text("Statistics"),
)
],
backgroundColor: Colors.black26,
elevation: 20,
currentIndex: page,
onTap: _controlPage,
),
body: (page == 0) ?
CustomPaint(
painter: WavePainter(currentSamples, _getBgColor(), context),
) :
Statistics(isRecording, startTime: startTime,)
),
);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
isActive = true;
print("Resume app");
_controlMicStream(command: memRecordingState ? Command.start : Command.stop);
}
else if (isActive){
memRecordingState = isRecording;
_controlMicStream(command: Command.stop);
print("Pause app");
isActive = false;
}
}
@override
void dispose() {
listener.cancel();
controller.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
class WavePainter extends CustomPainter {
List<int> samples;
List<Offset> points;
Color color;
BuildContext context;
Size size;
static int absMax = 0;
WavePainter(this.samples, this.color, this.context);
@override
void paint(Canvas canvas, Size size) {
this.size = context.size;
size = this.size;
Paint paint = new Paint()
..color = color
..strokeWidth = 1.0
..style = PaintingStyle.stroke;
points = toPoints(samples);
Path path = new Path();
path.addPolygon(points, false);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldPainting) => true;
// Maps a list of ints and their indices to a list of points on a cartesian grid
List<Offset> toPoints (List<int> samples) {
List<Offset> points = [];
if (samples == null) samples = List<int>.filled(size.width.toInt(), (0.5 * size.height).toInt());
else samples.forEach((sample) => absMax = max(absMax, sample.abs()));
for (int i = 0; i < min(size.width, samples.length).toInt(); i++) {
points.add(new Offset(i.toDouble(), project(samples[i], absMax, size.height)));
}
return points;
}
double project(int val, int max, double height) {
double waveHeight;
if (max == 0) waveHeight = val.toDouble();
else waveHeight = (val / max) * 0.5 * height;
return waveHeight + 0.5 * height;
}
}
class Statistics extends StatelessWidget {
final bool isRecording;
final DateTime startTime;
final String url = "https://github.com/anarchuser/mic_stream";
Statistics(this.isRecording, {this.startTime});
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget> [
ListTile(
leading: Icon(Icons.title),
title: Text("Microphone Streaming Example App")
),
ListTile(
leading: Icon(Icons.input),
title: MaterialButton(
child: Text("Github Repository"),
onPressed: _launchURL,
)
),
ListTile(
leading: Icon(Icons.keyboard_voice),
title: Text((isRecording ? "Recording" : "Not recording")),
),
ListTile(
leading: Icon(Icons.access_time),
title: Text((isRecording ? DateTime.now().difference(startTime).toString() : "Not recording"))
),
]
);
}
// According to "url_launcher"'s example implementation on https://pub.dev/packages/url_launcher
void _launchURL() => launch(url);
}