Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5584083809 | ||
|
|
9f9aec70b0 | ||
|
|
a8d1770871 | ||
|
|
d0d60b8ffc | ||
|
|
86f7112789 | ||
|
|
da1115e529 | ||
|
|
593da7ef3e | ||
|
|
de5377a95d | ||
|
|
7d7500b187 | ||
|
|
45dbd46da1 | ||
|
|
89d8bc6009 | ||
|
|
b99988836a | ||
|
|
3e94613eb3 | ||
|
|
832b9189f8 | ||
|
|
3464539bd3 | ||
|
|
815592117d | ||
|
|
b51341ef54 | ||
|
|
9ffbb5f155 | ||
|
|
9716867ade | ||
|
|
7c141c1fd2 | ||
|
|
42c4e7ca58 | ||
|
|
6a6ea4a79a | ||
|
|
5d0ca8451d | ||
|
|
4641f68eb8 | ||
|
|
dd33f57b9a | ||
|
|
f6b225ca9c | ||
|
|
4ac3f11ff8 | ||
|
|
62aaf27293 | ||
|
|
eac2d985e2 | ||
|
|
61ea7f3f1c | ||
|
|
40f7faa1b1 |
@@ -28,8 +28,8 @@
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="rW0-BH-s7l">
|
||||
<barButtonItem key="leftBarButtonItem" enabled="NO" id="d9R-Zo-Uml">
|
||||
<button key="customView" opaque="NO" contentMode="center" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9JC-B5-BAE">
|
||||
<rect key="frame" x="-23" y="-15" width="46" height="30"/>
|
||||
<button key="customView" opaque="NO" contentMode="center" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" id="9JC-B5-BAE">
|
||||
<rect key="frame" x="20" y="-1" width="109" height="46"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
|
||||
@@ -19,7 +19,17 @@
|
||||
- (id) init {
|
||||
self = [super init];
|
||||
|
||||
_appDelegate = [[UIApplication sharedApplication] delegate];
|
||||
// HACK: Avoid calling [UIApplication delegate] off the UI thread to keep
|
||||
// Main Thread Checker happy.
|
||||
if ([NSThread isMainThread]) {
|
||||
_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
|
||||
}
|
||||
else {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
|
||||
});
|
||||
}
|
||||
|
||||
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
[_managedObjectContext setPersistentStoreCoordinator:_appDelegate.persistentStoreCoordinator];
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -13,37 +13,37 @@
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "app_icon_settings_58x58_@2x-1.png",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "app_icon_settings_87x87_@3x.png",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "app_icon_spotlight_80x80_@2x-1.png",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "app_icon_spotlight_120x120_@3x.png",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "app_icon_120x120_@2x.png",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "app_icon_180x180_@3x.png",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
@@ -59,44 +59,50 @@
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "app_icon_settings_29x29_@1x.png",
|
||||
"filename" : "Icon-App-[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "app_icon_settings_58x58_@2x.png",
|
||||
"filename" : "Icon-App-29x29@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "app_icon_spotlight_40x40_@1x.png",
|
||||
"filename" : "Icon-App-[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "app_icon_spotlight_80x80_@2x.png",
|
||||
"filename" : "Icon-App-40x40@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "app_icon_76x76_@1x.png",
|
||||
"filename" : "Icon-App-[email protected]",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "app_icon_152x152_@2x.png",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "app_icon_167x167_@2x.png",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "1024x icon.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 41 KiB |
@@ -6,8 +6,8 @@
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "logo_universal.png"
|
||||
"filename" : "logo.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 36 KiB |
@@ -146,7 +146,8 @@
|
||||
{
|
||||
[_controllerStreamLock lock];
|
||||
@synchronized(controller) {
|
||||
LiSendMultiControllerEvent(controller.playerIndex, controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
||||
// Player 1 is always present for OSC
|
||||
LiSendMultiControllerEvent(controller.playerIndex, _controllerNumbers | 1, controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
||||
}
|
||||
[_controllerStreamLock unlock];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#import "ControllerSupport.h"
|
||||
|
||||
@implementation StreamView {
|
||||
CGPoint touchLocation;
|
||||
CGPoint touchLocation, originalLocation;
|
||||
BOOL touchMoved;
|
||||
OnScreenControls* onScreenControls;
|
||||
|
||||
@@ -46,11 +46,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (Boolean)isConfirmedMove:(CGPoint)currentPoint from:(CGPoint)originalPoint {
|
||||
// Movements of greater than 20 pixels are considered confirmed
|
||||
return hypotf(originalPoint.x - currentPoint.x, originalPoint.y - currentPoint.y) >= 20;
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
Log(LOG_D, @"Touch down");
|
||||
if (![onScreenControls handleTouchDownEvent:touches]) {
|
||||
UITouch *touch = [[event allTouches] anyObject];
|
||||
touchLocation = [touch locationInView:self];
|
||||
originalLocation = touchLocation = [touch locationInView:self];
|
||||
touchMoved = false;
|
||||
if ([[event allTouches] count] == 1 && !isDragging) {
|
||||
dragTimer = [NSTimer scheduledTimerWithTimeInterval:0.650
|
||||
@@ -88,8 +93,13 @@
|
||||
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
LiSendMouseMoveEvent(deltaX, deltaY);
|
||||
touchMoved = true;
|
||||
touchLocation = currentLocation;
|
||||
|
||||
// If we've moved far enough to confirm this wasn't just human/machine error,
|
||||
// mark it as such.
|
||||
if ([self isConfirmedMove:touchLocation from:originalLocation]) {
|
||||
touchMoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ([[event allTouches] count] == 2) {
|
||||
@@ -100,7 +110,13 @@
|
||||
if (touchLocation.y != avgLocation.y) {
|
||||
LiSendScrollEvent(avgLocation.y - touchLocation.y);
|
||||
}
|
||||
touchMoved = true;
|
||||
|
||||
// If we've moved far enough to confirm this wasn't just human/machine error,
|
||||
// mark it as such.
|
||||
if ([self isConfirmedMove:firstLocation from:originalLocation]) {
|
||||
touchMoved = true;
|
||||
}
|
||||
|
||||
touchLocation = avgLocation;
|
||||
}
|
||||
}
|
||||
@@ -139,6 +155,20 @@
|
||||
isDragging = false;
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||
}
|
||||
|
||||
// We we're moving from 2+ touches to 1. Synchronize the current position
|
||||
// of the active finger so we don't jump unexpectedly on the next touchesMoved
|
||||
// callback when finger 1 switches on us.
|
||||
if ([[event allTouches] count] - [touches count] == 1) {
|
||||
NSMutableSet *activeSet = [[NSMutableSet alloc] initWithCapacity:[[event allTouches] count]];
|
||||
[activeSet unionSet:[event allTouches]];
|
||||
[activeSet minusSet:touches];
|
||||
touchLocation = [[activeSet anyObject] locationInView:self];
|
||||
|
||||
// Mark this touch as moved so we don't send a left mouse click if the user
|
||||
// right clicks without moving their other finger.
|
||||
touchMoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.3</string>
|
||||
<string>1.4.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>2</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.games</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -102,10 +102,29 @@
|
||||
TemporaryHost *existingHost = [self getHostInDiscovery:host.uuid];
|
||||
if (existingHost != nil) {
|
||||
// Update address of existing host
|
||||
existingHost.address = existingHost.activeAddress = host.address;
|
||||
if (host.address != nil) {
|
||||
existingHost.address = host.address;
|
||||
}
|
||||
if (host.localAddress != nil) {
|
||||
existingHost.localAddress = host.localAddress;
|
||||
}
|
||||
if (host.externalAddress != nil) {
|
||||
existingHost.externalAddress = host.externalAddress;
|
||||
}
|
||||
existingHost.activeAddress = host.activeAddress;
|
||||
return NO;
|
||||
}
|
||||
else {
|
||||
// If we were added without an explicit address,
|
||||
// populate it from our other available addresses
|
||||
if (host.address == nil) {
|
||||
if (host.externalAddress != nil) {
|
||||
host.address = host.externalAddress;
|
||||
}
|
||||
else {
|
||||
host.address = host.localAddress;
|
||||
}
|
||||
}
|
||||
[_hostQueue addObject:host];
|
||||
if (shouldDiscover) {
|
||||
[_opQueue addOperation:[self createWorkerForHost:host]];
|
||||
|
||||
@@ -49,8 +49,8 @@ static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp";
|
||||
for (NSNetService* service in services) {
|
||||
if (service.hostName != nil) {
|
||||
TemporaryHost* host = [[TemporaryHost alloc] init];
|
||||
host.activeAddress = host.address = service.hostName;
|
||||
host.name = host.address;
|
||||
host.activeAddress = host.localAddress = service.hostName;
|
||||
host.name = service.hostName;
|
||||
[hosts addObject:host];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <VideoToolbox/VideoToolbox.h>
|
||||
|
||||
#include "Limelight.h"
|
||||
#include "opus.h"
|
||||
@@ -45,6 +46,12 @@ static int audioBufferQueueLength;
|
||||
static AudioComponentInstance audioUnit;
|
||||
static VideoDecoderRenderer* renderer;
|
||||
|
||||
int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags)
|
||||
{
|
||||
[renderer setupWithVideoFormat:videoFormat];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
{
|
||||
int offset = 0;
|
||||
@@ -65,7 +72,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
|
||||
return [renderer submitDecodeBuffer:data length:decodeUnit->fullLength];
|
||||
}
|
||||
|
||||
void ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig)
|
||||
int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int flags)
|
||||
{
|
||||
int err;
|
||||
|
||||
@@ -101,6 +108,7 @@ void ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig)
|
||||
|
||||
if (status) {
|
||||
Log(LOG_E, @"Unable to instantiate new AudioComponent: %d", (int32_t)status);
|
||||
return status;
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription audioFormat = {0};
|
||||
@@ -122,6 +130,7 @@ void ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig)
|
||||
sizeof(audioFormat));
|
||||
if (status) {
|
||||
Log(LOG_E, @"Unable to set audio unit to input: %d", (int32_t)status);
|
||||
return status;
|
||||
}
|
||||
|
||||
AURenderCallbackStruct callbackStruct = {0};
|
||||
@@ -136,17 +145,22 @@ void ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig)
|
||||
sizeof(callbackStruct));
|
||||
if (status) {
|
||||
Log(LOG_E, @"Unable to set audio unit callback: %d", (int32_t)status);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = AudioUnitInitialize(audioUnit);
|
||||
if (status) {
|
||||
Log(LOG_E, @"Unable to initialize audioUnit: %d", (int32_t)status);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = AudioOutputUnitStart(audioUnit);
|
||||
if (status) {
|
||||
Log(LOG_E, @"Unable to start audioUnit: %d", (int32_t)status);
|
||||
return status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ArCleanup(void)
|
||||
@@ -261,6 +275,14 @@ void ClDisplayTransientMessage(const char* message)
|
||||
[_callbacks displayTransientMessage: message];
|
||||
}
|
||||
|
||||
void ClLogMessage(const char* format, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
vfprintf(stderr, format, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
-(void) terminate
|
||||
{
|
||||
// We dispatch this async to get out because this can be invoked
|
||||
@@ -299,6 +321,12 @@ void ClDisplayTransientMessage(const char* message)
|
||||
_streamConfig.fps = config.frameRate;
|
||||
_streamConfig.bitrate = config.bitRate;
|
||||
|
||||
// On iOS 11, we can use HEVC if the server supports encoding it
|
||||
// and this device has hardware decode for it (A9 and later)
|
||||
if (@available(iOS 11.0, *)) {
|
||||
_streamConfig.supportsHevc = VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC);
|
||||
}
|
||||
|
||||
// FIXME: We should use 1024 when streaming remotely
|
||||
_streamConfig.packetSize = 1292;
|
||||
|
||||
@@ -308,8 +336,12 @@ void ClDisplayTransientMessage(const char* message)
|
||||
memcpy(_streamConfig.remoteInputAesIv, &riKeyId, sizeof(riKeyId));
|
||||
|
||||
LiInitializeVideoCallbacks(&_drCallbacks);
|
||||
_drCallbacks.setup = DrDecoderSetup;
|
||||
_drCallbacks.submitDecodeUnit = DrSubmitDecodeUnit;
|
||||
|
||||
// RFI doesn't work properly with HEVC on iOS 11 with an iPhone SE (at least)
|
||||
_drCallbacks.capabilities = CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
||||
|
||||
LiInitializeAudioCallbacks(&_arCallbacks);
|
||||
_arCallbacks.init = ArInit;
|
||||
_arCallbacks.cleanup = ArCleanup;
|
||||
@@ -323,6 +355,7 @@ void ClDisplayTransientMessage(const char* message)
|
||||
_clCallbacks.connectionTerminated = ClConnectionTerminated;
|
||||
_clCallbacks.displayMessage = ClDisplayMessage;
|
||||
_clCallbacks.displayTransientMessage = ClDisplayTransientMessage;
|
||||
_clCallbacks.logMessage = ClLogMessage;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -416,6 +449,7 @@ static OSStatus playbackCallback(void *inRefCon,
|
||||
&_clCallbacks,
|
||||
&_drCallbacks,
|
||||
&_arCallbacks,
|
||||
NULL, 0,
|
||||
NULL, 0);
|
||||
[initLock unlock];
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
- (id)initWithView:(UIView*)view;
|
||||
|
||||
- (void)setupWithVideoFormat:(int)videoFormat;
|
||||
|
||||
- (void)updateBufferForRange:(CMBlockBufferRef)existingBuffer data:(unsigned char *)data offset:(int)offset length:(int)nalLength;
|
||||
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length;
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
UIView *_view;
|
||||
|
||||
AVSampleBufferDisplayLayer* displayLayer;
|
||||
Boolean waitingForSps, waitingForPps;
|
||||
Boolean waitingForSps, waitingForPps, waitingForVps;
|
||||
int videoFormat;
|
||||
|
||||
NSData *spsData, *ppsData;
|
||||
NSData *spsData, *ppsData, *vpsData;
|
||||
CMVideoFormatDescriptionRef formatDesc;
|
||||
}
|
||||
|
||||
@@ -43,6 +44,8 @@
|
||||
spsData = nil;
|
||||
waitingForPps = true;
|
||||
ppsData = nil;
|
||||
waitingForVps = true;
|
||||
vpsData = nil;
|
||||
|
||||
if (formatDesc != nil) {
|
||||
CFRelease(formatDesc);
|
||||
@@ -61,11 +64,65 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupWithVideoFormat:(int)videoFormat
|
||||
{
|
||||
self->videoFormat = videoFormat;
|
||||
}
|
||||
|
||||
#define FRAME_START_PREFIX_SIZE 4
|
||||
#define NALU_START_PREFIX_SIZE 3
|
||||
#define NAL_LENGTH_PREFIX_SIZE 4
|
||||
#define NAL_TYPE_SPS 0x7
|
||||
#define NAL_TYPE_PPS 0x8
|
||||
#define AVC_NAL_TYPE_SPS 0x67
|
||||
#define AVC_NAL_TYPE_PPS 0x68
|
||||
#define HEVC_NAL_TYPE_VPS 0x40
|
||||
#define HEVC_NAL_TYPE_SPS 0x42
|
||||
#define HEVC_NAL_TYPE_PPS 0x44
|
||||
|
||||
#define IS_NAL_SPS(x) ((videoFormat == VIDEO_FORMAT_H264) ? ((x) == AVC_NAL_TYPE_SPS) : ((x) == HEVC_NAL_TYPE_SPS))
|
||||
#define IS_NAL_PPS(x) ((videoFormat == VIDEO_FORMAT_H264) ? ((x) == AVC_NAL_TYPE_PPS) : ((x) == HEVC_NAL_TYPE_PPS))
|
||||
#define IS_NAL_VPS(x) ((videoFormat == VIDEO_FORMAT_H265) && ((x) == HEVC_NAL_TYPE_VPS))
|
||||
|
||||
- (Boolean)readyForPictureData
|
||||
{
|
||||
if (videoFormat == VIDEO_FORMAT_H264) {
|
||||
return !waitingForSps && !waitingForPps;
|
||||
}
|
||||
else {
|
||||
// H.265 requires VPS in addition to SPS and PPS
|
||||
return !waitingForVps && !waitingForSps && !waitingForPps;
|
||||
}
|
||||
}
|
||||
|
||||
- (Boolean)isNalReferencePicture:(unsigned char)nalType
|
||||
{
|
||||
if (videoFormat == VIDEO_FORMAT_H264) {
|
||||
return nalType == 0x65;
|
||||
}
|
||||
else {
|
||||
// HEVC has several types of reference NALU types
|
||||
switch (nalType) {
|
||||
case 0x20:
|
||||
case 0x22:
|
||||
case 0x24:
|
||||
case 0x26:
|
||||
case 0x28:
|
||||
case 0x2A:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (Boolean)isNalPictureData:(unsigned char)nalType
|
||||
{
|
||||
if (videoFormat == VIDEO_FORMAT_H264) {
|
||||
return !(nalType == AVC_NAL_TYPE_SPS || nalType == AVC_NAL_TYPE_PPS);
|
||||
}
|
||||
else {
|
||||
return !(nalType == HEVC_NAL_TYPE_VPS || nalType == HEVC_NAL_TYPE_SPS || nalType == HEVC_NAL_TYPE_PPS);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateBufferForRange:(CMBlockBufferRef)existingBuffer data:(unsigned char *)data offset:(int)offset length:(int)nalLength
|
||||
{
|
||||
@@ -136,7 +193,7 @@
|
||||
// This function must free data
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length
|
||||
{
|
||||
unsigned char nalType = data[FRAME_START_PREFIX_SIZE] & 0x1F;
|
||||
unsigned char nalType = data[FRAME_START_PREFIX_SIZE];
|
||||
OSStatus status;
|
||||
|
||||
// Check for previous decoder errors before doing anything
|
||||
@@ -150,35 +207,69 @@
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
if (nalType == NAL_TYPE_SPS || nalType == NAL_TYPE_PPS) {
|
||||
if (nalType == NAL_TYPE_SPS) {
|
||||
if (![self isNalPictureData:nalType]) {
|
||||
if (IS_NAL_VPS(nalType)) {
|
||||
Log(LOG_I, @"Got VPS");
|
||||
vpsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
||||
waitingForVps = false;
|
||||
|
||||
// We got a new VPS so wait for a new SPS to match it
|
||||
waitingForSps = true;
|
||||
}
|
||||
else if (IS_NAL_SPS(nalType)) {
|
||||
Log(LOG_I, @"Got SPS");
|
||||
spsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
||||
waitingForSps = false;
|
||||
|
||||
// We got a new SPS so wait for a new PPS to match it
|
||||
waitingForPps = true;
|
||||
} else if (nalType == NAL_TYPE_PPS) {
|
||||
} else if (IS_NAL_PPS(nalType)) {
|
||||
Log(LOG_I, @"Got PPS");
|
||||
ppsData = [NSData dataWithBytes:&data[FRAME_START_PREFIX_SIZE] length:length - FRAME_START_PREFIX_SIZE];
|
||||
waitingForPps = false;
|
||||
}
|
||||
|
||||
// See if we've got all the parameter sets we need
|
||||
if (!waitingForSps && !waitingForPps) {
|
||||
const uint8_t* const parameterSetPointers[] = { [spsData bytes], [ppsData bytes] };
|
||||
const size_t parameterSetSizes[] = { [spsData length], [ppsData length] };
|
||||
|
||||
Log(LOG_I, @"Constructing new format description");
|
||||
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
||||
2, /* count of parameter sets */
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
// See if we've got all the parameter sets we need for our video format
|
||||
if ([self readyForPictureData]) {
|
||||
if (videoFormat == VIDEO_FORMAT_H264) {
|
||||
const uint8_t* const parameterSetPointers[] = { [spsData bytes], [ppsData bytes] };
|
||||
const size_t parameterSetSizes[] = { [spsData length], [ppsData length] };
|
||||
|
||||
Log(LOG_I, @"Constructing new H264 format description");
|
||||
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
||||
2, /* count of parameter sets */
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create H264 format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const uint8_t* const parameterSetPointers[] = { [vpsData bytes], [spsData bytes], [ppsData bytes] };
|
||||
const size_t parameterSetSizes[] = { [vpsData length], [spsData length], [ppsData length] };
|
||||
|
||||
Log(LOG_I, @"Constructing new HEVC format description");
|
||||
if (@available(iOS 11.0, *)) {
|
||||
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
|
||||
3, /* count of parameter sets */
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
nil,
|
||||
&formatDesc);
|
||||
} else {
|
||||
// This means Moonlight-common-c decided to give us an HEVC stream
|
||||
// even though we said we couldn't support it. All we can do is abort().
|
||||
abort();
|
||||
}
|
||||
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create HEVC format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,12 +286,6 @@
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
if (nalType != 0x1 && nalType != 0x5) {
|
||||
// Don't submit parameter set data
|
||||
free(data);
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
// Now we're decoding actual frame data here
|
||||
CMBlockBufferRef blockBuffer;
|
||||
|
||||
@@ -252,7 +337,7 @@
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_IsDependedOnByOthers, kCFBooleanTrue);
|
||||
|
||||
if (nalType == 1) {
|
||||
if (![self isNalReferencePicture:nalType]) {
|
||||
// P-frame
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanTrue);
|
||||
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanTrue);
|
||||
|
||||
@@ -49,6 +49,7 @@ static NSMutableSet* hostList;
|
||||
message:[NSString stringWithFormat:@"Enter the following PIN on the host machine: %@", PIN]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[_pairAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) {
|
||||
_pairAlert = nil;
|
||||
[_discMan startDiscovery];
|
||||
[self hideLoadingFrame];
|
||||
}]];
|
||||
@@ -56,24 +57,36 @@ static NSMutableSet* hostList;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)displayFailureDialog:(NSString *)message {
|
||||
UIAlertController* failedDialog = [UIAlertController alertControllerWithTitle:@"Pairing Failed"
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[failedDialog addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDestructive handler:nil]];
|
||||
[self presentViewController:failedDialog animated:YES completion:nil];
|
||||
|
||||
[_discMan startDiscovery];
|
||||
[self hideLoadingFrame];
|
||||
}
|
||||
|
||||
- (void)pairFailed:(NSString *)message {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_pairAlert dismissViewControllerAnimated:YES completion:^{
|
||||
_pairAlert = [UIAlertController alertControllerWithTitle:@"Pairing Failed"
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[_pairAlert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDestructive handler:nil]];
|
||||
[self presentViewController:_pairAlert animated:YES completion:nil];
|
||||
|
||||
[_discMan startDiscovery];
|
||||
[self hideLoadingFrame];
|
||||
}];
|
||||
if (_pairAlert != nil) {
|
||||
[_pairAlert dismissViewControllerAnimated:YES completion:^{
|
||||
[self displayFailureDialog:message];
|
||||
}];
|
||||
_pairAlert = nil;
|
||||
}
|
||||
else {
|
||||
[self displayFailureDialog:message];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)pairSuccessful {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_pairAlert dismissViewControllerAnimated:YES completion:nil];
|
||||
_pairAlert = nil;
|
||||
|
||||
[_discMan startDiscovery];
|
||||
[self alreadyPaired];
|
||||
});
|
||||
@@ -259,8 +272,10 @@ static NSMutableSet* hostList;
|
||||
|
||||
// If we are online, paired, and have a cached app list, skip straight
|
||||
// to the app grid without a loading frame. This is the fast path that users
|
||||
// should hit most.
|
||||
if (host.online && host.pairState == PairStatePaired && host.appList.count > 0) {
|
||||
// should hit most. Check for a valid view because we don't want to hit the fast
|
||||
// path after coming back from streaming, since we need to fetch serverinfo too
|
||||
// so that our active game data is correct.
|
||||
if (host.online && host.pairState == PairStatePaired && host.appList.count > 0 && view != nil) {
|
||||
[self alreadyPaired];
|
||||
return;
|
||||
}
|
||||
@@ -595,6 +610,9 @@ static NSMutableSet* hostList;
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[[self revealViewController] setPrimaryViewController:self];
|
||||
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
|
||||
// Hide 1px border line
|
||||
|
||||
@@ -180,6 +180,8 @@ typedef NS_ENUM(NSInteger, SWRevealToggleAnimationType)
|
||||
|
||||
@interface SWRevealViewController : UIViewController
|
||||
|
||||
- (void)setPrimaryViewController:(UIViewController*)viewController;
|
||||
|
||||
/* Basic API */
|
||||
|
||||
// Object instance init and rear view setting
|
||||
|
||||
@@ -583,6 +583,7 @@ static CGFloat scaledValue( CGFloat v1, CGFloat min2, CGFloat max2, CGFloat min1
|
||||
FrontViewPosition _panInitialFrontPosition;
|
||||
NSMutableArray *_animationQueue;
|
||||
BOOL _userInteractionStore;
|
||||
UIViewController *_primaryViewController;
|
||||
}
|
||||
|
||||
const int FrontViewPositionNone = 0xff;
|
||||
@@ -1364,6 +1365,26 @@ const int FrontViewPositionNone = 0xff;
|
||||
_enqueue( [theSelf _performTransitionOperation:operation withViewController:newViewController animated:animated] );
|
||||
}
|
||||
|
||||
- (void)setPrimaryViewController:(UIViewController*)viewController
|
||||
{
|
||||
_primaryViewController = viewController;
|
||||
|
||||
// These are derived from the primary view controller
|
||||
if (@available(iOS 11.0, *)) {
|
||||
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIViewController*)childViewControllerForHomeIndicatorAutoHidden
|
||||
{
|
||||
return _primaryViewController;
|
||||
}
|
||||
|
||||
- (UIViewController*)childViewControllerForScreenEdgesDeferringSystemGestures
|
||||
{
|
||||
return _primaryViewController;
|
||||
}
|
||||
|
||||
#pragma mark Animated view controller deployment and layout
|
||||
|
||||
|
||||
@@ -14,9 +14,30 @@
|
||||
|
||||
@implementation SettingsViewController {
|
||||
NSInteger _bitrate;
|
||||
Boolean _adjustedForSafeArea;
|
||||
}
|
||||
static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
// Adjust the subviews for the safe area on the iPhone X.
|
||||
if (!_adjustedForSafeArea) {
|
||||
if (@available(iOS 11.0, *)) {
|
||||
for (UIView* view in self.view.subviews) {
|
||||
// HACK: The official safe area is much too large for our purposes
|
||||
// so we'll just use the presence of any safe area to indicate we should
|
||||
// pad by 20.
|
||||
if (self.view.safeAreaInsets.left >= 20 || self.view.safeAreaInsets.right >= 20) {
|
||||
view.frame = CGRectMake(view.frame.origin.x + 20, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_adjustedForSafeArea = true;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
StreamManager *_streamMan;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated
|
||||
{
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[[self revealViewController] setPrimaryViewController:self];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
@@ -146,6 +153,11 @@
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
// Require a confirmation when streaming to activate a system gesture
|
||||
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
|
||||
return UIRectEdgeAll;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotate {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
9857AEEA1EBE85A20084F99E /* RtpFecQueue.c in Sources */ = {isa = PBXBuildFile; fileRef = 9857AEE91EBE85A20084F99E /* RtpFecQueue.c */; };
|
||||
9857AEF01EBE85E10084F99E /* rs.c in Sources */ = {isa = PBXBuildFile; fileRef = 9857AEEE1EBE85E10084F99E /* rs.c */; };
|
||||
98AB2E401CAD425A0089BB98 /* AudioStream.c in Sources */ = {isa = PBXBuildFile; fileRef = 98AB2E1A1CAD423B0089BB98 /* AudioStream.c */; };
|
||||
98AB2E411CAD425A0089BB98 /* ByteBuffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 98AB2E1B1CAD423B0089BB98 /* ByteBuffer.c */; };
|
||||
98AB2E421CAD425A0089BB98 /* Connection.c in Sources */ = {isa = PBXBuildFile; fileRef = 98AB2E1D1CAD423B0089BB98 /* Connection.c */; };
|
||||
@@ -35,6 +37,10 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
9857AEE91EBE85A20084F99E /* RtpFecQueue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = RtpFecQueue.c; sourceTree = "<group>"; };
|
||||
9857AEEB1EBE85AB0084F99E /* RtpFecQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RtpFecQueue.h; sourceTree = "<group>"; };
|
||||
9857AEEE1EBE85E10084F99E /* rs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rs.c; path = "moonlight-common-c/reedsolomon/rs.c"; sourceTree = "<group>"; };
|
||||
9857AEEF1EBE85E10084F99E /* rs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rs.h; path = "moonlight-common-c/reedsolomon/rs.h"; sourceTree = "<group>"; };
|
||||
98AB2DF01CAD422B0089BB98 /* callbacks.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = callbacks.c; sourceTree = "<group>"; };
|
||||
98AB2DF11CAD422B0089BB98 /* ChangeLog */ = {isa = PBXFileReference; lastKnownFileType = text; path = ChangeLog; sourceTree = "<group>"; };
|
||||
98AB2DF21CAD422B0089BB98 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = "<group>"; };
|
||||
@@ -113,6 +119,15 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
9857AEED1EBE85D50084F99E /* reedsolomon */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9857AEEE1EBE85E10084F99E /* rs.c */,
|
||||
9857AEEF1EBE85E10084F99E /* rs.h */,
|
||||
);
|
||||
name = reedsolomon;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
98AB2DEF1CAD422B0089BB98 /* enet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -193,6 +208,8 @@
|
||||
98AB2E191CAD423B0089BB98 /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9857AEEB1EBE85AB0084F99E /* RtpFecQueue.h */,
|
||||
9857AEE91EBE85A20084F99E /* RtpFecQueue.c */,
|
||||
98AB2E1A1CAD423B0089BB98 /* AudioStream.c */,
|
||||
98AB2E1B1CAD423B0089BB98 /* ByteBuffer.c */,
|
||||
98AB2E1C1CAD423B0089BB98 /* ByteBuffer.h */,
|
||||
@@ -228,6 +245,7 @@
|
||||
FB290E2519B37A4E004C83CF = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9857AEED1EBE85D50084F99E /* reedsolomon */,
|
||||
98AB2E191CAD423B0089BB98 /* src */,
|
||||
98AB2DEF1CAD422B0089BB98 /* enet */,
|
||||
FB290E2F19B37A4E004C83CF /* Products */,
|
||||
@@ -267,7 +285,7 @@
|
||||
FB290E2619B37A4E004C83CF /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0800;
|
||||
LastUpgradeCheck = 0810;
|
||||
ORGANIZATIONNAME = "Moonlight Stream";
|
||||
};
|
||||
buildConfigurationList = FB290E2919B37A4E004C83CF /* Build configuration list for PBXProject "moonlight-common" */;
|
||||
@@ -307,6 +325,7 @@
|
||||
98AB2E4C1CAD425A0089BB98 /* RtspParser.c in Sources */,
|
||||
98AB2E4D1CAD425A0089BB98 /* SdpGenerator.c in Sources */,
|
||||
98AB2E4E1CAD425A0089BB98 /* VideoDepacketizer.c in Sources */,
|
||||
9857AEEA1EBE85A20084F99E /* RtpFecQueue.c in Sources */,
|
||||
98AB2E4F1CAD425A0089BB98 /* VideoStream.c in Sources */,
|
||||
98AB2E501CAD427A0089BB98 /* callbacks.c in Sources */,
|
||||
98AB2E511CAD427A0089BB98 /* compress.c in Sources */,
|
||||
@@ -315,6 +334,7 @@
|
||||
98AB2E541CAD427A0089BB98 /* packet.c in Sources */,
|
||||
98AB2E551CAD427A0089BB98 /* peer.c in Sources */,
|
||||
98AB2E561CAD427A0089BB98 /* protocol.c in Sources */,
|
||||
9857AEF01EBE85E10084F99E /* rs.c in Sources */,
|
||||
98AB2E571CAD427A0089BB98 /* unix.c in Sources */,
|
||||
98AB2E581CAD427A0089BB98 /* win32.c in Sources */,
|
||||
);
|
||||
@@ -336,8 +356,10 @@
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
@@ -382,8 +404,10 @@
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
|
||||
|
||||