Add UI indications of the connection status and failure

This commit is contained in:
Cameron Gutman 2013-11-23 01:03:37 -05:00
parent c88d23001e
commit 4d5849f448
13 changed files with 569 additions and 244 deletions

View File

@ -2,7 +2,9 @@ package com.limelight;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@ -88,9 +90,10 @@ public class Connection extends Activity {
return; return;
} }
NvHTTP httpConn = new NvHTTP(hostText.getText().toString(), macAddress); NvHTTP httpConn;
String message; String message;
try {
httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()), macAddress);
try { try {
if (httpConn.getPairState()) { if (httpConn.getPairState()) {
message = "Already paired"; message = "Already paired";
@ -109,6 +112,9 @@ public class Connection extends Activity {
} catch (XmlPullParserException e) { } catch (XmlPullParserException e) {
message = e.getMessage(); message = e.getMessage();
} }
} catch (UnknownHostException e1) {
message = "Failed to resolve host";
}
final String toastMessage = message; final String toastMessage = message;
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {

View File

@ -1,7 +1,10 @@
package com.limelight; package com.limelight;
import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.NvConnectionListener;
import com.limelight.nvstream.input.NvControllerPacket; import com.limelight.nvstream.input.NvControllerPacket;
import com.limelight.utils.Dialog;
import com.limelight.utils.SpinnerDialog;
import android.app.Activity; import android.app.Activity;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
@ -18,7 +21,7 @@ import android.view.Window;
import android.view.WindowManager; 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 short inputMap = 0x0000;
private byte leftTrigger = 0x00; private byte leftTrigger = 0x00;
private byte rightTrigger = 0x00; private byte rightTrigger = 0x00;
@ -33,6 +36,8 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
private boolean hasMoved = false; private boolean hasMoved = false;
private NvConnection conn; private NvConnection conn;
private SpinnerDialog spinner;
private boolean displayedFailureDialog = false;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -59,6 +64,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
sh.setFixedSize(1280, 720); sh.setFixedSize(1280, 720);
sh.setFormat(PixelFormat.RGBX_8888); sh.setFormat(PixelFormat.RGBX_8888);
// Start the spinner
spinner = SpinnerDialog.displayDialog(this, "Establishing Connection", "Starting connection", true);
// Start the connection // Start the connection
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder().getSurface()); conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder().getSurface());
conn.start(); conn.start();
@ -94,6 +102,13 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
super.onPause(); super.onPause();
} }
@Override
protected void onDestroy() {
SpinnerDialog.closeDialogs();
Dialog.closeDialogs();
super.onDestroy();
}
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) { switch (keyCode) {
@ -439,4 +454,42 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
// Send it to the activity's touch event handler // Send it to the activity's touch event handler
return onTouchEvent(event); 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;
}
} }

View File

