iOS camera switching video capturer.

Introduces a new capture class derived from cricket::VideoCapturer that
provides the ability to switch cameras and updates AppRTCDemo to use it.
Some future work pending to clean up AppRTCDemo UI.

BUG=4070
R=magjed@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/48279005

Cr-Commit-Position: refs/heads/master@{#9137}
diff --git a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.h b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.h
index 1bb5b5c..7c1decb 100644
--- a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.h
+++ b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.h
@@ -32,6 +32,9 @@
 @class ARDVideoCallView;
 @protocol ARDVideoCallViewDelegate <NSObject>
 
+// Called when the camera switch button is pressed.
+- (void)videoCallViewDidSwitchCamera:(ARDVideoCallView *)view;
+
 // Called when the hangup button is pressed.
 - (void)videoCallViewDidHangup:(ARDVideoCallView *)view;
 
diff --git a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.m b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.m
index 852951e..47bfe89 100644
--- a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.m
+++ b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallView.m
@@ -30,19 +30,20 @@
 #import <AVFoundation/AVFoundation.h>
 #import "UIImage+ARDUtilities.h"
 
-static CGFloat const kHangupButtonPadding = 16;
-static CGFloat const kHangupButtonSize = 48;
-static CGFloat const kLocalVideoViewWidth = 90;
-static CGFloat const kLocalVideoViewHeight = 120;
+static CGFloat const kButtonPadding = 16;
+static CGFloat const kButtonSize = 48;
+static CGFloat const kLocalVideoViewSize = 120;
 static CGFloat const kLocalVideoViewPadding = 8;
 
 @interface ARDVideoCallView () <RTCEAGLVideoViewDelegate>
 @end
 
 @implementation ARDVideoCallView {
+  UIButton *_cameraSwitchButton;
   UIButton *_hangupButton;
   CGSize _localVideoSize;
   CGSize _remoteVideoSize;
+  BOOL _useRearCamera;
 }
 
 @synthesize statusLabel = _statusLabel;
@@ -56,17 +57,30 @@
     _remoteVideoView.delegate = self;
     [self addSubview:_remoteVideoView];
 
+    // TODO(tkchin): replace this with a view that renders layer from
+    // AVCaptureSession.
     _localVideoView = [[RTCEAGLVideoView alloc] initWithFrame:CGRectZero];
-    _localVideoView.transform = CGAffineTransformMakeScale(-1, 1);
     _localVideoView.delegate = self;
     [self addSubview:_localVideoView];
 
+    // TODO(tkchin): don't display this if we can't actually do camera switch.
+    _cameraSwitchButton = [UIButton buttonWithType:UIButtonTypeCustom];
+    _cameraSwitchButton.backgroundColor = [UIColor whiteColor];
+    _cameraSwitchButton.layer.cornerRadius = kButtonSize / 2;
+    _cameraSwitchButton.layer.masksToBounds = YES;
+    UIImage *image = [UIImage imageNamed:@"ic_switch_video_black_24dp.png"];
+    [_cameraSwitchButton setImage:image forState:UIControlStateNormal];
+    [_cameraSwitchButton addTarget:self
+                      action:@selector(onCameraSwitch:)
+            forControlEvents:UIControlEventTouchUpInside];
+    [self addSubview:_cameraSwitchButton];
+
     _hangupButton = [UIButton buttonWithType:UIButtonTypeCustom];
     _hangupButton.backgroundColor = [UIColor redColor];
-    _hangupButton.layer.cornerRadius = kHangupButtonSize / 2;
+    _hangupButton.layer.cornerRadius = kButtonSize / 2;
     _hangupButton.layer.masksToBounds = YES;
-    UIImage *image = [UIImage imageForName:@"ic_call_end_black_24dp.png"
-                                     color:[UIColor whiteColor]];
+    image = [UIImage imageForName:@"ic_call_end_black_24dp.png"
+                            color:[UIColor whiteColor]];
     [_hangupButton setImage:image forState:UIControlStateNormal];
     [_hangupButton addTarget:self
                       action:@selector(onHangup:)
@@ -104,21 +118,36 @@
     _remoteVideoView.frame = bounds;
   }
 
-  CGRect localVideoFrame = CGRectZero;
-  localVideoFrame.origin.x =
-      CGRectGetMaxX(bounds) - kLocalVideoViewWidth - kLocalVideoViewPadding;
-  localVideoFrame.origin.y =
-      CGRectGetMaxY(bounds) - kLocalVideoViewHeight - kLocalVideoViewPadding;
-  localVideoFrame.size.width = kLocalVideoViewWidth;
-  localVideoFrame.size.height = kLocalVideoViewHeight;
-  _localVideoView.frame = localVideoFrame;
+  if (_localVideoSize.width && _localVideoSize.height > 0) {
+    // Aspect fit local video view into a square box.
+    CGRect localVideoFrame =
+        CGRectMake(0, 0, kLocalVideoViewSize, kLocalVideoViewSize);
+    localVideoFrame =
+        AVMakeRectWithAspectRatioInsideRect(_localVideoSize, localVideoFrame);
 
+    // Place the view in the bottom right.
+    localVideoFrame.origin.x = CGRectGetMaxX(bounds)
+        - localVideoFrame.size.width - kLocalVideoViewPadding;
+    localVideoFrame.origin.y = CGRectGetMaxY(bounds)
+        - localVideoFrame.size.height - kLocalVideoViewPadding;
+    _localVideoView.frame = localVideoFrame;
+  } else {
+    _localVideoView.frame = bounds;
+  }
+
+  // Place hangup button in the bottom left.
   _hangupButton.frame =
-      CGRectMake(CGRectGetMinX(bounds) + kHangupButtonPadding,
-                 CGRectGetMaxY(bounds) - kHangupButtonPadding -
-                     kHangupButtonSize,
-                 kHangupButtonSize,
-                 kHangupButtonSize);
+      CGRectMake(CGRectGetMinX(bounds) + kButtonPadding,
+                 CGRectGetMaxY(bounds) - kButtonPadding -
+                     kButtonSize,
+                 kButtonSize,
+                 kButtonSize);
+
+  // Place button to the right of hangup button.
+  CGRect cameraSwitchFrame = _hangupButton.frame;
+  cameraSwitchFrame.origin.x =
+      CGRectGetMaxX(cameraSwitchFrame) + kButtonPadding;
+  _cameraSwitchButton.frame = cameraSwitchFrame;
 
   [_statusLabel sizeToFit];
   _statusLabel.center =
@@ -130,6 +159,7 @@
 - (void)videoView:(RTCEAGLVideoView*)videoView didChangeVideoSize:(CGSize)size {
   if (videoView == _localVideoView) {
     _localVideoSize = size;
+    _localVideoView.hidden = CGSizeEqualToSize(CGSizeZero, _localVideoSize);
   } else if (videoView == _remoteVideoView) {
     _remoteVideoSize = size;
   }
@@ -138,6 +168,10 @@
 
 #pragma mark - Private
 
+- (void)onCameraSwitch:(id)sender {
+  [_delegate videoCallViewDidSwitchCamera:self];
+}
+
 - (void)onHangup:(id)sender {
   [_delegate videoCallViewDidHangup:self];
 }
diff --git a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m
index af4aaff..b12a61a 100644
--- a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m
+++ b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m
@@ -27,11 +27,15 @@
 
 #import "ARDVideoCallViewController.h"
 
+#import "RTCAVFoundationVideoSource.h"
+
 #import "ARDAppClient.h"
 #import "ARDVideoCallView.h"
 
 @interface ARDVideoCallViewController () <ARDAppClientDelegate,
     ARDVideoCallViewDelegate>
+@property(nonatomic, strong) RTCVideoTrack *localVideoTrack;
+@property(nonatomic, strong) RTCVideoTrack *remoteVideoTrack;
 @property(nonatomic, readonly) ARDVideoCallView *videoCallView;
 @end
 
@@ -90,19 +94,13 @@
 
 - (void)appClient:(ARDAppClient *)client
     didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
-  if (!_localVideoTrack) {
-    _localVideoTrack = localVideoTrack;
-    [_localVideoTrack addRenderer:_videoCallView.localVideoView];
-  }
+  self.localVideoTrack = localVideoTrack;
 }
 
 - (void)appClient:(ARDAppClient *)client
     didReceiveRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack {
-  if (!_remoteVideoTrack) {
-    _remoteVideoTrack = remoteVideoTrack;
-    [_remoteVideoTrack addRenderer:_videoCallView.remoteVideoView];
-    _videoCallView.statusLabel.hidden = YES;
-  }
+  self.remoteVideoTrack = remoteVideoTrack;
+  _videoCallView.statusLabel.hidden = YES;
 }
 
 - (void)appClient:(ARDAppClient *)client
@@ -119,24 +117,54 @@
   [self hangup];
 }
 
