Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cab0fa176e | |||
| 18c93abcb3 | |||
| bd64dfb661 | |||
| 82619063ee | |||
| 5dbf18d66e | |||
| 6a34ff2728 | |||
| f7c7487756 | |||
| f966cb4ca0 | |||
| 549563a3d2 | |||
| c5f2a3f8fe | |||
| 81a3bbd5e8 | |||
| 1509a2a799 | |||
| fc547b734f | |||
| b3700b5a19 | |||
| 2b29682095 | |||
| 286094ee33 | |||
| c7a061d24e |
+2
-2
@@ -9,8 +9,8 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 32
|
||||
|
||||
versionName "10.1.1"
|
||||
versionCode = 274
|
||||
versionName "10.2"
|
||||
versionCode = 275
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
|
||||
@@ -452,7 +452,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
else {
|
||||
touchContextMap[i] = new RelativeTouchContext(conn, i,
|
||||
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
|
||||
streamView);
|
||||
streamView, prefConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1244,7 +1244,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
short deltaY = (short)inputCaptureProvider.getRelativeAxisY(event);
|
||||
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
conn.sendMouseMove(deltaX, deltaY);
|
||||
if (prefConfig.absoluteMouseMode) {
|
||||
conn.sendMouseMoveAsMousePosition(deltaX, deltaY, (short)view.getWidth(), (short)view.getHeight());
|
||||
}
|
||||
else {
|
||||
conn.sendMouseMove(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0) {
|
||||
|
||||
@@ -374,8 +374,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
private void doPair(final ComputerDetails computer) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
@@ -512,8 +511,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
private void doUnpair(final ComputerDetails computer) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,9 +72,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
this.prefConfig = prefConfig;
|
||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
|
||||
// HACK: For now we're hardcoding a 7% deadzone. Some deadzone
|
||||
// is required for controller batching support to work.
|
||||
int deadzonePercentage = 7;
|
||||
int deadzonePercentage = prefConfig.deadzonePercentage;
|
||||
|
||||
int[] ids = InputDevice.getDeviceIds();
|
||||
for (int id : ids) {
|
||||
@@ -703,9 +701,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
// SHIELD controllers will use small stick deadzones
|
||||
else if (devName.contains("SHIELD") || devName.contains("NVIDIA Controller")) {
|
||||
context.leftStickDeadzoneRadius = 0.07f;
|
||||
context.rightStickDeadzoneRadius = 0.07f;
|
||||
|
||||
// The big Nvidia button on the Shield controllers acts like a Search button. It
|
||||
// summons the Google Assistant on the Shield TV. On my Pixel 4, it seems to do
|
||||
// nothing, so we can hijack it to act like a mode button.
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.View;
|
||||
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
@@ -30,6 +31,7 @@ public class RelativeTouchContext implements TouchContext {
|
||||
private final int referenceWidth;
|
||||
private final int referenceHeight;
|
||||
private final View targetView;
|
||||
private final PreferenceConfiguration prefConfig;
|
||||
|
||||
private static final int TAP_MOVEMENT_THRESHOLD = 20;
|
||||
private static final int TAP_DISTANCE_THRESHOLD = 25;
|
||||
@@ -39,13 +41,15 @@ public class RelativeTouchContext implements TouchContext {
|
||||
private static final int SCROLL_SPEED_FACTOR = 5;
|
||||
|
||||
public RelativeTouchContext(NvConnection conn, int actionIndex,
|
||||
int referenceWidth, int referenceHeight, View view)
|
||||
int referenceWidth, int referenceHeight,
|
||||
View view, PreferenceConfiguration prefConfig)
|
||||
{
|
||||
this.conn = conn;
|
||||
this.actionIndex = actionIndex;
|
||||
this.referenceWidth = referenceWidth;
|
||||
this.referenceHeight = referenceHeight;
|
||||
this.targetView = view;
|
||||
this.prefConfig = prefConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -258,7 +262,16 @@ public class RelativeTouchContext implements TouchContext {
|
||||
conn.sendMouseHighResScroll((short)(deltaY * SCROLL_SPEED_FACTOR));
|
||||
}
|
||||
} else {
|
||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||
if (prefConfig.absoluteMouseMode) {
|
||||
conn.sendMouseMoveAsMousePosition(
|
||||
(short) deltaX,
|
||||
(short) deltaY,
|
||||
(short) targetView.getWidth(),
|
||||
(short) targetView.getHeight());
|
||||
}
|
||||
else {
|
||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
// If the scaling factor ended up rounding deltas to zero, wait until they are
|
||||
|
||||
@@ -49,7 +49,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
private MediaCodec videoDecoder;
|
||||
private Thread rendererThread;
|
||||
private boolean needsSpsBitstreamFixup, isExynos4;
|
||||
private boolean adaptivePlayback, directSubmit;
|
||||
private boolean adaptivePlayback, directSubmit, fusedIdrFrame;
|
||||
private boolean constrainedHighProfile;
|
||||
private boolean refFrameInvalidationAvc, refFrameInvalidationHevc;
|
||||
private boolean refFrameInvalidationActive;
|
||||
@@ -294,6 +294,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
}
|
||||
|
||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(selectedDecoderInfo, mimeType);
|
||||
fusedIdrFrame = MediaCodecHelper.decoderSupportsFusedIdrFrame(selectedDecoderInfo, mimeType);
|
||||
|
||||
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
||||
// due to implementation problems
|
||||
@@ -714,7 +715,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs) {
|
||||
int frameNumber, int frameType, long receiveTimeMs, long enqueueTimeMs) {
|
||||
if (stopping) {
|
||||
// Don't bother if we're stopping
|
||||
return MoonBridge.DR_OK;
|
||||
@@ -770,29 +771,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
activeWindowVideoStats.totalFramesReceived++;
|
||||
activeWindowVideoStats.totalFrames++;
|
||||
|
||||
int inputBufferIndex;
|
||||
ByteBuffer buf;
|
||||
|
||||
long timestampUs = enqueueTimeMs * 1000;
|
||||
|
||||
if (!FRAME_RENDER_TIME_ONLY) {
|
||||
// Count time from first packet received to enqueue time as receive time
|
||||
// We will count DU queue time as part of decoding, because it is directly
|
||||
// caused by a slow decoder.
|
||||
activeWindowVideoStats.totalTimeMs += enqueueTimeMs - receiveTimeMs;
|
||||
}
|
||||
|
||||
if (timestampUs <= lastTimestampUs) {
|
||||
// We can't submit multiple buffers with the same timestamp
|
||||
// so bump it up by one before queuing
|
||||
timestampUs = lastTimestampUs + 1;
|
||||
}
|
||||
|
||||
lastTimestampUs = timestampUs;
|
||||
|
||||
long timestampUs;
|
||||
int codecFlags = 0;
|
||||
|
||||
// H264 SPS
|
||||
@@ -932,9 +913,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
numPpsIn++;
|
||||
|
||||
// If this is the first CSD blob or we aren't supporting
|
||||
// adaptive playback, we will submit the CSD blob in a
|
||||
// fused IDR frames, we will submit the CSD blob in a
|
||||
// separate input buffer.
|
||||
if (!submittedCsd || !adaptivePlayback) {
|
||||
if (!submittedCsd || !fusedIdrFrame) {
|
||||
inputBufferIndex = dequeueInputBuffer();
|
||||
if (inputBufferIndex < 0) {
|
||||
// We're being torn down now
|
||||
@@ -958,6 +939,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
|
||||
// This is the CSD blob
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
timestampUs = 0;
|
||||
}
|
||||
else {
|
||||
// Batch this to submit together with the next I-frame
|
||||
@@ -971,6 +953,16 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
}
|
||||
}
|
||||
else {
|
||||
activeWindowVideoStats.totalFramesReceived++;
|
||||
activeWindowVideoStats.totalFrames++;
|
||||
|
||||
if (!FRAME_RENDER_TIME_ONLY) {
|
||||
// Count time from first packet received to enqueue time as receive time
|
||||
// We will count DU queue time as part of decoding, because it is directly
|
||||
// caused by a slow decoder.
|
||||
activeWindowVideoStats.totalTimeMs += enqueueTimeMs - receiveTimeMs;
|
||||
}
|
||||
|
||||
inputBufferIndex = dequeueInputBuffer();
|
||||
if (inputBufferIndex < 0) {
|
||||
// We're being torn down now
|
||||
@@ -997,6 +989,20 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
submitCsdNextCall = false;
|
||||
}
|
||||
|
||||
if (frameType == MoonBridge.FRAME_TYPE_IDR) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
|
||||
}
|
||||
|
||||
timestampUs = enqueueTimeMs * 1000;
|
||||
|
||||
if (timestampUs <= lastTimestampUs) {
|
||||
// We can't submit multiple buffers with the same timestamp
|
||||
// so bump it up by one before queuing
|
||||
timestampUs = lastTimestampUs + 1;
|
||||
}
|
||||
|
||||
lastTimestampUs = timestampUs;
|
||||
|
||||
numFramesIn++;
|
||||
}
|
||||
|
||||
@@ -1067,8 +1073,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
// Queue the new SPS
|
||||
return queueInputBuffer(inputIndex,
|
||||
0, inputBuffer.position(),
|
||||
System.nanoTime() / 1000,
|
||||
MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
||||
0, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1209,6 +1214,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
|
||||
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
|
||||
str += "RFI active: "+renderer.refFrameInvalidationActive+"\n";
|
||||
str += "Using modern SPS patching: "+(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)+"\n";
|
||||
str += "Fused IDR frames: "+renderer.fusedIdrFrame+"\n";
|
||||
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||
str += "FPS target: "+renderer.refreshRate+"\n";
|
||||
str += "Bitrate: "+renderer.prefs.bitrate+" Kbps \n";
|
||||
|
||||
@@ -425,6 +425,25 @@ public class MediaCodecHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsFusedIdrFrame(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
// If adaptive playback is supported, we can submit new CSD together with a keyframe
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
try {
|
||||
if (decoderInfo.getCapabilitiesForType(mimeType).
|
||||
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
|
||||
{
|
||||
LimeLog.info("Decoder supports fused IDR frames (FEATURE_AdaptivePlayback)");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Tolerate buggy codecs
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
// Possibly enable adaptive playback on KitKat and above
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
|
||||
@@ -311,7 +311,14 @@ public class NvConnection {
|
||||
MoonBridge.sendMousePosition(x, y, referenceWidth, referenceHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendMouseMoveAsMousePosition(deltaX, deltaY, referenceWidth, referenceHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseButtonDown(final byte mouseButton)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
|
||||
@@ -10,7 +10,7 @@ public abstract class VideoDecoderRenderer {
|
||||
// This is called once for each frame-start NALU. This means it will be called several times
|
||||
// for an IDR frame which contains several parameter sets and the I-frame data.
|
||||
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs);
|
||||
int frameNumber, int frameType, long receiveTimeMs, long enqueueTimeMs);
|
||||
|
||||
public abstract void cleanup();
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.limelight.nvstream.http;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.engines.AESLightEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@@ -21,7 +21,6 @@ public class PairingManager {
|
||||
|
||||
private PrivateKey pk;
|
||||
private X509Certificate cert;
|
||||
private SecretKey aesKey;
|
||||
private byte[] pemCertBytes;
|
||||
|
||||
private X509Certificate serverCert;
|
||||
@@ -125,43 +124,35 @@ public class PairingManager {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decryptAes(byte[] encryptedData, SecretKey secretKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
|
||||
int blockRoundedSize = ((encryptedData.length + 15) / 16) * 16;
|
||||
byte[] blockRoundedEncrypted = Arrays.copyOf(encryptedData, blockRoundedSize);
|
||||
byte[] fullDecrypted = new byte[blockRoundedSize];
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
||||
cipher.doFinal(blockRoundedEncrypted, 0,
|
||||
blockRoundedSize, fullDecrypted);
|
||||
return fullDecrypted;
|
||||
} catch (GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
private static byte[] performBlockCipher(BlockCipher blockCipher, byte[] input) {
|
||||
int blockSize = blockCipher.getBlockSize();
|
||||
int blockRoundedSize = (input.length + (blockSize - 1)) & ~(blockSize - 1);
|
||||
|
||||
byte[] blockRoundedInputData = Arrays.copyOf(input, blockRoundedSize);
|
||||
byte[] blockRoundedOutputData = new byte[blockRoundedSize];
|
||||
|
||||
for (int offset = 0; offset < blockRoundedSize; offset += blockSize) {
|
||||
blockCipher.processBlock(blockRoundedInputData, offset, blockRoundedOutputData, offset);
|
||||
}
|
||||
|
||||
return blockRoundedOutputData;
|
||||
}
|
||||
|
||||
private static byte[] encryptAes(byte[] data, SecretKey secretKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
|
||||
int blockRoundedSize = ((data.length + 15) / 16) * 16;
|
||||
byte[] blockRoundedData = Arrays.copyOf(data, blockRoundedSize);
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
return cipher.doFinal(blockRoundedData);
|
||||
} catch (GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private static byte[] decryptAes(byte[] encryptedData, byte[] aesKey) {
|
||||
BlockCipher aesEngine = new AESLightEngine();
|
||||
aesEngine.init(false, new KeyParameter(aesKey));
|
||||
return performBlockCipher(aesEngine, encryptedData);
|
||||
}
|
||||
|
||||
private static SecretKey generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||
byte[] aesTruncated = Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||
return new SecretKeySpec(aesTruncated, "AES");
|
||||
private static byte[] encryptAes(byte[] plaintextData, byte[] aesKey) {
|
||||
BlockCipher aesEngine = new AESLightEngine();
|
||||
aesEngine.init(true, new KeyParameter(aesKey));
|
||||
return performBlockCipher(aesEngine, plaintextData);
|
||||
}
|
||||
|
||||
private static byte[] generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||
return Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||
}
|
||||
|
||||
private static byte[] concatBytes(byte[] a, byte[] b) {
|
||||
@@ -200,8 +191,7 @@ public class PairingManager {
|
||||
byte[] salt = generateRandomBytes(16);
|
||||
|
||||
// Combine the salt and pin, then create an AES key from them
|
||||
byte[] saltAndPin = saltPin(salt, pin);
|
||||
aesKey = generateAesKey(hashAlgo, saltAndPin);
|
||||
byte[] aesKey = generateAesKey(hashAlgo, saltPin(salt, pin));
|
||||
|
||||
// Send the salt and get the server cert. This doesn't have a read timeout
|
||||
// because the user must enter the PIN before the server responds
|
||||
|
||||
@@ -27,6 +27,9 @@ public class MoonBridge {
|
||||
public static final int BUFFER_TYPE_PPS = 2;
|
||||
public static final int BUFFER_TYPE_VPS = 3;
|
||||
|
||||
public static final int FRAME_TYPE_PFRAME = 0;
|
||||
public static final int FRAME_TYPE_IDR = 1;
|
||||
|
||||
public static final int CAPABILITY_DIRECT_SUBMIT = 1;
|
||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC = 2;
|
||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC = 4;
|
||||
@@ -153,12 +156,12 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength,
|
||||
int decodeUnitType, int frameNumber,
|
||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, int frameType,
|
||||
long receiveTimeMs, long enqueueTimeMs) {
|
||||
if (videoRenderer != null) {
|
||||
return videoRenderer.submitDecodeUnit(decodeUnitData, decodeUnitLength,
|
||||
decodeUnitType, frameNumber, receiveTimeMs, enqueueTimeMs);
|
||||
decodeUnitType, frameNumber, frameType, receiveTimeMs, enqueueTimeMs);
|
||||
}
|
||||
else {
|
||||
return DR_OK;
|
||||
@@ -278,6 +281,8 @@ public class MoonBridge {
|
||||
|
||||
public static native void sendMousePosition(short x, short y, short referenceWidth, short referenceHeight);
|
||||
|
||||
public static native void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight);
|
||||
|
||||
public static native void sendMouseButton(byte buttonEvent, byte mouseButton);
|
||||
|
||||
public static native void sendMultiControllerInput(short controllerNumber,
|
||||
|
||||
@@ -44,6 +44,7 @@ public class PreferenceConfiguration {
|
||||
private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
|
||||
private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast";
|
||||
private static final String FRAME_PACING_PREF_STRING = "frame_pacing";
|
||||
private static final String ABSOLUTE_MOUSE_MODE_PREF_STRING = "checkbox_absolute_mouse_mode";
|
||||
|
||||
static final String DEFAULT_RESOLUTION = "1280x720";
|
||||
static final String DEFAULT_FPS = "60";
|
||||
@@ -51,7 +52,7 @@ public class PreferenceConfiguration {
|
||||
private static final boolean DEFAULT_SOPS = true;
|
||||
private static final boolean DEFAULT_DISABLE_TOASTS = false;
|
||||
private static final boolean DEFAULT_HOST_AUDIO = false;
|
||||
private static final int DEFAULT_DEADZONE = 15;
|
||||
private static final int DEFAULT_DEADZONE = 7;
|
||||
private static final int DEFAULT_OPACITY = 90;
|
||||
public static final String DEFAULT_LANGUAGE = "default";
|
||||
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
||||
@@ -73,6 +74,7 @@ public class PreferenceConfiguration {
|
||||
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
|
||||
private static final boolean DEFAULT_LATENCY_TOAST = false;
|
||||
private static final String DEFAULT_FRAME_PACING = "latency";
|
||||
private static final boolean DEFAULT_ABSOLUTE_MOUSE_MODE = false;
|
||||
|
||||
public static final int FORCE_H265_ON = -1;
|
||||
public static final int AUTOSELECT_H265 = 0;
|
||||
@@ -114,6 +116,7 @@ public class PreferenceConfiguration {
|
||||
public boolean touchscreenTrackpad;
|
||||
public MoonBridge.AudioConfiguration audioConfiguration;
|
||||
public int framePacing;
|
||||
public boolean absoluteMouseMode;
|
||||
|
||||
public static boolean isNativeResolution(int width, int height) {
|
||||
// It's not a native resolution if it matches an existing resolution option
|
||||
@@ -454,6 +457,7 @@ public class PreferenceConfiguration {
|
||||
config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
|
||||
config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
|
||||
config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST);
|
||||
config.absoluteMouseMode = prefs.getBoolean(ABSOLUTE_MOUSE_MODE_PREF_STRING, DEFAULT_ABSOLUTE_MOUSE_MODE);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -222,6 +222,15 @@ public class StreamSettings extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
// Hide remote desktop mouse mode on pre-Oreo (which doesn't have pointer capture)
|
||||
// and NVIDIA SHIELD devices (which support raw mouse input in pointer capture mode)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
getActivity().getPackageManager().hasSystemFeature("com.nvidia.feature.shield")) {
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_input_settings");
|
||||
category.removePreference(findPreference("checkbox_absolute_mouse_mode"));
|
||||
}
|
||||
|
||||
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
|
||||
// and on Fire OS where it violates the Amazon App Store guidelines for some reason.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
|
||||
@@ -27,7 +27,10 @@ import java.security.cert.CertificateEncodingException;
|
||||
public class ServerHelper {
|
||||
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
|
||||
|
||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
|
||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) throws IOException {
|
||||
if (computer.activeAddress == null) {
|
||||
throw new IOException("No active address for "+computer.name);
|
||||
}
|
||||
return computer.activeAddress;
|
||||
}
|
||||
|
||||
@@ -53,7 +56,7 @@ public class ServerHelper {
|
||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
Intent intent = new Intent(parent, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer));
|
||||
intent.putExtra(Game.EXTRA_HOST, computer.activeAddress);
|
||||
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||
@@ -72,8 +75,7 @@ public class ServerHelper {
|
||||
|
||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(parent, parent.getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeDrStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStart", "()V");
|
||||
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
|
||||
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJJ)I");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIIJJ)I");
|
||||
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
|
||||
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
|
||||
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
|
||||
@@ -159,8 +159,8 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
|
||||
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs,
|
||||
(jlong)decodeUnit->enqueueTimeMs);
|
||||
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
@@ -180,7 +180,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
|
||||
decodeUnit->frameNumber,
|
||||
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
|
||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: e62dc56047...d247873ade
@@ -17,6 +17,12 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendMousePosition(JNIEnv *env, jclass
|
||||
LiSendMousePositionEvent(x, y, referenceWidth, referenceHeight);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseMoveAsMousePosition(JNIEnv *env, jclass clazz,
|
||||
jshort deltaX, jshort deltaY, jshort referenceWidth, jshort referenceHeight) {
|
||||
LiSendMouseMoveAsMousePositionEvent(deltaX, deltaY, referenceWidth, referenceHeight);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseButton(JNIEnv *env, jclass clazz, jbyte buttonEvent, jbyte mouseButton) {
|
||||
LiSendMouseButtonEvent(buttonEvent, mouseButton);
|
||||
|
||||
@@ -242,4 +242,12 @@
|
||||
<string name="resolution_1440p">1440p</string>
|
||||
<string name="resolution_1080p">1080p</string>
|
||||
<string name="resolution_4k">4K</string>
|
||||
<string name="category_help">Aide</string>
|
||||
<string name="title_setup_guide">Guide de configuration</string>
|
||||
<string name="title_troubleshooting">Guide de dépannage</string>
|
||||
<string name="title_privacy_policy">Politique de confidentialité</string>
|
||||
<string name="summary_privacy_policy">Voir la politique de confidentialité de Moonlight</string>
|
||||
<string name="summary_setup_guide">Afficher les instructions sur la façon de configurer votre PC de jeu pour le streaming</string>
|
||||
<string name="summary_troubleshooting">Afficher des conseils pour diagnostiquer et résoudre les problèmes de streaming courants</string>
|
||||
<string name="pacing_balanced_alt">Équilibré avec limite FPS</string>
|
||||
</resources>
|
||||
@@ -38,4 +38,27 @@
|
||||
<string name="help_loading_title">Yardım</string>
|
||||
<string name="scut_invalid_app_id">Tanımlanmış uygulama geçerli değil</string>
|
||||
<string name="scut_invalid_uuid">Tanımlanmış bilgisayar geçerli değil</string>
|
||||
<string name="nettest_text_waiting">Moonlight, NVIDIA GameStream\'in engellenip engellenmediğini belirlemek için ağ bağlantınızı test ediyor.
|
||||
\n
|
||||
\nBu birkaç saniye sürebilir…</string>
|
||||
<string name="nettest_title_done">Ağ Testi Tamamlandı</string>
|
||||
<string name="nettest_text_inconclusive">Moonlight\'ın bağlantı testi sunucularından hiçbirine erişilemediği için ağ testi gerçekleştirilemedi. İnternet bağlantınızı kontrol edin veya daha sonra tekrar deneyin.</string>
|
||||
<string name="nettest_text_failure">Cihazınızın mevcut ağ bağlantısı Moonlight\'ı engelliyor gibi görünüyor. Bu ağa bağlıyken İnternet üzerinden yayın akışı çalışmayabilir.
|
||||
\n
|
||||
\nAşağıdaki ağ bağlantı noktaları engellenmiş:
|
||||
\n</string>
|
||||
<string name="nettest_text_blocked">Cihazınızın mevcut ağ bağlantısı Moonlight\'ı engelliyor. Bu ağa bağlıyken internet üzerinden yayın akışı çalışmayabilir.</string>
|
||||
<string name="pair_pc_ingame">Bilgisayar şu anda bir oyunda. Eşleştirmeden önce oyunu kapatmalısınız.</string>
|
||||
<string name="pair_pairing_msg">Lütfen hedef bilgisayarda aşağıdaki PIN kodunu girin:</string>
|
||||
<string name="pair_already_in_progress">Eşleştirme zaten devam ediyor</string>
|
||||
<string name="wol_pc_online">Bilgisayar çevrimiçi</string>
|
||||
<string name="wol_no_mac">GFE bir MAC adresi göndermediği için bilgisayar uyandırılamıyor</string>
|
||||
<string name="wol_waking_pc">Bilgisayar uyandırılıyor…</string>
|
||||
<string name="wol_fail">Yerel Ağda Uyandırma paketleri gönderilemedi</string>
|
||||
<string name="wol_waking_msg">Bilgisayarınızın uyanması birkaç saniye sürebilir. Eğer uyanmazsa, Yerel Ağda Uyandırma için doğru şekilde yapılandırıldığından emin olun.</string>
|
||||
<string name="unpair_fail">Eşleşme kaldırılamadı</string>
|
||||
<string name="unpair_error">Cihaz eşleştirilmedi</string>
|
||||
<string name="nettest_text_success">Ağınız Moonlight\'ı engelliyor gibi görünmüyor. Bağlanmakta hala sorun yaşıyorsanız, bilgisayarınızın güvenlik duvarı ayarlarını kontrol edin.
|
||||
\n
|
||||
\nİnternet üzerinden yayın yapmaya çalışıyorsanız, Moonlight İnternet Barındırma Aracını bilgisayarınıza yükleyin ve bilgisayarınızın internet bağlantısını kontrol etmek için birlikte verilen İnternet Akış Test Cihazını çalıştırın.</string>
|
||||
</resources>
|
||||
@@ -236,4 +236,16 @@
|
||||
<string name="fps_120">120 FPS</string>
|
||||
<string name="resolution_1080p">1080p</string>
|
||||
<string name="resolution_720p">720p</string>
|
||||
<string name="pacing_balanced">平衡</string>
|
||||
<string name="pacing_smoothness">优先视频流畅度(可能会显著增大延迟)</string>
|
||||
<string name="summary_setup_guide">查看有关如何设置游戏电脑以进行流传输的说明</string>
|
||||
<string name="title_privacy_policy">隐私政策</string>
|
||||
<string name="summary_privacy_policy">查看 Moonlight 隐私政策</string>
|
||||
<string name="category_help">帮助</string>
|
||||
<string name="title_setup_guide">安装向导</string>
|
||||
<string name="title_troubleshooting">解决方案向导</string>
|
||||
<string name="summary_troubleshooting">查看有关诊断和修复常见流传输问题的提示</string>
|
||||
<string name="summary_frame_pacing">指定如何平衡视频延迟和流畅度</string>
|
||||
<string name="pacing_balanced_alt">有FPS限制的平衡</string>
|
||||
<string name="pacing_latency">优先最低延迟</string>
|
||||
</resources>
|
||||
@@ -168,6 +168,7 @@
|
||||
<string name="title_checkbox_vibrate_fallback">Emulate rumble support with vibration</string>
|
||||
<string name="summary_checkbox_vibrate_fallback">Vibrates your device to emulate rumble if your gamepad does not support it</string>
|
||||
<string name="title_seekbar_deadzone">Adjust analog stick deadzone</string>
|
||||
<string name="summary_seekbar_deadzone">Note: Some games can enforce a larger deadzone than what Moonlight is configured to use.</string>
|
||||
<string name="suffix_seekbar_deadzone">%</string>
|
||||
<string name="title_checkbox_xb1_driver">Xbox 360/One USB gamepad driver</string>
|
||||
<string name="summary_checkbox_xb1_driver">Enables a built-in USB driver for devices without native Xbox controller support</string>
|
||||
@@ -179,6 +180,8 @@
|
||||
<string name="summary_checkbox_mouse_nav_buttons">Enabling this option may break right clicking on some buggy devices</string>
|
||||
<string name="title_checkbox_flip_face_buttons">Flip face buttons</string>
|
||||
<string name="summary_checkbox_flip_face_buttons">Switches the face buttons A/B and X/Y for gamepads and the on-screen controls</string>
|
||||
<string name="title_checkbox_absolute_mouse_mode">Remote desktop mouse mode</string>
|
||||
<string name="summary_checkbox_absolute_mouse_mode">This can make mouse acceleration behave more naturally for remote desktop usage, but it is incompatible with many games.</string>
|
||||
|
||||
<string name="category_on_screen_controls_settings">On-screen Controls Settings</string>
|
||||
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
|
||||
|
||||
@@ -52,12 +52,13 @@
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/category_input_settings"
|
||||
android:key="category_input_settings">
|
||||
<!--com.limelight.preferences.SeekBarPreference
|
||||
<com.limelight.preferences.SeekBarPreference
|
||||
android:key="seekbar_deadzone"
|
||||
android:defaultValue="15"
|
||||
android:max="50"
|
||||
android:defaultValue="7"
|
||||
android:max="20"
|
||||
android:summary="@string/summary_seekbar_deadzone"
|
||||
android:text="@string/suffix_seekbar_deadzone"
|
||||
android:title="@string/title_seekbar_deadzone"/-->
|
||||
android:title="@string/title_seekbar_deadzone"/>
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_touchscreen_trackpad"
|
||||
android:title="@string/title_checkbox_touchscreen_trackpad"
|
||||
@@ -99,6 +100,11 @@
|
||||
android:title="@string/title_checkbox_flip_face_buttons"
|
||||
android:summary="@string/summary_checkbox_flip_face_buttons"
|
||||
android:defaultValue="false" />
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_absolute_mouse_mode"
|
||||
android:title="@string/title_checkbox_absolute_mouse_mode"
|
||||
android:summary="@string/summary_checkbox_absolute_mouse_mode"
|
||||
android:defaultValue="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/category_on_screen_controls_settings"
|
||||
android:key="category_onscreen_controls">
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
- Added analog stick deadzone adjustment option
|
||||
- Added remote desktop mouse mode option for more natural mouse acceleration when not gaming
|
||||
- Improved accuracy of performance overlay statistics
|
||||
- Minor video decoder changes to improve compatibility
|
||||
- Fixed a crash when loading box art
|
||||
- Updated community contributed translations from Weblate
|
||||
Reference in New Issue
Block a user