@ -5,7 +5,6 @@ import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@ -35,6 +34,15 @@ public class NvAudioStream {
private boolean aborting = false; 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() public void abort()
{ {
if (aborting) { if (aborting) {
@ -66,21 +74,9 @@ public class NvAudioStream {
threads.clear(); threads.clear();
} }
public void startAudioStream(final String host) public void startAudioStream() throws SocketException
{ {
new Thread(new Runnable() { setupRtpSession();
@Override
public void run() {
try {
setupRtpSession(host);
} catch (SocketException e) {
e.printStackTrace();
return;
} catch (UnknownHostException e) {
e.printStackTrace();
return;
}
setupAudio(); setupAudio();
@ -93,13 +89,10 @@ public class NvAudioStream {
startUdpPingThread(); startUdpPingThread();
} }
}).start(); private void setupRtpSession() throws SocketException
}
private void setupRtpSession(String host) throws SocketException, UnknownHostException
{ {
rtp = new DatagramSocket(RTP_PORT); rtp = new DatagramSocket(RTP_PORT);
rtp.connect(InetAddress.getByName(host), RTP_PORT); rtp.connect(host, RTP_PORT);
} }
private void setupAudio() private void setupAudio()
@ -108,12 +101,8 @@ public class NvAudioStream {
int err; int err;
err = OpusDecoder.init(); err = OpusDecoder.init();
if (err == 0) { if (err != 0) {
System.out.println("Opus decoder initialized"); throw new IllegalStateException("Opus decoder failed to initialize");
}
else {
System.err.println("Opus decoder init failed: "+err);
return;
} }
switch (OpusDecoder.getChannelCount()) switch (OpusDecoder.getChannelCount())
@ -125,8 +114,7 @@ public class NvAudioStream {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break; break;
default: default:
System.err.println("Unsupported channel count"); throw new IllegalStateException("Opus decoder returned unhandled channel count");
return;
} }
track = new AudioTrack(AudioManager.STREAM_MUSIC, track = new AudioTrack(AudioManager.STREAM_MUSIC,
@ -153,7 +141,7 @@ public class NvAudioStream {
try { try {
packet = packets.take(); packet = packets.take();
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
@ -178,7 +166,7 @@ public class NvAudioStream {
try { try {
samples = depacketizer.getNextDecodedData(); samples = depacketizer.getNextDecodedData();
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
@ -204,7 +192,7 @@ public class NvAudioStream {
try { try {
rtp.receive(packet); rtp.receive(packet);
} catch (IOException e) { } catch (IOException e) {
abort(); listener.connectionTerminated();
return; return;
} }
@ -240,14 +228,14 @@ public class NvAudioStream {
try { try {
rtp.send(pingPacket); rtp.send(pingPacket);
} catch (IOException e) { } catch (IOException e) {
abort(); listener.connectionTerminated();
return; return;
} }
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
} }

View File

@ -38,10 +38,10 @@ public class NvComputer {
this.uniqueID = uniqueID; this.uniqueID = uniqueID;
try { try {
this.nvHTTP = new NvHTTP(this.ipAddressString, NvConnection.getMacAddressString()); this.nvHTTP = new NvHTTP(this.ipAddress, NvConnection.getMacAddressString());
} catch (SocketException e) { } catch (SocketException e) {
Log.e("NvComputer Constructor", "Unable to get MAC Address " + e.getMessage()); 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(); this.updatePairState();

View File

@ -23,18 +23,21 @@ import com.limelight.nvstream.input.NvController;
public class NvConnection { public class NvConnection {
private String host; private String host;
private Game activity; private Game activity;
private NvConnectionListener listener;
private InetAddress hostAddr;
private NvControl controlStream; private NvControl controlStream;
private NvController inputStream; private NvController inputStream;
private Surface video; private Surface video;
private NvVideoStream videoStream; private NvVideoStream videoStream;
private NvAudioStream audioStream = new NvAudioStream(); private NvAudioStream audioStream;
private ThreadPoolExecutor threadPool; private ThreadPoolExecutor threadPool;
public NvConnection(String host, Game activity, Surface video) public NvConnection(String host, Game activity, Surface video)
{ {
this.host = host; this.host = host;
this.listener = activity;
this.activity = activity; this.activity = activity;
this.video = video; this.video = video;
this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>()); this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>());
@ -117,6 +120,106 @@ public class NvConnection {
} }
} }
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() public void start()
{ {
new Thread(new Runnable() { new Thread(new Runnable() {
@ -125,31 +228,16 @@ public class NvConnection {
checkDataConnection(); checkDataConnection();
try { try {
host = InetAddress.getByName(host).getHostAddress(); hostAddr = InetAddress.getByName(host);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
e.printStackTrace();
displayToast(e.getMessage()); displayToast(e.getMessage());
listener.connectionTerminated();
return; return;
} }
try { establishConnection();
startSteamBigPicture();
performHandshake();
beginControlStream();
videoStream = new NvVideoStream(controlStream);
videoStream.startVideoStream(host, video);
audioStream.startAudioStream(host);
controlStream.startJitterPackets();
startController();
activity.hideSystemUi(); activity.hideSystemUi();
} catch (XmlPullParserException e) {
e.printStackTrace();
displayToast(e.getMessage());
stop();
} catch (IOException e) {
displayToast(e.getMessage());
stop();
}
} }
}).start(); }).start();
} }
@ -173,8 +261,7 @@ public class NvConnection {
try { try {
inputStream.sendMouseMove(deltaX, deltaY); inputStream.sendMouseMove(deltaX, deltaY);
} catch (IOException e) { } catch (IOException e) {
displayToast(e.getMessage()); listener.connectionTerminated();
NvConnection.this.stop();
} }
} }
}); });
@ -191,8 +278,7 @@ public class NvConnection {
try { try {
inputStream.sendMouseButtonDown(); inputStream.sendMouseButtonDown();
} catch (IOException e) { } catch (IOException e) {
displayToast(e.getMessage()); listener.connectionTerminated();
NvConnection.this.stop();
} }
} }
}); });
@ -209,8 +295,7 @@ public class NvConnection {
try { try {
inputStream.sendMouseButtonUp(); inputStream.sendMouseButtonUp();
} catch (IOException e) { } catch (IOException e) {
displayToast(e.getMessage()); listener.connectionTerminated();
NvConnection.this.stop();
} }
} }
}); });
@ -232,8 +317,7 @@ public class NvConnection {
rightTrigger, leftStickX, leftStickY, rightTrigger, leftStickX, leftStickY,
rightStickX, rightStickY); rightStickX, rightStickY);
} catch (IOException e) { } catch (IOException e) {
displayToast(e.getMessage()); listener.connectionTerminated();
NvConnection.this.stop();
} }
} }
}); });
@ -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);
}
} }

