mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-19 11:03:01 +00:00
Add UI indications of the connection status and failure
This commit is contained in:
parent
c88d23001e
commit
4d5849f448
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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<Runnable>());
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
30
src/com/limelight/nvstream/NvConnectionListener.java
Normal file
30
src/com/limelight/nvstream/NvConnectionListener.java
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<AvRtpPacket> packets = new LinkedBlockingQueue<AvRtpPacket>();
|
||||
|
||||
private InetAddress host;
|
||||
private DatagramSocket rtp;
|
||||
private Socket firstFrameSocket;
|
||||
|
||||
private LinkedList<Thread> threads = new LinkedList<Thread>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
66
src/com/limelight/utils/Dialog.java
Normal file
66
src/com/limelight/utils/Dialog.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
98
src/com/limelight/utils/SpinnerDialog.java
Normal file
98
src/com/limelight/utils/SpinnerDialog.java
Normal 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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user