+- (void)videoCallViewDidSwitchCamera:(ARDVideoCallView *)view {
+  // TODO(tkchin): Rate limit this so you can't tap continously on it.
+  // Probably through an animation.
+  [self switchCamera];
+}
+
 #pragma mark - Private
 
+- (void)setLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
+  if (_localVideoTrack == localVideoTrack) {
+    return;
+  }
+  [_localVideoTrack removeRenderer:_videoCallView.localVideoView];
+  _localVideoTrack = nil;
+  [_videoCallView.localVideoView renderFrame:nil];
+  _localVideoTrack = localVideoTrack;
+  [_localVideoTrack addRenderer:_videoCallView.localVideoView];
+}
+
+- (void)setRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack {
+  if (_remoteVideoTrack == remoteVideoTrack) {
+    return;
+  }
+  [_remoteVideoTrack removeRenderer:_videoCallView.localVideoView];
+  _remoteVideoTrack = nil;
+  [_videoCallView.remoteVideoView renderFrame:nil];
+  _remoteVideoTrack = remoteVideoTrack;
+  [_remoteVideoTrack addRenderer:_videoCallView.remoteVideoView];
+}
+
 - (void)hangup {
-  if (_remoteVideoTrack) {
-    [_remoteVideoTrack removeRenderer:_videoCallView.remoteVideoView];
-    _remoteVideoTrack = nil;
-    [_videoCallView.remoteVideoView renderFrame:nil];
-  }
-  if (_localVideoTrack) {
-    [_localVideoTrack removeRenderer:_videoCallView.localVideoView];
-    _localVideoTrack = nil;
-    [_videoCallView.localVideoView renderFrame:nil];
-  }
+  self.remoteVideoTrack = nil;
+  self.localVideoTrack = nil;
   [_client disconnect];
   [self.presentingViewController dismissViewControllerAnimated:YES
                                                     completion:nil];
 }
 
