From 4d5849f448685673fa2d21302ce1a33e0174ed1f Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 23 Nov 2013 01:03:37 -0500 Subject: [PATCH] Add UI indications of the connection status and failure --- src/com/limelight/Connection.java | 34 ++-- src/com/limelight/Game.java | 55 +++++- src/com/limelight/nvstream/NvAudioStream.java | 76 ++++---- src/com/limelight/nvstream/NvComputer.java | 4 +- src/com/limelight/nvstream/NvConnection.java | 181 +++++++++++------- .../nvstream/NvConnectionListener.java | 30 +++ src/com/limelight/nvstream/NvControl.java | 37 ++-- src/com/limelight/nvstream/NvHTTP.java | 18 +- src/com/limelight/nvstream/NvHandshake.java | 72 ++++--- src/com/limelight/nvstream/NvVideoStream.java | 126 ++++++------ .../nvstream/input/NvController.java | 16 +- src/com/limelight/utils/Dialog.java | 66 +++++++ src/com/limelight/utils/SpinnerDialog.java | 98 ++++++++++ 13 files changed, 569 insertions(+), 244 deletions(-) create mode 100644 src/com/limelight/nvstream/NvConnectionListener.java create mode 100644 src/com/limelight/utils/Dialog.java create mode 100644 src/com/limelight/utils/SpinnerDialog.java diff --git a/src/com/limelight/Connection.java b/src/com/limelight/Connection.java index 03544467..da6c333a 100644 --- a/src/com/limelight/Connection.java +++ b/src/com/limelight/Connection.java @@ -2,7 +2,9 @@ package com.limelight; import java.io.IOException; +import java.net.InetAddress; import java.net.SocketException; +import java.net.UnknownHostException; import org.xmlpull.v1.XmlPullParserException; @@ -88,26 +90,30 @@ public class Connection extends Activity { return; } - NvHTTP httpConn = new NvHTTP(hostText.getText().toString(), macAddress); - + NvHTTP httpConn; String message; try { - if (httpConn.getPairState()) { - message = "Already paired"; - } - else { - int session = httpConn.getSessionId(); - if (session == 0) { - message = "Pairing was declined by the target"; + httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()), macAddress); + try { + if (httpConn.getPairState()) { + message = "Already paired"; } else { - message = "Pairing was successful"; + int session = httpConn.getSessionId(); + if (session == 0) { + message = "Pairing was declined by the target"; + } + else { + message = "Pairing was successful"; + } } + } catch (IOException e) { + message = e.getMessage(); + } catch (XmlPullParserException e) { + message = e.getMessage(); } - } catch (IOException e) { - message = e.getMessage(); - } catch (XmlPullParserException e) { - message = e.getMessage(); + } catch (UnknownHostException e1) { + message = "Failed to resolve host"; } final String toastMessage = message; diff --git a/src/com/limelight/Game.java b/src/com/limelight/Game.java index 4ec667db..11ff051d 100644 --- a/src/com/limelight/Game.java +++ b/src/com/limelight/Game.java @@ -1,7 +1,10 @@ package com.limelight; import com.limelight.nvstream.NvConnection; +import com.limelight.nvstream.NvConnectionListener; import com.limelight.nvstream.input.NvControllerPacket; +import com.limelight.utils.Dialog; +import com.limelight.utils.SpinnerDialog; import android.app.Activity; import android.graphics.PixelFormat; @@ -18,7 +21,7 @@ import android.view.Window; import android.view.WindowManager; -public class Game extends Activity implements OnGenericMotionListener, OnTouchListener { +public class Game extends Activity implements OnGenericMotionListener, OnTouchListener, NvConnectionListener { private short inputMap = 0x0000; private byte leftTrigger = 0x00; private byte rightTrigger = 0x00; @@ -33,6 +36,8 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi private boolean hasMoved = false; private NvConnection conn; + private SpinnerDialog spinner; + private boolean displayedFailureDialog = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -59,6 +64,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi sh.setFixedSize(1280, 720); sh.setFormat(PixelFormat.RGBX_8888); + // Start the spinner + spinner = SpinnerDialog.displayDialog(this, "Establishing Connection", "Starting connection", true); + // Start the connection conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder().getSurface()); conn.start(); @@ -94,6 +102,13 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi super.onPause(); } + @Override + protected void onDestroy() { + SpinnerDialog.closeDialogs(); + Dialog.closeDialogs(); + super.onDestroy(); + } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -439,4 +454,42 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi // Send it to the activity's touch event handler return onTouchEvent(event); } + + @Override + public void stageStarting(Stage stage) { + if (spinner != null) { + spinner.setMessage("Starting "+stage.getName()); + } + } + + @Override + public void stageComplete(Stage stage) { + } + + @Override + public void stageFailed(Stage stage) { + spinner.dismiss(); + spinner = null; + + if (!displayedFailureDialog) { + displayedFailureDialog = true; + Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true); + conn.stop(); + } + } + + @Override + public void connectionTerminated() { + if (!displayedFailureDialog) { + displayedFailureDialog = true; + Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true); + conn.stop(); + } + } + + @Override + public void connectionStarted() { + spinner.dismiss(); + spinner = null; + } } diff --git a/src/com/limelight/nvstream/NvAudioStream.java b/src/com/limelight/nvstream/NvAudioStream.java index efd1ecc9..413285e1 100644 --- a/src/com/limelight/nvstream/NvAudioStream.java +++ b/src/com/limelight/nvstream/NvAudioStream.java @@ -5,7 +5,6 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; -import java.net.UnknownHostException; import java.util.LinkedList; import java.util.concurrent.LinkedBlockingQueue; @@ -35,6 +34,15 @@ public class NvAudioStream { private boolean aborting = false; + private InetAddress host; + private NvConnectionListener listener; + + public NvAudioStream(InetAddress host, NvConnectionListener listener) + { + this.host = host; + this.listener = listener; + } + public void abort() { if (aborting) { @@ -66,40 +74,25 @@ public class NvAudioStream { threads.clear(); } - public void startAudioStream(final String host) - { - new Thread(new Runnable() { - - @Override - public void run() { - try { - setupRtpSession(host); - } catch (SocketException e) { - e.printStackTrace(); - return; - } catch (UnknownHostException e) { - e.printStackTrace(); - return; - } - - setupAudio(); - - startReceiveThread(); - - startDepacketizerThread(); - - startDecoderThread(); - - startUdpPingThread(); - } - - }).start(); + public void startAudioStream() throws SocketException + { + setupRtpSession(); + + setupAudio(); + + startReceiveThread(); + + startDepacketizerThread(); + + startDecoderThread(); + + startUdpPingThread(); } - private void setupRtpSession(String host) throws SocketException, UnknownHostException + private void setupRtpSession() throws SocketException { rtp = new DatagramSocket(RTP_PORT); - rtp.connect(InetAddress.getByName(host), RTP_PORT); + rtp.connect(host, RTP_PORT); } private void setupAudio() @@ -108,12 +101,8 @@ public class NvAudioStream { int err; err = OpusDecoder.init(); - if (err == 0) { - System.out.println("Opus decoder initialized"); - } - else { - System.err.println("Opus decoder init failed: "+err); - return; + if (err != 0) { + throw new IllegalStateException("Opus decoder failed to initialize"); } switch (OpusDecoder.getChannelCount()) @@ -125,8 +114,7 @@ public class NvAudioStream { channelConfig = AudioFormat.CHANNEL_OUT_STEREO; break; default: - System.err.println("Unsupported channel count"); - return; + throw new IllegalStateException("Opus decoder returned unhandled channel count"); } track = new AudioTrack(AudioManager.STREAM_MUSIC, @@ -153,7 +141,7 @@ public class NvAudioStream { try { packet = packets.take(); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } @@ -178,7 +166,7 @@ public class NvAudioStream { try { samples = depacketizer.getNextDecodedData(); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } @@ -204,7 +192,7 @@ public class NvAudioStream { try { rtp.receive(packet); } catch (IOException e) { - abort(); + listener.connectionTerminated(); return; } @@ -240,14 +228,14 @@ public class NvAudioStream { try { rtp.send(pingPacket); } catch (IOException e) { - abort(); + listener.connectionTerminated(); return; } try { Thread.sleep(100); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } } diff --git a/src/com/limelight/nvstream/NvComputer.java b/src/com/limelight/nvstream/NvComputer.java index 83ae34f0..3fb6d57f 100644 --- a/src/com/limelight/nvstream/NvComputer.java +++ b/src/com/limelight/nvstream/NvComputer.java @@ -38,10 +38,10 @@ public class NvComputer { this.uniqueID = uniqueID; try { - this.nvHTTP = new NvHTTP(this.ipAddressString, NvConnection.getMacAddressString()); + this.nvHTTP = new NvHTTP(this.ipAddress, NvConnection.getMacAddressString()); } catch (SocketException e) { Log.e("NvComputer Constructor", "Unable to get MAC Address " + e.getMessage()); - this.nvHTTP = new NvHTTP(this.ipAddressString, "00:00:00:00:00:00"); + this.nvHTTP = new NvHTTP(this.ipAddress, "00:00:00:00:00:00"); } this.updatePairState(); diff --git a/src/com/limelight/nvstream/NvConnection.java b/src/com/limelight/nvstream/NvConnection.java index 1b1f6a43..ac791e31 100644 --- a/src/com/limelight/nvstream/NvConnection.java +++ b/src/com/limelight/nvstream/NvConnection.java @@ -23,18 +23,21 @@ import com.limelight.nvstream.input.NvController; public class NvConnection { private String host; private Game activity; + private NvConnectionListener listener; + private InetAddress hostAddr; private NvControl controlStream; private NvController inputStream; private Surface video; private NvVideoStream videoStream; - private NvAudioStream audioStream = new NvAudioStream(); + private NvAudioStream audioStream; private ThreadPoolExecutor threadPool; public NvConnection(String host, Game activity, Surface video) { this.host = host; + this.listener = activity; this.activity = activity; this.video = video; this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue()); @@ -116,6 +119,106 @@ public class NvConnection { inputStream = null; } } + + private boolean startSteamBigPicture() throws XmlPullParserException, IOException + { + NvHTTP h = new NvHTTP(hostAddr, getMacAddressString()); + + if (!h.getPairState()) { + displayToast("Device not paired with computer"); + return false; + } + + int sessionId = h.getSessionId(); + int appId = h.getSteamAppId(sessionId); + + h.launchApp(sessionId, appId); + + return true; + } + + private boolean startControlStream() throws IOException + { + controlStream = new NvControl(hostAddr, listener); + controlStream.initialize(); + controlStream.start(); + return true; + } + + private boolean startVideoStream() throws IOException + { + videoStream = new NvVideoStream(hostAddr, listener, controlStream); + videoStream.startVideoStream(video); + return true; + } + + private boolean startAudioStream() throws IOException + { + audioStream = new NvAudioStream(hostAddr, listener); + audioStream.startAudioStream(); + return true; + } + + private boolean startInputConnection() throws IOException + { + inputStream = new NvController(hostAddr); + inputStream.initialize(); + return true; + } + + private void establishConnection() { + for (NvConnectionListener.Stage currentStage : NvConnectionListener.Stage.values()) + { + boolean success = false; + + listener.stageStarting(currentStage); + try { + switch (currentStage) + { + case LAUNCH_APP: + success = startSteamBigPicture(); + break; + + case HANDSHAKE: + success = NvHandshake.performHandshake(hostAddr); + break; + + case CONTROL_START: + success = startControlStream(); + break; + + case VIDEO_START: + success = startVideoStream(); + break; + + case AUDIO_START: + success = startAudioStream(); + break; + + case CONTROL_START2: + controlStream.startJitterPackets(); + success = true; + break; + + case INPUT_START: + success = startInputConnection(); + break; + } + } catch (Exception e) { + success = false; + } + + if (success) { + listener.stageComplete(currentStage); + } + else { + listener.stageFailed(currentStage); + return; + } + } + + listener.connectionStarted(); + } public void start() { @@ -125,31 +228,16 @@ public class NvConnection { checkDataConnection(); try { - host = InetAddress.getByName(host).getHostAddress(); + hostAddr = InetAddress.getByName(host); } catch (UnknownHostException e) { - e.printStackTrace(); displayToast(e.getMessage()); + listener.connectionTerminated(); return; } - try { - startSteamBigPicture(); - performHandshake(); - beginControlStream(); - videoStream = new NvVideoStream(controlStream); - videoStream.startVideoStream(host, video); - audioStream.startAudioStream(host); - controlStream.startJitterPackets(); - startController(); - activity.hideSystemUi(); - } catch (XmlPullParserException e) { - e.printStackTrace(); - displayToast(e.getMessage()); - stop(); - } catch (IOException e) { - displayToast(e.getMessage()); - stop(); - } + establishConnection(); + + activity.hideSystemUi(); } }).start(); } @@ -173,8 +261,7 @@ public class NvConnection { try { inputStream.sendMouseMove(deltaX, deltaY); } catch (IOException e) { - displayToast(e.getMessage()); - NvConnection.this.stop(); + listener.connectionTerminated(); } } }); @@ -191,8 +278,7 @@ public class NvConnection { try { inputStream.sendMouseButtonDown(); } catch (IOException e) { - displayToast(e.getMessage()); - NvConnection.this.stop(); + listener.connectionTerminated(); } } }); @@ -209,8 +295,7 @@ public class NvConnection { try { inputStream.sendMouseButtonUp(); } catch (IOException e) { - displayToast(e.getMessage()); - NvConnection.this.stop(); + listener.connectionTerminated(); } } }); @@ -232,8 +317,7 @@ public class NvConnection { rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY); } catch (IOException e) { - displayToast(e.getMessage()); - NvConnection.this.stop(); + listener.connectionTerminated(); } } }); @@ -248,43 +332,4 @@ public class NvConnection { } }); } - - private void startSteamBigPicture() throws XmlPullParserException, IOException - { - NvHTTP h = new NvHTTP(host, getMacAddressString()); - - if (!h.getPairState()) - { - displayToast("Device not paired with computer"); - return; - } - - int sessionId = h.getSessionId(); - int appId = h.getSteamAppId(sessionId); - - System.out.println("Starting game session"); - int gameSession = h.launchApp(sessionId, appId); - System.out.println("Started game session: "+gameSession); - } - - private void performHandshake() throws UnknownHostException, IOException - { - System.out.println("Starting handshake"); - NvHandshake.performHandshake(host); - System.out.println("Handshake complete"); - } - - private void beginControlStream() throws UnknownHostException, IOException - { - controlStream = new NvControl(host); - - System.out.println("Starting control"); - controlStream.start(); - } - - private void startController() throws UnknownHostException, IOException - { - System.out.println("Starting input"); - inputStream = new NvController(host); - } } diff --git a/src/com/limelight/nvstream/NvConnectionListener.java b/src/com/limelight/nvstream/NvConnectionListener.java new file mode 100644 index 00000000..7b7085ab --- /dev/null +++ b/src/com/limelight/nvstream/NvConnectionListener.java @@ -0,0 +1,30 @@ +package com.limelight.nvstream; + +public interface NvConnectionListener { + + public enum Stage { + LAUNCH_APP("app"), + HANDSHAKE("handshake"), + CONTROL_START("control connection"), + VIDEO_START("video stream"), + AUDIO_START("audio stream"), + CONTROL_START2("control connection"), + INPUT_START("input connection"); + + private String name; + private Stage(String name) { + this.name = name; + } + + public String getName() { + return name; + } + }; + + public void stageStarting(Stage stage); + public void stageComplete(Stage stage); + public void stageFailed(Stage stage); + + public void connectionStarted(); + public void connectionTerminated(); +} diff --git a/src/com/limelight/nvstream/NvControl.java b/src/com/limelight/nvstream/NvControl.java index 4a4a4402..9cd38b69 100644 --- a/src/com/limelight/nvstream/NvControl.java +++ b/src/com/limelight/nvstream/NvControl.java @@ -3,8 +3,9 @@ package com.limelight.nvstream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Socket; -import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -14,6 +15,8 @@ public class NvControl implements ConnectionStatusListener { public static final int PORT = 47995; + public static final int CONTROL_TIMEOUT = 3000; + public static final short PTYPE_HELLO = 0x1204; public static final short PPAYLEN_HELLO = 0x0004; public static final byte[] PPAYLOAD_HELLO = @@ -140,6 +143,9 @@ public class NvControl implements ConnectionStatusListener { private int seqNum; + private NvConnectionListener listener; + private InetAddress host; + private Socket s; private InputStream in; private OutputStream out; @@ -148,20 +154,28 @@ public class NvControl implements ConnectionStatusListener { private Thread jitterThread; private boolean aborting = false; - public NvControl(String host) throws UnknownHostException, IOException + public NvControl(InetAddress host, NvConnectionListener listener) { - s = new Socket(host, PORT); + this.listener = listener; + this.host = host; + } + + public void initialize() throws IOException + { + s = new Socket(); + s.setSoTimeout(CONTROL_TIMEOUT); + s.connect(new InetSocketAddress(host, PORT), CONTROL_TIMEOUT); in = s.getInputStream(); out = s.getOutputStream(); } - public void sendPacket(NvCtlPacket packet) throws IOException + private void sendPacket(NvCtlPacket packet) throws IOException { out.write(packet.toWire()); out.flush(); } - public NvControl.NvCtlResponse sendAndGetReply(NvCtlPacket packet) throws IOException + private NvControl.NvCtlResponse sendAndGetReply(NvCtlPacket packet) throws IOException { sendPacket(packet); return new NvCtlResponse(in); @@ -208,15 +222,10 @@ public class NvControl implements ConnectionStatusListener { public void start() throws IOException { - System.out.println("CTL: Sending hello"); sendHello(); - System.out.println("CTL: Sending config"); sendConfig(); - System.out.println("CTL: Initial ping/pong"); pingPong(); - System.out.println("CTL: Sending and waiting for 1405"); send1405AndGetResponse(); - System.out.println("CTL: Launching heartbeat thread"); heartbeatThread = new Thread() { @Override @@ -226,7 +235,7 @@ public class NvControl implements ConnectionStatusListener { try { sendHeartbeat(); } catch (IOException e1) { - abort(); + listener.connectionTerminated(); return; } @@ -234,7 +243,7 @@ public class NvControl implements ConnectionStatusListener { try { Thread.sleep(3000); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } } @@ -253,14 +262,14 @@ public class NvControl implements ConnectionStatusListener { try { sendJitter(); } catch (IOException e1) { - abort(); + listener.connectionTerminated(); return; } try { Thread.sleep(100); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } } diff --git a/src/com/limelight/nvstream/NvHTTP.java b/src/com/limelight/nvstream/NvHTTP.java index 2dff7d14..c9fdd62b 100644 --- a/src/com/limelight/nvstream/NvHTTP.java +++ b/src/com/limelight/nvstream/NvHTTP.java @@ -3,7 +3,9 @@ package com.limelight.nvstream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.InetAddress; import java.net.URL; +import java.net.URLConnection; import java.util.LinkedList; import java.util.Stack; @@ -15,11 +17,16 @@ public class NvHTTP { private String macAddress; public static final int PORT = 47989; + + public static final int CONNECTION_TIMEOUT = 3000; + public static final int REQUEST_TIMEOUT = 15000; + + public String baseUrl; - public NvHTTP(String host, String macAddress) { + public NvHTTP(InetAddress host, String macAddress) { this.macAddress = macAddress; - this.baseUrl = "http://" + host + ":" + PORT; + this.baseUrl = "http://" + host.getHostAddress() + ":" + PORT; } private String getXmlString(InputStream in, String tagname) @@ -53,7 +60,12 @@ public class NvHTTP { } private InputStream openHttpConnection(String url) throws IOException { - return new URL(url).openConnection().getInputStream(); + URLConnection conn = new URL(url).openConnection(); + conn.setConnectTimeout(CONNECTION_TIMEOUT); + conn.setDefaultUseCaches(false); + conn.setReadTimeout(REQUEST_TIMEOUT); + conn.connect(); + return conn.getInputStream(); } public String getAppVersion() throws XmlPullParserException, IOException { diff --git a/src/com/limelight/nvstream/NvHandshake.java b/src/com/limelight/nvstream/NvHandshake.java index 510ec648..ae7b8d06 100644 --- a/src/com/limelight/nvstream/NvHandshake.java +++ b/src/com/limelight/nvstream/NvHandshake.java @@ -3,15 +3,23 @@ package com.limelight.nvstream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Socket; -import java.net.UnknownHostException;; public class NvHandshake { public static final int PORT = 47991; - // android + public static final int HANDSHAKE_TIMEOUT = 3000; + public static final byte[] PLATFORM_HELLO = { + (byte)0x07, + (byte)0x00, + (byte)0x00, + (byte)0x00, + + // android in ASCII (byte)0x61, (byte)0x6e, (byte)0x64, @@ -19,6 +27,7 @@ public class NvHandshake { (byte)0x6f, (byte)0x69, (byte)0x64, + (byte)0x03, (byte)0x01, (byte)0x00, @@ -56,46 +65,61 @@ public class NvHandshake { (byte)0x00 }; - private static void waitAndDiscardResponse(InputStream in) throws IOException + private static boolean waitAndDiscardResponse(InputStream in) { // Wait for response and discard response - in.read(); - try { - Thread.sleep(250); - } catch (InterruptedException e) { } - - for (int i = 0; i < in.available(); i++) in.read(); + + // Wait for the full response to come in + Thread.sleep(250); + + for (int i = 0; i < in.available(); i++) + in.read(); + + } catch (IOException e1) { + return false; + } catch (InterruptedException e) { + return false; + } + + return true; } - - public static void performHandshake(String host) throws UnknownHostException, IOException + + public static boolean performHandshake(InetAddress host) throws IOException { - Socket s = new Socket(host, PORT); + Socket s = new Socket(); + s.connect(new InetSocketAddress(host, PORT), HANDSHAKE_TIMEOUT); + s.setSoTimeout(HANDSHAKE_TIMEOUT); OutputStream out = s.getOutputStream(); InputStream in = s.getInputStream(); // First packet - out.write(new byte[]{0x07, 0x00, 0x00, 0x00}); out.write(PLATFORM_HELLO); + out.flush(); - System.out.println("HS: Waiting for hello response"); - - waitAndDiscardResponse(in); + if (!waitAndDiscardResponse(in)) { + s.close(); + return false; + } // Second packet out.write(PACKET_2); - - System.out.println("HS: Waiting stage 2 response"); - - waitAndDiscardResponse(in); + out.flush(); + + if (!waitAndDiscardResponse(in)) { + s.close(); + return false; + } // Third packet out.write(PACKET_3); + out.flush(); - System.out.println("HS: Waiting for stage 3 response"); - - waitAndDiscardResponse(in); + if (!waitAndDiscardResponse(in)) { + s.close(); + return false; + } // Fourth packet out.write(PACKET_4); @@ -103,5 +127,7 @@ public class NvHandshake { // Done s.close(); + + return true; } } diff --git a/src/com/limelight/nvstream/NvVideoStream.java b/src/com/limelight/nvstream/NvVideoStream.java index fa7b206b..7269ada4 100644 --- a/src/com/limelight/nvstream/NvVideoStream.java +++ b/src/com/limelight/nvstream/NvVideoStream.java @@ -5,9 +5,9 @@ import java.io.InputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; -import java.net.UnknownHostException; import java.util.LinkedList; import java.util.concurrent.LinkedBlockingQueue; @@ -29,13 +29,17 @@ public class NvVideoStream { public static final int RTCP_PORT = 47999; public static final int FIRST_FRAME_PORT = 47996; + public static final int FIRST_FRAME_TIMEOUT = 3000; + private LinkedBlockingQueue packets = new LinkedBlockingQueue(); + private InetAddress host; private DatagramSocket rtp; private Socket firstFrameSocket; private LinkedList threads = new LinkedList(); + private NvConnectionListener listener; private AvVideoDepacketizer depacketizer; private DecoderRenderer decrend; @@ -43,9 +47,11 @@ public class NvVideoStream { private boolean aborting = false; - public NvVideoStream(ConnectionStatusListener listener) + public NvVideoStream(InetAddress host, NvConnectionListener listener, ConnectionStatusListener avConnListener) { - depacketizer = new AvVideoDepacketizer(listener); + this.host = host; + this.listener = listener; + this.depacketizer = new AvVideoDepacketizer(avConnListener); } public void abort() @@ -89,14 +95,15 @@ public class NvVideoStream { threads.clear(); } - private void readFirstFrame(String host) throws IOException + private void readFirstFrame() throws IOException { byte[] firstFrame = new byte[1500]; - System.out.println("VID: Waiting for first frame"); - firstFrameSocket = new Socket(host, FIRST_FRAME_PORT); - + firstFrameSocket = new Socket(); + firstFrameSocket.setSoTimeout(FIRST_FRAME_TIMEOUT); + try { + firstFrameSocket.connect(new InetSocketAddress(host, FIRST_FRAME_PORT), FIRST_FRAME_TIMEOUT); InputStream firstFrameStream = firstFrameSocket.getInputStream(); int offset = 0; @@ -110,7 +117,6 @@ public class NvVideoStream { offset += bytesRead; } - System.out.println("VID: First frame read ("+offset+" bytes)"); depacketizer.addInputData(new AvVideoPacket(new AvByteBufferDescriptor(firstFrame, 0, offset))); } finally { firstFrameSocket.close(); @@ -118,13 +124,10 @@ public class NvVideoStream { } } - public void setupRtpSession(String host) throws SocketException, UnknownHostException + public void setupRtpSession() throws SocketException { rtp = new DatagramSocket(RTP_PORT); - rtp.connect(InetAddress.getByName(host), RTP_PORT); - rtp.setReceiveBufferSize(2097152); - System.out.println("RECV BUF: "+rtp.getReceiveBufferSize()); - System.out.println("SEND BUF: "+rtp.getSendBufferSize()); + rtp.connect(host, RTP_PORT); } public void setupDecoderRenderer(Surface renderTarget) { @@ -146,61 +149,40 @@ public class NvVideoStream { } } - public void startVideoStream(final String host, final Surface surface) + public void startVideoStream(final Surface surface) throws IOException { - // This thread becomes the output display thread - Thread t = new Thread() { - @Override - public void run() { - // Setup the decoder and renderer - setupDecoderRenderer(surface); - - // Open RTP sockets and start session - try { - setupRtpSession(host); - } catch (SocketException e) { - e.printStackTrace(); - return; - } catch (UnknownHostException e) { - e.printStackTrace(); - return; - } - - // Start pinging before reading the first frame - // so Shield Proxy knows we're here and sends us - // the reference frame - startUdpPingThread(); - - // Read the first frame to start the UDP video stream - // This MUST be called before the normal UDP receive thread - // starts in order to avoid state corruption caused by two - // threads simultaneously adding input data. - try { - readFirstFrame(host); - } catch (IOException e2) { - abort(); - return; - } - - if (decrend != null) { - // Start the receive thread early to avoid missing - // early packets - startReceiveThread(); - - // Start the depacketizer thread to deal with the RTP data - startDepacketizerThread(); - - // Start decoding the data we're receiving - startDecoderThread(); - - // Start the renderer - decrend.start(); - startedRendering = true; - } - } - }; - threads.add(t); - t.start(); + // Setup the decoder and renderer + setupDecoderRenderer(surface); + + // Open RTP sockets and start session + setupRtpSession(); + + // Start pinging before reading the first frame + // so Shield Proxy knows we're here and sends us + // the reference frame + startUdpPingThread(); + + // Read the first frame to start the UDP video stream + // This MUST be called before the normal UDP receive thread + // starts in order to avoid state corruption caused by two + // threads simultaneously adding input data. + readFirstFrame(); + + if (decrend != null) { + // Start the receive thread early to avoid missing + // early packets + startReceiveThread(); + + // Start the depacketizer thread to deal with the RTP data + startDepacketizerThread(); + + // Start decoding the data we're receiving + startDecoderThread(); + + // Start the renderer + decrend.start(); + startedRendering = true; + } } private void startDecoderThread() @@ -216,7 +198,7 @@ public class NvVideoStream { try { du = depacketizer.getNextDecodeUnit(); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } @@ -242,7 +224,7 @@ public class NvVideoStream { try { packet = packets.take(); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } @@ -269,7 +251,7 @@ public class NvVideoStream { try { rtp.receive(packet); } catch (IOException e) { - abort(); + listener.connectionTerminated(); return; } @@ -305,14 +287,14 @@ public class NvVideoStream { try { rtp.send(pingPacket); } catch (IOException e) { - abort(); + listener.connectionTerminated(); return; } try { Thread.sleep(100); } catch (InterruptedException e) { - abort(); + listener.connectionTerminated(); return; } } diff --git a/src/com/limelight/nvstream/input/NvController.java b/src/com/limelight/nvstream/input/NvController.java index f762f1da..b1b273e7 100644 --- a/src/com/limelight/nvstream/input/NvController.java +++ b/src/com/limelight/nvstream/input/NvController.java @@ -2,19 +2,29 @@ package com.limelight.nvstream.input; import java.io.IOException; import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Socket; -import java.net.UnknownHostException; public class NvController { public final static int PORT = 35043; + public final static int CONTROLLER_TIMEOUT = 3000; + + private InetAddress host; private Socket s; private OutputStream out; - public NvController(String host) throws UnknownHostException, IOException + public NvController(InetAddress host) { - s = new Socket(host, PORT); + this.host = host; + } + + public void initialize() throws IOException + { + s = new Socket(); + s.connect(new InetSocketAddress(host, PORT), CONTROLLER_TIMEOUT); s.setTcpNoDelay(true); out = s.getOutputStream(); } diff --git a/src/com/limelight/utils/Dialog.java b/src/com/limelight/utils/Dialog.java new file mode 100644 index 00000000..30821be3 --- /dev/null +++ b/src/com/limelight/utils/Dialog.java @@ -0,0 +1,66 @@ +package com.limelight.utils; + +import java.util.ArrayList; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; + +public class Dialog implements Runnable { + private String title, message; + private Activity activity; + private boolean endAfterDismiss; + + AlertDialog alert; + + private static ArrayList rundownDialogs = new ArrayList(); + + public Dialog(Activity activity, String title, String message, boolean endAfterDismiss) + { + this.activity = activity; + this.title = title; + this.message = message; + this.endAfterDismiss = endAfterDismiss; + } + + public static void closeDialogs() + { + for (Dialog d : rundownDialogs) + d.alert.dismiss(); + + rundownDialogs.clear(); + } + + public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss) + { + activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss)); + } + + @Override + public void run() { + // If we're dying, don't bother creating a dialog + if (activity.isFinishing()) + return; + + alert = new AlertDialog.Builder(activity).create(); + + alert.setTitle(title); + alert.setMessage(message); + alert.setCancelable(false); + alert.setCanceledOnTouchOutside(false); + + alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + alert.dismiss(); + rundownDialogs.remove(this); + + if (endAfterDismiss) + activity.finish(); + } + }); + + rundownDialogs.add(this); + alert.show(); + } + +} diff --git a/src/com/limelight/utils/SpinnerDialog.java b/src/com/limelight/utils/SpinnerDialog.java new file mode 100644 index 00000000..9fd61201 --- /dev/null +++ b/src/com/limelight/utils/SpinnerDialog.java @@ -0,0 +1,98 @@ +package com.limelight.utils; + +import java.util.ArrayList; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; + +public class SpinnerDialog implements Runnable,OnCancelListener { + private String title, message; + private Activity activity; + private ProgressDialog progress; + private boolean finish; + + private static ArrayList rundownDialogs = new ArrayList(); + + public SpinnerDialog(Activity activity, String title, String message, boolean finish) + { + this.activity = activity; + this.title = title; + this.message = message; + this.progress = null; + this.finish = finish; + } + + public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish) + { + SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish); + activity.runOnUiThread(spinner); + return spinner; + } + + public static void closeDialogs() + { + for (SpinnerDialog d : rundownDialogs) + d.progress.dismiss(); + + rundownDialogs.clear(); + } + + public void dismiss() + { + // Running again with progress != null will destroy it + activity.runOnUiThread(this); + } + + public void setMessage(final String message) + { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + progress.setMessage(message); + } + }); + } + + @Override + public void run() { + + if (progress == null) + { + // If we're dying, don't bother creating a dialog + if (activity.isFinishing()) + return; + + progress = new ProgressDialog(activity); + + progress.setTitle(title); + progress.setMessage(message); + progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); + progress.setOnCancelListener(this); + + // If we want to finish the activity when this is killed, make it cancellable + if (finish) + { + progress.setCancelable(true); + progress.setCanceledOnTouchOutside(false); + } + else + { + progress.setCancelable(false); + } + + progress.show(); + } + else + { + progress.dismiss(); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + // This will only be called if finish was true, so we don't need to check again + activity.finish(); + } +}