Compare commits

...

193 Commits

Author SHA1 Message Date
Cameron Gutman 81c815840d Version 9.8.5 2021-04-03 12:39:02 -05:00
Cameron Gutman e9cd63dc5f Removed deprecated ProGuard option 2021-04-03 12:00:53 -05:00
Cameron Gutman 1ae8f67d93 Add Norwegian Bokmål option to the language list 2021-04-03 11:59:57 -05:00
Cameron Gutman daa1e10333 Merge remote-tracking branch 'origin/weblate' 2021-04-03 11:47:27 -05:00
Cameron Gutman a8a356e703 Add Amazon Luna support in Xbox 360 driver 2021-04-03 11:45:02 -05:00
Rener kaka ca440cc5dd Added translation using Weblate (Kurdish (Central)) 2021-04-02 21:11:43 +02:00
Øyvind Heddeland Instefjord 95a9fb4f62 Translated using Weblate (Norwegian Bokmål)
Currently translated at 90.1% (164 of 182 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/nb_NO/
2021-04-01 17:26:57 +02:00
Cameron Gutman 7db9e27112 Update NDK to r22b 2021-03-31 20:07:08 -05:00
Cameron Gutman 03bcdbe3f7 Update moonlight-common-c to pick up AMF HEVC parsing fix 2021-03-31 20:06:53 -05:00
Cameron Gutman f0762a6213 Version 9.8.4 2021-03-21 21:51:54 -05:00
Allan Nordhøy 67fbc6b3ad Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.4% (161 of 182 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/nb_NO/
2021-03-21 10:37:10 +01:00
Jorys Paulin d9662d7396 Translated using Weblate (French)
Currently translated at 95.6% (174 of 182 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/fr/
2021-03-21 10:37:10 +01:00
Nedelcu Constantin Marius Nedelcu 5ccbbf259d Translated using Weblate (Norwegian Bokmål)
Currently translated at 87.3% (159 of 182 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/nb_NO/
2021-03-21 06:29:37 +01:00
Allan Nordhøy 179c2f8723 Translated using Weblate (Norwegian Bokmål)
Currently translated at 87.3% (159 of 182 strings)

Translation: Moonlight Game Streaming/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/nb_NO/
2021-03-21 06:29:36 +01:00
Allan Nordhøy c76e0a40a7 Translated using Weblate (Norwegian Bokmål)
Currently translated at 59.3% (108 of 182 strings)

Translation: moonlight/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/nb_NO/
2021-03-21 02:34:36 +01:00
Artem 03407e528f Translated using Weblate (Russian)
Currently translated at 94.5% (172 of 182 strings)

Translation: moonlight/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/ru/
2021-03-21 02:34:36 +01:00
reloxx13 0c41d742cf Translated using Weblate (German)
Currently translated at 100.0% (182 of 182 strings)

Translation: moonlight/moonlight-android
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-android/de/
2021-03-21 02:34:36 +01:00
Allan Nordhøy ed2f471a4e Added translation using Weblate (Norwegian Bokmål) 2021-03-21 02:34:36 +01:00
Cameron Gutman 04efec101e Sync Xbox driver VIDs with Linux 5.11 2021-03-20 18:49:34 -05:00
Cameron Gutman a6c69012cc Add Weblate link and badge 2021-03-20 18:15:40 -05:00
Cameron Gutman 0045c54d8e Reapply a portion of 1d3e42f that should not have been reverted 2021-03-20 11:11:12 -05:00
Cameron Gutman 45436c006f Cancel a pending drag timer before setting a new one 2021-03-20 11:04:34 -05:00
Cameron Gutman cc183c0da8 Cancel a pending timer before setting a new one 2021-03-20 10:59:47 -05:00
Cameron Gutman 523f1df98b Remove superfluous simulated shift key up/down events
Setting the shift modifier flag alone is sufficient for current GFE versions
2021-03-20 10:38:15 -05:00
Cameron Gutman 5843dff278 Apply new fix for #840 2021-03-20 10:24:06 -05:00
Cameron Gutman 7f24f47978 Revert "Use a global set of modifier flags rather than per-device flags"
This reverts commit 1d3e42f92e.
2021-03-20 10:08:58 -05:00
Cameron Gutman b1f9fd459e Update NDK to r22 2021-03-20 10:07:08 -05:00
Cameron Gutman 48988eb785 Update AGP to 4.1.3 2021-03-20 10:06:51 -05:00
Cameron Gutman 0045a885b9 Migrate to AppVeyor 2021-03-03 19:56:20 -06:00
Cameron Gutman 0b57f60454 Migrate from travis-ci.org to travis-ci.com 2021-03-03 01:54:20 -06:00
Cameron Gutman f0857c7da2 Add issue template 2021-03-03 01:41:13 -06:00
Cameron Gutman 15faa2e841 Version 9.8.3 2021-03-02 18:48:12 -06:00
Cameron Gutman da103f7197 Don't use our built-in Switch Pro mapping on Android 10+ 2021-02-28 16:35:17 -06:00
Cameron Gutman 1d3e42f92e Use a global set of modifier flags rather than per-device flags
Fixes #840
2021-02-28 11:26:35 -06:00
Cameron Gutman 20ced841dd Handle pointer capture on SOURCE_TOUCHPAD devices 2021-02-27 15:48:37 -06:00
Cameron Gutman 54ebd0a796 Fix streaming in the Android 12 emulator 2021-02-27 15:46:59 -06:00
Cameron Gutman e636a7171b Add explicit android:exported value for Android 12 2021-02-27 15:46:23 -06:00
Cameron Gutman e8f847065b Version 9.8.2 2021-01-31 21:08:12 -06:00
Cameron Gutman 1c806bb572 Only use the virtual device as a gamepad if at least one gamepad is present 2021-01-31 19:42:41 -06:00
Cameron Gutman 963133598f Add hack to work around https://issuetracker.google.com/issues/163120692 2021-01-31 19:29:57 -06:00
Cameron Gutman fedaa74c47 Update AGP to 4.1.2 2021-01-31 19:28:42 -06:00
Cameron Gutman e322baf1d7 Version 9.8.1 2021-01-09 19:42:45 -06:00
Cameron Gutman 173a07cb59 Update ENet 2021-01-09 19:25:21 -06:00
Cameron Gutman 364afff860 Allow display resolution adjustment when streaming at a native resolution 2021-01-09 19:24:21 -06:00
Cameron Gutman 1b59e61b8e Include PC name in the PC context menu header 2020-12-31 16:42:26 -06:00
Cameron Gutman b1f453f7ba Charge time spent in the decode unit queue to the decoder rather than receive time 2020-12-31 16:35:49 -06:00
Cameron Gutman 175e842feb Support multiple native resolution options 2020-12-30 16:29:07 -06:00
Cameron Gutman d7a9a37a0e Version 9.8 2020-12-30 13:08:49 -06:00
Cameron Gutman 836b9240de Make native resolution warning more stern 2020-12-30 12:52:05 -06:00
Cameron Gutman bdac2df4b9 Fixed crash if we get a short read from the Xbox One controller 2020-12-24 11:59:33 -06:00
Cameron Gutman 57b507ad50 Use the game title as the context menu header 2020-12-24 11:50:59 -06:00
Cameron Gutman 35201b69f6 Add specific error text for an early termination 2020-12-24 11:32:10 -06:00
Cameron Gutman 0d138c26e9 Remove the native option if it duplicates a pre-existing resolution 2020-12-23 16:49:18 -06:00
Cameron Gutman b4a7393dca Normalize resolution orientation on pre-M devices 2020-12-23 16:46:07 -06:00
Cameron Gutman d86092df1a Update AGP to 4.1.1 2020-12-23 16:23:15 -06:00
Cameron Gutman b392d7f8e3 Add option to stream at device native resolution
Fixes #155
2020-12-23 16:17:06 -06:00
Cameron Gutman 7cc7953879 Display failing ports when the connection is unsuccessful 2020-12-23 14:30:24 -06:00
Cameron Gutman 7b26852a1f Use LiStringifyPortFlags() instead of coding it ourselves 2020-12-23 14:19:19 -06:00
Cameron Gutman f26b384697 Add a PC menu header to show PC status 2020-12-13 13:05:36 -06:00
Cameron Gutman ab0531aa76 Update moonlight-common-c submodule 2020-12-07 20:07:48 -06:00
Cameron Gutman 6873720d81 Fix build 2020-11-28 17:50:26 -06:00
Cameron Gutman 1e30c4a219 Remove "View Apps" and change "View Hidden Apps" to "View All Apps" 2020-11-28 17:28:17 -06:00
Cameron Gutman 0a0e3ff970 Don't trim XML strings
We should display the apps exactly as reported in GFE.
2020-11-21 17:09:34 -06:00
Cameron Gutman 5c42fd86a6 Update moonlight-common-c to avoid QoS on IPv6 2020-11-21 17:06:15 -06:00
Cameron Gutman 16cc829906 Fix some incorrect tap behavior on right clicks 2020-11-10 15:27:48 -06:00
Cameron Gutman 829e7cf33c Allow 2 finger scrolling in relative mode 2020-11-10 15:12:17 -06:00
Cameron Gutman 02bfa90417 Ignore movement from cancelled touches 2020-11-10 15:09:51 -06:00
Daniel 0b2466cf26 fixed some german typos in the UI (#894)
* fixed some german typos

* added more translations from english

* correct order

* typo

* typos
2020-11-10 10:48:57 -06:00
Cameron Gutman 9d8df04c5c Catch IllegalArgumentException when trying to insert an entry to TvContract.Channels.CONTENT_URI
HarmonyOS has FEATURE_LEANBACK but doesn't support this URI
2020-11-10 10:46:39 -06:00
Cameron Gutman 34a1697d50 Revert "Fix crash on HarmonyOS due to broken TV content provider APIs"
This reverts commit ce0b19605a.
2020-11-10 10:44:41 -06:00
Cameron Gutman 17cf711c3d Don't check brand when whitelisting ranchu for HEVC
HarmonyOS also uses "ranchu" as the hardware name, but doesn't use "google" as the brand name
2020-11-08 20:40:59 -06:00
Cameron Gutman ce0b19605a Fix crash on HarmonyOS due to broken TV content provider APIs
Fixes #883
2020-11-08 20:39:47 -06:00
Cameron Gutman 35bd9ecda3 Version 9.7.7 2020-10-28 21:14:26 -05:00
Cameron Gutman ca89849dd2 Update moonlight-common-c with QoS fix 2020-10-28 20:58:21 -05:00
Cameron Gutman ac1cb6d56b Version 9.7.6 2020-10-25 12:44:26 -05:00
Cameron Gutman dfbffea0fc Disable mouse acceleration on Nvidia Shield TV devices 2020-10-25 12:18:27 -05:00
Cameron Gutman 7ae9c993f1 Version 9.7.5 2020-10-19 23:10:22 -05:00
Cameron Gutman 91d739f8d6 Use the Nvidia button on Shield controllers as a Guide button 2020-10-18 21:14:53 -05:00
Cameron Gutman f0c625d85c Only emulate buttons that aren't physically present 2020-10-18 21:07:43 -05:00
Cameron Gutman b5f5e73076 Revert "Remove button emulation"
This reverts commit 092830ed07.
2020-10-18 20:45:11 -05:00
Cameron Gutman 1fb5eff7f1 Update dependencies 2020-10-18 20:38:13 -05:00
Cameron Gutman 5116cfd141 Fix inverted assert condition 2020-10-18 20:08:55 -05:00
Cameron Gutman e53a1f90b0 Correct some callers of time functions that expect monotonic clocks 2020-10-18 20:05:09 -05:00
Cameron Gutman 766c9628b0 Update moonlight-common-c with MTU test 2020-10-17 21:55:38 -05:00
Cameron Gutman 6a4abdd74c Update to AGP 4.1.0 2020-10-17 21:54:31 -05:00
Cameron Gutman fc8bc5ba1e Version 9.7.4 2020-10-09 20:06:39 -05:00
Cameron Gutman 0fde5d44c0 Enable HEVC for all Amlogic decoders on API 28+ 2020-10-06 21:40:18 -05:00
Cameron Gutman dc6b5a3d49 Update AGP to 4.0.2 2020-10-06 21:35:31 -05:00
Cameron Gutman 396522f249 Version 9.7.3 2020-09-07 11:50:03 -07:00
Cameron Gutman 86ab39e4ca Update moonlight-common-c for increased connection reliability 2020-09-06 18:18:41 -07:00
Cameron Gutman a4c9cb0e55 Version 9.7.2 2020-09-05 10:53:04 -07:00
Cameron Gutman e6c6feac10 Remove suffix_seekbar_bitrate string 2020-09-04 15:23:32 -07:00
Cameron Gutman ca0aee58ab Bump bitrate max to 150 Mbps 2020-09-04 15:15:44 -07:00
Cameron Gutman 6391f2c43d Use a 1 Mbps key increment for bitrate 2020-09-04 15:14:30 -07:00
Cameron Gutman 32171bb70c Display bitrate in Mbps 2020-09-04 15:11:24 -07:00
Cameron Gutman fd6675a3a3 Populate the external IP address when a PC is added manually using an RFC 1918 IPv4 address 2020-08-30 18:39:25 -07:00
bubuleur 9d883978a8 Mise à jour langue française (#865)
* Mise à jour langue française

* Update strings.xml

* Update strings.xml

* Update French

* Update \'
2020-08-30 13:32:05 -07:00
Cameron Gutman 1aae65575c Add warning if no key frames can be received in 10 seconds 2020-08-29 21:27:44 -07:00
Udalov Nikita c5d58e1aab Update Russian translations (#872)
* Update Russian translations
2020-08-29 19:17:45 -07:00
Cameron Gutman 56394471fa Don't hide games immediately 2020-08-11 18:47:01 -07:00
Cameron Gutman 4cae6959df Update inconclusive test result text 2020-08-09 17:16:51 -07:00
Cameron Gutman f02d7b4516 Version 9.7.1 2020-08-09 17:13:44 -07:00
Cameron Gutman f5c83112df Update gitignore 2020-08-09 16:47:16 -07:00
Cameron Gutman a413dc81c1 Avoid doing client connectivity tests on the main thread 2020-08-09 16:22:50 -07:00
Cameron Gutman c9eddab191 Remove UDP 7 and add UDP 47009 for WoL 2020-08-09 14:40:44 -07:00
Cameron Gutman ec1268bd71 Version 9.7 2020-08-09 12:08:50 -07:00
Cameron Gutman 22eb2b5823 Always show the network test option 2020-08-06 22:11:22 -07:00
Cameron Gutman 9669da026f Test network when the connection terminates due to lack of video traffic 2020-08-06 22:01:45 -07:00
Cameron Gutman 7b14e54eab Test network connectivity when adding a PC fails 2020-08-06 20:43:17 -07:00
Cameron Gutman 6b30ee4593 Change connection test domain name 2020-08-06 20:31:15 -07:00
Cameron Gutman 17c47a15da Improve display mode selection algorithm
- Allow the refresh rate to drop if it results in a better match for the stream frame rate
- Allow the resolution to drop for > 60 FPS streams to allow matching a higher refresh rate
2020-08-06 20:14:56 -07:00
Cameron Gutman 8f55517236 Prevent assert when control stream connection fails 2020-08-06 19:13:50 -07:00
Cameron Gutman 41ad086dfa Upgrade to AGP 4.0.1 2020-08-06 19:07:32 -07:00
Cameron Gutman e19ef7dcae Remove redundant Cancel option in app grid menu 2020-08-04 02:09:33 -07:00
Cameron Gutman f361265d70 Add automatic network test for failed connection stages 2020-08-01 22:56:32 -07:00
Cameron Gutman ef72e3ef77 Only show the option to hide the app if it's not running or already hidden 2020-08-01 22:48:22 -07:00
Cameron Gutman 770f1a1ca0 Add network connection test 2020-08-01 22:19:40 -07:00
Cameron Gutman e8fc91191f Add the option to hide games in the app list
Fixes #640
2020-08-01 18:20:39 -07:00
Cameron Gutman 105ad3317d Pass parent view into grid adapters 2020-08-01 17:52:55 -07:00
Cameron Gutman 22bf4775cd Enable poll() in ENet 2020-07-27 00:12:26 -07:00
Cameron Gutman 5c6be7969a Disable max operating rate trick on all Snapdragon 765G devices
Fixes #783
2020-07-26 22:39:10 -07:00
Cameron Gutman c6e23f4be2 Update common-c with client connectivity test and select() replacement 2020-07-26 22:06:46 -07:00
Cameron Gutman 05547c22ec Use SecureRandom for PINs 2020-07-12 12:16:11 -07:00
Cameron Gutman cc7ac79fa6 Version 9.6.4 2020-07-10 18:31:41 -07:00
Cameron Gutman 4c5c27dfc1 Re-enable the max operating rate trick on Android 10 except on the Mi 10 Lite 5G
It still provides nice performance gains on Pixel 2 running Android 10
2020-07-10 18:29:29 -07:00
Cameron Gutman 4aabfbd52e Add missing jlong cast to fix Lint warning 2020-07-07 01:10:10 -05:00
Cameron Gutman 6eab842361 Fix Lint error due to extra translated strings 2020-07-07 01:05:10 -05:00
a6969 b729dfd702 Added Ukrainian language (#857)
* Added Ukrainian language strings.xml

Translated the application into Ukrainian language.
2020-07-07 00:59:42 -05:00
Cameron Gutman 6366840781 Update common-c to remove FEC validation assert that fails on GFE 3.20.4 2020-07-07 00:58:44 -05:00
Cameron Gutman 704a2ee90b Propagate exceptions caused by GFE response parsing errors 2020-07-07 00:57:37 -05:00
Cameron Gutman 484be9bfe6 Wrap and propagate unexpected exceptions 2020-07-07 00:52:11 -05:00
Cameron Gutman a99e070c26 Fix missing return causing invalid parameters to be passed to LiStartConnection() 2020-07-07 00:47:12 -05:00
Cameron Gutman bf803f88af Refactor TLS initialization code 2020-07-06 02:32:06 -05:00
Cameron Gutman 9af6febca5 Fix pairing issue due to picking up a final local variable instead of a class member 2020-07-06 02:30:49 -05:00
Cameron Gutman 0101d0a1bd Fix TLS error when connecting to GFE 3.20.4 on Android 4.x 2020-07-06 01:44:35 -05:00
Cameron Gutman 266874609d Fix hostname validation for CA-issued certificates 2020-07-04 20:09:06 -05:00
Cameron Gutman 2ba7feedfc Fix several Lint warnings 2020-07-04 15:41:41 -05:00
Cameron Gutman 43c67b4939 Avoid using max operating rate on Android Q and non-Qualcomm devices 2020-07-01 11:26:40 -05:00
Cameron Gutman 2d9915e43a Enable GWP-ASan on Android 11 2020-07-01 11:07:53 -05:00
Cameron Gutman 2329b41bce Rethrow the original validation error if the cert isn't pinned or self-signed 2020-06-29 11:29:33 -07:00
Cameron Gutman 536496184e Use the default X509TrustManager to validate non-pinned certificates
This allows the certificate to be rotated without re-adding the PC.
2020-06-29 11:20:14 -07:00
Cameron Gutman 429c32477c Version 9.6.1 2020-06-25 22:15:24 -07:00
Cameron Gutman f5d51b2061 Disable PiP option on Fire OS due to Amazon guidelines 2020-06-24 17:26:58 -07:00
Zero O 2ad1aaa277 Update strings.xml (#850)
update translation
2020-06-24 17:21:54 -07:00
Zero O 3afd32dbc1 Update strings.xml (#851)
update translation
2020-06-24 17:21:45 -07:00
Cameron Gutman 092830ed07 Remove button emulation
It was never well documented to users and it really only makes sense
with much older controllers that don't have Start or Select buttons.
2020-06-23 22:00:56 -07:00
Cameron Gutman d118a6d3ff Prevent edges of analog sticks from being clipped 2020-06-23 21:48:50 -07:00
Cameron Gutman fe97ffdc2f Slightly reduce size of analog sticks to allow a gap before the edge of the screen
This reduces false analog stick releases caused when the finger goes off the display's touch area.
2020-06-23 21:36:33 -07:00
Cameron Gutman 964d2ce59c Version 9.6 2020-06-18 23:11:35 -07:00
Cameron Gutman dc52684cbc Update moonlight-common-c to fix QoS-related connection issues 2020-06-12 22:08:01 -07:00
Cameron Gutman 191bedc56f Improve behavior and description of small box art checkbox 2020-06-11 22:01:48 -07:00
Cameron Gutman 47b2ace7fd New app grid UI 2020-06-11 21:51:07 -07:00
Cameron Gutman 9fb7359a3e Use startAnimation() instead of setAnimation() 2020-06-11 21:47:28 -07:00
Cameron Gutman 4a5de26406 Remove the small PC grid UI 2020-06-11 21:32:39 -07:00
Cameron Gutman 6fa18e126f Remove list view in preparation for grid redesign 2020-06-11 21:21:37 -07:00
Cameron Gutman 1149002e0c Improve PC and game details dialogs 2020-06-11 20:36:59 -07:00
Cameron Gutman d704cb0b50 Use SoftReferences instead of WeakReferences for the eviction cache 2020-06-11 19:10:43 -07:00
Cameron Gutman d59e5ae9cf Store the original bitmap dimensions for the box art 2020-06-11 19:08:25 -07:00
Cameron Gutman 4587c1550d Cache WeakReferences to our box art bitmaps after LRU evictions 2020-06-10 23:13:07 -07:00
Cameron Gutman b5bd329ada Fade in the box art when loading from the network 2020-06-10 22:52:37 -07:00
Cameron Gutman beccd7a4ac Fade in the box art as we load it 2020-06-10 22:37:54 -07:00
Cameron Gutman 61262fa939 Refactor grid adapters for new grid UI 2020-06-10 22:13:02 -07:00
Cameron Gutman 7c6b006631 Remove OSC rumble option if a vibrator isn't present 2020-06-10 21:15:21 -07:00
Cameron Gutman dbd149354a Change "crashes" to "instability" 2020-06-10 21:09:24 -07:00
Cameron Gutman 4306ba5004 Add a mapping for the Nintendo Switch Pro controller
Fixes #842
2020-06-10 21:05:08 -07:00
Cameron Gutman 6de370b82f Update for Android 11 2020-06-10 20:31:32 -07:00
Cameron Gutman 45781666b8 Disable the latency toast by default
It causes crashes on the MiBox
2020-06-06 18:24:34 -07:00
Cameron Gutman 538231eb6f Attempt to appease Amazon content review 2020-06-06 17:53:09 -07:00
Cameron Gutman eb74f87f2c Move PiP and unlock FPS options out of basic settings 2020-06-06 17:44:38 -07:00
Cameron Gutman 59d71ffdcf Don't show PiP option on devices where PiP is disabled 2020-06-06 17:32:26 -07:00
Cameron Gutman d1b93d4011 Remove vibration option if the device can't vibrate 2020-06-06 17:25:01 -07:00
Cameron Gutman d8ddf2e740 Update NDK for Travis CI 2020-05-28 22:19:58 -07:00
Cameron Gutman 581327dc8e Improve resolution preference storage to remove 16:9 assumptions 2020-05-28 22:05:57 -07:00
Cameron Gutman 76e4512a0c Update for Android Studio 4.0 2020-05-28 21:50:28 -07:00
Cameron Gutman efdd55beca Add Download links 2020-05-27 19:47:30 -07:00
Cameron Gutman 2c115649b9 Update README 2020-05-27 19:44:56 -07:00
Cameron Gutman 2ddcc31a93 Update metadata for Quadro streaming 2020-05-27 18:34:59 -07:00
Cameron Gutman 3bcce5b749 Version 9.5.1 2020-05-27 18:34:26 -07:00
Cameron Gutman 80dac27214 Update moonlight-common-c 2020-05-27 00:02:33 -07:00
Cameron Gutman 4a1177d048 Use a better workaround for the GFE 3.20.3 high FPS bug 2020-05-25 19:28:00 -07:00
Cameron Gutman 4725d8f270 Revert "Disable SOPS for streams over 60 FPS for GFE 3.20.3"
This reverts commit 63072aa8e1.
2020-05-25 19:24:33 -07:00
Zero O 07b3528515 Update strings.xml (#833)
update translation
2020-05-20 19:27:47 -07:00
Zero O d2d1b1ea26 Update strings.xml (#834)
update translation
2020-05-20 19:27:32 -07:00
Cameron Gutman 232b897abc Version 9.5 2020-05-16 21:40:41 -07:00
Cameron Gutman efd076bc6c Ignore absolute touch events from outside the stream view 2020-05-12 00:20:07 -07:00
Cameron Gutman cc877480ff Add an option for absolute touch mode 2020-05-11 23:53:49 -07:00
Christoph Papke 363145a284 Optimize button mapping for 8BitDo controllers (#826)
* Optimize button mapping for 8BitDo controllers #825
2020-05-05 16:04:31 -07:00
Cameron Gutman 755571ad33 Switch on-screen control buttons when flip face buttons is enabled 2020-05-04 22:23:03 -07:00
Eero Kelly 39edb55721 Add option to invert A/B X/Y (#824)
* Add option to invert A/B X/Y

* Remove redundant prefConfig
2020-05-04 22:10:35 -07:00
Cameron Gutman 15aa7ecc2e Add a friendly error message when no video traffic is received 2020-05-01 21:54:26 -07:00
Cameron Gutman ce9e91153e Add special error text for the -1 launch error code 2020-04-25 16:10:44 -07:00
Cameron Gutman 9ee0a46606 Add new init packet to switch out of BT mode 2020-04-24 17:47:31 -07:00
Cameron Gutman 20dc351f4c Fix parsing rare GFE status code of 0xFFFFFFFF 2020-04-23 18:47:01 -07:00
105 changed files with 2784 additions and 1108 deletions
+48
View File
@@ -0,0 +1,48 @@
---
name: Bug report
about: Follow the troubleshooting guide before reporting a bug
---
**READ ME FIRST!**
If you're here because something basic is not working (like gamepad input, video, or similar), it's probably something specific to your setup, so make sure you've gone through the Troubleshooting Guide first: https://github.com/moonlight-stream/moonlight-docs/wiki/Troubleshooting
If you still have trouble with basic functionality after following the guide, join our Discord server where there are many other volunteers who can help (or direct you back here if it looks like a Moonlight bug after all). https://moonlight-stream.org/discord
**Describe the bug**
A clear and concise description of what the bug is.
**Steps to reproduce**
Any special steps that are required for the bug to appear.
**Screenshots**
If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots.
**Affected games**
List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there.
**Other Moonlight clients**
- Does the issue occur when using Moonlight on PC or iOS?
**Moonlight settings (please complete the following information)**
- Have any settings been adjusted from defaults?
- If so, which settings have been changed?
- Does the problem still occur after reverting settings back to default?
**Gamepad-related issues (please complete if problem is gamepad-related)**
- Do you have any gamepads connected to your host PC directly?
- If gamepad input is not working, does it work if you use Moonlight's on-screen controls?
- Does the problem still remain if you stream the desktop and use https://html5gamepad.com to test your gamepad?
- Instructions for streaming the desktop can be found here: https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide
**Device details (please complete the following information)**
- Android version: [e.g. Android 10]
- Device model: [e.g. Samsung Galaxy S21]
**Server PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809]
- GeForce Experience version: [e.g. 3.16.0.140]
- Nvidia GPU driver: [e.g. 417.35]
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
**Additional context**
Anything else you think may be relevant to the issue or special about your specific setup.
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+17
View File
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+1
View File
@@ -3,6 +3,7 @@
*.ap_
*.aab
output.json
output-metadata.json
out/
# files for the dex VM
-18
View File
@@ -1,18 +0,0 @@
language: android
dist: trusty
git:
depth: 1
android:
components:
- tools
- platform-tools
- build-tools-29.0.3
- android-29
before_install:
- sdkmanager --list
install:
- yes | sdkmanager "ndk;20.0.5594570"
+12 -40
View File
@@ -1,56 +1,28 @@
# Moonlight Android
[![Travis CI Status](https://travis-ci.org/moonlight-stream/moonlight-android.svg?branch=master)](https://travis-ci.org/moonlight-stream/moonlight-android)
[![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](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
We reverse engineered the Shield streaming software and created a version that can be run on any Android device.
[Moonlight for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
Moonlight will allow you to stream your full collection of games from your Windows PC to your Android device,
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.
Check our [wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed information or a troubleshooting guide.
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [iOS/tvOS client](https://github.com/moonlight-stream/moonlight-ios).
## Features
You can follow development on our [Discord server](https://moonlight-stream.org/discord) and help translate Moonlight into your language on [Weblate](https://hosted.weblate.org/projects/moonlight/moonlight-android/).
* Streams any of your games from your PC to your Android device
* Full gamepad support for MOGA, Xbox 360, PS3, OUYA, and Shield
* Automatically finds GameStream-compatible PCs on your network
## Installation
* Download and install Moonlight for Android from
[Google Play](https://play.google.com/store/apps/details?id=com.limelight), [F-Droid](https://f-droid.org/packages/com.limelight/), [Amazon App Store](http://www.amazon.com/gp/product/B00JK4MFN2), or directly from the [releases page](https://github.com/moonlight-stream/moonlight-android/releases)
* Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC
## Requirements
* [GameStream compatible](http://shield.nvidia.com/play-pc-games/) computer with an NVIDIA GeForce GTX 600 series or higher desktop or mobile GPU (GT-series and AMD GPUs not supported)
* Android device running 4.1 (Jelly Bean) or higher
* High-end wireless router (802.11n dual-band recommended)
## Usage
* Turn on GameStream in the GFE settings
* If you are connecting from outside the same network, turn on internet
streaming
* When on the same network as your PC, open Moonlight and tap on your PC in the list
* Accept the pairing confirmation on your PC and add the PIN if needed
* Tap your PC again to view the list of apps to stream
* Play games!
## Contribute
This project is being actively developed at [XDA Developers](http://forum.xda-developers.com/showthread.php?t=2505510)
1. Fork us
2. Write code
3. Send Pull Requests
## Downloads
* [Google Play Store](https://play.google.com/store/apps/details?id=com.limelight)
* [Amazon App Store](https://www.amazon.com/gp/product/B00JK4MFN2)
* [F-Droid](https://f-droid.org/packages/com.limelight)
* [APK](https://github.com/moonlight-stream/moonlight-android/releases)
## Building
* Install Android Studio and the Android NDK
* Run git submodule update --init --recursive from within moonlight-android/
* In moonlight-android/, create a file called local.properties. Add an ndk.dir= property to the local.properties file and set it equal to your NDK directory.
* Build the APK using Android Studio
* Build the APK using Android Studio or gradle
## Authors
+9 -8
View File
@@ -1,14 +1,16 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
ndkVersion "22.1.7171670"
compileSdkVersion 30
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
targetSdkVersion 30
versionName "9.2.1"
versionCode = 223
versionName "9.8.5"
versionCode = 256
}
flavorDimensions "root"
@@ -65,7 +67,6 @@ android {
applicationIdSuffix ".debug"
minifyEnabled true
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
@@ -114,10 +115,10 @@ android {
}
dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
implementation 'org.bouncycastle:bcprov-jdk15on:1.66'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.66'
implementation 'org.jcodec:jcodec:0.2.3'
implementation 'com.squareup.okhttp3:okhttp:3.12.10'
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.squareup.okio:okio:1.17.5'
implementation 'org.jmdns:jmdns:3.5.5'
}
+13 -1
View File
@@ -42,6 +42,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:installLocation="auto"
android:gwpAsanMode="always"
android:theme="@style/AppTheme">
<provider
android:name=".PosterContentProvider"
@@ -63,6 +64,7 @@
<activity
android:name=".PcView"
android:exported="true"
android:resizeableActivity="true"
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
<intent-filter>
@@ -120,10 +122,20 @@
android:resizeableActivity="true"
android:launchMode="singleTask"
android:excludeFromRecents="true"
android:theme="@style/StreamTheme">
android:theme="@style/StreamTheme"
android:preferMinimalPostProcessing="true">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.AppView" />
<!-- Special metadata for NVIDIA Shield devices to prevent input buffering
and most importantly, opt out of mouse acceleration while streaming -->
<meta-data
android:name="com.nvidia.immediateInput"
android:value="true" />
<meta-data
android:name="com.nvidia.rawCursorInput"
android:value="true" />
</activity>
<service
+63 -14
View File
@@ -2,6 +2,7 @@ package com.limelight;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashSet;
import java.util.List;
import com.limelight.computers.ComputerManagerListener;
@@ -26,6 +27,7 @@ import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -59,17 +61,22 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
private int lastRunningAppId;
private boolean suspendGridUpdates;
private boolean inForeground;
private boolean showHiddenApps;
private HashSet<Integer> hiddenAppIds = new HashSet<>();
private final static int START_OR_RESUME_ID = 1;
private final static int QUIT_ID = 2;
private final static int CANCEL_ID = 3;
private final static int START_WITH_QUIT = 4;
private final static int VIEW_DETAILS_ID = 5;
private final static int CREATE_SHORTCUT_ID = 6;
private final static int HIDE_APP_ID = 7;
public final static String HIDDEN_APPS_PREF_FILENAME = "HiddenApps";
public final static String NAME_EXTRA = "Name";
public final static String UUID_EXTRA = "UUID";
public final static String NEW_PAIR_EXTRA = "NewPair";
public final static String SHOW_HIDDEN_APPS_EXTRA = "ShowHiddenApps";
private ComputerManagerService.ComputerManagerBinder managerBinder;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@@ -98,13 +105,16 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
try {
appGridAdapter = new AppGridAdapter(AppView.this,
PreferenceConfiguration.readPreferences(AppView.this),
computer, localBinder.getUniqueId());
computer, localBinder.getUniqueId(),
showHiddenApps);
} catch (Exception e) {
e.printStackTrace();
finish();
return;
}
appGridAdapter.updateHiddenApps(hiddenAppIds, true);
// Now make the binder visible. We must do this after appGridAdapter
// is set to prevent us from reaching updateUiWithServerinfo() and
// touching the appGridAdapter prior to initialization.
@@ -285,8 +295,14 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
UiHelper.notifyNewRootView(this);
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
uuidString = getIntent().getStringExtra(UUID_EXTRA);
SharedPreferences hiddenAppsPrefs = getSharedPreferences(HIDDEN_APPS_PREF_FILENAME, MODE_PRIVATE);
for (String hiddenAppIdStr : hiddenAppsPrefs.getStringSet(uuidString, new HashSet<String>())) {
hiddenAppIds.add(Integer.parseInt(hiddenAppIdStr));
}
String computerName = getIntent().getStringExtra(NAME_EXTRA);
TextView label = findViewById(R.id.appListText);
@@ -298,6 +314,21 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
Service.BIND_AUTO_CREATE);
}
private void updateHiddenApps(boolean hideImmediately) {
HashSet<String> hiddenAppIdStringSet = new HashSet<>();
for (Integer hiddenAppId : hiddenAppIds) {
hiddenAppIdStringSet.add(hiddenAppId.toString());
}
getSharedPreferences(HIDDEN_APPS_PREF_FILENAME, MODE_PRIVATE)
.edit()
.putStringSet(uuidString, hiddenAppIdStringSet)
.apply();
appGridAdapter.updateHiddenApps(hiddenAppIds, hideImmediately);
}
private void populateAppGridWithCache() {
try {
// Try to load from cache
@@ -355,9 +386,12 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
AppObject selectedApp = (AppObject) appGridAdapter.getItem(info.position);
menu.setHeaderTitle(selectedApp.app.getAppName());
if (lastRunningAppId != 0) {
if (lastRunningAppId == selectedApp.app.getAppId()) {
menu.add(Menu.NONE, START_OR_RESUME_ID, 1, getResources().getString(R.string.applist_menu_resume));
@@ -365,10 +399,17 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}
else {
menu.add(Menu.NONE, START_WITH_QUIT, 1, getResources().getString(R.string.applist_menu_quit_and_start));
menu.add(Menu.NONE, CANCEL_ID, 2, getResources().getString(R.string.applist_menu_cancel));
}
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 3, getResources().getString(R.string.applist_menu_details));
// Only show the hide checkbox if this is not the currently running app or it's already hidden
if (lastRunningAppId != selectedApp.app.getAppId() || selectedApp.isHidden) {
MenuItem hideAppItem = menu.add(Menu.NONE, HIDE_APP_ID, 3, getResources().getString(R.string.applist_menu_hide_app));
hideAppItem.setCheckable(true);
hideAppItem.setChecked(selectedApp.isHidden);
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 4, getResources().getString(R.string.applist_menu_details));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Only add an option to create shortcut if box art is loaded
@@ -379,7 +420,7 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
BitmapDrawable drawable = (BitmapDrawable)appImageView.getDrawable();
if (drawable != null && drawable.getBitmap() != null) {
// We have a bitmap loaded too
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 4, getResources().getString(R.string.applist_menu_scut));
menu.add(Menu.NONE, CREATE_SHORTCUT_ID, 5, getResources().getString(R.string.applist_menu_scut));
}
}
}
@@ -430,12 +471,20 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
}, null);
return true;
case CANCEL_ID:
case VIEW_DETAILS_ID:
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details), app.app.toString(), false);
return true;
case VIEW_DETAILS_ID:
Dialog.displayDialog(AppView.this, getResources().getString(R.string.title_details),
getResources().getString(R.string.applist_details_id) + " " + app.app.getAppId(), false);
case HIDE_APP_ID:
if (item.isChecked()) {
// Transitioning hidden to shown
hiddenAppIds.remove(app.app.getAppId());
}
else {
// Transitioning shown to hidden
hiddenAppIds.add(app.app.getAppId());
}
updateHiddenApps(false);
return true;
case CREATE_SHORTCUT_ID:
@@ -565,9 +614,8 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
@Override
public int getAdapterFragmentLayoutId() {
return PreferenceConfiguration.readPreferences(this).listMode ?
R.layout.list_view : (PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
R.layout.app_grid_view_small : R.layout.app_grid_view);
return PreferenceConfiguration.readPreferences(AppView.this).smallIconMode ?
R.layout.app_grid_view_small : R.layout.app_grid_view;
}
@Override
@@ -592,9 +640,10 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
listView.requestFocus();
}
public class AppObject {
public static class AppObject {
public final NvApp app;
public boolean isRunning;
public boolean isHidden;
public AppObject(NvApp app) {
if (app == null) {
+181 -86
View File
@@ -6,9 +6,11 @@ import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.KeyboardTranslator;
import com.limelight.binding.input.capture.InputCaptureManager;
import com.limelight.binding.input.capture.InputCaptureProvider;
import com.limelight.binding.input.TouchContext;
import com.limelight.binding.input.touch.AbsoluteTouchContext;
import com.limelight.binding.input.touch.RelativeTouchContext;
import com.limelight.binding.input.driver.UsbDriverService;
import com.limelight.binding.input.evdev.EvdevListener;
import com.limelight.binding.input.touch.TouchContext;
import com.limelight.binding.input.virtual_controller.VirtualController;
import com.limelight.binding.video.CrashListener;
import com.limelight.binding.video.MediaCodecDecoderRenderer;
@@ -28,6 +30,7 @@ import com.limelight.ui.GameGestures;
import com.limelight.ui.StreamView;
import com.limelight.utils.Dialog;
import com.limelight.utils.NetHelper;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.ShortcutHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
@@ -61,6 +64,7 @@ import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.View.OnGenericMotionListener;
@@ -210,11 +214,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
prefConfig = PreferenceConfiguration.readPreferences(this);
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && prefConfig.stretchVideo) {
if (prefConfig.stretchVideo) {
// Allow the activity to layout under notches if the fill-screen option
// was turned on by the user
getWindow().getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
getWindow().getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getWindow().getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
}
// Listen for events on the game surface
@@ -474,9 +484,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize touch contexts
for (int i = 0; i < touchContextMap.length; i++) {
touchContextMap[i] = new TouchContext(conn, i,
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
streamView);
if (!prefConfig.touchscreenTrackpad) {
touchContextMap[i] = new AbsoluteTouchContext(conn, i, streamView);
}
else {
touchContextMap[i] = new RelativeTouchContext(conn, i,
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
streamView);
}
}
// Use sustained performance mode on N+ to ensure consistent
@@ -583,6 +598,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// We can't guarantee the state of modifiers keys which may have
// lifted while focus was not on us. Clear the modifier state.
this.modifierFlags = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Capture is lost when focus is lost, so it must be requested again
// when focus is regained.
@@ -602,27 +621,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
// FIXME: Remove when Android R SDK is finalized
private static void setPreferMinimalPostProcessingWithReflection(WindowManager.LayoutParams windowLayoutParams, boolean isPreferred) {
// Build.VERSION.PREVIEW_SDK_INT was added in M
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && Build.VERSION.PREVIEW_SDK_INT == 0) {
// Don't attempt this reflection unless on Android R Developer Preview
return;
}
}
else {
return;
}
try {
Field field = windowLayoutParams.getClass().getDeclaredField("preferMinimalPostProcessing");
field.set(windowLayoutParams, isPreferred);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
private boolean isRefreshRateGoodMatch(float refreshRate) {
return refreshRate >= prefConfig.fps &&
Math.round(refreshRate) % prefConfig.fps <= 3;
}
private float prepareDisplayForRendering() {
@@ -633,41 +634,61 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// On M, we can explicitly set the optimal display mode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Display.Mode bestMode = display.getMode();
boolean isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
for (Display.Mode candidate : display.getSupportedModes()) {
boolean refreshRateOk = candidate.getRefreshRate() >= bestMode.getRefreshRate();
boolean resolutionOk = candidate.getPhysicalWidth() >= bestMode.getPhysicalWidth() &&
candidate.getPhysicalHeight() >= bestMode.getPhysicalHeight() &&
candidate.getPhysicalWidth() <= 4096;
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
candidate.getPhysicalHeight() < bestMode.getPhysicalHeight();
boolean resolutionFitsStream = candidate.getPhysicalWidth() >= prefConfig.width &&
candidate.getPhysicalHeight() >= prefConfig.height;
LimeLog.info("Examining display mode: "+candidate.getPhysicalWidth()+"x"+
candidate.getPhysicalHeight()+"x"+candidate.getRefreshRate());
// On non-4K streams, we force the resolution to never change
if (prefConfig.width < 3840) {
if (candidate.getPhysicalWidth() > 4096 && prefConfig.width <= 4096) {
// Avoid resolutions options above 4K to be safe
continue;
}
// On non-4K streams, we force the resolution to never change unless it's above
// 60 FPS, which may require a resolution reduction due to HDMI bandwidth limitations,
// or it's a native resolution stream.
if (prefConfig.width < 3840 && prefConfig.fps <= 60 && !isNativeResolutionStream) {
if (display.getMode().getPhysicalWidth() != candidate.getPhysicalWidth() ||
display.getMode().getPhysicalHeight() != candidate.getPhysicalHeight()) {
continue;
}
}
// Ensure the frame rate stays around 60 Hz for <= 60 FPS streams
if (prefConfig.fps <= 60) {
if (candidate.getRefreshRate() >= 63) {
// Make sure the resolution doesn't regress unless if it's over 60 FPS
// where we may need to reduce resolution to achieve the desired refresh rate.
if (resolutionReduced && !(prefConfig.fps > 60 && resolutionFitsStream)) {
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())) {
continue;
}
}
// Make sure the refresh rate doesn't regress
if (!refreshRateOk) {
continue;
}
// Make sure the resolution doesn't regress
if (!resolutionOk) {
continue;
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
// We didn't have a good match and this match isn't good either, so just don't
// reduce the refresh rate.
if (refreshRateReduced) {
continue;
}
} else {
// We didn't have a good match and this match is good. Prefer this refresh rate
// even if it reduces the refresh rate. Lowering the refresh rate can be beneficial
// when streaming a 60 FPS stream on a 90 Hz device. We want to select 60 Hz to
// match the frame rate even if the active display mode is 90 Hz.
}
bestMode = candidate;
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
}
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
@@ -678,9 +699,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float bestRefreshRate = display.getRefreshRate();
for (float candidate : display.getSupportedRefreshRates()) {
if (candidate > bestRefreshRate) {
LimeLog.info("Examining refresh rate: "+candidate);
LimeLog.info("Examining refresh rate: "+candidate);
if (candidate > bestRefreshRate) {
// Ensure the frame rate stays around 60 Hz for <= 60 FPS streams
if (prefConfig.fps <= 60) {
if (candidate >= 63) {
@@ -702,7 +723,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
// Enable HDMI ALLM (game mode) on Android R
setPreferMinimalPostProcessingWithReflection(windowLayoutParams, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
windowLayoutParams.preferMinimalPostProcessing = true;
}
// Apply the display mode change
getWindow().setAttributes(windowLayoutParams);
@@ -858,34 +881,36 @@ public class Game extends Activity implements SurfaceHolder.Callback,
displayedFailureDialog = true;
stopConnection();
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
String message = null;
if (averageEndToEndLat > 0) {
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms";
if (averageDecoderLat > 0) {
message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)";
if (prefConfig.enableLatencyToast) {
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
String message = null;
if (averageEndToEndLat > 0) {
message = getResources().getString(R.string.conn_client_latency)+" "+averageEndToEndLat+" ms";
if (averageDecoderLat > 0) {
message += " ("+getResources().getString(R.string.conn_client_latency_hw)+" "+averageDecoderLat+" ms)";
}
}
else if (averageDecoderLat > 0) {
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
}
}
else if (averageDecoderLat > 0) {
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
}
// Add the video codec to the post-stream toast
if (message != null) {
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
message += " [H.265 HDR]";
// Add the video codec to the post-stream toast
if (message != null) {
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
message += " [H.265 HDR]";
}
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
message += " [H.265]";
}
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
message += " [H.264]";
}
}
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
message += " [H.265]";
}
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
message += " [H.264]";
}
}
if (message != null) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
if (message != null) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
// Clear the tombstone count if we terminated normally
@@ -975,8 +1000,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return false;
}
private static byte getModifierState(KeyEvent event) {
byte modifier = 0;
// We cannot simply use modifierFlags for all key event processing, because
// some IMEs will not generate real key events for pressing Shift. Instead
// they will simply send key events with isShiftPressed() returning true,
// and we will need to send the modifier flag ourselves.
private byte getModifierState(KeyEvent event) {
// Start with the global modifier state to ensure we cover the case
// detailed in https://github.com/moonlight-stream/moonlight-android/issues/840
byte modifier = getModifierState();
if (event.isShiftPressed()) {
modifier |= KeyboardPacket.MODIFIER_SHIFT;
}
@@ -1057,7 +1088,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
byte modifiers = getModifierState(event);
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_DOWN, modifiers);
}
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers);
}
@@ -1123,9 +1153,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
modifiers |= KeyboardPacket.MODIFIER_SHIFT;
}
conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers);
if (KeyboardTranslator.needsShift(event.getKeyCode())) {
conn.sendKeyboardInput((short) 0x8010, KeyboardPacket.KEY_UP, getModifierState(event));
}
}
return true;
@@ -1166,6 +1193,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
{
// This case is for mice and non-finger touch devices
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
event.getSource() == InputDevice.SOURCE_TOUCHPAD ||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
(event.getPointerCount() >= 1 &&
(event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE ||
@@ -1303,6 +1331,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return true;
}
if (view == null && !prefConfig.touchscreenTrackpad) {
// Absolute touch events should be dropped outside our view.
return true;
}
int actionIndex = event.getActionIndex();
int eventX = (int)event.getX(actionIndex);
@@ -1332,7 +1365,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
{
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_DOWN:
context.touchDownEvent(eventX, eventY);
for (TouchContext touchContext : touchContextMap) {
touchContext.setPointerCount(event.getPointerCount());
}
context.touchDownEvent(eventX, eventY, true);
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
@@ -1345,9 +1381,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
}
context.touchUpEvent(eventX, eventY);
for (TouchContext touchContext : touchContextMap) {
touchContext.setPointerCount(event.getPointerCount() - 1);
}
if (actionIndex == 0 && event.getPointerCount() > 1 && !context.isCancelled()) {
// The original secondary touch now becomes primary
context.touchDownEvent((int)event.getX(1), (int)event.getY(1));
context.touchDownEvent((int)event.getX(1), (int)event.getY(1), false);
}
break;
case MotionEvent.ACTION_MOVE:
@@ -1379,6 +1418,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
case MotionEvent.ACTION_CANCEL:
for (TouchContext aTouchContext : touchContextMap) {
aTouchContext.cancelTouch();
aTouchContext.setPointerCount(0);
}
break;
default:
@@ -1495,7 +1535,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
@Override
public void stageFailed(final String stage, final int errorCode) {
public void stageFailed(final String stage, final int portFlags, final int errorCode) {
// Perform a connection test if the failure could be due to a blocked port
// This does network I/O, so don't do it on the main thread.
final int portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER, 443, portFlags);
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1513,8 +1557,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
Toast.makeText(Game.this, getResources().getText(R.string.video_decoder_init_failed), Toast.LENGTH_LONG).show();
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.conn_error_msg) + " " + stage +" (error "+errorCode+")", true);
String dialogText = getResources().getString(R.string.conn_error_msg) + " " + stage +" (error "+errorCode+")";
if (portFlags != 0) {
dialogText += "\n\n" + getResources().getString(R.string.check_ports_msg) + "\n" +
MoonBridge.stringifyPortFlags(portFlags, "\n");
}
if (portTestResult != MoonBridge.ML_TEST_RESULT_INCONCLUSIVE && portTestResult != 0) {
dialogText += "\n\n" + getResources().getString(R.string.nettest_text_blocked);
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_error_title), dialogText, true);
}
}
});
@@ -1522,6 +1576,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void connectionTerminated(final int errorCode) {
// Perform a connection test if the failure could be due to a blocked port
// This does network I/O, so don't do it on the main thread.
final int portFlags = MoonBridge.getPortFlagsFromTerminationErrorCode(errorCode);
final int portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER,443, portFlags);
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1538,9 +1597,40 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Display the error dialog if it was an unexpected termination.
// Otherwise, just finish the activity immediately.
if (errorCode != 0) {
if (errorCode != MoonBridge.ML_ERROR_GRACEFUL_TERMINATION) {
String message;
if (portTestResult != MoonBridge.ML_TEST_RESULT_INCONCLUSIVE && portTestResult != 0) {
// If we got a blocked result, that supersedes any other error message
message = getResources().getString(R.string.nettest_text_blocked);
}
else {
switch (errorCode) {
case MoonBridge.ML_ERROR_NO_VIDEO_TRAFFIC:
message = getResources().getString(R.string.no_video_received_error);
break;
case MoonBridge.ML_ERROR_NO_VIDEO_FRAME:
message = getResources().getString(R.string.no_frame_received_error);
break;
case MoonBridge.ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
message = getResources().getString(R.string.early_termination_error);
break;
default:
message = getResources().getString(R.string.conn_terminated_msg);
break;
}
}
if (portFlags != 0) {
message += "\n\n" + getResources().getString(R.string.check_ports_msg) + "\n" +
MoonBridge.stringifyPortFlags(portFlags, "\n");
}
Dialog.displayDialog(Game.this, getResources().getString(R.string.conn_terminated_title),
getResources().getString(R.string.conn_terminated_msg), true);
message, true);
}
else {
finish();
@@ -1660,6 +1750,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceCreated = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Tell the OS about our frame rate to allow it to adapt the display refresh rate appropriately
holder.getSurface().setFrameRate(prefConfig.fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
}
}
@Override
+44 -18
View File
@@ -109,7 +109,6 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}
}
private final static int APP_LIST_ID = 1;
private final static int PAIR_ID = 2;
private final static int UNPAIR_ID = 3;
private final static int WOL_ID = 4;
@@ -117,6 +116,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
private final static int RESUME_ID = 6;
private final static int QUIT_ID = 7;
private final static int VIEW_DETAILS_ID = 8;
private final static int FULL_APP_LIST_ID = 9;
private final static int TEST_NETWORK_ID = 10;
private void initializeViews() {
setContentView(R.layout.activity_pc_view);
@@ -316,15 +317,32 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
// Add a header with PC status details
menu.clearHeader();
String headerTitle = computer.details.name + " - ";
switch (computer.details.state)
{
case ONLINE:
headerTitle += getResources().getString(R.string.pcview_menu_header_online);
break;
case OFFLINE:
menu.setHeaderIcon(R.drawable.ic_pc_offline);
headerTitle += getResources().getString(R.string.pcview_menu_header_offline);
break;
case UNKNOWN:
headerTitle += getResources().getString(R.string.pcview_menu_header_unknown);
break;
}
menu.setHeaderTitle(headerTitle);
// Inflate the context menu
if (computer.details.state == ComputerDetails.State.OFFLINE ||
computer.details.state == ComputerDetails.State.UNKNOWN) {
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
}
else if (computer.details.pairState != PairState.PAIRED) {
menu.add(Menu.NONE, PAIR_ID, 1, getResources().getString(R.string.pcview_menu_pair_pc));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
}
else {
if (computer.details.runningGameId != 0) {
@@ -332,13 +350,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
menu.add(Menu.NONE, QUIT_ID, 2, getResources().getString(R.string.applist_menu_quit));
}
menu.add(Menu.NONE, APP_LIST_ID, 3, getResources().getString(R.string.pcview_menu_app_list));
// FIXME: We used to be able to unpair here but it's been broken since GFE 2.1.x, so I've replaced
// it with delete which actually work
menu.add(Menu.NONE, DELETE_ID, 4, getResources().getString(R.string.pcview_menu_delete_pc));
menu.add(Menu.NONE, FULL_APP_LIST_ID, 4, getResources().getString(R.string.pcview_menu_app_list));
}
menu.add(Menu.NONE, VIEW_DETAILS_ID, 5, getResources().getString(R.string.pcview_menu_details));
menu.add(Menu.NONE, TEST_NETWORK_ID, 5, getResources().getString(R.string.pcview_menu_test_network));
menu.add(Menu.NONE, DELETE_ID, 6, getResources().getString(R.string.pcview_menu_delete_pc));
menu.add(Menu.NONE, VIEW_DETAILS_ID, 7, getResources().getString(R.string.pcview_menu_details));
}
@Override
@@ -442,7 +459,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
if (toastSuccess) {
// Open the app list after a successful pairing attempt
doAppList(computer, true);
doAppList(computer, true, false);
}
else {
// Start polling again if we're still in the foreground
@@ -541,7 +558,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}).start();
}
private void doAppList(ComputerDetails computer, boolean newlyPaired) {
private void doAppList(ComputerDetails computer, boolean newlyPaired, boolean showHiddenGames) {
if (computer.state == ComputerDetails.State.OFFLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return;
@@ -555,6 +572,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
i.putExtra(AppView.NAME_EXTRA, computer.name);
i.putExtra(AppView.UUID_EXTRA, computer.uuid);
i.putExtra(AppView.NEW_PAIR_EXTRA, newlyPaired);
i.putExtra(AppView.SHOW_HIDDEN_APPS_EXTRA, showHiddenGames);
startActivity(i);
}
@@ -592,8 +610,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}, null);
return true;
case APP_LIST_ID:
doAppList(computer.details, false);
case FULL_APP_LIST_ID:
doAppList(computer.details, false, true);
return true;
case RESUME_ID:
@@ -625,6 +643,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
Dialog.displayDialog(PcView.this, getResources().getString(R.string.title_details), computer.details.toString(), false);
return true;
case TEST_NETWORK_ID:
ServerHelper.doNetworkTest(PcView.this);
return true;
default:
return super.onContextItemSelected(item);
}
@@ -635,6 +657,12 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
new DiskAssetLoader(this).deleteAssetsForComputer(details.uuid);
// Delete hidden games preference value
getSharedPreferences(AppView.HIDDEN_APPS_PREF_FILENAME, MODE_PRIVATE)
.edit()
.remove(details.uuid)
.apply();
for (int i = 0; i < pcGridAdapter.getCount(); i++) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(i);
@@ -692,9 +720,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
@Override
public int getAdapterFragmentLayoutId() {
return PreferenceConfiguration.readPreferences(this).listMode ?
R.layout.list_view : (PreferenceConfiguration.readPreferences(this).smallIconMode ?
R.layout.pc_grid_view_small : R.layout.pc_grid_view);
return R.layout.pc_grid_view;
}
@Override
@@ -713,7 +739,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
// Pair an unpaired machine by default
doPair(computer.details);
} else {
doAppList(computer.details, false);
doAppList(computer.details, false, false);
}
}
});
@@ -721,7 +747,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
registerForContextMenu(listView);
}
public class ComputerObject {
public static class ComputerObject {
public ComputerDetails details;
public ComputerObject(ComputerDetails details) {
@@ -102,9 +102,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
LimeLog.warning("Corrupted certificate");
return false;
} catch (NoSuchAlgorithmException e) {
// Should never happen
e.printStackTrace();
return false;
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
// May happen if the key is corrupt
LimeLog.warning("Corrupted key");
@@ -124,10 +122,8 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", bcProvider);
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e1) {
// Should never happen
e1.printStackTrace();
return false;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
Date now = new Date();
@@ -152,8 +148,6 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
cert = new JcaX509CertificateConverter().setProvider(bcProvider).getCertificate(certBuilder.build(sigGen));
key = (RSAPrivateKey) keyPair.getPrivate();
} catch (Exception e) {
// Nothing should go wrong here
e.printStackTrace();
throw new RuntimeException(e);
}
@@ -107,6 +107,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
defaultContext.rightStickDeadzoneRadius = (float) stickDeadzone;
defaultContext.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
defaultContext.rightTriggerAxis = MotionEvent.AXIS_GAS;
defaultContext.hatXAxis = MotionEvent.AXIS_HAT_X;
defaultContext.hatYAxis = MotionEvent.AXIS_HAT_Y;
defaultContext.controllerNumber = (short) 0;
defaultContext.assignedControllerNumber = true;
defaultContext.external = false;
@@ -194,6 +196,28 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return true;
}
// HACK for https://issuetracker.google.com/issues/163120692
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
if (device.getId() == -1) {
// This "virtual" device could be input from any of the attached devices.
// Look to see if any gamepads are connected.
int[] ids = InputDevice.getDeviceIds();
for (int id : ids) {
InputDevice dev = InputDevice.getDevice(id);
if (dev == null) {
// This device was removed during enumeration
continue;
}
// If there are any gamepad devices connected, we'll
// report that this virtual device is a gamepad.
if (hasJoystickAxes(dev) || hasGamepadButtons(dev)) {
return true;
}
}
}
}
// Otherwise, we'll try anything that claims to be a non-alphabetic keyboard
return device.getKeyboardType() != InputDevice.KEYBOARD_TYPE_ALPHABETIC;
}
@@ -467,6 +491,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.vibrator = dev.getVibrator();
}
// Detect if the gamepad has Mode and Select buttons according to the Android key layouts.
// We do this first because other codepaths below may override these defaults.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
boolean[] buttons = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_MODE, KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BACK, 0);
context.hasMode = buttons[0];
context.hasSelect = buttons[1] || buttons[2];
}
context.leftStickXAxis = MotionEvent.AXIS_X;
context.leftStickYAxis = MotionEvent.AXIS_Y;
if (getMotionRangeForJoystickAxis(dev, context.leftStickXAxis) != null &&
@@ -525,6 +557,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// The old DS4 driver uses RX and RY for triggers
context.leftTriggerAxis = MotionEvent.AXIS_RX;
context.rightTriggerAxis = MotionEvent.AXIS_RY;
// DS4 has Select and Mode buttons (possibly mapped non-standard)
context.hasSelect = true;
context.hasMode = true;
}
else {
// If it's not a non-standard DS4 controller, it's probably an Xbox controller or
@@ -606,6 +642,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.backIsStart = true;
context.modeIsSelect = true;
context.triggerDeadzone = 0.30f;
context.hasSelect = true;
context.hasMode = false;
}
}
@@ -623,6 +661,8 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (!hasStartKey[0] && !hasStartKey[1]) {
context.backIsStart = true;
context.modeIsSelect = true;
context.hasSelect = true;
context.hasMode = false;
}
}
@@ -631,14 +671,26 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
context.triggerDeadzone = 0.30f;
}
// SHIELD controllers will use small stick deadzones
else if (devName.contains("SHIELD")) {
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.
if (devName.contains("NVIDIA Controller v01.03") || devName.contains("NVIDIA Controller v01.04")) {
context.searchIsMode = true;
context.hasMode = true;
}
}
// The Serval has a couple of unknown buttons that are start and select. It also has
// a back button which we want to ignore since there's already a select button.
else if (devName.contains("Razer Serval")) {
context.isServal = true;
// Serval has Select and Mode buttons (possibly mapped non-standard)
context.hasMode = true;
context.hasSelect = true;
}
// The Xbox One S Bluetooth controller has some mappings that need fixing up.
// However, Microsoft released a firmware update with no change to VID/PID
@@ -649,6 +701,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
else if (devName.equals("Xbox Wireless Controller")) {
if (gasRange == null) {
context.isNonStandardXboxBtController = true;
// Xbox One S has Select and Mode buttons (possibly mapped non-standard)
context.hasMode = true;
context.hasSelect = true;
}
}
}
@@ -671,6 +727,13 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
return null;
}
// HACK for https://issuetracker.google.com/issues/163120692
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
if (event.getDeviceId() == -1) {
return defaultContext;
}
}
// Return the existing context if it exists
InputDeviceContext context = inputDeviceContexts.get(event.getDeviceId());
if (context != null) {
@@ -827,6 +890,46 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
}
// Override mode button for 8BitDo controllers
if (context.vendorId == 0x2dc8 && event.getScanCode() == 306) {
return KeyEvent.KEYCODE_BUTTON_MODE;
}
// This mapping was adding in Android 10, then changed based on
// kernel changes (adding hid-nintendo) in Android 11. If we're
// on anything newer than Pie, just use the built-in mapping.
if ((context.vendorId == 0x057e && context.productId == 0x2009 && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) || // Switch Pro controller
(context.vendorId == 0x0f0d && context.productId == 0x00c1)) { // HORIPAD for Switch
switch (event.getScanCode()) {
case 0x130:
return KeyEvent.KEYCODE_BUTTON_A;
case 0x131:
return KeyEvent.KEYCODE_BUTTON_B;
case 0x132:
return KeyEvent.KEYCODE_BUTTON_X;
case 0x133:
return KeyEvent.KEYCODE_BUTTON_Y;
case 0x134:
return KeyEvent.KEYCODE_BUTTON_L1;
case 0x135:
return KeyEvent.KEYCODE_BUTTON_R1;
case 0x136:
return KeyEvent.KEYCODE_BUTTON_L2;
case 0x137:
return KeyEvent.KEYCODE_BUTTON_R2;
case 0x138:
return KeyEvent.KEYCODE_BUTTON_SELECT;
case 0x139:
return KeyEvent.KEYCODE_BUTTON_START;
case 0x13A:
return KeyEvent.KEYCODE_BUTTON_THUMBL;
case 0x13B:
return KeyEvent.KEYCODE_BUTTON_THUMBR;
case 0x13D:
return KeyEvent.KEYCODE_BUTTON_MODE;
}
}
if (context.usesLinuxGamepadStandardFaceButtons) {
// Android's Generic.kl swaps BTN_NORTH and BTN_WEST
switch (event.getScanCode()) {
@@ -987,10 +1090,29 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// Emulate the select button with mode
return KeyEvent.KEYCODE_BUTTON_SELECT;
}
else if (context.searchIsMode && keyCode == KeyEvent.KEYCODE_SEARCH) {
// Emulate the mode button with search
return KeyEvent.KEYCODE_BUTTON_MODE;
}
return keyCode;
}
private int handleFlipFaceButtons(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_A:
return KeyEvent.KEYCODE_BUTTON_B;
case KeyEvent.KEYCODE_BUTTON_B:
return KeyEvent.KEYCODE_BUTTON_A;
case KeyEvent.KEYCODE_BUTTON_X:
return KeyEvent.KEYCODE_BUTTON_Y;
case KeyEvent.KEYCODE_BUTTON_Y:
return KeyEvent.KEYCODE_BUTTON_X;
default:
return keyCode;
}
}
private Vector2d populateCachedVector(float x, float y) {
// Reinitialize our cached Vector2d object
inputVector.initialize(x, y);
@@ -1256,6 +1378,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
int keyCode = handleRemapping(context, event);
if (prefConfig.flipFaceButtons) {
keyCode = handleFlipFaceButtons(keyCode);
}
if (keyCode == 0) {
return true;
}
@@ -1417,12 +1544,18 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
int keyCode = handleRemapping(context, event);
if (prefConfig.flipFaceButtons) {
keyCode = handleFlipFaceButtons(keyCode);
}
if (keyCode == 0) {
return true;
}
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE:
context.hasMode = true;
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_START:
@@ -1434,6 +1567,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
context.hasSelect = true;
context.inputMap |= ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -1515,26 +1649,40 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
// Start+LB acts like select for controllers with one button
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
context.inputMap |= ControllerPacket.BACK_FLAG;
if (!context.hasSelect) {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastLbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.LB_FLAG);
context.inputMap |= ControllerPacket.BACK_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SELECT;
}
}
// We detect select+start or start+RB as the special button combo
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG) ||
context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
// If there is a physical select button, we'll use Start+Select as the special button combo
// otherwise we'll use Start+RB.
if (!context.hasMode) {
if (context.hasSelect) {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG)) {
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.BACK_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
}
else {
if (context.inputMap == (ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG) ||
(context.inputMap == ControllerPacket.PLAY_FLAG &&
SystemClock.uptimeMillis() - context.lastRbUpTime <= MAXIMUM_BUMPER_UP_DELAY_MS))
{
context.inputMap &= ~(ControllerPacket.PLAY_FLAG | ControllerPacket.RB_FLAG);
context.inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
context.emulatingButtonFlags |= ControllerHandler.EMULATING_SPECIAL;
}
}
}
// We don't need to send repeat key down events, but the platform
@@ -1618,7 +1766,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
usbDeviceContexts.put(controller.getControllerId(), context);
}
class GenericControllerContext {
static class GenericControllerContext {
public int id;
public boolean external;
@@ -1678,11 +1826,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public boolean isServal;
public boolean backIsStart;
public boolean modeIsSelect;
public boolean searchIsMode;
public boolean ignoreBack;
public boolean hasJoystickAxes;
public boolean pendingExit;
public int emulatingButtonFlags = 0;
public boolean hasSelect;
public boolean hasMode;
// Used for OUYA bumper state tracking since they force all buttons
// up when the OUYA button goes down. We watch the last time we get
@@ -38,23 +38,31 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
@Override
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE;
// SOURCE_MOUSE_RELATIVE is how SOURCE_MOUSE appears when our view has pointer capture.
// SOURCE_TOUCHPAD will have relative axes populated iff our view has pointer capture.
// See https://developer.android.com/reference/android/view/View#requestPointerCapture()
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
(event.getSource() == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
}
@Override
public float getRelativeAxisX(MotionEvent event) {
float x = event.getX();
int axis = (event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) ?
MotionEvent.AXIS_X : MotionEvent.AXIS_RELATIVE_X;
float x = event.getAxisValue(axis);
for (int i = 0; i < event.getHistorySize(); i++) {
x += event.getHistoricalX(i);
x += event.getHistoricalAxisValue(axis, i);
}
return x;
}
@Override
public float getRelativeAxisY(MotionEvent event) {
float y = event.getY();
int axis = (event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) ?
MotionEvent.AXIS_Y : MotionEvent.AXIS_RELATIVE_Y;
float y = event.getAxisValue(axis);
for (int i = 0; i < event.getHistorySize(); i++) {
y += event.getHistoricalY(i);
y += event.getHistoricalAxisValue(axis, i);
}
return y;
}
@@ -26,6 +26,7 @@ public class Xbox360Controller extends AbstractXboxController {
0x0f0d, // Hori
0x1038, // SteelSeries
0x11c9, // Nacon
0x1209, // Ardwiino
0x12ab, // Unknown
0x1430, // RedOctane
0x146b, // BigBen
@@ -33,8 +34,11 @@ public class Xbox360Controller extends AbstractXboxController {
0x15e4, // Numark
0x162e, // Joytech
0x1689, // Razer Onza
0x1949, // Lab126 (Amazon Luna)
0x1bad, // Harmonix
0x20d6, // PowerA
0x24c6, // PowerA
0x2f24, // GameSir
};
public static boolean canClaimDevice(UsbDevice device) {
@@ -66,8 +70,8 @@ public class Xbox360Controller extends AbstractXboxController {
@Override
protected boolean handleRead(ByteBuffer buffer) {
if (buffer.limit() < 14) {
LimeLog.severe("Read too small: "+buffer.limit());
if (buffer.remaining() < 14) {
LimeLog.severe("Read too small: "+buffer.remaining());
return false;
}
@@ -21,10 +21,13 @@ public class XboxOneController extends AbstractXboxController {
0x0e6f, // Unknown
0x0f0d, // Hori
0x1532, // Razer Wildcat
0x20d6, // PowerA
0x24c6, // PowerA
0x2e24, // Hyperkin
};
private static final byte[] FW2015_INIT = {0x05, 0x20, 0x00, 0x01, 0x00};
private static final byte[] ONE_S_INIT = {0x05, 0x20, 0x00, 0x0f, 0x06};
private static final byte[] HORI_INIT = {0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a,
0x00, 0x00, 0x00, (byte)0x80, 0x00};
private static final byte[] PDP_INIT1 = {0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14};
@@ -38,6 +41,8 @@ public class XboxOneController extends AbstractXboxController {
new InitPacket(0x0e6f, 0x0165, HORI_INIT),
new InitPacket(0x0f0d, 0x0067, HORI_INIT),
new InitPacket(0x0000, 0x0000, FW2015_INIT),
new InitPacket(0x045e, 0x02ea, ONE_S_INIT),
new InitPacket(0x045e, 0x0b00, ONE_S_INIT),
new InitPacket(0x0e6f, 0x0000, PDP_INIT1),
new InitPacket(0x0e6f, 0x0000, PDP_INIT2),
new InitPacket(0x24c6, 0x541a, RUMBLE_INIT1),
@@ -98,11 +103,21 @@ public class XboxOneController extends AbstractXboxController {
switch (buffer.get())
{
case 0x20:
if (buffer.remaining() < 17) {
LimeLog.severe("XBone button/axis read too small: "+buffer.remaining());
return false;
}
buffer.position(buffer.position()+3);
processButtons(buffer);
return true;
case 0x07:
if (buffer.remaining() < 4) {
LimeLog.severe("XBone mode read too small: "+buffer.remaining());
return false;
}
// The Xbox One S controller needs acks for mode reports otherwise
// it retransmits them forever.
if (buffer.get() == 0x30) {
@@ -0,0 +1,272 @@
package com.limelight.binding.input.touch;
import android.os.SystemClock;
import android.view.View;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.MouseButtonPacket;
import java.util.Timer;
import java.util.TimerTask;
public class AbsoluteTouchContext implements TouchContext {
private int lastTouchDownX = 0;
private int lastTouchDownY = 0;
private long lastTouchDownTime = 0;
private int lastTouchUpX = 0;
private int lastTouchUpY = 0;
private long lastTouchUpTime = 0;
private int lastTouchLocationX = 0;
private int lastTouchLocationY = 0;
private boolean cancelled;
private boolean confirmedLongPress;
private boolean confirmedTap;
private Timer longPressTimer;
private Timer tapDownTimer;
private float accumulatedScrollDelta;
private final NvConnection conn;
private final int actionIndex;
private final View targetView;
private static final int SCROLL_SPEED_DIVISOR = 20;
private static final int LONG_PRESS_TIME_THRESHOLD = 650;
private static final int LONG_PRESS_DISTANCE_THRESHOLD = 30;
private static final int DOUBLE_TAP_TIME_THRESHOLD = 250;
private static final int DOUBLE_TAP_DISTANCE_THRESHOLD = 60;
private static final int TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD = 100;
private static final int TOUCH_DOWN_DEAD_ZONE_DISTANCE_THRESHOLD = 20;
public AbsoluteTouchContext(NvConnection conn, int actionIndex, View view)
{
this.conn = conn;
this.actionIndex = actionIndex;
this.targetView = view;
}
@Override
public int getActionIndex()
{
return actionIndex;
}
@Override
public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
{
if (!isNewFinger) {
// We don't handle finger transitions for absolute mode
return true;
}
lastTouchLocationX = lastTouchDownX = eventX;
lastTouchLocationY = lastTouchDownY = eventY;
lastTouchDownTime = SystemClock.uptimeMillis();
cancelled = confirmedTap = confirmedLongPress = false;
accumulatedScrollDelta = 0;
if (actionIndex == 0) {
// Start the timers
startTapDownTimer();
startLongPressTimer();
}
return true;
}
private boolean distanceExceeds(int deltaX, int deltaY, double limit) {
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)) > limit;
}
private void updatePosition(int eventX, int eventY) {
// We may get values slightly outside our view region on ACTION_HOVER_ENTER and ACTION_HOVER_EXIT.
// Normalize these to the view size. We can't just drop them because we won't always get an event
// right at the boundary of the view, so dropping them would result in our cursor never really
// reaching the sides of the screen.
eventX = Math.min(Math.max(eventX, 0), targetView.getWidth());
eventY = Math.min(Math.max(eventY, 0), targetView.getHeight());
conn.sendMousePosition((short)eventX, (short)eventY, (short)targetView.getWidth(), (short)targetView.getHeight());
}
@Override
public void touchUpEvent(int eventX, int eventY)
{
if (cancelled) {
return;
}
if (actionIndex == 0) {
// Cancel the timers
cancelLongPressTimer();
cancelTapDownTimer();
// Raise the mouse buttons that we currently have down
if (confirmedLongPress) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
else if (confirmedTap) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
else {
// If we get here, this means that the tap completed within the touch down
// deadzone time. We'll need to send the touch down and up events now at the
// original touch down position.
tapConfirmed();
try {
// FIXME: Sleeping on the main thread sucks
Thread.sleep(50);
} catch (InterruptedException ignored) {}
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
lastTouchLocationX = lastTouchUpX = eventX;
lastTouchLocationY = lastTouchUpY = eventY;
lastTouchUpTime = SystemClock.uptimeMillis();
}
private synchronized void startLongPressTimer() {
longPressTimer = new Timer(true);
longPressTimer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (AbsoluteTouchContext.this) {
// Check if someone cancelled us
if (longPressTimer == null) {
return;
}
// Uncancellable now
longPressTimer = null;
// This timer should have already expired, but cancel it just in case
cancelTapDownTimer();
// Switch from a left click to a right click after a long press
confirmedLongPress = true;
if (confirmedTap) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
}
}
}, LONG_PRESS_TIME_THRESHOLD);
}
private synchronized void cancelLongPressTimer() {
if (longPressTimer != null) {
longPressTimer.cancel();
longPressTimer = null;
}
}
private synchronized void startTapDownTimer() {
tapDownTimer = new Timer(true);
tapDownTimer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (AbsoluteTouchContext.this) {
// Check if someone cancelled us
if (tapDownTimer == null) {
return;
}
// Uncancellable now
tapDownTimer = null;
// Start our tap
tapConfirmed();
}
}
}, TOUCH_DOWN_DEAD_ZONE_TIME_THRESHOLD);
}
private synchronized void cancelTapDownTimer() {
if (tapDownTimer != null) {
tapDownTimer.cancel();
tapDownTimer = null;
}
}
private void tapConfirmed() {
if (confirmedTap || confirmedLongPress) {
return;
}
confirmedTap = true;
cancelTapDownTimer();
// Left button down at original position
if (lastTouchDownTime - lastTouchUpTime > DOUBLE_TAP_TIME_THRESHOLD ||
distanceExceeds(lastTouchDownX - lastTouchUpX, lastTouchDownY - lastTouchUpY, DOUBLE_TAP_DISTANCE_THRESHOLD)) {
// Don't reposition for finger down events within the deadzone. This makes double-clicking easier.
updatePosition(lastTouchDownX, lastTouchDownY);
}
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
}
@Override
public boolean touchMoveEvent(int eventX, int eventY)
{
if (cancelled) {
return true;
}
if (actionIndex == 0) {
if (distanceExceeds(eventX - lastTouchDownX, eventY - lastTouchDownY, LONG_PRESS_DISTANCE_THRESHOLD)) {
// Moved too far since touch down. Cancel the long press timer.
cancelLongPressTimer();
}
// Ignore motion within the deadzone period after touch down
if (confirmedTap || distanceExceeds(eventX - lastTouchDownX, eventY - lastTouchDownY, TOUCH_DOWN_DEAD_ZONE_DISTANCE_THRESHOLD)) {
tapConfirmed();
updatePosition(eventX, eventY);
}
}
else if (actionIndex == 1) {
accumulatedScrollDelta += (eventY - lastTouchLocationY) / (float)SCROLL_SPEED_DIVISOR;
if ((short)accumulatedScrollDelta != 0) {
conn.sendMouseHighResScroll((short)accumulatedScrollDelta);
accumulatedScrollDelta -= (short)accumulatedScrollDelta;
}
}
lastTouchLocationX = eventX;
lastTouchLocationY = eventY;
return true;
}
@Override
public void cancelTouch() {
cancelled = true;
// Cancel the timers
cancelLongPressTimer();
cancelTapDownTimer();
// Raise the mouse buttons
if (confirmedLongPress) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
else if (confirmedTap) {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setPointerCount(int pointerCount) {
if (actionIndex == 0 && pointerCount > 1) {
cancelTouch();
}
}
}
@@ -1,5 +1,6 @@
package com.limelight.binding.input;
package com.limelight.binding.input.touch;
import android.os.SystemClock;
import android.view.View;
import com.limelight.nvstream.NvConnection;
@@ -8,7 +9,7 @@ import com.limelight.nvstream.input.MouseButtonPacket;
import java.util.Timer;
import java.util.TimerTask;
public class TouchContext {
public class RelativeTouchContext implements TouchContext {
private int lastTouchX = 0;
private int lastTouchY = 0;
private int originalTouchX = 0;
@@ -17,9 +18,12 @@ public class TouchContext {
private boolean cancelled;
private boolean confirmedMove;
private boolean confirmedDrag;
private boolean confirmedScroll;
private Timer dragTimer;
private double distanceMoved;
private double xFactor, yFactor;
private int pointerCount;
private int maxPointerCountInGesture;
private final NvConnection conn;
private final int actionIndex;
@@ -32,8 +36,10 @@ public class TouchContext {
private static final int TAP_TIME_THRESHOLD = 250;
private static final int DRAG_TIME_THRESHOLD = 650;
public TouchContext(NvConnection conn, int actionIndex,
int referenceWidth, int referenceHeight, View view)
private static final int SCROLL_SPEED_DIVISOR = 20;
public RelativeTouchContext(NvConnection conn, int actionIndex,
int referenceWidth, int referenceHeight, View view)
{
this.conn = conn;
this.actionIndex = actionIndex;
@@ -42,6 +48,7 @@ public class TouchContext {
this.targetView = view;
}
@Override
public int getActionIndex()
{
return actionIndex;
@@ -57,8 +64,18 @@ public class TouchContext {
private boolean isTap()
{
long timeDelta = System.currentTimeMillis() - originalTouchTime;
if (confirmedDrag || confirmedMove || confirmedScroll) {
return false;
}
// If this input wasn't the last finger down, do not report
// a tap. This ensures we don't report duplicate taps for each
// finger on a multi-finger tap gesture
if (actionIndex + 1 != maxPointerCountInGesture) {
return false;
}
long timeDelta = SystemClock.uptimeMillis() - originalTouchTime;
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
}
@@ -72,7 +89,8 @@ public class TouchContext {
}
}
public boolean touchDownEvent(int eventX, int eventY)
@Override
public boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger)
{
// Get the view dimensions to scale inputs on this touch
xFactor = referenceWidth / (double)targetView.getWidth();
@@ -80,18 +98,23 @@ public class TouchContext {
originalTouchX = lastTouchX = eventX;
originalTouchY = lastTouchY = eventY;
originalTouchTime = System.currentTimeMillis();
cancelled = confirmedDrag = confirmedMove = false;
distanceMoved = 0;
if (actionIndex == 0) {
// Start the timer for engaging a drag
startDragTimer();
if (isNewFinger) {
maxPointerCountInGesture = pointerCount;
originalTouchTime = SystemClock.uptimeMillis();
cancelled = confirmedDrag = confirmedMove = confirmedScroll = false;
distanceMoved = 0;
if (actionIndex == 0) {
// Start the timer for engaging a drag
startDragTimer();
}
}
return true;
}
@Override
public void touchUpEvent(int eventX, int eventY)
{
if (cancelled) {
@@ -124,11 +147,14 @@ public class TouchContext {
}
private synchronized void startDragTimer() {
// Cancel any existing drag timers
cancelDragTimer();
dragTimer = new Timer(true);
dragTimer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (TouchContext.this) {
synchronized (RelativeTouchContext.this) {
// Check if someone already set move
if (confirmedMove) {
return;
@@ -179,20 +205,33 @@ public class TouchContext {
}
}
private void checkForConfirmedScroll() {
// Enter scrolling mode if we've already left the tap zone
// and we have 2 fingers on screen. Leave scroll mode if
// we no longer have 2 fingers on screen
confirmedScroll = (actionIndex == 0 && pointerCount == 2 && confirmedMove);
}
@Override
public boolean touchMoveEvent(int eventX, int eventY)
{
if (cancelled) {
return true;
}
if (eventX != lastTouchX || eventY != lastTouchY)
{
checkForConfirmedMove(eventX, eventY);
checkForConfirmedScroll();
// We only send moves and drags for the primary touch point
if (actionIndex == 0) {
checkForConfirmedMove(eventX, eventY);
int deltaX = eventX - lastTouchX;
int deltaY = eventY - lastTouchY;
// Scale the deltas based on the factors passed to our constructor
deltaX = (int)Math.round((double)Math.abs(deltaX) * xFactor);
deltaY = (int)Math.round((double)Math.abs(deltaY) * yFactor);
deltaX = (int) Math.round((double) Math.abs(deltaX) * xFactor);
deltaY = (int) Math.round((double) Math.abs(deltaY) * yFactor);
// Fix up the signs
if (eventX < lastTouchX) {
@@ -202,6 +241,16 @@ public class TouchContext {
deltaY = -deltaY;
}
if (pointerCount == 2) {
if (confirmedScroll) {
deltaY /= SCROLL_SPEED_DIVISOR;
conn.sendMouseHighResScroll((short) deltaY);
}
} else {
conn.sendMouseMove((short) deltaX, (short) deltaY);
}
// If the scaling factor ended up rounding deltas to zero, wait until they are
// non-zero to update lastTouch that way devices that report small touch events often
// will work correctly
@@ -211,8 +260,6 @@ public class TouchContext {
if (deltaY != 0) {
lastTouchY = eventY;
}
conn.sendMouseMove((short)deltaX, (short)deltaY);
}
else {
lastTouchX = eventX;
@@ -223,6 +270,7 @@ public class TouchContext {
return true;
}
@Override
public void cancelTouch() {
cancelled = true;
@@ -235,7 +283,17 @@ public class TouchContext {
}
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setPointerCount(int pointerCount) {
this.pointerCount = pointerCount;
if (pointerCount > maxPointerCountInGesture) {
maxPointerCountInGesture = pointerCount;
}
}
}
@@ -0,0 +1,11 @@
package com.limelight.binding.input.touch;
public interface TouchContext {
int getActionIndex();
void setPointerCount(int pointerCount);
boolean touchDownEvent(int eventX, int eventY, boolean isNewFinger);
boolean touchMoveEvent(int eventX, int eventY);
void touchUpEvent(int eventX, int eventY);
void cancelTouch();
boolean isCancelled();
}
@@ -8,6 +8,7 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.SystemClock;
import android.view.MotionEvent;
import java.util.ArrayList;
@@ -210,7 +211,7 @@ public class AnalogStick extends VirtualControllerElement {
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// calculate new radius sizes depending
radius_complete = getPercent(getCorrectWidth() / 2, 100);
radius_complete = getPercent(getCorrectWidth() / 2, 100) - 2 * getDefaultStrokeWidth();
radius_dead_zone = getPercent(getCorrectWidth() / 2, 30);
radius_analog_stick = getPercent(getCorrectWidth() / 2, 20);
@@ -270,7 +271,7 @@ public class AnalogStick extends VirtualControllerElement {
// We also release the deadzone if the user keeps the stick pressed for a bit to allow
// them to make precise movements.
stick_state = (stick_state == STICK_STATE.MOVED_ACTIVE ||
System.currentTimeMillis() - timeLastClick > timeoutDeadzone ||
SystemClock.uptimeMillis() - timeLastClick > timeoutDeadzone ||
movement_radius > radius_dead_zone) ?
STICK_STATE.MOVED_ACTIVE : STICK_STATE.MOVED_IN_DEAD_ZONE;
@@ -311,7 +312,7 @@ public class AnalogStick extends VirtualControllerElement {
stick_state = STICK_STATE.MOVED_IN_DEAD_ZONE;
// check for double click
if (lastClickState == CLICK_STATE.SINGLE &&
timeLastClick + timeoutDoubleClick > System.currentTimeMillis()) {
timeLastClick + timeoutDoubleClick > SystemClock.uptimeMillis()) {
click_state = CLICK_STATE.DOUBLE;
notifyOnDoubleClick();
} else {
@@ -319,7 +320,7 @@ public class AnalogStick extends VirtualControllerElement {
notifyOnClick();
}
// reset last click timestamp
timeLastClick = System.currentTimeMillis();
timeLastClick = SystemClock.uptimeMillis();
// set item pressed and update
setPressed(true);
break;
@@ -177,6 +177,15 @@ public class DigitalButton extends VirtualControllerElement {
listener.onClick();
}
if (timerLongClick != null) {
timerLongClick.cancel();
timerLongClick = null;
}
if (longClickTimerTask != null) {
longClickTimerTask.cancel();
longClickTimerTask = null;
}
timerLongClick = new Timer();
longClickTimerTask = new TimerLongClickTimerTask();
timerLongClick.schedule(longClickTimerTask, timerLongClickTimeout);
@@ -200,9 +209,11 @@ public class DigitalButton extends VirtualControllerElement {
// We may be called for a release without a prior click
if (timerLongClick != null) {
timerLongClick.cancel();
timerLongClick = null;
}
if (longClickTimerTask != null) {
longClickTimerTask.cancel();
longClickTimerTask = null;
}
}
@@ -22,7 +22,7 @@ import java.util.Timer;
import java.util.TimerTask;
public class VirtualController {
public class ControllerInputContext {
public static class ControllerInputContext {
public short inputMap = 0x0000;
public byte leftTrigger = 0x00;
public byte rightTrigger = 0x00;
@@ -167,11 +167,11 @@ public class VirtualControllerConfigurationLoader {
private static final int DPAD_BASE_Y = 41;
private static final int DPAD_SIZE = 30;
private static final int ANALOG_L_BASE_X = 4;
private static final int ANALOG_L_BASE_Y = 1;
private static final int ANALOG_R_BASE_X = 96;
private static final int ANALOG_L_BASE_X = 6;
private static final int ANALOG_L_BASE_Y = 4;
private static final int ANALOG_R_BASE_X = 98;
private static final int ANALOG_R_BASE_Y = 42;
private static final int ANALOG_SIZE = 28;
private static final int ANALOG_SIZE = 26;
private static final int L3_R3_BASE_Y = 60;
@@ -205,7 +205,8 @@ public class VirtualControllerConfigurationLoader {
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_A,
ControllerPacket.A_FLAG, 0, 1, "A", -1, controller, context),
!config.flipFaceButtons ? ControllerPacket.A_FLAG : ControllerPacket.B_FLAG, 0, 1,
!config.flipFaceButtons ? "A" : "B", -1, controller, context),
screenScale(BUTTON_BASE_X, height) + rightDisplacement,
screenScale(BUTTON_BASE_Y + 2 * BUTTON_SIZE, height),
screenScale(BUTTON_SIZE, height),
@@ -214,7 +215,8 @@ public class VirtualControllerConfigurationLoader {
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_B,
ControllerPacket.B_FLAG, 0, 1, "B", -1, controller, context),
config.flipFaceButtons ? ControllerPacket.A_FLAG : ControllerPacket.B_FLAG, 0, 1,
config.flipFaceButtons ? "A" : "B", -1, controller, context),
screenScale(BUTTON_BASE_X + BUTTON_SIZE, height) + rightDisplacement,
screenScale(BUTTON_BASE_Y + BUTTON_SIZE, height),
screenScale(BUTTON_SIZE, height),
@@ -223,7 +225,8 @@ public class VirtualControllerConfigurationLoader {
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_X,
ControllerPacket.X_FLAG, 0, 1, "X", -1, controller, context),
!config.flipFaceButtons ? ControllerPacket.X_FLAG : ControllerPacket.Y_FLAG, 0, 1,
!config.flipFaceButtons ? "X" : "Y", -1, controller, context),
screenScale(BUTTON_BASE_X - BUTTON_SIZE, height) + rightDisplacement,
screenScale(BUTTON_BASE_Y + BUTTON_SIZE, height),
screenScale(BUTTON_SIZE, height),
@@ -232,7 +235,8 @@ public class VirtualControllerConfigurationLoader {
controller.addElement(createDigitalButton(
VirtualControllerElement.EID_Y,
ControllerPacket.Y_FLAG, 0, 1, "Y", -1, controller, context),
config.flipFaceButtons ? ControllerPacket.X_FLAG : ControllerPacket.Y_FLAG, 0, 1,
config.flipFaceButtons ? "X" : "Y", -1, controller, context),
screenScale(BUTTON_BASE_X, height) + rightDisplacement,
screenScale(BUTTON_BASE_Y, height),
screenScale(BUTTON_SIZE, height),
@@ -20,6 +20,7 @@ import android.media.MediaFormat;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CodecException;
import android.os.Build;
import android.os.SystemClock;
import android.util.Range;
import android.view.SurfaceHolder;
@@ -109,8 +110,10 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork)) {
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr) {
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
// > 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 {
@@ -249,6 +252,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
return -1;
}
if (width > 4096 || height > 4096) {
LimeLog.severe("> 4K streaming only supported on HEVC");
return -1;
}
// These fixups only apply to H264 decoders
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(selectedDecoderName);
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(selectedDecoderName);
@@ -318,8 +326,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
}
if (lowLatency) {
videoFormat.setInteger(MediaCodecHelper.KEY_LOW_LATENCY, 1);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && lowLatency) {
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Set the Qualcomm vendor low latency extension if the Android R option is unavailable
@@ -334,11 +342,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
}
// Operate at maximum rate to lower latency as much as possible on
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
// but that will actually result in the decoder crashing if it can't satisfy
// our (ludicrous) operating rate requirement.
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(selectedDecoderName)) {
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
}
}
configuredFormat = videoFormat;
@@ -412,7 +418,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
//
if (initialException != null) {
// This isn't the first time we've had an exception processing video
if (System.currentTimeMillis() - initialExceptionTimestamp >= EXCEPTION_REPORT_DELAY_MS) {
if (SystemClock.uptimeMillis() - initialExceptionTimestamp >= EXCEPTION_REPORT_DELAY_MS) {
// It's been over 3 seconds and we're still getting exceptions. Throw the original now.
if (!reportedCrash) {
reportedCrash = true;
@@ -429,7 +435,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
else {
initialException = new RendererException(this, e);
}
initialExceptionTimestamp = System.currentTimeMillis();
initialExceptionTimestamp = SystemClock.uptimeMillis();
}
}
}
@@ -632,14 +638,14 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
@SuppressWarnings("deprecation")
@Override
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
int frameNumber, long receiveTimeMs) {
int frameNumber, long receiveTimeMs, long enqueueTimeMs) {
if (stopping) {
// Don't bother if we're stopping
return MoonBridge.DR_OK;
}
if (lastFrameNumber == 0) {
activeWindowVideoStats.measurementStartTimestamp = System.currentTimeMillis();
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
} else if (frameNumber != lastFrameNumber && frameNumber != lastFrameNumber + 1) {
// We can receive the same "frame" multiple times if it's an IDR frame.
// In that case, each frame start NALU is submitted independently.
@@ -651,7 +657,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
lastFrameNumber = frameNumber;
// Flip stats windows roughly every second
if (System.currentTimeMillis() >= activeWindowVideoStats.measurementStartTimestamp + 1000) {
if (SystemClock.uptimeMillis() >= activeWindowVideoStats.measurementStartTimestamp + 1000) {
if (prefs.enablePerfOverlay) {
VideoStats lastTwo = new VideoStats();
lastTwo.add(lastWindowVideoStats);
@@ -684,7 +690,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
globalVideoStats.add(activeWindowVideoStats);
lastWindowVideoStats.copy(activeWindowVideoStats);
activeWindowVideoStats.clear();
activeWindowVideoStats.measurementStartTimestamp = System.currentTimeMillis();
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
}
activeWindowVideoStats.totalFramesReceived++;
@@ -693,11 +699,13 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
int inputBufferIndex;
ByteBuffer buf;
long timestampUs = System.nanoTime() / 1000;
long timestampUs = enqueueTimeMs * 1000;
if (!FRAME_RENDER_TIME_ONLY) {
// Count time from first packet received to decode start
activeWindowVideoStats.totalTimeMs += (timestampUs / 1000) - receiveTimeMs;
// 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) {
@@ -40,11 +40,10 @@ public class MediaCodecHelper {
private static final List<String> blacklisted59FpsDecoderPrefixes;
private static final List<String> qualcommDecoderPrefixes;
// FIXME: Remove when Android R SDK is finalized
public static final String FEATURE_LowLatency = "low-latency";
public static final String KEY_LOW_LATENCY = "low-latency";
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
private static boolean isLowEndSnapdragon = false;
private static boolean isAdreno620 = false;
private static boolean initialized = false;
static {
@@ -81,7 +80,7 @@ public class MediaCodecHelper {
// Blacklist software decoders that don't support H264 high profile,
// but exclude the official AOSP and CrOS emulator from this restriction.
if (!Build.HARDWARE.equals("ranchu") && !Build.HARDWARE.equals("cheets")) {
if (!IS_EMULATOR) {
blacklistedDecoderPrefixes.add("omx.google");
blacklistedDecoderPrefixes.add("AVCDecoder");
}
@@ -127,7 +126,7 @@ public class MediaCodecHelper {
whitelistedHevcDecoders = new LinkedList<>();
// Allow software HEVC decoding in the official AOSP emulator
if (Build.HARDWARE.equals("ranchu") && Build.BRAND.equals("google")) {
if (Build.HARDWARE.equals("ranchu")) {
whitelistedHevcDecoders.add("omx.google");
}
@@ -156,9 +155,15 @@ public class MediaCodecHelper {
whitelistedHevcDecoders.add("omx.mtk");
}
// 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) {
whitelistedHevcDecoders.add("omx.amlogic");
}
// These theoretically have good HEVC decoding capabilities (potentially better than
// their AVC decoders), but haven't been tested enough
//whitelistedHevcDecoders.add("omx.amlogic");
//whitelistedHevcDecoders.add("omx.rk");
// Let's see if HEVC decoders are finally stable with C2
@@ -235,19 +240,23 @@ public class MediaCodecHelper {
return modelNumber.charAt(1) == '0';
}
private static int getAdrenoRendererModelNumber(String glRenderer) {
String modelNumber = getAdrenoVersionString(glRenderer);
if (modelNumber == null) {
// Not an Adreno GPU
return -1;
}
return Integer.parseInt(modelNumber);
}
// This is a workaround for some broken devices that report
// only GLES 3.0 even though the GPU is an Adreno 4xx series part.
// An example of such a device is the Huawei Honor 5x with the
// Snapdragon 616 SoC (Adreno 405).
private static boolean isGLES31SnapdragonRenderer(String glRenderer) {
String modelNumber = getAdrenoVersionString(glRenderer);
if (modelNumber == null) {
// Not an Adreno GPU
return false;
}
// Snapdragon 4xx and higher support GLES 3.1
return modelNumber.charAt(0) >= '4';
return getAdrenoRendererModelNumber(glRenderer) >= 400;
}
public static void initialize(Context context, String glRenderer) {
@@ -262,6 +271,7 @@ public class MediaCodecHelper {
LimeLog.info("OpenGL ES version: "+configInfo.reqGlEsVersion);
isLowEndSnapdragon = isLowEndSnapdragonRenderer(glRenderer);
isAdreno620 = getAdrenoRendererModelNumber(glRenderer) == 620;
// Tegra K1 and later can do reference frame invalidation properly
if (configInfo.reqGlEsVersion >= 0x30000) {
@@ -343,10 +353,9 @@ public class MediaCodecHelper {
}
public static boolean decoderSupportsLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
// KitKat added CodecCapabilities.isFeatureSupported()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
if (decoderInfo.getCapabilitiesForType(mimeType).isFeatureSupported(FEATURE_LowLatency)) {
if (decoderInfo.getCapabilitiesForType(mimeType).isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
LimeLog.info("Low latency decoding mode supported (FEATURE_LowLatency)");
return true;
}
@@ -359,6 +368,21 @@ public class MediaCodecHelper {
return false;
}
public static boolean decoderSupportsMaxOperatingRate(String decoderName) {
// Operate at maximum rate to lower latency as much as possible on
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
// but that will actually result in the decoder crashing if it can't satisfy
// our (ludicrous) operating rate requirement. This seems to cause reliable
// crashes on the Xiaomi Mi 10 lite 5G and Redmi K30i 5G on Android 10, so
// we'll disable it on Snapdragon 765G and all non-Qualcomm devices to be safe.
//
// NB: Even on Android 10, this optimization still provides significant
// performance gains on Pixel 2.
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
isDecoderInList(qualcommDecoderPrefixes, decoderName) &&
!isAdreno620;
}
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
// Possibly enable adaptive playback on KitKat and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@@ -564,7 +588,7 @@ public class MediaCodecHelper {
private static boolean isCodecBlacklisted(MediaCodecInfo codecInfo) {
// Use the new isSoftwareOnly() function on Android Q
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (codecInfo.isSoftwareOnly()) {
if (!IS_EMULATOR && codecInfo.isSoftwareOnly()) {
LimeLog.info("Skipping software-only decoder: "+codecInfo.getName());
return true;
}
@@ -1,5 +1,7 @@
package com.limelight.binding.video;
import android.os.SystemClock;
class VideoStats {
long decoderTimeMs;
@@ -24,7 +26,7 @@ class VideoStats {
this.measurementStartTimestamp = other.measurementStartTimestamp;
}
assert other.measurementStartTimestamp <= this.measurementStartTimestamp;
assert other.measurementStartTimestamp >= this.measurementStartTimestamp;
}
void copy(VideoStats other) {
@@ -50,7 +52,7 @@ class VideoStats {
}
VideoStatsFps getFps() {
float elapsed = (System.currentTimeMillis() - this.measurementStartTimestamp) / (float) 1000;
float elapsed = (SystemClock.uptimeMillis() - this.measurementStartTimestamp) / (float) 1000;
VideoStatsFps fps = new VideoStatsFps();
if (elapsed > 0) {
@@ -4,8 +4,10 @@ import java.io.IOException;
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;
import java.util.List;
@@ -38,6 +40,7 @@ import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import org.xmlpull.v1.XmlPullParserException;
@@ -134,6 +137,18 @@ public class ComputerManagerService extends Service {
dbManager.updateComputer(existingComputer);
}
else {
try {
// If the active address is a site-local address (RFC 1918),
// then use STUN to populate the external address field if
// it's not set already.
if (details.remoteAddress == null) {
InetAddress addr = InetAddress.getByName(details.activeAddress);
if (addr.isSiteLocalAddress()) {
populateExternalAddress(details);
}
}
} catch (UnknownHostException ignored) {}
dbManager.updateComputer(details);
}
}
@@ -162,7 +177,7 @@ public class ComputerManagerService extends Service {
LimeLog.warning(tuple.computer.name + " is offline (try " + offlineCount + ")");
offlineCount++;
} else {
tuple.lastSuccessfulPollMs = System.currentTimeMillis();
tuple.lastSuccessfulPollMs = SystemClock.elapsedRealtime();
offlineCount = 0;
}
}
@@ -193,7 +208,7 @@ public class ComputerManagerService extends Service {
synchronized (pollingTuples) {
for (PollingTuple tuple : pollingTuples) {
// Enforce the poll data TTL
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
if (SystemClock.elapsedRealtime() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
LimeLog.info("Timing out polled state for "+tuple.computer.name);
tuple.computer.state = ComputerDetails.State.UNKNOWN;
}
@@ -2,6 +2,7 @@ package com.limelight.grid;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -16,8 +17,12 @@ import com.limelight.grid.assets.NetworkAssetLoader;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.preferences.PreferenceConfiguration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SuppressWarnings("unchecked")
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
@@ -27,23 +32,49 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
private final ComputerDetails computer;
private final String uniqueId;
private final boolean showHiddenApps;
private CachedAppAssetLoader loader;
private Set<Integer> hiddenAppIds = new HashSet<>();
private ArrayList<AppView.AppObject> allApps = new ArrayList<>();
public AppGridAdapter(Context context, PreferenceConfiguration prefs, ComputerDetails computer, String uniqueId) {
public AppGridAdapter(Context context, PreferenceConfiguration prefs, ComputerDetails computer, String uniqueId, boolean showHiddenApps) {
super(context, getLayoutIdForPreferences(prefs));
this.computer = computer;
this.uniqueId = uniqueId;
this.showHiddenApps = showHiddenApps;
updateLayoutWithPreferences(context, prefs);
}
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
if (prefs.listMode) {
return R.layout.simple_row;
public void updateHiddenApps(Set<Integer> newHiddenAppIds, boolean hideImmediately) {
this.hiddenAppIds.clear();
this.hiddenAppIds.addAll(newHiddenAppIds);
if (hideImmediately) {
// Reconstruct the itemList with the new hidden app set
itemList.clear();
for (AppView.AppObject app : allApps) {
app.isHidden = hiddenAppIds.contains(app.app.getAppId());
if (!app.isHidden || showHiddenApps) {
itemList.add(app);
}
}
}
else if (prefs.smallIconMode) {
else {
// Just update the isHidden state to show the correct UI indication
for (AppView.AppObject app : allApps) {
app.isHidden = hiddenAppIds.contains(app.app.getAppId());
}
}
notifyDataSetChanged();
}
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
if (prefs.smallIconMode) {
return R.layout.app_grid_item_small;
}
else {
@@ -90,8 +121,8 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
loader.freeCacheMemory();
}
private void sortList() {
Collections.sort(itemList, new Comparator<AppView.AppObject>() {
private static void sortList(List<AppView.AppObject> list) {
Collections.sort(list, new Comparator<AppView.AppObject>() {
@Override
public int compare(AppView.AppObject lhs, AppView.AppObject rhs) {
return lhs.app.getAppName().toLowerCase().compareTo(rhs.app.getAppName().toLowerCase());
@@ -100,43 +131,54 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
}
public void addApp(AppView.AppObject app) {
// Queue a request to fetch this bitmap into cache
loader.queueCacheLoad(app.app);
// Update hidden state
app.isHidden = hiddenAppIds.contains(app.app.getAppId());
// Add the app to our sorted list
itemList.add(app);
sortList();
// Always add the app to the all apps list
allApps.add(app);
sortList(allApps);
// Add the app to the adapter data if it's not hidden
if (showHiddenApps || !app.isHidden) {
// Queue a request to fetch this bitmap into cache
loader.queueCacheLoad(app.app);
// Add the app to our sorted list
itemList.add(app);
sortList(itemList);
}
}
public void removeApp(AppView.AppObject app) {
itemList.remove(app);
allApps.remove(app);
}
@Override
public boolean populateImageView(ImageView imgView, ProgressBar prgView, AppView.AppObject obj) {
public void clear() {
super.clear();
allApps.clear();
}
@Override
public void populateView(View parentView, ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, AppView.AppObject obj) {
// Let the cached asset loader handle it
loader.populateImageView(obj.app, imgView, prgView);
return true;
}
loader.populateImageView(obj.app, imgView, txtView);
@Override
public boolean populateTextView(TextView txtView, AppView.AppObject obj) {
// Select the text view so it starts marquee mode
txtView.setSelected(true);
// Return false to use the app's toString method
return false;
}
@Override
public boolean populateOverlayView(ImageView overlayView, AppView.AppObject obj) {
if (obj.isRunning) {
// Show the play button overlay
overlayView.setImageResource(R.drawable.ic_play);
return true;
overlayView.setVisibility(View.VISIBLE);
}
else {
overlayView.setVisibility(View.GONE);
}
// No overlay
return false;
if (obj.isHidden) {
parentView.setAlpha(0.40f);
}
else {
parentView.setAlpha(1.0f);
}
}
}
@@ -10,7 +10,6 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import com.limelight.R;
import com.limelight.preferences.PreferenceConfiguration;
import java.util.ArrayList;
@@ -55,9 +54,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
return i;
}
public abstract boolean populateImageView(ImageView imgView, ProgressBar prgView, T obj);
public abstract boolean populateTextView(TextView txtView, T obj);
public abstract boolean populateOverlayView(ImageView overlayView, T obj);
public abstract void populateView(View parentView, ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, T obj);
@Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
@@ -70,22 +67,7 @@ public abstract class GenericGridAdapter<T> extends BaseAdapter {
TextView txtView = convertView.findViewById(R.id.grid_text);
ProgressBar prgView = convertView.findViewById(R.id.grid_spinner);
if (imgView != null) {
if (!populateImageView(imgView, prgView, itemList.get(i))) {
imgView.setImageBitmap(null);
}
}
if (!populateTextView(txtView, itemList.get(i))) {
txtView.setText(itemList.get(i).toString());
}
if (overlayView != null) {
if (!populateOverlayView(overlayView, itemList.get(i))) {
overlayView.setVisibility(View.INVISIBLE);
}
else {
overlayView.setVisibility(View.VISIBLE);
}
}
populateView(convertView, imgView, prgView, txtView, overlayView, itemList.get(i));
return convertView;
}
@@ -22,15 +22,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
}
private static int getLayoutIdForPreferences(PreferenceConfiguration prefs) {
if (prefs.listMode) {
return R.layout.simple_row;
}
else if (prefs.smallIconMode) {
return R.layout.pc_grid_item_small;
}
else {
return R.layout.pc_grid_item;
}
return R.layout.pc_grid_item;
}
public void updateLayoutWithPreferences(Context context, PreferenceConfiguration prefs) {
@@ -57,7 +49,8 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
}
@Override
public boolean populateImageView(ImageView imgView, ProgressBar prgView, PcView.ComputerObject obj) {
public void populateView(View parentView, ImageView imgView, ProgressBar prgView, TextView txtView, ImageView overlayView, PcView.ComputerObject obj) {
imgView.setImageResource(R.drawable.ic_computer);
if (obj.details.state == ComputerDetails.State.ONLINE) {
imgView.setAlpha(1.0f);
}
@@ -72,12 +65,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
prgView.setVisibility(View.INVISIBLE);
}
imgView.setImageResource(R.drawable.ic_computer);
return true;
}
@Override
public boolean populateTextView(TextView txtView, PcView.ComputerObject obj) {
txtView.setText(obj.details.name);
if (obj.details.state == ComputerDetails.State.ONLINE) {
txtView.setAlpha(1.0f);
}
@@ -85,16 +73,10 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
txtView.setAlpha(0.4f);
}
// Return false to use the computer's toString method
return false;
}
@Override
public boolean populateOverlayView(ImageView overlayView, PcView.ComputerObject obj) {
if (obj.details.state == ComputerDetails.State.OFFLINE) {
overlayView.setImageResource(R.drawable.ic_pc_offline);
overlayView.setAlpha(0.4f);
return true;
overlayView.setVisibility(View.VISIBLE);
}
// We must check if the status is exactly online and unpaired
// to avoid colliding with the loading spinner when status is unknown
@@ -102,8 +84,10 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
obj.details.pairState == PairingManager.PairState.NOT_PAIRED) {
overlayView.setImageResource(R.drawable.ic_lock);
overlayView.setAlpha(1.0f);
return true;
overlayView.setVisibility(View.VISIBLE);
}
else {
overlayView.setVisibility(View.GONE);
}
return false;
}
}
@@ -6,9 +6,12 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvApp;
@@ -89,7 +92,7 @@ public class CachedAppAssetLoader {
memoryLoader.clearCache();
}
private Bitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
private ScaledBitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
// Try 3 times
for (int i = 0; i < 3; i++) {
// Check again whether we've been cancelled or the image view is gone
@@ -110,7 +113,7 @@ public class CachedAppAssetLoader {
// If there's a task associated with this load, we should return the bitmap
if (task != null) {
// If the cached bitmap is valid, return it. Otherwise, we'll try the load again
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
ScaledBitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
if (bmp != null) {
return bmp;
}
@@ -132,29 +135,29 @@ public class CachedAppAssetLoader {
return null;
}
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
private class LoaderTask extends AsyncTask<LoaderTuple, Void, ScaledBitmap> {
private final WeakReference<ImageView> imageViewRef;
private final WeakReference<ProgressBar> progressViewRef;
private final WeakReference<TextView> textViewRef;
private final boolean diskOnly;
private LoaderTuple tuple;
public LoaderTask(ImageView imageView, ProgressBar prgView, boolean diskOnly) {
public LoaderTask(ImageView imageView, TextView textView, boolean diskOnly) {
this.imageViewRef = new WeakReference<>(imageView);
this.progressViewRef = new WeakReference<>(prgView);
this.textViewRef = new WeakReference<>(textView);
this.diskOnly = diskOnly;
}
@Override
protected Bitmap doInBackground(LoaderTuple... params) {
protected ScaledBitmap doInBackground(LoaderTuple... params) {
tuple = params[0];
// Check whether it has been cancelled or the views are gone
if (isCancelled() || imageViewRef.get() == null || progressViewRef.get() == null) {
if (isCancelled() || imageViewRef.get() == null || textViewRef.get() == null) {
return null;
}
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
ScaledBitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
if (bmp == null) {
if (!diskOnly) {
// Try to load the asset from the network
@@ -183,45 +186,61 @@ public class CachedAppAssetLoader {
// If the current loader task for this view isn't us, do nothing
final ImageView imageView = imageViewRef.get();
final ProgressBar prgView = progressViewRef.get();
final TextView textView = textViewRef.get();
if (getLoaderTask(imageView) == this) {
// Now display the progress bar since we have to hit the network
if (prgView != null) {
prgView.setVisibility(View.VISIBLE);
}
// Set off another loader task on the network executor. This time our AsyncDrawable
// will use the app image placeholder bitmap, rather than an empty bitmap.
LoaderTask task = new LoaderTask(imageView, prgView, false);
LoaderTask task = new LoaderTask(imageView, textView, false);
AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getResources(), noAppImageBitmap, task);
imageView.setVisibility(View.VISIBLE);
imageView.setImageDrawable(asyncDrawable);
imageView.startAnimation(AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadein));
imageView.setVisibility(View.VISIBLE);
textView.setVisibility(View.VISIBLE);
task.executeOnExecutor(networkExecutor, tuple);
}
}
@Override
protected void onPostExecute(Bitmap bitmap) {
protected void onPostExecute(final ScaledBitmap bitmap) {
// Do nothing if cancelled
if (isCancelled()) {
return;
}
final ImageView imageView = imageViewRef.get();
final ProgressBar prgView = progressViewRef.get();
final TextView textView = textViewRef.get();
if (getLoaderTask(imageView) == this) {
// Set the bitmap
// Fade in the box art
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
// Show the text if it's a placeholder
textView.setVisibility(isBitmapPlaceholder(bitmap) ? View.VISIBLE : View.GONE);
// Hide the progress bar
if (prgView != null) {
prgView.setVisibility(View.INVISIBLE);
}
if (imageView.getVisibility() == View.VISIBLE) {
// Fade out the placeholder first
Animation fadeOutAnimation = AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadeout);
fadeOutAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
// Show the view
imageView.setVisibility(View.VISIBLE);
@Override
public void onAnimationEnd(Animation animation) {
// Fade in the new box art
imageView.setImageBitmap(bitmap.bitmap);
imageView.startAnimation(AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadein));
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
imageView.startAnimation(fadeOutAnimation);
}
else {
// View is invisible already, so just fade in the new art
imageView.setImageBitmap(bitmap.bitmap);
imageView.startAnimation(AnimationUtils.loadAnimation(imageView.getContext(), R.anim.boxart_fadein));
imageView.setVisibility(View.VISIBLE);
}
}
}
}
}
@@ -299,7 +318,13 @@ public class CachedAppAssetLoader {
});
}
public boolean populateImageView(NvApp app, ImageView imgView, ProgressBar prgView) {
private boolean isBitmapPlaceholder(ScaledBitmap bitmap) {
return (bitmap == null) ||
(bitmap.originalWidth == 130 && bitmap.originalHeight == 180) || // GFE 2.0
(bitmap.originalWidth == 628 && bitmap.originalHeight == 888); // GFE 3.0
}
public boolean populateImageView(NvApp app, ImageView imgView, TextView textView) {
LoaderTuple tuple = new LoaderTuple(computer, app);
// If there's already a task in progress for this view,
@@ -309,22 +334,26 @@ public class CachedAppAssetLoader {
return true;
}
// Hide the progress bar always on initial load
prgView.setVisibility(View.INVISIBLE);
// Always set the name text so we have it if needed later
textView.setText(app.getAppName());
// First, try the memory cache in the current context
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
ScaledBitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
if (bmp != null) {
// Show the bitmap immediately
imgView.setVisibility(View.VISIBLE);
imgView.setImageBitmap(bmp);
imgView.setImageBitmap(bmp.bitmap);
// Show the text if it's a placeholder bitmap
textView.setVisibility(isBitmapPlaceholder(bmp) ? View.VISIBLE : View.GONE);
return true;
}
// If it's not in memory, create an async task to load it. This task will be attached
// via AsyncDrawable to this view.
final LoaderTask task = new LoaderTask(imgView, prgView, true);
final LoaderTask task = new LoaderTask(imgView, textView, true);
final AsyncDrawable asyncDrawable = new AsyncDrawable(imgView.getResources(), placeholderBitmap, task);
textView.setVisibility(View.INVISIBLE);
imgView.setVisibility(View.INVISIBLE);
imgView.setImageDrawable(asyncDrawable);
@@ -333,7 +362,7 @@ public class CachedAppAssetLoader {
return false;
}
public class LoaderTuple {
public static class LoaderTuple {
public final ComputerDetails computer;
public final NvApp app;
@@ -64,7 +64,7 @@ public class DiskAssetLoader {
return inSampleSize;
}
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
public ScaledBitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
File file = getFile(tuple.computer.uuid, tuple.app.getAppId());
// Don't bother with anything if it doesn't exist
@@ -110,27 +110,33 @@ public class DiskAssetLoader {
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
if (bmp != null) {
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
return new ScaledBitmap(decodeOnlyOptions.outWidth, decodeOnlyOptions.outHeight, bmp);
}
}
else {
// On P, we can get a bitmap back in one step with ImageDecoder
final ScaledBitmap scaledBitmap = new ScaledBitmap();
try {
bmp = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
scaledBitmap.bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), new ImageDecoder.OnHeaderDecodedListener() {
@Override
public void onHeaderDecoded(ImageDecoder imageDecoder, ImageDecoder.ImageInfo imageInfo, ImageDecoder.Source source) {
scaledBitmap.originalWidth = imageInfo.getSize().getWidth();
scaledBitmap.originalHeight = imageInfo.getSize().getHeight();
imageDecoder.setTargetSize(STANDARD_ASSET_WIDTH, STANDARD_ASSET_HEIGHT);
if (isLowRamDevice) {
imageDecoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
}
}
});
return scaledBitmap;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
return bmp;
return null;
}
public File getFile(String computerUuid, int appId) {
@@ -1,37 +1,74 @@
package com.limelight.grid.assets;
import android.graphics.Bitmap;
import android.util.LruCache;
import com.limelight.LimeLog;
import java.lang.ref.SoftReference;
import java.util.HashMap;
public class MemoryAssetLoader {
private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
private static final LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(maxMemory / 16) {
private static final LruCache<String, ScaledBitmap> memoryCache = new LruCache<String, ScaledBitmap>(maxMemory / 16) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
protected int sizeOf(String key, ScaledBitmap bitmap) {
// Sizeof returns kilobytes
return bitmap.getByteCount() / 1024;
return bitmap.bitmap.getByteCount() / 1024;
}
@Override
protected void entryRemoved(boolean evicted, String key, ScaledBitmap oldValue, ScaledBitmap newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
if (evicted) {
// Keep a soft reference around to the bitmap as long as we can
evictionCache.put(key, new SoftReference<>(oldValue));
}
}
};
private static final HashMap<String, SoftReference<ScaledBitmap>> evictionCache = new HashMap<>();
private static String constructKey(CachedAppAssetLoader.LoaderTuple tuple) {
return tuple.computer.uuid+"-"+tuple.app.getAppId();
}
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
Bitmap bmp = memoryCache.get(constructKey(tuple));
public ScaledBitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple) {
final String key = constructKey(tuple);
ScaledBitmap bmp = memoryCache.get(key);
if (bmp != null) {
LimeLog.info("Memory cache hit for tuple: "+tuple);
LimeLog.info("LRU cache hit for tuple: "+tuple);
return bmp;
}
return bmp;
SoftReference<ScaledBitmap> bmpRef = evictionCache.get(key);
if (bmpRef != null) {
bmp = bmpRef.get();
if (bmp != null) {
LimeLog.info("Eviction cache hit for tuple: "+tuple);
// Put this entry back into the LRU cache
evictionCache.remove(key);
memoryCache.put(key, bmp);
return bmp;
}
else {
// The data is gone, so remove the dangling SoftReference now
evictionCache.remove(key);
}
}
return null;
}
public void populateCache(CachedAppAssetLoader.LoaderTuple tuple, Bitmap bitmap) {
public void populateCache(CachedAppAssetLoader.LoaderTuple tuple, ScaledBitmap bitmap) {
memoryCache.put(constructKey(tuple), bitmap);
}
public void clearCache() {
// We must evict first because that will push all items into the eviction cache
memoryCache.evictAll();
evictionCache.clear();
}
}
@@ -0,0 +1,18 @@
package com.limelight.grid.assets;
import android.graphics.Bitmap;
public class ScaledBitmap {
public int originalWidth;
public int originalHeight;
public Bitmap bitmap;
public ScaledBitmap() {}
public ScaledBitmap(int originalWidth, int originalHeight, Bitmap bitmap) {
this.originalWidth = originalWidth;
this.originalHeight = originalHeight;
this.bitmap = bitmap;
}
}
@@ -43,25 +43,26 @@ public class NvConnection {
this.context = new ConnectionContext();
this.context.streamConfig = config;
this.context.serverCert = serverCert;
try {
// This is unique per connection
this.context.riKey = generateRiAesKey();
} catch (NoSuchAlgorithmException e) {
// Should never happen
e.printStackTrace();
}
this.context.riKeyId = generateRiKeyId();
// This is unique per connection
this.context.riKey = generateRiAesKey();
context.riKeyId = generateRiKeyId();
this.isMonkey = ActivityManager.isUserAMonkey();
}
private static SecretKey generateRiAesKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
// RI keys are 128 bits
keyGen.init(128);
return keyGen.generateKey();
private static SecretKey generateRiAesKey() {
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
// RI keys are 128 bits
keyGen.init(128);
return keyGen.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private static int generateRiKeyId() {
@@ -114,7 +115,17 @@ public class NvConnection {
//
// Check for a supported stream resolution
if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
(h.getServerCodecModeSupport(serverInfo) & 0x200) == 0) {
context.connListener.displayMessage("Your host PC does not support streaming at resolutions above 4K.");
return false;
}
else if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
!context.streamConfig.getHevcSupported()) {
context.connListener.displayMessage("Your streaming device must support H.265 to stream at resolutions above 4K.");
return false;
}
else if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
// Client wants 4K but the server can't do it
context.connListener.displayTransientMessage("You must update GeForce Experience to stream in 4K. The stream will be 1080p.");
@@ -230,18 +241,19 @@ public class NvConnection {
try {
if (!startApp()) {
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, 0, 0);
return;
}
context.connListener.stageComplete(appName);
} catch (GfeHttpResponseException e) {
e.printStackTrace();
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, e.getErrorCode());
context.connListener.stageFailed(appName, 0, e.getErrorCode());
return;
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, MoonBridge.ML_PORT_FLAG_TCP_47984 | MoonBridge.ML_PORT_FLAG_TCP_47989, 0);
return;
}
@@ -254,7 +266,7 @@ public class NvConnection {
connectionAllowed.acquire();
} catch (InterruptedException e) {
context.connListener.displayMessage(e.getMessage());
context.connListener.stageFailed(appName, 0);
context.connListener.stageFailed(appName, 0, 0);
return;
}
@@ -348,6 +360,12 @@ public class NvConnection {
}
}
public void sendMouseHighResScroll(final short scrollAmount) {
if (!isMonkey) {
MoonBridge.sendMouseHighResScroll(scrollAmount);
}
}
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
}
@@ -3,7 +3,7 @@ package com.limelight.nvstream;
public interface NvConnectionListener {
void stageStarting(String stage);
void stageComplete(String stage);
void stageFailed(String stage, int errorCode);
void stageFailed(String stage, int portFlags, int errorCode);
void connectionStarted();
void connectionTerminated(int errorCode);
@@ -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);
int frameNumber, long receiveTimeMs, long enqueueTimeMs);
public abstract void cleanup();
@@ -69,9 +69,9 @@ public class ComputerDetails {
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("Name: ").append(name).append("\n");
str.append("State: ").append(state).append("\n");
str.append("Active Address: ").append(activeAddress).append("\n");
str.append("Name: ").append(name).append("\n");
str.append("UUID: ").append(uuid).append("\n");
str.append("Local Address: ").append(localAddress).append("\n");
str.append("Remote Address: ").append(remoteAddress).append("\n");
@@ -58,4 +58,13 @@ public class NvApp {
public boolean isInitialized() {
return this.initialized;
}
@Override
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("ID: ").append(appId).append("\n");
return str.toString();
}
}
@@ -1,20 +1,26 @@
package com.limelight.nvstream.http;
import android.os.Build;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
@@ -24,11 +30,16 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
@@ -42,7 +53,6 @@ import com.limelight.nvstream.ConnectionContext;
import com.limelight.nvstream.http.PairingManager.PairState;
import okhttp3.ConnectionPool;
import okhttp3.Handshake;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@@ -66,34 +76,36 @@ public class NvHTTP {
private OkHttpClient httpClient;
private OkHttpClient httpClientWithReadTimeout;
private X509TrustManager defaultTrustManager;
private X509TrustManager trustManager;
private X509KeyManager keyManager;
private X509Certificate serverCert;
void setServerCert(X509Certificate serverCert) {
this.serverCert = serverCert;
trustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
throw new IllegalStateException("Should never be called");
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
// Check the server certificate if we've paired to this host
if (!certs[0].equals(NvHTTP.this.serverCert)) {
throw new CertificateException("Certificate mismatch");
}
}
};
}
private void initializeHttpState(final X509Certificate serverCert, final LimelightCryptoProvider cryptoProvider) {
// Set up TrustManager
setServerCert(serverCert);
private static X509TrustManager getDefaultTrustManager() {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
return (X509TrustManager) tm;
}
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
throw new IllegalStateException("No X509 trust manager found");
}
private void initializeHttpState(final LimelightCryptoProvider cryptoProvider) {
keyManager = new X509KeyManager() {
public String chooseClientAlias(String[] keyTypes,
Principal[] issuers, Socket socket) { return "Limelight-RSA"; }
@@ -109,9 +121,51 @@ public class NvHTTP {
public String[] getServerAliases(String keyType, Principal[] issuers) { return null; }
};
// Ignore differences between given hostname and certificate hostname
defaultTrustManager = getDefaultTrustManager();
trustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
throw new IllegalStateException("Should never be called");
}
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
try {
// Try the default trust manager first to allow pairing with certificates
// that chain up to a trusted root CA. This will raise CertificateException
// if the certificate is not trusted (expected for GFE's self-signed certs).
defaultTrustManager.checkServerTrusted(certs, authType);
} catch (CertificateException e) {
// Check the server certificate if we've paired to this host
if (certs.length == 1 && NvHTTP.this.serverCert != null) {
if (!certs[0].equals(NvHTTP.this.serverCert)) {
throw new CertificateException("Certificate mismatch");
}
}
else {
// The cert chain doesn't look like a self-signed cert or we don't have
// a certificate pinned, so re-throw the original validation error.
throw e;
}
}
}
};
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) { return true; }
public boolean verify(String hostname, SSLSession session) {
try {
Certificate[] certificates = session.getPeerCertificates();
if (certificates.length == 1 && certificates[0].equals(NvHTTP.this.serverCert)) {
// Allow any hostname if it's our pinned cert
return true;
}
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
}
// Fall back to default HostnameVerifier for validating CA-issued certs
return HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session);
}
};
httpClient = new OkHttpClient.Builder()
@@ -131,7 +185,9 @@ public class NvHTTP {
// started by other Moonlight clients.
this.uniqueId = "0123456789ABCDEF";
initializeHttpState(serverCert, cryptoProvider);
this.serverCert = serverCert;
initializeHttpState(cryptoProvider);
try {
// The URI constructor takes care of escaping IPv6 literals
@@ -171,7 +227,7 @@ public class NvHTTP {
break;
case (XmlPullParser.TEXT):
if (currentTag.peek().equals(tagname)) {
return xpp.getText().trim();
return xpp.getText();
}
break;
}
@@ -186,20 +242,20 @@ public class NvHTTP {
}
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
String statusCodeText = xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_code");
if (statusCodeText == null) {
throw new GfeHttpResponseException(418, "Status code is missing");
}
try {
int statusCode = Integer.parseInt(statusCodeText);
if (statusCode != 200) {
throw new GfeHttpResponseException(statusCode, xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_message"));
// We use Long.parseLong() because in rare cases GFE can send back a status code of
// 0xFFFFFFFF, which will cause Integer.parseInt() to throw a NumberFormatException due
// to exceeding Integer.MAX_VALUE. We'll get the desired error code of -1 by just casting
// the resulting long into an int.
int statusCode = (int)Long.parseLong(xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_code"));
if (statusCode != 200) {
String statusMsg = xpp.getAttributeValue(XmlPullParser.NO_NAMESPACE, "status_message");
if (statusCode == -1 && "Invalid".equals(statusMsg)) {
// Special case handling an audio capture error which GFE doesn't
// provide any useful status message for.
statusCode = 418;
statusMsg = "Missing audio capture device. Reinstall GeForce Experience.";
}
}
catch (NumberFormatException e) {
// It seems like GFE 3.20.3.63 is returning garbage for status_code in rare cases.
// Surface this in a more friendly way rather than crashing.
throw new GfeHttpResponseException(418, "Status code is not a number: "+statusCodeText);
throw new GfeHttpResponseException(statusCode, statusMsg);
}
}
@@ -270,11 +326,7 @@ public class NvHTTP {
// This has some extra logic to always report unpaired if the pinned cert isn't there
details.pairState = getPairState(serverInfo);
try {
details.runningGameId = getCurrentGame(serverInfo);
} catch (NumberFormatException e) {
details.runningGameId = 0;
}
details.runningGameId = getCurrentGame(serverInfo);
// We could reach it so it's online
details.state = ComputerDetails.State.ONLINE;
@@ -290,24 +342,21 @@ public class NvHTTP {
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(new KeyManager[] { keyManager }, new TrustManager[] { trustManager }, new SecureRandom());
return client.newBuilder().sslSocketFactory(sc.getSocketFactory(), trustManager).build();
// TLS 1.2 is not enabled by default prior to Android 5.0, so we'll need a custom
// SSLSocketFactory in order to connect to GFE 3.20.4 which requires TLSv1.2 or later.
// We don't just always use TLSv12SocketFactory because explicitly specifying TLS versions
// prevents later TLS versions from being negotiated even if client and server otherwise
// support them.
return client.newBuilder().sslSocketFactory(
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
sc.getSocketFactory() : new TLSv12SocketFactory(sc),
trustManager).build();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException(e);
}
}
public X509Certificate getCertificateIfTrusted() {
try {
Response resp = httpClient.newCall(new Request.Builder().url(baseUrlHttps).get().build()).execute();
Handshake handshake = resp.handshake();
if (handshake != null) {
return (X509Certificate)handshake.peerCertificates().get(0);
}
} catch (IOException ignored) {}
return null;
}
// 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
@@ -316,10 +365,6 @@ public class NvHTTP {
Request request = new Request.Builder().url(url).get().build();
Response response;
if (serverCert == null && !url.startsWith(baseUrlHttp)) {
throw new IllegalStateException("Attempted HTTPS fetch without pinned cert");
}
if (enableReadTimeout) {
response = performAndroidTlsHack(httpClientWithReadTimeout).newCall(request).execute();
}
@@ -379,11 +424,6 @@ public class NvHTTP {
}
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
// If we don't have a server cert, we can't be paired even if the host thinks we are
if (serverCert == null) {
return PairState.NOT_PAIRED;
}
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
return PairState.NOT_PAIRED;
}
@@ -394,11 +434,7 @@ public class NvHTTP {
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsH264");
if (str != null) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return 0;
}
return Long.parseLong(str);
} else {
return 0;
}
@@ -407,11 +443,7 @@ public class NvHTTP {
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
if (str != null) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return 0;
}
return Long.parseLong(str);
} else {
return 0;
}
@@ -428,11 +460,7 @@ public class NvHTTP {
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "ServerCodecModeSupport");
if (str != null) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return 0;
}
return Long.parseLong(str);
} else {
return 0;
}
@@ -529,11 +557,11 @@ public class NvHTTP {
case (XmlPullParser.TEXT):
NvApp app = appList.getLast();
if (currentTag.peek().equals("AppTitle")) {
app.setAppName(xpp.getText().trim());
app.setAppName(xpp.getText());
} else if (currentTag.peek().equals("ID")) {
app.setAppId(xpp.getText().trim());
app.setAppId(xpp.getText());
} else if (currentTag.peek().equals("IsHdrSupported")) {
app.setHdrSupported(xpp.getText().trim().equals("1"));
app.setHdrSupported(xpp.getText().equals("1"));
}
break;
}
@@ -588,36 +616,23 @@ public class NvHTTP {
}
public int getServerMajorVersion(String serverInfo) throws XmlPullParserException, IOException {
int[] appVersionQuad = getServerAppVersionQuad(serverInfo);
if (appVersionQuad != null) {
return appVersionQuad[0];
}
else {
return 0;
}
return getServerAppVersionQuad(serverInfo)[0];
}
public int[] getServerAppVersionQuad(String serverInfo) throws XmlPullParserException, IOException {
try {
String serverVersion = getServerVersion(serverInfo);
if (serverVersion == null) {
LimeLog.warning("Missing server version field");
return null;
}
String[] serverVersionSplit = serverVersion.split("\\.");
if (serverVersionSplit.length != 4) {
LimeLog.warning("Malformed server version field");
return null;
}
int[] ret = new int[serverVersionSplit.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = Integer.parseInt(serverVersionSplit[i]);
}
return ret;
} catch (NumberFormatException e) {
e.printStackTrace();
return null;
String serverVersion = getServerVersion(serverInfo);
if (serverVersion == null) {
throw new IllegalArgumentException("Missing server version field");
}
String[] serverVersionSplit = serverVersion.split("\\.");
if (serverVersionSplit.length != 4) {
throw new IllegalArgumentException("Malformed server version field: "+serverVersion);
}
int[] ret = new int[serverVersionSplit.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = Integer.parseInt(serverVersionSplit[i]);
}
return ret;
}
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
@@ -632,6 +647,12 @@ public class NvHTTP {
}
public boolean launchApp(ConnectionContext context, int appId, boolean enableHdr) throws IOException, XmlPullParserException {
// Using an FPS value over 60 causes SOPS to default to 720p60,
// so force it to 0 to ensure the correct resolution is set. We
// used to use 60 here but that locked the frame rate to 60 FPS
// on GFE 3.20.3.
int fps = context.streamConfig.getLaunchRefreshRate() > 60 ? 0 : context.streamConfig.getLaunchRefreshRate();
// Using an unsupported resolution (not 720p, 1080p, or 4K) causes
// GFE to force SOPS to 720p60. This is fine for < 720p resolutions like
// 360p or 480p, but it is not ideal for 1440p and other resolutions.
@@ -645,20 +666,10 @@ public class NvHTTP {
enableSops = false;
}
// Using SOPS with FPS values over 60 causes GFE to fall back
// to 720p60. On previous GFE versions, we could avoid this by
// forcing the FPS value to 60 when launching the stream, but
// now on GFE 3.20.3 that seems to trigger some sort of
// frame rate limiter that locks the game to 60 FPS.
if (context.streamConfig.getLaunchRefreshRate() > 60) {
LimeLog.info("Disabling SOPS due to high frame rate: "+context.streamConfig.getLaunchRefreshRate());
enableSops = false;
}
String xmlStr = openHttpConnectionToString(baseUrlHttps +
"/launch?" + buildUniqueIdUuidString() +
"&appid=" + appId +
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + context.streamConfig.getLaunchRefreshRate() +
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
"&rikeyid="+context.riKeyId +
@@ -699,4 +710,62 @@ public class NvHTTP {
return true;
}
// Based on example code from https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/
private static class TLSv12SocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
public TLSv12SocketFactory(SSLContext context) {
internalSSLSocketFactory = context.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket());
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSv12OnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSv12OnSocket(Socket socket) {
if (socket instanceof SSLSocket) {
// TLS 1.2 is not enabled by default prior to Android 5.0. We must enable it
// explicitly to ensure we can communicate with GFE 3.20.4 which blocks TLS 1.0.
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"});
}
return socket;
}
}
}
@@ -14,7 +14,6 @@ import java.security.*;
import java.security.cert.*;
import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
public class PairingManager {
@@ -55,6 +54,10 @@ public class PairingManager {
private static byte[] hexToBytes(String s) {
int len = s.length();
if (len % 2 != 0) {
throw new IllegalArgumentException("Illegal string length: "+len);
}
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
@@ -74,7 +77,7 @@ public class PairingManager {
return (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certBytes));
} catch (CertificateException e) {
e.printStackTrace();
return null;
throw new RuntimeException(e);
}
}
else {
@@ -168,7 +171,7 @@ public class PairingManager {
}
public static String generatePinString() {
Random r = new Random();
SecureRandom r = new SecureRandom();
return String.format((Locale)null, "%d%d%d%d",
r.nextInt(10), r.nextInt(10),
r.nextInt(10), r.nextInt(10));
@@ -314,9 +317,8 @@ public class PairingManager {
return md.digest(data);
}
catch (NoSuchAlgorithmException e) {
// Shouldn't ever happen
e.printStackTrace();
return null;
throw new RuntimeException(e);
}
}
}
@@ -332,9 +334,8 @@ public class PairingManager {
return md.digest(data);
}
catch (NoSuchAlgorithmException e) {
// Shouldn't ever happen
e.printStackTrace();
return null;
throw new RuntimeException(e);
}
}
}
@@ -33,6 +33,30 @@ public class MoonBridge {
public static final int CONN_STATUS_OKAY = 0;
public static final int CONN_STATUS_POOR = 1;
public static final int ML_ERROR_GRACEFUL_TERMINATION = 0;
public static final int ML_ERROR_NO_VIDEO_TRAFFIC = -100;
public static final int ML_ERROR_NO_VIDEO_FRAME = -101;
public static final int ML_ERROR_UNEXPECTED_EARLY_TERMINATION = -102;
public static final int ML_PORT_INDEX_TCP_47984 = 0;
public static final int ML_PORT_INDEX_TCP_47989 = 1;
public static final int ML_PORT_INDEX_TCP_48010 = 2;
public static final int ML_PORT_INDEX_UDP_47998 = 8;
public static final int ML_PORT_INDEX_UDP_47999 = 9;
public static final int ML_PORT_INDEX_UDP_48000 = 10;
public static final int ML_PORT_INDEX_UDP_48010 = 11;
public static final int ML_PORT_FLAG_ALL = 0xFFFFFFFF;
public static final int ML_PORT_FLAG_TCP_47984 = 0x0001;
public static final int ML_PORT_FLAG_TCP_47989 = 0x0002;
public static final int ML_PORT_FLAG_TCP_48010 = 0x0004;
public static final int ML_PORT_FLAG_UDP_47998 = 0x0100;
public static final int ML_PORT_FLAG_UDP_47999 = 0x0200;
public static final int ML_PORT_FLAG_UDP_48000 = 0x0400;
public static final int ML_PORT_FLAG_UDP_48010 = 0x0800;
public static final int ML_TEST_RESULT_INCONCLUSIVE = 0xFFFFFFFF;
private static AudioRenderer audioRenderer;
private static VideoDecoderRenderer videoRenderer;
private static NvConnectionListener connectionListener;
@@ -125,11 +149,11 @@ public class MoonBridge {
}
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength,
int decodeUnitType,
int frameNumber, long receiveTimeMs) {
int decodeUnitType, int frameNumber,
long receiveTimeMs, long enqueueTimeMs) {
if (videoRenderer != null) {
return videoRenderer.submitDecodeUnit(decodeUnitData, decodeUnitLength,
decodeUnitType, frameNumber, receiveTimeMs);
decodeUnitType, frameNumber, receiveTimeMs, enqueueTimeMs);
}
else {
return DR_OK;
@@ -183,7 +207,7 @@ public class MoonBridge {
public static void bridgeClStageFailed(int stage, int errorCode) {
if (connectionListener != null) {
connectionListener.stageFailed(getStageName(stage), errorCode);
connectionListener.stageFailed(getStageName(stage), getPortFlagsFromStage(stage), errorCode);
}
}
@@ -258,6 +282,8 @@ public class MoonBridge {
public static native void sendMouseScroll(byte scrollClicks);
public static native void sendMouseHighResScroll(short scrollAmount);
public static native String getStageName(int stage);
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
@@ -266,5 +292,13 @@ public class MoonBridge {
public static native int getPendingVideoFrames();
public static native int testClientConnectivity(String testServerHostName, int referencePort, int testFlags);
public static native int getPortFlagsFromStage(int stage);
public static native int getPortFlagsFromTerminationErrorCode(int errorCode);
public static native String stringifyPortFlags(int portFlags, String separator);
public static native void init();
}
@@ -11,8 +11,9 @@ import com.limelight.nvstream.http.ComputerDetails;
public class WakeOnLanSender {
private static final int[] PORTS_TO_TRY = new int[] {
7, 9, // Standard WOL ports
47998, 47999, 48000, 48002, 48010 // Ports opened by GFE
9, // Standard WOL port (privileged port)
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
};
public static void sendWolPacket(ComputerDetails computer) throws IOException {
@@ -15,7 +15,9 @@ import com.limelight.computers.ComputerManagerService;
import com.limelight.R;
import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.jni.MoonBridge;
import com.limelight.utils.Dialog;
import com.limelight.utils.ServerHelper;
import com.limelight.utils.SpinnerDialog;
import com.limelight.utils.UiHelper;
@@ -97,6 +99,7 @@ public class AddComputerManually extends Activity {
private void doAddPc(String host) {
boolean wrongSiteLocal = false;
boolean success;
int portTestResult;
SpinnerDialog dialog = SpinnerDialog.displayDialog(this, getResources().getString(R.string.title_add_pc),
getResources().getString(R.string.msg_add_pc), false);
@@ -104,12 +107,6 @@ public class AddComputerManually extends Activity {
try {
ComputerDetails details = new ComputerDetails();
details.manualAddress = host;
try {
NvHTTP http = new NvHTTP(host, managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(this));
details.serverCert = http.getCertificateIfTrusted();
} catch (IOException ignored) {}
success = managerBinder.addComputerBlocking(details);
} catch (IllegalArgumentException e) {
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
@@ -120,6 +117,14 @@ public class AddComputerManually extends Activity {
if (!success){
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
}
if (!success && !wrongSiteLocal) {
// Run the test before dismissing the spinner because it can take a few seconds.
portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER, 443,
MoonBridge.ML_PORT_FLAG_TCP_47984 | MoonBridge.ML_PORT_FLAG_TCP_47989);
} else {
// Don't bother with the test if we succeeded or the IP address was bogus
portTestResult = MoonBridge.ML_TEST_RESULT_INCONCLUSIVE;
}
dialog.dismiss();
@@ -127,7 +132,14 @@ public class AddComputerManually extends Activity {
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_wrong_sitelocal), false);
}
else if (!success) {
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), getResources().getString(R.string.addpc_fail), false);
String dialogText;
if (portTestResult != MoonBridge.ML_TEST_RESULT_INCONCLUSIVE && portTestResult != 0) {
dialogText = getResources().getString(R.string.nettest_text_blocked);
}
else {
dialogText = getResources().getString(R.string.addpc_fail);
}
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title), dialogText, false);
}
else {
AddComputerManually.this.runOnUiThread(new Runnable() {
@@ -23,7 +23,6 @@ public class PreferenceConfiguration {
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
private static final String OSC_OPACITY_PREF_STRING = "seekbar_osc_opacity";
private static final String LANGUAGE_PREF_STRING = "list_languages";
private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode";
private static final String SMALL_ICONS_PREF_STRING = "checkbox_small_icon_mode";
private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
static final String AUDIO_CONFIG_PREF_STRING = "list_audio_config";
@@ -41,8 +40,11 @@ public class PreferenceConfiguration {
static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps";
private static final String VIBRATE_OSC_PREF_STRING = "checkbox_vibrate_osc";
private static final String VIBRATE_FALLBACK_PREF_STRING = "checkbox_vibrate_fallback";
private static final String FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons";
private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast";
static final String DEFAULT_RESOLUTION = "720p";
static final String DEFAULT_RESOLUTION = "1280x720";
static final String DEFAULT_FPS = "60";
private static final boolean DEFAULT_STRETCH = false;
private static final boolean DEFAULT_SOPS = true;
@@ -51,7 +53,6 @@ public class PreferenceConfiguration {
private static final int DEFAULT_DEADZONE = 15;
private static final int DEFAULT_OPACITY = 90;
public static final String DEFAULT_LANGUAGE = "default";
private static final boolean DEFAULT_LIST_MODE = false;
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
private static final boolean DEFAULT_USB_DRIVER = true;
private static final String DEFAULT_VIDEO_FORMAT = "auto";
@@ -67,12 +68,23 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_UNLOCK_FPS = false;
private static final boolean DEFAULT_VIBRATE_OSC = true;
private static final boolean DEFAULT_VIBRATE_FALLBACK = false;
private static final boolean DEFAULT_FLIP_FACE_BUTTONS = false;
private static final boolean DEFAULT_TOUCHSCREEN_TRACKPAD = true;
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
private static final boolean DEFAULT_LATENCY_TOAST = false;
public static final int FORCE_H265_ON = -1;
public static final int AUTOSELECT_H265 = 0;
public static final int FORCE_H265_OFF = 1;
public static final String RES_360P = "640x360";
public static final String RES_480P = "854x480";
public static final String RES_720P = "1280x720";
public static final String RES_1080P = "1920x1080";
public static final String RES_1440P = "2560x1440";
public static final String RES_4K = "3840x2160";
public static final String RES_NATIVE = "Native";
public int width, height, fps;
public int bitrate;
public int videoFormat;
@@ -80,73 +92,95 @@ public class PreferenceConfiguration {
public int oscOpacity;
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
public String language;
public boolean listMode, smallIconMode, multiController, usbDriver;
public boolean smallIconMode, multiController, usbDriver, flipFaceButtons;
public boolean onscreenController;
public boolean onlyL3R3;
public boolean disableFrameDrop;
public boolean enableHdr;
public boolean enablePip;
public boolean enablePerfOverlay;
public boolean enableLatencyToast;
public boolean bindAllUsb;
public boolean mouseEmulation;
public boolean mouseNavButtons;
public boolean unlockFps;
public boolean vibrateOsc;
public boolean vibrateFallbackToDevice;
public boolean touchscreenTrackpad;
public MoonBridge.AudioConfiguration audioConfiguration;
private static int getHeightFromResolutionString(String resString) {
public static boolean isNativeResolution(int width, int height) {
// It's not a native resolution if it matches an existing resolution option
if (width == 640 && height == 360) {
return false;
}
else if (width == 854 && height == 480) {
return false;
}
else if (width == 1280 && height == 720) {
return false;
}
else if (width == 1920 && height == 1080) {
return false;
}
else if (width == 2560 && height == 1440) {
return false;
}
else if (width == 3840 && height == 2160) {
return false;
}
return true;
}
private static String convertFromLegacyResolutionString(String resString) {
if (resString.equalsIgnoreCase("360p")) {
return 360;
return RES_360P;
}
else if (resString.equalsIgnoreCase("480p")) {
return 480;
return RES_480P;
}
else if (resString.equalsIgnoreCase("720p")) {
return 720;
return RES_720P;
}
else if (resString.equalsIgnoreCase("1080p")) {
return 1080;
return RES_1080P;
}
else if (resString.equalsIgnoreCase("1440p")) {
return 1440;
return RES_1440P;
}
else if (resString.equalsIgnoreCase("4K")) {
return 2160;
return RES_4K;
}
else {
// Should be unreachable
return 720;
return RES_720P;
}
}
private static int getWidthFromResolutionString(String resString) {
int height = getHeightFromResolutionString(resString);
if (height == 480) {
// This isn't an exact 16:9 resolution
return 854;
}
else {
return (height * 16) / 9;
}
return Integer.parseInt(resString.split("x")[0]);
}
private static int getHeightFromResolutionString(String resString) {
return Integer.parseInt(resString.split("x")[1]);
}
private static String getResolutionString(int width, int height) {
switch (height) {
case 360:
return "360p";
return RES_360P;
case 480:
return "480p";
return RES_480P;
default:
case 720:
return "720p";
return RES_720P;
case 1080:
return "1080p";
return RES_1080P;
case 1440:
return "1440p";
return RES_1440P;
case 2160:
return "4K";
return RES_4K;
}
}
@@ -317,11 +351,24 @@ public class PreferenceConfiguration {
else {
// Use the new preference location
String resStr = prefs.getString(RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION);
// Convert legacy resolution strings to the new style
if (!resStr.contains("x")) {
resStr = PreferenceConfiguration.convertFromLegacyResolutionString(resStr);
prefs.edit().putString(RESOLUTION_PREF_STRING, resStr).apply();
}
config.width = PreferenceConfiguration.getWidthFromResolutionString(resStr);
config.height = PreferenceConfiguration.getHeightFromResolutionString(resStr);
config.fps = Integer.parseInt(prefs.getString(FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS));
}
if (!prefs.contains(SMALL_ICONS_PREF_STRING)) {
// We need to write small icon mode's default to disk for the settings page to display
// the current state of the option properly
prefs.edit().putBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context)).apply();
}
// This must happen after the preferences migration to ensure the preferences are populated
config.bitrate = prefs.getInt(BITRATE_PREF_STRING, prefs.getInt(BITRATE_PREF_OLD_STRING, 0) * 1000);
if (config.bitrate == 0) {
@@ -352,7 +399,6 @@ public class PreferenceConfiguration {
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH);
config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO);
config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE);
config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, getDefaultSmallMode(context));
config.multiController = prefs.getBoolean(MULTI_CONTROLLER_PREF_STRING, DEFAULT_MULTI_CONTROLLER);
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
@@ -368,6 +414,9 @@ public class PreferenceConfiguration {
config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS);
config.vibrateOsc = prefs.getBoolean(VIBRATE_OSC_PREF_STRING, DEFAULT_VIBRATE_OSC);
config.vibrateFallbackToDevice = prefs.getBoolean(VIBRATE_FALLBACK_PREF_STRING, DEFAULT_VIBRATE_FALLBACK);
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);
return config;
}
@@ -14,6 +14,8 @@ import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.util.Locale;
// Based on a Stack Overflow example: http://stackoverflow.com/questions/1974193/slider-on-my-preferencescreen
public class SeekBarPreference extends DialogPreference
{
@@ -30,6 +32,8 @@ public class SeekBarPreference extends DialogPreference
private final int maxValue;
private final int minValue;
private final int stepSize;
private final int keyStepSize;
private final int divisor;
private int currentValue;
public SeekBarPreference(Context context, AttributeSet attrs) {
@@ -59,6 +63,8 @@ public class SeekBarPreference extends DialogPreference
maxValue = attrs.getAttributeIntValue(ANDROID_SCHEMA_URL, "max", 100);
minValue = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "min", 1);
stepSize = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "step", 1);
divisor = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "divisor", 1);
keyStepSize = attrs.getAttributeIntValue(SEEKBAR_SCHEMA_URL, "keyStep", 0);
}
@Override
@@ -101,7 +107,14 @@ public class SeekBarPreference extends DialogPreference
return;
}
String t = String.valueOf(value);
String t;
if (divisor != 1) {
float floatValue = roundedValue / (float)divisor;
t = String.format((Locale)null, "%.1f", floatValue);
}
else {
t = String.valueOf(value);
}
valueText.setText(suffix == null ? t : t.concat(suffix.length() > 1 ? " "+suffix : suffix));
}
@@ -119,6 +132,9 @@ public class SeekBarPreference extends DialogPreference
}
seekBar.setMax(maxValue);
if (keyStepSize != 0) {
seekBar.setKeyProgressIncrement(keyStepSize);
}
seekBar.setProgress(currentValue);
return layout;
@@ -128,6 +144,9 @@ public class SeekBarPreference extends DialogPreference
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
seekBar.setMax(maxValue);
if (keyStepSize != 0) {
seekBar.setKeyProgressIncrement(keyStepSize);
}
seekBar.setProgress(currentValue);
}
@@ -1,5 +1,6 @@
package com.limelight.preferences;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.MediaCodecInfo;
@@ -7,6 +8,7 @@ import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.os.Handler;
import android.os.Vibrator;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
@@ -23,8 +25,11 @@ import com.limelight.LimeLog;
import com.limelight.PcView;
import com.limelight.R;
import com.limelight.binding.video.MediaCodecHelper;
import com.limelight.utils.Dialog;
import com.limelight.utils.UiHelper;
import java.util.Arrays;
public class StreamSettings extends Activity {
private PreferenceConfiguration previousPrefs;
@@ -54,9 +59,7 @@ public class StreamSettings extends Activity {
// Check for changes that require a UI reload to take effect
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
if (newPrefs.listMode != previousPrefs.listMode ||
newPrefs.smallIconMode != previousPrefs.smallIconMode ||
!newPrefs.language.equals(previousPrefs.language)) {
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);
@@ -65,6 +68,7 @@ public class StreamSettings extends Activity {
}
public static class SettingsFragment extends PreferenceFragment {
private int nativeResolutionStartIndex = Integer.MAX_VALUE;
private void setValue(String preferenceKey, String value) {
ListPreference pref = (ListPreference) findPreference(preferenceKey);
@@ -72,6 +76,37 @@ public class StreamSettings extends Activity {
pref.setValue(value);
}
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight) {
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
String newName = getResources().getString(R.string.resolution_prefix_native) + " ("+nativeWidth+"x"+nativeHeight+")";
String newValue = nativeWidth+"x"+nativeHeight;
CharSequence[] values = pref.getEntryValues();
// Check if the native resolution is already present
for (CharSequence value : values) {
if (newValue.equals(value.toString())) {
// It is present in the default list, so don't add it again
return;
}
}
CharSequence[] newEntries = Arrays.copyOf(pref.getEntries(), pref.getEntries().length + 1);
CharSequence[] newValues = Arrays.copyOf(values, values.length + 1);
// Add the new native option
newEntries[newEntries.length - 1] = newName;
newValues[newValues.length - 1] = newValue;
pref.setEntries(newEntries);
pref.setEntryValues(newValues);
if (newValues.length - 1 < nativeResolutionStartIndex) {
nativeResolutionStartIndex = newValues.length - 1;
}
}
private void removeValue(String preferenceKey, String value, Runnable onMatched) {
int matchingCount = 0;
@@ -108,8 +143,6 @@ public class StreamSettings extends Activity {
pref.setEntryValues(entryValues);
}
private void resetBitrateToDefault(SharedPreferences prefs, String res, String fps) {
if (res == null) {
res = prefs.getString(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION);
@@ -141,18 +174,42 @@ public class StreamSettings extends Activity {
// hide on-screen controls category on non touch screen devices
if (!getActivity().getPackageManager().
hasSystemFeature("android.hardware.touchscreen")) {
PreferenceCategory category =
(PreferenceCategory) findPreference("category_onscreen_controls");
screen.removePreference(category);
{
PreferenceCategory category =
(PreferenceCategory) findPreference("category_onscreen_controls");
screen.removePreference(category);
}
{
PreferenceCategory category =
(PreferenceCategory) findPreference("category_input_settings");
category.removePreference(findPreference("checkbox_touchscreen_trackpad"));
}
}
// Remove PiP mode on devices pre-Oreo
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// 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 ||
!getActivity().getPackageManager().hasSystemFeature("android.software.picture_in_picture") ||
getActivity().getPackageManager().hasSystemFeature("com.amazon.software.fireos")) {
PreferenceCategory category =
(PreferenceCategory) findPreference("category_basic_settings");
(PreferenceCategory) findPreference("category_ui_settings");
category.removePreference(findPreference("checkbox_enable_pip"));
}
// Remove the vibration options if the device can't vibrate
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
PreferenceCategory category =
(PreferenceCategory) findPreference("category_input_settings");
category.removePreference(findPreference("checkbox_vibrate_fallback"));
// The entire OSC category may have already been removed by the touchscreen check above
category = (PreferenceCategory) findPreference("category_onscreen_controls");
if (category != null) {
category.removePreference(findPreference("checkbox_vibrate_osc"));
}
}
int maxSupportedFps = 0;
// Hide non-supported resolution/FPS combinations
@@ -176,6 +233,8 @@ public class StreamSettings extends Activity {
int width = Math.max(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
int height = Math.min(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
addNativeResolutionEntry(width, height);
if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) {
maxSupportedResW = 3840;
}
@@ -241,33 +300,33 @@ public class StreamSettings extends Activity {
if (maxSupportedResW != 0) {
if (maxSupportedResW < 3840) {
// 4K is unsupported
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "4K", new Runnable() {
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_4K, new Runnable() {
@Override
public void run() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p");
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P);
resetBitrateToDefault(prefs, null, null);
}
});
}
if (maxSupportedResW < 2560) {
// 1440p is unsupported
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p", new Runnable() {
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P, new Runnable() {
@Override
public void run() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p");
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P);
resetBitrateToDefault(prefs, null, null);
}
});
}
if (maxSupportedResW < 1920) {
// 1080p is unsupported
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p", new Runnable() {
removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P, new Runnable() {
@Override
public void run() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "720p");
setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_720P);
resetBitrateToDefault(prefs, null, null);
}
});
@@ -275,6 +334,12 @@ public class StreamSettings extends Activity {
// Never remove 720p
}
}
else {
Display display = getActivity().getWindowManager().getDefaultDisplay();
int width = Math.max(display.getWidth(), display.getHeight());
int height = Math.min(display.getWidth(), display.getHeight());
addNativeResolutionEntry(width, height);
}
if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) {
// We give some extra room in case the FPS is rounded down
@@ -319,7 +384,7 @@ public class StreamSettings extends Activity {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
LimeLog.info("Excluding unlock FPS toggle based on OS");
PreferenceCategory category =
(PreferenceCategory) findPreference("category_basic_settings");
(PreferenceCategory) findPreference("category_advanced_settings");
category.removePreference(findPreference("checkbox_unlock_fps"));
}
else {
@@ -384,6 +449,25 @@ public class StreamSettings extends Activity {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
String valueStr = (String) newValue;
// Detect if this value is the native resolution option
CharSequence[] values = ((ListPreference)preference).getEntryValues();
boolean isNativeRes = true;
for (int i = 0; i < values.length; i++) {
// Look for a match prior to the start of the native resolution entries
if (valueStr.equals(values[i].toString()) && i < nativeResolutionStartIndex) {
isNativeRes = false;
break;
}
}
// If this is native resolution, show the warning dialog
if (isNativeRes) {
Dialog.displayDialog(getActivity(),
getResources().getString(R.string.title_native_res_dialog),
getResources().getString(R.string.text_native_res_dialog),
false);
}
// Write the new bitrate value
resetBitrateToDefault(prefs, valueStr, null);
@@ -3,41 +3,22 @@ package com.limelight.utils;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import com.limelight.HelpActivity;
public class HelpLauncher {
private static boolean isKnownBrowser(Context context, Intent i) {
ResolveInfo resolvedActivity = context.getPackageManager().resolveActivity(i, PackageManager.MATCH_DEFAULT_ONLY);
if (resolvedActivity == null) {
// No browser
return false;
}
String name = resolvedActivity.activityInfo.name;
if (name == null) {
return false;
}
name = name.toLowerCase();
return name.contains("chrome") || name.contains("firefox");
}
private static void launchUrl(Context context, String url) {
// Try to launch the default browser
try {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
// Several Android TV devices will lie and say they do have a browser
// even though the OS just shows an error dialog if we try to use it. We need to
// be a bit more clever on these devices and detect if the browser is a legitimate
// browser or just a fake error message activity.
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) ||
isKnownBrowser(context, i)) {
// Several Android TV devices will lie and say they do have a browser even though the OS
// just shows an error dialog if we try to use it. We used to try to be clever and check
// the package name of the resolved intent, but it's not worth it anymore with Android 11's
// package visibility changes. We'll just always use the WebView on Android TV.
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
context.startActivity(i);
return;
}
@@ -6,6 +6,7 @@ import android.widget.Toast;
import com.limelight.AppView;
import com.limelight.Game;
import com.limelight.PcView;
import com.limelight.R;
import com.limelight.ShortcutTrampoline;
import com.limelight.binding.PlatformBinding;
@@ -14,6 +15,7 @@ import com.limelight.nvstream.http.ComputerDetails;
import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.jni.MoonBridge;
import org.xmlpull.v1.XmlPullParserException;
@@ -23,6 +25,8 @@ import java.net.UnknownHostException;
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) {
return computer.activeAddress;
}
@@ -76,6 +80,38 @@ public class ServerHelper {
parent.startActivity(createStartIntent(parent, app, computer, managerBinder));
}
public static void doNetworkTest(final Activity parent) {
new Thread(new Runnable() {
@Override
public void run() {
SpinnerDialog spinnerDialog = SpinnerDialog.displayDialog(parent,
parent.getResources().getString(R.string.nettest_title_waiting),
parent.getResources().getString(R.string.nettest_text_waiting),
false);
int ret = MoonBridge.testClientConnectivity(CONNECTION_TEST_SERVER, 443, MoonBridge.ML_PORT_FLAG_ALL);
spinnerDialog.dismiss();
String dialogSummary;
if (ret == MoonBridge.ML_TEST_RESULT_INCONCLUSIVE) {
dialogSummary = parent.getResources().getString(R.string.nettest_text_inconclusive);
}
else if (ret == 0) {
dialogSummary = parent.getResources().getString(R.string.nettest_text_success);
}
else {
dialogSummary = parent.getResources().getString(R.string.nettest_text_failure);
dialogSummary += MoonBridge.stringifyPortFlags(ret, "\n");
}
Dialog.displayDialog(parent,
parent.getResources().getString(R.string.nettest_title_done),
dialogSummary,
false);
}
}).start();
}
public static void doQuit(final Activity parent,
final ComputerDetails computer,
final NvApp app,
@@ -77,8 +77,18 @@ public class TvChannelHelper {
return;
}
Uri channelUri = context.getContentResolver().insert(
TvContract.Channels.CONTENT_URI, builder.toContentValues());
Uri channelUri;
try {
channelUri = context.getContentResolver().insert(
TvContract.Channels.CONTENT_URI, builder.toContentValues());
} catch (IllegalArgumentException e) {
// This can happen on HarmonyOS devices which report to
// support Leanback APIs, yet don't implement this URI
e.printStackTrace();
return;
}
if (channelUri != null) {
long id = ContentUris.parseId(channelUri);
updateChannelIcon(id);
@@ -144,8 +154,15 @@ public class TvChannelHelper {
return;
}
context.getContentResolver().insert(TvContract.PreviewPrograms.CONTENT_URI,
builder.toContentValues());
try {
context.getContentResolver().insert(TvContract.PreviewPrograms.CONTENT_URI,
builder.toContentValues());
} catch (IllegalArgumentException e) {
// This can happen on HarmonyOS devices which report to
// support Leanback APIs, yet don't implement this URI
e.printStackTrace();
return;
}
TvContract.requestChannelBrowsable(context, channelId);
}
@@ -11,6 +11,7 @@ LOCAL_MODULE := moonlight-core
LOCAL_SRC_FILES := moonlight-common-c/src/AudioStream.c \
moonlight-common-c/src/ByteBuffer.c \
moonlight-common-c/src/Connection.c \
moonlight-common-c/src/ConnectionTester.c \
moonlight-common-c/src/ControlStream.c \
moonlight-common-c/src/FakeCallbacks.c \
moonlight-common-c/src/InputStream.c \
+4 -3
View File
@@ -79,7 +79,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", "([BIIIJ)I");
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJJ)I");
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
@@ -157,7 +157,8 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
decodeUnit->frameNumber, decodeUnit->receiveTimeMs);
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs,
(jlong)decodeUnit->enqueueTimeMs);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
@@ -178,7 +179,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
decodeUnit->frameNumber,
decodeUnit->receiveTimeMs);
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
if ((*env)->ExceptionCheck(env)) {
// We will crash here
(*JVM)->DetachCurrentThread(JVM);
@@ -49,6 +49,11 @@ Java_com_limelight_nvstream_jni_MoonBridge_sendMouseScroll(JNIEnv *env, jclass c
LiSendScrollEvent(scrollClicks);
}
JNIEXPORT void JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_sendMouseHighResScroll(JNIEnv *env, jclass clazz, jshort scrollAmount) {
LiSendHighResScrollEvent(scrollAmount);
}
JNIEXPORT void JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_stopConnection(JNIEnv *env, jclass clazz) {
LiStopConnection();
@@ -96,4 +101,37 @@ Java_com_limelight_nvstream_jni_MoonBridge_getPendingAudioDuration(JNIEnv *env,
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPendingVideoFrames(JNIEnv *env, jclass clazz) {
return LiGetPendingVideoFrames();
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_testClientConnectivity(JNIEnv *env, jclass clazz, jstring testServerHostName, jint referencePort, jint testFlags) {
int ret;
const char* testServerHostNameStr = (*env)->GetStringUTFChars(env, testServerHostName, NULL);
ret = LiTestClientConnectivity(testServerHostNameStr, (unsigned short)referencePort, testFlags);
(*env)->ReleaseStringUTFChars(env, testServerHostName, testServerHostNameStr);
return ret;
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFlagsFromStage(JNIEnv *env, jclass clazz, jint stage) {
return LiGetPortFlagsFromStage(stage);
}
JNIEXPORT jint JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_getPortFlagsFromTerminationErrorCode(JNIEnv *env, jclass clazz, jint errorCode) {
return LiGetPortFlagsFromTerminationErrorCode(errorCode);
}
JNIEXPORT jstring JNICALL
Java_com_limelight_nvstream_jni_MoonBridge_stringifyPortFlags(JNIEnv *env, jclass clazz, jint portFlags, jstring separator) {
const char* separatorStr = (*env)->GetStringUTFChars(env, separator, NULL);
char outputBuffer[512];
LiStringifyPortFlags(portFlags, separatorStr, outputBuffer, sizeof(outputBuffer));
(*env)->ReleaseStringUTFChars(env, separator, separatorStr);
return (*env)->NewStringUTF(env, outputBuffer);
}
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="100"/>
</set>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="100"/>
</set>
+20 -40
View File
@@ -1,49 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp">
<RelativeLayout
android:id="@+id/grid_image_layout"
android:layout_width="150dp"
android:layout_height="200dp"
android:padding="10dp">
<ImageView
android:id="@+id/grid_image"
android:cropToPadding="false"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ImageView>
<ImageView
android:id="@+id/grid_overlay"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/grid_image"
android:cropToPadding="false"
android:scaleType="fitXY"
android:layout_centerHorizontal="true"
android:layout_width="150dp"
android:layout_height="175dp">
</ImageView>
<ProgressBar
android:id="@+id/grid_spinner"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="75dp"
android:layout_height="75dp"
android:indeterminate="true">
</ProgressBar>
<ImageView
android:id="@+id/grid_overlay"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="50dp"
android:layout_height="50dp">
</ImageView>
</RelativeLayout>
android:layout_marginTop="10dp"
android:layout_width="50dp"
android:layout_height="50dp">
</ImageView>
<TextView
android:id="@+id/grid_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/grid_image_layout"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:layout_height="match_parent"
android:layout_below="@id/grid_overlay"
android:layout_margin="5dp"
android:gravity="center"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:textSize="18sp" >
android:textSize="18sp">
</TextView>
</RelativeLayout>
+20 -40
View File
@@ -1,49 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp">
<RelativeLayout
android:id="@+id/grid_image_layout"
android:layout_width="100dp"
android:layout_height="133dp"
android:padding="5dp">
<ImageView
android:id="@+id/grid_image"
android:cropToPadding="false"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ImageView>
<ImageView
android:id="@+id/grid_overlay"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/grid_image"
android:cropToPadding="false"
android:scaleType="fitXY"
android:layout_centerHorizontal="true"
android:layout_width="100dp"
android:layout_height="117dp">
</ImageView>
<ProgressBar
android:id="@+id/grid_spinner"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="50dp"
android:layout_height="50dp"
android:indeterminate="true">
</ProgressBar>
<ImageView
android:id="@+id/grid_overlay"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_width="33dp"
android:layout_height="33dp">
</ImageView>
</RelativeLayout>
android:layout_marginTop="10dp"
android:layout_width="33dp"
android:layout_height="33dp">
</ImageView>
<TextView
android:id="@+id/grid_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/grid_image_layout"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:layout_height="match_parent"
android:layout_below="@id/grid_overlay"
android:layout_margin="5dp"
android:gravity="center"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:textSize="14sp" >
android:textSize="14sp">
</TextView>
</RelativeLayout>
+1 -1
View File
@@ -4,7 +4,7 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="auto_fit"
android:columnWidth="160dp"
android:columnWidth="150dp"
android:stretchMode="spacingWidthUniform"
android:focusable="true"
android:focusableInTouchMode="true"
@@ -4,7 +4,7 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="auto_fit"
android:columnWidth="105dp"
android:columnWidth="100dp"
android:stretchMode="spacingWidthUniform"
android:focusable="true"
android:focusableInTouchMode="true"
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fastScrollEnabled="true"
android:longClickable="false"
android:focusable="true"
android:focusableInTouchMode="true"
android:stackFromBottom="false"
android:clipToPadding="false"/>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp">
<RelativeLayout
android:id="@+id/grid_image_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/grid_image"
android:layout_centerHorizontal="true"
android:layout_width="75dp"
android:layout_height="75dp">
</ImageView>
<ImageView
android:id="@+id/grid_overlay"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:layout_width="33dp"
android:layout_height="33dp">
</ImageView>
<ProgressBar
android:id="@+id/grid_spinner"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:layout_width="33dp"
android:layout_height="33dp"
android:indeterminate="true">
</ProgressBar>
</RelativeLayout>
<TextView
android:id="@+id/grid_text"
android:layout_width="75dp"
android:layout_height="wrap_content"
android:layout_below="@id/grid_image_layout"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:gravity="center"
android:textSize="14sp" >
</TextView>
</RelativeLayout>
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="auto_fit"
android:columnWidth="105dp"
android:stretchMode="spacingWidthUniform"
android:focusable="true"
android:focusableInTouchMode="true"
android:nextFocusLeft="@id/settingsButton"
android:clipToPadding="false"
android:gravity="center"/>
-15
View File
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:padding="10dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/grid_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textIsSelectable="false"
android:textSize="16sp" >
</TextView>
</LinearLayout>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
+164 -129
View File
@@ -1,190 +1,225 @@
<?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 gelöscht</string>
<string name="scut_not_paired">PC nicht verbunden</string>
<string name="scut_pc_not_found">PC nicht gefunden</string>
<string name="scut_invalid_uuid">Angegebener PC ist unzulässig</string>
<string name="scut_deleted_pc">Host gelöscht</string>
<string name="scut_not_paired">Host nicht gekoppelt</string>
<string name="scut_pc_not_found">Host nicht gefunden</string>
<string name="scut_invalid_uuid">Angegebener Host ist ungültig</string>
<string name="scut_invalid_app_id">Angegebene App ist unzulässig</string>
<!-- Help strings -->
<string name="help_loading_title">Hilfsanzeige</string>
<string name="help_loading_msg">Lade Hilfesanzeige</string>
<string name="help_loading_msg">Hilfe wird geladen</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Spiele List anzeigen</string>
<string name="pcview_menu_pair_pc">Mit PC verbinden</string>
<string name="pcview_menu_unpair_pc">Verbindung beenden</string>
<string name="pcview_menu_app_list">Alle Apps anzeigen</string>
<string name="pcview_menu_pair_pc">Mit Host koppeln</string>
<string name="pcview_menu_unpair_pc">Kopplung trennen</string>
<string name="pcview_menu_send_wol">Wake-On-LAN Anfrage senden</string>
<string name="pcview_menu_delete_pc">PC löschen</string>
<string name="pcview_menu_delete_pc">Host löschen</string>
<string name="pcview_menu_test_network">Netzwerkverbindung testen</string>
<string name="pcview_menu_details">Details anzeigen</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Netzwerkverbindung wird getestet</string>
<string name="nettest_text_waiting">Moonlight testet Ihre Netzwerkverbindung, um festzustellen, ob NVIDIA GameStream blockiert ist.
\n
\nDies kann einige Sekunden dauern…</string>
<string name="nettest_title_done">Netzwerktest abgeschlossen</string>
<string name="nettest_text_success">Dein Netzwerk scheint Moonlight nicht zu blockieren. Wenn du weiterhin Probleme mit der Verbindung hast, überprüfe die Firewall-Einstellungen deines Hosts
\n
\nWenn du versuchst, über das Internet zu streamen, installiere das Moonlight Internet Hosting Tool auf deinem Host und führe den mitgelieferten Internet-Streaming-Tester aus, um die Internetverbindung des Hosts zu überprüfen.</string>
<string name="nettest_text_inconclusive">Der Netzwerktest konnte nicht durchgeführt werden, da keiner der Verbindungstestserver von Moonlight erreichbar war. Überprüfe deine Internetverbindung oder versuche es später noch einmal.</string>
<string name="nettest_text_failure">Die aktuelle Netzwerkverbindung deines Geräts scheint Moonlight zu blockieren. Das Streaming über das Internet funktioniert möglicherweise nicht, während du mit diesem Netzwerk verbunden bist.
\n
\nFolgende Netzwerkports wurden blockiert:
\n</string>
<string name="nettest_text_blocked">Die aktuelle Netzwerkverbindung deines Geräts blockiert Moonlight. Das Streaming über das Internet funktioniert möglicherweise nicht, während du mit diesem Netzwerk verbunden bist.</string>
<!-- Pair messages -->
<string name="pairing">Verbinden…</string>
<string name="pair_pc_offline">Computer ist offline</string>
<string name="pair_pc_ingame">Am Computer ist bereits ein Spiel aktiv. Sie müssen das Spiel beenden bevor sie sich koppeln können.</string>
<string name="pair_pairing_title">Verbinden</string>
<string name="pair_pairing_msg">Bitte geben sie folgenden PIN auf ihrem PC ein:</string>
<string name="pairing">Koppeln…</string>
<string name="pair_pc_offline">Host ist offline</string>
<string name="pair_pc_ingame">Auf dem Host ist bereits eine App aktiv. Du musst die App vor dem koppeln beenden.</string>
<string name="pair_pairing_title">Koppeln</string>
<string name="pair_pairing_msg">Bitte gebe folgenden PIN auf deinem Host ein:</string>
<string name="pair_incorrect_pin">PIN inkorrekt</string>
<string name="pair_fail">Verbindung fehlgeschlagen</string>
<string name="pair_already_in_progress">Verbindungsaufbau rebreits im gange</string>
<string name="pair_fail">Koppeln fehlgeschlagen</string>
<string name="pair_already_in_progress">Koppeln bereits im Gange</string>
<!-- WOL messages -->
<string name="wol_pc_online">Computer ist online</string>
<string name="wol_no_mac">PC konnte nicht geweckt werden, da GFE keine MAC-Adresse gesendet hat.</string>
<string name="wol_waking_pc">PC wird geweckt…</string>
<string name="wol_waking_msg">Es kann einige Momente dauert ihren PC zu wecken.
Sollte dies fehlschlagen stellen sie bitte sicher, dass Wake-On-LAN korrekt konfiguriert ist.
</string>
<string name="wol_fail">Sender der Wake-On-LAN Pakete ist fehlgeschlagen</string>
<string name="wol_pc_online">Host ist online</string>
<string name="wol_no_mac">Host konnte nicht aufgeweckt werden, da GFE keine MAC-Adresse gesendet hat</string>
<string name="wol_waking_pc">Host wird aufgeweckt…</string>
<string name="wol_waking_msg">Es kann einige Momente dauern deinen Host aufzuwecken. Sollte dies fehlschlagen, stelle bitte sicher, dass Wake-On-LAN korrekt konfiguriert ist.</string>
<string name="wol_fail">Senden der Wake-On-LAN Pakete ist fehlgeschlagen</string>
<!-- Unpair messages -->
<string name="unpairing">Trenne Verbindung…</string>
<string name="unpair_success">Verbindung erfolgreich getrennt</string>
<string name="unpair_fail">Trennung der Verbindung fehlgeschlagen</string>
<string name="unpair_error">Gerät wurde nicht verbunden</string>
<string name="unpairing">Trenne Kopplung…</string>
<string name="unpair_success">Kopplung erfolgreich getrennt</string>
<string name="unpair_fail">Trennung der Kopplung fehlgeschlagen</string>
<string name="unpair_error">Gerät wurde nicht gekoppelt</string>
<!-- Errors -->
<string name="error_pc_offline">Computer ist offline</string>
<string name="error_manager_not_running">Der ComputerManager Service läuft nicht. Bitte warten sie ein paar Sekunden oder starten sie Die App neu.</string>
<string name="error_unknown_host">Host konnte nicht aufgelößt werden.</string>
<string name="error_404">GFE ist auf einen HTTP 404 Fehler gestoßen. Stellen sie sicher, dass die GPU ihres PCs unterstützt wird.
Die verwendung von Remote-Desktop Software kann ebenso diesen Fehler verursachen. Starten sie ihren Computer neu oder reinstallieren sie GFE.
</string>
<string name="title_decoding_error">Video Decoder ist gecrashed</string>
<string name="message_decoding_error">Moonlight ist wegen einer Inkompatibilität zu dem Video-Decoder ihres Gerätes gecrasht. Stellen sie sicher, dass GeForce Experience Version auf ihrem PC auf dem neuesten Stand ist. Sollten weiterin Crashes auftreten, versuchen sie ihre Stream Einstellungen zu justieren.</string>
<string name="error_pc_offline">Host ist offline</string>
<string name="error_manager_not_running">Der ComputerManager Dienst läuft nicht. Bitte warte ein paar Sekunden oder starte die App neu.</string>
<string name="error_unknown_host">Hostname konnte nicht aufgelöst werden</string>
<string name="error_404">GFE ist auf einen HTTP 404 Fehler gestoßen. Stelle sicher, dass die GPU deines Hosts unterstützt wird. Die Verwendung von Remote-Desktop Software kann ebenso diesen Fehler verursachen. Starte deinen Host neu oder installiere GFE neu.</string>
<string name="title_decoding_error">Video Dekodierer ist abgestürzt</string>
<string name="message_decoding_error">Moonlight ist wegen einer Inkompatibilität zu dem Video Dekodierer deines Gerätes abgestürzt. Stelle sicher, dass die GeForce Experience Version auf deinem Host auf dem neuesten Stand ist. Sollten weiterin Abstürze auftreten, versuche deine Streaming Einstellungen zu ändern.</string>
<string name="title_decoding_reset">Video Einstellungen zurücksetzen</string>
<string name="message_decoding_reset">Der Video-Decoder ihres Geärts ist wiederholt mit den ausgewählten Einstellungen gecrasht. Ihre streaming einstellungen wurden zurückgesetzt.</string>
<string name="message_decoding_reset">Der Video Dekodierer deines Geräts ist wiederholt mit den ausgewählten Einstellungen abgestürzt. Deine Streamingeinstellungen wurden zurückgesetzt.</string>
<string name="error_usb_prohibited">USB Zugriff ist administrativ unterbunden. Bitte überprüfen sie ihre Knox oder MDM Einstellungen.</string>
<string name="unable_to_pin_shortcut">Die zur Zeit aktive Launcher App ünterstützt das erstellen angehefteter Shortcuts nicht.</string>
<string name="unable_to_pin_shortcut">Deine momentane Launcher App ünterstützt das erstellen angehefteter Shortcuts nicht.</string>
<string name="video_decoder_init_failed">Der Video Dekodierer konnte nicht initialisiert werden. Dein Gerät unterstützt möglicherweise die gewählte Auflösung oder Framerate nicht.</string>
<string name="no_video_received_error">Kein Video vom Host empfangen.</string>
<string name="no_frame_received_error">Deine Netzwerkverbindung funktioniert nicht gut. Reduziere die Einstellung der Videobitrate oder benutze eine schnellere Verbindung.</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Verbindung herstellen</string>
<string name="conn_establishing_msg">Verbindung starten</string>
<string name="conn_metered">Warnung: Das Datentransfairvolument ihrer Netzwerkverbindung ist limitiert!</string>
<string name="conn_metered">Warnung: Das Datentransfervolumen deiner Netzwerkverbindung ist limitiert!</string>
<string name="conn_client_latency">Durchschnittliche Frame-Dekodierungslatenz:</string>
<string name="conn_client_latency_hw">Hardware-Dekodierungslatenz:</string>
<string name="conn_hardware_latency">Durchschnittliche Hardware-Dekodierungslatenz:</string>
<string name="conn_starting">Startet</string>
<string name="conn_error_title">Verbindingsfehler</string>
<string name="conn_starting">Starten</string>
<string name="conn_error_title">Verbindungsfehler</string>
<string name="conn_error_msg">Start fehlgeschlagen</string>
<string name="conn_terminated_title">Verbindung beendet</string>
<string name="conn_terminated_msg">Die verbinding wurde beendet</string>
<string name="conn_terminated_msg">Die Verbindung wurde beendet</string>
<!-- General strings -->
<string name="ip_hint">IP-Adresse des GeForce PCs</string>
<string name="searching_pc">Suche nach PCs wo GeForce Experience aktiv ist…</string>
<string name="ip_hint">IP-Adresse des GeForce Host</string>
<string name="searching_pc">Suche nach Hosts auf denen GeForce Experience aktiv ist…
\n
\nStelle sicher, dass GameStream in den GeForce Experience SHIELD-Einstellungen aktiviert ist.</string>
<string name="yes">Ja</string>
<string name="no">Nein</string>
<string name="lost_connection">Verbindung zum PC verloren</string>
<string name="lost_connection">Verbindung zum Host verloren</string>
<string name="title_details">Details</string>
<string name="help">Hilfe</string>
<string name="delete_pc_msg">Sind sie sicher, dass sie diesen PC löschen möchten?</string>
<string name="slow_connection_msg">Langsame Verbindung zum PC\nReduzieren sie die Bitrate</string>
<string name="poor_connection_msg">Sehr langsame Verbindung zum PC</string>
<string name="perf_overlay_text">Videodimensionen: %1$s\nDecoder: %2$s\nGeschätzte PC Bildwiederholrate: %3$.2f FPS\nBildwiederholrate der Netzwerkübertragung: %4$.2f FPS\nWiedergabe-Bildwiederholungsrate: %5$.2f FPS\nWegen Netzwerkübertraung ausgelassene Frames: %6$.2f%%\nDurchschnittliche Übertragunsdauer: %7$.2f ms\nDurchschnittliche decoding dauer: %8$.2f ms</string>
<string name="delete_pc_msg">Bist du sicher, dass du diesen Host löschen möchtest\?</string>
<string name="slow_connection_msg">Langsame Verbindung zum Host
\nReduziere die Bitrate</string>
<string name="poor_connection_msg">Sehr langsame Verbindung zum Host</string>
<string name="perf_overlay_text">Videodimensionen: %1$s
\nDecoder: %2$s
\nGeschätzte Host Bildwiederholrate: %3$.2f FPS
\nBildwiederholrate der Netzwerkübertragung: %4$.2f FPS
\nWiedergabe-Bildwiederholungsrate: %5$.2f FPS
\nWegen Netzwerkübertragung ausgelassene Frames: %6$.2f%%
\nDurchschnittliche Übertragungsdauer: %7$.2f ms
\nDurchschnittliche Dekodierungszeit: %8$.2f ms</string>
<!-- AppList activity -->
<string name="applist_connect_msg">Verbinde mit PC</string>
<string name="applist_menu_resume">Sitzung Wiederherstellen</string>
<string name="applist_menu_quit">Sitzung Beenden</string>
<string name="applist_menu_quit_and_start">Aktuelles Spiel beenden und Starte</string>
<string name="applist_connect_msg">Verbinde mit Host</string>
<string name="applist_menu_resume">Sitzung wiederherstellen</string>
<string name="applist_menu_quit">Sitzung beenden</string>
<string name="applist_menu_quit_and_start">Aktuelle App beenden und starten</string>
<string name="applist_menu_cancel">Abbrechen</string>
<string name="applist_menu_details">Details anzeigen</string>
<string name="applist_menu_scut">Shortcut erstellen</string>
<string name="applist_menu_tv_channel">Zu Kanal hinzufügen</string>
<string name="applist_menu_hide_app">App ausblenden</string>
<string name="applist_refresh_title">App Liste</string>
<string name="applist_refresh_msg">Aktualisiere Apps</string>
<string name="applist_refresh_msg">Apps aktualisieren</string>
<string name="applist_refresh_error_title">Fehler</string>
<string name="applist_refresh_error_msg">Abfrage der App-Liste fehlgeschlagen</string>
<string name="applist_refresh_error_msg">Die App-Liste konnte nicht geladen werden</string>
<string name="applist_quit_app">Beenden</string>
<string name="applist_quit_success">Erfolgreich Beendet</string>
<string name="applist_quit_success">Erfolgreich beendet</string>
<string name="applist_quit_fail">Beenden fehlgeschlagen</string>
<string name="applist_quit_confirmation">Sind sie sicher, dass die die laufende App schließen möchten? Ungespeicherte Daten gehen verloren.</string>
<string name="applist_quit_confirmation">Bist du sicher, dass du die laufende App schließen möchtest\? Ungespeicherte Daten gehen verloren.</string>
<string name="applist_details_id">App ID:</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">PC manuell hinzufügen</string>
<string name="msg_add_pc">Verbinde zum PC</string>
<string name="addpc_fail">Verbindung fehlgeschlagen. Stellen sie sicher, dass die benötigten ports von einer Firewall gefiltert werden.</string>
<string name="addpc_success">Computer erfolgreich hinzugefügt</string>
<string name="addpc_unknown_host">Auflößen der PC-Adresse fehlgeschlagen. Stellen sie sicher, dass die Adresse keine Tippfehler beinhaltet.</string>
<string name="addpc_enter_ip">Sie müssen eine IP-Addresse eingeben.</string>
<string name="addpc_wrong_sitelocal">Diese Adresse sieht falsch aus. Sie müssen die öffentliche IP-Adresse ihreres Routers benutzen um über das Internet zu streamen.</string>
<string name="title_add_pc">Host manuell hinzufügen</string>
<string name="msg_add_pc">Verbinde zum Host</string>
<string name="addpc_fail">Verbindung fehlgeschlagen. Stelle sicher, dass die benötigten Ports von deiner Firewall nicht gesperrt sind.</string>
<string name="addpc_success">Host erfolgreich hinzugefügt</string>
<string name="addpc_unknown_host">Auflösen der Hostadresse fehlgeschlagen. Stelle sicher, dass die Adresse keine Tippfehler beinhaltet.</string>
<string name="addpc_enter_ip">Du musst eine IP-Adresse eingeben</string>
<string name="addpc_wrong_sitelocal">Diese Adresse sieht falsch aus. Du musst die öffentliche IP-Adresse deines Routers benutzen, um übers Internet zu Streamen.</string>
<!-- Preferences -->
<string name="category_basic_settings">Allgemeine Einstellungen</string>
<string name="title_resolution_list">Videoauflösung</string>
<string name="summary_resolution_list">Ehöhen für klarere Bilder. Reduzieren für flüssigere Darstellung auf langsameren Geräten und Netzwerken.</string>
<string name="title_fps_list">Bildwiederholungsrate</string>
<string name="summary_resolution_list">Erhöhen für klarere Bilder. Reduzieren für flüssigere Darstellung auf langsameren Geräten und Netzwerken.</string>
<string name="title_fps_list">Video Framerate</string>
<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="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">Kbps</string>
<string name="title_unlock_fps">Alle Bildwiederholungsraten freigeben</string>
<string name="summary_unlock_fps">Streaming mit 90 oder 120 FPS reduziert gegebenfalls die Latenz auf High-End Geräten, für jedoch zu Crashes oder Lag auf Geräten die dies nicht untersützen können.</string>
<string name="title_checkbox_stretch_video">Video auf den ganzen Bildschirm ausdehnen</string>
<string name="title_checkbox_disable_warnings">Warnhinweise deaktivieren</string>
<string name="summary_checkbox_disable_warnings">Verbindungswarnungen nicht als Overlay wärend des Streamens anzeigen</string>
<string name="title_checkbox_enable_pip">Bild-in-Bild Überwachungsmodus aktivieren</string>
<string name="summary_checkbox_enable_pip">Stream auch während des Multitaskings anzeigen (ohne Steuerung)</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="title_checkbox_stretch_video">Video auf Vollbildschirm strecken</string>
<string name="category_audio_settings">Audio Einstellungen</string>
<string name="category_input_settings">Eingabe Einstellungen</string>
<string name="title_checkbox_multi_controller">Automatische GamePad-Erkennung</string>
<string name="summary_checkbox_multi_controller">Abwählen dieser Option erzwingt dass immer ein GamePad present ist</string>
<string name="title_audio_config_list">Surround Sound Konfiguration </string>
<string name="summary_audio_config_list">Aktivieren von 5.1- oder 7.1-Surround-Sound für Heimkinosysteme</string>
<string name="category_input_settings">Controller Einstellungen</string>
<string name="title_checkbox_touchscreen_trackpad">Touchscreen als Trackpad</string>
<string name="summary_checkbox_touchscreen_trackpad">Wenn aktiviert, verhält sich der Touchscreen wie ein Trackpad. Wenn es deaktiviert ist, steuert der Touchscreen den Mauszeiger direkt.</string>
<string name="title_checkbox_multi_controller">Automatische Controller Erkennung</string>
<string name="summary_checkbox_multi_controller">Abwählen dieser Option erzwingt dass immer ein Controller präsent ist</string>
<string name="title_checkbox_vibrate_fallback">Vibrationsemulation aktivieren</string>
<string name="summary_checkbox_vibrate_fallback">Lässt das Gerät vibrieren falls das GamePad keine Vibration unterstütz</string>
<string name="title_seekbar_deadzone">Tot Bereich des Analogsticks</string>
<string name="summary_checkbox_vibrate_fallback">Lässt das Gerät vibrieren falls der Controller keine Vibration unterstützt</string>
<string name="title_seekbar_deadzone">Deadzone des Analogsticks</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One GamePad Treiber</string>
<string name="summary_checkbox_xb1_driver">Aktiviert eingebauten USB Treiber für Geräte die keinen Xbox GamePad-Unterstützung haben</string>
<string name="title_checkbox_usb_bind_all">Android GamePad Unterstütung überlagern</string>
<string name="summary_checkbox_usb_bind_all">Erzwingt die Vernwedung von Moonlight\'s USB Treiber für alle Xbox kompatiblen GamePads</string>
<string name="title_checkbox_mouse_emulation">Maus Emulation via GamePad</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One Controller Treiber</string>
<string name="summary_checkbox_xb1_driver">Aktiviert den eingebauten USB Treiber für Geräte die keine Xbox-Controller Unterstützung haben</string>
<string name="title_checkbox_usb_bind_all">Android Controller Unterstützung überschreiben</string>
<string name="summary_checkbox_usb_bind_all">Erzwingt die Verwendung von Moonlights USB Treiber für alle Xbox kompatiblen Controller</string>
<string name="title_checkbox_mouse_emulation">Maus Emulation via Controller</string>
<string name="summary_checkbox_mouse_emulation">Langes gedrückt halten der Start-Taste wechselt in den Maus Modus</string>
<string name="title_checkbox_mouse_nav_buttons">Vor- und Zurück-Tasten aktivieren</string>
<string name="summary_checkbox_mouse_nav_buttons">Aktivierung dieser Option kann auf fehleranfälligen Geräten Rechts-Clicks verunmöglichen</string>
<string name="category_on_screen_controls_settings">On-Screen Steuerungseinstellungen</string>
<string name="title_checkbox_show_onscreen_controls">Zeige On-Screen Steuerung</string>
<string name="summary_checkbox_show_onscreen_controls">Zeige virtuelle Steuerelemente als Overlay auf dem Touchscreen</string>
<string name="title_checkbox_flip_face_buttons">Tasten umkehren</string>
<string name="summary_checkbox_flip_face_buttons">Kehrt die Tasten A/B und X/Y für Controller und Bildschirmsteuerung um</string>
<string name="category_on_screen_controls_settings">On-Screen-Controller Einstellungen</string>
<string name="title_checkbox_show_onscreen_controls">On-Screen-Controller einblenden</string>
<string name="summary_checkbox_show_onscreen_controls">Blendet einen virtuellen Controller als Overlay auf dem Touchscreen ein</string>
<string name="title_checkbox_vibrate_osc">Vibrationen aktivieren</string>
<string name="summary_checkbox_vibrate_osc">Vibrieren als haptisches Feedback für die On-Screen Steuerelemente</string>
<string name="title_only_l3r3">Nur L3 und R3 anzeigen</string>
<string name="summary_only_l3r3">Alle virtuellen Steuerelemente außer L3 und R3 verbregen.</string>
<string name="title_reset_osc">Gespeicherte On-Screen Steuerungelemente zurücksetzen</string>
<string name="summary_reset_osc">Alle On-Screen Steuerelemente auf ihre Vorgabegröße und Position zurück setzen</string>
<string name="dialog_title_reset_osc">Layout Zurücksetzen</string>
<string name="dialog_text_reset_osc">Sind sie sicher, dass sie das gepseicherte on-screen Layout der Steuerelemte löschen wollen?</string>
<string name="toast_reset_osc_success">On-Screen Steuerelemente wurden zurückgesetzt</string>
<string name="summary_checkbox_vibrate_osc">Vibrieren als haptisches Feedback für den On-Screen-Controller</string>
<string name="title_only_l3r3">Nur L3 und R3 einblenden</string>
<string name="summary_only_l3r3">Alle virtuellen Tasten außer L3 und R3 ausblenden</string>
<string name="title_reset_osc">Gespeichertes On-Screen-Controller Layout zurücksetzen</string>
<string name="summary_reset_osc">Größe und Position der On-Screen-Controllertasten zurücksetzen</string>
<string name="dialog_title_reset_osc">Layout zurücksetzen</string>
<string name="dialog_text_reset_osc">Bist du sicher, dass du das gespeicherte On-Screen-Controllertsten Layout löschen willst\?</string>
<string name="toast_reset_osc_success">On-Screen-Controller wurde zurückgesetzt</string>
<string name="title_osc_opacity">Transparenz der On-Screen-Controllertasten ändern</string>
<string name="summary_osc_opacity">On-Screen-Controllertasten auf dem Bildschirm mehr/weniger transparent machen</string>
<string name="suffix_osc_opacity">%</string>
<string name="dialog_title_osc_opacity">Transparenz ändern</string>
<string name="category_ui_settings">UI Einstellungen</string>
<string name="title_checkbox_enable_pip">Aktivieren des Bild-in-Bild Modus</string>
<string name="summary_checkbox_enable_pip">Ermöglicht die Anzeige (aber nicht die Steuerung) des Streams während des Multitasking</string>
<string name="title_language_list">Sprache</string>
<string name="summary_language_list">Sprache die Moonlight verwenden soll</string>
<string name="title_checkbox_list_mode">Zeige Listen anstelle von Rastern.</string>
<string name="summary_checkbox_list_mode">Zeige Apps und PCs als Liste anstelle eines Rasters</string>
<string name="title_checkbox_small_icon_mode">Verwende kleine Icons</string>
<string name="summary_checkbox_small_icon_mode">Verwende kleine Icons in der Rasteransicht, um mehr gleichzeitig anzeigen zu können</string>
<string name="title_checkbox_small_icon_mode">Verwende kleine Cover</string>
<string name="summary_checkbox_small_icon_mode">Verwende kleine Cover in der Rasteransicht, um mehr gleichzeitig anzeigen zu können</string>
<string name="category_host_settings">Host Einstellungen</string>
<string name="title_checkbox_enable_sops">Spieleinstellungen optimieren</string>
<string name="summary_checkbox_enable_sops">GFE erlauben die die Spieleinstellungen zu optimieren.</string>
<string name="title_checkbox_host_audio">Audio auf dem PC wiedergeben</string>
<string name="summary_checkbox_host_audio">Audio auf dem PC und auf diesem Gerät wiedergeben</string>
<string name="summary_checkbox_enable_sops">GFE erlauben, die Spieleinstellungen für ein optimales Streaming zu ändern</string>
<string name="title_checkbox_host_audio">Audio auf dem Host wiedergeben</string>
<string name="summary_checkbox_host_audio">Audio auf dem Host und auf diesem Gerät wiedergeben</string>
<string name="category_advanced_settings">Erweiterte Einstellungen</string>
<string name="title_disable_frame_drop">Nie Frames Überspringen</string>
<string name="summary_disable_frame_drop">Kann potentiell das Mikro-Ruckeln auf einigen Geräten reduzieren, allerdings erhöt dies gleichzeitig die Latenz</string>
<string name="title_video_format">Ändere H.265 Einstellungen</string>
<string name="summary_video_format">H.265 verringerd die Video-Bandbreitenanforderung, funktioniert allerdings nur auf sehr neuen Geräten</string>
<string name="title_unlock_fps">Alle möglichen Framerates freischalten</string>
<string name="summary_unlock_fps">Streaming mit 90 oder 120 FPS kann die Latenz auf High-End-Geräten verringern, kann aber bei Geräten, die dies nicht unterstützen, zu stottern oder Instabilität führen</string>
<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_enable_hdr">HDR aktivieren (experimentell)</string>
<string name="summary_enable_hdr">HDR-Streaming sofern dies von der PC GPU unterstützt wird. HDR erfordert eine GPU der GTX 1000 Serie oder neuer.</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>
<string name="summary_enable_perf_overlay">Leistungsmerkmale während des Streamens in Echtzeit einblenden.</string>
<string name="suffix_osc_opacity">%</string>
<string name="dialog_title_osc_opacity">Transparenz</string>
</resources>
<string name="summary_enable_perf_overlay">Leistungsmerkmale während des Streamens in Echtzeit einblenden</string>
<string name="title_enable_post_stream_toast">Zeige Latenz-Informationen nach dem Streaming</string>
<string name="summary_enable_post_stream_toast">Anzeige einer Informationsmeldung über die Latenz nach dem Ende des Streams</string>
<string name="resolution_prefix_native">Nativ</string>
<string name="text_native_res_dialog">Native Auflösungsmodi werden von GeForce Experience nicht offiziell unterstützt, daher wird es die Auflösung deines Host-Displays nicht selbst einstellen. Du musst sie manuell im Spiel setzen.
\n
\nWenn du eine benutzerdefinierte Auflösung in der NVIDIA Systemsteuerung erstellst, um die Auflösung deines Geräts anzupassen, stelle bitte sicher, dass du die Warnung von NVIDIA bezüglich möglicher Monitorschäden, PC-Instabilität und anderer potenzieller Probleme gelesen und verstanden hast.
\n
\nWir sind nicht verantwortlich für Probleme, die aus der Erstellung einer angepassten Auflösung auf deinem PC resultieren.
\n
\nSchließlich kann es sein, dass dein Gerät oder dein Host das Streaming in nativer Auflösung nicht unterstützt. Wenn es auf deinem Gerät nicht funktioniert, hast du leider Pech gehabt.</string>
<string name="title_native_res_dialog">Native Auflösungswarnung</string>
<string name="check_ports_msg">Überprüfe deine Firewall und Portweiterleitungsregeln für folgende(n) Port(s):</string>
<string name="early_termination_error">Beim Starten des Streams auf deinem Host ist etwas schief gelaufen.
\n
\nStelle sicher, dass du keine DRM-geschützten Inhalte auf deinem Host geöffnet hast. Du kannst auch versuchen, deinen Host neu zu starten.
\n
\nWenn das Problem weiterhin besteht, versuche deinen GPU-Treiber und GeForce Experience neu zu installieren.</string>
<string name="pcview_menu_header_unknown">Aktualisiere</string>
<string name="pcview_menu_header_offline">Offline</string>
<string name="pcview_menu_header_online">Online</string>
</resources>
-3
View File
@@ -88,7 +88,6 @@
<string name="summary_resolution_list">Establecer unos valores demasiado altos puede causar lag o cierres inesperados</string>
<string name="title_seekbar_bitrate">Seleccionar bitrate de vídeo</string>
<string name="summary_seekbar_bitrate">Usa bitrate bajo para reducir "parpadeo". Incrementa el bitrate para mayor calidad de imagen.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Ajustar vídeo a pantalla completa</string>
<string name="title_checkbox_disable_warnings">Desactivar mensajes de advertencia</string>
<string name="summary_checkbox_disable_warnings">Desactivar mensajes de advertencia en pantalla durante la transmisión</string>
@@ -111,8 +110,6 @@
<string name="category_ui_settings">Configuración de la interfaz</string>
<string name="title_language_list">Idioma</string>
<string name="summary_language_list">Idioma a usar por Moonlight</string>
<string name="title_checkbox_list_mode">Usar listas en lugar de cuadrículas</string>
<string name="summary_checkbox_list_mode">Mostrar aplicaciones y PCs en listas en lugar de el cuadrículas</string>
<string name="title_checkbox_small_icon_mode">Usar iconos pequeños</string>
<string name="summary_checkbox_small_icon_mode">Usar iconos pequeños en las entradas de cuadrículas para permitir más entradas en la pantalla</string>
+25 -27
View File
@@ -1,17 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Shortcut strings -->
<string name="scut_deleted_pc">PC supprimé</string>
<string name="scut_not_paired">PC non appairé</string>
<string name="scut_pc_not_found">PC non trouvé</string>
<string name="scut_invalid_uuid">Le PC fourni n\'est pas valide</string>
<string name="scut_invalid_app_id">L\'application fournie n\'est pas valide</string>
<!-- Help strings -->
<string name="help_loading_title">Visionneuse d\'aide</string>
<string name="help_loading_msg">Chargement de la page d\'aide…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Afficher la liste des jeux</string>
<string name="pcview_menu_pair_pc">Appairer avec PC</string>
@@ -19,7 +16,14 @@
<string name="pcview_menu_send_wol">Envoyer la requête Wake-On-LAN</string>
<string name="pcview_menu_delete_pc">Supprimer PC</string>
<string name="pcview_menu_details">Voir les détails</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Test de la connexion réseau</string>
<string name="nettest_text_waiting">Moonlight teste votre connexion réseau pour déterminer si NVIDIA GameStream est bloqué.\n\nCela peut prendre quelques secondes.</string>
<string name="nettest_title_done">Test du réseau terminé</string>
<string name="nettest_text_success">Votre réseau ne semble pas bloquer Moonlight. Si vous avez encore de la difficulté à vous connecter, vérifiez les paramètres du pare-feu de votre PC.\n\nSi vous essayez de diffuser sur Internet, installez l\'outil d\'hébergement Internet Moonlight sur votre PC et exécutez le testeur de streaming Internet inclus pour vérifier la connexion Internet de votre PC.</string>
<string name="nettest_text_inconclusive">Le test réseau na pas pu être effectué car aucun des serveurs de test de connexion Moonlight n’était accessible. Vérifiez votre connexion Internet ou réessayez plus tard.</string>
<string name="nettest_text_failure">La connexion réseau actuelle de votre appareil semble bloquer Moonlight. Le streaming sur Internet peut ne pas fonctionner lorsquil est connecté à ce réseau.\n\nLes ports réseau suivants ont été bloqués:\n</string>
<string name="nettest_text_blocked">La connexion réseau actuelle de votre appareil bloque Moonlight. Le streaming sur Internet peut ne pas fonctionner lorsquil est connecté à ce réseau.</string>
<!-- Pair messages -->
<string name="pairing">Appariement…</string>
<string name="pair_pc_offline">L\'ordinateur est hors ligne</string>
@@ -29,7 +33,6 @@
<string name="pair_incorrect_pin">Code PIN incorrect</string>
<string name="pair_fail">Échec de l\'appariement</string>
<string name="pair_already_in_progress">Appariement déjà en cours</string>
<!-- WOL messages -->
<string name="wol_pc_online">L\'ordinateur est en ligne</string>
<string name="wol_no_mac">Impossible de réveiller le PC car GFE n\'a pas envoyé d\'adresse MAC</string>
@@ -38,13 +41,11 @@
Si ce n\'est pas le cas, assurez-vous qu\'il est correctement configuré pour Wake-On-LAN.
</string>
<string name="wol_fail">Échec de l\'envoi des paquets Wake-On-LAN</string>
<!-- Unpair messages -->
<string name="unpairing">Désappariage…</string>
<string name="unpair_success">Désapparié avec succès</string>
<string name="unpair_fail">Échec de désappariement</string>
<string name="unpair_error">Le périphérique n\'a pas été appareillé</string>
<!-- Errors -->
<string name="error_pc_offline">L\'ordinateur est déconnecté</string>
<string name="error_manager_not_running">Le service ComputerManager n\'est pas en cours d\'exécution. Veuillez patienter quelques secondes ou redémarrer l\'application.</string>
@@ -58,7 +59,6 @@
<string name="message_decoding_reset">Le décodeur vidéo de votre appareil continue de planter avec les paramètres de diffusion sélectionnés. Vos paramètres de diffusion ont été réinitialisés par défaut.</string>
<string name="error_usb_prohibited">L\'accès USB est interdit par votre appareil. Vérifiez vos paramètres Knox ou MDM.</string>
<string name="unable_to_pin_shortcut">Votre lanceur actuel ne permet pas de créer des raccourcis épinglés.</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Établissement de la connexion</string>
<string name="conn_establishing_msg">Démarrage de la connection</string>
@@ -71,7 +71,6 @@
<string name="conn_error_msg">Impossible de démarrer</string>
<string name="conn_terminated_title">Connexion terminée</string>
<string name="conn_terminated_msg">La connexion a été interrompue</string>
<!-- General strings -->
<string name="ip_hint">Adresse IP de GeForce PC</string>
<string name="searching_pc">Recherche de PC avec GameStream en cours…\n\n
@@ -85,7 +84,6 @@
<string name="slow_connection_msg">Connexion lente au PC\nRéduisez votre débit</string>
<string name="poor_connection_msg">Mauvaise connexion au PC</string>
<string name="perf_overlay_text">Video dimensions: %1$s\nDécodeur: %2$s\nEstimation de la fréquence d\'images de l\'ordinateur hôte: %3$.2f FPS\nFréquence d\'images entrantes du réseau: %4$.2f FPS\nTaux de rendu: %5$.2f FPS\nImages envoyé par votre connexion réseau: %6$.2f%%\nTemps moyen de réception: %7$.2f ms\nTemps de décodage moyen: %8$.2f ms</string>
<!-- AppList activity -->
<string name="applist_connect_msg">Connexion au PC…</string>
<string name="applist_menu_resume">Reprise de la session</string>
@@ -104,7 +102,6 @@
<string name="applist_quit_fail">Échec de la fermeture</string>
<string name="applist_quit_confirmation">Voulez-vous vraiment quitter l\'application en cours d\'exécution? Toutes les données non enregistrées seront perdues.</string>
<string name="applist_details_id">ID app:</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">Ajouter un PC manuellement</string>
<string name="msg_add_pc">Connexion au PC…</string>
@@ -113,7 +110,6 @@
<string name="addpc_unknown_host">Impossible de résoudre l\'adresse du PC. Assurez-vous que vous n\'avez pas fait une faute de frappe dans l\'adresse.</string>
<string name="addpc_enter_ip">Vous devez entrer une adresse IP</string>
<string name="addpc_wrong_sitelocal">Cette adresse ne semble pas correcte. Vous devez utiliser l\'adresse IP publique de votre routeur pour la diffusion en continu sur Internet..</string>
<!-- Preferences -->
<string name="category_basic_settings">Paramètres de base</string>
<string name="title_resolution_list">Résolution vidéo</string>
@@ -122,20 +118,16 @@
<string name="summary_fps_list">Augmenter pour un flux vidéo plus lisse. Diminution pour de meilleures performances sur les périphériques bas de gamme.</string>
<string name="title_seekbar_bitrate">Sélectionnez le bitrate vidéo à obtenir</string>
<string name="summary_seekbar_bitrate">Bitrate inférieur pour réduire la saccade. Augmentez le bitrate pour augmenter la qualité de l\'image.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps">Débloquer tous les taux d\'images possibles</string>
<string name="summary_unlock_fps">La diffusion en continu à 90 ou 120 FPS peut réduire la latence sur les périphériques haut de gamme, mais peut provoquer des retards ou des blocages sur les périphériques qui ne peuvent \pas le prendre en charge</string>
<string name="title_checkbox_stretch_video">Étirez la vidéo en plein écran</string>
<string name="title_checkbox_disable_warnings">Désactiver les messages d\'avertissement</string>
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant le streaming</string>
<string name="title_checkbox_enable_pip">Activer le mode observateur dans l\'image</string>
<string name="summary_checkbox_enable_pip">Permet de visualiser le flux (sans le contrôleur) tout en multitâche</string>
<string name="category_audio_settings">Paramètres audio</string>
<string name="title_audio_config_list">Configuration son surround</string>
<string name="summary_audio_config_list">Activer le son surround 5.1 ou 7.1 pour les systèmes home cinéma</string>
<string name="category_input_settings">Paramètres d\'entrée</string>
<string name="title_checkbox_touchscreen_trackpad">Utilisez l\'écran tactile comme trackpad</string>
<string name="summary_checkbox_touchscreen_trackpad">S\'il est activé, l\'écran tactile agit comme un trackpad. S\'il est désactivé, l\'écran tactile contrôle directement le curseur de la souris.</string>
<string name="title_checkbox_multi_controller">Prise en charge de plusieurs contrôleurs</string>
<string name="summary_checkbox_multi_controller">Lorsqu\'elle n\'est pas cochée, tous les contrôleurs sont regroupés</string>
<string name="title_checkbox_vibrate_fallback">Emuler support vibration de secours</string>
@@ -150,7 +142,8 @@
<string name="summary_checkbox_mouse_emulation">Appuyez longuement sur le bouton Start pour faire basculer la manette de jeu en mode souris.</string>
<string name="title_checkbox_mouse_nav_buttons">Activer les boutons de la souris arrière et avant</string>
<string name="summary_checkbox_mouse_nav_buttons">L\' activation de cette option peut entraîner un clic droit sur certains périphériques.</string>
<string name="title_checkbox_flip_face_buttons">Boutons de face inversé</string>
<string name="summary_checkbox_flip_face_buttons">Commute les boutons de face A/B et X/Y pour les manettes de jeu et les commandes à l\'écran</string>
<string name="category_on_screen_controls_settings">Paramètres des contrôles à l\'écran</string>
<string name="title_checkbox_show_onscreen_controls">Afficher les commandes à l\'écran</string>
<string name="summary_checkbox_show_onscreen_controls">Afficher la superposition du contrôleur virtuel sur l\'écran tactile</string>
@@ -167,23 +160,21 @@
<string name="summary_osc_opacity">Rendre les contrôles à l\'écran plus/moins transparents</string>
<string name="dialog_title_osc_opacity">Modifiez l\'opacité</string>
<string name="suffix_osc_opacity">%</string>
<string name="category_ui_settings">Paramètres de l\'interface utilisateur</string>
<string name="title_language_list">Langue</string>
<string name="summary_language_list">Langue à utiliser pour Moonlight</string>
<string name="title_checkbox_list_mode">Utiliser des listes au lieu des grilles</string>
<string name="summary_checkbox_list_mode">Afficher les applications et les PC en listes au lieu de grilles</string>
<string name="title_checkbox_small_icon_mode">Utiliser des petites icônes</string>
<string name="summary_checkbox_small_icon_mode">Utilisez les petites icônes dans les éléments de la grille pour permettre plus d\'éléments à l\'écran</string>
<string name="category_host_settings">Paramètres de l\'hôte</string>
<string name="title_checkbox_enable_sops">Optimiser les paramètres de jeu</string>
<string name="summary_checkbox_enable_sops">Autoriser GFE à modifier les paramètres de jeu pour une diffusion optimale</string>
<string name="title_checkbox_host_audio">Jouer l\'audio sur le PC</string>
<string name="summary_checkbox_host_audio">Lire l\'audio de l\'ordinateur et de ce périphérique</string>
<string name="category_advanced_settings">Réglages avancés</string>
<string name="title_disable_frame_drop">Désactiver la suppression d\'image</string>
<string name="title_unlock_fps">Débloquez toutes les fréquences d\'images possibles</string>
<string name="summary_unlock_fps">Le streaming à 90 ou 120 FPS peut réduire la latence sur les appareils haut de gamme, mais peut entraîner un décalage ou une instabilité sur les appareils qui ne peuvent pas le prendre en charge</string>
<string name="summary_checkbox_disable_warnings">Désactiver les messages d\'avertissement de connexion à l\'écran pendant la diffusion</string>
<string name="title_disable_frame_drop">Ne jamais laisser tomber les frames</string>
<string name="summary_disable_frame_drop">Peut réduire les micro-saccades sur certains appareils, mais peut augmenter la latence</string>
<string name="title_video_format">Modifier les paramètres H.265</string>
<string name="summary_video_format">H.265 réduit les besoins en bande passante vidéo mais nécessite un périphérique très récent</string>
@@ -191,5 +182,12 @@
<string name="summary_enable_hdr">Diffuser du HDR lorsque le jeu et le processeur graphique du PC le prennent en charge. HDR nécessite un GPU série GTX 1000 ou une version ultérieure.</string>
<string name="title_enable_perf_overlay">Activer la superposition de performance</string>
<string name="summary_enable_perf_overlay">Afficher une superposition à l\'écran avec des informations de performance en temps réel pendant la lecture en continu</string>
</resources>
<string name="title_enable_post_stream_toast">Afficher le message de latence après la diffusion en continu</string>
<string name="summary_enable_post_stream_toast">Afficher un message dinformations de latence après la fin du flux</string>
<string name="no_video_received_error">Aucune vidéo reçue depuis l\'hôte.</string>
<string name="video_decoder_init_failed">Le décodeur vidéo n\'a pas pu démarrer. Vérifiez que votre appareil supporte la résolution ou la fréquence d\'images choisie.</string>
<string name="pcview_menu_test_network">Tester la connection réseau</string>
<string name="pcview_menu_header_unknown">Actualisation</string>
<string name="pcview_menu_header_offline">Hors ligne</string>
<string name="pcview_menu_header_online">En ligne</string>
</resources>
-3
View File
@@ -105,7 +105,6 @@
<string name="summary_resolution_list">Valori troppo elevati possono causare lag o crash</string>
<string name="title_seekbar_bitrate">Velocità di trasmissione video</string>
<string name="summary_seekbar_bitrate">Abbassa la velocità di trasmissione per ridurre lo stuttering; alzala per migliorare la qualità dell\'immagine</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Forza video a schermo intero</string>
<string name="title_checkbox_disable_warnings">Disabilita messaggi di warning</string>
<string name="summary_checkbox_disable_warnings">Disabilita i messaggi di warning sullo schermo durante lo streaming</string>
@@ -139,8 +138,6 @@
<string name="category_ui_settings">Impostazioni dell\'interfaccia</string>
<string name="title_language_list">Lingua</string>
<string name="summary_language_list">Lingua da usare in Moonlight</string>
<string name="title_checkbox_list_mode">Usa lista invece della griglia</string>
<string name="summary_checkbox_list_mode">Visualizza le applicazioni e i PC in una lista invece di una griglia</string>
<string name="title_checkbox_small_icon_mode">Usa icone piccole</string>
<string name="summary_checkbox_small_icon_mode">Usa icone piccole nella griglia per avere più oggetti a schermo</string>
-3
View File
@@ -84,7 +84,6 @@
<string name="summary_resolution_list">品質が高いほどラグとクラッシュが発生しやすくなります</string>
<string name="title_seekbar_bitrate">映像のビットレート</string>
<string name="summary_seekbar_bitrate">ビットレートを低くすればカクつきが抑制され、高くすれば画質が向上します</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">映像を全画面に拡大</string>
<string name="title_checkbox_disable_warnings">警告を無効化</string>
<string name="summary_checkbox_disable_warnings">ストリーミング中に画面に警告メッセージを表示しない</string>
@@ -105,8 +104,6 @@
<string name="category_ui_settings">インターフェース</string>
<string name="title_language_list">言語</string>
<string name="summary_language_list">Moonlightで使用する言語</string>
<string name="title_checkbox_list_mode">リストメニュー</string>
<string name="summary_checkbox_list_mode">ゲームをグリッドではなく、リストで表示します</string>
<string name="title_checkbox_small_icon_mode">小さなアイコン</string>
<string name="summary_checkbox_small_icon_mode">グリッドで小さなアイコンを使用します</string>
-3
View File
@@ -99,7 +99,6 @@
<string name="summary_resolution_list">세팅 값이 자신의 PC 성능보다 너무 높으면 렉이나 깨짐을 유발할 수 있습니다.</string>
<string name="title_seekbar_bitrate">비트레이트 타겟 지정</string>
<string name="summary_seekbar_bitrate">낮은 비트레이트는 끊김을 줄이고, 높은 비트레이트는 품질을 높입니다.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">전체 화면으로 렌더링 스크린 늘이기</string>
<string name="title_checkbox_disable_warnings">경고 메세지 끄기</string>
<string name="summary_checkbox_disable_warnings">화면 상의 연결 경고 메세지를 스트리밍 중에 비활성화합니다.</string>
@@ -120,8 +119,6 @@
<string name="category_ui_settings">UI 설정</string>
<string name="title_language_list">언어</string>
<string name="summary_language_list">Moonlight에서 사용할 언어를 선택합니다.</string>
<string name="title_checkbox_list_mode">그리드(바둑판) 대신 리스트 뷰 사용</string>
<string name="summary_checkbox_list_mode">그리드 대신 리스트로 앱과 PC를 표시합니다.</string>
<string name="title_checkbox_small_icon_mode">작은 아이콘 사용</string>
<string name="summary_checkbox_small_icon_mode">더 많이 표시하기 위해 그리드 표시에서 작은 아이콘을 사용합니다.</string>
+210
View File
@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_checkbox_multi_controller">Automatisk oppdaging av spillkontroller</string>
<string name="title_checkbox_touchscreen_trackpad">Bruk denne pekeskjermen som pekeflate</string>
<string name="title_checkbox_stretch_video">Strekk video til hele skjermen</string>
<string name="title_seekbar_bitrate">Video-bitrate</string>
<string name="title_fps_list">Videobilderammetakt</string>
<string name="applist_quit_confirmation">Er du sikker på at du vil avslutte det kjørende programmet\? All ulagret data vil gå tapt.</string>
<string name="applist_refresh_msg">Gjenoppfrisker programmer …</string>
<string name="perf_overlay_text">Videooppløsning: %1$s
\nDekoder: %2$s
\nAntatt bilderammetakt på verts-PC: %3$.2f BPS
\nInnkommende bilderammetakt fra nettverket: %4$.2f BPS
\nOpptegningsbilderammetakt: %5$.2f BPS
\nBilderammer droppet av din nettverkstilknytning: %6$.2f%%
\nGjennomsnittlig mottakstid: %7$.2f ms
\nGjennomsnittlig dekodingstid: %8$.2f ms</string>
<string name="lost_connection">Mistet tilknytning til PC</string>
<string name="searching_pc">Søker etter PC-er med GameStream kjørende …
\n
\nForsikre deg om at GameStream er påslått i GeForce Experience SHEILD-innstillingene.</string>
<string name="conn_starting">Starter</string>
<string name="conn_hardware_latency">Gjennomsnittlig latens for maskinvarerammedekoding:</string>
<string name="conn_client_latency_hw">maskinvaredekoderlatens:</string>
<string name="conn_client_latency">Gjennomsnittlig latens for bilderammedekoding:</string>
<string name="check_ports_msg">Sjekk om din brannmur og portvideresendingsreglene for port(ene):</string>
<string name="early_termination_error">Noe gikk galt på din verts-PC under oppstart av strømmen.
\n
\nForsikre deg om at du ikke har noe DRM-beskyttet innhold åpent på din PC. Du kan også prøve å starte din verts-PC på ny.
\n
\nHvis problemet vedvarer, kan du prøve å reinstallere skjermkortsdriverne og GeForce Experience.</string>
<string name="no_frame_received_error">Din nettverkstilkobling fungerer ikke så godt. Reduser videobitraten fra dens innstilling, eller prøv en raskere tilknytning.</string>
<string name="no_video_received_error">Fikk ingen video fra verten.</string>
<string name="unable_to_pin_shortcut">Din nåværende oppstarter tillater ikke opprettelse av festede snarveier.</string>
<string name="title_decoding_error">Videodekoderen krasjet</string>
<string name="error_unknown_host">Klarte ikke å utlede vertsadresse</string>
<string name="unpair_error">Enheten ble ikke parret</string>
<string name="unpair_fail">Klarte ikke å oppheve parring</string>
<string name="unpair_success">Parring opphevet</string>
<string name="unpairing">Opphever parring …</string>
<string name="wol_fail">Klarte ikke å sende Wake-On-LAN-pakker</string>
<string name="wol_waking_msg">Det kan ta et par sekunder før PC-en din våkner. Hvis den ikke gjør det må du sjekke at den er satt opp riktig for Wake-On-LAN.</string>
<string name="wol_no_mac">Kunne ikke vekke opp PC fordi GFE ikke sendte en MAC-adresse</string>
<string name="pair_already_in_progress">Parring allerede underveis</string>
<string name="pair_fail">Parring mislyktes</string>
<string name="pair_pairing_msg">Skriv inn følgende PIN på mål-PC-en:</string>
<string name="pair_pairing_title">Parring</string>
<string name="pair_pc_ingame">Datamaskinen er for øyeblikket i et spill. Du må lukke spillet før parring.</string>
<string name="pairing">Parrer …</string>
<string name="nettest_title_done">Nettverkstest fullført</string>
<string name="title_seekbar_deadzone">Dødsonejustering for analogstikke</string>
<string name="title_checkbox_xb1_driver">Driver for Xbox 360/One USB-spillkontroller</string>
<string name="title_video_format">Endre H.265-innstillinger</string>
<string name="title_disable_frame_drop">Aldri forkast bilderammer</string>
<string name="title_checkbox_disable_warnings">Skru av advarselsmeldinger</string>
<string name="title_unlock_fps">Lås opp alle mulige bilderammetakter</string>
<string name="category_advanced_settings">Avanserte innstillinger</string>
<string name="title_checkbox_host_audio">Spill lyd på PC</string>
<string name="title_checkbox_enable_sops">Optimaliser spillinnstillinger</string>
<string name="category_host_settings">Vertsinnstillinger</string>
<string name="summary_language_list">Språk brukt i Moonlight</string>
<string name="title_language_list">Språk</string>
<string name="category_ui_settings">Grensesnittsinnstillinger</string>
<string name="suffix_osc_opacity">%</string>
<string name="dialog_title_osc_opacity">Endre dekkevne</string>
<string name="dialog_title_reset_osc">Tilbakestill oppsett</string>
<string name="summary_only_l3r3">Skjul alle virtuelle knapper unntatt L3 og R3</string>
<string name="title_only_l3r3">Kun vis L3 og R3</string>
<string name="title_checkbox_vibrate_osc">Skru på vibrering</string>
<string name="title_checkbox_mouse_emulation">Museemulering via spillkontroller</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="category_input_settings">Inndatainnstillinger</string>
<string name="title_audio_config_list">Kringlydsoppsett</string>
<string name="category_audio_settings">Lydinnstillinger</string>
<string name="title_resolution_list">Videooppløsning</string>
<string name="category_basic_settings">Grunnleggende innstillinger</string>
<string name="addpc_enter_ip">Du må skrive inn en IP-adresse</string>
<string name="addpc_success">Datamaskin tillagt</string>
<string name="msg_add_pc">Kobler til PC-en …</string>
<string name="title_add_pc">Legg til PC manuelt</string>
<string name="applist_details_id">Program-ID:</string>
<string name="applist_quit_fail">Klarte ikke å avslutte</string>
<string name="applist_quit_success">Avsluttet</string>
<string name="applist_quit_app">Avslutter …</string>
<string name="applist_refresh_error_msg">Klarte ikke å hente programliste</string>
<string name="applist_refresh_error_title">Feil</string>
<string name="applist_refresh_title">Programliste</string>
<string name="applist_menu_hide_app">Skjul prgram</string>
<string name="applist_menu_tv_channel">Legg til i kanal</string>
<string name="applist_menu_scut">Opprett snarvei</string>
<string name="applist_menu_details">Vis detaljer</string>
<string name="applist_menu_cancel">Avbryt</string>
<string name="applist_menu_quit_and_start">Avslutt nåværende spill og start</string>
<string name="applist_menu_quit">Avslutt økt</string>
<string name="applist_menu_resume">Fortsett økt</string>
<string name="applist_connect_msg">Kobler til PC …</string>
<string name="poor_connection_msg">Dårlig tilknytning til PC</string>
<string name="slow_connection_msg">Vis tilkobling til PC
\nReduser bitraten din</string>
<string name="delete_pc_msg">Er du sikker på at du vil slette denne PC-en\?</string>
<string name="help">Hjelp</string>
<string name="title_details">Detaljer</string>
<string name="no">Nei</string>
<string name="yes">Ja</string>
<string name="ip_hint">IP-adresse for GeForce PC</string>
<string name="conn_terminated_msg">Tilkoblingen ble avsluttet</string>
<string name="conn_terminated_title">Tilkobling avsluttet</string>
<string name="conn_error_msg">Klarte ikke å starte</string>
<string name="conn_error_title">Tilkoblingsfeil</string>
<string name="conn_metered">Advarsel: Din aktive nettverkstilknytning er kvotebasert.</string>
<string name="conn_establishing_msg">Starter tilkobling</string>
<string name="conn_establishing_title">Etablerer tilkobling</string>
<string name="error_pc_offline">Datamaskinen er frakoblet</string>
<string name="wol_waking_pc">Vekker opp PC …</string>
<string name="wol_pc_online">Datamaskinen er tilkoblet</string>
<string name="pair_incorrect_pin">Feil PIN</string>
<string name="pair_pc_offline">Datamaskinen er frakoblet</string>
<string name="nettest_title_waiting">Testing av nettverkstilkobling</string>
<string name="pcview_menu_details">Vis detaljer</string>
<string name="pcview_menu_test_network">Test nettverkstilkobling</string>
<string name="pcview_menu_delete_pc">Slett PC</string>
<string name="pcview_menu_send_wol">Send Wake-On-LAN-forespørsel</string>
<string name="pcview_menu_app_list">Vis alle programmer</string>
<string name="pcview_menu_header_unknown">Oppdaterer</string>
<string name="pcview_menu_header_offline">Frakoblet</string>
<string name="pcview_menu_header_online">Tilkoblet</string>
<string name="help_loading_msg">Laster inn hjelpeside …</string>
<string name="help_loading_title">Hjelpeviser</string>
<string name="scut_invalid_app_id">Angitt program er ikke gyldig</string>
<string name="scut_invalid_uuid">Angitt PC er ikke gyldig</string>
<string name="scut_pc_not_found">PC ikke funnet</string>
<string name="scut_deleted_pc">PC slettet</string>
<string name="summary_checkbox_mouse_emulation">Å lang-trykke «Start»-knappen veksler spillkontroller til musemodus</string>
<string name="title_checkbox_usb_bind_all">Overstyr systemspesifikk kontrollerstøtte</string>
<string name="summary_checkbox_xb1_driver">Skrur på en innebygd USB-driver for enheter uten systemspesifikk Xbox-kontrollerstøtte</string>
<string name="summary_checkbox_vibrate_fallback">Vibrerer din kontroller for å emulere vibrasjon, hvis din kontroller støtter det</string>
<string name="title_checkbox_vibrate_fallback">Vibrasjonsemulering</string>
<string name="summary_checkbox_touchscreen_trackpad">Får pekeskjermen til å oppføre seg som en pekeflate. Ellers kontrolleres pekeskjermen musen direkte.</string>
<string name="title_decoding_reset">Tilbakestilling av videoinnstillinger</string>
<string name="message_decoding_error">Moonlight har krasjet som følge av inkompabilitet med denne enhetens videodekoder. Forsikre deg om at GeForce Experience er oppdatert til seneste versjon på din PC. Prøv å justere strømmingsinnstillingene hvis problemet vedvarer.</string>
<string name="error_404">GFE returnerte en HTTP-404-feil. Forsikre deg om at PC-en kjører et støttet skjermkort. Bruk av fjernskrivebordsprogramvare kan også forårsake denne feilen. Prøv å starte maskinen på ny, eller å reinstallere GFE.</string>
<string name="error_manager_not_running">ComputerManager-tjenesten kjører ikke. Vent noen sekunder eller start programmet på ny.</string>
<string name="nettest_text_blocked">Nettverkstilkoblingen på din enhet ser ut til å blokkere Moonlight. Strømming over Internett vil muligens ikke fungere tilkoblet gjennom dette nettverket.</string>
<string name="nettest_text_failure">Nettverkstilkoblingen på din enhet ser ut til å blokkere Moonlight. Strømming over Internett vil muligens ikke fungere når tilkoblet gjennom dette nettverket.
\n
\nFølgende nettverksporter ble blokkert:
\n</string>
<string name="nettest_text_inconclusive">Nettverkstesten kunne ikke utføres fordi ingen av Moonlight sine tilkoblingstesttjenere kunne nås. Sjekk din tilkobling til Internett og prøv igjen senere.</string>
<string name="summary_enable_perf_overlay">Viser ytelsesinfo i sanntid under strømming</string>
<string name="title_enable_perf_overlay">Vis ytelsesstatistikk under strømming</string>
<string name="summary_enable_hdr">Strøm HDR når spillet og PC-ens skjermkort støtter det. HDR krever GTX 1000-eller nyere.</string>
<string name="summary_checkbox_enable_pip">Tillater strømmen å vises (men ikke kontrolleres) under fleroppgaveløsning</string>
<string name="title_checkbox_enable_pip">Skru på bilde-i-bilde-observatørmodus</string>
<string name="title_reset_osc">Tøm lagret oppsett for innstillingskontroller som vises på skjermen</string>
<string name="summary_checkbox_vibrate_osc">Vibrerer din enhet for innstillingskontroller som vises på skjermen</string>
<string name="summary_checkbox_show_onscreen_controls">Vis virtuelle innstillingskontroller for innstillinger i overleggslag på pekeskjermen</string>
<string name="title_checkbox_show_onscreen_controls">Vis innstillingskontroller på skjermen</string>
<string name="category_on_screen_controls_settings">Innstillinger for innstillingskontroller som vises på skjermen</string>
<string name="summary_checkbox_usb_bind_all">Bruk Moonlight sin USB-driver for alle støttede spillkontrollere, selv når systemspesifikk kontrollerstøtte er tilgjengelig</string>
<string name="summary_audio_config_list">Skru på for 5,1- eller 7,1-kringlyd for hjemmekinosystemer</string>
<string name="summary_seekbar_bitrate">Øk for bedret bildekvalitet. Senk for bedre ytelse på trege tilkoblinger.</string>
<string name="summary_fps_list">Øk for jevnere videostrøm. Senk for bedre ytelse på trege enheter.</string>
<string name="video_decoder_init_failed">Klarte ikke å starte videodekoder. Enheten din støtter kanskje ikke valgt oppløsning eller bilderammetakt.</string>
<string name="error_usb_prohibited">USB-tilgang har blitt forbudt av din enhetsadministrator. Sjekk dine Knox- eller MDM-innstillinger.</string>
<string name="message_decoding_reset">Strømmingsinnstilinger tilbakestilt til forvalg som følge av at enhetens videodekoder fortsatte å krasje.</string>
<string name="summary_resolution_list">Øk for å forbedre bildeklarhet. Senk for bedre ytelse på svakere enheter og tregere nettverk.</string>
<string name="addpc_wrong_sitelocal">Den adressen ser ikke riktig ut. Du må bruke den offentlige IP-adressen for ruteren din for strømming over Internett.</string>
<string name="addpc_unknown_host">Kunne ikke utlede adresse tilhørende PC. Forsikre deg om at du ikke har skrevet den inn feil.</string>
<string name="addpc_fail">Kunne ikke koble til angitt datamaskin. Forsikre deg om at påkrevde porter er tillatt gjennom brannmuren.</string>
<string name="summary_enable_post_stream_toast">Vis en informasjonsmelding om latens etter at strømmen sluttføres</string>
<string name="title_enable_post_stream_toast">Vis latensmelding etter strømming</string>
<string name="title_enable_hdr">Skru på HDR (eksperimentelt)</string>
<string name="summary_video_format">H.265 senker båndbreddekravene, men krever en nyere enhet</string>
<string name="summary_disable_frame_drop">Kan redusere mikro-hakking på noen enheter, men kan øke latens</string>
<string name="summary_checkbox_disable_warnings">Skru av visning av tilkoblingsadvarsler på skjermen under strømming</string>
<string name="summary_unlock_fps">Strømming i 90 eller 120 BPS kan redusere latens på flaggskipsenheter, men kan forårsake forsinkelser eller ustabilitet på enheter som ikke klarer det.</string>
<string name="summary_checkbox_host_audio">Spill lyd fra datamaskinen og denne enheten</string>
<string name="summary_checkbox_enable_sops">Tillat GFE å endre spill-innstillinger for optimal strømming</string>
<string name="summary_checkbox_small_icon_mode">Mindre bilder for omslagskunst i programrutenettet lar deg se flere programmer på skjermen</string>
<string name="title_checkbox_small_icon_mode">Bruk liten omslagskunst</string>
<string name="summary_osc_opacity">Gjør innstillingskontroller som vises på skjermen mer/mindre gjennomsiktige</string>
<string name="title_osc_opacity">Endre dekkevne for innstillingskontroller som vises på skjermen</string>
<string name="toast_reset_osc_success">Innstillingskontroller som vises på skjermen tilbakestilt til forvalg</string>
<string name="dialog_text_reset_osc">Er du sikker på at du vil slette oppsettet ditt for innstillingskontroller som vises på skjermen\?</string>
<string name="summary_reset_osc">Tilbakestiller alle innstillingskontroller som vises på skjermen til forvalgt størrelse og posisjon</string>
<string name="summary_checkbox_mouse_nav_buttons">Å skru på dette ødelegger for høyreklikking på noen enheter</string>
<string name="title_checkbox_mouse_nav_buttons">Skru på museknapper for forover og tilbake</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="nettest_text_success">Det ser ikke ut til at nettverket ditt blokkerer Moonlight. Hvis du fremdeles har problemer bør du sjekke PC-ens brannmursinnstillinger.
\n
\nHvis du prøver å strømme over Internett, kan du installere Moonlight Internett-vertstjeningsverktøyet på din PC og kjøre den inkluderte Internett-strømmingstesteren for å sjekke PC-ens tilkobling til Internett.</string>
<string name="nettest_text_waiting">Moonlight tester din nettverksforbindelse for å fastsette hvorvidt NVIDIA GameStream er blokkert.
\n
\nDette kan ta noen sekunder …</string>
<string name="pcview_menu_unpair_pc">Opphev parring</string>
<string name="pcview_menu_pair_pc">Parr med PC</string>
<string name="scut_not_paired">PC ikke parret</string>
<string name="summary_checkbox_flip_face_buttons">Veksler A/B og X/Y-knapper for spillkontrollere, og innstillingskontroller som vises på skjermen</string>
<string name="text_native_res_dialog">Innebygd oppløsningsmodus støttes ikke offisielt av GeForce Experience, så det vil ikke sette skjermoppløsning for verten på egenhånd. Du vil måtte sette det manuelt inne i spillet.
\n
\nHvis du velger å opprette en egendefinert oppløsning i NVIDIA-kontrollpanelet for å samsvare med enhetsoppløsningen må du forsikre deg om at du har lest og forstått NVIDIA sin advarsel om mulig skjermskade, PC-instabilitet, og andre potensielle problemer.
\n
\nVi er ikke ansvarlige for noen problemer som oppstår ved å lage en egendefinert oppløsning på din PC.
\n
\nDet kan hende din enhet eller verts-PC ikke støtter strømming i egentlig oppløsning. Hvis det ikke fungerer på din enhet er det dessverre ingenting du kan gjøre.</string>
<string name="summary_checkbox_multi_controller">Å fjerne dette valget påtvinger tilkoblet spillkontroller</string>
<string name="resolution_prefix_native">Innebygd</string>
<string name="title_native_res_dialog">Advarsel om innebygd oppløsning</string>
<string name="title_checkbox_flip_face_buttons">Veksle diamantknapper</string>
</resources>
-3
View File
@@ -88,7 +88,6 @@
<string name="summary_resolution_list">Te hoge instellingen kunnen crashes en haperingen veroorzaken.</string>
<string name="title_seekbar_bitrate">Selecteer doel video bitsnelheid</string>
<string name="summary_seekbar_bitrate">Verlaag bitsnelheid om haperingen te verminderen. Verhoog de bitsnelheid voor een betere videokwaliteit.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Rek video uit tot volledig scherm</string>
<string name="title_checkbox_disable_warnings">Verberg waarschuwingsberichten</string>
<string name="summary_checkbox_disable_warnings">Verberg on-screen verbindingswaarschuwingen tijdens het streamen</string>
@@ -109,8 +108,6 @@
<string name="category_ui_settings">UI Installingen</string>
<string name="title_language_list">Taal</string>
<string name="summary_language_list">Taal te gebruiken in Moonlight</string>
<string name="title_checkbox_list_mode">Gebruik lijsten in plaats van kolommen</string>
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
<string name="title_checkbox_small_icon_mode">Gebruik kleine iconen</string>
<string name="summary_checkbox_small_icon_mode">Gebruik kleine iconen in kolom onderdelen zodat meer items tegelijk zichtbaar worden.</string>
-3
View File
@@ -120,7 +120,6 @@
<string name="summary_fps_list">Crește-o pentru a îmbunătăți fluiditatea imaginilor. Descrește-o pentru dispozitive neperformante sau conexiune slabă.</string>
<string name="title_seekbar_bitrate">Rata de biți</string>
<string name="summary_seekbar_bitrate">Crește-o pentru a îmbunătăți calitatea imaginilor. Descrește-o pentru dispozitive neperformante sau conexiune slabă.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps">Deblochează toate ratele de cadre posibile</string>
<string name="summary_unlock_fps">Fluxul video de rate mai mari poate reduce latența folosind dispozitive performante, dar poate introduce erori daca nu sunt suportate.</string>
<string name="title_checkbox_stretch_video">Întindeți video pe ecranul complet</string>
@@ -169,8 +168,6 @@
<string name="category_ui_settings">Setari UI</string>
<string name="title_language_list">Limba (Language)</string>
<string name="summary_language_list">Limba folosită de către Moonlight</string>
<string name="title_checkbox_list_mode">Folosește liste în loc de grile</string>
<string name="summary_checkbox_list_mode">Aplicațiile și PC-urile vor fi afișate in liste in loc de grile</string>
<string name="title_checkbox_small_icon_mode">Folosește iconițe mici</string>
<string name="summary_checkbox_small_icon_mode">Iconițele folosite în grile vor fi mici pentru a încăpea mai multe odata</string>
+6
View File
@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="audio_config_names">
<item>Стерео</item>
<item>5.1 Объёмный звук</item>
<item>7.1 Объёмный звук</item>
</string-array>
<string-array name="decoder_names">
<item>Автоматический выбор декодера</item>
<item>Принудительное программное декодирование</item>
+24 -20
View File
@@ -1,13 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Посмотреть список игр</string>
<string name="pcview_menu_pair_pc">Создать пару с PC</string>
<string name="pcview_menu_unpair_pc">Разорвать пару</string>
<string name="pcview_menu_send_wol">Отправить Wake-On-LAN запрос</string>
<string name="pcview_menu_delete_pc">Удалить PC</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Тестирование Сетевого Подключения</string>
<string name="nettest_text_waiting">Moonlight тестирует ваше сетевое подключение, чтобы определить, заблокирован ли NVIDIA GameStream.\n\nЭто может занять некоторое время…</string>
<string name="nettest_title_done">Тестирование Подключения Завершено</string>
<string name="nettest_text_success">Ваша сеть не блокирует Moonlight. Если у вас всё ещё есть проблемы с соединением, проверьте настройки брандмауэра на компьютере.\n\nЕсли вы пытаетесь транслировать через интернет, установите Moonlight Internet Hosting Tool на ваш компьютер и запустите установленный Internet Streaming Tester, чтобы проверить Интернет-соединение на вашем компьютере.</string>
<string name="nettest_text_inconclusive">Тестирование подключения не может быть выполнено, потому что сервера тестирования подключения Moonlight не доступны. Проверьте ваше Интернет-соединение или повторите попытку позже.</string>
<string name="nettest_text_failure">Похоже, что текущее сетевое подключение вашего устройства блокирует Moonlight. Трансляция через Интернет может не работать при подключении к этой сети.\n\nСледующие сетевые порты были заблокированы:\n</string>
<!-- Pair messages -->
<string name="pairing">Создание пары…</string>
<string name="pair_pc_offline">Компьютер выключен или находится не в сети</string>
@@ -16,7 +21,6 @@
<string name="pair_pairing_msg">Пожалуйста введите этот PIN на PC:</string>
<string name="pair_incorrect_pin">Неправильный PIN</string>
<string name="pair_fail">Создание пары не удалось</string>
<!-- WOL messages -->
<string name="wol_pc_online">Компьютер в сети</string>
<string name="wol_no_mac">Невозможно разбудить PC потому что GFE не отправило MAC адрес</string>
@@ -25,13 +29,11 @@
Если этого не происходит, удостоверьтесь что Wake-On-LAN настроен правильно.
</string>
<string name="wol_fail">Ошибка при отправке Wake-On-LAN пакетов</string>
<!-- Unpair messages -->
<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_manager_not_running">Сервис ComputerManager не запущен. Пожалуйста, подождите несколько секунд или перезапустите приложение.</string>
@@ -39,7 +41,6 @@
<string name="error_404">GFE вернул ошибку HTTP 404. Убедитесь что Ваш PC использует поддерживаемый GPU.
Использование программ для удалённого доступа также может вызывать эту ошибку. Попробуйте перезагрузить компьютер или переустановить GFE.
</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Создание соединения</string>
<string name="conn_establishing_msg">Подключение</string>
@@ -52,7 +53,6 @@
<string name="conn_error_msg">Запуск не удался</string>
<string name="conn_terminated_title">Соединение прекращено</string>
<string name="conn_terminated_msg">Подключение было прервано</string>
<!-- General strings -->
<string name="ip_hint">IP-адрес компьютера с GeForce</string>
<string name="searching_pc">Поиск компьютеров с запущенным GameStream…\n\n
@@ -60,7 +60,6 @@
<string name="yes">Да</string>
<string name="no">Нет</string>
<string name="lost_connection">Потеряно соединение с PC</string>
<!-- AppList activity -->
<string name="applist_menu_resume">Возобновить сессию</string>
<string name="applist_menu_quit">Выйти из сессии</string>
@@ -75,7 +74,6 @@
<string name="applist_quit_success">Выход произошёл успешно из</string>
<string name="applist_quit_fail">Ошибка при выходе</string>
<string name="applist_quit_confirmation">Вы уверены, что хотите выйти из запущенного приложения? Все несохраненные данные будут потеряны.</string>
<!-- Add computer manually activity -->
<string name="title_add_pc">Добавление PC вручную</string>
<string name="msg_add_pc">Соединение с PC…</string>
@@ -83,41 +81,36 @@
<string name="addpc_success">Компьютер добавлен успешно</string>
<string name="addpc_unknown_host">Не удалось найти PC по указанному адресу. Убедитесь, что Вы не совершили ошибок во время его написания.</string>
<string name="addpc_enter_ip">Вы должны ввести IP адрес</string>
<!-- Preferences -->
<string name="category_basic_settings">Общие Настройки</string>
<string name="title_resolution_list">Выберите разрешение и частоту кадров</string>
<string name="summary_resolution_list">Выбор слишком высокого значеня для своего устройства может вызвать тормоза или вылеты</string>
<string name="title_seekbar_bitrate">Выберите битрейт видео</string>
<string name="summary_seekbar_bitrate">Низкий битрейт уменьшит зависания. Увеличение битрейта улучшит качество изображения.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_checkbox_stretch_video">Растягивать видео на весь экран</string>
<string name="title_checkbox_disable_warnings">Отключить сообщения с предупреждениями</string>
<string name="summary_checkbox_disable_warnings">Выключить экранные предупреждения о соединении во время трансляции</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>
<string name="title_checkbox_multi_controller">Поддержка нескольких контроллеров</string>
<string name="summary_checkbox_multi_controller">Когда отключена, все контроллеры определяются как один</string>
<string name="title_seekbar_deadzone">Регулировать мертвую зону аналогового стика</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Драйвер контроллеров Xbox 360/One</string>
<string name="summary_checkbox_xb1_driver">Включить встроенный USB драйвер для устройств без собственной поддержки контроллеров Xbox</string>
<string name="title_checkbox_flip_face_buttons">Перевернуть кнопки A/B и X/Y</string>
<string name="summary_checkbox_flip_face_buttons">Меняет местами кнопки A/B и X/Y на геймпадах и экранных кнопках</string>
<string name="category_ui_settings">Настройки Интерфейса</string>
<string name="title_language_list">Язык</string>
<string name="summary_language_list">Язык, который будет использоваться в Moonlight</string>
<string name="title_checkbox_list_mode">Использовать списки вместо сеток</string>
<string name="summary_checkbox_list_mode">Выводить приложения и компьютеры списком, вместо использования сетки</string>
<string name="title_checkbox_small_icon_mode">Использовать маленькие иконки</string>
<string name="summary_checkbox_small_icon_mode">Использовать маленькие иконки в сетке для отображения большего числа элементов на экране</string>
<string name="category_host_settings">Настройки Хоста</string>
<string name="title_checkbox_enable_sops">Оптимизировать игровые настройки</string>
<string name="summary_checkbox_enable_sops">Разрешить GFE изменять настройки игр для оптимальной трансляции</string>
<string name="title_checkbox_host_audio">Проигрывать звук на PC</string>
<string name="summary_checkbox_host_audio">Проигрывать звук на компьютере и текущем устройстве</string>
<string name="category_advanced_settings">Расширенные Настройки</string>
<string name="title_video_format">Изменить настройки H.265</string>
<string name="summary_video_format">H.265 снижает требования к пропускной способности, но требует очень нового устройства</string>
@@ -126,6 +119,8 @@
<string name="summary_checkbox_show_onscreen_controls">Отображать оверлей виртуального контроллера на сенсорном экране</string>
<string name="title_only_l3r3">Показывать только L3 и R3</string>
<string name="summary_only_l3r3">Скрывать все экранные кнопки кроме L3 и R3</string>
<string name="summary_osc_opacity">Сделать экранные кнопки более/менее прозрачными</string>
<string name="dialog_title_osc_opacity">Изменить прозрачность</string>
<string name="scut_deleted_pc">PC удален</string>
<string name="scut_not_paired">PC не сопряжен</string>
<string name="help_loading_title">Просмотр Помощи</string>
@@ -154,12 +149,15 @@
<string name="summary_disable_frame_drop">Может уменьшить микрозависания на некоторых устройствах, но также увеличить задержку</string>
<string name="title_enable_hdr">Включить HDR (Экспериментально)</string>
<string name="summary_enable_hdr">Транслировать в HDR если игра и GPU компьютера поддерживают это. HDR требует видеокарты GTX 1000 серии или более новой.</string>
<string name="title_enable_post_stream_toast">Показывать отчёт о задержке после трансляции</string>
<string name="summary_enable_post_stream_toast">Отобразить сообщение с информацией о задержке после окончания трансляции.</string>
<string name="title_checkbox_vibrate_osc">Включить вибрацию</string>
<string name="title_fps_list">Частота кадров</string>
<string name="applist_menu_details">Детали</string>
<string name="applist_menu_scut">Создать ярлык</string>
<string name="category_input_settings">Настройки ввода</string>
<string name="title_checkbox_touchscreen_trackpad">Использовать сенсор как тачпад</string>
<string name="summary_checkbox_touchscreen_trackpad">Если включено, сенсор выступает в роли тачпада. Если отключено, сенсор напрямую контролирует курсор мыши.</string>
<string name="delete_pc_msg">Вы уверены что хотите удалить этот PC?</string>
<string name="pcview_menu_details">Детали</string>
<string name="poor_connection_msg">Слабое соединение с PC</string>
@@ -182,4 +180,10 @@
<string name="scut_invalid_uuid">Указанный PC недействителен</string>
<string name="scut_invalid_app_id">Указанное приложение недействительно</string>
<string name="suffix_osc_opacity">%</string>
</resources>
<string name="suffix_seekbar_bitrate_mbps">Мб/с</string>
<string name="applist_menu_hide_app">Скрыть приложение</string>
<string name="pcview_menu_test_network">Тестовое подключение к сети</string>
<string name="pcview_menu_header_unknown">Обновление</string>
<string name="pcview_menu_header_offline">Оффлайн</string>
<string name="pcview_menu_header_online">Онлайн</string>
</resources>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="decoder_names">
<item>Автоматичний вибір декодера</item>
<item>Примусове програмне декодування</item>
<item>Примусове апаратне декодування</item>
</string-array>
<string-array name="video_format_names">
<item>Використовувати H.265 тільки якщо безпечно</item>
<item>Завжди використовувати H.265 якщо доступно</item>
<item>Ніколи не використовувати H.265</item>
</string-array>
</resources>
+194
View File
@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">Подивитися список ігор</string>
<string name="pcview_menu_pair_pc">Створити пару з ПК</string>
<string name="pcview_menu_unpair_pc">Розірвати пару</string>
<string name="pcview_menu_send_wol">Надіслати Wake-On-LAN запит</string>
<string name="pcview_menu_delete_pc">Видалити ПК</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="pair_pairing_msg">Будь ласка, введіть цей PIN на ПК:</string>
<string name="pair_incorrect_pin">Неправильний PIN</string>
<string name="pair_fail">Створення пари не вдалося</string>
<!-- WOL messages -->
<string name="wol_pc_online">Комп\'ютер в мережі</string>
<string name="wol_no_mac">Неможливо розбудити ПК бо GFE не відправили MAC адреса</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="unpair_success">Розрив пари закінчився успішно</string>
<string name="unpair_fail">Розрив пари не вдався</string>
<string name="unpair_error">Пристрій не було спарено</string>
<!-- Errors -->
<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. Переконайтеся що Ваш ПК використовує підтримуваний GPU.
Використання програм для віддаленого доступу також може викликати цю помилку. Спробуйте перезавантажити комп\'ютер або перевстановити GFE.
</string>
<!-- Start application messages -->
<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_error_title">Помилка з\'єднання</string>
<string name="conn_error_msg">Запуск не вдався</string>
<string name="conn_terminated_title">З\'єднання припинено</string>
<string name="conn_terminated_msg">Підключення було перервано</string>
<!-- General strings -->
<string name="ip_hint">IP-адреса комп\'ютера з GeForce</string>
<string name="searching_pc">Пошук комп\'ютерів із запущеним GameStream…\n\n
Переконайтеся що GameStream увімкнений в налаштуваннях GeForce Experience в розділі SHIELD.</string>
<string name="yes">Так</string>
<string name="no">Ні</string>
<string name="lost_connection">З\'єднання втрачено з ПК</string>
<!-- AppList activity -->
<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_tv_channel">Додати на канал</string>
<string name="applist_refresh_title">Список додатків</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>
<!-- Add computer manually activity -->
<string name="title_add_pc">Додавання ПК вручну</string>
<string name="msg_add_pc">З\'єднання з ПК…</string>
<string name="addpc_fail">Неможливо підключитися до вибраного комп\'ютера. Переконайтеся, що необхідні порти дозволені в налаштуваннях брандмауера.</string>
<string name="addpc_success">Комп\'ютер доданий успішно</string>
<string name="addpc_unknown_host">Неможливо знайти ПК за вказаною адресою. Переконайтеся, що Ви не зробили помилок під час його написання.</string>
<string name="addpc_enter_ip">Ви повинні ввести IP адресу</string>
<!-- Preferences -->
<string name="category_basic_settings">Загальні Налаштування</string>
<string name="title_resolution_list">Виберіть розширення і частоту кадрів</string>
<string name="summary_resolution_list">Вибір занадто високого значеня для свого пристрою може викликати гальма або вильоти</string>
<string name="title_seekbar_bitrate">Виберіть бітрейт відео</string>
<string name="summary_seekbar_bitrate">Низький бітрейт зменшить зависання. Збільшення бітрейта поліпшить якість зображення.</string>
<string name="title_checkbox_stretch_video">Розтягувати відео на весь екран</string>
<string name="title_checkbox_disable_warnings">Відключити повідомлення з попередженнями</string>
<string name="summary_checkbox_disable_warnings">Вимкнути екранні попередження про з\'єднання під час трансляції</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>
<string name="title_checkbox_multi_controller">Підтримка декількох контролерів</string>
<string name="summary_checkbox_multi_controller">Коли вимкнено, всі контролери визначаються як один</string>
<string name="title_seekbar_deadzone">Регулювати мертву зону аналогового стіку</string>
<string name="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Драйвер контролерів Xbox 360/One</string>
<string name="summary_checkbox_xb1_driver">Увімкнути вбудований USB драйвер для пристроїв без власної підтримки контролерів Xbox</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>
<string name="summary_checkbox_small_icon_mode">Використовувати маленькі іконки в сітці для відображення більшої кількості елементів на екрані</string>
<string name="category_host_settings">Налаштування Хоста</string>
<string name="title_checkbox_enable_sops">Оптимізувати ігрові налаштування</string>
<string name="summary_checkbox_enable_sops">Дозволити GFE змінювати налаштування ігор для оптимальної трансляції</string>
<string name="title_checkbox_host_audio">Програвати звук на ПК</string>
<string name="summary_checkbox_host_audio">Програвати звук на комп\'ютері і поточному пристрої</string>
<string name="category_advanced_settings">Розширені Налаштування</string>
<string name="title_video_format">Змінити налаштування H.265</string>
<string name="summary_video_format">H.265 знижує вимоги до пропускної здатності, але вимагає дуже нового пристрою</string>
<string name="category_on_screen_controls_settings">Налаштування дисплею з кнопками</string>
<string name="title_checkbox_show_onscreen_controls">Показувати екранні кнопки</string>
<string name="summary_checkbox_show_onscreen_controls">Відображати оверлей віртуального контролера на сенсорному екрані</string>
<string name="title_only_l3r3">Показувати тільки L3 і R3</string>
<string name="summary_only_l3r3">Приховувати всі екранні кнопки крім L3 і R3</string>
<string name="scut_deleted_pc">ПК видалений</string>
<string name="scut_not_paired">ПК не сполучений</string>
<string name="help_loading_title">Перегляд Допомоги</string>
<string name="help_loading_msg">Завантаження сторінки допомоги…</string>
<string name="pair_already_in_progress">Сполучення вже в процесі</string>
<string name="help">Допомога</string>
<string name="applist_connect_msg">Підключення до ПК…</string>
<string name="title_decoding_error">Збій відео декодера</string>
<string name="message_decoding_error">Стався збій Moonlight через проблеми з відео декодером даного пристрою. Спробуйте змінити налаштування трансляції якщо збої будуть продовжуватися.</string>
<string name="title_decoding_reset">Відео Налаштування Скинуті</string>
<string name="message_decoding_reset">Відео декодер Вашого пристрою давав збої з вибраними налаштуваннями. Налаштування трансляції були скинуті до значень за замовчуванням.</string>
<string name="error_usb_prohibited">USB доступ заборонений адміністратором пристрою. Перевірте налаштування Knox або MDM.</string>
<string name="addpc_wrong_sitelocal">Адреса вказана невірно. Ви повинні ввести публічну IP-адресу Вашого роутера для передачі через інтернет.</string>
<string name="title_checkbox_enable_pip">Увімкнути перегляд у режимі \"Картинка в картинці\"</string>
<string name="summary_checkbox_enable_pip">Дозволяє переглядати трансляцію (але не керувати нею) під час роботи в інших додатках</string>
<string name="title_checkbox_usb_bind_all">Відхилити підтримку контролерів Android</string>
<string name="summary_checkbox_usb_bind_all">Змушує USB драйвер Moonlight взяти на себе роботу з усіма підтримуваними Xbox геймпадами</string>
<string name="title_checkbox_mouse_emulation">Емуляція миші на геймпаді</string>
<string name="summary_checkbox_mouse_emulation">Довге натиснення кнопки Start перемкне геймпад в режим миші</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="toast_reset_osc_success">Екранні елементи керування повернуті до положень за замовчуванням</string>
<string name="title_disable_frame_drop">Ніколи не пропускати кадри</string>
<string name="summary_disable_frame_drop">Може зменшити мікрозависання на деяких пристроях, але також збільшити затримку</string>
<string name="title_enable_hdr">Увімкнути HDR (Експериментально)</string>
<string name="summary_enable_hdr">Транслювати в HDR якщо гра і GPU комп\'ютера підтримують це. HDR вимагає відеокарти GTX 1000 серії або більш нової.</string>
<string name="title_checkbox_vibrate_osc">Увімкнути вібрацію</string>
<string name="title_fps_list">Частота кадрів</string>
<string name="applist_menu_details">Деталі</string>
<string name="applist_menu_scut">Створити ярлик</string>
<string name="category_input_settings">Налаштування введення</string>
<string name="title_checkbox_touchscreen_trackpad">Використовувати сенсорний екран як трекпад</string>
<string name="summary_checkbox_touchscreen_trackpad">Якщо увімкнено, сенсорний екран працює як трекпад. Якщо його вимкнено, сенсорний екран безпосередньо керує курсором миші.</string>
<string name="delete_pc_msg">Ви впевнені що хочете видалити цей ПК?</string>
<string name="pcview_menu_details">Деталі</string>
<string name="poor_connection_msg">Слабке з\'єднання з ПК</string>
<string name="title_details">Деталі</string>
<string name="title_enable_perf_overlay">Увімкнути відображення статистики</string>
<string name="title_unlock_fps">Розблокувати всі можливі частоти оновлення</string>
<string name="applist_details_id">ID додатку:</string>
<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="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>
<string name="unable_to_pin_shortcut">Поточний лаунчер не дозволяє створювати закріплені ярлики</string>
<string name="title_checkbox_mouse_nav_buttons">Увімкнути кнопки вперед і назад для миші</string>
<string name="slow_connection_msg">Повільне підключення до ПК\nЗменшити бітрейт</string>
<string name="summary_unlock_fps">Трансляція зі швидкістю 90 або 120 кадрів в секунду може зменшити затримку на пристроях високого класу, але може викликати затримки або збій на пристроях без підтримки цього функціоналу</string>
<string name="summary_enable_perf_overlay">Відображення накладення на екрані з інформацією про продуктивність під час трансляції в режимі реального часу</string>
<string name="perf_overlay_text">Розширення відео: %1$s\nДекодер: %2$s\nРозрахункова частота кадрів ПК-хоста: %3$.2f FPS\nВхідна частота кадрів з мережі: %4$.2f FPS\nЧастота кадрів під час рендерінгу: %5$.2f FPS\nВідкинутих кадрів вашою мережею: %6$.2f%%\nСередній час отримання: %7$.2f ms\nСередній час декодування: %8$.2f ms</string>
<string name="summary_fps_list">Збільшення для більш плавного відео потоку. Зменшіть для кращої продуктивності на більш слабких пристроях.</string>
<string name="scut_invalid_uuid">Зазначений ПК недійсний</string>
<string name="scut_invalid_app_id">Зазначений додаток недійсне</string>
<string name="title_osc_opacity">Змінити непрозорість екранних елементів керування</string>
<string name="summary_osc_opacity">Зробити екранні елементи керування більш/менш прозорими</string>
<string name="dialog_title_osc_opacity">Зміна непрозорості</string>
<string name="suffix_osc_opacity">%</string>
<string name="title_enable_post_stream_toast">Показувати затримку після трансляції</string>
<string name="summary_enable_post_stream_toast">Вивести інформаційне повідомлення про затримку після закінчення потоку</string>
</resources>
+8 -23
View File
@@ -1,7 +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"> 电脑已删除 </string>
@@ -124,7 +122,6 @@
<string name="summary_fps_list"> 高帧数提升视频流流畅度。 \n 低帧数提升在低端设备中的串流体验。</string>
<string name="title_seekbar_bitrate"> 视频码率 </string>
<string name="summary_seekbar_bitrate"> 高码率提升图像质量。 \n 低码率提升在较慢网络中的串流体验。 </string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps"> 解锁所有可用帧数 </string>
<string name="summary_unlock_fps"> 以90或120帧串流可能会减少在高端设备上的网络延迟,但会在不支持的设备上造成卡顿或崩溃。 </string>
<string name="title_checkbox_stretch_video"> 将画面拉伸至全屏 </string>
@@ -137,7 +134,9 @@
<string name="title_audio_config_list"> 环绕声设置 </string>
<string name="summary_audio_config_list"> 为家庭影院系统启用5.1或7.1环绕声 </string>
<string name="category_input_settings">输入设置</string>
<string name="category_input_settings">输入设置</string>
<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="title_checkbox_vibrate_fallback"> 用设备震动模拟游戏震动效果 </string>
@@ -151,7 +150,9 @@
<string name="title_checkbox_mouse_emulation"> 通过手柄模拟鼠标 </string>
<string name="summary_checkbox_mouse_emulation"> 长按开始键将手柄切换为鼠标模式 </string>
<string name="title_checkbox_mouse_nav_buttons"> 启用前进后退鼠标键 </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="category_on_screen_controls_settings"> 屏幕控制按钮设置 </string>
<string name="title_checkbox_show_onscreen_controls"> 显示屏幕控制按钮 </string>
@@ -169,8 +170,6 @@
<string name="category_ui_settings"> 界面设置 </string>
<string name="title_language_list"> 语言 </string>
<string name="summary_language_list"> 选择Moonlight显示的语言 </string>
<string name="title_checkbox_list_mode"> 使用列表代替图标 </string>
<string name="summary_checkbox_list_mode"> 列表显示电脑和游戏 </string>
<string name="title_checkbox_small_icon_mode"> 使用小图标 </string>
<string name="summary_checkbox_small_icon_mode"> 使用小图标以在屏幕上显示更多项目 </string>
@@ -187,24 +186,10 @@
<string name="summary_video_format">H.265能降低视频带宽需求,但需要较新的设备才能支持</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="suffix_osc_opacity">%</string>
+8 -23
View File
@@ -1,7 +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"> 電腦已刪除 </string>
@@ -124,7 +122,6 @@
<string name="summary_fps_list"> 高幀數提升視頻流流暢度。 \n 低幀數提升在低端設備中的串流體驗。</string>
<string name="title_seekbar_bitrate"> 視頻碼率 </string>
<string name="summary_seekbar_bitrate"> 高碼率提升圖像品質。 \n 低碼率提升在較慢網路中的串流體驗。 </string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps"> 解鎖所有可用幀數 </string>
<string name="summary_unlock_fps"> 以90或120幀串流可能會減少在高端設備上的網路延遲,但會在不支援的設備上造成卡頓或崩潰。 </string>
<string name="title_checkbox_stretch_video"> 將畫面拉伸至全屏 </string>
@@ -137,7 +134,9 @@
<string name="title_audio_config_list"> 環繞聲設置 </string>
<string name="summary_audio_config_list"> 為家庭劇院系統啟用5.1或7.1環繞聲 </string>
<string name="category_input_settings">輸入設置</string>
<string name="category_input_settings"> 輸入設置 </string>
<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="title_checkbox_vibrate_fallback"> 用設備震動類比遊戲震動效果 </string>
@@ -151,7 +150,9 @@
<string name="title_checkbox_mouse_emulation"> 通過手柄類比滑鼠 </string>
<string name="summary_checkbox_mouse_emulation"> 長按開始鍵將手柄切換為滑鼠模式 </string>
<string name="title_checkbox_mouse_nav_buttons"> 啟用前進後退滑鼠鍵 </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="category_on_screen_controls_settings"> 螢幕控制按鈕設置 </string>
<string name="title_checkbox_show_onscreen_controls"> 顯示幕幕控制按鈕 </string>
@@ -169,8 +170,6 @@
<string name="category_ui_settings"> 介面設置 </string>
<string name="title_language_list"> 語言 </string>
<string name="summary_language_list"> 選擇Moonlight顯示的語言 </string>
<string name="title_checkbox_list_mode"> 使用清單代替圖示 </string>
<string name="summary_checkbox_list_mode"> 清單顯示電腦和遊戲 </string>
<string name="title_checkbox_small_icon_mode"> 使用小圖示 </string>
<string name="summary_checkbox_small_icon_mode"> 使用小圖示以在螢幕上顯示更多專案 </string>
@@ -187,24 +186,10 @@
<string name="summary_video_format">H.265能降低視頻頻寬需求,但需要較新的設備才能支援</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="suffix_osc_opacity">%</string>
+12 -6
View File
@@ -8,13 +8,15 @@
<item>1440p</item>
<item>4K</item>
</string-array>
<!-- Keep this in sync with PreferenceConfiguration.isNativeResolution()! -->
<string-array name="resolution_values" translatable="false">
<item>360p</item>
<item>480p</item>
<item>720p</item>
<item>1080p</item>
<item>1440p</item>
<item>4K</item>
<item>640x360</item>
<item>854x480</item>
<item>1280x720</item>
<item>1920x1080</item>
<item>2560x1440</item>
<item>3840x2160</item>
</string-array>
<string-array name="fps_names">
@@ -55,6 +57,8 @@
<item>Français</item>
<item>Deutsch</item>
<item>Română</item>
<item>Українська</item>
<item>Norwegian Bokmål</item>
</string-array>
<string-array name="language_values" translatable="false">
<item>default</item>
@@ -70,6 +74,8 @@
<item>fr</item>
<item>de</item>
<item>ro</item>
<item>uk</item>
<item>nb-NO</item>
</string-array>
<string-array name="decoder_names">
+43 -18
View File
@@ -15,13 +15,26 @@
<string name="help_loading_msg">Loading help page…</string>
<!-- PC view menu entries -->
<string name="pcview_menu_app_list">View Game List</string>
<string name="pcview_menu_header_online">Online</string>
<string name="pcview_menu_header_offline">Offline</string>
<string name="pcview_menu_header_unknown">Refreshing</string>
<string name="pcview_menu_app_list">View All Apps</string>
<string name="pcview_menu_pair_pc">Pair with PC</string>
<string name="pcview_menu_unpair_pc">Unpair</string>
<string name="pcview_menu_send_wol">Send Wake-On-LAN request</string>
<string name="pcview_menu_delete_pc">Delete PC</string>
<string name="pcview_menu_test_network">Test Network Connection</string>
<string name="pcview_menu_details">View Details</string>
<!-- Network test strings -->
<string name="nettest_title_waiting">Testing Network Connection</string>
<string name="nettest_text_waiting">Moonlight is testing your network connection to determine if NVIDIA GameStream is blocked.\n\nThis may take a few seconds…</string>
<string name="nettest_title_done">Network Test Complete</string>
<string name="nettest_text_success">Your network does not appear to be blocking Moonlight. If you still have trouble connecting, check your PC\'s firewall settings.\n\nIf you are trying to stream over the Internet, install the Moonlight Internet Hosting Tool on your PC and run the included Internet Streaming Tester to check your PC\'s Internet connection.</string>
<string name="nettest_text_inconclusive">The network test could not be performed because none of Moonlight\'s connection testing servers were reachable. Check your Internet connection or try again later.</string>
<string name="nettest_text_failure">Your device\'s current network connection seems to be blocking Moonlight. Streaming over the Internet may not work while connected to this network.\n\nThe following network ports were blocked:\n</string>
<string name="nettest_text_blocked">Your device\'s current network connection is blocking Moonlight. Streaming over the Internet may not work while connected to this network.</string>
<!-- Pair messages -->
<string name="pairing">Pairing…</string>
<string name="pair_pc_offline">Computer is offline</string>
@@ -61,6 +74,10 @@
<string name="error_usb_prohibited">USB access is prohibited by your device administrator. Check your Knox or MDM settings.</string>
<string name="unable_to_pin_shortcut">Your current launcher does not allow for creating pinned shortcuts.</string>
<string name="video_decoder_init_failed">Video decoder failed to initialize. Your device may not support the selected resolution or frame rate.</string>
<string name="no_video_received_error">No video received from host.</string>
<string name="no_frame_received_error">Your network connection isn\'t performing well. Reduce your video bitrate setting or try a faster connection.</string>
<string name="early_termination_error">Something went wrong on your host PC when starting the stream.\n\nMake sure you don\'t have any DRM-protected content open on your host PC. You can also try restarting your host PC.\n\nIf the issue persists, try reinstalling your GPU drivers and GeForce Experience.</string>
<string name="check_ports_msg">Check your firewall and port forwarding rules for port(s):</string>
<!-- Start application messages -->
<string name="conn_establishing_title">Establishing Connection</string>
@@ -98,6 +115,7 @@
<string name="applist_menu_details">View Details</string>
<string name="applist_menu_scut">Create Shortcut</string>
<string name="applist_menu_tv_channel">Add to Channel</string>
<string name="applist_menu_hide_app">Hide App</string>
<string name="applist_refresh_title">App List</string>
<string name="applist_refresh_msg">Refreshing apps…</string>
<string name="applist_refresh_error_title">Error</string>
@@ -121,38 +139,39 @@
<string name="category_basic_settings">Basic Settings</string>
<string name="title_resolution_list">Video resolution</string>
<string name="summary_resolution_list">Increase to improve image clarity. Decrease for better performance on lower end devices and slower networks.</string>
<string name="title_native_res_dialog">Native Resolution Warning</string>
<string name="text_native_res_dialog">Native resolution modes are not officially supported by GeForce Experience, so it will not set your host display resolution itself. You will need to set it manually while in game.\n\nIf you choose to create a custom resolution in NVIDIA Control Panel to match your device resolution, please ensure you have read and understood NVIDIA\'s warning regarding possible monitor damage, PC instability, and other potential problems.\n\nWe are not responsible for any problems resulting from creating a custom resolution on your PC.\n\nFinally, your device or host PC may not support streaming at native resolution. If it doesn\'t work on your device, you\'re just out of luck unfortunately.</string>
<string name="title_fps_list">Video frame rate</string>
<string name="summary_fps_list">Increase for a smoother video stream. Decrease for better performance on lower end devices.</string>
<string name="title_seekbar_bitrate">Video bitrate</string>
<string name="summary_seekbar_bitrate">Increase for better image quality. Decrease to improve performance on slower connections.</string>
<string name="suffix_seekbar_bitrate">Kbps</string>
<string name="title_unlock_fps">Unlock all possible frame rates</string>
<string name="summary_unlock_fps">Streaming at 90 or 120 FPS may reduce latency on high-end devices but can cause lag or crashes on devices that can\'t support it</string>
<string name="suffix_seekbar_bitrate_mbps">Mbps</string>
<string name="title_checkbox_stretch_video">Stretch video to full-screen</string>
<string name="title_checkbox_disable_warnings">Disable warning messages</string>
<string name="summary_checkbox_disable_warnings">Disable on-screen connection warning messages while streaming</string>
<string name="title_checkbox_enable_pip">Enable Picture-in-Picture observer mode</string>
<string name="summary_checkbox_enable_pip">Allows the stream to be viewed (but not controlled) while multitasking</string>
<string name="resolution_prefix_native">Native</string>
<string name="category_audio_settings">Audio Settings</string>
<string name="title_audio_config_list">Surround sound configuration</string>
<string name="summary_audio_config_list">Enable 5.1 or 7.1 surround sound for home-theater systems</string>
<string name="category_input_settings">Input Settings</string>
<string name="title_checkbox_touchscreen_trackpad">Use the touchscreen as a trackpad</string>
<string name="summary_checkbox_touchscreen_trackpad">If enabled, the touchscreen acts like a trackpad. If disabled, the touchscreen directly controls the mouse cursor.</string>
<string name="title_checkbox_multi_controller">Automatic gamepad presence detection</string>
<string name="summary_checkbox_multi_controller">Unchecking this option forces a gamepad to always be present</string>
<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="suffix_seekbar_deadzone">%</string>
<string name="title_checkbox_xb1_driver">Xbox 360/One controller driver</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>
<string name="title_checkbox_usb_bind_all">Override Android controller support</string>
<string name="summary_checkbox_usb_bind_all">Forces Moonlight\'s USB driver to take over all supported Xbox gamepads</string>
<string name="title_checkbox_usb_bind_all">Override native Xbox gamepad support</string>
<string name="summary_checkbox_usb_bind_all">Use Moonlight\'s USB driver for all supported gamepads, even if native Xbox controller support is present</string>
<string name="title_checkbox_mouse_emulation">Mouse emulation via gamepad</string>
<string name="summary_checkbox_mouse_emulation">Long pressing the Start button will switch the gamepad into mouse mode</string>
<string name="title_checkbox_mouse_nav_buttons">Enable back and forward mouse buttons</string>
<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="category_on_screen_controls_settings">On-screen Controls Settings</string>
<string name="title_checkbox_show_onscreen_controls">Show on-screen controls</string>
@@ -172,12 +191,12 @@
<string name="suffix_osc_opacity">%</string>
<string name="category_ui_settings">UI Settings</string>
<string name="title_checkbox_enable_pip">Enable Picture-in-Picture observer mode</string>
<string name="summary_checkbox_enable_pip">Allows the stream to be viewed (but not controlled) while multitasking</string>
<string name="title_language_list">Language</string>
<string name="summary_language_list">Language to use for Moonlight</string>
<string name="title_checkbox_list_mode">Use lists instead of grids</string>
<string name="summary_checkbox_list_mode">Display apps and PCs in lists instead of grids</string>
<string name="title_checkbox_small_icon_mode">Use small icons</string>
<string name="summary_checkbox_small_icon_mode">Use small icons in grid items to allow more items on screen</string>
<string name="title_checkbox_small_icon_mode">Use small box art</string>
<string name="summary_checkbox_small_icon_mode">Smaller box art in the app grid allows more apps to be visible on screen</string>
<string name="category_host_settings">Host Settings</string>
<string name="title_checkbox_enable_sops">Optimize game settings</string>
@@ -186,13 +205,19 @@
<string name="summary_checkbox_host_audio">Play audio from the computer and this device</string>
<string name="category_advanced_settings">Advanced Settings</string>
<string name="title_unlock_fps">Unlock all possible frame rates</string>
<string name="summary_unlock_fps">Streaming at 90 or 120 FPS may reduce latency on high-end devices but can cause lag or instability on devices that can\'t support it</string>
<string name="title_checkbox_disable_warnings">Disable warning messages</string>
<string name="summary_checkbox_disable_warnings">Disable on-screen connection warning messages while streaming</string>
<string name="title_disable_frame_drop">Never drop frames</string>
<string name="summary_disable_frame_drop">May reduce micro-stuttering on some devices, but can increase latency</string>
<string name="title_video_format">Change H.265 settings</string>
<string name="summary_video_format">H.265 lowers video bandwidth requirements but requires a very recent device</string>
<string name="summary_video_format">H.265 lowers video bandwidth requirements but requires a newer device</string>
<string name="title_enable_hdr">Enable HDR (Experimental)</string>
<string name="summary_enable_hdr">Stream HDR when the game and PC GPU support it. HDR requires a GTX 1000 series GPU or later.</string>
<string name="title_enable_perf_overlay">Enable performance overlay</string>
<string name="summary_enable_perf_overlay">Display an on-screen overlay with real-time performance information while streaming</string>
<string name="title_enable_perf_overlay">Show performance stats while streaming</string>
<string name="summary_enable_perf_overlay">Display real-time stream performance information while streaming</string>
<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>
</resources>
+34 -20
View File
@@ -10,7 +10,7 @@
android:summary="@string/summary_resolution_list"
android:entries="@array/resolution_names"
android:entryValues="@array/resolution_values"
android:defaultValue="720p" />
android:defaultValue="1280x720" />
<ListPreference
android:key="list_fps"
android:title="@string/title_fps_list"
@@ -22,25 +22,17 @@
android:key="seekbar_bitrate_kbps"
android:dialogMessage="@string/summary_seekbar_bitrate"
seekbar:min="500"
android:max="150000"
seekbar:step="500"
android:max="100000"
seekbar:keyStep="1000"
seekbar:divisor="1000"
android:summary="@string/summary_seekbar_bitrate"
android:text="@string/suffix_seekbar_bitrate"
android:text="@string/suffix_seekbar_bitrate_mbps"
android:title="@string/title_seekbar_bitrate" />
<CheckBoxPreference
android:key="checkbox_unlock_fps"
android:title="@string/title_unlock_fps"
android:summary="@string/summary_unlock_fps"
android:defaultValue="false" />
<CheckBoxPreference
android:key="checkbox_stretch_video"
android:title="@string/title_checkbox_stretch_video"
android:defaultValue="false" />
<CheckBoxPreference
android:key="checkbox_enable_pip"
android:title="@string/title_checkbox_enable_pip"
android:summary="@string/summary_checkbox_enable_pip"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_audio_settings">
<ListPreference
@@ -51,13 +43,19 @@
android:entryValues="@array/audio_config_values"
android:defaultValue="2" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_input_settings">
<PreferenceCategory android:title="@string/category_input_settings"
android:key="category_input_settings">
<!--com.limelight.preferences.SeekBarPreference
android:key="seekbar_deadzone"
android:defaultValue="15"
android:max="50"
android:text="@string/suffix_seekbar_deadzone"
android:title="@string/title_seekbar_deadzone"/-->
<CheckBoxPreference
android:key="checkbox_touchscreen_trackpad"
android:title="@string/title_checkbox_touchscreen_trackpad"
android:summary="@string/summary_checkbox_touchscreen_trackpad"
android:defaultValue="true" />
<CheckBoxPreference
android:key="checkbox_multi_controller"
android:title="@string/title_checkbox_multi_controller"
@@ -89,6 +87,11 @@
android:title="@string/title_checkbox_vibrate_fallback"
android:summary="@string/summary_checkbox_vibrate_fallback"
android:defaultValue="false" />
<CheckBoxPreference
android:key="checkbox_flip_face_buttons"
android:title="@string/title_checkbox_flip_face_buttons"
android:summary="@string/summary_checkbox_flip_face_buttons"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_on_screen_controls_settings"
android:key="category_onscreen_controls">
@@ -142,7 +145,13 @@
android:summary="@string/summary_checkbox_host_audio"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_ui_settings">
<PreferenceCategory android:title="@string/category_ui_settings"
android:key="category_ui_settings">
<CheckBoxPreference
android:key="checkbox_enable_pip"
android:title="@string/title_checkbox_enable_pip"
android:summary="@string/summary_checkbox_enable_pip"
android:defaultValue="false" />
<ListPreference
android:key="list_languages"
android:title="@string/title_language_list"
@@ -154,14 +163,14 @@
android:key="checkbox_small_icon_mode"
android:title="@string/title_checkbox_small_icon_mode"
android:summary="@string/summary_checkbox_small_icon_mode" />
<CheckBoxPreference
android:key="checkbox_list_mode"
android:title="@string/title_checkbox_list_mode"
android:summary="@string/summary_checkbox_list_mode"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/category_advanced_settings"
android:key="category_advanced_settings">
<CheckBoxPreference
android:key="checkbox_unlock_fps"
android:title="@string/title_unlock_fps"
android:summary="@string/summary_unlock_fps"
android:defaultValue="false" />
<CheckBoxPreference
android:key="checkbox_disable_warnings"
android:title="@string/title_checkbox_disable_warnings"
@@ -189,5 +198,10 @@
android:title="@string/title_enable_perf_overlay"
android:summary="@string/summary_enable_perf_overlay"
android:defaultValue="false"/>
<CheckBoxPreference
android:key="checkbox_enable_post_stream_toast"
android:title="@string/title_enable_post_stream_toast"
android:summary="@string/summary_enable_post_stream_toast"
android:defaultValue="false"/>
</PreferenceCategory>
</PreferenceScreen>
+18
View File
@@ -0,0 +1,18 @@
version: 0.0.0.{build}
clone_depth: 1
image: Visual Studio 2019
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'
build_script:
- gradlew.bat build connectedCheck
after_build:
- 'appveyor PushArtifact app\build\reports\lint-results.html'
deploy: off
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.1.3'
}
}
@@ -0,0 +1,5 @@
- Direct touchscreen mouse control is now supported! (disable touchscreen trackpad mode in settings)
- Added an option to switch the A/B and X/Y face buttons on gamepads and on-screen controls
- Added a special error message if no video was received instead of just displaying a black screen
- Improved support for Xbox One S controllers using the new 4.8 firmware
- Improved handling of audio capture errors reported by GeForce Experience
@@ -0,0 +1,4 @@
- Fixed stream starting in 720p when configured for 1080p or 4K at 90 or 120 FPS
- Fixed incorrect behavior of right-side modifier keys while streaming
- Fixed the mouse cursor not reaching the bottom and right edges of the host's screen on some devices
- Updated Traditional and Simplified Chinese translations
@@ -0,0 +1,6 @@
- Improved PC and App Grid UI
- Android 11 optimizations
- Added mapping for Nintendo Switch Pro controller
- Post-stream latency message is now disabled by default
- Fixed control stream connection error on some networks
- Reorganized some of the settings options
@@ -0,0 +1,3 @@
- Removed old button emulation feature which prevented LB+Start, RB+Start, and Start+Select from working as expected
- Fixed edges of on-screen analog sticks being clipped
- Updated Simplified and Traditional Chinese translations
@@ -0,0 +1,4 @@
- Added Ukrainian translation
- Fixed SSL error connecting to GFE 3.20.4 on Android 4.1-4.4
- Fixed a random crash when starting a stream in bad network conditions
- Fixed decoder-related crashes on Xiaomi Mi 10 Lite 5G devices
@@ -0,0 +1,4 @@
- Added the ability to hide unwanted games via a long press on the game icon
- Added a network test to check if Moonlight's connection is being blocked
- Improved display mode selection logic for better frame rate matching
- Fixed crashes on some Snapdragon 765G devices
@@ -0,0 +1,4 @@
- Hiding games now only takes effect after returning to the app list
- Display a warning if the connection is too unstable to stream
- Increase maximum bitrate to 150 Mbps for Ethernet-connected devices
- Update Russian and French translations
@@ -0,0 +1 @@
- Improved connection reliability
@@ -0,0 +1 @@
- Improved performance on Google TV Chromecast and other newer Amlogic devices by using HEVC by default
@@ -0,0 +1,3 @@
- Gamepad button combos to emulate missing Select and Guide buttons have been reintroduced
- For gamepads without a Select button, LB+Start will trigger the Select button and RB+Start will trigger the Guide button
- For gamepads with a Select button, Start+Select will trigger the Guide button
@@ -0,0 +1 @@
- Enabled raw mouse input on the Nvidia Shield TV to avoid mouse acceleration
@@ -0,0 +1 @@
- Fixed missing video or audio when streaming over some Internet connections
@@ -0,0 +1,4 @@
- Added the option to stream at your device's native resolution
- Added 2 finger scrolling in touchpad mode
- Added additional troubleshooting information when a connection fails
- Minor UI tweaks and crash fixes
@@ -0,0 +1,3 @@
- Added support for multiple native resolution options
- Improve accuracy of decoder latency reports for very slow decoders
- Minor UI improvements
@@ -0,0 +1,2 @@
- Add a workaround for the Android 11 gamepad bug when Accessibility features are enabled
- See https://issuetracker.google.com/issues/163120692 for more details

Some files were not shown because too many files have changed in this diff Show More