From d17d536577ad277032baaba19f3bdbd9aa38099d Mon Sep 17 00:00:00 2001 From: denicija Date: Wed, 2 Nov 2016 02:56:09 -0700 Subject: [PATCH] Add setting to AppRTCMobile for iOS, that can change capture resolution. To achieve this, several changes needed to be made on both UI and app logic level. * Settings view controller is added (modally shown when the settings button is pressed). - From there the user can see the current capture resolution and select another capture resolution. * Model class for the capture resolution added. - Improves readability and makes separation of concerns cleaner - Handles persisting - Provides defaults - Maps video resolution setting to RTCMediaConstraints dictionary * Test for the model class In future it would be possible to extend this CL and add further settings (i.e bit rate). Also it would be easy to remove the hardcoded resolutions and use dynamic values depending on device capability. BUG=webrtc:6473 Review-Url: https://codereview.webrtc.org/2462623002 Cr-Commit-Position: refs/heads/master@{#14881} --- webrtc/examples/BUILD.gn | 6 + .../examples/objc/AppRTCMobile/ARDAppClient.h | 6 +- .../examples/objc/AppRTCMobile/ARDAppClient.m | 25 +-- .../AppRTCMobile/ios/ARDMainViewController.m | 14 +- .../ios/ARDMediaConstraintsModel+Private.h | 22 +++ .../ios/ARDMediaConstraintsModel.h | 60 +++++++ .../ios/ARDMediaConstraintsModel.m | 104 +++++++++++ .../ios/ARDMediaConstraintsSettingsStore.h | 34 ++++ .../ios/ARDMediaConstraintsSettingsStore.m | 29 ++++ .../ios/ARDSettingsViewController.h | 37 ++++ .../ios/ARDSettingsViewController.m | 162 ++++++++++++++++++ .../ios/ARDVideoCallViewController.m | 13 +- .../tests/ARDMediaConstraintsModelTests.mm | 159 +++++++++++++++++ 13 files changed, 654 insertions(+), 17 deletions(-) create mode 100644 webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel+Private.h create mode 100644 webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.h create mode 100644 webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.m create mode 100644 webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.h create mode 100644 webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.m create mode 100644 webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.h create mode 100644 webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.m create mode 100644 webrtc/examples/objc/AppRTCMobile/tests/ARDMediaConstraintsModelTests.mm diff --git a/webrtc/examples/BUILD.gn b/webrtc/examples/BUILD.gn index 3945e560c2..faa05d3b7e 100644 --- a/webrtc/examples/BUILD.gn +++ b/webrtc/examples/BUILD.gn @@ -241,6 +241,12 @@ if (is_ios || (is_mac && target_cpu != "x86")) { "objc/AppRTCMobile/ios/ARDMainView.m", "objc/AppRTCMobile/ios/ARDMainViewController.h", "objc/AppRTCMobile/ios/ARDMainViewController.m", + "objc/AppRTCMobile/ios/ARDMediaConstraintsModel.h", + "objc/AppRTCMobile/ios/ARDMediaConstraintsModel.m", + "objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.h", + "objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.m", + "objc/AppRTCMobile/ios/ARDSettingsViewController.h", + "objc/AppRTCMobile/ios/ARDSettingsViewController.m", "objc/AppRTCMobile/ios/ARDStatsView.h", "objc/AppRTCMobile/ios/ARDStatsView.m", "objc/AppRTCMobile/ios/ARDVideoCallView.h", diff --git a/webrtc/examples/objc/AppRTCMobile/ARDAppClient.h b/webrtc/examples/objc/AppRTCMobile/ARDAppClient.h index 2186abe693..d906ec726f 100644 --- a/webrtc/examples/objc/AppRTCMobile/ARDAppClient.h +++ b/webrtc/examples/objc/AppRTCMobile/ARDAppClient.h @@ -23,6 +23,8 @@ typedef NS_ENUM(NSInteger, ARDAppClientState) { }; @class ARDAppClient; +@class RTCMediaConstraints; + // The delegate is informed of pertinent events and will be called on the // main queue. @protocol ARDAppClientDelegate @@ -56,11 +58,13 @@ typedef NS_ENUM(NSInteger, ARDAppClientState) { @property(nonatomic, assign) BOOL shouldGetStats; @property(nonatomic, readonly) ARDAppClientState state; @property(nonatomic, weak) id delegate; - // Convenience constructor since all expected use cases will need a delegate // in order to receive remote tracks. - (instancetype)initWithDelegate:(id)delegate; +// Sets camera constraints. +- (void)setCameraConstraints:(RTCMediaConstraints *)mediaConstraints; + // Establishes a connection with the AppRTC servers for the given room id. // If |isLoopback| is true, the call will connect to itself. // If |isAudioOnly| is true, video will be disabled for the call. diff --git a/webrtc/examples/objc/AppRTCMobile/ARDAppClient.m b/webrtc/examples/objc/AppRTCMobile/ARDAppClient.m index 1020621cc1..9ef46b4742 100644 --- a/webrtc/examples/objc/AppRTCMobile/ARDAppClient.m +++ b/webrtc/examples/objc/AppRTCMobile/ARDAppClient.m @@ -101,6 +101,7 @@ static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB. @implementation ARDAppClient { RTCFileLogger *_fileLogger; ARDTimerProxy *_statsTimer; + RTCMediaConstraints *_cameraConstraints; } @synthesize shouldGetStats = _shouldGetStats; @@ -321,6 +322,10 @@ static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB. #endif } +- (void)setCameraConstraints:(RTCMediaConstraints *)mediaConstraints { + _cameraConstraints = mediaConstraints; +} + #pragma mark - ARDSignalingChannelDelegate - (void)channel:(id)channel @@ -695,10 +700,10 @@ static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB. // trying to open a local stream. #if !TARGET_IPHONE_SIMULATOR if (!_isAudioOnly) { - RTCMediaConstraints *mediaConstraints = - [self defaultMediaStreamConstraints]; + RTCMediaConstraints *cameraConstraints = + [self cameraConstraints]; RTCAVFoundationVideoSource *source = - [_factory avFoundationVideoSourceWithConstraints:mediaConstraints]; + [_factory avFoundationVideoSourceWithConstraints:cameraConstraints]; localVideoTrack = [_factory videoTrackWithSource:source trackId:kARDVideoTrackId]; @@ -737,18 +742,14 @@ static int64_t const kARDAppClientRtcEventLogMaxSizeInBytes = 5e6; // 5 MB. NSString *valueLevelControl = _shouldUseLevelControl ? kRTCMediaConstraintsValueTrue : kRTCMediaConstraintsValueFalse; NSDictionary *mandatoryConstraints = @{ kRTCMediaConstraintsLevelControl : valueLevelControl }; - RTCMediaConstraints* constraints = - [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatoryConstraints - optionalConstraints:nil]; + RTCMediaConstraints *constraints = + [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatoryConstraints + optionalConstraints:nil]; return constraints; } -- (RTCMediaConstraints *)defaultMediaStreamConstraints { - RTCMediaConstraints* constraints = - [[RTCMediaConstraints alloc] - initWithMandatoryConstraints:nil - optionalConstraints:nil]; - return constraints; +- (RTCMediaConstraints *)cameraConstraints { + return _cameraConstraints; } - (RTCMediaConstraints *)defaultAnswerConstraints { diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDMainViewController.m b/webrtc/examples/objc/AppRTCMobile/ios/ARDMainViewController.m index f7e03bc72f..33ff8fa9c6 100644 --- a/webrtc/examples/objc/AppRTCMobile/ios/ARDMainViewController.m +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDMainViewController.m @@ -19,9 +19,11 @@ #import "ARDAppClient.h" #import "ARDMainView.h" +#import "ARDMediaConstraintsModel.h" +#import "ARDSettingsViewController.h" #import "ARDVideoCallViewController.h" -static NSString *barButtonImageString = @"ic_settings_black_24dp.png"; +static NSString *const barButtonImageString = @"ic_settings_black_24dp.png"; @interface ARDMainViewController () < ARDMainViewDelegate, @@ -169,6 +171,16 @@ static NSString *barButtonImageString = @"ic_settings_black_24dp.png"; #pragma mark - Private - (void)showSettings:(id)sender { + ARDSettingsViewController *settingsController = + [[ARDSettingsViewController alloc] initWithStyle:UITableViewStylePlain + mediaConstraintsModel:[[ARDMediaConstraintsModel alloc] init]]; + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:settingsController]; + [self presentViewControllerAsModal:navigationController]; +} + +- (void)presentViewControllerAsModal:(UIViewController *)viewController { + [self presentViewController:viewController animated:YES completion:nil]; } - (void)configureAudioSession { diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel+Private.h b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel+Private.h new file mode 100644 index 0000000000..57d507b814 --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel+Private.h @@ -0,0 +1,22 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import "ARDMediaConstraintsModel.h" + +@class ARDMediaConstraintsSettingsStore; + +NS_ASSUME_NONNULL_BEGIN +@interface ARDMediaConstraintsModel () +- (ARDMediaConstraintsSettingsStore *)settingsStore; +- (nullable NSString *)currentVideoResolutionWidthFromStore; +- (nullable NSString *)currentVideoResolutionHeightFromStore; +@end +NS_ASSUME_NONNULL_END diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.h b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.h new file mode 100644 index 0000000000..2f963e0361 --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.h @@ -0,0 +1,60 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN +/** + * Model class for user defined media constraints. + * + * Currently used for streaming media constraints only. + * In future audio media constraints support can be added as well. + * Offers list of avaliable video resolutions that can construct streaming media constraint. + * Exposes methods for reading and storing media constraints from persistent store. + * Also translates current user defined media constraint into RTCMediaConstraints + * dictionary. + */ +@interface ARDMediaConstraintsModel : NSObject + +/** + * Returns array of available capture resoultions. + * + * The capture resolutions are represented as strings in the following format + * [width]x[height] + */ +- (NSArray *)availableVideoResoultionsMediaConstraints; + +/** + * Returns current video resolution media constraint string. + * If no constraint is in store, default value of 640x480 is returned. + * When defaulting to value, the default is saved in store for consistency reasons. + */ +- (NSString *)currentVideoResoultionConstraintFromStore; + +/** + * Stores the provided video resolution media constraint string into the store. + * + * If the provided constraint is no part of the available video resolutions + * the store operation will not be executed and NO will be returned. + * @param constraint the string to be stored. + * @return YES/NO depending on success. + */ +- (BOOL)storeVideoResoultionConstraint:(NSString *)constraint; + +/** + * Converts the current media constraints from store into dictionary with RTCMediaConstraints + * values. + * + * @return NSDictionary with RTC width and height parameters + */ +- (nullable NSDictionary *)currentMediaConstraintFromStoreAsRTCDictionary; + +@end +NS_ASSUME_NONNULL_END diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.m b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.m new file mode 100644 index 0000000000..0299301525 --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsModel.m @@ -0,0 +1,104 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "ARDMediaConstraintsModel+Private.h" +#import "ARDMediaConstraintsSettingsStore.h" +#import "WebRTC/RTCMediaConstraints.h" + +NS_ASSUME_NONNULL_BEGIN +static NSArray *videoResolutionsStaticValues() { + return @[ @"640x480", @"960x540", @"1280x720" ]; +} + +@interface ARDMediaConstraintsModel () { + ARDMediaConstraintsSettingsStore *_settingsStore; +} +@end + +@implementation ARDMediaConstraintsModel + +- (NSArray *)availableVideoResoultionsMediaConstraints { + return videoResolutionsStaticValues(); +} + +- (NSString *)currentVideoResoultionConstraintFromStore { + NSString *constraint = [[self settingsStore] videoResolutionConstraintsSetting]; + if (!constraint) { + constraint = [self defaultVideoResolutionMediaConstraint]; + // To ensure consistency add the default to the store. + [[self settingsStore] setVideoResolutionConstraintsSetting:constraint]; + } + return constraint; +} + +- (BOOL)storeVideoResoultionConstraint:(NSString *)constraint { + if (![[self availableVideoResoultionsMediaConstraints] containsObject:constraint]) { + return NO; + } + [[self settingsStore] setVideoResolutionConstraintsSetting:constraint]; + return YES; +} + +#pragma mark - Testable + +- (ARDMediaConstraintsSettingsStore *)settingsStore { + if (!_settingsStore) { + _settingsStore = [[ARDMediaConstraintsSettingsStore alloc] init]; + } + return _settingsStore; +} + +- (nullable NSString *)currentVideoResolutionWidthFromStore { + NSString *mediaConstraintFromStore = [self currentVideoResoultionConstraintFromStore]; + + return [self videoResolutionComponentAtIndex:0 inConstraintsString:mediaConstraintFromStore]; +} + +- (nullable NSString *)currentVideoResolutionHeightFromStore { + NSString *mediaConstraintFromStore = [self currentVideoResoultionConstraintFromStore]; + return [self videoResolutionComponentAtIndex:1 inConstraintsString:mediaConstraintFromStore]; +} + +#pragma mark - + +- (NSString *)defaultVideoResolutionMediaConstraint { + return videoResolutionsStaticValues()[0]; +} + +- (nullable NSString *)videoResolutionComponentAtIndex:(int)index + inConstraintsString:(NSString *)constraint { + if (index != 0 && index != 1) { + return nil; + } + NSArray *components = [constraint componentsSeparatedByString:@"x"]; + if (components.count != 2) { + return nil; + } + return components[index]; +} + +#pragma mark - Conversion to RTCMediaConstraints + +- (nullable NSDictionary *)currentMediaConstraintFromStoreAsRTCDictionary { + NSDictionary *mediaConstraintsDictionary = nil; + + NSString *widthConstraint = [self currentVideoResolutionWidthFromStore]; + NSString *heightConstraint = [self currentVideoResolutionHeightFromStore]; + if (widthConstraint && heightConstraint) { + mediaConstraintsDictionary = @{ + kRTCMediaConstraintsMinWidth : widthConstraint, + kRTCMediaConstraintsMinHeight : heightConstraint + }; + } + return mediaConstraintsDictionary; +} + +@end +NS_ASSUME_NONNULL_END diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.h b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.h new file mode 100644 index 0000000000..83e4a073ca --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Light-weight persistent store for media constraints user settings. + * + * It will persist between application launches and application updates. + */ +@interface ARDMediaConstraintsSettingsStore : NSObject + +/** + * Returns current video resolution media constraint string stored in the store. + */ +- (nullable NSString *)videoResolutionConstraintsSetting; + +/** + * Stores the provided value as video resolution media constraint. + * @param value the string to be stored + */ +- (void)setVideoResolutionConstraintsSetting:(NSString *)value; + +@end +NS_ASSUME_NONNULL_END diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.m b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.m new file mode 100644 index 0000000000..c8fb206575 --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDMediaConstraintsSettingsStore.m @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "ARDMediaConstraintsSettingsStore.h" + +static NSString *const kUserDefaultsMediaConstraintsKey = + @"rtc_video_resolution_media_constraints_key"; + +NS_ASSUME_NONNULL_BEGIN +@implementation ARDMediaConstraintsSettingsStore + +- (nullable NSString *)videoResolutionConstraintsSetting { + return [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsMediaConstraintsKey]; +} + +- (void)setVideoResolutionConstraintsSetting:(NSString *)constraintsString { + [[NSUserDefaults standardUserDefaults] setObject:constraintsString + forKey:kUserDefaultsMediaConstraintsKey]; +} + +@end +NS_ASSUME_NONNULL_END diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.h b/webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.h new file mode 100644 index 0000000000..91b9fc94a5 --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.h @@ -0,0 +1,37 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +@class ARDMediaConstraintsModel; + +NS_ASSUME_NONNULL_BEGIN +/** + * Displays settings options. + */ +@interface ARDSettingsViewController : UITableViewController + +/** + * Creates new instance. + * + * @param style the table view style that should be used + * @param mediaConstraintsModel model class for the media constraints settings. + */ +- (instancetype)initWithStyle:(UITableViewStyle)style + mediaConstraintsModel:(ARDMediaConstraintsModel *)mediaConstraintsModel; + +#pragma mark - Unavailable + +- (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype) new NS_UNAVAILABLE; + +@end +NS_ASSUME_NONNULL_END diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.m b/webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.m new file mode 100644 index 0000000000..1df9892673 --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDSettingsViewController.m @@ -0,0 +1,162 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "ARDSettingsViewController.h" +#import "ARDMediaConstraintsModel.h" + +NS_ASSUME_NONNULL_BEGIN +@interface ARDSettingsViewController () { + ARDMediaConstraintsModel *_mediaConstraintsModel; +} + +@end + +@implementation ARDSettingsViewController + +- (instancetype)initWithStyle:(UITableViewStyle)style + mediaConstraintsModel:(ARDMediaConstraintsModel *)mediaConstraintsModel { + self = [super initWithStyle:style]; + if (self) { + _mediaConstraintsModel = mediaConstraintsModel; + } + return self; +} + +#pragma mark - View lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + self.title = @"Settings"; + [self addDoneBarButton]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self selectCurrentlyStoredOrDefaultMediaConstraints]; +} + +#pragma mark - Data source + +- (NSArray *)mediaConstraintsArray { + return _mediaConstraintsModel.availableVideoResoultionsMediaConstraints; +} + +#pragma mark - + +- (void)addDoneBarButton { + UIBarButtonItem *barItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(dismissModally:)]; + self.navigationItem.leftBarButtonItem = barItem; +} + +- (void)selectCurrentlyStoredOrDefaultMediaConstraints { + NSString *currentSelection = [_mediaConstraintsModel currentVideoResoultionConstraintFromStore]; + + NSUInteger indexOfSelection = [[self mediaConstraintsArray] indexOfObject:currentSelection]; + NSIndexPath *pathToBeSelected = [NSIndexPath indexPathForRow:indexOfSelection inSection:0]; + [self.tableView selectRowAtIndexPath:pathToBeSelected + animated:NO + scrollPosition:UITableViewScrollPositionNone]; + // Manully invoke the delegate method because the previous invocation will not. + [self tableView:self.tableView didSelectRowAtIndexPath:pathToBeSelected]; +} + +#pragma mark - Dismissal of view controller + +- (void)dismissModally:(id)sender { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.mediaConstraintsArray.count; +} + +#pragma mark - Table view delegate + +- (BOOL)sectionIsMediaConstraints:(int)section { + return section == 0; +} + +- (BOOL)indexPathIsMediaConstraints:(NSIndexPath *)indexPath { + return [self sectionIsMediaConstraints:indexPath.section]; +} + +- (nullable NSString *)tableView:(UITableView *)tableView + titleForHeaderInSection:(NSInteger)section { + if ([self sectionIsMediaConstraints:section]) { + return @"Media constraints"; + } + return @""; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if ([self indexPathIsMediaConstraints:indexPath]) { + return [self mediaConstraintsTableViewCellForTableView:tableView atIndexPath:indexPath]; + } + return [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:@"identifier"]; +} + +- (nullable NSIndexPath *)tableView:(UITableView *)tableView + willSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath { + if ([self indexPathIsMediaConstraints:indexPath]) { + return [self tableView:tableView willDeselectMediaConstraintsRowAtIndexPath:indexPath]; + } + return indexPath; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if ([self indexPathIsMediaConstraints:indexPath]) { + [self tableView:tableView didSelectMediaConstraintsCellAtIndexPath:indexPath]; + } +} + +#pragma mark - Table view delegate(Media Constraints) + +- (UITableViewCell *)mediaConstraintsTableViewCellForTableView:(UITableView *)tableView + atIndexPath:(NSIndexPath *)indexPath { + NSString *dequeueIdentifier = @"ARDSettingsMediaConstraintsViewCellIdentifier"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:dequeueIdentifier]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:dequeueIdentifier]; + } + cell.textLabel.text = self.mediaConstraintsArray[indexPath.row]; + return cell; +} + +- (void)tableView:(UITableView *)tableView + didSelectMediaConstraintsCellAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; + cell.accessoryType = UITableViewCellAccessoryCheckmark; + + NSString *mediaConstraintsString = self.mediaConstraintsArray[indexPath.row]; + [_mediaConstraintsModel storeVideoResoultionConstraint:mediaConstraintsString]; +} + +- (NSIndexPath *)tableView:(UITableView *)tableView + willDeselectMediaConstraintsRowAtIndexPath:(NSIndexPath *)indexPath { + NSIndexPath *oldSelection = [tableView indexPathForSelectedRow]; + UITableViewCell *cell = [tableView cellForRowAtIndexPath:oldSelection]; + cell.accessoryType = UITableViewCellAccessoryNone; + return indexPath; +} + +@end +NS_ASSUME_NONNULL_END diff --git a/webrtc/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m b/webrtc/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m index aa9ea2113d..d4008894d6 100644 --- a/webrtc/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m +++ b/webrtc/examples/objc/AppRTCMobile/ios/ARDVideoCallViewController.m @@ -12,12 +12,13 @@ #import "webrtc/modules/audio_device/ios/objc/RTCAudioSession.h" +#import "ARDAppClient.h" +#import "ARDMediaConstraintsModel.h" +#import "ARDVideoCallView.h" #import "WebRTC/RTCAVFoundationVideoSource.h" #import "WebRTC/RTCDispatcher.h" #import "WebRTC/RTCLogging.h" - -#import "ARDAppClient.h" -#import "ARDVideoCallView.h" +#import "WebRTC/RTCMediaConstraints.h" @interface ARDVideoCallViewController () @@ -45,6 +46,12 @@ if (self = [super init]) { _delegate = delegate; _client = [[ARDAppClient alloc] initWithDelegate:self]; + ARDMediaConstraintsModel *mediaConstraintsModel = [[ARDMediaConstraintsModel alloc] init]; + RTCMediaConstraints *cameraConstraints = [[RTCMediaConstraints alloc] + initWithMandatoryConstraints:nil + optionalConstraints:[mediaConstraintsModel + currentMediaConstraintFromStoreAsRTCDictionary]]; + [_client setCameraConstraints:cameraConstraints]; [_client connectToRoomWithId:room isLoopback:isLoopback isAudioOnly:isAudioOnly diff --git a/webrtc/examples/objc/AppRTCMobile/tests/ARDMediaConstraintsModelTests.mm b/webrtc/examples/objc/AppRTCMobile/tests/ARDMediaConstraintsModelTests.mm new file mode 100644 index 0000000000..e4a25b94d8 --- /dev/null +++ b/webrtc/examples/objc/AppRTCMobile/tests/ARDMediaConstraintsModelTests.mm @@ -0,0 +1,159 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import +#import +#import "ARDMediaConstraintsModel+Private.h" +#import "ARDMediaConstraintsSettingsStore.h" +#import "WebRTC/RTCMediaConstraints.h" +#include "webrtc/base/gunit.h" + + +@interface ARDMediaConstraintsModelTests : NSObject { + ARDMediaConstraintsModel *_model; +} + +- (void)testStoringInavlidConstraintReturnsNo; +- (void)testDefaultMediaFromStore; +- (void)testWidthConstraintFromStore; +- (void)testHeightConstraintFromStore; + +@end + +@implementation ARDMediaConstraintsModelTests + +- (instancetype)init { + self = [super init]; + if (self) { + _model = [[ARDMediaConstraintsModel alloc] init]; + } + return self; +} + +- (id)setupMockStoreWithMediaConstraintString:(NSString *)constraintString { + id storeMock = [OCMockObject mockForClass:[ARDMediaConstraintsSettingsStore class]]; + [([[storeMock stub] andReturn:constraintString]) videoResolutionConstraintsSetting]; + + id partialMock = [OCMockObject partialMockForObject:_model]; + [[[partialMock stub] andReturn:storeMock] settingsStore]; + + return storeMock; +} + +- (void)testDefaultMediaFromStore { + // given + id storeMock = [self setupMockStoreWithMediaConstraintString:nil]; + + [[storeMock expect] setVideoResolutionConstraintsSetting:@"640x480"]; + + // when + NSString *string = [_model currentVideoResoultionConstraintFromStore]; + + // then + EXPECT_TRUE([string isEqualToString:@"640x480"]); + [storeMock verify]; +} + +- (void)testStoringInavlidConstraintReturnsNo { + // given + id storeMock = [self setupMockStoreWithMediaConstraintString:@"960x480"]; + + // when + BOOL result = [_model storeVideoResoultionConstraint:@"960x480"]; + + // then + EXPECT_TRUE(result); +} + +- (void)testWidthConstraintFromStore { + // given + [self setupMockStoreWithMediaConstraintString:@"1270x480"]; + + // when + NSString *width = [_model currentVideoResolutionWidthFromStore]; + + // then + EXPECT_TRUE([width isEqualToString:@"1270"]); +} + +- (void)testHeightConstraintFromStore { + // given + [self setupMockStoreWithMediaConstraintString:@"960x540"]; + // when + NSString *height = [_model currentVideoResolutionHeightFromStore]; + + // then + EXPECT_TRUE([height isEqualToString:@"540"]); +} + +- (void)testConstraintComponentIsNilWhenInvalidConstraintString { + // given + [self setupMockStoreWithMediaConstraintString:@"invalid"]; + + // when + NSString *width = [_model currentVideoResolutionWidthFromStore]; + + // then + EXPECT_TRUE(width == nil); +} + +- (void)testConstraintsDictionaryIsNilWhenInvalidConstraintString { + // given + [self setupMockStoreWithMediaConstraintString:@"invalid"]; + + // when + NSDictionary *constraintsDictionary = [_model currentMediaConstraintFromStoreAsRTCDictionary]; + + // then + EXPECT_TRUE(constraintsDictionary == nil); +} +@end + +class ARDMediaConstraintsModelTest : public ::testing::Test { + protected: + ARDMediaConstraintsModelTests *test; + ARDMediaConstraintsModelTest() { test = [[ARDMediaConstraintsModelTests alloc] init]; } +}; + +TEST_F(ARDMediaConstraintsModelTest, DefaultMediaFromStore) { + @autoreleasepool { + [test testDefaultMediaFromStore]; + } +} + +TEST_F(ARDMediaConstraintsModelTest, StoringInvalidConstraintsReturnsNo) { + @autoreleasepool { + [test testStoringInavlidConstraintReturnsNo]; + } +} + +TEST_F(ARDMediaConstraintsModelTest, WidthConstraintFromStore) { + @autoreleasepool { + [test testWidthConstraintFromStore]; + } +} + +TEST_F(ARDMediaConstraintsModelTest, HeightConstraintFromStore) { + @autoreleasepool { + [test testHeightConstraintFromStore]; + } +} + +TEST_F(ARDMediaConstraintsModelTest, ConstratintIsNil) { + @autoreleasepool { + [test testConstraintComponentIsNilWhenInvalidConstraintString]; + } +} + +TEST_F(ARDMediaConstraintsModelTest, DictionaryIsNil) { + @autoreleasepool { + [test testConstraintsDictionaryIsNilWhenInvalidConstraintString]; + } +}