2014-10-20 13:35:42 -04:00
|
|
|
//
|
|
|
|
|
// StreamManager.m
|
2015-05-12 21:48:30 -04:00
|
|
|
// Moonlight
|
2014-10-20 13:35:42 -04:00
|
|
|
//
|
|
|
|
|
// Created by Diego Waxemberg on 10/20/14.
|
2015-05-12 21:48:30 -04:00
|
|
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
2014-10-20 13:35:42 -04:00
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#import "StreamManager.h"
|
|
|
|
|
#import "CryptoManager.h"
|
|
|
|
|
#import "HttpManager.h"
|
2014-10-20 19:20:50 -04:00
|
|
|
#import "Utils.h"
|
2018-03-27 08:50:40 +02:00
|
|
|
|
2015-01-10 00:37:34 -05:00
|
|
|
#import "StreamView.h"
|
2015-02-01 04:26:47 -05:00
|
|
|
#import "ServerInfoResponse.h"
|
|
|
|
|
#import "HttpResponse.h"
|
|
|
|
|
#import "HttpRequest.h"
|
2015-10-31 23:03:21 -07:00
|
|
|
#import "IdManager.h"
|
2014-10-20 13:35:42 -04:00
|
|
|
|
2021-05-15 17:22:46 -05:00
|
|
|
#include <Limelight.h>
|
|
|
|
|
|
2014-10-20 13:35:42 -04:00
|
|
|
@implementation StreamManager {
|
2014-10-20 20:54:03 -04:00
|
|
|
StreamConfiguration* _config;
|
2018-03-27 08:50:40 +02:00
|
|
|
|
2020-11-01 16:50:02 -06:00
|
|
|
UIView* _renderView;
|
2014-10-21 14:54:10 -04:00
|
|
|
id<ConnectionCallbacks> _callbacks;
|
2014-10-21 01:54:31 -04:00
|
|
|
Connection* _connection;
|
2014-10-20 13:35:42 -04:00
|
|
|
}
|
|
|
|
|
|
2020-11-01 16:50:02 -06:00
|
|
|
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callbacks {
|
2018-03-27 08:50:40 +02:00
|
|
|
self = [super init];
|
|
|
|
|
_config = config;
|
|
|
|
|
_renderView = view;
|
|
|
|
|
_callbacks = callbacks;
|
|
|
|
|
_config.riKey = [Utils randomBytes:16];
|
|
|
|
|
_config.riKeyId = arc4random();
|
|
|
|
|
return self;
|
|
|
|
|
}
|
2014-10-20 20:54:03 -04:00
|
|
|
|
2014-10-20 13:35:42 -04:00
|
|
|
- (void)main {
|
2018-08-27 00:34:34 -07:00
|
|
|
[CryptoManager generateKeyPairUsingSSL];
|
2014-10-20 13:35:42 -04:00
|
|
|
|
2023-02-02 21:54:52 -06:00
|
|
|
HttpManager* hMan = [[HttpManager alloc] initWithAddress:_config.host httpsPort:_config.httpsPort
|
2018-12-22 00:05:48 -08:00
|
|
|
serverCert:_config.serverCert];
|
2014-10-20 13:35:42 -04:00
|
|
|
|
2015-02-01 04:26:47 -05:00
|
|
|
ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init];
|
2018-11-16 19:02:31 -08:00
|
|
|
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[hMan newServerInfoRequest:false]
|
2015-07-08 23:03:23 -07:00
|
|
|
fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]];
|
2015-02-01 04:26:47 -05:00
|
|
|
NSString* pairStatus = [serverInfoResp getStringTag:@"PairStatus"];
|
2015-02-01 20:31:57 -05:00
|
|
|
NSString* appversion = [serverInfoResp getStringTag:@"appversion"];
|
2016-10-05 17:32:30 -07:00
|
|
|
NSString* gfeVersion = [serverInfoResp getStringTag:@"GfeVersion"];
|
2015-11-11 18:24:36 -08:00
|
|
|
NSString* serverState = [serverInfoResp getStringTag:@"state"];
|
2019-10-12 16:41:32 -07:00
|
|
|
if (![serverInfoResp isStatusOk]) {
|
|
|
|
|
[_callbacks launchFailed:serverInfoResp.statusMessage];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (pairStatus == NULL || appversion == NULL || serverState == NULL) {
|
2014-10-23 23:41:07 -04:00
|
|
|
[_callbacks launchFailed:@"Failed to connect to PC"];
|
2014-10-21 15:54:19 -04:00
|
|
|
return;
|
|
|
|
|
}
|
2014-10-23 14:53:31 -04:00
|
|
|
|
|
|
|
|
if (![pairStatus isEqualToString:@"1"]) {
|
|
|
|
|
// Not paired
|
2014-10-23 23:41:07 -04:00
|
|
|
[_callbacks launchFailed:@"Device not paired to PC"];
|
2014-10-23 14:53:31 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-02 21:54:52 -06:00
|
|
|
// Only perform this check on GFE (as indicated by MJOLNIR in state value)
|
|
|
|
|
if ((_config.width > 4096 || _config.height > 4096) && [serverState containsString:@"MJOLNIR"]) {
|
2020-12-24 13:33:54 -06:00
|
|
|
// Pascal added support for 8K HEVC encoding support. Maxwell 2 could encode HEVC but only up to 4K.
|
|
|
|
|
// We can't directly identify Pascal, but we can look for HEVC Main10 which was added in the same generation.
|
|
|
|
|
NSString* codecSupport = [serverInfoResp getStringTag:@"ServerCodecModeSupport"];
|
|
|
|
|
if (codecSupport == nil || !([codecSupport intValue] & 0x200)) {
|
|
|
|
|
[_callbacks launchFailed:@"Your host PC's GPU doesn't support streaming video resolutions over 4K."];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-02 23:13:09 -06:00
|
|
|
// Populate the config's version fields from serverinfo
|
|
|
|
|
_config.appVersion = appversion;
|
|
|
|
|
_config.gfeVersion = gfeVersion;
|
|
|
|
|
|
2014-10-23 23:41:07 -04:00
|
|
|
// resumeApp and launchApp handle calling launchFailed
|
2021-07-02 17:31:45 -05:00
|
|
|
NSString* sessionUrl;
|
2015-11-11 18:24:36 -08:00
|
|
|
if ([serverState hasSuffix:@"_SERVER_BUSY"]) {
|
2014-10-20 21:26:08 -04:00
|
|
|
// App already running, resume it
|
2021-07-02 17:31:45 -05:00
|
|
|
if (![self resumeApp:hMan receiveSessionUrl:&sessionUrl]) {
|
2014-10-21 15:54:19 -04:00
|
|
|
return;
|
|
|
|
|
}
|
2014-10-20 21:26:08 -04:00
|
|
|
} else {
|
|
|
|
|
// Start app
|
2021-07-02 17:31:45 -05:00
|
|
|
if (![self launchApp:hMan receiveSessionUrl:&sessionUrl]) {
|
2014-10-21 15:54:19 -04:00
|
|
|
return;
|
|
|
|
|
}
|
2014-10-20 21:26:08 -04:00
|
|
|
}
|
2018-08-26 15:55:39 -07:00
|
|
|
|
2021-07-02 17:31:45 -05:00
|
|
|
// Populate RTSP session URL from launch/resume response
|
|
|
|
|
_config.rtspSessionUrl = sessionUrl;
|
|
|
|
|
|
2018-06-02 01:01:45 -07:00
|
|
|
// Initializing the renderer must be done on the main thread
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2022-02-06 17:13:03 -06:00
|
|
|
VideoDecoderRenderer* renderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView callbacks:self->_callbacks streamAspectRatio:(float)self->_config.width / (float)self->_config.height useFramePacing:self->_config.useFramePacing];
|
2018-06-02 01:01:45 -07:00
|
|
|
self->_connection = [[Connection alloc] initWithConfig:self->_config renderer:renderer connectionCallbacks:self->_callbacks];
|
|
|
|
|
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
|
|
|
|
|
[opQueue addOperation:self->_connection];
|
|
|
|
|
});
|
2014-10-21 01:54:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) stopStream
|
|
|
|
|
{
|
|
|
|
|
[_connection terminate];
|
2014-10-20 13:35:42 -04:00
|
|
|
}
|
|
|
|
|
|
2021-07-02 17:31:45 -05:00
|
|
|
- (BOOL) launchApp:(HttpManager*)hMan receiveSessionUrl:(NSString**)sessionUrl {
|
2015-02-01 04:26:47 -05:00
|
|
|
HttpResponse* launchResp = [[HttpResponse alloc] init];
|
2023-02-02 23:13:59 -06:00
|
|
|
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:launchResp withUrlRequest:[hMan newLaunchOrResumeRequest:@"launch" config:_config]]];
|
2015-02-01 04:26:47 -05:00
|
|
|
NSString *gameSession = [launchResp getStringTag:@"gamesession"];
|
2018-12-28 01:16:56 -08:00
|
|
|
if (![launchResp isStatusOk]) {
|
|
|
|
|
[_callbacks launchFailed:launchResp.statusMessage];
|
2015-02-10 21:26:55 -05:00
|
|
|
Log(LOG_E, @"Failed Launch Response: %@", launchResp.statusMessage);
|
2014-10-23 23:41:07 -04:00
|
|
|
return FALSE;
|
|
|
|
|
} else if (gameSession == NULL || [gameSession isEqualToString:@"0"]) {
|
2018-12-28 01:16:56 -08:00
|
|
|
[_callbacks launchFailed:@"Failed to launch app"];
|
|
|
|
|
Log(LOG_E, @"Failed to parse game session");
|
2014-10-21 14:54:10 -04:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-02 17:31:45 -05:00
|
|
|
*sessionUrl = [launchResp getStringTag:@"sessionUrl0"];
|
2014-10-21 14:54:10 -04:00
|
|
|
return TRUE;
|
2014-10-20 21:26:08 -04:00
|
|
|
}
|
|
|
|
|
|
2021-07-02 17:31:45 -05:00
|
|
|
- (BOOL) resumeApp:(HttpManager*)hMan receiveSessionUrl:(NSString**)sessionUrl {
|
2015-02-01 04:26:47 -05:00
|
|
|
HttpResponse* resumeResp = [[HttpResponse alloc] init];
|
2023-02-02 23:13:59 -06:00
|
|
|
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:resumeResp withUrlRequest:[hMan newLaunchOrResumeRequest:@"resume" config:_config]]];
|
2015-02-01 04:26:47 -05:00
|
|
|
NSString* resume = [resumeResp getStringTag:@"resume"];
|
2018-12-28 01:16:56 -08:00
|
|
|
if (![resumeResp isStatusOk]) {
|
|
|
|
|
[_callbacks launchFailed:resumeResp.statusMessage];
|
2015-02-10 21:26:55 -05:00
|
|
|
Log(LOG_E, @"Failed Resume Response: %@", resumeResp.statusMessage);
|
2014-10-23 23:41:07 -04:00
|
|
|
return FALSE;
|
|
|
|
|
} else if (resume == NULL || [resume isEqualToString:@"0"]) {
|
2018-12-28 01:16:56 -08:00
|
|
|
[_callbacks launchFailed:@"Failed to resume app"];
|
|
|
|
|
Log(LOG_E, @"Failed to parse resume response");
|
2014-10-21 14:54:10 -04:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-02 17:31:45 -05:00
|
|
|
*sessionUrl = [resumeResp getStringTag:@"sessionUrl0"];
|
2014-10-21 14:54:10 -04:00
|
|
|
return TRUE;
|
2014-10-20 21:26:08 -04:00
|
|
|
}
|
|
|
|
|
|
2020-11-01 13:27:10 -06:00
|
|
|
- (NSString*) getStatsOverlayText {
|
|
|
|
|
video_stats_t stats;
|
|
|
|
|
|
|
|
|
|
if (!_connection) {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (![_connection getVideoStats:&stats]) {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-15 17:22:46 -05:00
|
|
|
uint32_t rtt, variance;
|
|
|
|
|
NSString* latencyString;
|
|
|
|
|
if (LiGetEstimatedRttInfo(&rtt, &variance)) {
|
|
|
|
|
latencyString = [NSString stringWithFormat:@"%u ms (variance: %u ms)", rtt, variance];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
latencyString = @"N/A";
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-27 03:57:49 -05:00
|
|
|
NSString* hostProcessingString;
|
|
|
|
|
if (stats.framesWithHostProcessingLatency != 0) {
|
|
|
|
|
hostProcessingString = [NSString stringWithFormat:@"\nHost processing latency min/max/avg: %.1f/%.1f/%.1f ms",
|
|
|
|
|
stats.minHostProcessingLatency / 10.f,
|
|
|
|
|
stats.maxHostProcessingLatency / 10.f,
|
|
|
|
|
(float)stats.totalHostProcessingLatency / stats.framesWithHostProcessingLatency / 10.f];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
hostProcessingString = @"";
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-01 13:27:10 -06:00
|
|
|
float interval = stats.endTime - stats.startTime;
|
2023-09-27 03:57:49 -05:00
|
|
|
return [NSString stringWithFormat:@"Video stream: %dx%d %.2f FPS (Codec: %@)\nFrames dropped by your network connection: %.2f%%\nAverage network latency: %@%@",
|
2020-11-01 13:27:10 -06:00
|
|
|
_config.width,
|
|
|
|
|
_config.height,
|
|
|
|
|
stats.totalFrames / interval,
|
|
|
|
|
[_connection getActiveCodecName],
|
2021-05-15 17:22:46 -05:00
|
|
|
stats.networkDroppedFrames / interval,
|
2023-09-27 03:57:49 -05:00
|
|
|
latencyString,
|
|
|
|
|
hostProcessingString];
|
2020-11-01 13:27:10 -06:00
|
|
|
}
|
|
|
|
|
|
2014-10-20 13:35:42 -04:00
|
|
|
@end
|