diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 110d2fed..3b9b828f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -7,7 +7,9 @@
-
+
+
+
+
+
+
+
diff --git a/gen/com/limelight/R.java b/gen/com/limelight/R.java
index d86869e4..1244305e 100644
--- a/gen/com/limelight/R.java
+++ b/gen/com/limelight/R.java
@@ -9,6 +9,17 @@ package com.limelight;
public final class R {
public static final class attr {
+ /**
Must be a reference to another resource, in the form "@[+][package:]type:name
"
+or to a theme attribute in the form "?[package:][type:]name
".
+ */
+ public static final int buttonBarButtonStyle=0x7f010001;
+ /**
Must be a reference to another resource, in the form "@[+][package:]type:name
"
+or to a theme attribute in the form "?[package:][type:]name
".
+ */
+ public static final int buttonBarStyle=0x7f010000;
+ }
+ public static final class color {
+ public static final int black_overlay=0x7f040000;
}
public static final class dimen {
/** Default screen margins, per the Android Design guidelines.
@@ -17,31 +28,41 @@ public final class R {
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
*/
- public static final int activity_horizontal_margin=0x7f040000;
- public static final int activity_vertical_margin=0x7f040001;
+ public static final int activity_horizontal_margin=0x7f050000;
+ public static final int activity_vertical_margin=0x7f050001;
}
public static final class drawable {
public static final int ic_launcher=0x7f020000;
}
+ public static final class id {
+ public static final int videoView=0x7f080000;
+ }
public static final class layout {
public static final int activity_connection=0x7f030000;
+ public static final int activity_game=0x7f030001;
}
public static final class string {
- public static final int app_name=0x7f050000;
+ public static final int app_name=0x7f060000;
+ public static final int dummy_button=0x7f060002;
+ public static final int dummy_content=0x7f060003;
+ public static final int title_activity_game=0x7f060001;
}
public static final class style {
/**
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
+
Base application theme for API 11+. This theme completely replaces
AppBaseTheme from res/values/styles.xml on API 11+ devices.
+
API 11 theme customizations can go here.
@@ -51,10 +72,56 @@ public final class R {
API 14 theme customizations can go here.
*/
- public static final int AppBaseTheme=0x7f060000;
+ public static final int AppBaseTheme=0x7f070000;
/** Application theme.
All customizations that are NOT specific to a particular API-level can go here.
*/
- public static final int AppTheme=0x7f060001;
+ public static final int AppTheme=0x7f070001;
+ public static final int ButtonBar=0x7f070003;
+ public static final int ButtonBarButton=0x7f070004;
+ public static final int FullscreenActionBarStyle=0x7f070005;
+ public static final int FullscreenTheme=0x7f070002;
}
+ public static final class styleable {
+ /**
+ Declare custom theme attributes that allow changing which styles are
+ used for button bars depending on the API level.
+ ?android:attr/buttonBarStyle is new as of API 11 so this is
+ necessary to support previous API levels.
+
+
Includes the following attributes:
+
+
+
+ Attribute | Description |
+ {@link #ButtonBarContainerTheme_buttonBarButtonStyle com.limelight:buttonBarButtonStyle} | |
+ {@link #ButtonBarContainerTheme_buttonBarStyle com.limelight:buttonBarStyle} | |
+
+ @see #ButtonBarContainerTheme_buttonBarButtonStyle
+ @see #ButtonBarContainerTheme_buttonBarStyle
+ */
+ public static final int[] ButtonBarContainerTheme = {
+ 0x7f010000, 0x7f010001
+ };
+ /**
+ This symbol is the offset where the {@link com.limelight.R.attr#buttonBarButtonStyle}
+ attribute's value can be found in the {@link #ButtonBarContainerTheme} array.
+
+
+
Must be a reference to another resource, in the form "@[+][package:]type:name
"
+or to a theme attribute in the form "?[package:][type:]name
".
+ @attr name android:buttonBarButtonStyle
+ */
+ public static final int ButtonBarContainerTheme_buttonBarButtonStyle = 1;
+ /**
+
This symbol is the offset where the {@link com.limelight.R.attr#buttonBarStyle}
+ attribute's value can be found in the {@link #ButtonBarContainerTheme} array.
+
+
+
Must be a reference to another resource, in the form "@[+][package:]type:name
"
+or to a theme attribute in the form "?[package:][type:]name
".
+ @attr name android:buttonBarStyle
+ */
+ public static final int ButtonBarContainerTheme_buttonBarStyle = 0;
+ };
}
diff --git a/res/layout/activity_game.xml b/res/layout/activity_game.xml
new file mode 100644
index 00000000..26f62b06
--- /dev/null
+++ b/res/layout/activity_game.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
index 3c02242a..12f5f138 100644
--- a/res/values-v11/styles.xml
+++ b/res/values-v11/styles.xml
@@ -1,11 +1,24 @@
-
-
-
-
-
-
+ AppBaseTheme from res/values/styles.xml on API 11+ devices.
+ -->
+
+
+
+
+
+
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 00000000..e67df0a3
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 00000000..327c0604
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #66000000
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5d9f3c89..fba8ffbf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2,5 +2,8 @@
Limelight
+ Game
+ Dummy Button
+ DUMMY\nCONTENT
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6ce89c7b..4d77c75e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,20 +1,38 @@
-
-
-
-
-
-
-
-
-
+ backward-compatibility can go here.
+ -->
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/limelight/Game.java b/src/com/limelight/Game.java
new file mode 100644
index 00000000..913b4db8
--- /dev/null
+++ b/src/com/limelight/Game.java
@@ -0,0 +1,30 @@
+package com.limelight;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.MediaController;
+import android.widget.VideoView;
+
+
+public class Game extends Activity {
+ private VideoView vv;
+ private MediaController mc;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_game);
+
+ vv = (VideoView) findViewById(R.id.videoView);
+
+ mc = new MediaController(this);
+ mc.setAnchorView(vv);
+
+ Uri video = Uri.parse("rtsp://141.213.191.236:47902/nvstream");
+ vv.setMediaController(mc);
+ vv.setVideoURI(video);
+
+ vv.start();
+ }
+}
diff --git a/src/com/limelight/nvstream/NvAudioStream.java b/src/com/limelight/nvstream/NvAudioStream.java
new file mode 100644
index 00000000..d34f915b
--- /dev/null
+++ b/src/com/limelight/nvstream/NvAudioStream.java
@@ -0,0 +1,73 @@
+package com.limelight.nvstream;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import android.net.rtp.AudioCodec;
+import android.net.rtp.AudioGroup;
+import android.net.rtp.AudioStream;
+
+public class NvAudioStream {
+ private AudioGroup group;
+ private AudioStream stream;
+
+ public static final int PORT = 48000;
+
+ /*public void startStream(String host) throws SocketException, UnknownHostException
+ {
+ System.out.println("Starting audio group");
+ group = new AudioGroup();
+ group.setMode(AudioGroup.MODE_NORMAL);
+
+ System.out.println("Starting audio stream");
+ stream = new AudioStream(InetAddress.getByAddress(new byte[]{0,0,0,0}));
+ stream.setMode(AudioStream.MODE_NORMAL);
+ stream.associate(InetAddress.getByName(host), PORT);
+ stream.setCodec(AudioCodec.PCMA);
+ stream.join(group);
+
+ for (AudioCodec c : AudioCodec.getCodecs())
+ System.out.println(c.type + " " + c.fmtp + " " + c.rtpmap);
+
+ System.out.println("Joined");
+ }*/
+
+ public void start()
+ {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ DatagramSocket ds;
+ try {
+ ds = new DatagramSocket(PORT);
+ } catch (SocketException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ return;
+ }
+
+ for (;;)
+ {
+
+ DatagramPacket dp = new DatagramPacket(new byte[1500], 1500);
+
+ try {
+ ds.receive(dp);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ break;
+ }
+
+ //System.out.println("Got UDP 48000: "+dp.getLength());
+ }
+ }
+
+ }).start();
+ }
+}
diff --git a/src/com/limelight/nvstream/NvConnection.java b/src/com/limelight/nvstream/NvConnection.java
index c4e5c4b3..3edf4d03 100644
--- a/src/com/limelight/nvstream/NvConnection.java
+++ b/src/com/limelight/nvstream/NvConnection.java
@@ -12,20 +12,67 @@ public class NvConnection {
this.host = host;
}
+ private void delay(int ms)
+ {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+
public void doShit() throws XmlPullParserException, IOException
{
NvHttp h = new NvHttp(host, "b0:ee:45:57:5d:5f");
- System.out.println("Shield Shit");
+ System.out.println("Begin Shield Action");
System.out.println(h.getAppVersion());
System.out.println(h.getPairState());
-
int sessionId = h.getSessionId();
System.out.println("Session ID: "+sessionId);
int appId = h.getSteamAppId(sessionId);
System.out.println("Steam app ID: "+appId);
int gameSession = h.launchApp(sessionId, appId);
System.out.println("Started game session: "+gameSession);
+
+ System.out.println("Starting handshake");
+ NvHandshake.performHandshake(host);
+ System.out.println("Handshake complete");
+
+ NvControl nvC = new NvControl(host);
+
+ System.out.println("Starting control");
+ nvC.beginControl();
+
+ System.out.println("Startup controller");
+ NvController controller = new NvController(host);
+
+ // Wait 3 seconds to start input
+ delay(3000);
+
+ System.out.println("Beginning controller input");
+ controller.sendLeftButton();
+ delay(100);
+ controller.clearButtons();
+ delay(250);
+ controller.sendRightButton();
+ delay(100);
+ controller.clearButtons();
+ delay(250);
+ controller.sendRightButton();
+ delay(100);
+ controller.clearButtons();
+ delay(250);
+ controller.sendRightButton();
+ delay(100);
+ controller.clearButtons();
+ delay(250);
+ controller.sendLeftButton();
+ delay(100);
+ controller.clearButtons();
+
+ new NvAudioStream().start();
+ new NvVideoStream().start(host);
}
}
diff --git a/src/com/limelight/nvstream/NvControl.java b/src/com/limelight/nvstream/NvControl.java
new file mode 100644
index 00000000..87ec3e73
--- /dev/null
+++ b/src/com/limelight/nvstream/NvControl.java
@@ -0,0 +1,384 @@
+package com.limelight.nvstream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class NvControl {
+
+ public static final int PORT = 47995;
+
+ public static final short PTYPE_1 = 0x1204;
+ public static final short PPAYLEN_1 = 0x0004;
+ public static final byte[] PPAYLOAD_1 =
+ {
+ (byte)0x00,
+ (byte)0x05,
+ (byte)0x00,
+ (byte)0x00
+ };
+
+ public static final short PTYPE_KEEPALIVE = 0x13ff;
+ public static final short PPAYLEN_KEEPALIVE = 0x0000;
+
+ public static final short PTYPE_HEARTBEAT = 0x1401;
+ public static final short PPAYLEN_HEARTBEAT = 0x0000;
+
+ public static final short PTYPE_1405 = 0x1405;
+ public static final short PPAYLEN_1405 = 0x0000;
+
+ public static final short PTYPE_1404 = 0x1404;
+ public static final short PPAYLEN_1404 = 0x0010;
+ public static final byte[] PPAYLOAD_1404 = new byte[]
+ {
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00
+ };
+
+ public static final short PTYPE_CONFIG = 0x1205;
+ public static final short PPAYLEN_CONFIG = 0x0004;
+ public static final int[] PPAYLOAD_CONFIG =
+ {
+ 720,
+ 266758,
+ 1,
+ 266762,
+ 30,
+ 70151,
+ 68291329,
+ 1280,
+ 68291584,
+ 1280,
+ 68291840,
+ 15360,
+ 68292096,
+ 25600,
+ 68292352,
+ 2048,
+ 68292608,
+ 1024,
+ 68289024,
+ 262144,
+ 17957632,
+ 302055424,
+ 134217729,
+ 16777490,
+ 70153,
+ 68293120,
+ 768000,
+ 17961216,
+ 303235072,
+ 335609857,
+ 838861842,
+ 352321536,
+ 1006634002,
+ 369098752,
+ 335545362,
+ 385875968,
+ 1042,
+ 402653184,
+ 134218770,
+ 419430400,
+ 167773203,
+ 436207616,
+ 855638290,
+ 266779,
+ 7000,
+ 266780,
+ 2000,
+ 266781,
+ 50,
+ 266782,
+ 3000,
+ 266783,
+ 2,
+ 266794,
+ 5000,
+ 266795,
+ 500,
+ 266784,
+ 75,
+ 266785,
+ 25,
+ 266786,
+ 10,
+ 266787,
+ 60,
+ 266788,
+ 30,
+ 266789,
+ 3,
+ 266790,
+ 1000,
+ 266791,
+ 5000,
+ 266792,
+ 5000,
+ 266793,
+ 5000,
+ 70190,
+ 68301063,
+ 10240,
+ 68301312,
+ 6400,
+ 68301568,
+ 768000,
+ 68299776,
+ 768,
+ 68300032,
+ 2560,
+ 68300544,
+ 0,
+ 34746368,
+ (int)0xFE000000
+ };
+
+ public static final short PTYPE_HELLO = 0x1204;
+ public static final short PPAYLEN_HELLO = 0x0004;
+ public static final byte[] PPAYLOAD_HELLO = new byte[] {
+ 0x04, 0x00, 0x00, 0x05
+ };
+
+ private Socket s;
+ private InputStream in;
+ private OutputStream out;
+
+ public NvControl(String host) throws UnknownHostException, IOException
+ {
+ s = new Socket(host, PORT);
+ in = s.getInputStream();
+ out = s.getOutputStream();
+ }
+
+ public void sendPacket(NvCtlPacket packet) throws IOException
+ {
+ out.write(packet.toWire());
+ out.flush();
+ }
+ public NvControl.NvCtlResponse sendAndGetReply(NvCtlPacket packet) throws IOException
+ {
+ sendPacket(packet);
+ return new NvCtlResponse(in);
+ }
+
+ public void beginControl() 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: Sending 1404");
+ //send1404();
+ System.out.println("CTL: Launching heartbeat thread");
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ for (;;)
+ {
+ try {
+ sendHeartbeat();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ break;
+ }
+
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+ }).start();
+ }
+
+ public void endControl() throws IOException
+ {
+ s.close();
+ }
+
+ private NvControl.NvCtlResponse send1405AndGetResponse() throws IOException
+ {
+ return sendAndGetReply(new NvCtlPacket(PTYPE_1405, PPAYLEN_1405));
+ }
+
+ private void send1404() throws IOException
+ {
+ sendPacket(new NvCtlPacket(PTYPE_1404, PPAYLEN_1404));
+ }
+
+ private void sendHello() throws IOException
+ {
+ sendPacket(new NvCtlPacket(PTYPE_HELLO, PPAYLEN_HELLO, PPAYLOAD_HELLO));
+ }
+
+ private void sendConfig() throws IOException
+ {
+ ByteBuffer conf = ByteBuffer.wrap(new byte[PPAYLOAD_CONFIG.length * 4 + 3]).order(ByteOrder.LITTLE_ENDIAN);
+
+ for (int i : PPAYLOAD_CONFIG)
+ conf.putInt(i);
+
+ conf.putShort((short)0x0013);
+ conf.put((byte) 0x00);
+
+ sendPacket(new NvCtlPacket(PTYPE_CONFIG, PPAYLEN_CONFIG, conf.array()));
+ }
+
+ private void sendHeartbeat() throws IOException
+ {
+ sendPacket(new NvCtlPacket(PTYPE_HEARTBEAT, PPAYLEN_HEARTBEAT));
+ }
+
+ private NvControl.NvCtlResponse pingPong() throws IOException
+ {
+ sendPacket(new NvCtlPacket(PTYPE_KEEPALIVE, PPAYLEN_KEEPALIVE));
+ return new NvControl.NvCtlResponse(in);
+ }
+
+ class NvCtlPacket {
+ public short type;
+ public short paylen;
+ public byte[] payload;
+
+ public NvCtlPacket(InputStream in) throws IOException
+ {
+ byte[] header = new byte[4];
+
+ int offset = 0;
+ do
+ {
+ offset = in.read(header, offset, header.length - offset);
+ } while (offset != header.length);
+
+ ByteBuffer bb = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN);
+
+ type = bb.getShort();
+ paylen = bb.getShort();
+
+ if (paylen != 0)
+ {
+ payload = new byte[paylen];
+
+ offset = 0;
+ do
+ {
+ offset = in.read(payload, offset, payload.length - offset);
+ } while (offset != payload.length);
+ }
+ }
+
+ public NvCtlPacket(byte[] payload)
+ {
+ ByteBuffer bb = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
+
+ type = bb.getShort();
+ paylen = bb.getShort();
+
+ if (bb.hasRemaining())
+ {
+ payload = new byte[bb.remaining()];
+ bb.get(payload);
+ }
+ }
+
+ public NvCtlPacket(short type, short paylen)
+ {
+ this.type = type;
+ this.paylen = paylen;
+ }
+
+ public NvCtlPacket(short type, short paylen, byte[] payload)
+ {
+ this.type = type;
+ this.paylen = paylen;
+ this.payload = payload;
+ }
+
+ public short getType()
+ {
+ return type;
+ }
+
+ public short getPaylen()
+ {
+ return paylen;
+ }
+
+ public void setType(short type)
+ {
+ this.type = type;
+ }
+
+ public void setPaylen(short paylen)
+ {
+ this.paylen = paylen;
+ }
+
+ public byte[] toWire()
+ {
+ ByteBuffer bb = ByteBuffer.allocate(4 + (payload != null ? payload.length : 0)).order(ByteOrder.LITTLE_ENDIAN);
+
+ bb.putShort(type);
+ bb.putShort(paylen);
+
+ if (payload != null)
+ bb.put(payload);
+
+ return bb.array();
+ }
+ }
+
+ class NvCtlResponse extends NvCtlPacket {
+ public short status;
+
+ public NvCtlResponse(InputStream in) throws IOException {
+ super(in);
+ }
+
+ public NvCtlResponse(short type, short paylen) {
+ super(type, paylen);
+ }
+
+ public NvCtlResponse(short type, short paylen, byte[] payload) {
+ super(type, paylen, payload);
+ }
+
+ public NvCtlResponse(byte[] payload) {
+ super(payload);
+ }
+
+ public void setStatusCode(short status)
+ {
+ this.status = status;
+ }
+
+ public short getStatusCode()
+ {
+ return status;
+ }
+ }
+}
diff --git a/src/com/limelight/nvstream/NvController.java b/src/com/limelight/nvstream/NvController.java
new file mode 100644
index 00000000..c2a88659
--- /dev/null
+++ b/src/com/limelight/nvstream/NvController.java
@@ -0,0 +1,40 @@
+package com.limelight.nvstream;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class NvController {
+
+ public final static int PORT = 35043;
+
+ private Socket s;
+ private OutputStream out;
+
+ public NvController(String host) throws UnknownHostException, IOException
+ {
+ s = new Socket(host, PORT);
+ out = s.getOutputStream();
+ }
+
+ // Example
+ public void sendLeftButton() throws IOException
+ {
+ out.write(new NvInputPacket(NvInputPacket.LEFT_FLAG, (byte)0, (byte)0, (short)0, (short)0).toWire());
+ }
+
+ // Example
+ public void sendRightButton() throws IOException
+ {
+ out.write(new NvInputPacket(NvInputPacket.RIGHT_FLAG, (byte)0, (byte)0, (short)0, (short)0).toWire());
+ }
+
+ // Example
+ public void clearButtons() throws IOException
+ {
+ out.write(new NvInputPacket((short)0, (byte)0, (byte)0, (short)0, (short)0).toWire());
+ }
+}
diff --git a/src/com/limelight/nvstream/NvHandshake.java b/src/com/limelight/nvstream/NvHandshake.java
new file mode 100644
index 00000000..510ec648
--- /dev/null
+++ b/src/com/limelight/nvstream/NvHandshake.java
@@ -0,0 +1,107 @@
+package com.limelight.nvstream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.UnknownHostException;;
+
+public class NvHandshake {
+ public static final int PORT = 47991;
+
+ // android
+ public static final byte[] PLATFORM_HELLO =
+ {
+ (byte)0x61,
+ (byte)0x6e,
+ (byte)0x64,
+ (byte)0x72,
+ (byte)0x6f,
+ (byte)0x69,
+ (byte)0x64,
+ (byte)0x03,
+ (byte)0x01,
+ (byte)0x00,
+ (byte)0x00
+ };
+
+ public static final byte[] PACKET_2 =
+ {
+ (byte)0x01,
+ (byte)0x03,
+ (byte)0x02,
+ (byte)0x00,
+ (byte)0x08,
+ (byte)0x00
+ };
+
+ public static final byte[] PACKET_3 =
+ {
+ (byte)0x04,
+ (byte)0x01,
+ (byte)0x00,
+ (byte)0x00,
+
+ (byte)0x00,
+ (byte)0x00,
+ (byte)0x00,
+ (byte)0x00
+ };
+
+ public static final byte[] PACKET_4 =
+ {
+ (byte)0x01,
+ (byte)0x01,
+ (byte)0x00,
+ (byte)0x00
+ };
+
+ private static void waitAndDiscardResponse(InputStream in) throws IOException
+ {
+ // 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();
+ }
+
+ public static void performHandshake(String host) throws UnknownHostException, IOException
+ {
+ Socket s = new Socket(host, PORT);
+ OutputStream out = s.getOutputStream();
+ InputStream in = s.getInputStream();
+
+ // First packet
+ out.write(new byte[]{0x07, 0x00, 0x00, 0x00});
+ out.write(PLATFORM_HELLO);
+
+ System.out.println("HS: Waiting for hello response");
+
+ waitAndDiscardResponse(in);
+
+ // Second packet
+ out.write(PACKET_2);
+
+ System.out.println("HS: Waiting stage 2 response");
+
+ waitAndDiscardResponse(in);
+
+ // Third packet
+ out.write(PACKET_3);
+
+ System.out.println("HS: Waiting for stage 3 response");
+
+ waitAndDiscardResponse(in);
+
+ // Fourth packet
+ out.write(PACKET_4);
+ out.flush();
+
+ // Done
+ s.close();
+ }
+}
diff --git a/src/com/limelight/nvstream/NvInputPacket.java b/src/com/limelight/nvstream/NvInputPacket.java
new file mode 100644
index 00000000..642fbd35
--- /dev/null
+++ b/src/com/limelight/nvstream/NvInputPacket.java
@@ -0,0 +1,78 @@
+package com.limelight.nvstream;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class NvInputPacket {
+ public static final byte[] HEADER =
+ {
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x18,
+ 0x0A,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x14
+ };
+
+ public static final byte[] TAIL =
+ {
+ (byte)0x9C,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x55,
+ 0x00
+ };
+
+ public static final short A_FLAG = 0x1000;
+ public static final short B_FLAG = 0x2000;
+ public static final short X_FLAG = 0x4000;
+ public static final short Y_FLAG = (short)0x8000;
+ public static final short UP_FLAG = 0x0001;
+ public static final short DOWN_FLAG = 0x0002;
+ public static final short LEFT_FLAG = 0x0004;
+ public static final short RIGHT_FLAG = 0x0008;
+ public static final short RB_FLAG = 0x0100;
+ public static final short LB_FLAG = 0x0200;
+ public static final short LS_CLK_FLAG = 0x0004;
+ public static final short RS_CLK_FLAG = 0x0008;
+ public static final short PLAY_FLAG = 0x0001;
+ public static final short BACK_FLAG = 0x0002;
+
+ public static final short PACKET_LENGTH = 28;
+
+ private short buttonFlags;
+ private byte leftTrigger;
+ private byte rightTrigger;
+ private short leftStick;
+ private short rightStick;
+
+ public NvInputPacket(short buttonFlags, byte leftTrigger, byte rightTrigger,
+ short leftStick, short rightStick)
+ {
+ this.buttonFlags = buttonFlags;
+ this.leftTrigger = leftTrigger;
+ this.rightTrigger = rightTrigger;
+ this.leftStick = leftStick;
+ this.rightStick = rightStick;
+ }
+
+ public byte[] toWire()
+ {
+ ByteBuffer bb = ByteBuffer.allocate(PACKET_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
+
+ bb.put(HEADER);
+ bb.putShort(buttonFlags);
+ bb.put(leftTrigger);
+ bb.put(rightTrigger);
+ bb.putShort(leftStick);
+ bb.putShort(rightStick);
+ bb.put(TAIL);
+
+ return bb.array();
+ }
+ }
\ No newline at end of file
diff --git a/src/com/limelight/nvstream/NvVideoStream.java b/src/com/limelight/nvstream/NvVideoStream.java
new file mode 100644
index 00000000..4a2ee3b4
--- /dev/null
+++ b/src/com/limelight/nvstream/NvVideoStream.java
@@ -0,0 +1,71 @@
+package com.limelight.nvstream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+public class NvVideoStream {
+ public static final int PORT = 47998;
+ public static final int FIRST_FRAME_PORT = 47996;
+
+ private InputStream getFirstFrame(String host) throws UnknownHostException, IOException
+ {
+ Socket s = new Socket(host, FIRST_FRAME_PORT);
+ return s.getInputStream();
+ }
+
+ public void start(final String host)
+ {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ System.out.println("VID: Waiting for first frame");
+ InputStream firstFrameStream = getFirstFrame(host);
+ firstFrameStream.read();
+ System.out.println("VID: First frame: "+firstFrameStream.available()+1);
+ firstFrameStream.close();
+ System.out.println("VID: Got first frame");
+ } catch (UnknownHostException e2) {
+ // TODO Auto-generated catch block
+ e2.printStackTrace();
+ return;
+ } catch (IOException e2) {
+ // TODO Auto-generated catch block
+ e2.printStackTrace();
+ return;
+ }
+
+ DatagramSocket ds;
+ try {
+ ds = new DatagramSocket(PORT);
+ } catch (SocketException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ return;
+ }
+
+ for (;;)
+ {
+ DatagramPacket dp = new DatagramPacket(new byte[1500], 1500);
+
+ try {
+ ds.receive(dp);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ break;
+ }
+
+ System.out.println("Got UDP 47998: "+dp.getLength());
+ }
+ }
+
+ }).start();
+ }
+}