package eu.hgross.blaubotcam.audio;
import android.content.Context;
import android.media.MediaRecorder;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import eu.hgross.blaubot.core.IBlaubotDevice;
import eu.hgross.blaubot.messaging.BlaubotMessage;
import eu.hgross.blaubot.messaging.IBlaubotChannel;
import eu.hgross.blaubot.messaging.IBlaubotMessageListener;
import eu.hgross.blaubot.util.Log;
/**
* Uses the microphone to record and send data through a BlaubotChannel.
*/
public class BlaubotWalkieTalkie {
private static final String LOG_TAG = BlaubotWalkieTalkie.class.getSimpleName();
private final IBlaubotDevice mOwnDevice;
private final IBlaubotChannel mBlaubotChannel;
private final Context mContext;
private MediaRecorder mMediaRecorder;
private Object mRecorderLock = new Object();
private CopyOnWriteArrayList<IRecordingListener> mRecordingListeners;
private CopyOnWriteArrayList<IPlaybackListener> mPlaybackListeners;
private ExecutorService mExecutorService = Executors.newCachedThreadPool();
private boolean mReceiveOwnMessages;
public BlaubotWalkieTalkie(Context context, IBlaubotChannel channel, IBlaubotDevice ownDevice) {
this.mContext = context;
this.mOwnDevice = ownDevice;
this.mBlaubotChannel = channel;
this.mRecordingListeners = new CopyOnWriteArrayList<>();
this.mPlaybackListeners = new CopyOnWriteArrayList<>();
this.mReceiveOwnMessages = false;
channel.subscribe();
channel.addMessageListener(messageListener);
}
/**
* Receives audio data via a Blaubot channel
*/
private final IBlaubotMessageListener messageListener = new IBlaubotMessageListener() {
@Override
public void onMessage(final BlaubotMessage blaubotMessage) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
WalkieTalkieMessage walkieTalkieMessage = WalkieTalkieMessage.fromBytes(blaubotMessage.getPayload());
Log.d(LOG_TAG, "Received " + blaubotMessage.getPayload().length + " bytes");
// play
try {
notifyPlaybackStarted(walkieTalkieMessage);
walkieTalkieMessage.play(mContext);
} catch (IOException e) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Could not play received audio data: " + e.getMessage(), e);
}
}
notifyPlaybackFinished(walkieTalkieMessage);
}
});
}
};
private File mRecordFile;
/**
* Starts recording
*/
public void startRecording() {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "startRecording()");
}
synchronized (mRecorderLock) {
if (mRecordFile != null) {
return;
}
try {
mRecordFile = File.createTempFile("BlaubotTempAudioRecording", "3gp", mContext.getCacheDir());
} catch (IOException e) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Could not create TempFile: " + e.getMessage(), e);
}
return;
}
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setAudioChannels(1);
mMediaRecorder.setAudioEncodingBitRate(48000);
mMediaRecorder.setOutputFile(mRecordFile.getAbsolutePath());
try {
mMediaRecorder.prepare();
} catch (IOException e) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "prepare() failed: " + e.getMessage());
}
}
mMediaRecorder.start();
}
notifyRecordingStarted();
}
/**
* Stops recording (if any)
*/
public void stopRecording() {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "stopRecording()");
}
synchronized (mRecorderLock) {
if (mMediaRecorder == null) {
return;
}
try {
mMediaRecorder.stop();
mMediaRecorder.reset();
} catch (RuntimeException e) {
// we have to catch it, see stop() JavaDoc
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Could not stop or reset media recorder: " + e.getMessage(), e);
}
mMediaRecorder = null;
}
final String filePath = mRecordFile.getAbsolutePath();
onRecordingStopped(filePath);
mRecordFile.delete();
mRecordFile = null;
}
notifyRecordingStopped();
}
/**
* Releases all resources
*/
public void release() {
synchronized (mRecorderLock) {
try {
if (mMediaRecorder != null) {
mMediaRecorder.stop();
}
} catch (RuntimeException e) {
// dont care
}
if (mRecordFile != null) {
mRecordFile.delete();
}
try {
if (mMediaRecorder != null) {
mMediaRecorder.release();
}
} catch (RuntimeException e) {
// dont care
}
}
}
/**
* Called whenever a audio recording was made
*
* @param dataFile the path of the file holding the audio data
*/
private void onRecordingStopped(String dataFile) {
// get the bytes
try {
byte[] audioBytes = IOUtils.toByteArray(new FileInputStream(new File(dataFile)));
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Recorded " + audioBytes.length + " bytes");
}
WalkieTalkieMessage walkieTalkieMessage = new WalkieTalkieMessage(audioBytes, mOwnDevice.getUniqueDeviceID());
mBlaubotChannel.publish(walkieTalkieMessage.toBytes(), !mReceiveOwnMessages);
} catch (IOException e) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Could not read bytes from file: " + e.getMessage(), e);
}
}
}
/**
* Set if the walkie talkie should receiv it's own messages (audio sent by this instance)
*
* @param receiveOwnMessages if true, messages sent by this walkie talkie will also be received by this walkie talkie
*/
public void setReceiveOwnMessages(boolean receiveOwnMessages) {
this.mReceiveOwnMessages = receiveOwnMessages;
}
/**
* Returns the receive own messages flag.
*
* @return if true, messages sent by this walkie talkie will also be received by this walkie talkie
*/
public boolean isReceiveOwnMessages() {
return mReceiveOwnMessages;
}
/*
Listener implementation from here
*/
/**
* Notifies the listeners, that the recording started
*/
private void notifyRecordingStarted() {
for (IRecordingListener listener : mRecordingListeners) {
listener.onRecordingStarted();
}
}
/**
* Notifies the listeners, that the recording stopped
*/
private void notifyRecordingStopped() {
for (IRecordingListener listener : mRecordingListeners) {
listener.onRecordingStopped();
}
}
/**
* Adds a recording listener to this walkietalkie
*
* @param recordingListener the listener
*/
public void addRecordingListener(IRecordingListener recordingListener) {
mRecordingListeners.add(recordingListener);
}
/**
* Removes a recording listener from this walkietalkie
*
* @param recordingListener the listener to be removed
*/
public void removeRecordingListener(IRecordingListener recordingListener) {
mRecordingListeners.remove(recordingListener);
}
/**
* Notifies the listeners, that the plaback started
*
* @param msg the message
*/
private void notifyPlaybackStarted(WalkieTalkieMessage msg) {
for (IPlaybackListener listener : mPlaybackListeners) {
listener.beforePlayback(msg);
}
}
/**
* Notifies the listeners, that the playback has finished started
*
* @param msg the message
*/
private void notifyPlaybackFinished(WalkieTalkieMessage msg) {
for (IPlaybackListener listener : mPlaybackListeners) {
listener.afterPlayback(msg);
}
}
/**
* Adds a playback listener to this walkietalkie
*
* @param playbackListener the listener
*/
public void addPlaybackListener(IPlaybackListener playbackListener) {
mPlaybackListeners.add(playbackListener);
}
/**
* Removes a playback listener from this walkietalkie
*
* @param playbackListener the listener to be removed
*/
public void removePlaybackListener(IPlaybackListener playbackListener) {
mPlaybackListeners.remove(playbackListener);
}
}