Merge branch 'mdns'

* mdns:
  implemented mdns host discovery

Conflicts:
	MainFrame-iPad.storyboard
	MainFrame-iPhone.storyboard
This commit is contained in:
Diego Waxemberg 2014-10-14 19:26:37 -04:00
commit fec330b76e
9 changed files with 238 additions and 53 deletions

View File

@ -38,6 +38,8 @@
FB290E7919B37D81004C83CF /* MainFrame-iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB290E7819B37D81004C83CF /* MainFrame-iPad.storyboard */; };
FB290E7B19B38036004C83CF /* MainFrame-iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FB290E7A19B38036004C83CF /* MainFrame-iPhone.storyboard */; };
FB7E794419C8B71B00A15F68 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB7E794319C8B71B00A15F68 /* libiconv.dylib */; };
FBAB29F219EDB08B00929691 /* MDNSManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FBAB29F119EDB08B00929691 /* MDNSManager.m */; };
FBAB29F619EDE0F800929691 /* Computer.m in Sources */ = {isa = PBXBuildFile; fileRef = FBAB29F519EDE0F800929691 /* Computer.m */; };
FBD8B40519C906230032E027 /* libavcodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FBD8B3FD19C906230032E027 /* libavcodec.a */; };
FBD8B40619C906230032E027 /* libavdevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FBD8B3FE19C906230032E027 /* libavdevice.a */; };
FBD8B40719C906230032E027 /* libavfilter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FBD8B3FF19C906230032E027 /* libavfilter.a */; };
@ -106,6 +108,10 @@
FB290E7819B37D81004C83CF /* MainFrame-iPad.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "MainFrame-iPad.storyboard"; sourceTree = SOURCE_ROOT; };
FB290E7A19B38036004C83CF /* MainFrame-iPhone.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "MainFrame-iPhone.storyboard"; sourceTree = SOURCE_ROOT; };
FB7E794319C8B71B00A15F68 /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; };
FBAB29F119EDB08B00929691 /* MDNSManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDNSManager.m; sourceTree = "<group>"; };
FBAB29F319EDB0C400929691 /* MDNSManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MDNSManager.h; sourceTree = "<group>"; };
FBAB29F419EDE0F800929691 /* Computer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Computer.h; sourceTree = "<group>"; };
FBAB29F519EDE0F800929691 /* Computer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Computer.m; sourceTree = "<group>"; };
FBD8B3FD19C906230032E027 /* libavcodec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libavcodec.a; sourceTree = "<group>"; };
FBD8B3FE19C906230032E027 /* libavdevice.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libavdevice.a; sourceTree = "<group>"; };
FBD8B3FF19C906230032E027 /* libavfilter.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libavfilter.a; sourceTree = "<group>"; };
@ -300,6 +306,10 @@
FB290D0819B2C406004C83CF /* Images.xcassets */,
FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */,
FB290CFA19B2C406004C83CF /* Supporting Files */,
FBAB29F119EDB08B00929691 /* MDNSManager.m */,
FBAB29F319EDB0C400929691 /* MDNSManager.h */,
FBAB29F419EDE0F800929691 /* Computer.h */,
FBAB29F519EDE0F800929691 /* Computer.m */,
);
path = Limelight;
sourceTree = "<group>";
@ -697,9 +707,11 @@
buildActionMask = 2147483647;
files = (
FB290D3D19B2C6E3004C83CF /* VideoDepacketizer.m in Sources */,
FBAB29F219EDB08B00929691 /* MDNSManager.m in Sources */,
FB290D0719B2C406004C83CF /* Limelight.xcdatamodeld in Sources */,
FB290D3E19B2C6E3004C83CF /* VideoRenderer.m in Sources */,
FB290D0419B2C406004C83CF /* AppDelegate.m in Sources */,
FBAB29F619EDE0F800929691 /* Computer.m in Sources */,
FB290D3A19B2C6E3004C83CF /* StreamFrameViewController.m in Sources */,
FB290D0019B2C406004C83CF /* main.m in Sources */,
FB290D3919B2C6E3004C83CF /* MainFrameViewController.m in Sources */,

18
Limelight/Computer.h Normal file
View File

@ -0,0 +1,18 @@
//
// Computer.h
// Limelight
//
// Created by Diego Waxemberg on 10/14/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Computer : NSObject
@property NSString* displayName;
@property NSString* hostName;
@property BOOL paired;
- (id) initWithHost:(NSNetService*)host;
@end

22
Limelight/Computer.m Normal file
View File

@ -0,0 +1,22 @@
//
// Computer.m
// Limelight
//
// Created by Diego Waxemberg on 10/14/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import "Computer.h"
@implementation Computer
- (id) initWithHost:(NSNetService *)host {
self = [super init];
self.hostName = [host hostName];
self.displayName = [host name];
return self;
}
@end

28
Limelight/MDNSManager.h Normal file
View File

@ -0,0 +1,28 @@
//
// MDNSManager.h
// Limelight
//
// Created by Diego Waxemberg on 10/14/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol MDNSCallback <NSObject>
- (void) updateHosts:(NSArray*)hosts;
@end
@interface MDNSManager : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
@property id<MDNSCallback> callback;
- (id) initWithCallback:(id<MDNSCallback>) callback;
- (void) searchForHosts;
- (NSArray*) getFoundHosts;
@end

74
Limelight/MDNSManager.m Normal file
View File

@ -0,0 +1,74 @@
//
// MDNSManager.m
// Limelight
//
// Created by Diego Waxemberg on 10/14/14.
// Copyright (c) 2014 Limelight Stream. All rights reserved.
//
#import "MDNSManager.h"
#import "Computer.h"
@implementation MDNSManager : NSObject
NSNetServiceBrowser* mDNSBrowser;
NSMutableArray* domains;
NSMutableArray* services;
static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp";
- (id) initWithCallback:(id<MDNSCallback>)callback {
self = [super init];
self.callback = callback;
mDNSBrowser = [[NSNetServiceBrowser alloc] init];
[mDNSBrowser setDelegate:self];
domains = [[NSMutableArray alloc] init];
services = [[NSMutableArray alloc] init];
return self;
}
- (void) searchForHosts {
NSLog(@"Searching for hosts...");
[mDNSBrowser searchForServicesOfType:NV_SERVICE_TYPE inDomain:@""];
}
- (NSArray*) getFoundHosts {
NSMutableArray* hosts = [[NSMutableArray alloc] init];
for (NSNetService* service in services) {
if (service.hostName != nil) {
[hosts addObject:[[Computer alloc] initWithHost:service]];
}
}
return [[NSArray alloc] initWithArray:hosts];
}
- (void)netServiceDidResolveAddress:(NSNetService *)service {
NSLog(@"Resolved address: %@ -> %@", service, service.hostName);
[self.callback updateHosts:[self getFoundHosts]];
}
- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict {
NSLog(@"Did not resolve address for: %@\n%@", sender, [errorDict description]);
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
NSLog(@"Found service: %@", aNetService);
[aNetService setDelegate:self];
[aNetService resolveWithTimeout:5];
[services addObject:aNetService];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
NSLog(@"Removing service: %@", aNetService);
[services removeObject:aNetService];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didNotSearch:(NSDictionary *)errorDict {
NSLog(@"Did not perform search");
NSLog(@"%@", [errorDict description]);
}
@end

View File

@ -7,13 +7,16 @@
//
#import <UIKit/UIKit.h>
#import "MDNSManager.h"
@interface MainFrameViewController : UIViewController <UIPickerViewDataSource,UIPickerViewDelegate>
@interface MainFrameViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate, MDNSCallback>
@property (strong, nonatomic) IBOutlet UIPickerView *HostPicker;
- (IBAction)StreamButton:(UIButton *)sender;
- (IBAction)PairButton:(UIButton *)sender;
@property (strong, nonatomic) IBOutlet UITextField *HostField;
@property (strong, nonatomic) IBOutlet UIPickerView *StreamConfigs;
@property (strong, nonatomic) NSArray* streamConfigVals;
@property (strong, nonatomic) NSArray* hostPickerVals;
+ (const char*)getHostAddr;
- (void) segueIntoStream;

View File

@ -9,13 +9,11 @@
#import "MainFrameViewController.h"
#import "VideoDepacketizer.h"
#import "ConnectionHandler.h"
@interface MainFrameViewController ()
@end
#import "Computer.h"
@implementation MainFrameViewController
static NSString* hostAddr;
NSString* hostAddr;
MDNSManager* mDNSManager;
+ (const char*)getHostAddr
{
@ -25,14 +23,12 @@ static NSString* hostAddr;
- (void)PairButton:(UIButton *)sender
{
NSLog(@"Pair Button Pressed!");
hostAddr = self.HostField.text;
[ConnectionHandler pairWithHost:hostAddr];
}
- (void)StreamButton:(UIButton *)sender
{
NSLog(@"Stream Button Pressed!");
hostAddr = self.HostField.text;
[ConnectionHandler streamWithHost:hostAddr viewController:self];
}
@ -42,11 +38,24 @@ static NSString* hostAddr;
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [self.streamConfigVals objectAtIndex:row];
if (pickerView == self.StreamConfigs) {
return [self.streamConfigVals objectAtIndex:row];
} else if (pickerView == self.HostPicker) {
return ((Computer*)([self.hostPickerVals objectAtIndex:row])).displayName;
} else {
return nil;
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
//self.hostPickerVals = [mDNSManager getFoundHosts];
//[self.HostPicker reloadAllComponents];
if (pickerView == self.HostPicker) {
hostAddr = ((Computer*)([self.hostPickerVals objectAtIndex:[self.HostPicker selectedRowInComponent:0]])).hostName;
}
//TODO: figure out how to save this info!!
}
@ -59,14 +68,29 @@ static NSString* hostAddr;
// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return 4;
if (pickerView == self.StreamConfigs) {
return self.streamConfigVals.count;
} else if (pickerView == self.HostPicker) {
return self.hostPickerVals.count;
} else {
return 0;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.streamConfigVals = [[NSArray alloc] initWithObjects:@"1280x720 (30Hz)",@"1280x720 (60Hz)",@"1920x1080 (30Hz)",@"1920x1080 (60Hz)",nil];
self.streamConfigVals = [[NSArray alloc] initWithObjects:@"1280x720 (30Hz)", @"1280x720 (60Hz)", @"1920x1080 (30Hz)", @"1920x1080 (60Hz)",nil];
self.hostPickerVals = [[NSArray alloc] init];
mDNSManager = [[MDNSManager alloc] initWithCallback:self];
[mDNSManager searchForHosts];
}
- (void)updateHosts:(NSArray *)hosts {
self.hostPickerVals = hosts;
[self.HostPicker reloadAllComponents];
}
- (void)didReceiveMemoryWarning

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="14A386a" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" initialViewController="wb7-af-jn8">
<dependencies>
<deployment defaultVersion="1808" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
</dependencies>
<scenes>
@ -17,20 +16,15 @@
<rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Geforce PC Host" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="iC2-gT-zgS">
<rect key="frame" x="20" y="107" width="984" height="30"/>
<fontDescription key="fontDescription" type="system" pointSize="22"/>
<textInputTraits key="textInputTraits"/>
</textField>
<pickerView contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rnA-uG-hAA">
<rect key="frame" x="0.0" y="211" width="1024" height="216"/>
<pickerView contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bJR-sz-8uh">
<rect key="frame" x="0.0" y="80" width="1024" height="216"/>
<connections>
<outlet property="dataSource" destination="wb7-af-jn8" id="25E-op-oh6"/>
<outlet property="delegate" destination="wb7-af-jn8" id="gjZ-oj-THD"/>
<outlet property="dataSource" destination="wb7-af-jn8" id="EW1-pG-aj0"/>
<outlet property="delegate" destination="wb7-af-jn8" id="Piq-cV-gxM"/>
</connections>
</pickerView>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="taW-Um-zc6" userLabel="Start Stream Btn">
<rect key="frame" x="317" y="145" width="391" height="141"/>
<rect key="frame" x="317" y="273" width="391" height="141"/>
<fontDescription key="fontDescription" type="system" pointSize="26"/>
<state key="normal" title="Start Streaming Steam!">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
@ -39,6 +33,13 @@
<action selector="StreamButton:" destination="wb7-af-jn8" eventType="touchUpInside" id="u2m-8A-Tor"/>
</connections>
</button>
<pickerView contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rnA-uG-hAA">
<rect key="frame" x="0.0" y="380" width="1024" height="216"/>
<connections>
<outlet property="dataSource" destination="wb7-af-jn8" id="25E-op-oh6"/>
<outlet property="delegate" destination="wb7-af-jn8" id="gjZ-oj-THD"/>
</connections>
</pickerView>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0Nz-KX-Du4" userLabel="Pair Btn">
<rect key="frame" x="439" y="595" width="147" height="36"/>
<fontDescription key="fontDescription" type="system" pointSize="26"/>
@ -54,7 +55,7 @@
</view>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
<connections>
<outlet property="HostField" destination="iC2-gT-zgS" id="JsQ-oS-2QL"/>
<outlet property="HostPicker" destination="bJR-sz-8uh" id="pWB-P3-ZJ2"/>
<outlet property="StreamConfigs" destination="rnA-uG-hAA" id="LPP-jQ-5eW"/>
<segue destination="OIm-0n-i9v" kind="modal" identifier="createStreamFrame" modalPresentationStyle="fullScreen" id="MOD-9A-3Sk"/>
</connections>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="14A386a" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="dgh-JZ-Q7z">
<dependencies>
<deployment defaultVersion="1808" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
</dependencies>
<scenes>
@ -16,20 +17,8 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Geforce PC Host" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="1q6-JB-JyL">
<rect key="frame" x="20" y="45" width="280" height="30"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" returnKeyType="done" enablesReturnKeyAutomatically="YES"/>
</textField>
<pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0xb-lk-PHK">
<rect key="frame" x="0.0" y="83" width="320" height="162"/>
<connections>
<outlet property="dataSource" destination="dgh-JZ-Q7z" id="R0Y-EX-neJ"/>
<outlet property="delegate" destination="dgh-JZ-Q7z" id="mkk-oI-y7K"/>
</connections>
</pickerView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="J5M-pv-UHA">
<rect key="frame" x="20" y="231" width="280" height="30"/>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="J5M-pv-UHA">
<rect key="frame" x="82" y="231" width="156" height="30"/>
<state key="normal" title="Start Streaming Steam">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
@ -37,7 +26,7 @@
<action selector="StreamButton:" destination="dgh-JZ-Q7z" eventType="touchUpInside" id="3Bj-I0-bKs"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ATh-Tf-xRL">
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ATh-Tf-xRL">
<rect key="frame" x="137" y="269" width="46" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="46" id="mm8-gm-flQ"/>
@ -49,27 +38,41 @@
<action selector="PairButton:" destination="dgh-JZ-Q7z" eventType="touchUpInside" id="jHN-B5-hsw"/>
</connections>
</button>
<pickerView contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0xb-lk-PHK">
<rect key="frame" x="0.0" y="286" width="320" height="162"/>
<connections>
<outlet property="dataSource" destination="dgh-JZ-Q7z" id="R0Y-EX-neJ"/>
<outlet property="delegate" destination="dgh-JZ-Q7z" id="mkk-oI-y7K"/>
</connections>
</pickerView>
<pickerView contentMode="scaleToFill" verticalHuggingPriority="249" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nFR-to-qQs">
<rect key="frame" x="0.0" y="20" width="320" height="216"/>
<connections>
<outlet property="dataSource" destination="dgh-JZ-Q7z" id="C28-Eh-A6L"/>
<outlet property="delegate" destination="dgh-JZ-Q7z" id="dTy-8g-aj1"/>
</connections>
</pickerView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="centerX" secondItem="0xb-lk-PHK" secondAttribute="centerX" id="EN8-nj-InR"/>
<constraint firstItem="J5M-pv-UHA" firstAttribute="top" secondItem="0xb-lk-PHK" secondAttribute="bottom" constant="-14" id="Fwm-ht-YIW"/>
<constraint firstItem="1q6-JB-JyL" firstAttribute="top" secondItem="0HR-9w-kvY" secondAttribute="bottom" constant="25" id="I05-VM-Byl"/>
<constraint firstItem="0xb-lk-PHK" firstAttribute="top" secondItem="1q6-JB-JyL" secondAttribute="bottom" constant="8" symbolic="YES" id="JUS-RK-mpl"/>
<constraint firstItem="1q6-JB-JyL" firstAttribute="leading" secondItem="J5M-pv-UHA" secondAttribute="leading" id="QbW-wK-06K"/>
<constraint firstItem="1q6-JB-JyL" firstAttribute="leading" secondItem="eqF-iA-Rqx" secondAttribute="leading" constant="20" symbolic="YES" id="X1l-68-WXh"/>
<constraint firstItem="0xb-lk-PHK" firstAttribute="width" secondItem="eqF-iA-Rqx" secondAttribute="width" id="YXg-JU-HOP"/>
<constraint firstItem="ATh-Tf-xRL" firstAttribute="top" secondItem="J5M-pv-UHA" secondAttribute="bottom" constant="8" symbolic="YES" id="ZaE-Uw-gIG"/>
<constraint firstItem="1q6-JB-JyL" firstAttribute="trailing" secondItem="J5M-pv-UHA" secondAttribute="trailing" id="a4A-wo-bng"/>
<constraint firstItem="ATh-Tf-xRL" firstAttribute="centerX" secondItem="J5M-pv-UHA" secondAttribute="centerX" id="ggX-Oh-h0J"/>
<constraint firstAttribute="centerX" secondItem="J5M-pv-UHA" secondAttribute="centerX" id="sea-rp-rrt"/>
<constraint firstItem="0xb-lk-PHK" firstAttribute="leading" secondItem="2rs-P3-zn7" secondAttribute="leading" id="350-bN-0q6"/>
<constraint firstAttribute="centerX" secondItem="J5M-pv-UHA" secondAttribute="centerX" id="5jf-C1-faJ"/>
<constraint firstAttribute="centerX" secondItem="0xb-lk-PHK" secondAttribute="centerX" id="By0-u3-3MA"/>
<constraint firstItem="0xb-lk-PHK" firstAttribute="centerX" secondItem="J5M-pv-UHA" secondAttribute="centerX" id="Gab-EA-Il5"/>
<constraint firstItem="0xb-lk-PHK" firstAttribute="top" secondItem="J5M-pv-UHA" secondAttribute="bottom" constant="25" id="RNw-zD-7u7"/>
<constraint firstItem="0xb-lk-PHK" firstAttribute="top" secondItem="ATh-Tf-xRL" secondAttribute="bottom" constant="-13" id="TN4-kv-Yli"/>
<constraint firstItem="J5M-pv-UHA" firstAttribute="top" secondItem="nFR-to-qQs" secondAttribute="bottom" constant="-5" id="Vcz-ix-cdp"/>
<constraint firstItem="nFR-to-qQs" firstAttribute="top" secondItem="0HR-9w-kvY" secondAttribute="bottom" id="XWH-Vw-VfH"/>
<constraint firstItem="0xb-lk-PHK" firstAttribute="centerX" secondItem="ATh-Tf-xRL" secondAttribute="centerX" id="bEv-zi-oip"/>
<constraint firstItem="ATh-Tf-xRL" firstAttribute="top" secondItem="J5M-pv-UHA" secondAttribute="bottom" constant="8" id="gNc-KC-Q9G"/>
<constraint firstAttribute="centerX" secondItem="nFR-to-qQs" secondAttribute="centerX" id="h9S-ek-7bf"/>
</constraints>
</view>
<toolbarItems/>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="HostField" destination="1q6-JB-JyL" id="a9N-Vm-gQC"/>
<outlet property="StreamConfigs" destination="0xb-lk-PHK" id="whL-Jt-2e4"/>
<outlet property="HostPicker" destination="nFR-to-qQs" id="lTE-tT-lYY"/>
<outlet property="StreamConfigs" destination="0xb-lk-PHK" id="VxB-Ur-PNy"/>
<segue destination="mI3-9F-XwU" kind="modal" identifier="createStreamFrame" id="cPc-eP-un1"/>
</connections>
</viewController>