mirror of
https://github.com/moonlight-stream/moonlight-ios.git
synced 2026-10-22 08:07:46 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ba71db7b2 | ||
|
|
c1e6aeb43c | ||
|
|
e3ffb66ef0 | ||
|
|
5daa82b0ba | ||
|
|
a8bcc6afba | ||
|
|
e6aa16ad01 | ||
|
|
ad0705d126 | ||
|
|
2f31f93bf8 | ||
|
|
ea3be613c5 | ||
|
|
b495246f34 | ||
|
|
814470f4bf | ||
|
|
82293a08b6 | ||
|
|
20d66336a7 | ||
|
|
c8441ec9fa | ||
|
|
f72135c227 | ||
|
|
100e6e4ce8 | ||
|
|
ec8be20d62 |
No files matched your search
@@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (nonatomic) PairState pairState;
|
@property (nonatomic) PairState pairState;
|
||||||
@property (nonatomic) NSString * activeAddress;
|
@property (nonatomic) NSString * activeAddress;
|
||||||
|
|
||||||
|
- (NSComparisonResult)compareName:(Host *)other;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -14,4 +14,8 @@
|
|||||||
@synthesize online;
|
@synthesize online;
|
||||||
@synthesize activeAddress;
|
@synthesize activeAddress;
|
||||||
|
|
||||||
|
- (NSComparisonResult)compareName:(Host *)other {
|
||||||
|
return [self.name caseInsensitiveCompare:other.name];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.0</string>
|
<string>1.0.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>3</string>
|
<string>4</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.games</string>
|
<string>public.app-category.games</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|||||||
@@ -87,20 +87,32 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
|
|||||||
|
|
||||||
Log(LOG_D, @"%@ has %d unique addresses", _host.name, [addresses count]);
|
Log(LOG_D, @"%@ has %d unique addresses", _host.name, [addresses count]);
|
||||||
|
|
||||||
for (NSString *address in addresses) {
|
// Give the PC 3 tries to respond before declaring it offline
|
||||||
if (self.cancelled) {
|
for (int i = 0; i < 3; i++) {
|
||||||
// Get out without updating the status because
|
for (NSString *address in addresses) {
|
||||||
// it might not have finished checking the various
|
if (self.cancelled) {
|
||||||
// addresses
|
// Get out without updating the status because
|
||||||
return;
|
// it might not have finished checking the various
|
||||||
|
// addresses
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerInfoResponse* serverInfoResp = [self requestInfoAtAddress:address];
|
||||||
|
receivedResponse = [self checkResponse:serverInfoResp];
|
||||||
|
if (receivedResponse) {
|
||||||
|
_host.activeAddress = address;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerInfoResponse* serverInfoResp = [self requestInfoAtAddress:address];
|
|
||||||
receivedResponse = [self checkResponse:serverInfoResp];
|
|
||||||
if (receivedResponse) {
|
if (receivedResponse) {
|
||||||
_host.activeAddress = address;
|
Log(LOG_I, @"Received serverinfo response on try %d", i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Wait for one second then retry
|
||||||
|
[NSThread sleepForTimeInterval:1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_host.online = receivedResponse;
|
_host.online = receivedResponse;
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ static const NSString* HTTPS_PORT = @"47984";
|
|||||||
NSURL* url = [[NSURL alloc] initWithString:urlString];
|
NSURL* url = [[NSURL alloc] initWithString:urlString];
|
||||||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
|
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
|
||||||
if (normalTimeout) {
|
if (normalTimeout) {
|
||||||
// Timeout the request after 5 seconds
|
// Timeout the request after 7 seconds
|
||||||
[request setTimeoutInterval:5];
|
[request setTimeoutInterval:7];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Timeout the request after 60 seconds
|
// Timeout the request after 60 seconds
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ([serverInfoResp isStatusOk]) {
|
if ([serverInfoResp isStatusOk]) {
|
||||||
if (![[serverInfoResp getStringTag:@"currentgame"] isEqual:@"0"]) {
|
if (![[serverInfoResp getStringTag:@"state"] hasSuffix:@"_SERVER_AVAILABLE"]) {
|
||||||
[_callback pairFailed:@"You must stop streaming before attempting to pair."];
|
[_callback pairFailed:@"You must stop streaming before attempting to pair."];
|
||||||
} else if (![[serverInfoResp getStringTag:@"PairStatus"] isEqual:@"1"]) {
|
} else if (![[serverInfoResp getStringTag:@"PairStatus"] isEqual:@"1"]) {
|
||||||
[self initiatePair];
|
[self initiatePair];
|
||||||
|
|||||||
@@ -48,11 +48,11 @@
|
|||||||
ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init];
|
ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init];
|
||||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[hMan newServerInfoRequest]
|
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[hMan newServerInfoRequest]
|
||||||
fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]];
|
fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]];
|
||||||
NSString* currentGame = [serverInfoResp getStringTag:@"currentgame"];
|
|
||||||
NSString* pairStatus = [serverInfoResp getStringTag:@"PairStatus"];
|
NSString* pairStatus = [serverInfoResp getStringTag:@"PairStatus"];
|
||||||
NSString* currentClient = [serverInfoResp getStringTag:@"CurrentClient"];
|
NSString* currentClient = [serverInfoResp getStringTag:@"CurrentClient"];
|
||||||
NSString* appversion = [serverInfoResp getStringTag:@"appversion"];
|
NSString* appversion = [serverInfoResp getStringTag:@"appversion"];
|
||||||
if (![serverInfoResp isStatusOk] || currentGame == NULL || pairStatus == NULL || appversion == NULL) {
|
NSString* serverState = [serverInfoResp getStringTag:@"state"];
|
||||||
|
if (![serverInfoResp isStatusOk] || pairStatus == NULL || appversion == NULL || serverState == NULL) {
|
||||||
[_callbacks launchFailed:@"Failed to connect to PC"];
|
[_callbacks launchFailed:@"Failed to connect to PC"];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resumeApp and launchApp handle calling launchFailed
|
// resumeApp and launchApp handle calling launchFailed
|
||||||
if (![currentGame isEqualToString:@"0"]) {
|
if ([serverState hasSuffix:@"_SERVER_BUSY"]) {
|
||||||
if (![currentClient isEqualToString:@"1"]) {
|
if (![currentClient isEqualToString:@"1"]) {
|
||||||
// The server is streaming to someone else
|
// The server is streaming to someone else
|
||||||
[_callbacks launchFailed:@"There is another stream in progress"];
|
[_callbacks launchFailed:@"There is another stream in progress"];
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
@interface UIAppView : UIView
|
@interface UIAppView : UIView
|
||||||
|
|
||||||
- (id) initWithApp:(App*)app andCallback:(id<AppCallback>)callback;
|
- (id) initWithApp:(App*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback;
|
||||||
- (void) updateAppImage;
|
- (void) updateAppImage;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
+41
-14
@@ -13,36 +13,39 @@
|
|||||||
UIButton* _appButton;
|
UIButton* _appButton;
|
||||||
UILabel* _appLabel;
|
UILabel* _appLabel;
|
||||||
UIImageView* _appOverlay;
|
UIImageView* _appOverlay;
|
||||||
|
NSCache* _artCache;
|
||||||
id<AppCallback> _callback;
|
id<AppCallback> _callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id) initWithApp:(App*)app andCallback:(id<AppCallback>)callback {
|
static UIImage* noImage;
|
||||||
|
|
||||||
|
- (id) initWithApp:(App*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
_app = app;
|
_app = app;
|
||||||
_callback = callback;
|
_callback = callback;
|
||||||
|
_artCache = cache;
|
||||||
|
|
||||||
|
// Cache the NoAppImage ourselves to avoid
|
||||||
|
// having to load it each time
|
||||||
|
if (noImage == nil) {
|
||||||
|
noImage = [UIImage imageNamed:@"NoAppImage"];
|
||||||
|
}
|
||||||
|
|
||||||
_appButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
_appButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
UIImage* noImage = [UIImage imageNamed:@"NoAppImage"];
|
|
||||||
[_appButton setBackgroundImage:noImage forState:UIControlStateNormal];
|
[_appButton setBackgroundImage:noImage forState:UIControlStateNormal];
|
||||||
[_appButton setContentEdgeInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
[_appButton setContentEdgeInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||||
[_appButton sizeToFit];
|
[_appButton sizeToFit];
|
||||||
[_appButton addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventTouchUpInside];
|
[_appButton addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
|
||||||
_appOverlay = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Play"]];
|
|
||||||
_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
|
|
||||||
_appOverlay.layer.shadowOffset = CGSizeMake(0, 0);
|
|
||||||
_appOverlay.layer.shadowOpacity = 1;
|
|
||||||
_appOverlay.layer.shadowRadius = 2.0;
|
|
||||||
[_appOverlay setHidden: YES];
|
|
||||||
|
|
||||||
[self addSubview:_appButton];
|
[self addSubview:_appButton];
|
||||||
[self addSubview:_appOverlay];
|
|
||||||
[self sizeToFit];
|
[self sizeToFit];
|
||||||
|
|
||||||
_appButton.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height);
|
_appButton.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height);
|
||||||
_appOverlay.frame = CGRectMake(0, 0, noImage.size.width / 2.f, noImage.size.height / 4.f);
|
|
||||||
self.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height);
|
self.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height);
|
||||||
[_appOverlay setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/6)];
|
|
||||||
|
// Rasterizing the cell layer increases rendering performance by quite a bit
|
||||||
|
self.layer.shouldRasterize = YES;
|
||||||
|
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -52,13 +55,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void) updateAppImage {
|
- (void) updateAppImage {
|
||||||
[_appOverlay setHidden:!_app.isRunning];
|
if (_appOverlay != nil) {
|
||||||
|
[_appOverlay removeFromSuperview];
|
||||||
|
_appOverlay = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_app.isRunning) {
|
||||||
|
// Only create the app overlay if needed
|
||||||
|
_appOverlay = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Play"]];
|
||||||
|
_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||||
|
_appOverlay.layer.shadowOffset = CGSizeMake(0, 0);
|
||||||
|
_appOverlay.layer.shadowOpacity = 1;
|
||||||
|
_appOverlay.layer.shadowRadius = 2.0;
|
||||||
|
|
||||||
|
[self addSubview:_appOverlay];
|
||||||
|
|
||||||
|
_appOverlay.frame = CGRectMake(0, 0, noImage.size.width / 2.f, noImage.size.height / 4.f);
|
||||||
|
[_appOverlay setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/6)];
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Improve no-app image detection
|
// TODO: Improve no-app image detection
|
||||||
BOOL noAppImage = false;
|
BOOL noAppImage = false;
|
||||||
|
|
||||||
if (_app.image != nil) {
|
if (_app.image != nil) {
|
||||||
UIImage* appImage = [UIImage imageWithData:_app.image];
|
// Load the decoded image from the cache
|
||||||
|
UIImage* appImage = [_artCache objectForKey:_app];
|
||||||
|
if (appImage == nil) {
|
||||||
|
// Not cached; we have to decode this now
|
||||||
|
appImage = [UIImage imageWithData:_app.image];
|
||||||
|
[_artCache setObject:appImage forKey:_app];
|
||||||
|
}
|
||||||
|
|
||||||
// This size of image might be blank image received from GameStream.
|
// This size of image might be blank image received from GameStream.
|
||||||
if (!(appImage.size.width == 130.f && appImage.size.height == 180.f)) {
|
if (!(appImage.size.width == 130.f && appImage.size.height == 180.f)) {
|
||||||
_appButton.frame = CGRectMake(0, 0, appImage.size.width / 2, appImage.size.height / 2);
|
_appButton.frame = CGRectMake(0, 0, appImage.size.width / 2, appImage.size.height / 2);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
@import ImageIO;
|
||||||
|
|
||||||
#import "MainFrameViewController.h"
|
#import "MainFrameViewController.h"
|
||||||
#import "CryptoManager.h"
|
#import "CryptoManager.h"
|
||||||
#import "HttpManager.h"
|
#import "HttpManager.h"
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
UIScrollView* hostScrollView;
|
UIScrollView* hostScrollView;
|
||||||
int currentPosition;
|
int currentPosition;
|
||||||
NSArray* _sortedAppList;
|
NSArray* _sortedAppList;
|
||||||
|
NSCache* _boxArtCache;
|
||||||
}
|
}
|
||||||
static NSMutableSet* hostList;
|
static NSMutableSet* hostList;
|
||||||
|
|
||||||
@@ -97,11 +100,26 @@ static NSMutableSet* hostList;
|
|||||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
HttpManager* hMan = [[HttpManager alloc] initWithHost:host.activeAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert];
|
HttpManager* hMan = [[HttpManager alloc] initWithHost:host.activeAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert];
|
||||||
|
|
||||||
AppListResponse* appListResp = [[AppListResponse alloc] init];
|
|
||||||
|
|
||||||
// Exempt this host from discovery while handling the applist query
|
// Exempt this host from discovery while handling the applist query
|
||||||
[_discMan removeHostFromDiscovery:host];
|
[_discMan removeHostFromDiscovery:host];
|
||||||
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appListResp withUrlRequest:[hMan newAppListRequest]]];
|
|
||||||
|
// Try up to 5 times to get the app list
|
||||||
|
AppListResponse* appListResp;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
appListResp = [[AppListResponse alloc] init];
|
||||||
|
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appListResp withUrlRequest:[hMan newAppListRequest]]];
|
||||||
|
if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) {
|
||||||
|
Log(LOG_W, @"Failed to get applist on try %d: %@", i, appListResp.statusMessage);
|
||||||
|
|
||||||
|
// Wait for one second then retry
|
||||||
|
[NSThread sleepForTimeInterval:1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log(LOG_I, @"App list successfully retreived - took %d tries", i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[_discMan addHostToDiscovery:host];
|
[_discMan addHostToDiscovery:host];
|
||||||
|
|
||||||
if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) {
|
if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) {
|
||||||
@@ -195,6 +213,10 @@ static NSMutableSet* hostList;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void) receivedAssetForApp:(App*)app {
|
- (void) receivedAssetForApp:(App*)app {
|
||||||
|
// Update the box art cache now so we don't have to do it
|
||||||
|
// on the main thread
|
||||||
|
[self updateBoxArtCacheForApp:app];
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self.collectionView reloadData];
|
[self.collectionView reloadData];
|
||||||
});
|
});
|
||||||
@@ -500,6 +522,8 @@ static NSMutableSet* hostList;
|
|||||||
hostList = [[NSMutableSet alloc] init];
|
hostList = [[NSMutableSet alloc] init];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_boxArtCache = [[NSCache alloc] init];
|
||||||
|
|
||||||
[self setAutomaticallyAdjustsScrollViewInsets:NO];
|
[self setAutomaticallyAdjustsScrollViewInsets:NO];
|
||||||
|
|
||||||
hostScrollView = [[ComputerScrollView alloc] init];
|
hostScrollView = [[ComputerScrollView alloc] init];
|
||||||
@@ -524,10 +548,28 @@ static NSMutableSet* hostList;
|
|||||||
[self retrieveSavedHosts];
|
[self retrieveSavedHosts];
|
||||||
_discMan = [[DiscoveryManager alloc] initWithHosts:[hostList allObjects] andCallback:self];
|
_discMan = [[DiscoveryManager alloc] initWithHosts:[hostList allObjects] andCallback:self];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||||
|
selector: @selector(handleReturnToForeground)
|
||||||
|
name: UIApplicationWillEnterForegroundNotification
|
||||||
|
object: nil];
|
||||||
|
|
||||||
[self updateHosts];
|
[self updateHosts];
|
||||||
[self.view addSubview:hostScrollView];
|
[self.view addSubview:hostScrollView];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-(void)dealloc
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)handleReturnToForeground
|
||||||
|
{
|
||||||
|
// This will refresh the applist
|
||||||
|
if (_selectedHost != nil) {
|
||||||
|
[self hostClicked:_selectedHost view:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated
|
- (void)viewDidAppear:(BOOL)animated
|
||||||
{
|
{
|
||||||
[super viewDidAppear:animated];
|
[super viewDidAppear:animated];
|
||||||
@@ -540,10 +582,7 @@ static NSMutableSet* hostList;
|
|||||||
|
|
||||||
[_discMan startDiscovery];
|
[_discMan startDiscovery];
|
||||||
|
|
||||||
// This will refresh the applist
|
[self handleReturnToForeground];
|
||||||
if (_selectedHost != nil) {
|
|
||||||
[self hostClicked:_selectedHost view:nil];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated
|
- (void)viewDidDisappear:(BOOL)animated
|
||||||
@@ -552,6 +591,9 @@ static NSMutableSet* hostList;
|
|||||||
// when discovery stops, we must create a new instance because you cannot restart an NSOperation when it is finished
|
// when discovery stops, we must create a new instance because you cannot restart an NSOperation when it is finished
|
||||||
[_discMan stopDiscovery];
|
[_discMan stopDiscovery];
|
||||||
|
|
||||||
|
// Purge the box art cache
|
||||||
|
[_boxArtCache removeAllObjects];
|
||||||
|
|
||||||
// In case the host objects were updated in the background
|
// In case the host objects were updated in the background
|
||||||
[[[DataManager alloc] init] saveData];
|
[[[DataManager alloc] init] saveData];
|
||||||
}
|
}
|
||||||
@@ -598,13 +640,16 @@ static NSMutableSet* hostList;
|
|||||||
UIComputerView* compView;
|
UIComputerView* compView;
|
||||||
float prevEdge = -1;
|
float prevEdge = -1;
|
||||||
@synchronized (hostList) {
|
@synchronized (hostList) {
|
||||||
for (Host* comp in hostList) {
|
// Sort the host list in alphabetical order
|
||||||
|
NSArray* sortedHostList = [[hostList allObjects] sortedArrayUsingSelector:@selector(compareName:)];
|
||||||
|
for (Host* comp in sortedHostList) {
|
||||||
compView = [[UIComputerView alloc] initWithComputer:comp andCallback:self];
|
compView = [[UIComputerView alloc] initWithComputer:comp andCallback:self];
|
||||||
compView.center = CGPointMake([self getCompViewX:compView addComp:addComp prevEdge:prevEdge], hostScrollView.frame.size.height / 2);
|
compView.center = CGPointMake([self getCompViewX:compView addComp:addComp prevEdge:prevEdge], hostScrollView.frame.size.height / 2);
|
||||||
prevEdge = compView.frame.origin.x + compView.frame.size.width;
|
prevEdge = compView.frame.origin.x + compView.frame.size.width;
|
||||||
[hostScrollView addSubview:compView];
|
[hostScrollView addSubview:compView];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prevEdge = [self getCompViewX:addComp addComp:addComp prevEdge:prevEdge];
|
prevEdge = [self getCompViewX:addComp addComp:addComp prevEdge:prevEdge];
|
||||||
addComp.center = CGPointMake(prevEdge, hostScrollView.frame.size.height / 2);
|
addComp.center = CGPointMake(prevEdge, hostScrollView.frame.size.height / 2);
|
||||||
|
|
||||||
@@ -620,6 +665,30 @@ static NSMutableSet* hostList;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function forces immediate decoding of the UIImage, rather
|
||||||
|
// than the default lazy decoding that results in janky scrolling.
|
||||||
|
+ (UIImage*) loadBoxArtForCaching:(App*)app {
|
||||||
|
|
||||||
|
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)app.image, NULL);
|
||||||
|
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: (id)kCFBooleanTrue});
|
||||||
|
|
||||||
|
UIImage *boxArt = [UIImage imageWithCGImage:cgImage];
|
||||||
|
|
||||||
|
CGImageRelease(cgImage);
|
||||||
|
CFRelease(source);
|
||||||
|
|
||||||
|
return boxArt;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) updateBoxArtCacheForApp:(App*)app {
|
||||||
|
if (app.image == nil) {
|
||||||
|
[_boxArtCache removeObjectForKey:app];
|
||||||
|
}
|
||||||
|
else if ([_boxArtCache objectForKey:app] == nil) {
|
||||||
|
[_boxArtCache setObject:[MainFrameViewController loadBoxArtForCaching:app] forKey:app];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void) updateAppsForHost:(Host*)host {
|
- (void) updateAppsForHost:(Host*)host {
|
||||||
if (host != _selectedHost) {
|
if (host != _selectedHost) {
|
||||||
Log(LOG_W, @"Mismatched host during app update");
|
Log(LOG_W, @"Mismatched host during app update");
|
||||||
@@ -629,6 +698,33 @@ static NSMutableSet* hostList;
|
|||||||
_sortedAppList = [host.appList allObjects];
|
_sortedAppList = [host.appList allObjects];
|
||||||
_sortedAppList = [_sortedAppList sortedArrayUsingSelector:@selector(compareName:)];
|
_sortedAppList = [_sortedAppList sortedArrayUsingSelector:@selector(compareName:)];
|
||||||
|
|
||||||
|
// Split the sorted array in half to allow 2 jobs to process app assets at once
|
||||||
|
NSArray *firstHalf;
|
||||||
|
NSArray *secondHalf;
|
||||||
|
NSRange range;
|
||||||
|
|
||||||
|
range.location = 0;
|
||||||
|
range.length = [_sortedAppList count] / 2;
|
||||||
|
|
||||||
|
firstHalf = [_sortedAppList subarrayWithRange:range];
|
||||||
|
|
||||||
|
range.location = range.length;
|
||||||
|
range.length = [_sortedAppList count] - range.length;
|
||||||
|
|
||||||
|
secondHalf = [_sortedAppList subarrayWithRange:range];
|
||||||
|
|
||||||
|
// Start 2 jobs
|
||||||
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
for (App* app in firstHalf) {
|
||||||
|
[self updateBoxArtCacheForApp:app];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
for (App* app in secondHalf) {
|
||||||
|
[self updateBoxArtCacheForApp:app];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
[hostScrollView removeFromSuperview];
|
[hostScrollView removeFromSuperview];
|
||||||
[self.collectionView reloadData];
|
[self.collectionView reloadData];
|
||||||
}
|
}
|
||||||
@@ -636,9 +732,8 @@ static NSMutableSet* hostList;
|
|||||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AppCell" forIndexPath:indexPath];
|
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AppCell" forIndexPath:indexPath];
|
||||||
|
|
||||||
|
|
||||||
App* app = _sortedAppList[indexPath.row];
|
App* app = _sortedAppList[indexPath.row];
|
||||||
UIAppView* appView = [[UIAppView alloc] initWithApp:app andCallback:self];
|
UIAppView* appView = [[UIAppView alloc] initWithApp:app cache:_boxArtCache andCallback:self];
|
||||||
[appView updateAppImage];
|
[appView updateAppImage];
|
||||||
|
|
||||||
if (appView.bounds.size.width > 10.0) {
|
if (appView.bounds.size.width > 10.0) {
|
||||||
|
|||||||
@@ -38,13 +38,39 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
|
|||||||
NSInteger onscreenControls = [currentSettings.onscreenControls integerValue];
|
NSInteger onscreenControls = [currentSettings.onscreenControls integerValue];
|
||||||
|
|
||||||
[self.resolutionSelector setSelectedSegmentIndex:resolution];
|
[self.resolutionSelector setSelectedSegmentIndex:resolution];
|
||||||
|
[self.resolutionSelector addTarget:self action:@selector(newResolutionFpsChosen) forControlEvents:UIControlEventValueChanged];
|
||||||
[self.framerateSelector setSelectedSegmentIndex:framerate];
|
[self.framerateSelector setSelectedSegmentIndex:framerate];
|
||||||
|
[self.framerateSelector addTarget:self action:@selector(newResolutionFpsChosen) forControlEvents:UIControlEventValueChanged];
|
||||||
[self.onscreenControlSelector setSelectedSegmentIndex:onscreenControls];
|
[self.onscreenControlSelector setSelectedSegmentIndex:onscreenControls];
|
||||||
[self.bitrateSlider setValue:(_bitrate / BITRATE_INTERVAL) animated:YES];
|
[self.bitrateSlider setValue:(_bitrate / BITRATE_INTERVAL) animated:YES];
|
||||||
[self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged];
|
[self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged];
|
||||||
[self updateBitrateText];
|
[self updateBitrateText];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) newResolutionFpsChosen {
|
||||||
|
NSInteger frameRate = [self getChosenFrameRate];
|
||||||
|
NSInteger resHeight = [self getChosenStreamHeight];
|
||||||
|
NSInteger defaultBitrate;
|
||||||
|
|
||||||
|
// 1080p60 is 20 Mbps
|
||||||
|
if (frameRate == 60 && resHeight == 1080) {
|
||||||
|
defaultBitrate = 20000;
|
||||||
|
}
|
||||||
|
// 720p60 and 1080p30 are 10 Mbps
|
||||||
|
else if (frameRate == 60 || resHeight == 1080) {
|
||||||
|
defaultBitrate = 10000;
|
||||||
|
}
|
||||||
|
// 720p30 is 5 Mbps
|
||||||
|
else {
|
||||||
|
defaultBitrate = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bitrate = defaultBitrate;
|
||||||
|
[self.bitrateSlider setValue:defaultBitrate / BITRATE_INTERVAL animated:YES];
|
||||||
|
|
||||||
|
[self updateBitrateText];
|
||||||
|
}
|
||||||
|
|
||||||
- (void) bitrateSliderMoved {
|
- (void) bitrateSliderMoved {
|
||||||
_bitrate = BITRATE_INTERVAL * (int)self.bitrateSlider.value;
|
_bitrate = BITRATE_INTERVAL * (int)self.bitrateSlider.value;
|
||||||
[self updateBitrateText];
|
[self updateBitrateText];
|
||||||
@@ -55,11 +81,23 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
|
|||||||
[self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, _bitrate / 1000.]];
|
[self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, _bitrate / 1000.]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSInteger) getChosenFrameRate {
|
||||||
|
return [self.framerateSelector selectedSegmentIndex] == 0 ? 30 : 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger) getChosenStreamHeight {
|
||||||
|
return [self.resolutionSelector selectedSegmentIndex] == 0 ? 720 : 1080;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger) getChosenStreamWidth {
|
||||||
|
return [self getChosenStreamHeight] == 720 ? 1280 : 1920;
|
||||||
|
}
|
||||||
|
|
||||||
- (void) saveSettings {
|
- (void) saveSettings {
|
||||||
DataManager* dataMan = [[DataManager alloc] init];
|
DataManager* dataMan = [[DataManager alloc] init];
|
||||||
NSInteger framerate = [self.framerateSelector selectedSegmentIndex] == 0 ? 30 : 60;
|
NSInteger framerate = [self getChosenFrameRate];
|
||||||
NSInteger height = [self.resolutionSelector selectedSegmentIndex] == 0 ? 720 : 1080;
|
NSInteger height = [self getChosenStreamHeight];
|
||||||
NSInteger width = height == 720 ? 1280 : 1920;
|
NSInteger width = [self getChosenStreamWidth];
|
||||||
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
|
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
|
||||||
[dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height width:width onscreenControls:onscreenControls];
|
[dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height width:width onscreenControls:onscreenControls];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
#Moonlight iOS
|
#Moonlight iOS
|
||||||
|
|
||||||
#### Update 7/16/15: We are not accepting more users into the beta program at this time. However, Moonlight iOS is getting ready to launch on the App Store. To be notified when this happens, [please sign up here](http://eepurl.com/btvkG9) to receive a one-time email notification when it's available to download onto your iOS device.
|
#### Now available on the [App Store](https://itunes.apple.com/us/app/moonlight-game-streaming/id1000551566?mt=8)
|
||||||
|
|
||||||
[Moonlight](https://github.com/moonlight-stream) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for iOS. Moonlight iOS allows you to stream your full collection of Steam games from
|
[Moonlight](https://github.com/moonlight-stream) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for iOS. Moonlight iOS allows you to stream your full collection of Steam games from
|
||||||
your powerful desktop computer to your iOS Device.
|
your powerful desktop computer to your iOS Device.
|
||||||
|
|
||||||
This is actively being developed and will be available on the App Store soon - check back here or on [our website](http://moonlight-stream.com) for updates.
|
There are also versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [PC](https://github.com/moonlight-stream/moonlight-pc).
|
||||||
|
|
||||||
We understand that Moonlight users are eager for the opportunity to stream their Steam collection to their iOS devices. Moonlight for iOS is in the testing stage and preliminary versions are available for beta testers.
|
|
||||||
|
|
||||||
In the meantime, check out the existing versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [PC](https://github.com/moonlight-stream/moonlight-pc). Also, a client for [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) is in the works and actively being developed.
|
|
||||||
|
|
||||||
Please be patient as we work to bring Moonlight to more platforms.
|
|
||||||
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
<inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/>
|
<inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="fv6-NS-qsK">
|
<collectionViewCell clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="fv6-NS-qsK">
|
||||||
<rect key="frame" x="20" y="40" width="150" height="200"/>
|
<rect key="frame" x="20" y="40" width="150" height="200"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
<inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/>
|
<inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="Uqv-Di-fzX">
|
<collectionViewCell clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="Uqv-Di-fzX">
|
||||||
<rect key="frame" x="20" y="40" width="90" height="120"/>
|
<rect key="frame" x="20" y="40" width="90" height="120"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
|
|||||||
Reference in new issue
Block a user