Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fafb8e0ff | |||
| fbcbe09255 | |||
| e336a4446a | |||
| ffb35b2cdd | |||
| 2d0af6281c | |||
| 472a7f6c8a | |||
| cd06559c66 | |||
| d833933aaa | |||
| dc3495d59b | |||
| e3a2e40043 | |||
| 31e1fb743e | |||
| bc59f11096 | |||
| 6d97775aa9 | |||
| 3fff34e08a | |||
| 15e856dccb | |||
| 07d04171c3 | |||
| 42bd93cb3a | |||
| 7d289f1134 |
+3
-3
@@ -5,14 +5,14 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '27.0.1'
|
||||
buildToolsVersion '27.0.3'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 27
|
||||
|
||||
versionName "5.6.1"
|
||||
versionCode = 139
|
||||
versionName "5.6.3"
|
||||
versionCode = 142
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
|
||||
@@ -302,7 +302,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setBitrate(prefConfig.bitrate * 1000)
|
||||
.setEnableSops(prefConfig.enableSops)
|
||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||
.setMaxPacketSize(remote ? 1024 : 1292)
|
||||
.setMaxPacketSize((remote || prefConfig.width <= 1920) ? 1024 : 1292)
|
||||
.setRemote(remote)
|
||||
.setHevcBitratePercentageMultiplier(75)
|
||||
.setHevcSupported(decoderRenderer.isHevcSupported())
|
||||
|
||||
@@ -176,14 +176,14 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
public void start() {}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// Immediately drop all pending data
|
||||
track.pause();
|
||||
track.flush();
|
||||
}
|
||||
public void stop() {}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// Immediately drop all pending data
|
||||
track.pause();
|
||||
track.flush();
|
||||
|
||||
track.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,10 +100,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
// for even required levels of HEVC.
|
||||
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||
if (decoderInfo != null) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork, requestedHdr)) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork)) {
|
||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
|
||||
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
|
||||
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr) {
|
||||
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
|
||||
}
|
||||
else {
|
||||
@@ -165,6 +166,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
refFrameInvalidationAvc = MediaCodecHelper.decoderSupportsRefFrameInvalidationAvc(avcDecoder.getName(), prefs.height);
|
||||
refFrameInvalidationHevc = MediaCodecHelper.decoderSupportsRefFrameInvalidationHevc(avcDecoder.getName());
|
||||
|
||||
if (consecutiveCrashCount % 2 == 1) {
|
||||
refFrameInvalidationAvc = refFrameInvalidationHevc = false;
|
||||
LimeLog.warning("Disabling RFI due to previous crash");
|
||||
}
|
||||
|
||||
if (directSubmit) {
|
||||
LimeLog.info("Decoder "+avcDecoder.getName()+" will use direct submit");
|
||||
}
|
||||
@@ -573,25 +579,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
// May be called already, but we'll call it now to be safe
|
||||
prepareForStop();
|
||||
|
||||
try {
|
||||
// Invalidate pending decode buffers
|
||||
videoDecoder.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Wait for the renderer thread to shut down
|
||||
try {
|
||||
rendererThread.join();
|
||||
} catch (InterruptedException ignored) { }
|
||||
|
||||
try {
|
||||
// Stop the video decoder
|
||||
videoDecoder.stop();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Halt the spinner threads
|
||||
stopSpinnerThreads();
|
||||
}
|
||||
@@ -657,6 +649,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
@Override
|
||||
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs) {
|
||||
if (stopping) {
|
||||
// Don't bother if we're stopping
|
||||
return MoonBridge.DR_OK;
|
||||
}
|
||||
|
||||
totalFramesReceived++;
|
||||
|
||||
// We can receive the same "frame" multiple times if it's an IDR frame.
|
||||
@@ -1043,6 +1040,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
str += "Build fingerprint: "+Build.FINGERPRINT+"\n";
|
||||
str += "Foreground: "+renderer.foreground+"\n";
|
||||
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
|
||||
str += "RFI active: "+renderer.refFrameInvalidationActive+"\n";
|
||||
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||
str += "FPS target: "+renderer.refreshRate+"\n";
|
||||
str += "Bitrate: "+renderer.prefs.bitrate+" Mbps \n";
|
||||
|
||||
@@ -292,6 +292,13 @@ public class MediaCodecHelper {
|
||||
if (videoHeight > 720 && isLowEndSnapdragon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This device seems to crash constantly at 720p, so try disabling
|
||||
// RFI to see if we can get that under control.
|
||||
if (Build.PRODUCT.equalsIgnoreCase("b3_att_us")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isDecoderInList(refFrameInvalidationAvcPrefixes, decoderName);
|
||||
}
|
||||
|
||||
@@ -299,7 +306,7 @@ public class MediaCodecHelper {
|
||||
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, boolean willStreamHdr) {
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData) {
|
||||
// TODO: Shield Tablet K1/LTE?
|
||||
//
|
||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||
@@ -335,7 +342,7 @@ public class MediaCodecHelper {
|
||||
// typically because it can't support reference frame invalidation.
|
||||
// However, we will use it for HDR and for streaming over mobile networks
|
||||
// since it works fine otherwise.
|
||||
if ((meteredData || willStreamHdr) && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||
if (meteredData && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||
LimeLog.info("Selected deprioritized decoder");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package com.limelight.computers;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -35,7 +37,7 @@ public class ComputerManagerService extends Service {
|
||||
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
|
||||
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
private static final int FAST_POLL_TIMEOUT = 500;
|
||||
private static final int FAST_POLL_TIMEOUT = 1000;
|
||||
private static final int OFFLINE_POLL_TRIES = 5;
|
||||
private static final int INITIAL_POLL_TRIES = 2;
|
||||
private static final int EMPTY_LIST_THRESHOLD = 3;
|
||||
@@ -132,7 +134,7 @@ public class ComputerManagerService extends Service {
|
||||
public void run() {
|
||||
|
||||
int offlineCount = 0;
|
||||
while (!isInterrupted() && pollingActive) {
|
||||
while (!isInterrupted() && pollingActive && tuple.thread == this) {
|
||||
try {
|
||||
// Only allow one request to the machine at a time
|
||||
synchronized (tuple.networkLock) {
|
||||
@@ -367,6 +369,10 @@ public class ComputerManagerService extends Service {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (!manuallyAdded) {
|
||||
LimeLog.warning("Auto-discovered PC failed to respond: "+addr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -386,6 +392,7 @@ public class ComputerManagerService extends Service {
|
||||
if (tuple.thread != null) {
|
||||
// Interrupt the thread on this entry
|
||||
tuple.thread.interrupt();
|
||||
tuple.thread = null;
|
||||
}
|
||||
pollingTuples.remove(tuple);
|
||||
break;
|
||||
@@ -500,14 +507,26 @@ public class ComputerManagerService extends Service {
|
||||
return ComputerDetails.Reachability.OFFLINE;
|
||||
}
|
||||
|
||||
private static boolean isAddressLikelyLocal(String str) {
|
||||
try {
|
||||
// This will tend to be wrong for IPv6 but falling back to
|
||||
// remote will be fine in that case. For IPv4, it should be
|
||||
// pretty accurate due to NAT prevalence.
|
||||
InetAddress addr = InetAddress.getByName(str);
|
||||
return addr.isSiteLocalAddress() || addr.isLinkLocalAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
|
||||
ComputerDetails polledDetails;
|
||||
ComputerDetails.Reachability reachability;
|
||||
|
||||
// If the local address is routable across the Internet,
|
||||
// always consider this PC remote to be conservative
|
||||
if (details.localAddress.equals(details.remoteAddress)) {
|
||||
reachability = ComputerDetails.Reachability.REMOTE;
|
||||
reachability = isAddressLikelyLocal(details.localAddress) ?
|
||||
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
|
||||
}
|
||||
else {
|
||||
// Do a TCP-level connection to the HTTP server to see if it's listening
|
||||
@@ -553,7 +572,14 @@ public class ComputerManagerService extends Service {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (polledDetails.remoteAddress.equals(reachableAddr)) {
|
||||
// If both addresses are the same, guess whether we're local based on
|
||||
// IP address heuristics.
|
||||
if (reachableAddr.equals(polledDetails.localAddress) &&
|
||||
reachableAddr.equals(polledDetails.remoteAddress)) {
|
||||
polledDetails.reachability = isAddressLikelyLocal(reachableAddr) ?
|
||||
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
|
||||
}
|
||||
else if (polledDetails.remoteAddress.equals(reachableAddr)) {
|
||||
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
|
||||
}
|
||||
else if (polledDetails.localAddress.equals(reachableAddr)) {
|
||||
@@ -815,6 +841,7 @@ public class ComputerManagerService extends Service {
|
||||
} while (waitPollingDelay());
|
||||
}
|
||||
};
|
||||
thread.setName("App list polling thread for " + computer.localAddress);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ import java.io.OutputStream;
|
||||
|
||||
public class DiskAssetLoader {
|
||||
// 5 MB
|
||||
private final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
|
||||
private static final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
|
||||
|
||||
// Standard box art is 300x400
|
||||
private static final int STANDARD_ASSET_WIDTH = 300;
|
||||
private static final int STANDARD_ASSET_HEIGHT = 400;
|
||||
|
||||
private final File cacheDir;
|
||||
|
||||
@@ -25,33 +29,65 @@ public class DiskAssetLoader {
|
||||
return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||
}
|
||||
|
||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
|
||||
InputStream in = null;
|
||||
Bitmap bmp = null;
|
||||
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;
|
||||
}
|
||||
// https://developer.android.com/topic/performance/graphics/load-bitmap.html
|
||||
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
|
||||
// Raw height and width of image
|
||||
final int height = options.outHeight;
|
||||
final int width = options.outWidth;
|
||||
int inSampleSize = 1;
|
||||
|
||||
in = CacheHelper.openCacheFileForInput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = sampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
bmp = BitmapFactory.decodeStream(in, null, options);
|
||||
} catch (IOException ignored) {
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ignored) {}
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
|
||||
// Calculates the largest inSampleSize value that is a power of 2 and keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
|
||||
File file = CacheHelper.openPath(false, cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||
|
||||
// Don't bother with anything if it doesn't exist
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make sure the cached asset doesn't exceed the maximum size
|
||||
if (file.length() > MAX_ASSET_SIZE) {
|
||||
LimeLog.warning("Removing cached tuple exceeding size threshold: "+tuple);
|
||||
file.delete();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Lookup bounds of the downloaded image
|
||||
BitmapFactory.Options decodeOnlyOptions = new BitmapFactory.Options();
|
||||
decodeOnlyOptions.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), decodeOnlyOptions);
|
||||
if (decodeOnlyOptions.outWidth <= 0 || decodeOnlyOptions.outHeight <= 0) {
|
||||
// Dimensions set to -1 on error. Return value always null.
|
||||
return null;
|
||||
}
|
||||
|
||||
LimeLog.info("Tuple "+tuple+" has cached art of size: "+decodeOnlyOptions.outWidth+"x"+decodeOnlyOptions.outHeight);
|
||||
|
||||
// Load the image scaled to the appropriate size
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = calculateInSampleSize(decodeOnlyOptions,
|
||||
STANDARD_ASSET_WIDTH / sampleSize,
|
||||
STANDARD_ASSET_HEIGHT / sampleSize);
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
options.inDither = true;
|
||||
Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
|
||||
if (bmp != null) {
|
||||
LimeLog.info("Disk cache hit for tuple: "+tuple);
|
||||
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
|
||||
}
|
||||
|
||||
return bmp;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
public class CacheHelper {
|
||||
private static File openPath(boolean createPath, File root, String... path) {
|
||||
public static File openPath(boolean createPath, File root, String... path) {
|
||||
File f = root;
|
||||
for (int i = 0; i < path.length; i++) {
|
||||
String component = path[i];
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Non-root application name -->
|
||||
<!-- FIXME: We should set extractNativeLibs=false but this breaks installation on the Fire TV 3 -->
|
||||
<application android:label="Moonlight" />
|
||||
</manifest>
|
||||
|
||||
@@ -2,5 +2,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Root application name -->
|
||||
<application android:label="Moonlight (Root)" />
|
||||
<!-- Ensure native libraries are always extracted for root builds,
|
||||
since we must invoke the evdev_reader binary ourselves -->
|
||||
<application
|
||||
android:label="Moonlight (Root)"
|
||||
android:extractNativeLibs="true" />
|
||||
</manifest>
|
||||
|
||||
+1
-1
Submodule moonlight-common updated: 688353e1c6...a23b48229b
Reference in New Issue
Block a user