xref: /aosp_15_r20/external/webrtc/examples/objc/AppRTCMobile/ios/ARDMainViewController.m (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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