View File

@ -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();
}

View File

@ -3,8 +3,9 @@ package com.limelight.nvstream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@ -14,6 +15,8 @@ public class NvControl implements ConnectionStatusListener {
public static final int PORT = 47995; public static final int PORT = 47995;
public static final int CONTROL_TIMEOUT = 3000;
public static final short PTYPE_HELLO = 0x1204; public static final short PTYPE_HELLO = 0x1204;
public static final short PPAYLEN_HELLO = 0x0004; public static final short PPAYLEN_HELLO = 0x0004;
public static final byte[] PPAYLOAD_HELLO = public static final byte[] PPAYLOAD_HELLO =
@ -140,6 +143,9 @@ public class NvControl implements ConnectionStatusListener {
private int seqNum; private int seqNum;
private NvConnectionListener listener;
private InetAddress host;
private Socket s; private Socket s;
private InputStream in; private InputStream in;
private OutputStream out; private OutputStream out;
@ -148,20 +154,28 @@ public class NvControl implements ConnectionStatusListener {
private Thread jitterThread; private Thread jitterThread;
private boolean aborting = false; 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(); in = s.getInputStream();
out = s.getOutputStream(); out = s.getOutputStream();
} }
public void sendPacket(NvCtlPacket packet) throws IOException private void sendPacket(NvCtlPacket packet) throws IOException
{ {
out.write(packet.toWire()); out.write(packet.toWire());
out.flush(); out.flush();
} }
public NvControl.NvCtlResponse sendAndGetReply(NvCtlPacket packet) throws IOException private NvControl.NvCtlResponse sendAndGetReply(NvCtlPacket packet) throws IOException
{ {
sendPacket(packet); sendPacket(packet);
return new NvCtlResponse(in); return new NvCtlResponse(in);
@ -208,15 +222,10 @@ public class NvControl implements ConnectionStatusListener {
public void start() throws IOException public void start() throws IOException
{ {
System.out.println("CTL: Sending hello");
sendHello(); sendHello();
System.out.println("CTL: Sending config");
sendConfig(); sendConfig();
System.out.println("CTL: Initial ping/pong");
pingPong(); pingPong();
System.out.println("CTL: Sending and waiting for 1405");
send1405AndGetResponse(); send1405AndGetResponse();
System.out.println("CTL: Launching heartbeat thread");
heartbeatThread = new Thread() { heartbeatThread = new Thread() {
@Override @Override
@ -226,7 +235,7 @@ public class NvControl implements ConnectionStatusListener {
try { try {
sendHeartbeat(); sendHeartbeat();
} catch (IOException e1) { } catch (IOException e1) {
abort(); listener.connectionTerminated();
return; return;
} }
@ -234,7 +243,7 @@ public class NvControl implements ConnectionStatusListener {
try { try {
Thread.sleep(3000); Thread.sleep(3000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
} }
@ -253,14 +262,14 @@ public class NvControl implements ConnectionStatusListener {
try { try {
sendJitter(); sendJitter();
} catch (IOException e1) { } catch (IOException e1) {
abort(); listener.connectionTerminated();
return; return;
} }
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
} }

View File

@ -3,7 +3,9 @@ package com.limelight.nvstream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Stack; import java.util.Stack;
@ -15,11 +17,16 @@ public class NvHTTP {
private String macAddress; private String macAddress;
public static final int PORT = 47989; public static final int PORT = 47989;
public static final int CONNECTION_TIMEOUT = 3000;
public static final int REQUEST_TIMEOUT = 15000;
public String baseUrl; public String baseUrl;
public NvHTTP(String host, String macAddress) { public NvHTTP(InetAddress host, String macAddress) {
this.macAddress = macAddress; this.macAddress = macAddress;
this.baseUrl = "http://" + host + ":" + PORT; this.baseUrl = "http://" + host.getHostAddress() + ":" + PORT;
} }
private String getXmlString(InputStream in, String tagname) private String getXmlString(InputStream in, String tagname)
@ -53,7 +60,12 @@ public class NvHTTP {
} }
private InputStream openHttpConnection(String url) throws IOException { 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 { public String getAppVersion() throws XmlPullParserException, IOException {

View File

@ -3,15 +3,23 @@ package com.limelight.nvstream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException;;
public class NvHandshake { public class NvHandshake {
public static final int PORT = 47991; public static final int PORT = 47991;
// android public static final int HANDSHAKE_TIMEOUT = 3000;
public static final byte[] PLATFORM_HELLO = public static final byte[] PLATFORM_HELLO =
{ {
(byte)0x07,
(byte)0x00,
(byte)0x00,
(byte)0x00,
// android in ASCII
(byte)0x61, (byte)0x61,
(byte)0x6e, (byte)0x6e,
(byte)0x64, (byte)0x64,
@ -19,6 +27,7 @@ public class NvHandshake {
(byte)0x6f, (byte)0x6f,
(byte)0x69, (byte)0x69,
(byte)0x64, (byte)0x64,
(byte)0x03, (byte)0x03,
(byte)0x01, (byte)0x01,
(byte)0x00, (byte)0x00,
@ -56,46 +65,61 @@ public class NvHandshake {
(byte)0x00 (byte)0x00
}; };
private static void waitAndDiscardResponse(InputStream in) throws IOException private static boolean waitAndDiscardResponse(InputStream in)
{ {
// Wait for response and discard response // Wait for response and discard response
try {
in.read(); in.read();
try { // Wait for the full response to come in
Thread.sleep(250); Thread.sleep(250);
} catch (InterruptedException e) { }
for (int i = 0; i < in.available(); i++) for (int i = 0; i < in.available(); i++)
in.read(); in.read();
} catch (IOException e1) {
return false;
} catch (InterruptedException e) {
return false;
} }
public static void performHandshake(String host) throws UnknownHostException, IOException return true;
}
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(); OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream(); InputStream in = s.getInputStream();
// First packet // First packet
out.write(new byte[]{0x07, 0x00, 0x00, 0x00});
out.write(PLATFORM_HELLO); out.write(PLATFORM_HELLO);
out.flush();
System.out.println("HS: Waiting for hello response"); if (!waitAndDiscardResponse(in)) {
s.close();
waitAndDiscardResponse(in); return false;
}
// Second packet // Second packet
out.write(PACKET_2); out.write(PACKET_2);
out.flush();
System.out.println("HS: Waiting stage 2 response"); if (!waitAndDiscardResponse(in)) {
s.close();
waitAndDiscardResponse(in); return false;
}
// Third packet // Third packet
out.write(PACKET_3); out.write(PACKET_3);
out.flush();
System.out.println("HS: Waiting for stage 3 response"); if (!waitAndDiscardResponse(in)) {
s.close();
waitAndDiscardResponse(in); return false;
}
// Fourth packet // Fourth packet
out.write(PACKET_4); out.write(PACKET_4);
@ -103,5 +127,7 @@ public class NvHandshake {
// Done // Done
s.close(); s.close();
return true;
} }
} }

View File

@ -5,9 +5,9 @@ import java.io.InputStream;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@ -29,13 +29,17 @@ public class NvVideoStream {
public static final int RTCP_PORT = 47999; public static final int RTCP_PORT = 47999;
public static final int FIRST_FRAME_PORT = 47996; public static final int FIRST_FRAME_PORT = 47996;
public static final int FIRST_FRAME_TIMEOUT = 3000;
private LinkedBlockingQueue<AvRtpPacket> packets = new LinkedBlockingQueue<AvRtpPacket>(); private LinkedBlockingQueue<AvRtpPacket> packets = new LinkedBlockingQueue<AvRtpPacket>();
private InetAddress host;
private DatagramSocket rtp; private DatagramSocket rtp;
private Socket firstFrameSocket; private Socket firstFrameSocket;
private LinkedList<Thread> threads = new LinkedList<Thread>(); private LinkedList<Thread> threads = new LinkedList<Thread>();
private NvConnectionListener listener;
private AvVideoDepacketizer depacketizer; private AvVideoDepacketizer depacketizer;
private DecoderRenderer decrend; private DecoderRenderer decrend;
@ -43,9 +47,11 @@ public class NvVideoStream {
private boolean aborting = false; 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() public void abort()
@ -89,14 +95,15 @@ public class NvVideoStream {
threads.clear(); threads.clear();
} }
private void readFirstFrame(String host) throws IOException private void readFirstFrame() throws IOException
{ {
byte[] firstFrame = new byte[1500]; byte[] firstFrame = new byte[1500];
System.out.println("VID: Waiting for first frame"); firstFrameSocket = new Socket();
firstFrameSocket = new Socket(host, FIRST_FRAME_PORT); firstFrameSocket.setSoTimeout(FIRST_FRAME_TIMEOUT);
try { try {
firstFrameSocket.connect(new InetSocketAddress(host, FIRST_FRAME_PORT), FIRST_FRAME_TIMEOUT);
InputStream firstFrameStream = firstFrameSocket.getInputStream(); InputStream firstFrameStream = firstFrameSocket.getInputStream();
int offset = 0; int offset = 0;
@ -110,7 +117,6 @@ public class NvVideoStream {
offset += bytesRead; offset += bytesRead;
} }
System.out.println("VID: First frame read ("+offset+" bytes)");
depacketizer.addInputData(new AvVideoPacket(new AvByteBufferDescriptor(firstFrame, 0, offset))); depacketizer.addInputData(new AvVideoPacket(new AvByteBufferDescriptor(firstFrame, 0, offset)));
} finally { } finally {
firstFrameSocket.close(); 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 = new DatagramSocket(RTP_PORT);
rtp.connect(InetAddress.getByName(host), RTP_PORT); rtp.connect(host, RTP_PORT);
rtp.setReceiveBufferSize(2097152);
System.out.println("RECV BUF: "+rtp.getReceiveBufferSize());
System.out.println("SEND BUF: "+rtp.getSendBufferSize());
} }
public void setupDecoderRenderer(Surface renderTarget) { public void setupDecoderRenderer(Surface renderTarget) {
@ -146,25 +149,13 @@ 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 // Setup the decoder and renderer
setupDecoderRenderer(surface); setupDecoderRenderer(surface);
// Open RTP sockets and start session // Open RTP sockets and start session
try { setupRtpSession();
setupRtpSession(host);
} catch (SocketException e) {
e.printStackTrace();
return;
} catch (UnknownHostException e) {
e.printStackTrace();
return;
}
// Start pinging before reading the first frame // Start pinging before reading the first frame
// so Shield Proxy knows we're here and sends us // so Shield Proxy knows we're here and sends us
@ -175,12 +166,7 @@ public class NvVideoStream {
// This MUST be called before the normal UDP receive thread // This MUST be called before the normal UDP receive thread
// starts in order to avoid state corruption caused by two // starts in order to avoid state corruption caused by two
// threads simultaneously adding input data. // threads simultaneously adding input data.
try { readFirstFrame();
readFirstFrame(host);
} catch (IOException e2) {
abort();
return;
}
if (decrend != null) { if (decrend != null) {
// Start the receive thread early to avoid missing // Start the receive thread early to avoid missing
@ -198,10 +184,6 @@ public class NvVideoStream {
startedRendering = true; startedRendering = true;
} }
} }
};
threads.add(t);
t.start();
}
private void startDecoderThread() private void startDecoderThread()
{ {
@ -216,7 +198,7 @@ public class NvVideoStream {
try { try {
du = depacketizer.getNextDecodeUnit(); du = depacketizer.getNextDecodeUnit();
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
@ -242,7 +224,7 @@ public class NvVideoStream {
try { try {
packet = packets.take(); packet = packets.take();
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
@ -269,7 +251,7 @@ public class NvVideoStream {
try { try {
rtp.receive(packet); rtp.receive(packet);
} catch (IOException e) { } catch (IOException e) {
abort(); listener.connectionTerminated();
return; return;
} }
@ -305,14 +287,14 @@ public class NvVideoStream {
try { try {
rtp.send(pingPacket); rtp.send(pingPacket);
} catch (IOException e) { } catch (IOException e) {
abort(); listener.connectionTerminated();
return; return;
} }
try { try {
Thread.sleep(100); Thread.sleep(100);
} catch (InterruptedException e) { } catch (InterruptedException e) {
abort(); listener.connectionTerminated();
return; return;
} }
} }

View File

@ -2,19 +2,29 @@ package com.limelight.nvstream.input;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException;
public class NvController { public class NvController {
public final static int PORT = 35043; public final static int PORT = 35043;
public final static int CONTROLLER_TIMEOUT = 3000;
private InetAddress host;
private Socket s; private Socket s;
private OutputStream out; 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); s.setTcpNoDelay(true);
out = s.getOutputStream(); out = s.getOutputStream();
} }

View File

@ -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<Dialog> rundownDialogs = new ArrayList<Dialog>();
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();
}
}

View File

@ -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<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
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();
}
}