Compare commits

..

17 Commits

Author SHA1 Message Date
Cameron Gutman cab0fa176e Version 10.2 2022-05-31 21:05:59 -05:00
Cameron Gutman 18c93abcb3 Submit fused IDR frames on decoders that support adaptive playback even if they are blocked from using it 2022-05-29 22:21:15 -05:00
Cameron Gutman bd64dfb661 Submit codec config data with a timestamp of 0 like MediaCodec does with csd-0 2022-05-29 22:10:49 -05:00
Cameron Gutman 82619063ee Plumb frame type information into the decoder 2022-05-29 21:58:28 -05:00
Cameron Gutman 5dbf18d66e Fix miscounting IDR frames in video stats 2022-05-29 21:10:41 -05:00
Cameron Gutman 6a34ff2728 Rewrite AES pairing functions to avoid Play Store's ECB warning
ECB is safe in this context because it's encrypting one-time messages
using a one-time key. All input data going through encryptAes() is
either random or partially random and passed through a secure hashing
function (SHA-256 on modern GFE versions).

Message authentication is not a concern either, because it is performed
by the pairing process itself via RSA signature verification. Any
ciphertext tampering would cause signature verification to fail later in
the pairing process.
2022-05-29 14:38:56 -05:00
Cameron Gutman f7c7487756 Merge remote-tracking branch 'origin/weblate' 2022-05-28 18:01:30 -05:00
weng weng f966cb4ca0 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (218 of 218 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hans/
2022-05-29 01:01:12 +02:00
Jorys Paulin 549563a3d2 Translated using Weblate (French)
Currently translated at 100.0% (218 of 218 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/fr/
2022-05-29 01:01:12 +02:00
Cameron Gutman c5f2a3f8fe Tweak remote desktop mouse mode string 2022-05-28 18:00:42 -05:00
Cameron Gutman 81a3bbd5e8 Implement remote desktop optimized mouse mode 2022-05-28 16:38:22 -05:00
Cameron Gutman 1509a2a799 Fix default deadzone setting 2022-05-28 16:16:46 -05:00
Cameron Gutman fc547b734f Fix crashes caused by calling NvHTTP with a null address 2022-05-28 15:54:21 -05:00
Cameron Gutman b3700b5a19 Plumb LiSendMouseMoveAsMousePositionEvent() into JNI 2022-05-28 15:21:58 -05:00
Cameron Gutman 2b29682095 Update AGP 2022-05-28 15:13:10 -05:00
Cameron Gutman 286094ee33 Add dead zone configuration option
Fixes #1075
2022-05-28 15:12:58 -05:00
ToldYouThat c7a061d24e Translated using Weblate (Turkish)
Currently translated at 25.6% (56 of 218 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/tr/
2022-05-25 14:14:38 +02:00
24 changed files with 217 additions and 100 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 16
targetSdkVersion 32
versionName "10.1.1"
versionCode = 274
versionName "10.2"
versionCode = 275
}
flavorDimensions "root"
+7 -2
View File
@@ -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) {
+2 -4
View File
@@ -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;
}
+4 -4
View File
@@ -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
@@ -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);
+8
View File
@@ -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>
+23
View File
@@ -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>
+3
View File
@@ -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>
+10 -4
View File
@@ -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
View File
@@ -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