Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 27 additions & 62 deletions android/src/main/java/com/code/aaron/micstream/MicStreamPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;

import android.annotation.SuppressLint;
import android.media.AudioFormat;
Expand All @@ -21,38 +20,30 @@
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/** MicStreamPlugin
* In reference to flutters official sensors plugin
* and the example of the streams_channel (v0.2.2) plugin
*/

public class MicStreamPlugin implements FlutterPlugin, EventChannel.StreamHandler, MethodCallHandler {
private static final String MICROPHONE_CHANNEL_NAME = "aaron.code.com/mic_stream";
private static final String MICROPHONE_METHOD_CHANNEL_NAME = "aaron.code.com/mic_stream_method_channel";

/// New way of registering plugin
private EventChannel eventChannel;
private MethodChannel methodChannel;

@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
registerWith(binding.getBinaryMessenger());
}

/// Cleanup after connection loss to flutter
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
onCancel(null);
}

/// Deprecated way of registering plugin
public void registerWith(Registrar registrar) {
registerWith(registrar.messenger());
eventChannel.setStreamHandler(null);
methodChannel.setMethodCallHandler(null);
}

private void registerWith(BinaryMessenger messenger) {
final EventChannel microphone = new EventChannel(messenger, MICROPHONE_CHANNEL_NAME);
microphone.setStreamHandler(this);
MethodChannel methodChannel = new MethodChannel(messenger, MICROPHONE_METHOD_CHANNEL_NAME);
eventChannel = new EventChannel(messenger, MICROPHONE_CHANNEL_NAME);
eventChannel.setStreamHandler(this);
methodChannel = new MethodChannel(messenger, MICROPHONE_METHOD_CHANNEL_NAME);
methodChannel.setMethodCallHandler(this);
}

Expand All @@ -73,12 +64,11 @@ private void registerWith(BinaryMessenger messenger) {
private volatile boolean record = false;
private volatile boolean isRecording = false;

// Method channel handlers to get sample rate / bit-depth
@Override
public void onMethodCall(MethodCall call, @NonNull Result result) {
switch (call.method) {
case "getSampleRate":
result.success((double) this.actualSampleRate); // cast to double just for compatibility with the iOS version
result.success((double) this.actualSampleRate);
break;
case "getBitDepth":
result.success(this.actualBitDepth);
Expand All @@ -94,13 +84,11 @@ public void onMethodCall(MethodCall call, @NonNull Result result) {

@SuppressLint("MissingPermission")
private void initRecorder() {
// Try to initialize and start the recorder
recorder = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE);
if (recorder.getState() != AudioRecord.STATE_INITIALIZED) {
eventSink.error("-1", "PlatformError", null);
return;
}

recorder.startRecording();
}

Expand All @@ -126,113 +114,90 @@ public void run() {
break;
}

// Wait until recorder is initialised
while (recorder == null || recorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING);

// Allocate a new buffer to write data to
ByteBuffer data = ByteBuffer.allocateDirect(BUFFER_SIZE);

// Set ByteOrder to native
ByteOrder nativeOrder = ByteOrder.nativeOrder();
data.order(nativeOrder);
System.out.println("mic_stream: Using native byte order " + nativeOrder);

// Repeatedly push audio samples to stream
while (record) {
// Read audio data into buffer
recorder.read(data, BUFFER_SIZE, AudioRecord.READ_BLOCKING);

// push data into stream
try {
eventSink.success(data.array());
} catch (IllegalArgumentException e) {
System.out.println("mic_stream: " + data + " is not valid!");
eventSink.error("-1", "Invalid Data", e);
}
data.clear();
}
isRecording = false;
}
};

/// Bug fix by https://github.com/Lokhozt
/// following https://github.com/flutter/flutter/issues/34993
private static class MainThreadEventSink implements EventChannel.EventSink {
private final EventChannel.EventSink eventSink;
private final Handler handler;

MainThreadEventSink(EventChannel.EventSink eventSink) {
this.eventSink = eventSink;
handler = new Handler(Looper.getMainLooper());
this.eventSink = eventSink;
handler = new Handler(Looper.getMainLooper());
}

@Override
public void success(final Object o) {
handler.post(new Runnable() {
@Override
public void run() {
eventSink.success(o);
}
});
handler.post(() -> eventSink.success(o));
}

@Override
public void error(final String s, final String s1, final Object o) {
handler.post(new Runnable() {
@Override
public void run() {
eventSink.error(s, s1, o);
}
});
handler.post(() -> eventSink.error(s, s1, o));
}

@Override
public void endOfStream() {
handler.post(new Runnable() {
@Override
public void run() {
eventSink.endOfStream();
}
});
handler.post(() -> eventSink.endOfStream());
}
}
/// End

@Override
public void onListen(Object args, final EventChannel.EventSink eventSink) {
if (isRecording) return;

// Read and validate AudioRecord parameters
if (!(args instanceof ArrayList)) {
eventSink.error("-4", "Invalid parameter type. Expected ArrayList.", null);
return;
}

@SuppressWarnings("unchecked")
ArrayList<Integer> config = (ArrayList<Integer>) args;

try {
AUDIO_SOURCE = config.get(0);
SAMPLE_RATE = config.get(1);
CHANNEL_CONFIG = config.get(2);
AUDIO_FORMAT = config.get(3);
BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
} catch (java.lang.IndexOutOfBoundsException e) {
eventSink.error("-4", "Invalid number of parameteres. Expected 4, got " + config.size(), e);
} catch (IndexOutOfBoundsException e) {
eventSink.error("-4", "Invalid number of parameters. Expected 4, got " + config.size(), e);
} catch (Exception e) {
eventSink.error("-3", "Invalid AudioRecord parameters", e);
}

this.eventSink = new MainThreadEventSink(eventSink);

// Start runnable
record = true;
new Thread(runnable).start();
}

@Override
public void onCancel(Object o) {
// Stop runnable
record = false;
while (isRecording);
if(recorder != null) {
// Stop and reset audio recorder
if (recorder != null) {
recorder.stop();
recorder.release();
}
recorder = null;
}
}

}