+- (void)switchCamera {
+  RTCVideoSource* source = self.localVideoTrack.source;
+  if ([source isKindOfClass:[RTCAVFoundationVideoSource class]]) {
+    RTCAVFoundationVideoSource* avSource = (RTCAVFoundationVideoSource*)source;
+    avSource.useBackCamera = !avSource.useBackCamera;
+    _videoCallView.localVideoView.transform = avSource.useBackCamera ?
+        CGAffineTransformIdentity : CGAffineTransformMakeScale(-1, 1);
+  }
+}
+
 - (NSString *)statusTextForState:(RTCICEConnectionState)state {
   switch (state) {
     case RTCICEConnectionNew:
diff --git a/talk/examples/objc/AppRTCDemo/ios/resources/ic_switch_video_black_24dp.png b/talk/examples/objc/AppRTCDemo/ios/resources/ic_switch_video_black_24dp.png
new file mode 100644
index 0000000..85271c8
--- /dev/null
+++ b/talk/examples/objc/AppRTCDemo/ios/resources/ic_switch_video_black_24dp.png
Binary files differ
diff --git a/talk/examples/objc/AppRTCDemo/ios/resources/ic_switch_video_black_24dp@2x.png b/talk/examples/objc/AppRTCDemo/ios/resources/ic_switch_video_black_24dp@2x.png
new file mode 100644
index 0000000..62b13a6
--- /dev/null
+++ b/talk/examples/objc/AppRTCDemo/ios/resources/ic_switch_video_black_24dp@2x.png
Binary files differ