diff --git a/README.md b/README.md index bc0bd9c..ad8cdad 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,8 @@ The current version of _Protect Baby Monitor_ is rudimentary at best. It is capa of successfully advertising itself on the network, allows clients to connect, and streams audio. Room for improvement includes: -1. Decent UI -2. Hook into audio controls to adjust volume -3. Robust usage of the AudioTrack API -4. Handle dropped packets gracefully +1. Robust usage of the AudioTrack API +2. Handle dropped packets gracefully At the time this project was started there was no obvious open source solution for a baby monitor for Android. There are both free and paid options available for Android, @@ -36,3 +34,5 @@ proposed changed. App icon originals from [WPZOOM](http://www.wpzoom.com/wpzoom/new-freebie-wpzoom-developer-icon-set-154-free-icons) and formatted using [Android Asset Studio](https://romannurik.github.io/AndroidAssetStudio/index.html). + +Audio file originals from [freesound](https://freesound.org). diff --git a/res/raw/upward_beep_chromatic_fifths.ogg b/res/raw/upward_beep_chromatic_fifths.ogg new file mode 100644 index 0000000..bcc41e8 Binary files /dev/null and b/res/raw/upward_beep_chromatic_fifths.ogg differ diff --git a/src/protect/babymonitor/DiscoverActivity.java b/src/protect/babymonitor/DiscoverActivity.java index e4cb665..e625785 100644 --- a/src/protect/babymonitor/DiscoverActivity.java +++ b/src/protect/babymonitor/DiscoverActivity.java @@ -27,7 +27,6 @@ import android.view.View; import android.widget.Button; import android.widget.TableLayout; import android.widget.TableRow; -import android.widget.TextView; public class DiscoverActivity extends Activity { @@ -99,13 +98,13 @@ public class DiscoverActivity extends Activity public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { // Called when the resolve fails. Use the error code to debug. - Log.e(TAG, "Resolve failed" + errorCode); + Log.e(TAG, "Resolve failed: error " + errorCode + " for service: " + serviceInfo); } @Override public void onServiceResolved(final NsdServiceInfo serviceInfo) { - Log.e(TAG, "Resolve Succeeded. " + serviceInfo); + Log.i(TAG, "Resolve Succeeded: " + serviceInfo); DiscoverActivity.this.runOnUiThread(new Runnable() { @@ -159,7 +158,7 @@ public class DiscoverActivity extends Activity { // When the network service is no longer available. // Internal bookkeeping code goes here. - Log.e(TAG, "service lost" + service); + Log.e(TAG, "Service lost: " + service); } @Override @@ -171,14 +170,14 @@ public class DiscoverActivity extends Activity @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { - Log.e(TAG, "Discovery failed: Error code:" + errorCode); + Log.e(TAG, "Discovery failed: Error code: " + errorCode); nsdManager.stopServiceDiscovery(this); } @Override public void onStopDiscoveryFailed(String serviceType, int errorCode) { - Log.e(TAG, "Discovery failed: Error code:" + errorCode); + Log.e(TAG, "Discovery failed: Error code: " + errorCode); nsdManager.stopServiceDiscovery(this); } }; diff --git a/src/protect/babymonitor/ListenActivity.java b/src/protect/babymonitor/ListenActivity.java index 0e1b66e..d0f1795 100644 --- a/src/protect/babymonitor/ListenActivity.java +++ b/src/protect/babymonitor/ListenActivity.java @@ -28,6 +28,7 @@ import android.app.Activity; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; +import android.media.MediaPlayer; import android.os.Bundle; import android.util.Log; import android.widget.TextView; @@ -139,18 +140,27 @@ public class ListenActivity extends Activity Log.e(TAG, "Failed to stream audio", e); } - ListenActivity.this.runOnUiThread(new Runnable() + if(Thread.currentThread().isInterrupted() == false) { - @Override - public void run() - { - final TextView connectedText = (TextView) findViewById(R.id.connectedTo); - connectedText.setText(""); + // If this thread has not been interrupted, likely something + // bad happened with the connection to the child device. Play + // an alert to notify the user that the connection has been + // interrupted. + playAlert(); - final TextView statusText = (TextView) findViewById(R.id.textStatus); - statusText.setText(R.string.disconnected); - } - }); + ListenActivity.this.runOnUiThread(new Runnable() + { + @Override + public void run() + { + final TextView connectedText = (TextView) findViewById(R.id.connectedTo); + connectedText.setText(""); + + final TextView statusText = (TextView) findViewById(R.id.textStatus); + statusText.setText(R.string.disconnected); + } + }); + } } }); @@ -165,4 +175,26 @@ public class ListenActivity extends Activity super.onDestroy(); } + + private void playAlert() + { + final MediaPlayer mp = MediaPlayer.create(this, R.raw.upward_beep_chromatic_fifths); + if(mp != null) + { + Log.i(TAG, "Playing alert"); + mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() + { + @Override + public void onCompletion(MediaPlayer mp) + { + mp.release(); + } + }); + mp.start(); + } + else + { + Log.e(TAG, "Failed to play alert"); + } + } } diff --git a/src/protect/babymonitor/MonitorActivity.java b/src/protect/babymonitor/MonitorActivity.java index ba41b1f..52895bb 100644 --- a/src/protect/babymonitor/MonitorActivity.java +++ b/src/protect/babymonitor/MonitorActivity.java @@ -42,7 +42,6 @@ public class MonitorActivity extends Activity NsdManager.RegistrationListener _registrationListener; - ServerSocket _serverSocket; Thread _serviceThread; private void serviceConnection(Socket socket) throws IOException @@ -67,21 +66,26 @@ public class MonitorActivity extends Activity audioEncoding, bufferSize); byte[] buffer = new byte[bufferSize*2]; - audioRecord.startRecording(); - OutputStream out = socket.getOutputStream(); - - socket.setSendBufferSize(bufferSize); - Log.d(TAG, "Socket send buffer size: " + socket.getSendBufferSize()); - - while (socket.isConnected() && Thread.currentThread().isInterrupted() == false) + try { - int read = audioRecord.read(buffer, 0, bufferSize); - out.write(buffer, 0, read); - } + audioRecord.startRecording(); - socket.close(); - audioRecord.stop(); + OutputStream out = socket.getOutputStream(); + + socket.setSendBufferSize(bufferSize); + Log.d(TAG, "Socket send buffer size: " + socket.getSendBufferSize()); + + while (socket.isConnected() && Thread.currentThread().isInterrupted() == false) + { + int read = audioRecord.read(buffer, 0, bufferSize); + out.write(buffer, 0, read); + } + } + finally + { + audioRecord.stop(); + } } @Override @@ -94,57 +98,70 @@ public class MonitorActivity extends Activity super.onCreate(savedInstanceState); setContentView(R.layout.activity_monitor); - try + _serviceThread = new Thread(new Runnable() { - // Initialize a server socket on the next available port. - _serverSocket = new ServerSocket(0); - - // Store the chosen port. - int localPort = _serverSocket.getLocalPort(); - - registerService(localPort); - - _serviceThread = new Thread(new Runnable() + @Override + public void run() { - @Override - public void run() + while(Thread.currentThread().isInterrupted() == false) { - try - { - Socket socket = _serverSocket.accept(); - serviceConnection(socket); - } - catch (IOException e) - { - Log.e(TAG, "Failed when serving connection", e); - } + ServerSocket serverSocket = null; try { - _serverSocket.close(); - } - catch (IOException e) - { + // Initialize a server socket on the next available port. + serverSocket = new ServerSocket(0); - } + // Store the chosen port. + int localPort = serverSocket.getLocalPort(); - MonitorActivity.this.runOnUiThread(new Runnable() - { - @Override - public void run() + // Register the service so that parent devices can + // locate the child device + registerService(localPort); + + // Wait for a parent to find us and connect + Socket socket = serverSocket.accept(); + Log.i(TAG, "Connection from parent device received"); + + // We now have a client connection. + // Unregister so no other clients will + // attempt to connect + serverSocket.close(); + serverSocket = null; + unregisterService(); + + try { - final TextView statusText = (TextView) findViewById(R.id.textStatus); - statusText.setText(R.string.stopped); + serviceConnection(socket); } - }); + finally + { + socket.close(); + } + } + catch(IOException e) + { + Log.e(TAG, "Connection failed", e); + } + + // If an exception was thrown before the connection + // could be closed, clean it up + if(serverSocket != null) + { + try + { + serverSocket.close(); + } + catch (IOException e) + { + Log.e(TAG, "Failed to close stray connection", e); + } + serverSocket = null; + } } - }); - _serviceThread.start(); - } - catch (IOException e) - { - Log.e(TAG, "Failed to create server socket", e); - } + } + }); + _serviceThread.start(); } @Override @@ -152,13 +169,7 @@ public class MonitorActivity extends Activity { Log.i(TAG, "Baby monitor stop"); - if(_registrationListener != null) - { - Log.i(TAG, "Unregistering monitoring service"); - - _nsdManager.unregisterService(_registrationListener); - _registrationListener = null; - } + unregisterService(); if(_serviceThread != null) { @@ -190,7 +201,7 @@ public class MonitorActivity extends Activity return super.onOptionsItemSelected(item); } - public void registerService(int port) + private void registerService(int port) { NsdServiceInfo serviceInfo = new NsdServiceInfo(); serviceInfo.setServiceName("ProtectBabyMonitor"); @@ -249,4 +260,19 @@ public class MonitorActivity extends Activity _nsdManager.registerService( serviceInfo, NsdManager.PROTOCOL_DNS_SD, _registrationListener); } + + /** + * Uhregistered the service and assigns the listener + * to null. + */ + private void unregisterService() + { + if(_registrationListener != null) + { + Log.i(TAG, "Unregistering monitoring service"); + + _nsdManager.unregisterService(_registrationListener); + _registrationListener = null; + } + } }