mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-10-23 16:43:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c025f9f02b | ||
|
|
b737acedb0 | ||
|
|
f15bfe3038 | ||
|
|
8938f51292 | ||
|
|
4b92b8f714 | ||
|
|
5f13b9bca4 | ||
|
|
2f219aac6f | ||
|
|
1d9efb30e2 | ||
|
|
ed7be00881 | ||
|
|
a6003f6bff | ||
|
|
4619045375 | ||
|
|
e61b8f1b34 | ||
|
|
79b6ec839a | ||
|
|
fd12e30c53 | ||
|
|
87a9ca4318 | ||
|
|
3f64411174 | ||
|
|
57b0da1a3a | ||
|
|
7d3e74a67f | ||
|
|
d704e322df | ||
|
|
f598153818 | ||
|
|
f395a0c170 | ||
|
|
654b33d27f | ||
|
|
6c12da96c9 | ||
|
|
1a6f639b81 | ||
|
|
59a00a38c9 | ||
|
|
2beee168e3 |
No files matched your search
@@ -19,7 +19,7 @@ Check our [wiki](https://github.com/moonlight-stream/moonlight-android/wiki) for
|
|||||||
##Installation
|
##Installation
|
||||||
|
|
||||||
* Download and install Moonlight for Android from
|
* Download and install Moonlight for Android from
|
||||||
[Google Play](https://play.google.com/store/apps/details?id=com.limelight)
|
[Google Play](https://play.google.com/store/apps/details?id=com.limelight), [Amazon App Store](http://www.amazon.com/gp/product/B00JK4MFN2), or directly from the [releases page](https://github.com/moonlight-stream/moonlight-android/releases)
|
||||||
* Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC
|
* Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC
|
||||||
|
|
||||||
##Requirements
|
##Requirements
|
||||||
|
|||||||
+1
-1
@@ -114,7 +114,7 @@
|
|||||||
<orderEntry type="library" exported="" name="limelight-common" level="project" />
|
<orderEntry type="library" exported="" name="limelight-common" level="project" />
|
||||||
<orderEntry type="library" exported="" name="jmdns-3.4.2" level="project" />
|
<orderEntry type="library" exported="" name="jmdns-3.4.2" level="project" />
|
||||||
<orderEntry type="library" exported="" name="okhttp-2.4.0" level="project" />
|
<orderEntry type="library" exported="" name="okhttp-2.4.0" level="project" />
|
||||||
<orderEntry type="library" exported="" name="jcodec-0.1.9" level="project" />
|
<orderEntry type="library" exported="" name="jcodec-0.1.9-patched" level="project" />
|
||||||
<orderEntry type="library" exported="" name="okio-1.5.0" level="project" />
|
<orderEntry type="library" exported="" name="okio-1.5.0" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
+4
-5
@@ -5,14 +5,14 @@ apply plugin: 'com.android.application'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 23
|
||||||
buildToolsVersion "22.0.1"
|
buildToolsVersion "23.0.1"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
|
|
||||||
versionName "3.1.9"
|
versionName "3.1.11"
|
||||||
versionCode = 64
|
versionCode = 66
|
||||||
}
|
}
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
@@ -62,8 +62,6 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile group: 'org.jcodec', name: 'jcodec', version: '0.1.9'
|
|
||||||
|
|
||||||
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.52'
|
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.52'
|
||||||
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.52'
|
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.52'
|
||||||
|
|
||||||
@@ -73,4 +71,5 @@ dependencies {
|
|||||||
compile files('libs/jmdns-3.4.2.jar')
|
compile files('libs/jmdns-3.4.2.jar')
|
||||||
compile files('libs/limelight-common.jar')
|
compile files('libs/limelight-common.jar')
|
||||||
compile files('libs/tinyrtsp.jar')
|
compile files('libs/tinyrtsp.jar')
|
||||||
|
compile files('libs/jcodec-0.1.9-patched.jar')
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -520,9 +520,56 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||||
{
|
{
|
||||||
|
// This case is for mice
|
||||||
|
if (event.getSource() == InputDevice.SOURCE_MOUSE)
|
||||||
|
{
|
||||||
|
int changedButtons = event.getButtonState() ^ lastButtonState;
|
||||||
|
|
||||||
|
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||||
|
// Send the vertical scroll packet
|
||||||
|
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||||
|
conn.sendMouseScroll(vScrollClicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||||
|
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
|
||||||
|
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
|
||||||
|
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
|
||||||
|
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First process the history
|
||||||
|
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||||
|
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now process the current values
|
||||||
|
updateMousePosition((int)event.getX(), (int)event.getY());
|
||||||
|
|
||||||
|
lastButtonState = event.getButtonState();
|
||||||
|
}
|
||||||
// This case is for touch-based input devices
|
// This case is for touch-based input devices
|
||||||
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN ||
|
else
|
||||||
event.getSource() == InputDevice.SOURCE_STYLUS)
|
|
||||||
{
|
{
|
||||||
int actionIndex = event.getActionIndex();
|
int actionIndex = event.getActionIndex();
|
||||||
|
|
||||||
@@ -601,59 +648,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This case is for mice
|
|
||||||
else if (event.getSource() == InputDevice.SOURCE_MOUSE)
|
|
||||||
{
|
|
||||||
int changedButtons = event.getButtonState() ^ lastButtonState;
|
|
||||||
|
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
|
||||||
// Send the vertical scroll packet
|
|
||||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
|
||||||
conn.sendMouseScroll(vScrollClicks);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
|
||||||
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
|
|
||||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
|
|
||||||
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
|
|
||||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
|
|
||||||
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
|
|
||||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First process the history
|
|
||||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
|
||||||
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now process the current values
|
|
||||||
updateMousePosition((int)event.getX(), (int)event.getY());
|
|
||||||
|
|
||||||
lastButtonState = event.getButtonState();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Unknown source
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handled a known source
|
// Handled a known source
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
private RelativeLayout noPcFoundLayout;
|
private RelativeLayout noPcFoundLayout;
|
||||||
private PcGridAdapter pcGridAdapter;
|
private PcGridAdapter pcGridAdapter;
|
||||||
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
private ComputerManagerService.ComputerManagerBinder managerBinder;
|
||||||
private boolean freezeUpdates, runningPolling;
|
private boolean freezeUpdates, runningPolling, hasResumed;
|
||||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
public void onServiceConnected(ComponentName className, IBinder binder) {
|
public void onServiceConnected(ComponentName className, IBinder binder) {
|
||||||
final ComputerManagerService.ComputerManagerBinder localBinder =
|
final ComputerManagerService.ComputerManagerBinder localBinder =
|
||||||
@@ -215,6 +215,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
hasResumed = true;
|
||||||
startComputerUpdates();
|
startComputerUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +223,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
|
||||||
|
hasResumed = false;
|
||||||
stopComputerUpdates(false);
|
stopComputerUpdates(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,13 +243,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
|
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
|
||||||
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
|
||||||
startComputerUpdates();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inflate the context menu
|
// Inflate the context menu
|
||||||
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE ||
|
||||||
|
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||||
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
|
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
|
||||||
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
|
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
|
||||||
}
|
}
|
||||||
@@ -271,7 +270,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContextMenuClosed(Menu menu) {
|
public void onContextMenuClosed(Menu menu) {
|
||||||
startComputerUpdates();
|
// For some reason, this gets called again _after_ onPause() is called on this activity.
|
||||||
|
// We don't want to start computer updates again, so we need to keep track of whether we're paused.
|
||||||
|
if (hasResumed) {
|
||||||
|
startComputerUpdates();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doPair(final ComputerDetails computer) {
|
private void doPair(final ComputerDetails computer) {
|
||||||
@@ -378,7 +381,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doWakeOnLan(final ComputerDetails computer) {
|
private void doWakeOnLan(final ComputerDetails computer) {
|
||||||
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) {
|
if (computer.state == ComputerDetails.State.ONLINE) {
|
||||||
Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show();
|
Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -614,10 +617,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
|||||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||||
long id) {
|
long id) {
|
||||||
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
|
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
|
||||||
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN ||
|
||||||
// Do nothing
|
computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||||
} else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
// Open the context menu if a PC is offline or refreshing
|
||||||
// Open the context menu if a PC is offline
|
|
||||||
openContextMenu(arg1);
|
openContextMenu(arg1);
|
||||||
} else if (computer.details.pairState != PairState.PAIRED) {
|
} else if (computer.details.pairState != PairState.PAIRED) {
|
||||||
// Pair an unpaired machine by default
|
// Pair an unpaired machine by default
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package com.limelight.binding.input;
|
|||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class TouchContext {
|
public class TouchContext {
|
||||||
private int lastTouchX = 0;
|
private int lastTouchX = 0;
|
||||||
private int lastTouchY = 0;
|
private int lastTouchY = 0;
|
||||||
@@ -10,14 +13,20 @@ public class TouchContext {
|
|||||||
private int originalTouchY = 0;
|
private int originalTouchY = 0;
|
||||||
private long originalTouchTime = 0;
|
private long originalTouchTime = 0;
|
||||||
private boolean cancelled;
|
private boolean cancelled;
|
||||||
|
private boolean confirmedMove;
|
||||||
|
private boolean confirmedDrag;
|
||||||
|
private Timer dragTimer;
|
||||||
|
private double distanceMoved;
|
||||||
|
|
||||||
private final NvConnection conn;
|
private final NvConnection conn;
|
||||||
private final int actionIndex;
|
private final int actionIndex;
|
||||||
private final double xFactor;
|
private final double xFactor;
|
||||||
private final double yFactor;
|
private final double yFactor;
|
||||||
|
|
||||||
private static final int TAP_MOVEMENT_THRESHOLD = 10;
|
private static final int TAP_MOVEMENT_THRESHOLD = 20;
|
||||||
|
private static final int TAP_DISTANCE_THRESHOLD = 25;
|
||||||
private static final int TAP_TIME_THRESHOLD = 250;
|
private static final int TAP_TIME_THRESHOLD = 250;
|
||||||
|
private static final int DRAG_TIME_THRESHOLD = 650;
|
||||||
|
|
||||||
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
|
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
|
||||||
{
|
{
|
||||||
@@ -32,15 +41,19 @@ public class TouchContext {
|
|||||||
return actionIndex;
|
return actionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isWithinTapBounds(int touchX, int touchY)
|
||||||
|
{
|
||||||
|
int xDelta = Math.abs(touchX - originalTouchX);
|
||||||
|
int yDelta = Math.abs(touchY - originalTouchY);
|
||||||
|
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
|
||||||
|
yDelta <= TAP_MOVEMENT_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isTap()
|
private boolean isTap()
|
||||||
{
|
{
|
||||||
int xDelta = Math.abs(lastTouchX - originalTouchX);
|
|
||||||
int yDelta = Math.abs(lastTouchY - originalTouchY);
|
|
||||||
long timeDelta = System.currentTimeMillis() - originalTouchTime;
|
long timeDelta = System.currentTimeMillis() - originalTouchTime;
|
||||||
|
|
||||||
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
|
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
|
||||||
yDelta <= TAP_MOVEMENT_THRESHOLD &&
|
|
||||||
timeDelta <= TAP_TIME_THRESHOLD;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte getMouseButtonIndex()
|
private byte getMouseButtonIndex()
|
||||||
@@ -58,7 +71,13 @@ public class TouchContext {
|
|||||||
originalTouchX = lastTouchX = eventX;
|
originalTouchX = lastTouchX = eventX;
|
||||||
originalTouchY = lastTouchY = eventY;
|
originalTouchY = lastTouchY = eventY;
|
||||||
originalTouchTime = System.currentTimeMillis();
|
originalTouchTime = System.currentTimeMillis();
|
||||||
cancelled = false;
|
cancelled = confirmedDrag = confirmedMove = false;
|
||||||
|
distanceMoved = 0;
|
||||||
|
|
||||||
|
if (actionIndex == 0) {
|
||||||
|
// Start the timer for engaging a drag
|
||||||
|
startDragTimer();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -69,10 +88,17 @@ public class TouchContext {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTap())
|
// Cancel the drag timer
|
||||||
{
|
cancelDragTimer();
|
||||||
byte buttonIndex = getMouseButtonIndex();
|
|
||||||
|
|
||||||
|
byte buttonIndex = getMouseButtonIndex();
|
||||||
|
|
||||||
|
if (confirmedDrag) {
|
||||||
|
// Raise the button after a drag
|
||||||
|
conn.sendMouseButtonUp(buttonIndex);
|
||||||
|
}
|
||||||
|
else if (isTap())
|
||||||
|
{
|
||||||
// Lower the mouse button
|
// Lower the mouse button
|
||||||
conn.sendMouseButtonDown(buttonIndex);
|
conn.sendMouseButtonDown(buttonIndex);
|
||||||
|
|
||||||
@@ -87,24 +113,101 @@ public class TouchContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void startDragTimer() {
|
||||||
|
dragTimer = new Timer(true);
|
||||||
|
dragTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (TouchContext.this) {
|
||||||
|
// Check if someone already set move
|
||||||
|
if (confirmedMove) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if someone cancelled us
|
||||||
|
if (dragTimer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncancellable now
|
||||||
|
dragTimer = null;
|
||||||
|
|
||||||
|
// We haven't been cancelled before the timer expired so begin dragging
|
||||||
|
confirmedDrag = true;
|
||||||
|
conn.sendMouseButtonDown(getMouseButtonIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, DRAG_TIME_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void cancelDragTimer() {
|
||||||
|
if (dragTimer != null) {
|
||||||
|
dragTimer.cancel();
|
||||||
|
dragTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void checkForConfirmedMove(int eventX, int eventY) {
|
||||||
|
// If we've already confirmed something, get out now
|
||||||
|
if (confirmedMove || confirmedDrag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it leaves the tap bounds before the drag time expires, it's a move.
|
||||||
|
if (!isWithinTapBounds(eventX, eventY)) {
|
||||||
|
confirmedMove = true;
|
||||||
|
cancelDragTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we've exceeded the maximum distance moved
|
||||||
|
distanceMoved += Math.sqrt(Math.pow(eventX - lastTouchX, 2) + Math.pow(eventY - lastTouchY, 2));
|
||||||
|
if (distanceMoved >= TAP_DISTANCE_THRESHOLD) {
|
||||||
|
confirmedMove = true;
|
||||||
|
cancelDragTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean touchMoveEvent(int eventX, int eventY)
|
public boolean touchMoveEvent(int eventX, int eventY)
|
||||||
{
|
{
|
||||||
if (eventX != lastTouchX || eventY != lastTouchY)
|
if (eventX != lastTouchX || eventY != lastTouchY)
|
||||||
{
|
{
|
||||||
// We only send moves for the primary touch point
|
// We only send moves and drags for the primary touch point
|
||||||
if (actionIndex == 0) {
|
if (actionIndex == 0) {
|
||||||
|
checkForConfirmedMove(eventX, eventY);
|
||||||
|
|
||||||
int deltaX = eventX - lastTouchX;
|
int deltaX = eventX - lastTouchX;
|
||||||
int deltaY = eventY - lastTouchY;
|
int deltaY = eventY - lastTouchY;
|
||||||
|
|
||||||
// Scale the deltas based on the factors passed to our constructor
|
// Scale the deltas based on the factors passed to our constructor
|
||||||
deltaX = (int)Math.round((double)deltaX * xFactor);
|
deltaX = (int)Math.round((double)Math.abs(deltaX) * xFactor);
|
||||||
deltaY = (int)Math.round((double)deltaY * yFactor);
|
deltaY = (int)Math.round((double)Math.abs(deltaY) * yFactor);
|
||||||
|
|
||||||
|
// Fix up the signs
|
||||||
|
if (eventX < lastTouchX) {
|
||||||
|
deltaX = -deltaX;
|
||||||
|
}
|
||||||
|
if (eventY < lastTouchY) {
|
||||||
|
deltaY = -deltaY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the scaling factor ended up rounding deltas to zero, wait until they are
|
||||||
|
// non-zero to update lastTouch that way devices that report small touch events often
|
||||||
|
// will work correctly
|
||||||
|
if (deltaX != 0) {
|
||||||
|
lastTouchX = eventX;
|
||||||
|
}
|
||||||
|
if (deltaY != 0) {
|
||||||
|
lastTouchY = eventY;
|
||||||
|
}
|
||||||
|
|
||||||
conn.sendMouseMove((short)deltaX, (short)deltaY);
|
conn.sendMouseMove((short)deltaX, (short)deltaY);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
lastTouchX = eventX;
|
lastTouchX = eventX;
|
||||||
lastTouchY = eventY;
|
lastTouchY = eventY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -112,6 +215,14 @@ public class TouchContext {
|
|||||||
|
|
||||||
public void cancelTouch() {
|
public void cancelTouch() {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
|
|
||||||
|
// Cancel the drag timer
|
||||||
|
cancelDragTimer();
|
||||||
|
|
||||||
|
// If it was a confirmed drag, we'll need to raise the button now
|
||||||
|
if (confirmedDrag) {
|
||||||
|
conn.sendMouseButtonUp(getMouseButtonIndex());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCancelled() {
|
public boolean isCancelled() {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
private final boolean needsSpsBitstreamFixup, isExynos4;
|
private final boolean needsSpsBitstreamFixup, isExynos4;
|
||||||
private VideoDepacketizer depacketizer;
|
private VideoDepacketizer depacketizer;
|
||||||
private final boolean adaptivePlayback, directSubmit;
|
private final boolean adaptivePlayback, directSubmit;
|
||||||
|
private final boolean constrainedHighProfile;
|
||||||
private int initialWidth, initialHeight;
|
private int initialWidth, initialHeight;
|
||||||
|
|
||||||
private boolean needsBaselineSpsHack;
|
private boolean needsBaselineSpsHack;
|
||||||
@@ -56,7 +57,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
if (decoder == null) {
|
if (decoder == null) {
|
||||||
// This case is handled later in setup()
|
// This case is handled later in setup()
|
||||||
needsSpsBitstreamFixup = isExynos4 =
|
needsSpsBitstreamFixup = isExynos4 =
|
||||||
adaptivePlayback = directSubmit = false;
|
adaptivePlayback = directSubmit =
|
||||||
|
constrainedHighProfile = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +69,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder);
|
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder);
|
||||||
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder);
|
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder);
|
||||||
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(decoderName, decoder);
|
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(decoderName, decoder);
|
||||||
|
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(decoderName, decoder);
|
||||||
isExynos4 = MediaCodecHelper.isExynos4Device();
|
isExynos4 = MediaCodecHelper.isExynos4Device();
|
||||||
if (needsSpsBitstreamFixup) {
|
if (needsSpsBitstreamFixup) {
|
||||||
LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup");
|
LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup");
|
||||||
@@ -74,6 +77,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
if (needsBaselineSpsHack) {
|
if (needsBaselineSpsHack) {
|
||||||
LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack");
|
LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack");
|
||||||
}
|
}
|
||||||
|
if (constrainedHighProfile) {
|
||||||
|
LimeLog.info("Decoder "+decoderName+" needs constrained high profile");
|
||||||
|
}
|
||||||
if (isExynos4) {
|
if (isExynos4) {
|
||||||
LimeLog.info("Decoder "+decoderName+" is on Exynos 4");
|
LimeLog.info("Decoder "+decoderName+" is on Exynos 4");
|
||||||
}
|
}
|
||||||
@@ -433,6 +439,23 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void doProfileSpecificSpsPatching(SeqParameterSet sps) {
|
||||||
|
// Some devices benefit from setting constraint flags 4 & 5 to make this Constrained
|
||||||
|
// High Profile which allows the decoder to assume there will be no B-frames and
|
||||||
|
// reduce delay and buffering accordingly. Some devices (Marvell, Exynos 4) don't
|
||||||
|
// like it so we only set them on devices that are confirmed to benefit from it.
|
||||||
|
if (sps.profile_idc == 100 && constrainedHighProfile) {
|
||||||
|
LimeLog.info("Setting constraint set flags for constrained high profile");
|
||||||
|
sps.constraint_set_4_flag = true;
|
||||||
|
sps.constraint_set_5_flag = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Force the constraints unset otherwise (some may be set by default)
|
||||||
|
sps.constraint_set_4_flag = false;
|
||||||
|
sps.constraint_set_5_flag = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void submitDecodeUnit(DecodeUnit decodeUnit, int inputBufferIndex) {
|
private void submitDecodeUnit(DecodeUnit decodeUnit, int inputBufferIndex) {
|
||||||
long timestampUs = System.nanoTime() / 1000;
|
long timestampUs = System.nanoTime() / 1000;
|
||||||
@@ -499,12 +522,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
// Some devices don't like these so we remove them here.
|
// Some devices don't like these so we remove them here.
|
||||||
sps.vuiParams.video_signal_type_present_flag = false;
|
sps.vuiParams.video_signal_type_present_flag = false;
|
||||||
sps.vuiParams.colour_description_present_flag = false;
|
sps.vuiParams.colour_description_present_flag = false;
|
||||||
sps.vuiParams.colour_primaries = 2;
|
|
||||||
sps.vuiParams.transfer_characteristics = 2;
|
|
||||||
sps.vuiParams.matrix_coefficients = 2;
|
|
||||||
sps.vuiParams.chroma_loc_info_present_flag = false;
|
sps.vuiParams.chroma_loc_info_present_flag = false;
|
||||||
sps.vuiParams.chroma_sample_loc_type_bottom_field = 0;
|
|
||||||
sps.vuiParams.chroma_sample_loc_type_top_field = 0;
|
|
||||||
|
|
||||||
if (needsSpsBitstreamFixup || isExynos4) {
|
if (needsSpsBitstreamFixup || isExynos4) {
|
||||||
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
||||||
@@ -547,6 +565,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
savedSps = sps;
|
savedSps = sps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Patch the SPS constraint flags
|
||||||
|
doProfileSpecificSpsPatching(sps);
|
||||||
|
|
||||||
// Write the annex B header
|
// Write the annex B header
|
||||||
buf.put(header.data, header.offset, 5);
|
buf.put(header.data, header.offset, 5);
|
||||||
|
|
||||||
@@ -599,6 +620,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
// Switch the H264 profile back to high
|
// Switch the H264 profile back to high
|
||||||
savedSps.profile_idc = 100;
|
savedSps.profile_idc = 100;
|
||||||
|
|
||||||
|
// Patch the SPS constraint flags
|
||||||
|
doProfileSpecificSpsPatching(savedSps);
|
||||||
|
|
||||||
// Write the SPS data
|
// Write the SPS data
|
||||||
savedSps.write(inputBuffer);
|
savedSps.write(inputBuffer);
|
||||||
|
|
||||||
@@ -704,6 +728,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||||
str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
|
str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
|
||||||
str += "Total frames: "+renderer.totalFrames+"\n";
|
str += "Total frames: "+renderer.totalFrames+"\n";
|
||||||
|
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
|
||||||
|
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
|
||||||
|
|
||||||
if (currentBuffer != null) {
|
if (currentBuffer != null) {
|
||||||
str += "Current buffer: ";
|
str += "Current buffer: ";
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public class MediaCodecHelper {
|
|||||||
private static final List<String> whitelistedAdaptiveResolutionPrefixes;
|
private static final List<String> whitelistedAdaptiveResolutionPrefixes;
|
||||||
private static final List<String> baselineProfileHackPrefixes;
|
private static final List<String> baselineProfileHackPrefixes;
|
||||||
private static final List<String> directSubmitPrefixes;
|
private static final List<String> directSubmitPrefixes;
|
||||||
|
private static final List<String> constrainedHighProfilePrefixes;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
directSubmitPrefixes = new LinkedList<String>();
|
directSubmitPrefixes = new LinkedList<String>();
|
||||||
@@ -58,7 +59,6 @@ public class MediaCodecHelper {
|
|||||||
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
|
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
|
||||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
|
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
|
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
|
||||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
|
|
||||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
|
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
|
||||||
|
|
||||||
baselineProfileHackPrefixes = new LinkedList<String>();
|
baselineProfileHackPrefixes = new LinkedList<String>();
|
||||||
@@ -69,6 +69,9 @@ public class MediaCodecHelper {
|
|||||||
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
|
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
|
||||||
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
|
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
|
||||||
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
|
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
|
||||||
|
|
||||||
|
constrainedHighProfilePrefixes = new LinkedList<String>();
|
||||||
|
constrainedHighProfilePrefixes.add("omx.intel");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||||
@@ -117,6 +120,10 @@ public class MediaCodecHelper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean decoderNeedsConstrainedHighProfile(String decoderName, MediaCodecInfo decoderInfo) {
|
||||||
|
return isDecoderInList(constrainedHighProfilePrefixes, decoderName);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean decoderCanDirectSubmit(String decoderName, MediaCodecInfo decoderInfo) {
|
public static boolean decoderCanDirectSubmit(String decoderName, MediaCodecInfo decoderInfo) {
|
||||||
return isDecoderInList(directSubmitPrefixes, decoderName) && !isExynos4Device();
|
return isDecoderInList(directSubmitPrefixes, decoderName) && !isExynos4Device();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,6 +368,11 @@ public class ComputerManagerService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
|
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
|
||||||
|
// Fast poll this address first to determine if we can connect at the TCP layer
|
||||||
|
if (!fastPollIp(ipAddr)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
|
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
|
||||||
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||||
@@ -451,7 +456,7 @@ public class ComputerManagerService extends Service {
|
|||||||
return ComputerDetails.Reachability.OFFLINE;
|
return ComputerDetails.Reachability.OFFLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
|
||||||
ComputerDetails polledDetails;
|
ComputerDetails polledDetails;
|
||||||
ComputerDetails.Reachability reachability;
|
ComputerDetails.Reachability reachability;
|
||||||
|
|
||||||
@@ -465,11 +470,11 @@ public class ComputerManagerService extends Service {
|
|||||||
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localIp+", "+details.remoteIp+")");
|
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localIp+", "+details.remoteIp+")");
|
||||||
reachability = fastPollPc(details.localIp, details.remoteIp);
|
reachability = fastPollPc(details.localIp, details.remoteIp);
|
||||||
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
|
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
|
||||||
}
|
|
||||||
|
|
||||||
// If no connection could be established to either IP address, there's nothing we can do
|
// If no connection could be established to either IP address, there's nothing we can do
|
||||||
if (reachability == ComputerDetails.Reachability.OFFLINE) {
|
if (reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||||
return false;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
|
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
|
||||||
@@ -481,6 +486,7 @@ public class ComputerManagerService extends Service {
|
|||||||
polledDetails = tryPollIp(details, details.remoteIp);
|
polledDetails = tryPollIp(details, details.remoteIp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InetAddress reachableAddr = null;
|
||||||
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
|
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
|
||||||
// Failed, so let's try the fallback
|
// Failed, so let's try the fallback
|
||||||
if (!localFirst) {
|
if (!localFirst) {
|
||||||
@@ -490,27 +496,59 @@ public class ComputerManagerService extends Service {
|
|||||||
polledDetails = tryPollIp(details, details.remoteIp);
|
polledDetails = tryPollIp(details, details.remoteIp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The fallback poll worked
|
|
||||||
if (polledDetails != null) {
|
if (polledDetails != null) {
|
||||||
polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL :
|
// The fallback poll worked
|
||||||
ComputerDetails.Reachability.REMOTE;
|
reachableAddr = !localFirst ? details.localIp : details.remoteIp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (polledDetails != null) {
|
else if (polledDetails != null) {
|
||||||
polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL :
|
reachableAddr = localFirst ? details.localIp : details.remoteIp;
|
||||||
ComputerDetails.Reachability.REMOTE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Machine was unreachable both tries
|
if (reachableAddr == null) {
|
||||||
if (polledDetails == null) {
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (polledDetails.remoteIp.equals(reachableAddr)) {
|
||||||
|
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
|
||||||
|
}
|
||||||
|
else if (polledDetails.localIp.equals(reachableAddr)) {
|
||||||
|
polledDetails.reachability = ComputerDetails.Reachability.LOCAL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
polledDetails.reachability = ComputerDetails.Reachability.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ReachabilityTuple(polledDetails, reachableAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
||||||
|
ReachabilityTuple initialReachTuple = pollForReachability(details);
|
||||||
|
if (initialReachTuple == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initialReachTuple.computer.reachability == ComputerDetails.Reachability.UNKNOWN) {
|
||||||
|
// Neither IP address reported in the serverinfo response was the one we used.
|
||||||
|
// Poll again to see if we can contact this machine on either of its reported addresses.
|
||||||
|
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer);
|
||||||
|
if (confirmationReachTuple == null) {
|
||||||
|
// Neither of those seem to work, so we'll hold onto the address that did work
|
||||||
|
initialReachTuple.computer.localIp = initialReachTuple.reachableAddress;
|
||||||
|
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We got it on one of the returned addresses; replace the original reach tuple
|
||||||
|
// with the new one
|
||||||
|
initialReachTuple = confirmationReachTuple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save the old MAC address
|
// Save the old MAC address
|
||||||
String savedMacAddress = details.macAddress;
|
String savedMacAddress = details.macAddress;
|
||||||
|
|
||||||
// If we got here, it's reachable
|
// If we got here, it's reachable
|
||||||
details.update(polledDetails);
|
details.update(initialReachTuple.computer);
|
||||||
|
|
||||||
// If the new MAC address is empty, restore the old one (workaround for GFE bug)
|
// If the new MAC address is empty, restore the old one (workaround for GFE bug)
|
||||||
if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) {
|
if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) {
|
||||||
@@ -685,4 +723,14 @@ class PollingTuple {
|
|||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.thread = thread;
|
this.thread = thread;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReachabilityTuple {
|
||||||
|
public final InetAddress reachableAddress;
|
||||||
|
public final ComputerDetails computer;
|
||||||
|
|
||||||
|
public ReachabilityTuple(ComputerDetails computer, InetAddress reachableAddress) {
|
||||||
|
this.computer = computer;
|
||||||
|
this.reachableAddress = reachableAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,9 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class DiskAssetLoader {
|
public class DiskAssetLoader {
|
||||||
|
// 5 MB
|
||||||
|
private final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
private final File cacheDir;
|
private final File cacheDir;
|
||||||
|
|
||||||
public DiskAssetLoader(File cacheDir) {
|
public DiskAssetLoader(File cacheDir) {
|
||||||
@@ -27,13 +30,19 @@ public class DiskAssetLoader {
|
|||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
Bitmap bmp = null;
|
Bitmap bmp = null;
|
||||||
try {
|
try {
|
||||||
|
// Make sure the cached asset doesn't exceed the maximum size
|
||||||
|
if (CacheHelper.getFileSize(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png") > MAX_ASSET_SIZE) {
|
||||||
|
LimeLog.warning("Removing cached tuple exceeding size threshold: "+tuple);
|
||||||
|
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
in = CacheHelper.openCacheFileForInput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
in = CacheHelper.openCacheFileForInput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inSampleSize = sampleSize;
|
options.inSampleSize = sampleSize;
|
||||||
|
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||||
bmp = BitmapFactory.decodeStream(in, null, options);
|
bmp = BitmapFactory.decodeStream(in, null, options);
|
||||||
} catch (FileNotFoundException ignored) {
|
} catch (IOException ignored) {
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
} finally {
|
||||||
if (in != null) {
|
if (in != null) {
|
||||||
try {
|
try {
|
||||||
@@ -51,9 +60,11 @@ public class DiskAssetLoader {
|
|||||||
|
|
||||||
public void populateCacheWithStream(CachedAppAssetLoader.LoaderTuple tuple, InputStream input) {
|
public void populateCacheWithStream(CachedAppAssetLoader.LoaderTuple tuple, InputStream input) {
|
||||||
OutputStream out = null;
|
OutputStream out = null;
|
||||||
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||||
CacheHelper.writeInputStreamToOutputStream(input, out);
|
CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE);
|
||||||
|
success = true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -62,6 +73,11 @@ public class DiskAssetLoader {
|
|||||||
out.close();
|
out.close();
|
||||||
} catch (IOException ignored) {}
|
} catch (IOException ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
LimeLog.warning("Unable to populate cache with tuple: "+tuple);
|
||||||
|
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,24 +90,7 @@ public class PreferenceConfiguration {
|
|||||||
|
|
||||||
public static int getDefaultBitrate(Context context) {
|
public static int getDefaultBitrate(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return getDefaultBitrate(prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS));
|
||||||
String str = prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS);
|
|
||||||
if (str.equals("720p30")) {
|
|
||||||
return BITRATE_DEFAULT_720_30;
|
|
||||||
}
|
|
||||||
else if (str.equals("720p60")) {
|
|
||||||
return BITRATE_DEFAULT_720_60;
|
|
||||||
}
|
|
||||||
else if (str.equals("1080p30")) {
|
|
||||||
return BITRATE_DEFAULT_1080_30;
|
|
||||||
}
|
|
||||||
else if (str.equals("1080p60")) {
|
|
||||||
return BITRATE_DEFAULT_1080_60;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Should never get here
|
|
||||||
return DEFAULT_BITRATE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getDecoderValue(Context context) {
|
private static int getDecoderValue(Context context) {
|
||||||
|
|||||||
@@ -16,14 +16,17 @@ import com.limelight.utils.UiHelper;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class StreamSettings extends Activity {
|
public class StreamSettings extends Activity {
|
||||||
|
private PreferenceConfiguration previousPrefs;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
String locale = PreferenceConfiguration.readPreferences(this).language;
|
previousPrefs = PreferenceConfiguration.readPreferences(this);
|
||||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
|
||||||
|
if (!previousPrefs.language.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||||
Configuration config = new Configuration(getResources().getConfiguration());
|
Configuration config = new Configuration(getResources().getConfiguration());
|
||||||
config.locale = new Locale(locale);
|
config.locale = new Locale(previousPrefs.language);
|
||||||
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,10 +42,16 @@ public class StreamSettings extends Activity {
|
|||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
finish();
|
finish();
|
||||||
|
|
||||||
// Restart the PC view to apply UI changes
|
// Check for changes that require a UI reload to take effect
|
||||||
Intent intent = new Intent(this, PcView.class);
|
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
if (newPrefs.listMode != previousPrefs.listMode ||
|
||||||
startActivity(intent, null);
|
newPrefs.smallIconMode != previousPrefs.smallIconMode ||
|
||||||
|
!newPrefs.language.equals(previousPrefs.language)) {
|
||||||
|
// Restart the PC view to apply UI changes
|
||||||
|
Intent intent = new Intent(this, PcView.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SettingsFragment extends PreferenceFragment {
|
public static class SettingsFragment extends PreferenceFragment {
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ public class CacheHelper {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long getFileSize(File root, String... path) {
|
||||||
|
return openPath(false, root, path).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean deleteCacheFile(File root, String... path) {
|
||||||
|
return openPath(false, root, path).delete();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean cacheFileExists(File root, String... path) {
|
public static boolean cacheFileExists(File root, String... path) {
|
||||||
return openPath(false, root, path).exists();
|
return openPath(false, root, path).exists();
|
||||||
}
|
}
|
||||||
@@ -42,11 +50,15 @@ public class CacheHelper {
|
|||||||
return new BufferedOutputStream(new FileOutputStream(openPath(true, root, path)));
|
return new BufferedOutputStream(new FileOutputStream(openPath(true, root, path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeInputStreamToOutputStream(InputStream in, OutputStream out) throws IOException {
|
public static void writeInputStreamToOutputStream(InputStream in, OutputStream out, long maxLength) throws IOException {
|
||||||
byte[] buf = new byte[4096];
|
byte[] buf = new byte[4096];
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
|
|
||||||
while ((bytesRead = in.read(buf)) != -1) {
|
while ((bytesRead = in.read(buf)) != -1) {
|
||||||
|
maxLength -= bytesRead;
|
||||||
|
if (maxLength <= 0) {
|
||||||
|
throw new IOException("Stream exceeded max size");
|
||||||
|
}
|
||||||
out.write(buf, 0, bytesRead);
|
out.write(buf, 0, bytesRead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-4
@@ -12,8 +12,8 @@ This file serves to document some of the decoder errata when using MediaCodec ha
|
|||||||
4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS on first I-frame
|
4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS on first I-frame
|
||||||
- Affected decoders: Qualcomm in GS3 on 4.3+, Exynos 4 at 1080p only
|
- Affected decoders: Qualcomm in GS3 on 4.3+, Exynos 4 at 1080p only
|
||||||
|
|
||||||
5. Some decoders will hang if max_dec_frame_buffering is not present
|
5. Some decoders will hang or crash if max_dec_frame_buffering is not present and level_idc is >= 50
|
||||||
- Affected decoders: MediaTek decoder in Fire HD 7 (2014)
|
- Affected decoders: MediaTek decoder in Fire HD 6/7 (2014)
|
||||||
|
|
||||||
6. Some decoders will hang if max_dec_frame_buffering IS present
|
6. Some decoders will hang if max_dec_frame_buffering IS present
|
||||||
- Affected decoders: Exynos 5 in Galaxy Note 10.1 (2014)
|
- Affected decoders: Exynos 5 in Galaxy Note 10.1 (2014)
|
||||||
@@ -21,5 +21,11 @@ This file serves to document some of the decoder errata when using MediaCodec ha
|
|||||||
7. Some decoders will not enter low latency mode if adaptive playback is enabled
|
7. Some decoders will not enter low latency mode if adaptive playback is enabled
|
||||||
- Affected decoders: Intel decoder in Nexus Player
|
- Affected decoders: Intel decoder in Nexus Player
|
||||||
|
|
||||||
8. Some decoders will not enter low latency mode if the profile isn't baseline in the first SPS.
|
8. Some decoders will not enter low latency mode if the profile isn't baseline in the first SPS because B-frames may be present.
|
||||||
- Affected decoders: Intel decoder in Nexus Player
|
- Affected decoders: Intel decoder in Nexus Player (prior to Android 6.0)
|
||||||
|
|
||||||
|
9. Some decoders will not enter low latency mode if the profile isn't constrained high profile because B-frames may be present.
|
||||||
|
- Affected decoders: Intel decoder in Nexus Player (after Android 6.0)
|
||||||
|
|
||||||
|
10. Some decoders actually suffer increased latency when max_dec_frame_buffering=1
|
||||||
|
- Affected decoders: MediaTek decoder in Fire TV 2015
|
||||||
Reference in new issue
Block a user