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