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