mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-06-17 06:10:58 +00:00
Modernize HTTPS launch/resume for Sunshine
This commit is contained in:
@@ -470,6 +470,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
.setAudioEncryption(true)
|
.setAudioEncryption(true)
|
||||||
.setColorSpace(decoderRenderer.getPreferredColorSpace())
|
.setColorSpace(decoderRenderer.getPreferredColorSpace())
|
||||||
.setColorRange(decoderRenderer.getPreferredColorRange())
|
.setColorRange(decoderRenderer.getPreferredColorRange())
|
||||||
|
.setPersistGamepadsAfterDisconnect(!prefConfig.multiController)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Initialize the connection
|
// Initialize the connection
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import javax.crypto.SecretKey;
|
|||||||
public class ConnectionContext {
|
public class ConnectionContext {
|
||||||
public ComputerDetails.AddressTuple serverAddress;
|
public ComputerDetails.AddressTuple serverAddress;
|
||||||
public int httpsPort;
|
public int httpsPort;
|
||||||
|
public boolean isNvidiaServerSoftware;
|
||||||
public X509Certificate serverCert;
|
public X509Certificate serverCert;
|
||||||
public StreamConfiguration streamConfig;
|
public StreamConfiguration streamConfig;
|
||||||
public NvConnectionListener connListener;
|
public NvConnectionListener connListener;
|
||||||
|
|||||||
@@ -231,6 +231,9 @@ public class NvConnection {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComputerDetails details = h.getComputerDetails(serverInfo);
|
||||||
|
context.isNvidiaServerSoftware = details.nvidiaServer;
|
||||||
|
|
||||||
// May be missing for older servers
|
// May be missing for older servers
|
||||||
context.serverGfeVersion = h.getGfeVersion(serverInfo);
|
context.serverGfeVersion = h.getGfeVersion(serverInfo);
|
||||||
|
|
||||||
@@ -306,7 +309,7 @@ public class NvConnection {
|
|||||||
if (h.getCurrentGame(serverInfo) != 0) {
|
if (h.getCurrentGame(serverInfo) != 0) {
|
||||||
try {
|
try {
|
||||||
if (h.getCurrentGame(serverInfo) == app.getAppId()) {
|
if (h.getCurrentGame(serverInfo) == app.getAppId()) {
|
||||||
if (!h.resumeApp(context)) {
|
if (!h.launchApp(context, "resume", app.getAppId(), context.negotiatedHdr)) {
|
||||||
context.connListener.displayMessage("Failed to resume existing session");
|
context.connListener.displayMessage("Failed to resume existing session");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -364,7 +367,7 @@ public class NvConnection {
|
|||||||
private boolean launchNotRunningApp(NvHTTP h, ConnectionContext context)
|
private boolean launchNotRunningApp(NvHTTP h, ConnectionContext context)
|
||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
// Launch the app since it's not running
|
// Launch the app since it's not running
|
||||||
if (!h.launchApp(context, context.streamConfig.getApp().getAppId(), context.negotiatedHdr)) {
|
if (!h.launchApp(context, "launch", context.streamConfig.getApp().getAppId(), context.negotiatedHdr)) {
|
||||||
context.connListener.displayMessage("Failed to launch application");
|
context.connListener.displayMessage("Failed to launch application");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class StreamConfiguration {
|
|||||||
private int encryptionFlags;
|
private int encryptionFlags;
|
||||||
private int colorRange;
|
private int colorRange;
|
||||||
private int colorSpace;
|
private int colorSpace;
|
||||||
|
private boolean persistGamepadsAfterDisconnect;
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private StreamConfiguration config = new StreamConfiguration();
|
private StreamConfiguration config = new StreamConfiguration();
|
||||||
@@ -109,6 +110,11 @@ public class StreamConfiguration {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StreamConfiguration.Builder setPersistGamepadsAfterDisconnect(boolean value) {
|
||||||
|
config.persistGamepadsAfterDisconnect = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public StreamConfiguration.Builder setClientRefreshRateX100(int refreshRateX100) {
|
public StreamConfiguration.Builder setClientRefreshRateX100(int refreshRateX100) {
|
||||||
config.clientRefreshRateX100 = refreshRateX100;
|
config.clientRefreshRateX100 = refreshRateX100;
|
||||||
return this;
|
return this;
|
||||||
@@ -231,6 +237,10 @@ public class StreamConfiguration {
|
|||||||
return attachedGamepadMask;
|
return attachedGamepadMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getPersistGamepadsAfterDisconnect() {
|
||||||
|
return persistGamepadsAfterDisconnect;
|
||||||
|
}
|
||||||
|
|
||||||
public int getClientRefreshRateX100() {
|
public int getClientRefreshRateX100() {
|
||||||
return clientRefreshRateX100;
|
return clientRefreshRateX100;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public class ComputerDetails {
|
|||||||
public PairingManager.PairState pairState;
|
public PairingManager.PairState pairState;
|
||||||
public int runningGameId;
|
public int runningGameId;
|
||||||
public String rawAppList;
|
public String rawAppList;
|
||||||
|
public boolean nvidiaServer;
|
||||||
|
|
||||||
public ComputerDetails() {
|
public ComputerDetails() {
|
||||||
// Use defaults
|
// Use defaults
|
||||||
@@ -143,6 +144,7 @@ public class ComputerDetails {
|
|||||||
this.httpsPort = details.httpsPort;
|
this.httpsPort = details.httpsPort;
|
||||||
this.pairState = details.pairState;
|
this.pairState = details.pairState;
|
||||||
this.runningGameId = details.runningGameId;
|
this.runningGameId = details.runningGameId;
|
||||||
|
this.nvidiaServer = details.nvidiaServer;
|
||||||
this.rawAppList = details.rawAppList;
|
this.rawAppList = details.rawAppList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -343,9 +343,8 @@ public class NvHTTP {
|
|||||||
return new ComputerDetails.AddressTuple(address, port);
|
return new ComputerDetails.AddressTuple(address, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerDetails getComputerDetails(boolean likelyOnline) throws IOException, XmlPullParserException {
|
public ComputerDetails getComputerDetails(String serverInfo) throws IOException, XmlPullParserException {
|
||||||
ComputerDetails details = new ComputerDetails();
|
ComputerDetails details = new ComputerDetails();
|
||||||
String serverInfo = getServerInfo(likelyOnline);
|
|
||||||
|
|
||||||
details.name = getXmlString(serverInfo, "hostname", false);
|
details.name = getXmlString(serverInfo, "hostname", false);
|
||||||
if (details.name == null || details.name.isEmpty()) {
|
if (details.name == null || details.name.isEmpty()) {
|
||||||
@@ -369,12 +368,19 @@ public class NvHTTP {
|
|||||||
details.pairState = getPairState(serverInfo);
|
details.pairState = getPairState(serverInfo);
|
||||||
details.runningGameId = getCurrentGame(serverInfo);
|
details.runningGameId = getCurrentGame(serverInfo);
|
||||||
|
|
||||||
|
// The MJOLNIR codename was used by GFE but never by any third-party server
|
||||||
|
details.nvidiaServer = getXmlString(serverInfo, "state", true).contains("MJOLNIR");
|
||||||
|
|
||||||
// We could reach it so it's online
|
// We could reach it so it's online
|
||||||
details.state = ComputerDetails.State.ONLINE;
|
details.state = ComputerDetails.State.ONLINE;
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComputerDetails getComputerDetails(boolean likelyOnline) throws IOException, XmlPullParserException {
|
||||||
|
return getComputerDetails(getServerInfo(likelyOnline));
|
||||||
|
}
|
||||||
|
|
||||||
// This hack is Android-specific but we do it on all platforms
|
// This hack is Android-specific but we do it on all platforms
|
||||||
// because it doesn't really matter
|
// because it doesn't really matter
|
||||||
private OkHttpClient performAndroidTlsHack(OkHttpClient client) {
|
private OkHttpClient performAndroidTlsHack(OkHttpClient client) {
|
||||||
@@ -731,27 +737,30 @@ public class NvHTTP {
|
|||||||
return new String(hexChars);
|
return new String(hexChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean launchApp(ConnectionContext context, int appId, boolean enableHdr) throws IOException, XmlPullParserException {
|
public boolean launchApp(ConnectionContext context, String verb, int appId, boolean enableHdr) throws IOException, XmlPullParserException {
|
||||||
// Using an FPS value over 60 causes SOPS to default to 720p60,
|
// 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
|
// 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
|
// used to use 60 here but that locked the frame rate to 60 FPS
|
||||||
// on GFE 3.20.3.
|
// on GFE 3.20.3.
|
||||||
int fps = context.streamConfig.getLaunchRefreshRate() > 60 ? 0 : context.streamConfig.getLaunchRefreshRate();
|
int fps = context.isNvidiaServerSoftware && context.streamConfig.getLaunchRefreshRate() > 60 ?
|
||||||
|
0 : context.streamConfig.getLaunchRefreshRate();
|
||||||
|
|
||||||
|
boolean enableSops = context.streamConfig.getSops();
|
||||||
|
if (context.isNvidiaServerSoftware) {
|
||||||
// Using an unsupported resolution (not 720p, 1080p, or 4K) causes
|
// Using an unsupported resolution (not 720p, 1080p, or 4K) causes
|
||||||
// GFE to force SOPS to 720p60. This is fine for < 720p resolutions like
|
// 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.
|
// 360p or 480p, but it is not ideal for 1440p and other resolutions.
|
||||||
// When we detect an unsupported resolution, disable SOPS unless it's under 720p.
|
// When we detect an unsupported resolution, disable SOPS unless it's under 720p.
|
||||||
// FIXME: Detect support resolutions using the serverinfo response, not a hardcoded list
|
// FIXME: Detect support resolutions using the serverinfo response, not a hardcoded list
|
||||||
boolean enableSops = context.streamConfig.getSops();
|
|
||||||
if (context.negotiatedWidth * context.negotiatedHeight > 1280 * 720 &&
|
if (context.negotiatedWidth * context.negotiatedHeight > 1280 * 720 &&
|
||||||
context.negotiatedWidth * context.negotiatedHeight != 1920 * 1080 &&
|
context.negotiatedWidth * context.negotiatedHeight != 1920 * 1080 &&
|
||||||
context.negotiatedWidth * context.negotiatedHeight != 3840 * 2160) {
|
context.negotiatedWidth * context.negotiatedHeight != 3840 * 2160) {
|
||||||
LimeLog.info("Disabling SOPS due to non-standard resolution: "+context.negotiatedWidth+"x"+context.negotiatedHeight);
|
LimeLog.info("Disabling SOPS due to non-standard resolution: "+context.negotiatedWidth+"x"+context.negotiatedHeight);
|
||||||
enableSops = false;
|
enableSops = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "launch",
|
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), verb,
|
||||||
"appid=" + appId +
|
"appid=" + appId +
|
||||||
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
||||||
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
||||||
@@ -760,24 +769,11 @@ public class NvHTTP {
|
|||||||
(!enableHdr ? "" : "&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0") +
|
(!enableHdr ? "" : "&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0") +
|
||||||
"&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) +
|
"&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) +
|
||||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo() +
|
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo() +
|
||||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
|
"&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() +
|
||||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""));
|
"&gcmap=" + context.streamConfig.getAttachedGamepadMask() +
|
||||||
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
|
"&gcpersist="+(context.streamConfig.getPersistGamepadsAfterDisconnect() ? 1 : 0));
|
||||||
// sessionUrl0 will be missing for older GFE versions
|
if ((verb.equals("launch") && !getXmlString(xmlStr, "gamesession", true).equals("0") ||
|
||||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
(verb.equals("resume") && !getXmlString(xmlStr, "resume", true).equals("0")))) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
|
|
||||||
String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "resume",
|
|
||||||
"rikey="+bytesToHex(context.riKey.getEncoded()) +
|
|
||||||
"&rikeyid="+context.riKeyId +
|
|
||||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo());
|
|
||||||
if (!getXmlString(xmlStr, "resume", true).equals("0")) {
|
|
||||||
// sessionUrl0 will be missing for older GFE versions
|
// sessionUrl0 will be missing for older GFE versions
|
||||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user