1*d9f75844SAndroid Build Coastguard Worker/* 2*d9f75844SAndroid Build Coastguard Worker * Copyright 2015 The WebRTC Project Authors. All rights reserved. 3*d9f75844SAndroid Build Coastguard Worker * 4*d9f75844SAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license 5*d9f75844SAndroid Build Coastguard Worker * that can be found in the LICENSE file in the root of the source 6*d9f75844SAndroid Build Coastguard Worker * tree. An additional intellectual property rights grant can be found 7*d9f75844SAndroid Build Coastguard Worker * in the file PATENTS. All contributing project authors may 8*d9f75844SAndroid Build Coastguard Worker * be found in the AUTHORS file in the root of the source tree. 9*d9f75844SAndroid Build Coastguard Worker */ 10*d9f75844SAndroid Build Coastguard Worker 11*d9f75844SAndroid Build Coastguard Worker#import "ARDMainViewController.h" 12*d9f75844SAndroid Build Coastguard Worker 13*d9f75844SAndroid Build Coastguard Worker#import <AVFoundation/AVFoundation.h> 14*d9f75844SAndroid Build Coastguard Worker 15*d9f75844SAndroid Build Coastguard Worker#import "sdk/objc/base/RTCLogging.h" 16*d9f75844SAndroid Build Coastguard Worker#import "sdk/objc/components/audio/RTCAudioSession.h" 17*d9f75844SAndroid Build Coastguard Worker#import "sdk/objc/components/audio/RTCAudioSessionConfiguration.h" 18*d9f75844SAndroid Build Coastguard Worker#import "sdk/objc/helpers/RTCDispatcher.h" 19*d9f75844SAndroid Build Coastguard Worker 20*d9f75844SAndroid Build Coastguard Worker#import "ARDAppClient.h" 21*d9f75844SAndroid Build Coastguard Worker#import "ARDMainView.h" 22*d9f75844SAndroid Build Coastguard Worker#import "ARDSettingsModel.h" 23*d9f75844SAndroid Build Coastguard Worker#import "ARDSettingsViewController.h" 24*d9f75844SAndroid Build Coastguard Worker#import "ARDVideoCallViewController.h" 25*d9f75844SAndroid Build Coastguard Worker 26*d9f75844SAndroid Build Coastguard Workerstatic NSString *const barButtonImageString = @"ic_settings_black_24dp.png"; 27*d9f75844SAndroid Build Coastguard Worker 28*d9f75844SAndroid Build Coastguard Worker// Launch argument to be passed to indicate that the app should start loopback immediatly 29*d9f75844SAndroid Build Coastguard Workerstatic NSString *const loopbackLaunchProcessArgument = @"loopback"; 30*d9f75844SAndroid Build Coastguard Worker 31*d9f75844SAndroid Build Coastguard Worker@interface ARDMainViewController () <ARDMainViewDelegate, 32*d9f75844SAndroid Build Coastguard Worker ARDVideoCallViewControllerDelegate, 33*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE (RTCAudioSessionDelegate)> 34*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) ARDMainView *mainView; 35*d9f75844SAndroid Build Coastguard Worker@property(nonatomic, strong) AVAudioPlayer *audioPlayer; 36*d9f75844SAndroid Build Coastguard Worker@end 37*d9f75844SAndroid Build Coastguard Worker 38*d9f75844SAndroid Build Coastguard Worker@implementation ARDMainViewController { 39*d9f75844SAndroid Build Coastguard Worker BOOL _useManualAudio; 40*d9f75844SAndroid Build Coastguard Worker} 41*d9f75844SAndroid Build Coastguard Worker 42*d9f75844SAndroid Build Coastguard Worker@synthesize mainView = _mainView; 43*d9f75844SAndroid Build Coastguard Worker@synthesize audioPlayer = _audioPlayer; 44*d9f75844SAndroid Build Coastguard Worker 45*d9f75844SAndroid Build Coastguard Worker- (void)viewDidLoad { 46*d9f75844SAndroid Build Coastguard Worker [super viewDidLoad]; 47*d9f75844SAndroid Build Coastguard Worker if ([[[NSProcessInfo processInfo] arguments] containsObject:loopbackLaunchProcessArgument]) { 48*d9f75844SAndroid Build Coastguard Worker [self mainView:nil didInputRoom:@"" isLoopback:YES]; 49*d9f75844SAndroid Build Coastguard Worker } 50*d9f75844SAndroid Build Coastguard Worker} 51*d9f75844SAndroid Build Coastguard Worker 52*d9f75844SAndroid Build Coastguard Worker- (void)loadView { 53*d9f75844SAndroid Build Coastguard Worker self.title = @"AppRTC Mobile"; 54*d9f75844SAndroid Build Coastguard Worker _mainView = [[ARDMainView alloc] initWithFrame:CGRectZero]; 55*d9f75844SAndroid Build Coastguard Worker _mainView.delegate = self; 56*d9f75844SAndroid Build Coastguard Worker self.view = _mainView; 57*d9f75844SAndroid Build Coastguard Worker [self addSettingsBarButton]; 58*d9f75844SAndroid Build Coastguard Worker 59*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *webRTCConfig = 60*d9f75844SAndroid Build Coastguard Worker [RTC_OBJC_TYPE(RTCAudioSessionConfiguration) webRTCConfiguration]; 61*d9f75844SAndroid Build Coastguard Worker webRTCConfig.categoryOptions = webRTCConfig.categoryOptions | 62*d9f75844SAndroid Build Coastguard Worker AVAudioSessionCategoryOptionDefaultToSpeaker; 63*d9f75844SAndroid Build Coastguard Worker [RTC_OBJC_TYPE(RTCAudioSessionConfiguration) setWebRTCConfiguration:webRTCConfig]; 64*d9f75844SAndroid Build Coastguard Worker 65*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 66*d9f75844SAndroid Build Coastguard Worker [session addDelegate:self]; 67*d9f75844SAndroid Build Coastguard Worker 68*d9f75844SAndroid Build Coastguard Worker [self configureAudioSession]; 69*d9f75844SAndroid Build Coastguard Worker [self setupAudioPlayer]; 70*d9f75844SAndroid Build Coastguard Worker} 71*d9f75844SAndroid Build Coastguard Worker 72*d9f75844SAndroid Build Coastguard Worker- (void)addSettingsBarButton { 73*d9f75844SAndroid Build Coastguard Worker UIBarButtonItem *settingsButton = 74*d9f75844SAndroid Build Coastguard Worker [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:barButtonImageString] 75*d9f75844SAndroid Build Coastguard Worker style:UIBarButtonItemStylePlain 76*d9f75844SAndroid Build Coastguard Worker target:self 77*d9f75844SAndroid Build Coastguard Worker action:@selector(showSettings:)]; 78*d9f75844SAndroid Build Coastguard Worker self.navigationItem.rightBarButtonItem = settingsButton; 79*d9f75844SAndroid Build Coastguard Worker} 80*d9f75844SAndroid Build Coastguard Worker 81*d9f75844SAndroid Build Coastguard Worker+ (NSString *)loopbackRoomString { 82*d9f75844SAndroid Build Coastguard Worker NSString *loopbackRoomString = 83*d9f75844SAndroid Build Coastguard Worker [[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""]; 84*d9f75844SAndroid Build Coastguard Worker return loopbackRoomString; 85*d9f75844SAndroid Build Coastguard Worker} 86*d9f75844SAndroid Build Coastguard Worker 87*d9f75844SAndroid Build Coastguard Worker#pragma mark - ARDMainViewDelegate 88*d9f75844SAndroid Build Coastguard Worker 89*d9f75844SAndroid Build Coastguard Worker- (void)mainView:(ARDMainView *)mainView didInputRoom:(NSString *)room isLoopback:(BOOL)isLoopback { 90*d9f75844SAndroid Build Coastguard Worker if (!room.length) { 91*d9f75844SAndroid Build Coastguard Worker if (isLoopback) { 92*d9f75844SAndroid Build Coastguard Worker // If this is a loopback call, allow a generated room name. 93*d9f75844SAndroid Build Coastguard Worker room = [[self class] loopbackRoomString]; 94*d9f75844SAndroid Build Coastguard Worker } else { 95*d9f75844SAndroid Build Coastguard Worker [self showAlertWithMessage:@"Missing room name."]; 96*d9f75844SAndroid Build Coastguard Worker return; 97*d9f75844SAndroid Build Coastguard Worker } 98*d9f75844SAndroid Build Coastguard Worker } 99*d9f75844SAndroid Build Coastguard Worker // Trim whitespaces. 100*d9f75844SAndroid Build Coastguard Worker NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceCharacterSet]; 101*d9f75844SAndroid Build Coastguard Worker NSString *trimmedRoom = [room stringByTrimmingCharactersInSet:whitespaceSet]; 102*d9f75844SAndroid Build Coastguard Worker 103*d9f75844SAndroid Build Coastguard Worker // Check that room name is valid. 104*d9f75844SAndroid Build Coastguard Worker NSError *error = nil; 105*d9f75844SAndroid Build Coastguard Worker NSRegularExpressionOptions options = NSRegularExpressionCaseInsensitive; 106*d9f75844SAndroid Build Coastguard Worker NSRegularExpression *regex = 107*d9f75844SAndroid Build Coastguard Worker [NSRegularExpression regularExpressionWithPattern:@"\\w+" 108*d9f75844SAndroid Build Coastguard Worker options:options 109*d9f75844SAndroid Build Coastguard Worker error:&error]; 110*d9f75844SAndroid Build Coastguard Worker if (error) { 111*d9f75844SAndroid Build Coastguard Worker [self showAlertWithMessage:error.localizedDescription]; 112*d9f75844SAndroid Build Coastguard Worker return; 113*d9f75844SAndroid Build Coastguard Worker } 114*d9f75844SAndroid Build Coastguard Worker NSRange matchRange = 115*d9f75844SAndroid Build Coastguard Worker [regex rangeOfFirstMatchInString:trimmedRoom 116*d9f75844SAndroid Build Coastguard Worker options:0 117*d9f75844SAndroid Build Coastguard Worker range:NSMakeRange(0, trimmedRoom.length)]; 118*d9f75844SAndroid Build Coastguard Worker if (matchRange.location == NSNotFound || 119*d9f75844SAndroid Build Coastguard Worker matchRange.length != trimmedRoom.length) { 120*d9f75844SAndroid Build Coastguard Worker [self showAlertWithMessage:@"Invalid room name."]; 121*d9f75844SAndroid Build Coastguard Worker return; 122*d9f75844SAndroid Build Coastguard Worker } 123*d9f75844SAndroid Build Coastguard Worker 124*d9f75844SAndroid Build Coastguard Worker ARDSettingsModel *settingsModel = [[ARDSettingsModel alloc] init]; 125*d9f75844SAndroid Build Coastguard Worker 126*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 127*d9f75844SAndroid Build Coastguard Worker session.useManualAudio = [settingsModel currentUseManualAudioConfigSettingFromStore]; 128*d9f75844SAndroid Build Coastguard Worker session.isAudioEnabled = NO; 129*d9f75844SAndroid Build Coastguard Worker 130*d9f75844SAndroid Build Coastguard Worker // Kick off the video call. 131*d9f75844SAndroid Build Coastguard Worker ARDVideoCallViewController *videoCallViewController = 132*d9f75844SAndroid Build Coastguard Worker [[ARDVideoCallViewController alloc] initForRoom:trimmedRoom 133*d9f75844SAndroid Build Coastguard Worker isLoopback:isLoopback 134*d9f75844SAndroid Build Coastguard Worker delegate:self]; 135*d9f75844SAndroid Build Coastguard Worker videoCallViewController.modalTransitionStyle = 136*d9f75844SAndroid Build Coastguard Worker UIModalTransitionStyleCrossDissolve; 137*d9f75844SAndroid Build Coastguard Worker videoCallViewController.modalPresentationStyle = UIModalPresentationFullScreen; 138*d9f75844SAndroid Build Coastguard Worker [self presentViewController:videoCallViewController 139*d9f75844SAndroid Build Coastguard Worker animated:YES 140*d9f75844SAndroid Build Coastguard Worker completion:nil]; 141*d9f75844SAndroid Build Coastguard Worker} 142*d9f75844SAndroid Build Coastguard Worker 143*d9f75844SAndroid Build Coastguard Worker- (void)mainViewDidToggleAudioLoop:(ARDMainView *)mainView { 144*d9f75844SAndroid Build Coastguard Worker if (mainView.isAudioLoopPlaying) { 145*d9f75844SAndroid Build Coastguard Worker [_audioPlayer stop]; 146*d9f75844SAndroid Build Coastguard Worker } else { 147*d9f75844SAndroid Build Coastguard Worker [_audioPlayer play]; 148*d9f75844SAndroid Build Coastguard Worker } 149*d9f75844SAndroid Build Coastguard Worker mainView.isAudioLoopPlaying = _audioPlayer.playing; 150*d9f75844SAndroid Build Coastguard Worker} 151*d9f75844SAndroid Build Coastguard Worker 152*d9f75844SAndroid Build Coastguard Worker#pragma mark - ARDVideoCallViewControllerDelegate 153*d9f75844SAndroid Build Coastguard Worker 154*d9f75844SAndroid Build Coastguard Worker- (void)viewControllerDidFinish:(ARDVideoCallViewController *)viewController { 155*d9f75844SAndroid Build Coastguard Worker if (![viewController isBeingDismissed]) { 156*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Dismissing VC"); 157*d9f75844SAndroid Build Coastguard Worker [self dismissViewControllerAnimated:YES completion:^{ 158*d9f75844SAndroid Build Coastguard Worker [self restartAudioPlayerIfNeeded]; 159*d9f75844SAndroid Build Coastguard Worker }]; 160*d9f75844SAndroid Build Coastguard Worker } 161*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 162*d9f75844SAndroid Build Coastguard Worker session.isAudioEnabled = NO; 163*d9f75844SAndroid Build Coastguard Worker} 164*d9f75844SAndroid Build Coastguard Worker 165*d9f75844SAndroid Build Coastguard Worker#pragma mark - RTC_OBJC_TYPE(RTCAudioSessionDelegate) 166*d9f75844SAndroid Build Coastguard Worker 167*d9f75844SAndroid Build Coastguard Worker- (void)audioSessionDidStartPlayOrRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)session { 168*d9f75844SAndroid Build Coastguard Worker // Stop playback on main queue and then configure WebRTC. 169*d9f75844SAndroid Build Coastguard Worker [RTC_OBJC_TYPE(RTCDispatcher) 170*d9f75844SAndroid Build Coastguard Worker dispatchAsyncOnType:RTCDispatcherTypeMain 171*d9f75844SAndroid Build Coastguard Worker block:^{ 172*d9f75844SAndroid Build Coastguard Worker if (self.mainView.isAudioLoopPlaying) { 173*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Stopping audio loop due to WebRTC start."); 174*d9f75844SAndroid Build Coastguard Worker [self.audioPlayer stop]; 175*d9f75844SAndroid Build Coastguard Worker } 176*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Setting isAudioEnabled to YES."); 177*d9f75844SAndroid Build Coastguard Worker session.isAudioEnabled = YES; 178*d9f75844SAndroid Build Coastguard Worker }]; 179*d9f75844SAndroid Build Coastguard Worker} 180*d9f75844SAndroid Build Coastguard Worker 181*d9f75844SAndroid Build Coastguard Worker- (void)audioSessionDidStopPlayOrRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)session { 182*d9f75844SAndroid Build Coastguard Worker // WebRTC is done with the audio session. Restart playback. 183*d9f75844SAndroid Build Coastguard Worker [RTC_OBJC_TYPE(RTCDispatcher) dispatchAsyncOnType:RTCDispatcherTypeMain 184*d9f75844SAndroid Build Coastguard Worker block:^{ 185*d9f75844SAndroid Build Coastguard Worker RTCLog(@"audioSessionDidStopPlayOrRecord"); 186*d9f75844SAndroid Build Coastguard Worker [self restartAudioPlayerIfNeeded]; 187*d9f75844SAndroid Build Coastguard Worker }]; 188*d9f75844SAndroid Build Coastguard Worker} 189*d9f75844SAndroid Build Coastguard Worker 190*d9f75844SAndroid Build Coastguard Worker#pragma mark - Private 191*d9f75844SAndroid Build Coastguard Worker- (void)showSettings:(id)sender { 192*d9f75844SAndroid Build Coastguard Worker ARDSettingsViewController *settingsController = 193*d9f75844SAndroid Build Coastguard Worker [[ARDSettingsViewController alloc] initWithStyle:UITableViewStyleGrouped 194*d9f75844SAndroid Build Coastguard Worker settingsModel:[[ARDSettingsModel alloc] init]]; 195*d9f75844SAndroid Build Coastguard Worker 196*d9f75844SAndroid Build Coastguard Worker UINavigationController *navigationController = 197*d9f75844SAndroid Build Coastguard Worker [[UINavigationController alloc] initWithRootViewController:settingsController]; 198*d9f75844SAndroid Build Coastguard Worker [self presentViewControllerAsModal:navigationController]; 199*d9f75844SAndroid Build Coastguard Worker} 200*d9f75844SAndroid Build Coastguard Worker 201*d9f75844SAndroid Build Coastguard Worker- (void)presentViewControllerAsModal:(UIViewController *)viewController { 202*d9f75844SAndroid Build Coastguard Worker [self presentViewController:viewController animated:YES completion:nil]; 203*d9f75844SAndroid Build Coastguard Worker} 204*d9f75844SAndroid Build Coastguard Worker 205*d9f75844SAndroid Build Coastguard Worker- (void)configureAudioSession { 206*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *configuration = 207*d9f75844SAndroid Build Coastguard Worker [[RTC_OBJC_TYPE(RTCAudioSessionConfiguration) alloc] init]; 208*d9f75844SAndroid Build Coastguard Worker configuration.category = AVAudioSessionCategoryAmbient; 209*d9f75844SAndroid Build Coastguard Worker configuration.categoryOptions = AVAudioSessionCategoryOptionDuckOthers; 210*d9f75844SAndroid Build Coastguard Worker configuration.mode = AVAudioSessionModeDefault; 211*d9f75844SAndroid Build Coastguard Worker 212*d9f75844SAndroid Build Coastguard Worker RTC_OBJC_TYPE(RTCAudioSession) *session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance]; 213*d9f75844SAndroid Build Coastguard Worker [session lockForConfiguration]; 214*d9f75844SAndroid Build Coastguard Worker BOOL hasSucceeded = NO; 215*d9f75844SAndroid Build Coastguard Worker NSError *error = nil; 216*d9f75844SAndroid Build Coastguard Worker if (session.isActive) { 217*d9f75844SAndroid Build Coastguard Worker hasSucceeded = [session setConfiguration:configuration error:&error]; 218*d9f75844SAndroid Build Coastguard Worker } else { 219*d9f75844SAndroid Build Coastguard Worker hasSucceeded = [session setConfiguration:configuration 220*d9f75844SAndroid Build Coastguard Worker active:YES 221*d9f75844SAndroid Build Coastguard Worker error:&error]; 222*d9f75844SAndroid Build Coastguard Worker } 223*d9f75844SAndroid Build Coastguard Worker if (!hasSucceeded) { 224*d9f75844SAndroid Build Coastguard Worker RTCLogError(@"Error setting configuration: %@", error.localizedDescription); 225*d9f75844SAndroid Build Coastguard Worker } 226*d9f75844SAndroid Build Coastguard Worker [session unlockForConfiguration]; 227*d9f75844SAndroid Build Coastguard Worker} 228*d9f75844SAndroid Build Coastguard Worker 229*d9f75844SAndroid Build Coastguard Worker- (void)setupAudioPlayer { 230*d9f75844SAndroid Build Coastguard Worker NSString *audioFilePath = 231*d9f75844SAndroid Build Coastguard Worker [[NSBundle mainBundle] pathForResource:@"mozart" ofType:@"mp3"]; 232*d9f75844SAndroid Build Coastguard Worker NSURL *audioFileURL = [NSURL URLWithString:audioFilePath]; 233*d9f75844SAndroid Build Coastguard Worker _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL 234*d9f75844SAndroid Build Coastguard Worker error:nil]; 235*d9f75844SAndroid Build Coastguard Worker _audioPlayer.numberOfLoops = -1; 236*d9f75844SAndroid Build Coastguard Worker _audioPlayer.volume = 1.0; 237*d9f75844SAndroid Build Coastguard Worker [_audioPlayer prepareToPlay]; 238*d9f75844SAndroid Build Coastguard Worker} 239*d9f75844SAndroid Build Coastguard Worker 240*d9f75844SAndroid Build Coastguard Worker- (void)restartAudioPlayerIfNeeded { 241*d9f75844SAndroid Build Coastguard Worker [self configureAudioSession]; 242*d9f75844SAndroid Build Coastguard Worker if (_mainView.isAudioLoopPlaying && !self.presentedViewController) { 243*d9f75844SAndroid Build Coastguard Worker RTCLog(@"Starting audio loop due to WebRTC end."); 244*d9f75844SAndroid Build Coastguard Worker [_audioPlayer play]; 245*d9f75844SAndroid Build Coastguard Worker } 246*d9f75844SAndroid Build Coastguard Worker} 247*d9f75844SAndroid Build Coastguard Worker 248*d9f75844SAndroid Build Coastguard Worker- (void)showAlertWithMessage:(NSString*)message { 249*d9f75844SAndroid Build Coastguard Worker UIAlertController *alert = 250*d9f75844SAndroid Build Coastguard Worker [UIAlertController alertControllerWithTitle:nil 251*d9f75844SAndroid Build Coastguard Worker message:message 252*d9f75844SAndroid Build Coastguard Worker preferredStyle:UIAlertControllerStyleAlert]; 253*d9f75844SAndroid Build Coastguard Worker 254*d9f75844SAndroid Build Coastguard Worker UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"OK" 255*d9f75844SAndroid Build Coastguard Worker style:UIAlertActionStyleDefault 256*d9f75844SAndroid Build Coastguard Worker handler:^(UIAlertAction *action){ 257*d9f75844SAndroid Build Coastguard Worker }]; 258*d9f75844SAndroid Build Coastguard Worker 259*d9f75844SAndroid Build Coastguard Worker [alert addAction:defaultAction]; 260*d9f75844SAndroid Build Coastguard Worker [self presentViewController:alert animated:YES completion:nil]; 261*d9f75844SAndroid Build Coastguard Worker} 262*d9f75844SAndroid Build Coastguard Worker 263*d9f75844SAndroid Build Coastguard Worker@end 264