Compare commits

..

134 Commits

Author SHA1 Message Date
Cameron Gutman babfc99c35 Version 10.6 2022-07-07 23:17:08 -05:00
Cameron Gutman 1eca461cb1 Merge remote-tracking branch 'origin/weblate' 2022-07-04 17:51:36 -05:00
Cameron Gutman ebd327c7a6 Use new ShieldControllerExtensions library for Shield Controller rumble support
https://github.com/cgutman/ShieldControllerExtensions
2022-06-30 18:04:02 -05:00
Cameron Gutman 602febe876 Use onPictureInPictureRequested() to enter PiP on Android 11 2022-06-29 23:28:52 -05:00
Cameron Gutman 84fcd3ae6a Use requestMetaKeyEvent API on Samsung devices
Inspired by #1078
2022-06-28 22:07:40 -05:00
Cameron Gutman 84296c6e1c Toggle the IME with a 3 finger tap rather than only showing it 2022-06-28 21:40:59 -05:00
Jorys Paulin 6012e0ea8c Translated using Weblate (French)
Currently translated at 100.0% (219 of 219 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/fr/
2022-06-27 15:16:50 +02:00
Cameron Gutman 9c76defad0 Add workaround for Galaxy S10 devices crashing during WifiLock acquisition 2022-06-26 13:59:39 -05:00
Cameron Gutman ffd6fab35c Prevent use of proxies 2022-06-25 14:18:38 -05:00
Cameron Gutman 296f97f7ca Version 10.5 2022-06-23 23:37:19 -05:00
Artem 9cbef34f29 Translated using Weblate (Ukrainian)
Currently translated at 94.5% (207 of 219 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/uk/
2022-06-22 22:20:52 +02:00
Cameron Gutman 7a65136d29 Disable predictive back gesture support because it breaks KEYCODE_BACK on InputDevices
Partially reverts b2e605838e
2022-06-18 16:19:02 -05:00
Cameron Gutman acaebea846 Merge remote-tracking branch 'origin/weblate' 2022-06-18 15:02:06 -05:00
Cameron Gutman ce850ac12f Fix crash on Xiaomi MiPad running newer custom ROMs
AVC Decoder: OMX.Nvidia.h264.decode
HEVC Decoder: OMX.Nvidia.h265.decode
AVC supported width range: [32, 3840]
AVC achievable FPS range: [146.0, 149.0]
HEVC supported width range: [32, 528]
HEVC achievable FPS range: UNSUPPORTED!
2022-06-18 15:00:10 -05:00
Cameron Gutman a93422d3ed Handle failure to bind com.nvidia.blakepairing more robustly 2022-06-18 14:31:38 -05:00
Cameron Gutman b2e605838e Opt in for new predictive back gesture support in Android 13 2022-06-18 14:26:13 -05:00
Cameron Gutman 2e14002442 Switch to the new native per-app language preference APIs on Android 13 2022-06-18 14:19:19 -05:00
Cameron Gutman c743949df5 Don't crash if no performance data was provided for the codec using the M API 2022-06-18 10:37:16 -05:00
Cameron Gutman f207a3f6d1 Use areSizeAndRateSupported() as a last resort if no performance data is available 2022-06-18 10:35:12 -05:00
Cameron Gutman d6211605a1 Fix crash on shortcut launch if PC has no known MAC address 2022-06-18 10:23:06 -05:00
Cameron Gutman b16676b54a Version 10.4 2022-06-18 10:18:37 -05:00
Licaon_Kter dc9bfe5189 Fastlane mention Sunshine (#1086)
* Mention Sunshine

* Update full DE

* Short EN

* Full EN

* remove for clarity

* clarify here too
2022-06-18 09:57:37 -05:00
metezd 80620ed4c6 Translated using Weblate (Turkish)
Currently translated at 69.4% (152 of 219 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/tr/
2022-06-15 14:17:48 +02:00
Wen-haur Chiu 76bd0ab696 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (219 of 219 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-06-15 14:17:47 +02:00
ㅤAbsurdUsername e0914df58a Translated using Weblate (Italian)
Currently translated at 100.0% (219 of 219 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/it/
2022-06-15 14:17:46 +02:00
Cameron Gutman 20039a422e Merge remote-tracking branch 'origin/weblate' 2022-06-13 21:44:02 -05:00
Cameron Gutman 22b9c9ca68 Use H.264 on Sabrina if possible for lowest latency 2022-06-13 21:40:41 -05:00
Eric 0c546e35ec Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (219 of 219 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hans/
2022-06-14 04:19:39 +02:00
Cameron Gutman b70370ac09 Merge remote-tracking branch 'origin/weblate' 2022-06-13 21:14:40 -05:00
Cameron Gutman aa10bb7dc5 Block HDR use on the known broken Shield TV firmware build 2022-06-13 20:23:18 -05:00
Cameron Gutman c6100a9be1 Catch potential older NVIDIA devices that use partial HEVC acceleration 2022-06-13 19:25:29 -05:00
Cameron Gutman 529a2f7bf8 Prevent PiP entry while the USB permission dialog is open 2022-06-13 19:23:03 -05:00
ㅤAbsurdUsername 9ec7e916c5 Translated using Weblate (Italian)
Currently translated at 100.0% (221 of 221 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/it/
2022-06-13 20:16:29 +02:00
Cameron Gutman 982b36cf98 Adjust app details text for new HDR behavior 2022-06-09 20:25:37 -05:00
Cameron Gutman a73eab5e92 Handle stale controller token mappings upon device removal 2022-06-09 19:43:46 -05:00
Cameron Gutman a8479ccb5f Implement support for rumble for Shield controllers on Shield devices 2022-06-09 18:51:23 -05:00
Cameron Gutman f55e4e0e01 Don't dock expanded PiP overlays when browsing PCs and apps 2022-06-09 00:05:19 -05:00
Cameron Gutman d08c32ce04 Map external keyboard keycodes to the QWERTY layout that GFE expects 2022-06-08 23:54:57 -05:00
Cameron Gutman 1d599c5e60 Target Android 13 2022-06-08 22:58:39 -05:00
Cameron Gutman e888ae59e4 Ignore 3 finger tap gesture when cancelled 2022-06-08 22:58:23 -05:00
Cameron Gutman 951d544894 Provide GameState updates to GameManager on Android 13 2022-06-08 22:41:16 -05:00
Cameron Gutman 49898b34e1 Don't export UsbEventReceiver on Android 13 2022-06-08 22:16:58 -05:00
Cameron Gutman 3854a6a42e Add predictive back support to HelpActivity 2022-06-08 21:45:00 -05:00
Cameron Gutman d4da5bc281 Disallow Game Mode downscaling on Android 12+ 2022-06-08 20:56:27 -05:00
Cameron Gutman 04954f5242 Add handling for MotionEvent.FLAG_CANCELED 2022-06-08 20:35:46 -05:00
Cameron Gutman 9fc5496526 Use VibrationAttributes to bypass interruption policy 2022-06-08 20:26:36 -05:00
Cameron Gutman e363d24b1c Add PiP title and subtilte on Android 13 2022-06-08 20:04:12 -05:00
Cameron Gutman 801f4027a2 Add preferKeepClear marks on important UI elements 2022-06-08 20:03:23 -05:00
Cameron Gutman c0dc344f76 Compile with API 33 SDK 2022-06-08 20:01:05 -05:00
TacoTheDank b5b3d81f00 Clean up flavors by using buildConfigField 2022-06-08 19:44:59 -05:00
TacoTheDank 8dd8dbc1d1 Clean up app build.gradle deprecations 2022-06-08 19:44:59 -05:00
TacoTheDank 8f31aa59a8 Update gradle wrapper 2022-06-08 19:44:59 -05:00
Cameron Gutman 5b581b6c0f Update string for HEVC auto setting 2022-06-06 17:30:18 -05:00
Cameron Gutman 297ac64fde Enable HEVC on all Shield TV devices 2022-06-06 17:29:47 -05:00
Cameron Gutman d4490f0e17 Fix performance point check for Android M - P 2022-06-06 17:26:59 -05:00
Cameron Gutman d04e7a3231 Enable HEVC on untested decoders if it's the only way to meet the performance target 2022-06-04 17:37:14 -05:00
Cameron Gutman 5b456aba27 Use a separate HandlerThread for Choreographer callbacks 2022-06-04 17:00:58 -05:00
Cameron Gutman 0c065dcc1f Print vendor parameters on Android 12 2022-06-04 15:42:06 -05:00
Cameron Gutman 531f73329d Quiet down excessive exception logging in debug builds 2022-06-04 15:33:12 -05:00
Cameron Gutman d6ba72032d Version 10.3 2022-06-03 20:56:48 -05:00
Cameron Gutman bfdc7a2609 Merge remote-tracking branch 'origin/weblate' 2022-06-03 19:20:29 -05:00
Wen-haur Chiu 031abf03da Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (221 of 221 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-06-04 02:19:58 +02:00
Cameron Gutman 6aac8e6be6 Use amazon.hardware.fire_tv feature to detect Fire TV devices 2022-06-03 19:03:56 -05:00
Wen-haur Chiu 8ff93d21c3 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (221 of 221 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-06-03 13:17:29 +02:00
weng weng 6df3d0bc44 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (221 of 221 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hans/
2022-06-03 13:17:29 +02:00
Cameron Gutman 0b18e8fdb4 Update decoder errata details 2022-06-03 01:39:39 -05:00
Cameron Gutman 19d8ae0f78 Revamp low latency option handling
- Introduce a tiered solution where we try progressively fewer options until one works
- Use vdec-lowlatency for all devices, since we know at least the Fire TV 3 supports it with an Amlogic SoC
- Enable HEVC on Fire TV 3 since vdec-lowlatency avoids the HEVC decoder bug
2022-06-03 01:04:11 -05:00
Cameron Gutman d7ffb5dddc Refactor decoder creation code to allow retries 2022-06-02 21:17:20 -05:00
Cameron Gutman 2859b73dfe Add Amlogic low latency vendor-defined option 2022-06-02 21:02:43 -05:00
Cameron Gutman 6f9021a5e6 Add magic performance boost for MediaTek devices 2022-06-01 22:06:11 -05:00
Cameron Gutman 3bfeaefdbd Update NDK to r23c 2022-06-01 19:24:03 -05:00
Cameron Gutman db1eace975 Add support for Android 13 themed app icons 2022-06-01 19:08:17 -05:00
Cameron Gutman cab0fa176e Version 10.2 2022-05-31 21:05:59 -05:00
Jorys Paulin 2f9ae107a2 Translated using Weblate (French)
Currently translated at 100.0% (221 of 221 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/fr/
2022-05-31 10:14:36 +02: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
Cameron Gutman 4bdc2e0aba Add F-Droid metadata for 274 2022-05-22 18:02:21 -05:00
Cameron Gutman e69061082b Version 10.1.1 2022-05-22 17:16:37 -05:00
Cameron Gutman 1da2ec3cb1 Merge remote-tracking branch 'origin/weblate' 2022-05-22 17:15:44 -05:00
Cameron Gutman 8ffc3b80b2 Rework use of URLs in NvHTTP
- Fixes parsing inconsistencies between URI and HttpUrl
- Fixes a couple of serverinfo requests sent without uniqueid and UUID
- Avoids PairingManager having to look into NvHTTP internals
2022-05-22 16:47:45 -05:00
Cameron Gutman 08f8b6cb8e Keep the SpinnerDialog visible while the connectivity test runs 2022-05-22 15:36:38 -05:00
Cameron Gutman fb09c9692c Fix handling of InterruptedExceptions 2022-05-22 15:31:06 -05:00
Cameron Gutman 4901b0c78f Stop parallel polling threads when we find a working address 2022-05-22 14:56:28 -05:00
Cameron Gutman 0a2117241f Wrap Choreographer calls to releaseOutputBuffer() in try/catch 2022-05-22 14:32:03 -05:00
Wen-haur Chiu f352cfd15b Translated using Weblate (Chinese (Traditional))
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_Hant/
2022-05-21 14:16:28 +02:00
Cameron Gutman ac7c5c1064 Improve handling of required XML tags 2022-05-20 17:15:26 -05:00
Cameron Gutman 077cb2103d Version 10.1 2022-05-18 22:44:46 -05:00
Cameron Gutman cdeda011a4 Temporarily disable in-app links until they are translated 2022-05-18 22:40:47 -05:00
Cameron Gutman 894c146988 Fix JAVA_HOME path on VS2022 CI image 2022-05-18 00:57:05 -05:00
Cameron Gutman 61cc9e151f Use newer AppVeyor machine image 2022-05-18 00:38:47 -05:00
Cameron Gutman cfe4c9ff21 Target Android 12L 2022-05-17 17:16:28 -05:00
Cameron Gutman d4bd29b320 Properly deal with battery saver mode in capped FPS mode 2022-05-17 00:14:55 -05:00
Cameron Gutman 7f2f2056c3 Add in-app privacy policy link to comply with Google Play policies
Also added Setup Guide and Troubleshooting Guide links too.
2022-05-15 15:56:19 -05:00
Cameron Gutman 4dd3b2cfb7 Tweak capped FPS option text 2022-05-14 23:33:43 -05:00
Cameron Gutman 2e62ad0f00 Merge remote-tracking branch 'origin/weblate' 2022-05-14 23:31:51 -05:00
Cameron Gutman 41ef292b82 Fix frame rate cap not taking effect with the unlock FPS option enabled 2022-05-14 21:19:51 -05:00
Cameron Gutman aa60671c88 Return the selected refresh rate now that the capped FPS mode is not default 2022-05-14 20:53:42 -05:00
Cameron Gutman f1ccba39e8 Don't raise refresh rate above stream FPS except in min latency mode 2022-05-14 20:53:07 -05:00
Cameron Gutman 226e580a30 Prevent microstutter in balanced mode when streaming at 60 FPS on a 120 Hz display 2022-05-14 20:08:41 -05:00
Cameron Gutman 6f8e719200 Update AGP 2022-05-14 18:25:48 -05:00
Cameron Gutman c127af1e05 Rewrite polling logic to avoid needing to poll using a separate socket first 2022-05-14 18:14:37 -05:00
Wen-haur Chiu 648904cc69 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (211 of 211 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-05-11 19:13:59 +02:00
Cameron Gutman dc85ddb3f9 Reintroduce option of using old frame pacing algorithm using capped FPS 2022-05-08 15:20:08 -05:00
Cameron Gutman 23a7d8555f Avoid activity restarts in StreamSettings and AddComputerManually
We would ideally save and restore state, but this is fine for these specific
transient user activities.

Fixes #1052
Fixes #1055
2022-05-08 14:55:47 -05:00
Cameron Gutman bc9e250d34 Merge remote-tracking branch 'origin/weblate' 2022-05-08 14:40:07 -05:00
Cameron Gutman 2203186527 Remove extra ViewGroup between OSC and StreamView
This allows touch events to be properly split
2022-05-08 14:39:32 -05:00
DankXylese 53d3d9ecb8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/uk/
2022-04-28 21:13:07 +02:00
Cameron Gutman de549f67a1 Update README 2022-04-05 19:51:44 -05:00
Jorys Paulin 755c41481a Translated using Weblate (French)
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/fr/
2022-04-04 10:12:11 +02:00
Dominik Chrástecký aebc2126bc Added translation using Weblate (Czech) 2022-04-03 19:50:26 +02:00
Wen-haur Chiu f43547fb31 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-03-25 18:11:43 +01:00
CorteX 398e4df7cf Translated using Weblate (Chinese (Simplified))
Currently translated at 98.0% (206 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hans/
2022-03-23 06:58:35 +01:00
reloxx13 ff68efc3f5 Translated using Weblate (German)
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/de/
2022-03-21 09:59:13 +01:00
Caio Gabriel 8ba2f51bda Translated using Weblate (Portuguese)
Currently translated at 9.0% (19 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt/
2022-03-19 22:58:22 +01:00
Caio Gabriel 87b79b278b Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt_BR/
2022-03-19 22:58:21 +01:00
Caio Gabriel 121e3ea9be Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt_BR/
2022-03-18 16:45:57 +01:00
Caio Gabriel ec6ed79ee1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt_BR/
2022-03-18 03:47:44 +01:00
Caio Gabriel ca125826a7 Translated using Weblate (Portuguese)
Currently translated at 8.5% (18 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/pt/
2022-03-17 22:58:19 +01:00
Cameron Gutman dd0aecf108 Update BouncyCastle 2022-03-15 22:16:41 -05:00
Wen-haur Chiu ef5cb2f0cd Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (210 of 210 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/zh_Hant/
2022-03-09 15:58:49 +01:00
81 changed files with 2508 additions and 886 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/232a8tadrrn8jv0k/branch/master?svg=true)](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
[![Translation Status](https://hosted.weblate.org/widgets/moonlight/-/moonlight-android/svg-badge.svg)](https://hosted.weblate.org/projects/moonlight/moonlight-android/)
[Moonlight for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
[Moonlight for Android](https://moonlight-stream.org) is an open source client for NVIDIA GameStream, as used by the NVIDIA Shield.
Moonlight for Android will allow you to stream your full collection of games from your Windows PC to your Android device,
whether in your own home or over the internet.
+21 -14
View File
@@ -1,25 +1,28 @@
apply plugin: 'com.android.application'
android {
ndkVersion "23.1.7779620"
ndkVersion "23.2.8568313"
compileSdkVersion 31
compileSdk 33
defaultConfig {
minSdkVersion 16
targetSdkVersion 31
minSdk 16
targetSdk 33
versionName "10.0"
versionCode = 272
versionName "10.6"
versionCode = 283
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
ndk.debugSymbolLevel = 'FULL'
}
flavorDimensions "root"
flavorDimensions.add("root")
productFlavors {
root {
// Android O has native mouse capture, so don't show the rooted
// version to devices running O on the Play Store.
maxSdkVersion 25
maxSdk 25
externalNativeBuild {
ndkBuild {
@@ -29,6 +32,7 @@ android {
applicationId "com.limelight.root"
dimension "root"
buildConfigField "boolean", "ROOT_BUILD", "true"
}
nonRoot {
@@ -40,6 +44,7 @@ android {
applicationId "com.limelight"
dimension "root"
buildConfigField "boolean", "ROOT_BUILD", "false"
}
}
@@ -55,7 +60,7 @@ android {
enableSplit = false
}
density {
// FIXME: This should not be neccessary but we get
// FIXME: This should not be necessary but we get
// weird crashes due to missing drawable resources
// when this split is enabled.
enableSplit = false
@@ -65,6 +70,8 @@ android {
buildTypes {
debug {
applicationIdSuffix ".debug"
resValue "string", "app_label", "Moonlight (Debug)"
resValue "string", "app_label_root", "Moonlight (Root Debug)"
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@@ -101,6 +108,8 @@ android {
//
// TL;DR: Leave the following line alone!
applicationIdSuffix ".unofficial"
resValue "string", "app_label", "Moonlight"
resValue "string", "app_label_root", "Moonlight (Root)"
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@@ -112,16 +121,14 @@ android {
path "src/main/jni/Android.mk"
}
}
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
android.defaultConfig.ndk.debugSymbolLevel = 'FULL'
}
dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.69'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
implementation 'org.jcodec:jcodec:0.2.3'
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
implementation 'com.squareup.okio:okio:1.17.5'
implementation 'org.jmdns:jmdns:3.5.7'
implementation 'com.github.cgutman:ShieldControllerExtensions:1.0'
}
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_label" translatable="false">Moonlight (Debug)</string>
<string name="app_label_root" translatable="false">Moonlight (Root Debug)</string>
</resources>
+12 -1
View File
@@ -44,22 +44,31 @@
android:roundIcon="@mipmap/ic_launcher"
android:installLocation="auto"
android:gwpAsanMode="always"
android:localeConfig="@xml/locales_config"
android:enableOnBackInvokedCallback="false"
android:theme="@style/AppTheme">
<provider
android:name=".PosterContentProvider"
android:authorities="poster.${applicationId}"
android:enabled="true"
android:exported="true">
</provider>
<!-- Samsung multi-window support -->
<uses-library
android:name="com.sec.android.app.multiwindow"
android:required="false" />
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
<!-- Disable Game Mode downscaling since it can break our UI dialogs and doesn't benefit
performance much for us since we don't use GL/Vulkan for rendering anyway -->
<meta-data
android:name="com.android.graphics.intervention.wm.allowDownscale"
android:value="false"/>
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
in each activity even though it is implied by targeting API 24+ -->
@@ -99,6 +108,7 @@
<activity
android:name=".preferences.StreamSettings"
android:resizeableActivity="true"
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
android:label="Streaming Settings">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@@ -108,6 +118,7 @@
android:name=".preferences.AddComputerManually"
android:resizeableActivity="true"
android:windowSoftInputMode="stateVisible"
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
android:label="Add Computer Manually">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@@ -293,6 +293,11 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
setContentView(R.layout.activity_app_view);
// Allow floating expanded PiP overlays while browsing apps
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
setShouldDockBigOverlays(false);
}
UiHelper.notifyNewRootView(this);
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
+203 -35
View File
@@ -78,6 +78,8 @@ import android.widget.TextView;
import android.widget.Toast;
import java.io.ByteArrayInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -87,7 +89,7 @@ import java.util.Locale;
public class Game extends Activity implements SurfaceHolder.Callback,
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks,
PerfOverlayListener
PerfOverlayListener, UsbDriverService.UsbDriverStateListener
{
private int lastButtonState = 0;
@@ -107,6 +109,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
private ControllerHandler controllerHandler;
private KeyboardTranslator keyboardTranslator;
private VirtualController virtualController;
private PreferenceConfiguration prefConfig;
@@ -120,6 +123,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private boolean autoEnterPip = false;
private boolean surfaceCreated = false;
private boolean attemptedConnection = false;
private int suppressPipRefCount = 0;
private String pcName;
private String appName;
private InputCaptureProvider inputCaptureProvider;
private int modifierFlags = 0;
@@ -150,6 +156,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
binder.setListener(controllerHandler);
binder.setStateListener(Game.this);
binder.start();
connectedToUsbDriverService = true;
}
@@ -259,22 +267,29 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Make sure Wi-Fi is fully powered up
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
highPerfWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Moonlight High Perf Lock");
highPerfWifiLock.setReferenceCounted(false);
highPerfWifiLock.acquire();
try {
highPerfWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Moonlight High Perf Lock");
highPerfWifiLock.setReferenceCounted(false);
highPerfWifiLock.acquire();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
lowLatencyWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "Moonlight Low Latency Lock");
lowLatencyWifiLock.setReferenceCounted(false);
lowLatencyWifiLock.acquire();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
lowLatencyWifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "Moonlight Low Latency Lock");
lowLatencyWifiLock.setReferenceCounted(false);
lowLatencyWifiLock.acquire();
}
} catch (SecurityException e) {
// Some Samsung Galaxy S10+/S10e devices throw a SecurityException from
// WifiLock.acquire() even though we have android.permission.WAKE_LOCK in our manifest.
e.printStackTrace();
}
appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
String appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
boolean appSupportsHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
@@ -389,6 +404,28 @@ public class Game extends Activity implements SurfaceHolder.Callback,
float displayRefreshRate = prepareDisplayForRendering();
LimeLog.info("Display refresh rate: "+displayRefreshRate);
// If the user requested frame pacing using a capped FPS, we will need to change our
// desired FPS setting here in accordance with the active display refresh rate.
int roundedRefreshRate = Math.round(displayRefreshRate);
int chosenFrameRate = prefConfig.fps;
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
if (prefConfig.fps >= roundedRefreshRate) {
if (prefConfig.fps > roundedRefreshRate + 3) {
// Use frame drops when rendering above the screen frame rate
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
LimeLog.info("Using drop mode for FPS > Hz");
} else if (roundedRefreshRate <= 49) {
// Let's avoid clearly bogus refresh rates and fall back to legacy rendering
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
LimeLog.info("Bogus refresh rate: " + roundedRefreshRate);
}
else {
chosenFrameRate = roundedRefreshRate - 1;
LimeLog.info("Adjusting FPS target for screen to " + chosenFrameRate);
}
}
}
boolean vpnActive = NetHelper.isActiveNetworkVpn(this);
if (vpnActive) {
LimeLog.info("Detected active network is a VPN");
@@ -397,7 +434,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
StreamConfiguration config = new StreamConfiguration.Builder()
.setResolution(prefConfig.width, prefConfig.height)
.setLaunchRefreshRate(prefConfig.fps)
.setRefreshRate(prefConfig.fps)
.setRefreshRate(chosenFrameRate)
.setApp(new NvApp(appName != null ? appName : "app", appId, appSupportsHdr))
.setBitrate(prefConfig.bitrate)
.setEnableSops(prefConfig.enableSops)
@@ -418,9 +455,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize the connection
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
keyboardTranslator = new KeyboardTranslator();
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
inputManager.registerInputDeviceListener(controllerHandler, null);
inputManager.registerInputDeviceListener(keyboardTranslator, null);
// Initialize touch contexts
for (int i = 0; i < touchContextMap.length; i++) {
@@ -430,7 +469,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);
}
}
@@ -491,6 +530,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
performanceOverlayView.setVisibility(View.GONE);
notificationOverlayView.setVisibility(View.GONE);
// Update GameManager state to indicate we're in PiP (still gaming, but interruptible)
UiHelper.notifyStreamEnteringPiP(this);
}
else {
isHidingOverlays = false;
@@ -506,6 +548,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
notificationOverlayView.setVisibility(requestedNotificationOverlayVisibility);
// Update GameManager state to indicate we're out of PiP (gaming, non-interruptible)
UiHelper.notifyStreamExitingPiP(this);
}
}
}
@@ -524,14 +569,28 @@ public class Game extends Activity implements SurfaceHolder.Callback,
builder.setSeamlessResizeEnabled(true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (appName != null) {
builder.setTitle(appName);
if (pcName != null) {
builder.setSubtitle(pcName);
}
}
else if (pcName != null) {
builder.setTitle(pcName);
}
}
return builder.build();
}
private void setPipAutoEnter(boolean autoEnter) {
private void updatePipAutoEnter() {
if (!prefConfig.enablePip) {
return;
}
boolean autoEnter = connected && suppressPipRefCount == 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setPictureInPictureParams(getPictureInPictureParams(autoEnter));
}
@@ -540,13 +599,42 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
public void setMetaKeyCaptureState(boolean enabled) {
// This uses custom APIs present on some Samsung devices to allow capture of
// meta key events while streaming.
try {
Class<?> semWindowManager = Class.forName("com.samsung.android.view.SemWindowManager");
Method getInstanceMethod = semWindowManager.getMethod("getInstance");
Object manager = getInstanceMethod.invoke(null);
if (manager != null) {
Class<?>[] parameterTypes = new Class<?>[2];
parameterTypes[0] = String.class;
parameterTypes[1] = boolean.class;
Method requestMetaKeyEventMethod = semWindowManager.getDeclaredMethod("requestMetaKeyEvent", parameterTypes);
requestMetaKeyEventMethod.invoke(manager, this.getComponentName(), enabled);
}
else {
LimeLog.warning("SemWindowManager.getInstance() returned null");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public void onUserLeaveHint() {
super.onUserLeaveHint();
// PiP is only supported on Oreo and later, and we don't need to manually enter PiP on
// Android S and later.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
// Android S and later. On Android R, we will use onPictureInPictureRequested() instead.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (autoEnterPip) {
try {
// This has thrown all sorts of weird exceptions on Samsung devices
@@ -560,6 +648,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
@Override
@TargetApi(Build.VERSION_CODES.R)
public boolean onPictureInPictureRequested() {
// Enter PiP when requested unless we're on Android 12 which supports auto-enter.
if (autoEnterPip && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
enterPictureInPictureMode(getPictureInPictureParams(false));
}
return true;
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
@@ -573,6 +671,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
inputCaptureProvider.onWindowFocusChanged(hasFocus);
}
private boolean isRefreshRateEqualMatch(float refreshRate) {
return refreshRate >= prefConfig.fps &&
refreshRate <= prefConfig.fps + 3;
}
private boolean isRefreshRateGoodMatch(float refreshRate) {
return refreshRate >= prefConfig.fps &&
Math.round(refreshRate) % prefConfig.fps <= 3;
@@ -608,6 +711,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
Display.Mode bestMode = display.getMode();
boolean isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
boolean refreshRateIsEqual = isRefreshRateEqualMatch(bestMode.getRefreshRate());
for (Display.Mode candidate : display.getSupportedModes()) {
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
@@ -639,12 +743,30 @@ public class Game extends Activity implements SurfaceHolder.Callback,
continue;
}
if (refreshRateIsGood) {
// We have a good matching refresh rate, so we're looking for equal or greater
// that is also a good matching refresh rate for our stream frame rate.
if (refreshRateReduced || !isRefreshRateGoodMatch(candidate.getRefreshRate())) {
if (prefConfig.framePacing != PreferenceConfiguration.FRAME_PACING_MIN_LATENCY &&
refreshRateIsEqual && !isRefreshRateEqualMatch(candidate.getRefreshRate())) {
// If we had an equal refresh rate and this one is not, skip it. In min latency
// mode, we want to always prefer the highest frame rate even though it may cause
// microstuttering.
continue;
}
else if (refreshRateIsGood) {
// We've already got a good match, so if this one isn't also good, it's not
// worth considering at all.
if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
continue;
}
// We don't want ever reduce our refresh rate unless we found an exact
// match and we're not in min latency mode.
if (refreshRateReduced) {
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_MIN_LATENCY) {
continue;
}
else if (!isRefreshRateEqualMatch(candidate.getRefreshRate())) {
continue;
}
}
}
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
// We didn't have a good match and this match isn't good either, so just don't
@@ -661,6 +783,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
bestMode = candidate;
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
refreshRateIsEqual = isRefreshRateEqualMatch(candidate.getRefreshRate());
}
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
@@ -741,9 +864,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return displayRefreshRate;
}
else {
// Use the actual refresh rate of the display, since the preferred refresh rate or mode
// may not actually be applied (ex: Pixel 4 with Smooth Display disabled).
return getWindowManager().getDefaultDisplay().getRefreshRate();
// Use the lower of the current refresh rate and the selected refresh rate.
// The preferred refresh rate may not actually be applied (ex: Battery Saver mode).
return Math.min(getWindowManager().getDefaultDisplay().getRefreshRate(), displayRefreshRate);
}
}
@@ -818,10 +941,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
protected void onDestroy() {
super.onDestroy();
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
if (controllerHandler != null) {
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
inputManager.unregisterInputDeviceListener(controllerHandler);
}
if (keyboardTranslator != null) {
inputManager.unregisterInputDeviceListener(keyboardTranslator);
}
if (lowLatencyWifiLock != null) {
lowLatencyWifiLock.release();
@@ -1041,7 +1167,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (!handled) {
// Try the keyboard handler
short translated = KeyboardTranslator.translate(event.getKeyCode());
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
if (translated == 0) {
return false;
}
@@ -1111,7 +1237,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (!handled) {
// Try the keyboard handler
short translated = KeyboardTranslator.translate(event.getKeyCode());
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
if (translated == 0) {
return false;
}
@@ -1146,10 +1272,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
@Override
public void showKeyboard() {
LimeLog.info("Showing keyboard overlay");
public void toggleKeyboard() {
LimeLog.info("Toggling keyboard overlay");
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
inputManager.toggleSoftInput(0, 0);
}
// Returns true if the event was consumed
@@ -1197,7 +1323,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) {
@@ -1376,15 +1507,23 @@ public class Game extends Activity implements SurfaceHolder.Callback,
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
if (event.getPointerCount() == 1) {
if (event.getPointerCount() == 1 &&
(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || (event.getFlags() & MotionEvent.FLAG_CANCELED) == 0)) {
// All fingers up
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
// This is a 3 finger tap to bring up the keyboard
showKeyboard();
toggleKeyboard();
return true;
}
}
context.touchUpEvent(eventX, eventY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && (event.getFlags() & MotionEvent.FLAG_CANCELED) != 0) {
context.cancelTouch();
}
else {
context.touchUpEvent(eventX, eventY);
}
for (TouchContext touchContext : touchContextMap) {
touchContext.setPointerCount(event.getPointerCount() - 1);
}
@@ -1521,11 +1660,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
private void stopConnection() {
if (connecting || connected) {
setPipAutoEnter(false);
connecting = connected = false;
updatePipAutoEnter();
controllerHandler.stop();
// Update GameManager state to indicate we're no longer in game
UiHelper.notifyStreamEnded(this);
// Stop may take a few hundred ms to do some network I/O to tell
// the server we're going away and clean up. Let it run in a separate
// thread to keep things smooth for the UI. Inside moonlight-common,
@@ -1595,6 +1737,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Enable cursor visibility again
inputCaptureProvider.disableCapture();
// Disable meta key capture
setMetaKeyCaptureState(false);
if (!displayedFailureDialog) {
displayedFailureDialog = true;
LimeLog.severe("Connection terminated: " + errorCode);
@@ -1686,9 +1831,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
spinner = null;
}
setPipAutoEnter(true);
connected = true;
connecting = false;
updatePipAutoEnter();
// Hide the mouse cursor now after a short delay.
// Doing it before dismissing the spinner seems to be undone
@@ -1706,6 +1851,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Keep the display on
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Enable meta key capture
setMetaKeyCaptureState(true);
// Update GameManager state to indicate we're in game
UiHelper.notifyStreamConnected(Game.this);
hideSystemUi(1000);
}
});
@@ -1755,6 +1906,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
if (!attemptedConnection) {
attemptedConnection = true;
// Update GameManager state to indicate we're "loading" while connecting
UiHelper.notifyStreamConnecting(Game.this);
decoderRenderer.setRenderTarget(holder);
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
}
@@ -1832,7 +1986,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void keyboardEvent(boolean buttonDown, short keyCode) {
short keyMap = KeyboardTranslator.translate(keyCode);
short keyMap = keyboardTranslator.translate(keyCode, -1);
if (keyMap != 0) {
// handleSpecialKeys() takes the Android keycode
if (handleSpecialKeys(keyCode, buttonDown)) {
@@ -1880,4 +2034,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
});
}
@Override
public void onUsbPermissionPromptStarting() {
// Disable PiP auto-enter while the USB permission prompt is on-screen. This prevents
// us from entering PiP while the user is interacting with the OS permission dialog.
suppressPipRefCount++;
updatePipAutoEnter();
}
@Override
public void onUsbPermissionPromptCompleted() {
suppressPipRefCount--;
updatePipAutoEnter();
}
}
@@ -2,9 +2,12 @@ package com.limelight;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import com.limelight.utils.SpinnerDialog;
@@ -13,10 +16,26 @@ public class HelpActivity extends Activity {
private SpinnerDialog loadingDialog;
private WebView webView;
private boolean backCallbackRegistered;
private OnBackInvokedCallback onBackInvokedCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedCallback = new OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
// We should always be able to go back because we unregister our callback
// when we can't go back. Nonetheless, we will still check anyway.
if (webView.canGoBack()) {
webView.goBack();
}
}
};
}
webView = new WebView(this);
setContentView(webView);
@@ -39,6 +58,8 @@ public class HelpActivity extends Activity {
getResources().getString(R.string.help_loading_title),
getResources().getString(R.string.help_loading_msg), false);
}
refreshBackDispatchState();
}
@Override
@@ -47,6 +68,8 @@ public class HelpActivity extends Activity {
loadingDialog.dismiss();
loadingDialog = null;
}
refreshBackDispatchState();
}
@Override
@@ -59,7 +82,33 @@ public class HelpActivity extends Activity {
webView.loadUrl(getIntent().getData().toString());
}
private void refreshBackDispatchState() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (webView.canGoBack() && !backCallbackRegistered) {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
backCallbackRegistered = true;
}
else if (!webView.canGoBack() && backCallbackRegistered) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
backCallbackRegistered = false;
}
}
}
@Override
protected void onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (backCallbackRegistered) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
}
}
super.onDestroy();
}
@Override
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
public void onBackPressed() {
// Back goes back through the WebView history
// until no more history remains
+7 -4
View File
@@ -124,6 +124,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
UiHelper.notifyNewRootView(this);
// Allow floating expanded PiP overlays while browsing PCs
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
setShouldDockBigOverlays(false);
}
// Set default preferences if we've never been run
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
@@ -374,8 +379,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 +516,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;
}
@@ -83,7 +83,7 @@ public class ShortcutTrampoline extends Activity {
}
// Try to wake the target PC if it's offline (up to some retry limit)
if (details.state == ComputerDetails.State.OFFLINE && --wakeHostTries >= 0) {
if (details.state == ComputerDetails.State.OFFLINE && details.macAddress != null && --wakeHostTries >= 0) {
try {
// Make a best effort attempt to wake the target PC
WakeOnLanSender.sendWolPacket(computer);
@@ -10,6 +10,7 @@ import android.media.AudioAttributes;
import android.os.Build;
import android.os.CombinedVibration;
import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
@@ -31,6 +32,8 @@ import com.limelight.preferences.PreferenceConfiguration;
import com.limelight.ui.GameGestures;
import com.limelight.utils.Vector2d;
import org.cgutman.shieldcontrollerextensions.SceManager;
import java.lang.reflect.InvocationTargetException;
import java.util.Timer;
import java.util.TimerTask;
@@ -60,6 +63,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
private final InputDeviceContext defaultContext = new InputDeviceContext();
private final GameGestures gestures;
private final Vibrator deviceVibrator;
private final SceManager sceManager;
private boolean hasGameController;
private final PreferenceConfiguration prefConfig;
@@ -72,9 +76,10 @@ 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;
this.sceManager = new SceManager(activityContext);
this.sceManager.start();
int deadzonePercentage = prefConfig.deadzonePercentage;
int[] ids = InputDevice.getDeviceIds();
for (int id : ids) {
@@ -201,6 +206,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
deviceContext.destroy();
}
sceManager.stop();
deviceVibrator.cancel();
}
@@ -506,6 +512,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
LimeLog.info(dev.toString());
context.inputDevice = dev;
context.name = devName;
context.id = dev.getId();
context.external = isExternal(dev);
@@ -703,9 +710,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.
@@ -1362,7 +1366,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
}
vm.vibrate(combo.combine());
VibrationAttributes.Builder vibrationAttributes = new VibrationAttributes.Builder()
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
vibrationAttributes.setUsage(VibrationAttributes.USAGE_MEDIA);
}
vm.vibrate(combo.combine(), vibrationAttributes.build());
}
private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
@@ -1387,10 +1398,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (vibrator.hasAmplitudeControl()) {
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.build();
vibrator.vibrate(effect, audioAttributes);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_MEDIA)
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
.build();
vibrator.vibrate(effect, vibrationAttributes);
}
else {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.build();
vibrator.vibrate(effect, audioAttributes);
}
return;
}
}
@@ -1400,7 +1420,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
long pwmPeriod = 20;
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
long offTime = pwmPeriod - onTime;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
.setUsage(VibrationAttributes.USAGE_MEDIA)
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
.build();
vibrator.vibrate(VibrationEffect.createWaveform(new long[]{0, onTime, offTime}, 0), vibrationAttributes);
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.build();
@@ -1421,10 +1448,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (deviceContext.controllerNumber == controllerNumber) {
foundMatchingDevice = true;
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
vibrated = true;
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
}
// On Shield devices, we can use their special API to rumble Shield controllers
else if (sceManager.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
vibrated = true;
}
// If all else fails, we have to try the old Vibrator API
else if (deviceContext.vibrator != null) {
vibrated = true;
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
@@ -1482,7 +1515,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// UI thread.
try {
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
switch (keyCode) {
@@ -1591,7 +1631,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
try {
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
@@ -1609,7 +1656,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
try {
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
@@ -1891,6 +1945,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public String name;
public VibratorManager vibratorManager;
public Vibrator vibrator;
public InputDevice inputDevice;
public int leftStickXAxis = -1;
public int leftStickYAxis = -1;
@@ -1,13 +1,20 @@
package com.limelight.binding.input;
import android.annotation.TargetApi;
import android.hardware.input.InputManager;
import android.os.Build;
import android.util.SparseArray;
import android.view.InputDevice;
import android.view.KeyEvent;
import java.util.Arrays;
/**
* Class to translate a Android key code into the codes GFE is expecting
* @author Diego Waxemberg
* @author Cameron Gutman
*/
public class KeyboardTranslator {
public class KeyboardTranslator implements InputManager.InputDeviceListener {
/**
* GFE's prefix for every key code
@@ -48,6 +55,55 @@ public class KeyboardTranslator {
public static final int VK_QUOTE = 222;
public static final int VK_PAUSE = 19;
private static class KeyboardMapping {
private final InputDevice device;
private final int[] deviceKeyCodeToQwertyKeyCode;
@TargetApi(33)
public KeyboardMapping(InputDevice device) {
int maxKeyCode = KeyEvent.getMaxKeyCode();
this.device = device;
this.deviceKeyCodeToQwertyKeyCode = new int[maxKeyCode + 1];
// Any unmatched keycodes are treated as unknown
Arrays.fill(deviceKeyCodeToQwertyKeyCode, KeyEvent.KEYCODE_UNKNOWN);
for (int i = 0; i <= maxKeyCode; i++) {
int deviceKeyCode = device.getKeyCodeForKeyLocation(i);
if (deviceKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
deviceKeyCodeToQwertyKeyCode[deviceKeyCode] = i;
}
}
}
@TargetApi(33)
public int getDeviceKeyCodeForQwertyKeyCode(int qwertyKeyCode) {
return device.getKeyCodeForKeyLocation(qwertyKeyCode);
}
public int getQwertyKeyCodeForDeviceKeyCode(int deviceKeyCode) {
if (deviceKeyCode > KeyEvent.getMaxKeyCode()) {
return KeyEvent.KEYCODE_UNKNOWN;
}
return deviceKeyCodeToQwertyKeyCode[deviceKeyCode];
}
}
private final SparseArray<KeyboardMapping> keyboardMappings = new SparseArray<>();
public KeyboardTranslator() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
for (int deviceId : InputDevice.getDeviceIds()) {
InputDevice device = InputDevice.getDevice(deviceId);
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
keyboardMappings.set(deviceId, new KeyboardMapping(device));
}
}
}
}
public static boolean needsShift(int keycode) {
switch (keycode)
{
@@ -65,10 +121,24 @@ public class KeyboardTranslator {
/**
* Translates the given keycode and returns the GFE keycode
* @param keycode the code to be translated
* @param deviceId InputDevice.getId() or -1 if unknown
* @return a GFE keycode for the given keycode
*/
public static short translate(int keycode) {
public short translate(int keycode, int deviceId) {
int translated;
// If a device ID was provided, look up the keyboard mapping
if (deviceId >= 0) {
KeyboardMapping mapping = keyboardMappings.get(deviceId);
if (mapping != null) {
// Try to map this device-specific keycode onto a QWERTY layout.
// GFE assumes incoming keycodes are from a QWERTY keyboard.
int qwertyKeyCode = mapping.getQwertyKeyCodeForDeviceKeyCode(keycode);
if (qwertyKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
keycode = qwertyKeyCode;
}
}
}
// This is a poor man's mapping between Android key codes
// and Windows VK_* codes. For all defined VK_ codes, see:
@@ -294,4 +364,30 @@ public class KeyboardTranslator {
return (short) ((KEY_PREFIX << 8) | translated);
}
@Override
public void onInputDeviceAdded(int index) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
InputDevice device = InputDevice.getDevice(index);
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
keyboardMappings.put(index, new KeyboardMapping(device));
}
}
}
@Override
public void onInputDeviceRemoved(int index) {
keyboardMappings.remove(index);
}
@Override
public void onInputDeviceChanged(int index) {
keyboardMappings.remove(index);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
InputDevice device = InputDevice.getDevice(index);
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
keyboardMappings.set(index, new KeyboardMapping(device));
}
}
}
}
@@ -2,8 +2,8 @@ package com.limelight.binding.input.capture;
import android.app.Activity;
import com.limelight.BuildConfig;
import com.limelight.LimeLog;
import com.limelight.LimelightBuildProps;
import com.limelight.R;
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
import com.limelight.binding.input.evdev.EvdevListener;
@@ -16,7 +16,7 @@ public class InputCaptureManager {
}
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
else if (!LimelightBuildProps.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
else if (!BuildConfig.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using NVIDIA mouse capture extension");
return new ShieldCaptureProvider(activity);
}
@@ -37,7 +37,9 @@ public abstract class AbstractXboxController extends AbstractController {
// around when we call notifyDeviceAdded(), we won't be able to claim
// the controller number used by the original InputDevice.
Thread.sleep(1000);
} catch (InterruptedException e) {}
} catch (InterruptedException e) {
return;
}
// Report that we're added _before_ reporting input
notifyDeviceAdded();
@@ -29,6 +29,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
private UsbManager usbManager;
private PreferenceConfiguration prefConfig;
private boolean started;
private final UsbEventReceiver receiver = new UsbEventReceiver();
private final UsbDriverBinder binder = new UsbDriverBinder();
@@ -36,6 +37,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
private final ArrayList<AbstractController> controllers = new ArrayList<>();
private UsbDriverListener listener;
private UsbDriverStateListener stateListener;
private int nextDeviceId;
@Override
@@ -93,6 +95,11 @@ public class UsbDriverService extends Service implements UsbDriverListener {
else if (action.equals(ACTION_USB_PERMISSION)) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// Permission dialog is now closed
if (stateListener != null) {
stateListener.onUsbPermissionPromptCompleted();
}
// If we got this far, we've already found we're able to handle this device
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
handleUsbDeviceState(device);
@@ -112,6 +119,18 @@ public class UsbDriverService extends Service implements UsbDriverListener {
}
}
}
public void setStateListener(UsbDriverStateListener stateListener) {
UsbDriverService.this.stateListener = stateListener;
}
public void start() {
UsbDriverService.this.start();
}
public void stop() {
UsbDriverService.this.stop();
}
}
private void handleUsbDeviceState(UsbDevice device) {
@@ -121,20 +140,29 @@ public class UsbDriverService extends Service implements UsbDriverListener {
if (!usbManager.hasPermission(device)) {
// Let's ask for permission
try {
// Tell the state listener that we're about to display a permission dialog
if (stateListener != null) {
stateListener.onUsbPermissionPromptStarting();
}
int intentFlags = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// This PendingIntent must be mutable to allow the framework to populate EXTRA_DEVICE and EXTRA_PERMISSION_GRANTED.
intentFlags |= PendingIntent.FLAG_MUTABLE;
}
// This function is not documented as throwing any exceptions (denying access
// is indicated by calling the PendingIntent with a false result). However,
// Samsung Knox has some policies which block this request, but rather than
// just returning a false result or returning 0 enumerated devices,
// they throw an undocumented SecurityException from this call, crashing
// the whole app. :(
int intentFlags = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// This PendingIntent must be mutable to allow the framework to populate EXTRA_DEVICE and EXTRA_PERMISSION_GRANTED.
intentFlags |= PendingIntent.FLAG_MUTABLE;
}
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), intentFlags));
} catch (SecurityException e) {
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
if (stateListener != null) {
stateListener.onUsbPermissionPromptCompleted();
}
}
return;
}
@@ -225,16 +253,23 @@ public class UsbDriverService extends Service implements UsbDriverListener {
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
}
@Override
public void onCreate() {
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
this.prefConfig = PreferenceConfiguration.readPreferences(this);
private void start() {
if (started) {
return;
}
started = true;
// Register for USB attach broadcasts and permission completions
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(ACTION_USB_PERMISSION);
registerReceiver(receiver, filter);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED);
}
else {
registerReceiver(receiver, filter);
}
// Enumerate existing devices
for (UsbDevice dev : usbManager.getDeviceList().values()) {
@@ -245,14 +280,16 @@ public class UsbDriverService extends Service implements UsbDriverListener {
}
}
@Override
public void onDestroy() {
private void stop() {
if (!started) {
return;
}
started = false;
// Stop the attachment receiver
unregisterReceiver(receiver);
// Remove listeners
listener = null;
// Stop all controllers
while (controllers.size() > 0) {
// Stop and remove the controller
@@ -260,8 +297,28 @@ public class UsbDriverService extends Service implements UsbDriverListener {
}
}
@Override
public void onCreate() {
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
this.prefConfig = PreferenceConfiguration.readPreferences(this);
}
@Override
public void onDestroy() {
stop();
// Remove listeners
listener = null;
stateListener = null;
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public interface UsbDriverStateListener {
void onUsbPermissionPromptStarting();
void onUsbPermissionPromptCompleted();
}
}
@@ -3,12 +3,12 @@ package com.limelight.binding.input.evdev;
import android.app.Activity;
import com.limelight.LimelightBuildProps;
import com.limelight.BuildConfig;
import com.limelight.binding.input.capture.InputCaptureProvider;
public class EvdevCaptureProviderShim {
public static boolean isCaptureProviderSupported() {
return LimelightBuildProps.ROOT_BUILD;
return BuildConfig.ROOT_BUILD;
}
// We need to construct our capture provider using reflection because it isn't included in non-root builds
@@ -116,7 +116,14 @@ public class AbsoluteTouchContext implements TouchContext {
try {
// FIXME: Sleeping on the main thread sucks
Thread.sleep(50);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
@@ -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
@@ -139,7 +143,14 @@ public class RelativeTouchContext implements TouchContext {
// do input detection by polling
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
// Raise the mouse button
conn.sendMouseButtonUp(buttonIndex);
@@ -251,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
@@ -9,12 +9,11 @@ import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.limelight.LimeLog;
import com.limelight.R;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.nvstream.NvConnection;
import java.util.ArrayList;
import java.util.List;
@@ -40,11 +39,10 @@ public class VirtualController {
private static final boolean _PRINT_DEBUG_INFORMATION = false;
private ControllerHandler controllerHandler;
private Context context = null;
private final ControllerHandler controllerHandler;
private final Context context;
private FrameLayout frame_layout = null;
private RelativeLayout relative_layout = null;
private Timer retransmitTimer;
@@ -60,10 +58,6 @@ public class VirtualController {
this.frame_layout = layout;
this.context = context;
relative_layout = new RelativeLayout(context);
frame_layout.addView(relative_layout);
buttonConfigure = new Button(context);
buttonConfigure.setAlpha(0.25f);
buttonConfigure.setFocusable(false);
@@ -87,7 +81,7 @@ public class VirtualController {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
relative_layout.invalidate();
buttonConfigure.invalidate();
for (VirtualControllerElement element : elements) {
element.invalidate();
@@ -99,11 +93,20 @@ public class VirtualController {
public void hide() {
retransmitTimer.cancel();
relative_layout.setVisibility(View.INVISIBLE);
for (VirtualControllerElement element : elements) {
element.setVisibility(View.INVISIBLE);
}
buttonConfigure.setVisibility(View.INVISIBLE);
}
public void show() {
relative_layout.setVisibility(View.VISIBLE);
for (VirtualControllerElement element : elements) {
element.setVisibility(View.VISIBLE);
}
buttonConfigure.setVisibility(View.VISIBLE);
// HACK: GFE sometimes discards gamepad packets when they are received
// very shortly after another. This can be critical if an axis zeroing packet
@@ -120,9 +123,11 @@ public class VirtualController {
public void removeElements() {
for (VirtualControllerElement element : elements) {
relative_layout.removeView(element);
frame_layout.removeView(element);
}
elements.clear();
frame_layout.removeView(buttonConfigure);
}
public void setOpacity(int opacity) {
@@ -134,10 +139,10 @@ public class VirtualController {
public void addElement(VirtualControllerElement element, int x, int y, int width, int height) {
elements.add(element);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width, height);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
layoutParams.setMargins(x, y, 0, 0);
relative_layout.addView(element, layoutParams);
frame_layout.addView(element, layoutParams);
}
public List<VirtualControllerElement> getElements() {
@@ -146,23 +151,20 @@ public class VirtualController {
private static final void _DBG(String text) {
if (_PRINT_DEBUG_INFORMATION) {
System.out.println("VirtualController: " + text);
LimeLog.info("VirtualController: " + text);
}
}
public void refreshLayout() {
relative_layout.removeAllViews();
removeElements();
DisplayMetrics screen = context.getResources().getDisplayMetrics();
int buttonSize = (int)(screen.heightPixels*0.06f);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(buttonSize, buttonSize);
params.leftMargin = 15;
params.topMargin = 15;
relative_layout.addView(buttonConfigure, params);
frame_layout.addView(buttonConfigure, params);
// Start with the default layout
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
@@ -12,7 +12,7 @@ import android.graphics.Paint;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.FrameLayout;
import org.json.JSONException;
import org.json.JSONObject;
@@ -72,7 +72,7 @@ public abstract class VirtualControllerElement extends View {
int newPos_x = (int) getX() + x - pressed_x;
int newPos_y = (int) getY() + y - pressed_y;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = newPos_x > 0 ? newPos_x : 0;
layoutParams.topMargin = newPos_y > 0 ? newPos_y : 0;
@@ -83,7 +83,7 @@ public abstract class VirtualControllerElement extends View {
}
protected void resizeElement(int pressed_x, int pressed_y, int width, int height) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
int newHeight = height + (startSize_y - pressed_y);
int newWidth = width + (startSize_x - pressed_x);
@@ -316,7 +316,7 @@ public abstract class VirtualControllerElement extends View {
public JSONObject getConfiguration() throws JSONException {
JSONObject configuration = new JSONObject();
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
configuration.put("LEFT", layoutParams.leftMargin);
configuration.put("TOP", layoutParams.topMargin);
@@ -327,7 +327,7 @@ public abstract class VirtualControllerElement extends View {
}
public void loadConfiguration(JSONObject configuration) throws JSONException {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = configuration.getInt("LEFT");
layoutParams.topMargin = configuration.getInt("TOP");
@@ -1,6 +1,7 @@
package com.limelight.binding.video;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.LinkedBlockingQueue;
@@ -14,6 +15,7 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.jni.MoonBridge;
import com.limelight.preferences.PreferenceConfiguration;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -22,7 +24,8 @@ import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CodecException;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SystemClock;
import android.util.Range;
import android.view.Choreographer;
@@ -49,7 +52,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;
@@ -86,6 +89,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
private LinkedBlockingQueue<Integer> outputBufferQueue = new LinkedBlockingQueue<>();
private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2;
private long lastRenderedFrameTimeNanos;
private HandlerThread choreographerHandlerThread;
private Handler choreographerHandler;
private int numSpsIn;
private int numPpsIn;
@@ -101,6 +107,61 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
return decoder;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.width, prefs.height, prefs.fps);
List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints();
if (perfPoints != null) {
for (MediaCodecInfo.VideoCapabilities.PerformancePoint perfPoint : perfPoints) {
// If we find a performance point that covers our target, we're good to go
if (perfPoint.covers(targetPerfPoint)) {
return true;
}
}
// We had performance point data but none met the specified streaming settings
return false;
}
// Fall-through to try the Android M API if there's no performance point data
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
// We'll ask the decoder what it can do for us at this resolution and see if our
// requested frame rate falls below or inside the range of achievable frame rates.
Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.width, prefs.height);
if (fpsRange != null) {
return prefs.fps <= fpsRange.getUpper();
}
// Fall-through to try the Android L API if there's no performance point data
} catch (IllegalArgumentException e) {
// Video size not supported at any frame rate
return false;
}
}
// As a last resort, we will use areSizeAndRateSupported() which is explicitly NOT a
// performance metric, but it can work at least for the purpose of determining if
// the codec is going to die when given a stream with the specified settings.
return caps.areSizeAndRateSupported(prefs.width, prefs.height, prefs.fps);
}
private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MediaCodecInfo.VideoCapabilities avcCaps = avcDecoderInfo.getCapabilitiesForType("video/avc").getVideoCapabilities();
MediaCodecInfo.VideoCapabilities hevcCaps = hevcDecoderInfo.getCapabilitiesForType("video/hevc").getVideoCapabilities();
return !decoderCanMeetPerformancePoint(avcCaps, prefs) && decoderCanMeetPerformancePoint(hevcCaps, prefs);
}
else {
// No performance data
return false;
}
}
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
// Don't return anything if HEVC is forced off
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
@@ -112,16 +173,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
// We need HEVC Main profile, so we could pass that constant to findProbableSafeDecoder, however
// some decoders (at least Qualcomm's Snapdragon 805) don't properly report support
// for even required levels of HEVC.
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
if (decoderInfo != null) {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork, prefs)) {
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
MediaCodecInfo hevcDecoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
if (hevcDecoderInfo != null) {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(hevcDecoderInfo.getName(), meteredNetwork, prefs)) {
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+hevcDecoderInfo.getName());
// Force HEVC enabled if the user asked for it
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
LimeLog.info("Forcing HEVC enabled despite non-whitelisted decoder");
}
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
else if (requestedHdr) {
LimeLog.info("Forcing HEVC enabled for HDR streaming");
}
// > 4K streaming also requires HEVC, so force it on there too.
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr ||
prefs.width > 4096 || prefs.height > 4096) {
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
else if (prefs.width > 4096 || prefs.height > 4096) {
LimeLog.info("Forcing HEVC enabled for over 4K streaming");
}
// Use HEVC if the H.264 decoder is unable to meet the performance point
else if (avcDecoder != null && decoderCanMeetPerformancePointWithHevcAndNotAvc(avcDecoder, hevcDecoderInfo, prefs)) {
LimeLog.info("Using non-whitelisted HEVC decoder to meet performance point");
}
else {
return null;
@@ -129,7 +200,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
}
}
return decoderInfo;
return hevcDecoderInfo;
}
public void setRenderTarget(SurfaceHolder renderTarget) {
@@ -231,6 +302,77 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
return this.videoFormat;
}
private MediaFormat createBaseMediaFormat(String mimeType) {
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, initialWidth, initialHeight);
// Avoid setting KEY_FRAME_RATE on Lollipop and earlier to reduce compatibility risk
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, refreshRate);
}
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
// so we don't fill these pre-KitKat
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, initialWidth);
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, initialHeight);
}
return videoFormat;
}
private boolean tryConfigureDecoder(MediaCodecInfo selectedDecoderInfo, MediaFormat format) {
try {
videoDecoder = MediaCodec.createByCodecName(selectedDecoderInfo.getName());
LimeLog.info("Configuring with format: "+format);
videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
configuredFormat = format;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// This will contain the actual accepted input format attributes
inputFormat = videoDecoder.getInputFormat();
LimeLog.info("Input format: "+inputFormat);
}
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
videoDecoder.setOnFrameRenderedListener(new MediaCodec.OnFrameRenderedListener() {
@Override
public void onFrameRendered(MediaCodec mediaCodec, long presentationTimeUs, long renderTimeNanos) {
long delta = (renderTimeNanos / 1000000L) - (presentationTimeUs / 1000);
if (delta >= 0 && delta < 1000) {
if (USE_FRAME_RENDER_TIME) {
activeWindowVideoStats.totalTimeMs += delta;
}
}
}
}, null);
}
LimeLog.info("Using codec "+selectedDecoderInfo.getName()+" for hardware decoding "+format.getString(MediaFormat.KEY_MIME));
// Start the decoder
videoDecoder.start();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
legacyInputBuffers = videoDecoder.getInputBuffers();
}
return true;
} catch (Exception e) {
e.printStackTrace();
if (videoDecoder != null) {
videoDecoder.release();
videoDecoder = null;
}
return false;
}
}
@Override
public int setup(int format, int width, int height, int redrawRate) {
this.initialWidth = width;
@@ -293,72 +435,25 @@ 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
try {
videoDecoder = MediaCodec.createByCodecName(selectedDecoderInfo.getName());
} catch (Exception e) {
e.printStackTrace();
return -4;
}
for (int tryNumber = 0;; tryNumber++) {
LimeLog.info("Decoder configuration try: "+tryNumber);
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, width, height);
MediaFormat mediaFormat = createBaseMediaFormat(mimeType);
// Avoid setting KEY_FRAME_RATE on Lollipop and earlier to reduce compatibility risk
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, redrawRate);
}
// This will try low latency options until we find one that works (or we give up).
boolean newFormat = MediaCodecHelper.setDecoderLowLatencyOptions(mediaFormat, selectedDecoderInfo, tryNumber);
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
// so we don't fill these pre-KitKat
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width);
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
}
MediaCodecHelper.setDecoderLowLatencyOptions(videoFormat, selectedDecoderInfo, mimeType);
configuredFormat = videoFormat;
LimeLog.info("Configuring with format: "+configuredFormat);
try {
videoDecoder.configure(videoFormat, renderTarget.getSurface(), null, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// This will contain the actual accepted input format attributes
inputFormat = videoDecoder.getInputFormat();
LimeLog.info("Input format: "+inputFormat);
if (tryConfigureDecoder(selectedDecoderInfo, mediaFormat)) {
// Success!
break;
}
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
videoDecoder.setOnFrameRenderedListener(new MediaCodec.OnFrameRenderedListener() {
@Override
public void onFrameRendered(MediaCodec mediaCodec, long presentationTimeUs, long renderTimeNanos) {
long delta = (renderTimeNanos / 1000000L) - (presentationTimeUs / 1000);
if (delta >= 0 && delta < 1000) {
if (USE_FRAME_RENDER_TIME) {
activeWindowVideoStats.totalTimeMs += delta;
}
}
}
}, null);
if (!newFormat) {
// We couldn't even configure a decoder without any low latency options
return -5;
}
LimeLog.info("Using codec "+selectedDecoderInfo.getName()+" for hardware decoding "+mimeType);
// Start the decoder
videoDecoder.start();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
legacyInputBuffers = videoDecoder.getInputBuffers();
}
} catch (Exception e) {
e.printStackTrace();
return -5;
}
return 0;
@@ -419,27 +514,59 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
return;
}
// Render up to one frame when in frame pacing mode.
//
// NB: Since the queue limit is 2, we won't starve the decoder of output buffers
// by holding onto them for too long. This also ensures we will have that 1 extra
// frame of buffer to smooth over network/rendering jitter.
Integer nextOutputBuffer = outputBufferQueue.poll();
if (nextOutputBuffer != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, frameTimeNanos);
}
else {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, true);
}
// Don't render unless a new frame is due. This prevents microstutter when streaming
// at a frame rate that doesn't match the display (such as 60 FPS on 120 Hz).
long actualFrameTimeDeltaNs = frameTimeNanos - lastRenderedFrameTimeNanos;
long expectedFrameTimeDeltaNs = 800000000 / refreshRate; // within 80% of the next frame
if (actualFrameTimeDeltaNs >= expectedFrameTimeDeltaNs) {
// Render up to one frame when in frame pacing mode.
//
// NB: Since the queue limit is 2, we won't starve the decoder of output buffers
// by holding onto them for too long. This also ensures we will have that 1 extra
// frame of buffer to smooth over network/rendering jitter.
Integer nextOutputBuffer = outputBufferQueue.poll();
if (nextOutputBuffer != null) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, frameTimeNanos);
}
else {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, true);
}
activeWindowVideoStats.totalFramesRendered++;
lastRenderedFrameTimeNanos = frameTimeNanos;
activeWindowVideoStats.totalFramesRendered++;
} catch (Exception e) {
// This will leak nextOutputBuffer, but there's really nothing else we can do
handleDecoderException(e, null, 0, false);
}
}
}
// Request another callback for next frame
Choreographer.getInstance().postFrameCallback(this);
}
private void startChoreographerThread() {
if (prefs.framePacing != PreferenceConfiguration.FRAME_PACING_BALANCED) {
// Not using Choreographer in this pacing mode
return;
}
// We use a separate thread to avoid any main thread delays from delaying rendering
choreographerHandlerThread = new HandlerThread("Video - Choreographer", Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_MORE_FAVORABLE);
choreographerHandlerThread.start();
// Start the frame callbacks
choreographerHandler = new Handler(choreographerHandlerThread.getLooper());
choreographerHandler.post(new Runnable() {
@Override
public void run() {
Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this);
}
});
}
private void startRendererThread()
{
rendererThread = new Thread() {
@@ -468,8 +595,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
presentationTimeUs = info.presentationTimeUs;
}
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS) {
// In max smoothness mode, we want to never drop frames
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS ||
prefs.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
// In max smoothness or cap FPS mode, we want to never drop frames
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Use a PTS that will cause this frame to never be dropped
videoDecoder.releaseOutputBuffer(lastIndex, 0);
@@ -581,18 +709,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
@Override
public void start() {
startRendererThread();
// Start Choreographer callbacks for rendering with frame pacing in balanced mode
// NB: This must be done on a thread with a looper!
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_BALANCED) {
Handler h = new Handler(Looper.getMainLooper());
h.post(new Runnable() {
@Override
public void run() {
Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this);
}
});
}
startChoreographerThread();
}
// !!! May be called even if setup()/start() fails !!!
@@ -605,12 +722,15 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
rendererThread.interrupt();
}
// Halt further Choreographer callbacks
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_BALANCED) {
Handler h = new Handler(Looper.getMainLooper());
h.post(new Runnable() {
// Post a quit message to the Choreographer looper (if we have one)
if (choreographerHandler != null) {
choreographerHandler.post(new Runnable() {
@Override
public void run() {
// Don't allow any further messages to be queued
choreographerHandlerThread.quit();
// Deregister the frame callback (if registered)
Choreographer.getInstance().removeFrameCallback(MediaCodecDecoderRenderer.this);
}
});
@@ -622,10 +742,31 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
// May be called already, but we'll call it now to be safe
prepareForStop();
// Wait for the Choreographer looper to shut down (if we have one)
if (choreographerHandlerThread != null) {
try {
choreographerHandlerThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
// Wait for the renderer thread to shut down
try {
rendererThread.join();
} catch (InterruptedException ignored) { }
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
@Override
@@ -693,7 +834,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;
@@ -749,29 +890,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
@@ -911,9 +1032,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
@@ -937,6 +1058,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
@@ -950,6 +1072,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
@@ -976,6 +1108,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++;
}
@@ -1046,8 +1192,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
@@ -1183,11 +1328,23 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
str += "SOC: "+Build.SOC_MANUFACTURER+" - "+Build.SOC_MODEL+"\n";
str += "Performance class: "+Build.VERSION.MEDIA_PERFORMANCE_CLASS+"\n";
str += "Vendor params: ";
List<String> params = renderer.videoDecoder.getSupportedVendorParameters();
if (params.isEmpty()) {
str += "NONE";
}
else {
for (String param : params) {
str += param + " ";
}
}
str += "\n";
}
str += "Foreground: "+renderer.foreground+"\n";
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";
@@ -41,6 +41,7 @@ public class MediaCodecHelper {
private static final List<String> qualcommDecoderPrefixes;
private static final List<String> kirinDecoderPrefixes;
private static final List<String> exynosDecoderPrefixes;
private static final List<String> amlogicDecoderPrefixes;
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
@@ -135,24 +136,26 @@ public class MediaCodecHelper {
// Exynos seems to be the only HEVC decoder that works reliably
whitelistedHevcDecoders.add("omx.exynos");
// On Darcy (Shield 2017), HEVC runs fine with no fixups required.
// For some reason, other X1 implementations require bitstream fixups.
if (Build.DEVICE.equalsIgnoreCase("darcy")) {
// On Darcy (Shield 2017), HEVC runs fine with no fixups required. For some reason,
// other X1 implementations require bitstream fixups. However, since numReferenceFrames
// has been supported in GFE since late 2017, we'll go ahead and enable HEVC for all
// device models.
//
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
// whether the performance is good enough to use for streaming, but they're
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
// fully accelerated HEVC pipeline. AFAIK, the only K1 devices with this
// partially accelerated HEVC decoder are the Shield Tablet and Xiaomi MiPad,
// so I'll check for those here.
//
// In case there are some that I missed, I will also exclude pre-Oreo OSes since
// only Shield ATV got an Oreo update and any newer Tegra devices will not ship
// with an old OS like Nougat.
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") &&
!Build.DEVICE.equalsIgnoreCase("mocha") &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
whitelistedHevcDecoders.add("omx.nvidia");
}
else {
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
}
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
whitelistedHevcDecoders.add("omx.mtk");
// This broke at some point on the Fire TV 3 and now the decoder
// never produces any output frames.
//whitelistedHevcDecoders.add("omx.amlogic");
}
// Plot twist: On newer Sony devices (BRAVIA_ATV2, BRAVIA_ATV3_4K, BRAVIA_UR1_4K) the H.264 decoder crashes
// on several configurations (> 60 FPS and 1440p) that work with HEVC, so we'll whitelist those devices for HEVC.
@@ -162,8 +165,14 @@ public class MediaCodecHelper {
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
// running Android 9 or later. HEVC is much lower latency than H.264 on Sabrina (S905X2).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// running Android 9 or later.
//
// NB: We don't do this on Sabrina (GCWGTV) because H.264 is lower latency when we use
// vendor.low-latency.enable. We will still use HEVC if decoderCanMeetPerformancePointWithHevcAndNotAvc()
// determines it's the only way to meet the performance requirements.
//
// FIXME: Should we do this for all Amlogic S905X SoCs?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !Build.DEVICE.equalsIgnoreCase("sabrina")) {
whitelistedHevcDecoders.add("omx.amlogic");
}
@@ -211,6 +220,12 @@ public class MediaCodecHelper {
exynosDecoderPrefixes.add("omx.exynos");
}
static {
amlogicDecoderPrefixes = new LinkedList<>();
amlogicDecoderPrefixes.add("omx.amlogic");
}
private static boolean isPowerVR(String glRenderer) {
return glRenderer.toLowerCase().contains("powervr");
}
@@ -269,6 +284,19 @@ public class MediaCodecHelper {
return;
}
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
// We still have to check Build.MANUFACTURER to catch Amazon Fire tablets.
if (context.getPackageManager().hasSystemFeature("amazon.hardware.fire_tv") ||
Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
whitelistedHevcDecoders.add("omx.mtk");
// This requires setting vdec-lowlatency on the Fire TV 3, otherwise the decoder
// never produces any output frames. See comment above for details on why we only
// do this for Fire TV devices.
whitelistedHevcDecoders.add("omx.amlogic");
}
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
@@ -388,41 +416,113 @@ public class MediaCodecHelper {
!isAdreno620;
}
public static void setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, String mimeType) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && decoderSupportsAndroidRLowLatency(decoderInfo, mimeType)) {
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
public static boolean setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, int tryNumber) {
// Options here should be tried in the order of most to least risky. The decoder will use
// the first MediaFormat that doesn't fail in configure().
boolean setNewOption = false;
if (tryNumber < 1) {
// Official Android 11+ low latency option (KEY_LOW_LATENCY).
videoFormat.setInteger("low-latency", 1);
setNewOption = true;
// If this decoder officially supports FEATURE_LowLatency, we will just use that alone
// for try 0. Otherwise, we'll include it as best effort with other options.
if (decoderSupportsAndroidRLowLatency(decoderInfo, videoFormat.getString(MediaFormat.KEY_MIME))) {
return true;
}
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
if (tryNumber < 2) {
// MediaTek decoders don't use vendor-defined keys for low latency mode. Instead, they have a modified
// version of AOSP's ACodec.cpp which supports the "vdec-lowlatency" option. This option is passed down
// to the decoder as OMX.MTK.index.param.video.LowLatencyDecode.
//
// MediaCodec vendor extension support was introduced in Android 8.0:
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Try vendor-specific low latency options
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
// This option is also plumbed for Amazon Amlogic-based devices like the Fire TV 3. Not only does it
// reduce latency on Amlogic, it fixes the HEVC bug that causes the decoder to not output any frames.
// On Fire TV 3, vdec-lowlatency is translated to OMX.amazon.fireos.index.video.lowLatencyDecode.
//
// https://github.com/yuan1617/Framwork/blob/master/frameworks/av/media/libstagefright/ACodec.cpp
// https://github.com/iykex/vendor_mediatek_proprietary_hardware/blob/master/libomx/video/MtkOmxVdecEx/MtkOmxVdecEx.h
videoFormat.setInteger("vdec-lowlatency", 1);
setNewOption = true;
}
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
//
// MediaCodec vendor extension support was introduced in Android 8.0:
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Try vendor-specific low latency options
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
//
// We will first try both, then try vendor.qti-ext-dec-low-latency.enable alone if that fails
if (tryNumber < 3) {
videoFormat.setInteger("vendor.qti-ext-dec-picture-order.enable", 1);
setNewOption = true;
}
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
if (tryNumber < 4) {
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
setNewOption = true;
}
}
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
if (tryNumber < 3) {
// Kirin low latency options
// https://developer.huawei.com/consumer/cn/forum/topic/0202325564295980115
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1);
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-rdy", -1);
setNewOption = true;
}
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
}
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
if (tryNumber < 3) {
// Exynos low latency option for H.264 decoder
videoFormat.setInteger("vendor.rtc-ext-dec-low-latency.enable", 1);
setNewOption = true;
}
}
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
else if (isDecoderInList(amlogicDecoderPrefixes, decoderInfo.getName())) {
if (tryNumber < 3) {
// Amlogic low latency vendor extension
// https://github.com/codewalkerster/android_vendor_amlogic_common_prebuilt_libstagefrighthw/commit/41fefc4e035c476d58491324a5fe7666bfc2989e
videoFormat.setInteger("vendor.low-latency.enable", 1);
setNewOption = true;
}
}
}
// FIXME: We should probably integrate this into the try system
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
}
return setNewOption;
}
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) {
@@ -486,20 +586,6 @@ public class MediaCodecHelper {
}
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, PreferenceConfiguration prefs) {
// TODO: Shield Tablet K1/LTE?
//
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
// whether the performance is good enough to use for streaming, but they're
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
// check for it here.
//
// TODO: Temporarily disabled with NVIDIA HEVC support
/*if (Build.DEVICE.equalsIgnoreCase("shieldtablet")) {
return false;
}*/
// Google didn't have official support for HEVC (or more importantly, a CTS test) until
// Lollipop. I've seen some MediaTek devices on 4.4 crash when attempting to use HEVC,
// so I'm restricting HEVC usage to Lollipop and higher.
@@ -5,8 +5,6 @@ import java.io.OutputStream;
import java.io.StringReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.LinkedList;
@@ -49,8 +47,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 = 1000;
private static final int OFFLINE_POLL_TRIES = 5;
private static final int OFFLINE_POLL_TRIES = 3;
private static final int INITIAL_POLL_TRIES = 2;
private static final int EMPTY_LIST_THRESHOLD = 3;
private static final int POLL_DATA_TTL_MS = 30000;
@@ -232,7 +229,13 @@ public class ComputerManagerService extends Service {
// Wait for the bind notification
discoveryServiceConnection.wait(1000);
}
} catch (InterruptedException ignored) {
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
@@ -241,11 +244,18 @@ public class ComputerManagerService extends Service {
while (activePolls.get() != 0) {
try {
Thread.sleep(250);
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
}
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
return ComputerManagerService.this.addComputerBlocking(fakeDetails);
}
@@ -399,9 +409,18 @@ public class ComputerManagerService extends Service {
details.ipv6Address = computer.getIpv6Address().getHostAddress();
}
// Kick off a serverinfo poll on this machine
if (!addComputerBlocking(details)) {
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
try {
// Kick off a blocking serverinfo poll on this machine
if (!addComputerBlocking(details)) {
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
}
@@ -449,28 +468,25 @@ public class ComputerManagerService extends Service {
}
}
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
// Block while we try to fill the details
try {
// We cannot use runPoll() here because it will attempt to persist the state of the machine
// in the database, which would be bad because we don't have our pinned cert loaded yet.
if (pollComputer(fakeDetails)) {
// See if we have record of this PC to pull its pinned cert
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
fakeDetails.serverCert = tuple.computer.serverCert;
break;
}
// We cannot use runPoll() here because it will attempt to persist the state of the machine
// in the database, which would be bad because we don't have our pinned cert loaded yet.
if (pollComputer(fakeDetails)) {
// See if we have record of this PC to pull its pinned cert
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
fakeDetails.serverCert = tuple.computer.serverCert;
break;
}
}
// Poll again, possibly with the pinned cert, to get accurate pairing information.
// This will insert the host into the database too.
runPoll(fakeDetails, true, 0);
}
} catch (InterruptedException e) {
return false;
// Poll again, possibly with the pinned cert, to get accurate pairing information.
// This will insert the host into the database too.
runPoll(fakeDetails, true, 0);
}
// If the machine is reachable, it was successful
@@ -528,11 +544,6 @@ public class ComputerManagerService extends Service {
}
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
// Fast poll this address first to determine if we can connect at the TCP layer
if (!fastPollIp(address)) {
return null;
}
try {
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
PlatformBinding.getCryptoProvider(ComputerManagerService.this));
@@ -551,146 +562,140 @@ public class ComputerManagerService extends Service {
return null;
}
// Set the new active address
newDetails.activeAddress = address;
return newDetails;
} catch (XmlPullParserException | IOException e) {
} catch (XmlPullParserException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
return null;
}
}
// Just try to establish a TCP connection to speculatively detect a running
// GFE server
private boolean fastPollIp(String address) {
if (address == null) {
// Don't bother if our address is null
return false;
private static class ParallelPollTuple {
public String address;
public ComputerDetails existingDetails;
public boolean complete;
public Thread pollingThread;
public ComputerDetails returnedDetails;
public ParallelPollTuple(String address, ComputerDetails existingDetails) {
this.address = address;
this.existingDetails = existingDetails;
}
Socket s = new Socket();
try {
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
s.close();
return true;
} catch (IOException e) {
return false;
public void interrupt() {
if (pollingThread != null) {
pollingThread.interrupt();
}
}
}
private void startFastPollThread(final String address, final boolean[] info) {
Thread t = new Thread() {
private void startParallelPollThread(ParallelPollTuple tuple, HashSet<String> uniqueAddresses) {
// Don't bother starting a polling thread for an address that doesn't exist
// or if the address has already been polled with an earlier tuple
if (tuple.address == null || !uniqueAddresses.add(tuple.address)) {
tuple.complete = true;
tuple.returnedDetails = null;
return;
}
tuple.pollingThread = new Thread() {
@Override
public void run() {
boolean pollRes = fastPollIp(address);
ComputerDetails details = tryPollIp(tuple.existingDetails, tuple.address);
synchronized (info) {
info[0] = true; // Done
info[1] = pollRes; // Polling result
synchronized (tuple) {
tuple.complete = true; // Done
tuple.returnedDetails = details; // Polling result
info.notify();
tuple.notify();
}
}
};
t.setName("Fast Poll - "+address);
t.start();
tuple.pollingThread.setName("Parallel Poll - "+tuple.address+" - "+tuple.existingDetails.name);
tuple.pollingThread.start();
}
private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress, final String ipv6Address) throws InterruptedException {
final boolean[] remoteInfo = new boolean[2];
final boolean[] localInfo = new boolean[2];
final boolean[] manualInfo = new boolean[2];
final boolean[] ipv6Info = new boolean[2];
private ComputerDetails parallelPollPc(ComputerDetails details) throws InterruptedException {
ParallelPollTuple localInfo = new ParallelPollTuple(details.localAddress, details);
ParallelPollTuple manualInfo = new ParallelPollTuple(details.manualAddress, details);
ParallelPollTuple remoteInfo = new ParallelPollTuple(details.remoteAddress, details);
ParallelPollTuple ipv6Info = new ParallelPollTuple(details.ipv6Address, details);
startFastPollThread(localAddress, localInfo);
startFastPollThread(remoteAddress, remoteInfo);
startFastPollThread(manualAddress, manualInfo);
startFastPollThread(ipv6Address, ipv6Info);
// These must be started in order of precedence for the deduplication algorithm
// to result in the correct behavior.
HashSet<String> uniqueAddresses = new HashSet<>();
startParallelPollThread(localInfo, uniqueAddresses);
startParallelPollThread(manualInfo, uniqueAddresses);
startParallelPollThread(remoteInfo, uniqueAddresses);
startParallelPollThread(ipv6Info, uniqueAddresses);
// Check local first
synchronized (localInfo) {
while (!localInfo[0]) {
localInfo.wait(500);
try {
// Check local first
synchronized (localInfo) {
while (!localInfo.complete) {
localInfo.wait();
}
if (localInfo.returnedDetails != null) {
localInfo.returnedDetails.activeAddress = localInfo.address;
return localInfo.returnedDetails;
}
}
if (localInfo[1]) {
return localAddress;
}
}
// Now manual
synchronized (manualInfo) {
while (!manualInfo.complete) {
manualInfo.wait();
}
// Now manual
synchronized (manualInfo) {
while (!manualInfo[0]) {
manualInfo.wait(500);
if (manualInfo.returnedDetails != null) {
manualInfo.returnedDetails.activeAddress = manualInfo.address;
return manualInfo.returnedDetails;
}
}
if (manualInfo[1]) {
return manualAddress;
}
}
// Now remote IPv4
synchronized (remoteInfo) {
while (!remoteInfo.complete) {
remoteInfo.wait();
}
// Now remote IPv4
synchronized (remoteInfo) {
while (!remoteInfo[0]) {
remoteInfo.wait(500);
if (remoteInfo.returnedDetails != null) {
remoteInfo.returnedDetails.activeAddress = remoteInfo.address;
return remoteInfo.returnedDetails;
}
}
if (remoteInfo[1]) {
return remoteAddress;
}
}
// Now global IPv6
synchronized (ipv6Info) {
while (!ipv6Info.complete) {
ipv6Info.wait();
}
// Now global IPv6
synchronized (ipv6Info) {
while (!ipv6Info[0]) {
ipv6Info.wait(500);
}
if (ipv6Info[1]) {
return ipv6Address;
if (ipv6Info.returnedDetails != null) {
ipv6Info.returnedDetails.activeAddress = ipv6Info.address;
return ipv6Info.returnedDetails;
}
}
} finally {
// Stop any further polling if we've found a working address or we've been
// interrupted by an attempt to stop polling.
localInfo.interrupt();
manualInfo.interrupt();
remoteInfo.interrupt();
ipv6Info.interrupt();
}
return null;
}
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
ComputerDetails polledDetails;
// Do a TCP-level connection to the HTTP server to see if it's listening.
// Do not write this address to details.activeAddress because:
// a) it's only a candidate and may be wrong (multiple PCs behind a single router)
// b) if it's null, it will be unexpectedly nulling the activeAddress of a possibly online PC
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
String candidateAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress, details.ipv6Address);
LimeLog.info("Fast poll for "+details.name+" returned candidate address: "+candidateAddress);
// If no connection could be established to either IP address, there's nothing we can do
if (candidateAddress == null) {
return false;
}
// Try using the active address from fast-poll
polledDetails = tryPollIp(details, candidateAddress);
if (polledDetails == null) {
// If that failed, try all unique addresses except what we've
// already tried
HashSet<String> uniqueAddresses = new HashSet<>();
uniqueAddresses.add(details.localAddress);
uniqueAddresses.add(details.manualAddress);
uniqueAddresses.add(details.remoteAddress);
uniqueAddresses.add(details.ipv6Address);
for (String addr : uniqueAddresses) {
if (addr == null || addr.equals(candidateAddress)) {
continue;
}
polledDetails = tryPollIp(details, addr);
if (polledDetails != null) {
break;
}
}
}
// Poll all addresses in parallel to speed up the process
LimeLog.info("Starting parallel poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
ComputerDetails polledDetails = parallelPollPc(details);
LimeLog.info("Parallel poll for "+details.name+" returned address: "+details.activeAddress);
if (polledDetails != null) {
details.update(polledDetails);
@@ -128,6 +128,13 @@ public class CachedAppAssetLoader {
try {
Thread.sleep((int) (1000 + (Math.random() * 500)));
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
return null;
}
}
@@ -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();
@@ -63,7 +63,7 @@ public class NvApp {
public String toString() {
StringBuilder str = new StringBuilder();
str.append("Name: ").append(appName).append("\n");
str.append("HDR: ").append(hdrSupported ? "Yes" : "No").append("\n");
str.append("HDR Supported: ").append(hdrSupported ? "Yes" : "Unknown").append("\n");
str.append("ID: ").append(appId).append("\n");
return str.toString();
}
@@ -9,10 +9,8 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -53,6 +51,7 @@ import com.limelight.nvstream.ConnectionContext;
import com.limelight.nvstream.http.PairingManager.PairState;
import okhttp3.ConnectionPool;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@@ -71,8 +70,8 @@ public class NvHTTP {
// Print URL and content to logcat on debug builds
private static boolean verbose = BuildConfig.DEBUG;
public String baseUrlHttps;
public String baseUrlHttp;
private HttpUrl baseUrlHttps;
private HttpUrl baseUrlHttp;
private OkHttpClient httpClient;
private OkHttpClient httpClientWithReadTimeout;
@@ -173,6 +172,7 @@ public class NvHTTP {
.hostnameVerifier(hv)
.readTimeout(0, TimeUnit.MILLISECONDS)
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.proxy(Proxy.NO_PROXY)
.build();
httpClientWithReadTimeout = httpClient.newBuilder()
@@ -190,22 +190,26 @@ public class NvHTTP {
initializeHttpState(cryptoProvider);
try {
// The URI constructor takes care of escaping IPv6 literals
this.baseUrlHttps = new URI("https", null, address, HTTPS_PORT, null, null, null).toString();
this.baseUrlHttp = new URI("http", null, address, HTTP_PORT, null, null, null).toString();
} catch (URISyntaxException e) {
// Encapsulate URISyntaxException into IOException for callers to handle more easily
this.baseUrlHttp = new HttpUrl.Builder()
.scheme("http")
.host(address)
.port(HTTP_PORT)
.build();
this.baseUrlHttps = new HttpUrl.Builder()
.scheme("https")
.host(address)
.port(HTTPS_PORT)
.build();
} catch (IllegalArgumentException e) {
// Encapsulate IllegalArgumentException into IOException for callers to handle more easily
throw new IOException(e);
}
this.pm = new PairingManager(this, cryptoProvider);
}
String buildUniqueIdUuidString() {
return "uniqueid="+uniqueId+"&uuid="+UUID.randomUUID();
}
static String getXmlString(Reader r, String tagname) throws XmlPullParserException, IOException {
static String getXmlString(Reader r, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
@@ -234,11 +238,19 @@ public class NvHTTP {
eventType = xpp.next();
}
if (throwIfMissing) {
// We throw an XmlPullParserException here for ease of handling in all the various callers.
// We could also throw an IOException, but some callers expect those in cases where the
// host may not be reachable. We want to distinguish unreachable hosts vs. hosts that
// are returning garbage XML to us, so we use XmlPullParserException instead.
throw new XmlPullParserException("Missing mandatory field in host response: "+tagname);
}
return null;
}
static String getXmlString(String str, String tagname) throws XmlPullParserException, IOException {
return getXmlString(new StringReader(str), tagname);
static String getXmlString(String str, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
return getXmlString(new StringReader(str), tagname, throwIfMissing);
}
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
@@ -272,7 +284,7 @@ public class NvHTTP {
if (serverCert != null) {
try {
try {
resp = openHttpConnectionToString(baseUrlHttps + "/serverinfo?"+buildUniqueIdUuidString(), true);
resp = openHttpConnectionToString(baseUrlHttps, "serverinfo", true);
} catch (SSLHandshakeException e) {
// Detect if we failed due to a server cert mismatch
if (e.getCause() instanceof CertificateException) {
@@ -292,7 +304,7 @@ public class NvHTTP {
catch (GfeHttpResponseException e) {
if (e.getErrorCode() == 401) {
// Cert validation error - fall back to HTTP
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
return openHttpConnectionToString(baseUrlHttp, "serverinfo", true);
}
// If it's not a cert validation error, throw it
@@ -303,7 +315,7 @@ public class NvHTTP {
}
else {
// No pinned cert, so use HTTP
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
return openHttpConnectionToString(baseUrlHttp , "serverinfo", true);
}
}
@@ -311,21 +323,21 @@ public class NvHTTP {
ComputerDetails details = new ComputerDetails();
String serverInfo = getServerInfo();
details.name = getXmlString(serverInfo, "hostname");
details.name = getXmlString(serverInfo, "hostname", false);
if (details.name == null || details.name.isEmpty()) {
details.name = "UNKNOWN";
}
details.uuid = getXmlString(serverInfo, "uniqueid");
details.macAddress = getXmlString(serverInfo, "mac");
details.localAddress = getXmlString(serverInfo, "LocalIP");
// UUID is mandatory to determine which machine is responding
details.uuid = getXmlString(serverInfo, "uniqueid", true);
// This may be null, but that's okay
details.remoteAddress = getXmlString(serverInfo, "ExternalIP");
details.macAddress = getXmlString(serverInfo, "mac", false);
details.localAddress = getXmlString(serverInfo, "LocalIP", false);
// This is missing on on recent GFE versions
details.remoteAddress = getXmlString(serverInfo, "ExternalIP", false);
// This has some extra logic to always report unpaired if the pinned cert isn't there
details.pairState = getPairState(serverInfo);
details.runningGameId = getCurrentGame(serverInfo);
// We could reach it so it's online
@@ -357,12 +369,26 @@ public class NvHTTP {
}
}
private HttpUrl getCompleteUrl(HttpUrl baseUrl, String path, String query) {
return baseUrl.newBuilder()
.addPathSegment(path)
.query(query)
.addQueryParameter("uniqueid", uniqueId)
.addQueryParameter("uuid", UUID.randomUUID().toString())
.build();
}
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
return openHttpConnection(baseUrl, path, null, enableReadTimeout);
}
// Read timeout should be enabled for any HTTP query that requires no outside action
// on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit.
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
// queries do not.
private ResponseBody openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
Request request = new Request.Builder().url(url).get().build();
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
HttpUrl completeUrl = getCompleteUrl(baseUrl, path, query);
Request request = new Request.Builder().url(completeUrl).get().build();
Response response;
if (enableReadTimeout) {
@@ -384,30 +410,31 @@ public class NvHTTP {
}
if (response.code() == 404) {
throw new FileNotFoundException(url);
throw new FileNotFoundException(completeUrl.toString());
}
else {
throw new GfeHttpResponseException(response.code(), response.message());
}
}
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws IOException {
try {
if (verbose) {
LimeLog.info("Requesting URL: "+url);
}
ResponseBody resp = openHttpConnection(url, enableReadTimeout);
private String openHttpConnectionToString(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
return openHttpConnectionToString(baseUrl, path, null, enableReadTimeout);
}
private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
try {
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout);
String respString = resp.string();
resp.close();
if (verbose) {
LimeLog.info(url+" -> "+respString);
if (verbose && !path.equals("serverinfo")) {
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
}
return respString;
} catch (IOException e) {
if (verbose) {
if (verbose && !path.equals("serverinfo")) {
LimeLog.warning(getCompleteUrl(baseUrl, path, query)+" -> "+e.getMessage());
e.printStackTrace();
}
@@ -416,7 +443,8 @@ public class NvHTTP {
}
public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "appversion");
// appversion is present in all supported GFE versions
return getXmlString(serverInfo, "appversion", true);
}
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
@@ -424,15 +452,14 @@ public class NvHTTP {
}
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
return PairState.NOT_PAIRED;
}
return PairState.PAIRED;
// appversion is present in all supported GFE versions
return NvHTTP.getXmlString(serverInfo, "PairStatus", true).equals("1") ?
PairState.PAIRED : PairState.NOT_PAIRED;
}
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsH264");
// MaxLumaPixelsH264 wasn't present on old GFE versions
String str = getXmlString(serverInfo, "MaxLumaPixelsH264", false);
if (str != null) {
return Long.parseLong(str);
} else {
@@ -441,7 +468,8 @@ public class NvHTTP {
}
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
// MaxLumaPixelsHEVC wasn't present on old GFE versions
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC", false);
if (str != null) {
return Long.parseLong(str);
} else {
@@ -458,7 +486,8 @@ public class NvHTTP {
// Bit 10: HEVC Main10 4:4:4
// Bit 11: ???
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "ServerCodecModeSupport");
// ServerCodecModeSupport wasn't present on old GFE versions
String str = getXmlString(serverInfo, "ServerCodecModeSupport", false);
if (str != null) {
return Long.parseLong(str);
} else {
@@ -467,16 +496,18 @@ public class NvHTTP {
}
public String getGpuType(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "gputype");
// ServerCodecModeSupport wasn't present on old GFE versions
return getXmlString(serverInfo, "gputype", false);
}
public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "GfeVersion");
// ServerCodecModeSupport wasn't present on old GFE versions
return getXmlString(serverInfo, "GfeVersion", false);
}
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
// Only allow 4K on GFE 3.x
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion");
// Only allow 4K on GFE 3.x. GfeVersion wasn't present on very old versions of GFE.
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion", false);
if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) {
return false;
}
@@ -488,10 +519,8 @@ public class NvHTTP {
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
// has the semantics that its name would indicate. To contain the effects of this change as much
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
String serverState = getXmlString(serverInfo, "state");
if (serverState != null && serverState.endsWith("_SERVER_BUSY")) {
String game = getXmlString(serverInfo, "currentgame");
return Integer.parseInt(game);
if (getXmlString(serverInfo, "state", true).endsWith("_SERVER_BUSY")) {
return Integer.parseInt(getXmlString(serverInfo, "currentgame", true));
}
else {
return 0;
@@ -588,8 +617,8 @@ public class NvHTTP {
return appList;
}
public String getAppListRaw() throws MalformedURLException, IOException {
return openHttpConnectionToString(baseUrlHttps + "/applist?"+buildUniqueIdUuidString(), true);
public String getAppListRaw() throws IOException {
return openHttpConnectionToString(baseUrlHttps, "applist", true);
}
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
@@ -598,20 +627,31 @@ public class NvHTTP {
return getAppListByReader(new StringReader(getAppListRaw()));
}
else {
ResponseBody resp = openHttpConnection(baseUrlHttps + "/applist?" + buildUniqueIdUuidString(), true);
ResponseBody resp = openHttpConnection(baseUrlHttps, "applist", true);
LinkedList<NvApp> appList = getAppListByReader(new InputStreamReader(resp.byteStream()));
resp.close();
return appList;
}
}
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException {
return openHttpConnectionToString(baseUrlHttp, "pair",
"devicename=roth&updateState=1&" + additionalArguments,
enableReadTimeout);
}
String executePairingChallenge() throws GfeHttpResponseException, IOException {
return openHttpConnectionToString(baseUrlHttps, "pair",
"devicename=roth&updateState=1&phrase=pairchallenge",
true);
}
public void unpair() throws IOException {
openHttpConnectionToString(baseUrlHttp + "/unpair?"+buildUniqueIdUuidString(), true);
openHttpConnectionToString(baseUrlHttp, "unpair", true);
}
public InputStream getBoxArt(NvApp app) throws IOException {
ResponseBody resp = openHttpConnection(baseUrlHttps + "/appasset?"+ buildUniqueIdUuidString() +
"&appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
ResponseBody resp = openHttpConnection(baseUrlHttps, "appasset", "appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
return resp.byteStream();
}
@@ -666,9 +706,8 @@ public class NvHTTP {
enableSops = false;
}
String xmlStr = openHttpConnectionToString(baseUrlHttps +
"/launch?" + buildUniqueIdUuidString() +
"&appid=" + appId +
String xmlStr = openHttpConnectionToString(baseUrlHttps, "launch",
"appid=" + appId +
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
@@ -679,9 +718,9 @@ public class NvHTTP {
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""),
false);
String gameSession = getXmlString(xmlStr, "gamesession");
if (gameSession != null && !gameSession.equals("0")) {
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0");
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
// sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
return true;
}
else {
@@ -690,14 +729,14 @@ public class NvHTTP {
}
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/resume?" + buildUniqueIdUuidString() +
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
String xmlStr = openHttpConnectionToString(baseUrlHttps, "resume",
"rikey="+bytesToHex(context.riKey.getEncoded()) +
"&rikeyid="+context.riKeyId +
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(),
false);
String resume = getXmlString(xmlStr, "resume");
if (Integer.parseInt(resume) != 0) {
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0");
if (!getXmlString(xmlStr, "resume", true).equals("0")) {
// sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
return true;
}
else {
@@ -706,9 +745,8 @@ public class NvHTTP {
}
public boolean quitApp() throws IOException, XmlPullParserException {
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false);
String cancel = getXmlString(xmlStr, "cancel");
if (Integer.parseInt(cancel) == 0) {
String xmlStr = openHttpConnectionToString(baseUrlHttps, "cancel", false);
if (getXmlString(xmlStr, "cancel", true).equals("0")) {
return false;
}
@@ -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;
@@ -68,7 +67,8 @@ public class PairingManager {
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
{
String certText = NvHTTP.getXmlString(text, "plaincert");
// Plaincert may be null if another client is already trying to pair
String certText = NvHTTP.getXmlString(text, "plaincert", false);
if (certText != null) {
byte[] certBytes = hexToBytes(certText);
@@ -124,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) {
@@ -199,16 +191,14 @@ 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
String getCert = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
String getCert = http.executePairingCommand("phrase=getservercert&salt="+
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
false);
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
if (!NvHTTP.getXmlString(getCert, "paired", true).equals("1")) {
return PairState.FAILED;
}
@@ -217,7 +207,7 @@ public class PairingManager {
if (serverCert == null) {
// Attempting to pair while another device is pairing will cause GFE
// to give an empty cert in the response.
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
http.unpair();
return PairState.ALREADY_IN_PROGRESS;
}
@@ -229,16 +219,14 @@ public class PairingManager {
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
// Send the encrypted challenge to the server
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
true);
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
String challengeResp = http.executePairingCommand("clientchallenge="+bytesToHex(encryptedChallenge), true);
if (!NvHTTP.getXmlString(challengeResp, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
// Decode the server's response and subsequent challenge
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse", true));
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
@@ -248,23 +236,21 @@ public class PairingManager {
byte[] clientSecret = generateRandomBytes(16);
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
true);
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
String secretResp = http.executePairingCommand("serverchallengeresp="+bytesToHex(challengeRespEncrypted), true);
if (!NvHTTP.getXmlString(secretResp, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
// Get the server's signed secret
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret"));
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret", true));
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
// Ensure the authenticity of the data
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
// Cancel the pairing process
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
http.unpair();
// Looks like a MITM
return PairState.FAILED;
@@ -274,7 +260,7 @@ public class PairingManager {
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
// Cancel the pairing process
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
http.unpair();
// Probably got the wrong PIN
return PairState.PIN_WRONG;
@@ -282,19 +268,16 @@ public class PairingManager {
// Send the server our signed secret
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
true);
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
String clientSecretResp = http.executePairingCommand("clientpairingsecret="+bytesToHex(clientPairingSecret), true);
if (!NvHTTP.getXmlString(clientSecretResp, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
// Do the initial challenge (seems neccessary for us to show as paired)
String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
// Do the initial challenge (seems necessary for us to show as paired)
String pairChallenge = http.executePairingChallenge();
if (!NvHTTP.getXmlString(pairChallenge, "paired", true).equals("1")) {
http.unpair();
return PairState.FAILED;
}
@@ -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,
@@ -96,7 +96,7 @@ public class AddComputerManually extends Activity {
}
}
private void doAddPc(String host) {
private void doAddPc(String host) throws InterruptedException {
boolean wrongSiteLocal = false;
boolean success;
int portTestResult;
@@ -108,12 +108,18 @@ public class AddComputerManually extends Activity {
ComputerDetails details = new ComputerDetails();
details.manualAddress = host;
success = managerBinder.addComputerBlocking(details);
} catch (InterruptedException e) {
// Propagate the InterruptedException to the caller for proper handling
dialog.dismiss();
throw e;
} catch (IllegalArgumentException e) {
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
e.printStackTrace();
success = false;
}
// Keep the SpinnerDialog open while testing connectivity
if (!success){
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
}
@@ -162,15 +168,12 @@ public class AddComputerManually extends Activity {
@Override
public void run() {
while (!isInterrupted()) {
String computer;
try {
computer = computersToAdd.take();
String computer = computersToAdd.take();
doAddPc(computer);
} catch (InterruptedException e) {
return;
}
doAddPc(computer);
}
}
};
@@ -184,7 +187,14 @@ public class AddComputerManually extends Activity {
try {
addThread.join();
} catch (InterruptedException ignored) {}
} catch (InterruptedException e) {
e.printStackTrace();
// InterruptedException clears the thread's interrupt status. Since we can't
// handle that here, we will re-interrupt the thread to set the interrupt
// status back to true.
Thread.currentThread().interrupt();
}
addThread = null;
}
@@ -0,0 +1,51 @@
package com.limelight.preferences;
import android.annotation.TargetApi;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.preference.ListPreference;
import android.provider.Settings;
import android.util.AttributeSet;
public class LanguagePreference extends ListPreference {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public LanguagePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LanguagePreference(Context context) {
super(context);
}
@Override
protected void onClick() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
try {
// Launch the Android native app locale settings page
Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + getContext().getPackageName()));
getContext().startActivity(intent, null);
return;
} catch (ActivityNotFoundException e) {
// App locale settings should be present on all Android 13 devices,
// but if not, we'll launch the old language chooser.
}
}
// If we don't have native app locale settings, launch the normal dialog
super.onClick();
}
}
@@ -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;
@@ -80,7 +82,8 @@ public class PreferenceConfiguration {
public static final int FRAME_PACING_MIN_LATENCY = 0;
public static final int FRAME_PACING_BALANCED = 1;
public static final int FRAME_PACING_MAX_SMOOTHNESS = 2;
public static final int FRAME_PACING_CAP_FPS = 2;
public static final int FRAME_PACING_MAX_SMOOTHNESS = 3;
public static final String RES_360P = "640x360";
public static final String RES_480P = "854x480";
@@ -113,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
@@ -288,6 +292,9 @@ public class PreferenceConfiguration {
else if (str.equals("balanced")) {
return FRAME_PACING_BALANCED;
}
else if (str.equals("cap-fps")) {
return FRAME_PACING_CAP_FPS;
}
else if (str.equals("smoothness")) {
return FRAME_PACING_MAX_SMOOTHNESS;
}
@@ -312,6 +319,19 @@ public class PreferenceConfiguration {
.apply();
}
public static void completeLanguagePreferenceMigration(Context context) {
// Put our language option back to default which tells us that we've already migrated it
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE).apply();
}
public static boolean isShieldAtvFirmwareWithBrokenHdr() {
// This particular Shield TV firmware crashes when using HDR
// https://www.nvidia.com/en-us/geforce/forums/notifications/comment/155192/
return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA") &&
Build.FINGERPRINT.contains("PPR1.180610.011/4079208_2235.1395");
}
public static PreferenceConfiguration readPreferences(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
PreferenceConfiguration config = new PreferenceConfiguration();
@@ -438,7 +458,7 @@ public class PreferenceConfiguration {
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR);
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR) && !isShieldAtvFirmwareWithBrokenHdr();
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
@@ -450,6 +470,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;
}
@@ -10,6 +10,7 @@ import android.os.Bundle;
import android.app.Activity;
import android.os.Handler;
import android.os.Vibrator;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
@@ -79,16 +80,20 @@ public class StreamSettings extends Activity {
}
@Override
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
public void onBackPressed() {
finish();
// Check for changes that require a UI reload to take effect
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
if (!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);
// Language changes are handled via configuration changes in Android 13+,
// so manual activity relaunching is no longer required.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
if (!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);
}
}
}
@@ -222,6 +227,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 ||
@@ -232,6 +246,13 @@ public class StreamSettings extends Activity {
category.removePreference(findPreference("checkbox_enable_pip"));
}
// Fire TV apps are not allowed to use WebViews or browsers, so hide the Help category
/*if (getActivity().getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
PreferenceCategory category =
(PreferenceCategory) findPreference("category_help");
screen.removePreference(category);
}*/
// Remove the vibration options if the device can't vibrate
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
PreferenceCategory category =
@@ -529,6 +550,15 @@ public class StreamSettings extends Activity {
(PreferenceCategory) findPreference("category_advanced_settings");
category.removePreference(findPreference("checkbox_enable_hdr"));
}
else if (PreferenceConfiguration.isShieldAtvFirmwareWithBrokenHdr()) {
LimeLog.info("Disabling HDR toggle on old broken SHIELD TV firmware");
PreferenceCategory category =
(PreferenceCategory) findPreference("category_advanced_settings");
CheckBoxPreference hdrPref = (CheckBoxPreference) category.findPreference("checkbox_enable_hdr");
hdrPref.setEnabled(false);
hdrPref.setChecked(false);
hdrPref.setSummary("Update the firmware on your NVIDIA SHIELD Android TV to enable HDR");
}
}
// Add a listener to the FPS and resolution preference
@@ -0,0 +1,45 @@
package com.limelight.preferences;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.preference.Preference;
import android.util.AttributeSet;
import com.limelight.utils.HelpLauncher;
public class WebLauncherPreference extends Preference {
private String url;
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(attrs);
}
public WebLauncherPreference(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(attrs);
}
private void initialize(AttributeSet attrs) {
if (attrs == null) {
throw new IllegalStateException("WebLauncherPreference must have attributes!");
}
url = attrs.getAttributeValue(null, "url");
if (url == null) {
throw new IllegalStateException("WebLauncherPreference must have 'url' attribute!");
}
}
@Override
public void onClick() {
HelpLauncher.launchUrl(getContext(), url);
}
}
@@ -1,5 +1,5 @@
package com.limelight.ui;
public interface GameGestures {
void showKeyboard();
void toggleKeyboard();
}
@@ -8,7 +8,7 @@ import android.net.Uri;
import com.limelight.HelpActivity;
public class HelpLauncher {
private static void launchUrl(Context context, String url) {
public static void launchUrl(Context context, String url) {
// Try to launch the default browser
try {
Intent i = new Intent(Intent.ACTION_VIEW);
@@ -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;
}
@@ -2,6 +2,9 @@ package com.limelight.utils;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.GameManager;
import android.app.GameState;
import android.app.LocaleManager;
import android.app.UiModeManager;
import android.content.Context;
import android.content.DialogInterface;
@@ -9,10 +12,12 @@ import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.os.Build;
import android.os.LocaleList;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import com.limelight.Game;
import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.preferences.PreferenceConfiguration;
@@ -24,25 +29,66 @@ public class UiHelper {
private static final int TV_VERTICAL_PADDING_DP = 15;
private static final int TV_HORIZONTAL_PADDING_DP = 15;
private static void setGameModeStatus(Context context, boolean streaming, boolean loading, boolean interruptible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
GameManager gameManager = context.getSystemService(GameManager.class);
if (streaming) {
gameManager.setGameState(new GameState(loading, interruptible ? GameState.MODE_GAMEPLAY_INTERRUPTIBLE : GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE));
}
else {
gameManager.setGameState(new GameState(loading, GameState.MODE_NONE));
}
}
}
public static void notifyStreamConnecting(Context context) {
setGameModeStatus(context, true, true, true);
}
public static void notifyStreamConnected(Context context) {
setGameModeStatus(context, true, false, false);
}
public static void notifyStreamEnteringPiP(Context context) {
setGameModeStatus(context, true, false, true);
}
public static void notifyStreamExitingPiP(Context context) {
setGameModeStatus(context, true, false, false);
}
public static void notifyStreamEnded(Context context) {
setGameModeStatus(context, false, false, false);
}
public static void setLocale(Activity activity)
{
String locale = PreferenceConfiguration.readPreferences(activity).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(activity.getResources().getConfiguration());
// Some locales include both language and country which must be separated
// before calling the Locale constructor.
if (locale.contains("-"))
{
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
locale.substring(locale.indexOf('-') + 1));
}
else
{
config.locale = new Locale(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// On Android 13, migrate this non-default language setting into the OS native API
LocaleManager localeManager = activity.getSystemService(LocaleManager.class);
localeManager.setApplicationLocales(LocaleList.forLanguageTags(locale));
PreferenceConfiguration.completeLanguagePreferenceMigration(activity);
}
else {
Configuration config = new Configuration(activity.getResources().getConfiguration());
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
// Some locales include both language and country which must be separated
// before calling the Locale constructor.
if (locale.contains("-"))
{
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
locale.substring(locale.indexOf('-') + 1));
}
else
{
config.locale = new Locale(locale);
}
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
}
}
}
@@ -68,6 +114,9 @@ public class UiHelper {
View rootView = activity.findViewById(android.R.id.content);
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
// Set GameState.MODE_NONE initially for all activities
setGameModeStatus(activity, false, false, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Allow this non-streaming activity to layout under notches.
//
+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);
@@ -13,6 +13,7 @@
android:text="@string/title_add_pc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:preferKeepClear="true"
android:layout_centerHorizontal="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_alignParentTop="true"/>
@@ -29,6 +30,7 @@
android:layout_alignParentStart="true"
android:ems="10"
android:singleLine="true"
android:preferKeepClear="true"
android:inputType="textNoSuggestions"
android:hint="@string/ip_hint" >
@@ -43,6 +45,7 @@
android:layout_below="@+id/manuallyAddPcText"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:preferKeepClear="true"
android:text="@android:string/ok"/>
</RelativeLayout>
@@ -25,6 +25,7 @@
android:gravity="center"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:preferKeepClear="true"
android:textSize="28sp"/>
</RelativeLayout>
@@ -21,6 +21,7 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="left"
android:background="#80000000"
android:preferKeepClear="true"
android:visibility="gone" />
<TextView
@@ -34,6 +35,7 @@
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="right"
android:background="#80000000"
android:preferKeepClear="true"
android:visibility="gone" />
</merge>
@@ -57,6 +57,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:src="@drawable/ic_settings"
android:preferKeepClear="true"
style="?android:attr/borderlessButtonStyle"/>
<ImageButton
@@ -69,6 +70,7 @@
android:layout_toRightOf="@+id/settingsButton"
android:layout_toEndOf="@+id/settingsButton"
android:src="@drawable/ic_help"
android:preferKeepClear="true"
style="?android:attr/borderlessButtonStyle"/>
<ImageButton
@@ -81,6 +83,7 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:src="@drawable/ic_add"
android:preferKeepClear="true"
style="?android:attr/borderlessButtonStyle"/>
</RelativeLayout>
@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_pc_scut_background"/>
<foreground android:drawable="@mipmap/ic_pc_scut_foreground"/>
<monochrome android:drawable="@mipmap/ic_pc_scut_foreground"/>
</adaptive-icon>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
+24 -5
View File
@@ -97,7 +97,6 @@
<string name="perf_overlay_renderingfps">Wiedergabe-Bildwiederholungsrate: %1$.2f FPS</string>
<string name="perf_overlay_netdrops">Wegen Netzwerkübertragung ausgelassene Frames: %1$.2f%%</string>
<string name="perf_overlay_dectime">Durchschnittliche Dekodierungszeit: %1$.2f ms</string>
<!-- AppList activity -->
<string name="applist_connect_msg">Verbinde mit Host…</string>
<string name="applist_menu_resume">Sitzung wiederherstellen</string>
@@ -133,7 +132,7 @@
<string name="summary_fps_list">Erhöhen für einen gleichmäßigeren Video-Stream. Verringern um auf langsameren Geräten eine bessere Performance zu erzielen.</string>
<string name="title_seekbar_bitrate">Video Bitrate</string>
<string name="summary_seekbar_bitrate">Erhöhen für einen schärferen Video-Stream. Verringern um auf langsameren Geräten eine bessere Performance zu erzielen.</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="suffix_seekbar_bitrate_mbps">Mbit/s</string>
<string name="title_checkbox_stretch_video">Video auf Vollbildschirm strecken</string>
<string name="category_audio_settings">Audio Einstellungen</string>
<string name="title_audio_config_list">Surround Sound Konfiguration </string>
@@ -192,8 +191,8 @@
<string name="title_checkbox_disable_warnings">Warnhinweise deaktivieren</string>
<string name="summary_checkbox_disable_warnings">On-Screen Warnmeldungen während des Streaming deaktivieren</string>
<string name="summary_disable_frame_drop">Kann das Mikro-Ruckeln auf einigen Geräten reduzieren, allerdings erhöht dies gleichzeitig die Latenz</string>
<string name="title_video_format">H265 Einstellungen ändern</string>
<string name="summary_video_format">H265 verringert die Video-Bandbreitenanforderung, funktioniert allerdings nur auf sehr neuen Geräten</string>
<string name="title_video_format">HEVC Einstellungen ändern</string>
<string name="summary_video_format">HEVC verringert die Video-Bandbreitenanforderung, funktioniert allerdings nur auf sehr neuen Geräten</string>
<string name="title_enable_hdr">HDR aktivieren (experimentell)</string>
<string name="summary_enable_hdr">HDR-Streaming sofern dies von der Host-GPU unterstützt wird. HDR erfordert eine GPU der GTX 1000 Serie oder neuer.</string>
<string name="title_enable_perf_overlay">Performance Overlay aktivieren</string>
@@ -218,9 +217,29 @@
<string name="pcview_menu_header_unknown">Aktualisiere</string>
<string name="pcview_menu_header_offline">Offline</string>
<string name="pcview_menu_header_online">Online</string>
<!-- Array strings -->
<string name="videoformat_hevcauto">Verwende HEVC so fern stabile Unterstützung vorhanden ist</string>
<string name="videoformat_hevcalways">Immer HEVC verwenden (könnte Crashes verursachen)</string>
<string name="videoformat_hevcnever">Nie HEVC verwenden</string>
<string name="title_frame_pacing">Video Frame-Pacing</string>
<string name="summary_frame_pacing">Lege fest, wie die Videolatenz und die flüssige Wiedergabe ausgeglichen werden sollen</string>
<string name="resolution_prefix_native_fullscreen">Natives Vollbild</string>
<string name="pacing_latency">Bevorzuge niedrigste Latenz</string>
<string name="pacing_balanced">Ausgeglichen</string>
<string name="pacing_smoothness">Bevorzuge flüssige Bildwiedergabe (kann die Latenzzeit deutlich erhöhen)</string>
<string name="perf_overlay_streamdetails">Video Stream: %1$s %2$.2f FPS</string>
<string name="resolution_360p">360p</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="audioconf_stereo">Stereo</string>
<string name="audioconf_51surround">5.1 Surround Sound</string>
<string name="audioconf_71surround">7.1 Surround Sound</string>
<string name="perf_overlay_netlatency">Durchschnittliche Netzwerklatenz: %1$d ms (Abweichung: %2$d ms)</string>
<string name="fps_30">30 FPS</string>
<string name="resolution_480p">480p</string>
<string name="fps_120">120 FPS</string>
<string name="resolution_720p">720p</string>
<string name="fps_60">60 FPS</string>
<string name="fps_90">90 FPS</string>
</resources>
+23 -5
View File
@@ -220,19 +220,37 @@
<string name="resolution_prefix_native_fullscreen">Plein-écran natif</string>
<string name="perf_overlay_netlatency">Latence réseau moyenne : %1$d ms (variance : %2$d ms)</string>
<string name="perf_overlay_streamdetails">Stream vidéo : %1$s %2$.2f FPS</string>
<!-- Array strings -->
<string name="fps_30">30 IPS</string>
<string name="fps_60">60 IPS</string>
<string name="fps_90">90 IPS</string>
<string name="fps_120">120 IPS</string>
<string name="audioconf_stereo">Stéréo</string>
<string name="audioconf_51surround">Son surround 5.1</string>
<string name="audioconf_71surround">Son surround 7.1</string>
<string name="videoformat_hevcauto">Utiliser HEVC uniquement s\'il est stable</string>
<string name="videoformat_hevcauto">Automatique</string>
<string name="videoformat_hevcalways">Utilisez toujours HEVC (mais il peut planter)</string>
<string name="videoformat_hevcnever">N\'utilisez jamais HEVC</string>
<string name="title_frame_pacing">Frame-pacing vidéo</string>
<string name="summary_frame_pacing">Spécifiez comment équilibrer latence et fluidité de la vidéo</string>
<string name="pacing_latency">Préférer une latence plus faible</string>
<string name="pacing_balanced">Équilibré</string>
<string name="resolution_720p">720p</string>
<string name="pacing_smoothness">Préférer la qualité vidéo (risque d\'augmenter la latence)</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<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>
<string name="title_checkbox_absolute_mouse_mode">Mode souris pour bureau à distance</string>
<string name="summary_seekbar_deadzone">Remarque : Certains jeux peuvent imposer une zone morte plus grande que celle que Moonlight est configuré pour utiliser.</string>
<string name="summary_checkbox_absolute_mouse_mode">Cela peut rendre l\'accélération de la souris plus naturelle pour l\'utilisation du bureau à distance, mais elle est incompatible avec de nombreux jeux.</string>
</resources>
+12 -1
View File
@@ -208,7 +208,7 @@
<string name="audioconf_stereo">Stereo</string>
<string name="audioconf_51surround">Surround 5.1</string>
<string name="audioconf_71surround">Surround 7.1</string>
<string name="videoformat_hevcauto">Usa HEVC solo se stabile</string>
<string name="videoformat_hevcauto">Automatico</string>
<string name="videoformat_hevcalways">Usa sempre HEVC (potrebbe essere instabile)</string>
<string name="videoformat_hevcnever">Non usare mai HEVC</string>
<string name="title_frame_pacing">Bilanciamento frame video</string>
@@ -243,4 +243,15 @@
<string name="perf_overlay_renderingfps">Frame rate renderizzato: %1$.2f FPS</string>
<string name="perf_overlay_netdrops">Frame scartati dalla tua connessione di rete: %1$.2f%%</string>
<string name="perf_overlay_netlatency">Latenza media della rete: %1$d ms (varianza: %2$d ms)</string>
<string name="category_help">Aiuto</string>
<string name="title_setup_guide">Guida configurazione</string>
<string name="pacing_balanced_alt">Bilanciato con il limite FPS</string>
<string name="summary_seekbar_deadzone">Nota: Alcuni giochi possono imporre una deadzone più grande di quella che Moonlight è configurato per utilizzare.</string>
<string name="title_checkbox_absolute_mouse_mode">Modalità mouse desktop remoto</string>
<string name="summary_checkbox_absolute_mouse_mode">Questo può rendere l\'accelerazione del mouse più naturale per l\'utilizzo del desktop remoto, ma è incompatibile con molti giochi.</string>
<string name="summary_setup_guide">Visualizza le istruzioni su come configurare il tuo PC da gaming per lo streaming</string>
<string name="title_troubleshooting">Guida alla risoluzione dei problemi</string>
<string name="title_privacy_policy">Informativa sulla privacy</string>
<string name="summary_troubleshooting">Visualizza i suggerimenti per la diagnosi e la risoluzione dei problemi di streaming più comuni</string>
<string name="summary_privacy_policy">Visualizza l\'informativa sulla privacy di Moonlight</string>
</resources>
+230 -1
View File
@@ -1,2 +1,231 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="title_frame_pacing">Ritmo de quadros</string>
<string name="pacing_balanced">Balanceado</string>
<string name="pacing_latency">Preferir baixa latência</string>
<string name="summary_enable_hdr">Transmita HDR quando o jogo e a GPU do PC suportarem. O HDR requer uma GPU da série GTX 1000 ou posterior.</string>
<string name="summary_enable_post_stream_toast">Exibe uma mensagem com informações de latência após o término da transmissão</string>
<string name="summary_unlock_fps">Transmitir a 90 ou 120 FPS pode reduzir a latência em dispositivos de última geração, mas pode causar atraso ou instabilidade em dispositivos que não suportam</string>
<string name="summary_checkbox_vibrate_osc">Vibra seu dispositivo para emular rumble nos controles de tela</string>
<string name="pcview_menu_header_offline">Indisponível</string>
<string name="pcview_menu_header_unknown">Atualizando</string>
<string name="pacing_smoothness">Preferir vídeo mais suave (pode aumentar significativamente a latência)</string>
<string name="summary_frame_pacing">Especifique o equilíbrio entre latência e suavidade do vídeo</string>
<string name="title_checkbox_disable_warnings">Desabilitar mensagens de alerta</string>
<string name="title_checkbox_enable_pip">Habilitar modo Picture-in-Picture</string>
<string name="summary_checkbox_small_icon_mode">Diminui o tamanho dos ícones permitindo que mais aplicativos sejam visíveis na tela</string>
<string name="dialog_text_reset_osc">Tem certeza de que deseja excluir o layout salvo dos controles de tela\?</string>
<string name="summary_checkbox_touchscreen_trackpad">Se habilitado, a tela sensível ao toque funciona como um trackpad. Se desativado, a tela controla diretamente o cursor do mouse.</string>
<string name="summary_checkbox_xb1_driver">Habilita um driver USB integrado para dispositivos sem suporte nativo aos controles Xbox</string>
<string name="title_checkbox_mouse_nav_buttons">Habilitar os botões de voltar e avançar do mouse</string>
<string name="addpc_fail">Não foi possível conectar ao computador especificado. Certifique-se de que as portas necessárias sejam permitidas pelo firewall.</string>
<string name="conn_error_msg">Falha ao iniciar</string>
<string name="ip_hint">Endereço de IP do PC GeForce</string>
<string name="searching_pc">Procurando por PCs rodando o GameStream...
\n
\nCertifique-se de que o GameStream esteja ativado nas configurações do SHIELD do GeForce Experience.</string>
<string name="nettest_text_success">Sua rede não parece estar bloqueando o Moonlight. Se você ainda tiver problemas para se conectar, verifique as configurações de firewall do seu PC.
\n
\nSe você estiver tentando transmitir pela Internet, instale o Moonlight Internet Hosting Tool em seu PC e execute o Internet Streaming Tester para verificar a conexão com a Internet do seu PC.</string>
<string name="check_ports_msg">Verifique o firewall e permita a(s) seguinte(s) para porta(s):</string>
<string name="wol_no_mac">Não foi possível acordar o PC porque o GFE não enviou um endereço MAC</string>
<string name="wol_waking_msg">Pode levar alguns segundos para o seu PC acordar. Se isso não acontecer, verifique se ele está configurado corretamente para Wake-On-LAN.</string>
<string name="error_404">GFE retornou um erro HTTP 404. Verifique se o seu PC está executando uma GPU compatível. O uso do software de área de trabalho remota também pode causar esse erro. Tente reiniciar sua máquina ou reinstalar o GFE.</string>
<string name="message_decoding_error">Moonlight crashou devido a uma incompatibilidade com o decodificador de vídeo deste dispositivo. Certifique-se de que o GeForce Experience esteja atualizado para a versão mais recente em seu PC. Tente ajustar as configurações de transmissão se as falhas continuarem.</string>
<string name="message_decoding_reset">O decodificador de vídeo do seu dispositivo continua crashando nas configurações de transmissão selecionadas. Suas configurações de transmissão foram redefinidas para o padrão.</string>
<string name="error_usb_prohibited">O acesso USB é proibido pelo administrador do seu dispositivo. Verifique suas configurações de Knox ou MDM.</string>
<string name="unable_to_pin_shortcut">Seu launcher atual não permite a criação de atalhos fixados.</string>
<string name="video_decoder_init_failed">Falha ao iniciar o decodificador de vídeo. Seu dispositivo pode não suportar a resolução ou taxa de quadros selecionada.</string>
<string name="no_video_received_error">Nenhum vídeo recebido do host.</string>
<string name="no_frame_received_error">Sua conexão de rede não está funcionando bem. Reduza a configuração da taxa de bits do vídeo ou tente uma conexão mais rápida.</string>
<string name="early_termination_error">Algo deu errado no seu PC ao iniciar a transmissão.
\n
\nCertifique-se de não ter nenhum conteúdo protegido por DRM aberto em seu PC. Você também pode tentar reiniciar ele.
\n
\nSe o problema persistir, tente reinstalar os drivers da GPU e o GeForce Experience.</string>
<string name="conn_establishing_msg">Iniciando a conexão</string>
<string name="conn_establishing_title">Estabelecendo Conexão</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<string name="resolution_720p">720p</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="fps_30">30 FPS</string>
<string name="fps_60">60 FPS</string>
<string name="videoformat_hevcalways">Sempre usar HEVC (talvez crashe)</string>
<string name="videoformat_hevcauto">Usar HEVC apenas se for estável</string>
<string name="fps_120">120 FPS</string>
<string name="audioconf_stereo">Stereo</string>
<string name="audioconf_51surround">5.1 Surround</string>
<string name="audioconf_71surround">7.1 Surround</string>
<string name="fps_90">90 FPS</string>
<string name="videoformat_hevcnever">Nunca usar HEVC</string>
<string name="scut_deleted_pc">PC deletado</string>
<string name="scut_not_paired">PC não pareado</string>
<string name="help_loading_title">Ver Ajuda</string>
<string name="help_loading_msg">Carregando página de ajuda…</string>
<string name="scut_pc_not_found">PC não encontrado</string>
<string name="scut_invalid_uuid">O PC informado não é válido</string>
<string name="scut_invalid_app_id">O aplicativo informado não é válido</string>
<string name="pcview_menu_header_online">Disponível</string>
<string name="pcview_menu_app_list">Ver Todos os Jogos</string>
<string name="pcview_menu_unpair_pc">Desparear</string>
<string name="pcview_menu_delete_pc">Deletar PC</string>
<string name="pcview_menu_test_network">Testar Conexão com a Internet</string>
<string name="pcview_menu_details">Ver Detalhes</string>
<string name="nettest_title_waiting">Testando a Conexão com a Internet</string>
<string name="nettest_title_done">Teste de Internet Completo</string>
<string name="pcview_menu_pair_pc">Parear com o PC</string>
<string name="pcview_menu_send_wol">Enviar Wake-On-LAN</string>
<string name="nettest_text_waiting">O Moonlight está testando a sua internet para determinar se o NVIDIA GameStream está bloqueado.
\n
\nIsso pode levar alguns segundos…</string>
<string name="nettest_text_inconclusive">O teste de rede não pôde ser executado porque nenhum dos servidores de teste de conexão do Moonlight estava acessível. Verifique sua conexão com a Internet ou tente novamente mais tarde.</string>
<string name="nettest_text_failure">A conexão de rede atual do seu dispositivo parece estar bloqueando o Moonlight. A transmissão pela Internet pode não funcionar enquanto estiver conectado a esta rede.
\n
\nAs seguintes portas de rede estão bloqueadas:
\n</string>
<string name="nettest_text_blocked">A conexão de rede atual do seu dispositivo está bloqueando o Moonlight. A transmissão pela Internet pode não funcionar enquanto estiver conectado a esta rede.</string>
<string name="pairing">Pareando…</string>
<string name="pair_pc_offline">Esse computador está indisponível</string>
<string name="pair_pc_ingame">Esse computador está atualmente em um jogo. Você deve fechar o jogo antes de parear.</string>
<string name="pair_pairing_title">Pareando</string>
<string name="pair_pairing_msg">Insira o seguinte PIN no PC de destino:</string>
<string name="pair_incorrect_pin">PIN incorreto</string>
<string name="pair_fail">Falha no pareamento</string>
<string name="pair_already_in_progress">Pareamento já em andamento</string>
<string name="wol_pc_online">Esse computador está disponível</string>
<string name="wol_waking_pc">Acordando o PC…</string>
<string name="wol_fail">Falha ao enviar pacotes Wake-On-LAN</string>
<string name="unpairing">Despareando…</string>
<string name="unpair_success">Despareado com sucesso</string>
<string name="unpair_fail">Falha ao desparear</string>
<string name="unpair_error">Dispositivo não pareado</string>
<string name="error_pc_offline">Esse computador está indisponível</string>
<string name="error_manager_not_running">O serviço ComputerManager não está em execução. Aguarde alguns segundos ou reinicie o aplicativo.</string>
<string name="error_unknown_host">Falha ao encontrar o host</string>
<string name="conn_terminated_msg">A conexão foi encerrada</string>
<string name="yes">Sim</string>
<string name="no">Não</string>
<string name="lost_connection">Conexão perdida com o PC</string>
<string name="title_details">Detalhes</string>
<string name="help">Ajuda</string>
<string name="delete_pc_msg">Tem certeza de que deseja excluir este PC\?</string>
<string name="slow_connection_msg">Conexão lenta com o PC
\nReduza sua taxa de bits</string>
<string name="poor_connection_msg">Conexão ruim com o PC</string>
<string name="conn_terminated_title">Conexão Encerrada</string>
<string name="title_decoding_error">Decodificador de Vídeo Crashou</string>
<string name="title_decoding_reset">Resetar Configurações de Vídeo</string>
<string name="conn_metered">Aviso: Sua conexão de rede atual é limitada!</string>
<string name="conn_client_latency">Latência média de decodificação de quadros:</string>
<string name="conn_client_latency_hw">latência do decodificador de hardware:</string>
<string name="conn_hardware_latency">Latência média de decodificação de hardware:</string>
<string name="conn_starting">Iniciando</string>
<string name="conn_error_title">Erro de Conexão</string>
<string name="perf_overlay_decoder">Decodificador: %1$s</string>
<string name="perf_overlay_incomingfps">Taxa de quadros recebidos pela rede:: %1$.2f FPS</string>
<string name="perf_overlay_renderingfps">Taxa de quadros renderizando: %1$.2f FPS</string>
<string name="perf_overlay_streamdetails">Transmissão: %1$s %2$.2f FPS</string>
<string name="perf_overlay_netlatency">Latência média da rede: %1$d ms (variação: %2$d ms)</string>
<string name="perf_overlay_dectime">Tempo médio de decodificação: %1$.2f ms</string>
<string name="applist_connect_msg">Conectando ao PC…</string>
<string name="applist_menu_resume">Retornar ao Jogo</string>
<string name="applist_menu_quit">Sair do Jogo</string>
<string name="applist_menu_quit_and_start">Sair do Jogo Atual e Iniciar</string>
<string name="applist_menu_cancel">Cancelar</string>
<string name="applist_menu_details">Ver Detalhes</string>
<string name="applist_menu_scut">Criar Atalho</string>
<string name="applist_menu_tv_channel">Adicionar ao Canal</string>
<string name="applist_menu_hide_app">Esconder Jogo</string>
<string name="applist_refresh_title">Lista de Aplicativos</string>
<string name="perf_overlay_netdrops">Quadros dropados pela rede: %1$.2f%%</string>
<string name="applist_quit_app">Saindo</string>
<string name="applist_quit_success">Saiu com sucesso</string>
<string name="applist_quit_fail">Falha ao sair</string>
<string name="applist_refresh_msg">Recarregando aplicativos…</string>
<string name="applist_refresh_error_title">Erro</string>
<string name="applist_refresh_error_msg">Falha ao obter a lista de aplicativos</string>
<string name="applist_quit_confirmation">Tem certeza de que deseja sair do aplicativo em execução\? Todos os dados não salvos serão perdidos.</string>
<string name="applist_details_id">ID do App:</string>
<string name="title_add_pc">Adicionar PC Manualmente</string>
<string name="msg_add_pc">Conectando ao PC…</string>
<string name="addpc_success">Computador adicionado com sucesso</string>
<string name="addpc_unknown_host">Não foi possível encontrar o PC. Certifique-se de que não cometeu um erro de digitação no endereço.</string>
<string name="addpc_enter_ip">Você deve inserir um endereço de IP</string>
<string name="category_input_settings">Configurações de Controles</string>
<string name="category_basic_settings">Configurações Básicas</string>
<string name="title_resolution_list">Resolução</string>
<string name="summary_resolution_list">Aumente para melhorar a clareza da imagem. Diminua para melhor desempenho em dispositivos de baixo custo e redes mais lentas.</string>
<string name="title_native_res_dialog">Alerta de Resolução Nativa</string>
<string name="text_native_res_dialog">Os modos de resolução nativa não são oficialmente suportados pelo GeForce Experience, portanto, ele não definirá a resolução da tela do host. Você precisará configurá-lo manualmente durante o jogo.
\n
\nSe você optar por criar uma resolução personalizada no Painel de controle da NVIDIA para corresponder à resolução do seu dispositivo, certifique-se de ter lido e entendido o aviso da NVIDIA sobre possíveis danos ao monitor, instabilidade do PC e outros problemas potenciais.
\n
\nNão nos responsabilizamos por quaisquer problemas resultantes da criação de uma resolução personalizada em seu PC.
\n
\nPor fim, seu dispositivo ou PC host pode não suportar transmissões na resolução nativa. Se não funcionar no seu dispositivo, você está sem sorte, infelizmente.</string>
<string name="title_fps_list">Taxa de quadros por segundo</string>
<string name="summary_fps_list">Aumente para uma transmissão de vídeo mais suave. Diminua para melhor desempenho em dispositivos de baixo custo.</string>
<string name="title_seekbar_bitrate">Taxa de bits</string>
<string name="summary_seekbar_bitrate">Aumente para uma melhor qualidade de imagem. Diminua para melhorar o desempenho em conexões mais lentas.</string>
<string name="title_checkbox_stretch_video">Esticar vídeo para tela cheia</string>
<string name="resolution_prefix_native">Nativa</string>
<string name="resolution_prefix_native_fullscreen">Nativa Tela Cheia</string>
<string name="category_audio_settings">Configurações de Áudio</string>
<string name="title_audio_config_list">Configurações de som Surround</string>
<string name="summary_audio_config_list">Habilita o som Surround 5.1 ou 7.1 para sistemas de home theater</string>
<string name="addpc_wrong_sitelocal">Esse endereço não parece certo. Você deve usar o endereço de IP público do seu roteador para transmitir pela Internet.</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="title_checkbox_show_onscreen_controls">Mostrar controles na tela</string>
<string name="summary_checkbox_show_onscreen_controls">Habilita o controle virtual na tela de toque</string>
<string name="title_checkbox_vibrate_osc">Habilitar vibração</string>
<string name="title_only_l3r3">Mostrar somente L3 e R3</string>
<string name="summary_only_l3r3">Esconde todos os botões virtuais exceto o L3 e R3</string>
<string name="title_checkbox_touchscreen_trackpad">Usar a tela como um trackpad</string>
<string name="title_checkbox_multi_controller">Detecção automática de gamepad</string>
<string name="summary_checkbox_multi_controller">Desmarque esta opção para forçar o gamepad a estar sempre presente</string>
<string name="title_checkbox_vibrate_fallback">Emular suporte rumble com vibração</string>
<string name="summary_checkbox_vibrate_fallback">Vibra seu dispositivo para emular rumble se seu gamepad não suportar</string>
<string name="title_seekbar_deadzone">Ajustar zona morta do analógico</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Driver de gamepad USB Xbox 360/One</string>
<string name="title_checkbox_usb_bind_all">Substituir o suporte nativo ao gamepad do Xbox</string>
<string name="summary_checkbox_usb_bind_all">Use o driver USB do Moonlight para todos os gamepads suportados, mesmo se houver suporte nativo ao controle Xbox</string>
<string name="title_checkbox_mouse_emulation">Emulação de mouse via gamepad</string>
<string name="summary_checkbox_mouse_emulation">Segure o botão Start para mudar o gamepad para o modo de mouse</string>
<string name="summary_checkbox_mouse_nav_buttons">Ativar esta opção pode bugar o clique com o botão direito em alguns dispositivos com bugs</string>
<string name="summary_checkbox_flip_face_buttons">Inverte os botões A/B e X/Y dos gamepads e controles de tela</string>
<string name="category_on_screen_controls_settings">Configurações de Controles de Tela</string>
<string name="title_checkbox_flip_face_buttons">Inverter botões</string>
<string name="title_unlock_fps">Liberar todas as taxas de quadros possíveis</string>
<string name="category_advanced_settings">Configurações Avançadas</string>
<string name="title_checkbox_host_audio">Reproduzir áudio no PC</string>
<string name="summary_checkbox_host_audio">Reproduz o áudio do computador e deste dispositivo</string>
<string name="title_checkbox_enable_sops">Otimizar configurações de jogo</string>
<string name="summary_checkbox_enable_sops">Permitir que o GFE modifique as configurações do jogo para otimizar a transmissão</string>
<string name="title_language_list">Idioma</string>
<string name="summary_language_list">Idioma para ser usado no Moonlight</string>
<string name="summary_checkbox_enable_pip">Permite que a transmissão seja visualizada (mas não controlada) enquanto usa outros apps</string>
<string name="category_ui_settings">Configurações de Interface</string>
<string name="suffix_osc_opacity">%</string>
<string name="category_host_settings">Configurações de Host</string>
<string name="dialog_title_osc_opacity">Mudar opacidade</string>
<string name="title_checkbox_small_icon_mode">Mostrar ícones menores</string>
<string name="summary_reset_osc">Redefine todos os controles na tela para seu tamanho e posição padrão</string>
<string name="dialog_title_reset_osc">Resetar Layout</string>
<string name="toast_reset_osc_success">Controles de tela redefinidos para o padrão</string>
<string name="title_osc_opacity">Mudar a opacidade dos controles de tela</string>
<string name="summary_osc_opacity">Deixe os controles de tela mais/menos transparentes</string>
<string name="title_reset_osc">Limpar o layout salvo dos controles de tela</string>
<string name="title_video_format">Mudar configurações do HEVC</string>
<string name="summary_video_format">HEVC reduz os requisitos de largura de banda de vídeo, mas requer um dispositivo mais atual</string>
<string name="summary_checkbox_disable_warnings">Desativa as mensagens de aviso de conexão na tela durante a transmissão</string>
<string name="title_disable_frame_drop">Nunca dropar quadros</string>
<string name="summary_disable_frame_drop">Talvez reduza o micro-stuttering em alguns dispositivos, mas pode aumentar a latência</string>
<string name="title_enable_hdr">Habilitar HDR (Experimental)</string>
<string name="title_enable_post_stream_toast">Mostrar aviso de latência após terminar a transmissão</string>
<string name="summary_enable_perf_overlay">Exibe informações de performance em tempo real durante a transmissão</string>
<string name="title_enable_perf_overlay">Mostrar status de performance durante a transmissão</string>
</resources>
+4
View File
@@ -13,4 +13,8 @@
<string name="pcview_menu_header_offline">Offline</string>
<string name="pcview_menu_app_list">Veja todos aplicativos</string>
<string name="pcview_menu_pair_pc">Sincronize com o PC</string>
<string name="pcview_menu_details">Ver Detalhes</string>
<string name="pair_pairing_title">Pareando</string>
<string name="pair_fail">Falha no pareamento</string>
<string name="nettest_title_waiting">Testar Conexão com a Internet</string>
</resources>
+135
View File
@@ -38,4 +38,139 @@
<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>
<string name="title_checkbox_multi_controller">Otomatik oyun kumandası varlığı algılama</string>
<string name="summary_checkbox_multi_controller">Bu seçeneğin işaretinin kaldırılması bir oyun kumandasının her zaman mevcut olmasını zorlar</string>
<string name="summary_checkbox_vibrate_fallback">Oyun kumandanız desteklemiyorsa, gürültüyü taklit etmek için cihazınızı titreştirir</string>
<string name="title_seekbar_deadzone">Analog çubuk ölü bölgesini ayarlama</string>
<string name="summary_checkbox_xb1_driver">Yerel Xbox oyun kumandası desteği olmayan cihazlar için yerleşik USB sürücüsünü etkinleştirir</string>
<string name="title_checkbox_mouse_emulation">Oyun kumandası üzerinden fare emülasyonu</string>
<string name="title_checkbox_mouse_nav_buttons">Geri ve ileri fare düğmelerini etkinleştirme</string>
<string name="category_on_screen_controls_settings">Ekran Kontrolleri Ayarları</string>
<string name="title_checkbox_show_onscreen_controls">Ekran kontrollerini göster</string>
<string name="error_manager_not_running">ComputerManager hizmeti çalışmıyor. Lütfen birkaç saniye bekleyin veya uygulamayı yeniden başlatın.</string>
<string name="error_unknown_host">Ana bilgisayar çözümlenemedi</string>
<string name="title_decoding_error">Video Kod Çözücü Çöktü</string>
<string name="message_decoding_reset">Cihazınızın video kod çözücüsü, seçtiğiniz akış ayarlarında çökmeye devam ediyor. Akış ayarlarınız varsayılana sıfırlandı.</string>
<string name="error_usb_prohibited">USB erişimi cihaz yöneticiniz tarafından yasaklanmıştır. Knox veya MDM ayarlarınızı kontrol edin.</string>
<string name="unable_to_pin_shortcut">Mevcut başlatıcınız sabitlenmiş kısayollar oluşturmaya izin vermiyor.</string>
<string name="video_decoder_init_failed">Video kod çözücü başlatılamadı. Cihazınız seçilen çözünürlüğü veya kare hızını desteklemiyor olabilir.</string>
<string name="no_video_received_error">Ana bilgisayardan video alınmadı.</string>
<string name="no_frame_received_error">Ağ bağlantınız iyi performans göstermiyor. Video bit hızı ayarınızı düşürün veya daha hızlı bir bağlantı deneyin.</string>
<string name="early_termination_error">Yayını başlatırken ana bilgisayarda bir şeyler ters gitti.
\n
\nAna bilgisayarınızda DRM korumalı herhangi bir içeriğin açık olmadığından emin olun. Ana bilgisayarınızı yeniden başlatmayı da deneyebilirsiniz.
\n
\nSorun devam ederse GPU sürücülerinizi ve GeForce Experience\'ı yeniden yüklemeyi deneyin.</string>
<string name="check_ports_msg">Bağlantı noktaları için güvenlik duvarınızı ve bağlantı noktası yönlendirme kurallarınızı kontrol edin:</string>
<string name="conn_establishing_title">Bağlantı kuruluyor</string>
<string name="conn_terminated_title">Bağlantı sonlandırıldı</string>
<string name="conn_terminated_msg">Bağlantı sonlandırıldı</string>
<string name="yes">Evet</string>
<string name="lost_connection">Bilgisayar ile bağlantı kesildi</string>
<string name="help">Yardım</string>
<string name="delete_pc_msg">Bu bilgisayarı silmek istediğinizden emin misiniz\?</string>
<string name="slow_connection_msg">Bilgisayara yavaş bağlantı
\nBit hızınızı düşürün</string>
<string name="poor_connection_msg">Bilgisayar ile zayıf bağlantı</string>
<string name="perf_overlay_streamdetails">Video akışı: %1$s %2$.2f FPS</string>
<string name="perf_overlay_decoder">Kod çözücü: %1$s</string>
<string name="perf_overlay_incomingfps">Ağdan gelen kare hızı: %1$.2f FPS</string>
<string name="perf_overlay_renderingfps">İşleme kare hızı: %1$.2f FPS</string>
<string name="perf_overlay_netdrops">Ağ bağlantınız tarafından düşen kare sayısı: %1$.2f%%</string>
<string name="perf_overlay_dectime">Ortalama kod çözme süresi: %1$.2f ms</string>
<string name="applist_menu_resume">Oturumu sürdür</string>
<string name="applist_menu_quit">Oturumu sonlandır</string>
<string name="applist_menu_quit_and_start">Mevcut oyundan çık ve başla</string>
<string name="applist_menu_cancel">İptal</string>
<string name="applist_menu_details">Detayları göster</string>
<string name="applist_menu_scut">Kısayol oluştur</string>
<string name="applist_menu_tv_channel">Kanala ekle</string>
<string name="applist_menu_hide_app">Uygulamayı gizle</string>
<string name="applist_refresh_title">Uygulama listesi</string>
<string name="applist_quit_success">Başarıyla çıkıldı</string>
<string name="applist_refresh_error_msg">Uygulama listesi alınamadı</string>
<string name="applist_quit_app">Çıkılıyor</string>
<string name="applist_quit_fail">Çıkılamadı</string>
<string name="applist_quit_confirmation">Çalışan uygulamadan çıkmak istediğinizden emin misiniz\? Kaydedilmemiş tüm veriler kaybolacaktır.</string>
<string name="applist_details_id">Uygulama kimliği:</string>
<string name="msg_add_pc">Bilgisayara bağlanıyor…</string>
<string name="addpc_success">Bilgisayar başarıyla eklendi</string>
<string name="addpc_fail">Belirtilen bilgisayara bağlanılamıyor. Gerekli bağlantı noktalarına güvenlik duvarı üzerinden izin verildiğinden emin olun.</string>
<string name="addpc_unknown_host">Bilgisayar adresi çözümlenemiyor. Adreste yazım hatası yapmadığınızdan emin olun.</string>
<string name="addpc_enter_ip">Bir IP adresi girmelisiniz</string>
<string name="category_basic_settings">Temel Ayarlar</string>
<string name="summary_resolution_list">Görüntü netliğini artırmak için artırın. Düşük uçlu cihazlarda ve daha yavaş ağlarda daha iyi performans için azaltın.</string>
<string name="title_native_res_dialog">Yerel çözünürlük uyarısı</string>
<string name="title_fps_list">Video kare hızı</string>
<string name="title_seekbar_bitrate">Video bit hızı</string>
<string name="summary_checkbox_touchscreen_trackpad">Etkinleştirilirse, dokunmatik ekran bir dokunmatik fare gibi davranır. Devre dışı bırakılırsa, dokunmatik ekran doğrudan fare imlecini kontrol eder.</string>
<string name="conn_establishing_msg">Bağlantı başlatılıyor</string>
<string name="conn_metered">Uyarı: Aktif ağ bağlantınız ölçülüdür!</string>
<string name="conn_client_latency">Ortalama kare kod çözme gecikmesi:</string>
<string name="conn_client_latency_hw">donanım kod çözücü gecikmesi:</string>
<string name="pcview_menu_send_wol">Yerel Ağda Uyandırma isteği gönder</string>
<string name="applist_refresh_msg">Uygulamalar yenileniyor…</string>
<string name="applist_refresh_error_title">Hata</string>
<string name="title_add_pc">Bilgisayarı manuel olarak Ekle</string>
<string name="title_resolution_list">Video çözünürlüğü</string>
<string name="summary_fps_list">Daha akıcı bir video akışı için artırın. Düşük uçlu cihazlarda daha iyi performans için azaltın.</string>
<string name="resolution_prefix_native">Yerel</string>
<string name="resolution_prefix_native_fullscreen">Yerel tam ekran</string>
<string name="category_audio_settings">Ses Ayarları</string>
<string name="title_audio_config_list">Çevresel ses yapılandırması</string>
<string name="summary_seekbar_deadzone">Not: Bazı oyunlar Moonlight\'ın kullanmak üzere yapılandırıldığından daha büyük bir ölü bölge uygulayabilir.</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One USB oyun kumandası sürücüsü</string>
<string name="title_checkbox_usb_bind_all">Yerel Xbox oyun kumandası desteğini geçersiz kılma</string>
<string name="summary_checkbox_mouse_emulation">Başlat düğmesine uzun süre basmak oyun kumandasını fare moduna geçirir</string>
<string name="summary_checkbox_mouse_nav_buttons">Bu seçeneği etkinleştirmek bazı hatalı cihazlarda sağ tıklamayı bozabilir</string>
<string name="title_checkbox_flip_face_buttons">Yüz düğmelerini çevirme</string>
<string name="title_checkbox_absolute_mouse_mode">Uzak masaüstü fare modu</string>
<string name="error_404">GFE bir HTTP 404 hatası bildirdi. Bilgisayarınızın desteklenen bir GPU çalıştırdığından emin olun. Uzak masaüstü yazılımı kullanmak da bu hataya neden olabilir. Cihazınızı yeniden başlatmayı veya GFE\'yi yeniden yüklemeyi deneyin.</string>
<string name="message_decoding_error">Moonlight, bu cihazın video kod çözücüsü ile uyumsuzluk nedeniyle çöktü. GeForce Experience\'ın bilgisayarınızdaki en son sürüme güncellendiğinden emin olun. Çökmeler devam ederse akış ayarlarını değiştirmeyi deneyin.</string>
<string name="conn_hardware_latency">Ortalama donanım kod çözme gecikmesi:</string>
<string name="title_decoding_reset">Video Ayarlarını Sıfırla</string>
<string name="searching_pc">GameStream\'in çalıştığı bilgisayarlar aranıyor...
\n
\n GeForce Experience SHIELD ayarlarında GameStream\'in etkinleştirildiğinden emin olun.</string>
<string name="applist_connect_msg">Bilgisayara bağlanıyor…</string>
<string name="no">Hayır</string>
<string name="perf_overlay_netlatency">Ortalama ağ gecikmesi: %1$d ms (varyans: %2$d ms)</string>
<string name="addpc_wrong_sitelocal">Bu adres doğru görünmüyor. İnternet üzerinden akış için yönlendiricinizin genel IP adresini kullanmanız gerekir.</string>
<string name="text_native_res_dialog">Yerel çözünürlük modları GeForce Experience tarafından resmi olarak desteklenmez, bu nedenle ana ekran çözünürlüğünüzü kendisi ayarlamaz. Oyun içindeyken manuel olarak ayarlamanız gerekecektir.
\n
\nCihazınızın çözünürlüğüne uyması için NVIDIA Denetim Masası\'nda özel bir çözünürlük oluşturmayı seçerseniz, lütfen NVIDIA\'nın olası monitör hasarı, bilgisayar kararsızlığı ve diğer olası sorunlarla ilgili uyarısını okuyup anladığınızdan emin olun.
\n
\nBilgisayarınızda özel bir çözünürlük oluşturulmasından kaynaklanan herhangi bir sorundan sorumlu değiliz.
\n
\nSon olarak, cihazınız veya bilgisayarınız doğal çözünürlükte akışı desteklemiyor olabilir. Eğer cihazınızda çalışmıyorsa, ne yazık ki şansınız yok demektir.</string>
<string name="summary_seekbar_bitrate">Daha iyi görüntü kalitesi için artırın. Daha yavaş bağlantılarda performansı artırmak için azaltın.</string>
<string name="title_checkbox_stretch_video">Videoyu tam ekrana genişlet</string>
<string name="title_checkbox_vibrate_fallback">Titreşim ile gürültü desteğini taklit etme</string>
<string name="summary_checkbox_usb_bind_all">Yerel Xbox oyun kumandası desteği mevcut olsa bile desteklenen tüm oyun kumandaları için Moonlight\'ın USB sürücüsünü kullan</string>
<string name="summary_checkbox_flip_face_buttons">Oyun kumandaları ve ekran kontrolleri için A/B ve X/Y yüz düğmelerini değiştirir</string>
<string name="summary_checkbox_absolute_mouse_mode">Bu fare hızlandırmanın uzak masaüstü kullanımı için daha doğal davranmasını sağlayabilir, ancak birçok oyunla uyumsuzdur.</string>
</resources>
+19 -2
View File
@@ -152,7 +152,7 @@
<string name="title_checkbox_vibrate_fallback">Емуляція вібровіддачі</string>
<string name="summary_checkbox_vibrate_osc">Вібрація пристрою для емуляції вібровіддачі при екранному управлінні</string>
<string name="summary_checkbox_vibrate_fallback">Вібрує пристрій для емуляції вібровіддачі, якщо під\'єднаний контролер не підтримує її</string>
<string name="summary_checkbox_mouse_nav_buttons">Включення цієї опції може привести до неправильної роботи правої клавіші миші на деяких пристроях</string>
<string name="summary_checkbox_mouse_nav_buttons">Вмикання цієї опції може привести до неправильної роботи правої клавіші миші на деяких пристроях</string>
<string name="title_checkbox_flip_face_buttons">Перевернути ґудзики</string>
<string name="summary_checkbox_flip_face_buttons">Перемикає ґудзики A/B та X/Y для контролерів та екранних елементів керування</string>
<string name="scut_pc_not_found">Пристрій не знайдено</string>
@@ -217,9 +217,26 @@
<string name="pcview_menu_header_unknown">Оновлення</string>
<string name="pcview_menu_header_offline">Поза мережею</string>
<string name="pcview_menu_header_online">В мережі</string>
<!-- Array strings -->
<string name="videoformat_hevcauto">Використовувати HEVC тільки якщо безпечно</string>
<string name="videoformat_hevcalways">Завжди використовувати HEVC якщо доступно</string>
<string name="videoformat_hevcnever">Ніколи не використовувати HEVC</string>
<string name="title_frame_pacing">Швидкість кадрів відео</string>
<string name="summary_frame_pacing">Укажіть баланс відео затримки та плавності</string>
<string name="pacing_latency">Перевага найменшій затримці</string>
<string name="pacing_smoothness">Перевага плавному відео (може значно збільшити затримку)</string>
<string name="pacing_balanced">Збалансовано</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<string name="resolution_720p">720p</string>
<string name="resolution_1080p">1080p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="fps_30">30 кадрів/сек</string>
<string name="fps_60">60 кадрів/сек</string>
<string name="fps_90">90 кадрів/сек</string>
<string name="fps_120">120 кадрів/сек</string>
<string name="audioconf_stereo">Стерео</string>
<string name="audioconf_51surround">5.1 Об\'ємний Звук</string>
<string name="audioconf_71surround">7.1 Об\'ємний Звук</string>
</resources>
+27 -3
View File
@@ -218,13 +218,37 @@
<string name="perf_overlay_netlatency">平均网络延迟: %1$d ms (抖动: %2$d ms)</string>
<string name="perf_overlay_streamdetails">视频流: %1$s %2$.2f FPS</string>
<string name="resolution_prefix_native_fullscreen">本地全屏</string>
<!-- Array strings -->
<string name="audioconf_stereo">立体声</string>
<string name="audioconf_51surround">5.1环绕声</string>
<string name="audioconf_71surround">7.1环绕声</string>
<string name="videoformat_hevcauto">如果稳定才使用HEVC</string>
<string name="videoformat_hevcauto">自动</string>
<string name="videoformat_hevcalways">强制使用HEVC(不稳定)</string>
<string name="videoformat_hevcnever">不使用HEVC</string>
<string name="title_frame_pacing">视频帧速调节</string>
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
<string name="resolution_1440p">1440p</string>
<string name="resolution_4k">4K</string>
<string name="fps_30">30 FPS</string>
<string name="fps_60">60 FPS</string>
<string name="fps_90">90 FPS</string>
<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>
<string name="summary_seekbar_deadzone">注意:有些游戏可以执行一个比Moonlight摇杆配置的更大的盲区。</string>
<string name="title_checkbox_absolute_mouse_mode">适合远程桌面的鼠标模式</string>
<string name="summary_checkbox_absolute_mouse_mode">这可以使得鼠标加速在远程桌面使用中表现得更自然,但它与许多游戏不兼容。</string>
</resources>
+70 -54
View File
@@ -8,7 +8,7 @@
<string name="scut_invalid_app_id">提供的應用程式無效</string>
<!-- Help strings -->
<string name="help_loading_title">說明檢視器</string>
<string name="help_loading_msg">正在載入說明頁面…</string>
<string name="help_loading_msg">正在載入說明頁面…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">檢視遊戲清單</string>
<string name="pcview_menu_pair_pc"> 和電腦配對 </string>
@@ -17,30 +17,30 @@
<string name="pcview_menu_delete_pc"> 刪除電腦 </string>
<string name="pcview_menu_details">檢視詳細資料</string>
<!-- Pair messages -->
<string name="pairing"> 配對中…… </string>
<string name="pair_pc_offline"> 電腦離線</string>
<string name="pair_pc_ingame">電腦正在遊戲中,在配對之前你必須先退出遊戲。</string>
<string name="pair_pairing_title"> 配對中 </string>
<string name="pairing">正在配對…</string>
<string name="pair_pc_offline">電腦離線</string>
<string name="pair_pc_ingame">電腦目前正在遊戲中,在配對之前你必須先退出遊戲。</string>
<string name="pair_pairing_title">正在配對</string>
<string name="pair_pairing_msg">請在目標電腦上輸入以下 PIN 碼:</string>
<string name="pair_incorrect_pin">PIN 碼錯誤</string>
<string name="pair_fail"> 配對失敗 </string>
<string name="pair_already_in_progress"> 配對,請稍候 </string>
<string name="pair_already_in_progress">正在配對,請稍候</string>
<!-- WOL messages -->
<string name="wol_pc_online"> 電腦線上中 </string>
<string name="wol_pc_online">電腦已上</string>
<string name="wol_no_mac">無法喚醒電腦因為 GFE 沒有傳送 MAC 位址</string>
<string name="wol_waking_pc"> 喚醒電腦中…… </string>
<string name="wol_waking_pc">正在喚醒電腦</string>
<string name="wol_waking_msg">喚醒電腦需要一些時間。如果電腦沒有喚醒,請確保 Wake-On-LAN 設定無誤。</string>
<string name="wol_fail">無法傳送 Wake-On-LAN 資料包</string>
<!-- Unpair messages -->
<string name="unpairing"> 取消配對中…… </string>
<string name="unpairing">正在取消配對</string>
<string name="unpair_success"> 成功取消配對 </string>
<string name="unpair_fail"> 無法配對 </string>
<string name="unpair_error">裝置沒有配對過</string>
<!-- Errors -->
<string name="error_pc_offline"> 電腦離線</string>
<string name="error_pc_offline">電腦離線</string>
<string name="error_manager_not_running">ComputerManager 服務未執行。請稍等幾秒或重啟應用程式。</string>
<string name="error_unknown_host"> 無法解析主機位址 </string>
<string name="error_404">GFE 返回了 HTTP 404 錯誤。確保你的電腦顯卡支援串流。使用遠端桌面軟體同樣會引起此錯誤,請嘗試重啟電腦或重新安裝 GFE。</string>
<string name="error_404">GFE 返回了 HTTP 404 錯誤。確保你的電腦顯卡支援串流。使用遠端桌面軟體同樣會引起此錯誤,請嘗試重啟電腦或重新安裝 GFE。</string>
<string name="title_decoding_error">視訊解碼器崩潰</string>
<string name="message_decoding_error">由於與該裝置的視訊解碼器不相容,Moonlight 已崩潰。確保你電腦上的 GFE 已更新至最新版本,如果崩潰繼續,請嘗試調整串流設定。</string>
<string name="title_decoding_reset">重設視訊設定</string>
@@ -48,20 +48,20 @@
<string name="error_usb_prohibited">裝置管理員已禁止 USB 訪問。請檢查您的 Knox 或 MDM 設定。</string>
<string name="unable_to_pin_shortcut">您目前的啟動器不允許創建釘選的捷徑。</string>
<!-- Start application messages -->
<string name="conn_establishing_title">建立連線</string>
<string name="conn_establishing_msg">啟動連線</string>
<string name="conn_establishing_title">正在建立連線</string>
<string name="conn_establishing_msg">正在啟動連線</string>
<string name="conn_metered">警告:你正在使用行動網路連線!</string>
<string name="conn_client_latency">平均每個影格解碼延時:</string>
<string name="conn_client_latency_hw">硬體解碼器延時:</string>
<string name="conn_hardware_latency">硬體解碼器平均延時:</string>
<string name="conn_starting">啟動</string>
<string name="conn_starting">正在啟動</string>
<string name="conn_error_title">連線錯誤</string>
<string name="conn_error_msg"> 啟動失敗 </string>
<string name="conn_terminated_title">連線</string>
<string name="conn_terminated_msg">連線已被中</string>
<string name="conn_terminated_title">連線已終</string>
<string name="conn_terminated_msg">連線已</string>
<!-- General strings -->
<string name="ip_hint">串流電腦的 IP 位址</string>
<string name="searching_pc">正在搜尋執行 GAMESTREAM 的電腦…
<string name="searching_pc">正在搜尋執行 GAMESTREAM 的電腦…
\n
\n請確保 GFE SHIELD 設定裡的 GAMESTREAM 已啟用。</string>
<string name="yes"> 確定 </string>
@@ -79,27 +79,27 @@
<string name="perf_overlay_netdrops">網路丟失影格:%1$.2f%%</string>
<string name="perf_overlay_dectime">平均解碼時間:%1$.2f ms</string>
<!-- AppList activity -->
<string name="applist_connect_msg">正在連線電腦…</string>
<string name="applist_menu_resume">恢復對話</string>
<string name="applist_menu_quit">退出對話</string>
<string name="applist_menu_quit_and_start">退出目前遊戲並開始這個遊戲</string>
<string name="applist_connect_msg">正在連線電腦…</string>
<string name="applist_menu_resume">恢復工作階段</string>
<string name="applist_menu_quit">結束工作階段</string>
<string name="applist_menu_quit_and_start">結束目前遊戲並開始這個遊戲</string>
<string name="applist_menu_cancel"> 取消 </string>
<string name="applist_menu_details">檢視詳細資料</string>
<string name="applist_menu_scut">建捷徑</string>
<string name="applist_menu_scut">捷徑</string>
<string name="applist_menu_tv_channel">新增至頻道</string>
<string name="applist_refresh_title">遊戲清單</string>
<string name="applist_refresh_msg">重新整理中…</string>
<string name="applist_refresh_msg">正在重新整理…</string>
<string name="applist_refresh_error_title"> 錯誤 </string>
<string name="applist_refresh_error_msg">取遊戲清單失敗</string>
<string name="applist_quit_app"> 退出中 </string>
<string name="applist_quit_success">退出成功</string>
<string name="applist_quit_fail">退出失敗</string>
<string name="applist_quit_confirmation">您確定要退出執行中的遊戲?所有未儲存的資料將丟失。</string>
<string name="applist_refresh_error_msg">遊戲清單失敗</string>
<string name="applist_quit_app">正在結束</string>
<string name="applist_quit_success">結束成功</string>
<string name="applist_quit_fail">結束失敗</string>
<string name="applist_quit_confirmation">您確定要結束執行中的遊戲?所有未儲存的資料將丟失。</string>
<string name="applist_details_id">App ID</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">手動新增電腦</string>
<string name="msg_add_pc">正在連線電腦…</string>
<string name="addpc_fail">無法連線至指定電腦。請確保所需埠沒有被防火牆阻止。</string>
<string name="msg_add_pc">正在連線電腦…</string>
<string name="addpc_fail">無法連線至指定電腦。請確保所需通訊埠沒有被防火牆阻止。</string>
<string name="addpc_success">成功新增電腦</string>
<string name="addpc_unknown_host">無法解析電腦的 IP 位址,請確保 IP 位址輸入無誤。</string>
<string name="addpc_enter_ip">請輸入一個 IP 位址</string>
@@ -118,7 +118,7 @@
<string name="title_checkbox_disable_warnings">停用錯誤訊息</string>
<string name="summary_checkbox_disable_warnings">停用串流中連線錯誤訊息</string>
<string name="title_checkbox_enable_pip">啟用子母畫面觀察模式</string>
<string name="summary_checkbox_enable_pip">允許多工時檢視串流畫面 (但不操作)</string>
<string name="summary_checkbox_enable_pip">允許多工時檢視串流畫面 (但無法控制)</string>
<string name="category_audio_settings">音訊設定</string>
<string name="title_audio_config_list">環場音效組態</string>
<string name="summary_audio_config_list">為家庭電影院系統啟用 5.1 或 7.1 環場音效</string>
@@ -126,14 +126,14 @@
<string name="title_checkbox_touchscreen_trackpad">將觸控式螢幕作為觸控板使用</string>
<string name="summary_checkbox_touchscreen_trackpad">如果啟用,則將觸控式螢幕作為觸控板使用。 如果停用,則觸控式螢幕直接控制滑鼠游標。</string>
<string name="title_checkbox_multi_controller">自動偵測手把</string>
<string name="summary_checkbox_multi_controller">禁用此項所有手把將視為一個手把</string>
<string name="summary_checkbox_multi_controller">取消此項所有手把將視為一個手把</string>
<string name="title_checkbox_vibrate_fallback">用震動仿真遊戲低頻音</string>
<string name="summary_checkbox_vibrate_fallback">如果你的手把不支援震動,則震動裝置以仿真遊戲低頻音</string>
<string name="title_seekbar_deadzone">調整類比搖杆死區</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One USB 手把驅動程式</string>
<string name="summary_checkbox_xb1_driver">為缺少原生 Xbox 控制器支援的裝置啟用內建 USB 驅動程式</string>
<string name="title_checkbox_usb_bind_all">原生 Xbox 手把支援</string>
<string name="title_checkbox_usb_bind_all">原生 Xbox 手把支援</string>
<string name="summary_checkbox_usb_bind_all">強制 Moonlight 的 USB 驅動程式接管所有受支援的原生 Xbox 控制器</string>
<string name="title_checkbox_mouse_emulation">透過手把仿真滑鼠</string>
<string name="summary_checkbox_mouse_emulation">長按開始鍵將手把切換為滑鼠模式</string>
@@ -148,12 +148,12 @@
<string name="summary_checkbox_vibrate_osc">使用螢幕控制按鈕時震動裝置以仿真遊戲低頻音</string>
<string name="title_only_l3r3">只顯示 L3 和 R3</string>
<string name="summary_only_l3r3">隱藏除 L3 和 R3 外的所有虛擬按鈕</string>
<string name="title_reset_osc">重設已儲存的螢幕控制按鈕佈局</string>
<string name="title_reset_osc">重設已儲存的螢幕控制按鈕版面配置</string>
<string name="summary_reset_osc">重設所有螢幕控制按鈕為預設大小和位置</string>
<string name="dialog_title_reset_osc">重設按鈕佈局</string>
<string name="dialog_text_reset_osc">你確定要刪除已儲存的螢幕按鈕佈局嗎?</string>
<string name="dialog_title_reset_osc">重設按鈕版面配置</string>
<string name="dialog_text_reset_osc">你確定要刪除已儲存的螢幕按鈕版面配置嗎?</string>
<string name="toast_reset_osc_success">螢幕按鈕佈局已經重設</string>
<string name="category_ui_settings">介面設定</string>
<string name="category_ui_settings">使用者介面設定</string>
<string name="title_language_list"> 語言 </string>
<string name="summary_language_list">選擇 Moonlight 顯示的語言</string>
<string name="title_checkbox_small_icon_mode"> 使用小圖示 </string>
@@ -166,30 +166,30 @@
<string name="category_advanced_settings">進階設定</string>
<string name="title_disable_frame_drop">永不丟失影格</string>
<string name="summary_disable_frame_drop">可能會減少在一些裝置上的卡頓,但會增加延時</string>
<string name="title_video_format"> HEVC 設定</string>
<string name="title_video_format">更 HEVC 設定</string>
<string name="summary_video_format">HEVC 能降低視訊頻寬需求,但需要較新的裝置才能支援</string>
<string name="title_enable_hdr">啟用 HDR (實驗)</string>
<string name="summary_enable_hdr">當遊戲和顯卡支援時以 HDR 模式串流。 HDR 需要 GTX 1000 系列或更高規格顯卡。</string>
<string name="title_enable_hdr">啟用 HDR (實驗)</string>
<string name="summary_enable_hdr">當遊戲和顯卡支援時以 HDR 模式串流。 HDR 需要 GTX 1000 系列或更高規格顯卡。</string>
<string name="title_enable_perf_overlay">顯示效能資訊</string>
<string name="summary_enable_perf_overlay">在串流中顯示即時效能資訊</string>
<string name="title_enable_post_stream_toast">串流完畢顯示延時資訊</string>
<string name="summary_enable_post_stream_toast">串流結束後顯示延時資訊</string>
<string name="title_osc_opacity">螢幕按鈕透明度</string>
<string name="dialog_title_osc_opacity">透明度</string>
<string name="title_osc_opacity">更螢幕按鈕透明度</string>
<string name="dialog_title_osc_opacity">變更不透明度</string>
<string name="suffix_osc_opacity">%</string>
<string name="summary_osc_opacity">令螢幕按钮變得更透明/更不透明</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="resolution_prefix_native">本機</string>
<string name="text_native_res_dialog">本機解析度模式不受 GFE 的官方支援,因此不會自動設定主機的顯示解析度。您需要在遊戲中手動進行設定。
\n
\n如果您選擇在 NVIDIA 控制台中創建自訂解析度以匹配裝置解析度,請確保您已閱讀並理解 NVIDIA 關於可能導致顯示器損毀和電腦不穩定以及其他潛在問題的警告。
\n如果您選擇在 NVIDIA 控制面板中建立自訂解析度以符合裝置解析度,請確保您已閱讀並理解 NVIDIA 關於可能導致監視器損毀和電腦不穩定以及其他潛在問題的警告。
\n
\n對於您在您的電腦上建自訂解析度而導致的任何問題,我們概不負責。
\n對於您在您的電腦上建自訂解析度而導致的任何問題,我們概不負責。
\n
\n最後,您的裝置或主機電腦可能不支援以本機解析度串流。如果此模式在您的裝置上無法正常執行,只能說您運氣欠佳了。</string>
<string name="title_native_res_dialog">本機解析度警告</string>
<string name="applist_menu_hide_app">隱藏遊戲</string>
<string name="check_ports_msg">檢查您的防火牆和埠轉規則中的埠:</string>
<string name="check_ports_msg">檢查您的防火牆和通訊埠轉規則中的通訊埠:</string>
<string name="early_termination_error">開始串流時您的主機電腦出了點問題。
\n
\n請確保沒有在主機電腦上開啟任何受 DRM 保護的內容。您也可以嘗試重新開機主機電腦。
@@ -198,33 +198,33 @@
<string name="no_frame_received_error">您的網路連線品質不佳。降低視訊位元速率設定或更換更快的連線。</string>
<string name="no_video_received_error">沒有接收到來自主機的視訊。</string>
<string name="video_decoder_init_failed">視訊解碼器初始化失敗。您的裝置可能不支援選定的解析度或影格速率。</string>
<string name="nettest_text_blocked">您裝置目前的網路連線攔截了 Moonlight。連線到該網路時可能無法透過網際網路串流。</string>
<string name="nettest_text_failure">您裝置目前的網路連線似乎攔截了 Moonlight。連線到該網路時可能無法透過網際網路串流。
<string name="nettest_text_blocked">您裝置目前的網路連線封鎖了 Moonlight。連線到該網路時可能無法透過網際網路串流。</string>
<string name="nettest_text_failure">您裝置目前的網路連線似乎封鎖了 Moonlight。連線到該網路時可能無法透過網際網路串流。
\n
\n以下網路埠被攔截
\n以下網路通訊埠被封鎖
\n</string>
<string name="nettest_text_inconclusive">由於沒有 Moonlight 連線測試伺服器可供訪問,因此無法執行網路測試。請檢查您的 Internet 連線或稍後再試。</string>
<string name="nettest_text_success">您的網路似乎沒有攔截 Moonlight。如果仍然無法連線,請檢查您電腦的防火牆設定。
<string name="nettest_text_success">您的網路似乎沒有封鎖 Moonlight。如果仍然無法連線,請檢查您電腦的防火牆設定。
\n
\n如果您是嘗試透過網際網路串流,請在您的電腦上安裝 Moonlight Internet Hosting Tool,然後執行裡面的 Internet Streaming Tester 來檢查電腦的網際網路連線。</string>
<string name="nettest_title_done">網路檢測完畢</string>
<string name="nettest_text_waiting">Moonlight 正在測您的網路連線以確認 NVIDIA 遊戲串流服務是否被攔截
<string name="nettest_text_waiting">Moonlight 正在測您的網路連線以確認 NVIDIA 遊戲串流服務是否被封鎖
\n
\n可能需要等待一些時間…</string>
<string name="nettest_title_waiting">正在測試網路連線</string>
<string name="pcview_menu_test_network">測試網路連線</string>
<string name="pcview_menu_header_unknown">重新整理</string>
<string name="pcview_menu_header_unknown">正在重新整理</string>
<string name="pcview_menu_header_offline">離線</string>
<string name="pcview_menu_header_online">線上</string>
<string name="perf_overlay_netlatency">平均網路延時:%1$d ms (抖動:%2$d ms)</string>
<string name="perf_overlay_streamdetails">視訊串流:%1$s %2$.2f FPS</string>
<string name="resolution_prefix_native_fullscreen">本機全螢幕</string>
<!-- Array strings -->
<string name="audioconf_stereo">身歷</string>
<string name="audioconf_stereo">立體</string>
<string name="audioconf_51surround">5.1 環場音效</string>
<string name="audioconf_71surround">7.1 環場音效</string>
<string name="videoformat_hevcauto">僅在穩定時使用 HEVC</string>
<string name="videoformat_hevcalways">強制使用 HEVC (可能會崩潰)</string>
<string name="videoformat_hevcauto">自動</string>
<string name="videoformat_hevcalways">強制使用 HEVC (可能會當機)</string>
<string name="videoformat_hevcnever">不使用 HEVC</string>
<string name="resolution_4k">4K</string>
<string name="fps_30">30 FPS</string>
@@ -236,4 +236,20 @@
<string name="resolution_480p">480P</string>
<string name="resolution_1080p">1080P</string>
<string name="resolution_1440p">1440P</string>
<string name="title_frame_pacing">視訊影格調步</string>
<string name="summary_frame_pacing">指定如何平衡視訊延遲和平滑度</string>
<string name="pacing_balanced">平衡</string>
<string name="pacing_smoothness">偏好平滑視訊 (可能顯著提高延遲)</string>
<string name="pacing_latency">偏好低延遲</string>
<string name="pacing_balanced_alt">FPS 上限平衡</string>
<string name="category_help">說明</string>
<string name="title_troubleshooting">疑難排解指南</string>
<string name="summary_setup_guide">檢視關於如何設定你的遊戲電腦以進行串流的指示</string>
<string name="title_privacy_policy">隱私權政策</string>
<string name="title_setup_guide">設定指南</string>
<string name="summary_troubleshooting">檢視診斷和修正常見串流問題的提示</string>
<string name="summary_privacy_policy">檢視 Moonlight 的隱私權政策</string>
<string name="summary_seekbar_deadzone">注意:有些遊戲可以強制執行一個比 Moonlight 設定大的死區。</string>
<string name="title_checkbox_absolute_mouse_mode">遠端桌面滑鼠模式</string>
<string name="summary_checkbox_absolute_mouse_mode">這可以讓滑鼠在遠端桌面使用中的加速表現更加自然,但與很多遊戲不相容。</string>
</resources>
+3
View File
@@ -43,6 +43,7 @@
<item>71</item>
</string-array>
<!-- Don't forget to update locales_config.xml when you modify this! -->
<string-array name="language_names" translatable="false">
<item>Default</item>
<item>English</item>
@@ -98,11 +99,13 @@
<string-array name="video_frame_pacing_names">
<item>@string/pacing_latency</item>
<item>@string/pacing_balanced</item>
<item>@string/pacing_balanced_alt</item>
<item>@string/pacing_smoothness</item>
</string-array>
<string-array name="video_frame_pacing_values" translatable="false">
<item>latency</item>
<item>balanced</item>
<item>cap-fps</item>
<item>smoothness</item>
</string-array>
</resources>
+13 -4
View File
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_label" translatable="false">Moonlight</string>
<string name="app_label_root" translatable="false">Moonlight (Root)</string>
<!-- Shortcut strings -->
<string name="scut_deleted_pc">PC deleted</string>
<string name="scut_not_paired">PC not paired</string>
@@ -168,6 +165,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 +177,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>
@@ -227,6 +227,14 @@
<string name="title_enable_post_stream_toast">Show latency message after streaming</string>
<string name="summary_enable_post_stream_toast">Display a latency information message after the stream ends</string>
<string name="category_help">Help</string>
<string name="title_setup_guide">Setup guide</string>
<string name="summary_setup_guide">View instructions on how to set up your gaming PC for streaming</string>
<string name="title_troubleshooting">Troubleshooting guide</string>
<string name="summary_troubleshooting">View tips for diagnosing and fixing common streaming issues</string>
<string name="title_privacy_policy">Privacy policy</string>
<string name="summary_privacy_policy">View Moonlight\'s privacy policy</string>
<!-- Array strings -->
<string name="resolution_360p">360p</string>
<string name="resolution_480p">480p</string>
@@ -244,7 +252,7 @@
<string name="audioconf_51surround">5.1 Surround Sound</string>
<string name="audioconf_71surround">7.1 Surround Sound</string>
<string name="videoformat_hevcauto">Use HEVC only if stable</string>
<string name="videoformat_hevcauto">Automatic</string>
<string name="videoformat_hevcalways">Always use HEVC (may crash)</string>
<string name="videoformat_hevcnever">Never use HEVC</string>
@@ -252,5 +260,6 @@
<string name="summary_frame_pacing">Specify how to balance video latency and smoothness</string>
<string name="pacing_latency">Prefer lowest latency</string>
<string name="pacing_balanced">Balanced</string>
<string name="pacing_balanced_alt">Balanced with FPS limit</string>
<string name="pacing_smoothness">Prefer smoothest video (may significantly increase latency)</string>
</resources>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Don't forget to update arrays.xml when you modify this file! -->
<locale android:name="en"/>
<locale android:name="it"/>
<locale android:name="ja"/>
<locale android:name="ru"/>
<locale android:name="nl"/>
<locale android:name="zh-CN"/>
<locale android:name="zh-TW"/>
<locale android:name="ko"/>
<locale android:name="es"/>
<locale android:name="fr"/>
<locale android:name="de"/>
<locale android:name="ro"/>
<locale android:name="uk"/>
<locale android:name="nb-NO"/>
<locale android:name="vi"/>
<locale android:name="hu"/>
<locale android:name="el"/>
</locale-config>
+26 -5
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">
@@ -159,7 +165,7 @@
android:title="@string/title_checkbox_enable_pip"
android:summary="@string/summary_checkbox_enable_pip"
android:defaultValue="false" />
<ListPreference
<com.limelight.preferences.LanguagePreference
android:key="list_languages"
android:title="@string/title_language_list"
android:entries="@array/language_names"
@@ -206,4 +212,19 @@
android:summary="@string/summary_enable_post_stream_toast"
android:defaultValue="false"/>
</PreferenceCategory>
<!--PreferenceCategory android:title="@string/category_help"
android:key="category_help">
<com.limelight.preferences.WebLauncherPreference
android:title="@string/title_setup_guide"
android:summary="@string/summary_setup_guide"
url="https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide"/>
<com.limelight.preferences.WebLauncherPreference
android:title="@string/title_troubleshooting"
android:summary="@string/summary_troubleshooting"
url="https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting"/>
<com.limelight.preferences.WebLauncherPreference
android:title="@string/title_privacy_policy"
android:summary="@string/summary_privacy_policy"
url="https://moonlight-stream.org/privacy.html"/>
</PreferenceCategory-->
</PreferenceScreen>
@@ -1,5 +0,0 @@
package com.limelight;
public class LimelightBuildProps {
public static final boolean ROOT_BUILD = false;
}
@@ -1,5 +0,0 @@
package com.limelight;
public class LimelightBuildProps {
public static final boolean ROOT_BUILD = true;
}
+2 -2
View File
@@ -2,13 +2,13 @@ version: 0.0.0.{build}
clone_depth: 1
image: Visual Studio 2019
image: Visual Studio 2022
before_build:
- 'git submodule update --init --recursive'
- 'mklink /D C:\android-sdk "C:\Program Files (x86)\Android\android-sdk"'
- 'set ANDROID_HOME=C:\android-sdk'
- 'set JAVA_HOME=C:\Program Files\Java\jdk11'
- 'set JAVA_HOME=C:\Program Files\Java\jdk17'
build_script:
- gradlew.bat build connectedCheck
+2 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath 'com.android.tools.build:gradle:7.2.1'
}
}
@@ -13,5 +13,6 @@ allprojects {
repositories {
mavenCentral()
google()
maven { url 'https://jitpack.io' }
}
}
+7 -1
View File
@@ -43,4 +43,10 @@ This file serves to document some of the decoder errata when using MediaCodec ha
- Affected decoders: Tegra X1 in Pixel C (but NOT in SHIELD TV darcy)
15. Some devices that support Android 11's FEATURE_LowLatency don't support it on their first compatible H.264/HEVC decoder. It is important to examine *all* decoders for FEATURE_LowLatency before deciding on one.
- Affected devices: Pixel 4 (c2.qti.avc.decoder.low_latency vs c2.qti.avc.decoder) and Galaxy S21 Exynos (OMX.Exynos.avc.dec [FEATURE_LowLatency] vs C2.Exynos.avc.decoder [no FEATURE_LowLatency])
- Affected devices: Pixel 4 (c2.qti.avc.decoder.low_latency vs c2.qti.avc.decoder) and Galaxy S21 Exynos (OMX.Exynos.avc.dec [FEATURE_LowLatency] vs C2.Exynos.avc.decoder [no FEATURE_LowLatency])
16. Some decoder have magic undocumented MediaFormat options to enable low latency prior to the introduction of KEY_LOW_LATENCY in Android 11. See MediaCodecHelper.java for info.
- Affected devices: MediaTek, Amlogic, Amazon, Qualcomm, Exynos, Huawei
17. Fire TV 3's Amlogic HEVC decoder doesn't produce any output frames without setting the magic "vdec-lowlatency" MediaFormat option
- Affected devices: Fire TV 3
@@ -1,4 +1,4 @@
Diese App streamt Spiele, Programme oder den kompletten Desktop von NVIDIA GameStream-kompatiebelen PCs mit NVIDIA GeForce Experience über das Lokale Netzwerk oder Internet. Gleichzeitig werden Maus-, Tastatur- und Gamepad-Eingaben von deinem Android Gerät zum PC übertragen.
Diese App streamt Spiele, Programme oder den kompletten Desktop von NVIDIA GameStream-kompatiebelen PCs oder <a href="https://github.com/SunshineStream/Sunshine">Sunshine Stream</a> über das Lokale Netzwerk oder Internet. Gleichzeitig werden Maus-, Tastatur- und Gamepad-Eingaben von deinem Android Gerät zum PC übertragen.
Die Streamingqualität kann aufgrund des verwendeten Android Geräts und der Netzwerkgegebenheiten variieren. HDR Unterstützung setzt ein HRD10-fähiges Gerät, eine GTX-1000-series GPU und HDR10-kompatibles Spiele voraus.
@@ -13,11 +13,15 @@ Die Streamingqualität kann aufgrund des verwendeten Android Geräts und der Net
* Kooperatives lokales Spielen mit bis zu 4 verbundenen Eingabegeräten
* Maussteuerung via Gamepad durch langes gedrückt halten der Starttaste
'''PC Anforderungen'''
'''PC Anforderungen mit NVIDIA'''
* NVIDIA GeForce GTX/RTX Serie GPU (''GT-Serie und AMD GPUs werden nicht von NVIDIA GameStream unterstützt'')
* Windows 7 oder neuer
* NVIDIA GeForce Experience (GFE) 2.2.2 oder neuer
'''PC Anforderungen mit SunshineStream'''
* Linux, MacOS, Windows
* AMD, Nvidia GPUs oder Software-Kodierung
'''Anleitung zur Schnellkonfiguration'''
# Stellen sie sicher dass GeForce Experience auf ihrem PC installiert ist. Aktivieren sie GameStream in den SHIELD Einstellungen.
# Wählen Sie den PC in Moonlight aus und tippen sie den PIN auf ihrem PC ein.
@@ -31,4 +35,4 @@ Besuchen die die vollständige Konfigurationsanleitung https://bit.ly/1skHFjN (E
* Über das Internet bzw. LTE zu streamen
* Eine Eingabegerät das direkt mit dem PC verbunden ist zu nutzen
* Den kompletten Desktop zu streamen
* Individuelle Apps zum Streamen hinzuzufügen
* Individuelle Apps zum Streamen hinzuzufügen
@@ -1 +1 @@
Spiele von deinem PC auf Android spielen (nur NVIDIA)
Spiele von deinem PC auf Android spielen (mit NVIDIA oder SunshineStream)
@@ -0,0 +1,6 @@
- Improved frame pacing when streaming 60 FPS on 120 Hz devices
- Reduced power usage when streaming below maximum display refresh rate
- Reintroduced previous frame pacing behavior as "Balanced with FPS limit" option
- Rewrote PC address detection logic to better handle some network configurations
- Fixed simultaneous mouse and on-screen controller input
- Updated community contributed translations from Weblate
@@ -0,0 +1,2 @@
- Fixed a few rare crashes and ANR bugs
- Updated community contributed translations from Weblate
@@ -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
@@ -0,0 +1,3 @@
- Significantly improved video latency on some MediaTek and Amlogic devices
- Implemented themed app icon support on Android 13
- Updated community contributed translations from Weblate
@@ -0,0 +1,7 @@
- HEVC will now be used automatically when using settings too high for the H.264 decoder
- Rumble is now supported on Shield controller when connected to Shield Android TV devices
- Reduced latency on Chromecast with Google TV by defaulting to H.264 for streaming at 1080p or below
- Fixed an issue that prevented HEVC from being used by default on some Shield Android TV devices
- Fixed an issue where parts of the UI were not visible on some Samsung devices
- Fixed handling of non-QWERTY keyboards using new Android 13 API
- Fixed stream unexpectedly entering PiP mode when a USB permission prompt appeared
@@ -0,0 +1,4 @@
- Implemented per-app language preferences on Android 13
- Improved automatic HEVC selection logic on older devices
- Fixed a few crashing bugs
- Updated community contributed translations from Weblate
@@ -0,0 +1,4 @@
- 3 finger tap can now dismiss the keyboard too
- Fixed crash on some Samsung devices when starting to stream
- Added meta key handling for DeX on newer Samsung devices
- Updated community contributed translations from Weblate
@@ -1,4 +1,4 @@
This app streams games, programs, or your full desktop from an NVIDIA GameStream-compatible PC on your local network or the Internet using NVIDIA GeForce Experience. Mouse, keyboard, and controller input is sent from your Android device to the PC.
This app streams games, programs, or your full desktop from an NVIDIA GameStream-compatible PC or <a href="https://github.com/SunshineStream/Sunshine">Sunshine Stream</a> on your local network or the Internet. Mouse, keyboard, and controller input is sent from your Android device to the PC.
Streaming performance may vary based on your client device and network setup. HDR requires an HDR10-capable device, GTX 1000-series GPU, and HDR10-enabled game.
@@ -14,11 +14,15 @@ Streaming performance may vary based on your client device and network setup. HD
* Local co-op with up to 4 connected controllers
* Mouse control via gamepad by long-pressing Start
'''PC Requirements'''
'''PC Requirements for NVIDIA'''
* NVIDIA GeForce GTX/RTX or NVIDIA Quadro GPU
* Windows 7 or later
* NVIDIA GeForce Experience or NVIDIA Quadro Experience installed
'''PC Requirements for SunshineStream'''
* Linux, MacOS, Windows
* AMD, Nvidia GPUs or Software encoding
'''Quick Setup Instructions'''
# Make sure GeForce/Quadro Experience is open on your PC. Turn on GameStream in the SHIELD settings page.
# Tap on the PC in Moonlight and type the PIN on your PC
@@ -32,4 +36,4 @@ See the full setup guide https://bit.ly/1skHFjN for:
* Streaming over the Internet or LTE
* Using a controller connected directly to your PC
* Streaming your full desktop
* Adding custom apps to stream
* Adding custom apps to stream
@@ -1 +1 @@
Play games from your PC on Android (NVIDIA-only)
Play games from your PC on Android (NVIDIA or SunshineStream)
Binary file not shown.
+1 -2
View File
@@ -1,6 +1,5 @@
#Mon Oct 12 13:42:18 CDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
Vendored
+186 -116
View File
@@ -1,79 +1,129 @@
#!/usr/bin/env bash
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn ( ) {
warn () {
echo "$*"
}
} >&2
die ( ) {
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -82,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -90,75 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+26 -27
View File
@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,20 +24,23 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,34 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell