Add 'audio-only' calls feature.

Signed-off-by: Ivan Sein <ivan@nextcloud.com>
This commit is contained in:
Ivan Sein 2018-03-20 11:46:50 +01:00
Родитель 86a3f38db0
Коммит 998313fb8d
14 изменённых файлов: 183 добавлений и 66 удалений

Просмотреть файл

@ -28,6 +28,6 @@
@property (nonatomic, strong) IBOutlet UIImageView *waitingImageView;
@property (nonatomic, strong) IBOutlet UILabel *waitingLabel;
- (instancetype)initCallInRoom:(NCRoom *)room asUser:(NSString*)displayName;
- (instancetype)initCallInRoom:(NCRoom *)room asUser:(NSString*)displayName audioOnly:(BOOL)audioOnly;
@end

Просмотреть файл

@ -34,6 +34,7 @@ typedef NS_ENUM(NSInteger, CallState) {
NCCallController *_callController;
ARDCaptureController *_captureController;
NSTimer *_detailedViewTimer;
BOOL _isAudioOnly;
BOOL _userDisabledVideo;
}
@ -51,17 +52,18 @@ typedef NS_ENUM(NSInteger, CallState) {
@synthesize delegate = _delegate;
- (instancetype)initCallInRoom:(NCRoom *)room asUser:(NSString*)displayName
- (instancetype)initCallInRoom:(NCRoom *)room asUser:(NSString*)displayName audioOnly:(BOOL)audioOnly
{
self = [super init];
if (!self) {
return nil;
}
_callController = [[NCCallController alloc] initWithDelegate:self];
_callController = [[NCCallController alloc] initWithDelegate:self forAudioOnlyCall:audioOnly];
_callController.room = room;
_callController.userDisplayName = displayName;
_room = room;
_isAudioOnly = audioOnly;
_peersInCall = [[NSMutableArray alloc] init];
_renderersDict = [[NSMutableDictionary alloc] init];
@ -94,7 +96,7 @@ typedef NS_ENUM(NSInteger, CallState) {
[self setWaitingScreen];
if ([[[NCSettingsController sharedInstance] videoSettingsModel] videoDisabledSettingFromStore]) {
if ([[[NCSettingsController sharedInstance] videoSettingsModel] videoDisabledSettingFromStore] || _isAudioOnly) {
_userDisabledVideo = YES;
[self disableLocalVideo];
}
@ -342,7 +344,7 @@ typedef NS_ENUM(NSInteger, CallState) {
[cell setUserAvatar:[_callController getUserIdFromSessionId:peerConnection.peerId]];
[cell setDisplayName:peerConnection.peerName];
[cell setAudioDisabled:peerConnection.isRemoteAudioDisabled];
[cell setVideoDisabled:peerConnection.isRemoteVideoDisabled];
[cell setVideoDisabled: (_isAudioOnly) ? YES : peerConnection.isRemoteVideoDisabled];
return cell;
}
@ -430,9 +432,11 @@ typedef NS_ENUM(NSInteger, CallState) {
[cell setAudioDisabled:peer.isRemoteAudioDisabled];
}];
} else if ([message isEqualToString:@"videoOn"] || [message isEqualToString:@"videoOff"]) {
[self updatePeer:peer block:^(CallParticipantViewCell *cell) {
[cell setVideoDisabled:peer.isRemoteVideoDisabled];
}];
if (!_isAudioOnly) {
[self updatePeer:peer block:^(CallParticipantViewCell *cell) {
[cell setVideoDisabled:peer.isRemoteVideoDisabled];
}];
}
}
}
- (void)callController:(NCCallController *)callController didReceiveNick:(NSString *)nick fromPeer:(NCPeerConnection *)peer

Просмотреть файл

@ -8,6 +8,9 @@
#import <UIKit/UIKit.h>
extern NSString * const NCSelectedContactForVoiceCallNotification;
extern NSString * const NCSelectedContactForVideoCallNotification;
@interface ContactsTableViewController : UITableViewController
@end

Просмотреть файл

@ -17,6 +17,9 @@
#import "UIImageView+Letters.h"
#import "UIImageView+AFNetworking.h"
NSString * const NCSelectedContactForVoiceCallNotification = @"NCSelectedContactForVoiceCallNotification";
NSString * const NCSelectedContactForVideoCallNotification = @"NCSelectedContactForVideoCallNotification";
@interface ContactsTableViewController () <UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating>
{
NSMutableArray *_contacts;
@ -162,6 +165,62 @@
}];
}
- (void)presentJoinCallOptionsForContactAtIndexPath:(NSIndexPath *)indexPath
{
NCUser *contact = [_contacts objectAtIndex:indexPath.row];
if (_searchController.active) {
contact = [_resultTableViewController.filteredContacts objectAtIndex:indexPath.row];
}
UIAlertController *optionsActionSheet =
[UIAlertController alertControllerWithTitle:contact.name
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[optionsActionSheet addAction:[UIAlertAction actionWithTitle:@"Call"
style:UIAlertActionStyleDefault
handler:^void (UIAlertAction *action) {
[self createCallWithContact:contact audioOnly:YES];
}]];
[optionsActionSheet addAction:[UIAlertAction actionWithTitle:@"Videocall"
style:UIAlertActionStyleDefault
handler:^void (UIAlertAction *action) {
[self createCallWithContact:contact audioOnly:NO];
}]];
[optionsActionSheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
// Presentation on iPads
optionsActionSheet.popoverPresentationController.sourceView = self.tableView;
optionsActionSheet.popoverPresentationController.sourceRect = [self.tableView rectForRowAtIndexPath:indexPath];
[self presentViewController:optionsActionSheet animated:YES completion:nil];
}
- (void)createCallWithContact:(NCUser *)contact audioOnly:(BOOL)audioOnly
{
[[NCAPIController sharedInstance] createRoomWith:contact.userId
ofType:kNCRoomTypeOneToOneCall
andName:nil
withCompletionBlock:^(NSString *token, NSError *error) {
if (!error) {
NSLog(@"Room %@ with %@ created", token, contact.name);
if (audioOnly) {
[[NSNotificationCenter defaultCenter] postNotificationName:NCSelectedContactForVoiceCallNotification
object:self
userInfo:@{@"token":token}];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:NCSelectedContactForVideoCallNotification
object:self
userInfo:@{@"token":token}];
}
} else {
NSLog(@"Failed creating a room with %@", contact.name);
}
}];
}
#pragma mark - Search controller
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
@ -213,25 +272,7 @@
if ([NCConnectionController sharedInstance].connectionState == kConnectionStateDisconnected) {
[[NCUserInterfaceController sharedInstance] presentOfflineWarningAlert];
} else {
NCUser *contact = [_contacts objectAtIndex:indexPath.row];
if (_searchController.active) {
contact = [_resultTableViewController.filteredContacts objectAtIndex:indexPath.row];
}
[[NCAPIController sharedInstance] createRoomWith:contact.userId
ofType:kNCRoomTypeOneToOneCall
andName:nil
withCompletionBlock:^(NSString *token, NSError *error) {
if (!error) {
// Join created room.
NSLog(@"Room %@ with %@ created", token, contact.name);
[[NSNotificationCenter defaultCenter] postNotificationName:NCRoomCreatedNotification
object:self
userInfo:@{@"token":token}];
} else {
NSLog(@"Failed creating a room with %@", contact.name);
}
}];
[self presentJoinCallOptionsForContactAtIndexPath:indexPath];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];

Просмотреть файл

@ -45,8 +45,6 @@ typedef void (^UnsubscribeToNextcloudServerCompletionBlock)(NSError *error);
typedef void (^SubscribeToPushProxyCompletionBlock)(NSError *error);
typedef void (^UnsubscribeToPushProxyCompletionBlock)(NSError *error);
extern NSString * const NCRoomCreatedNotification;
@interface NCAPIController : NSObject

Просмотреть файл

@ -15,8 +15,6 @@
NSString * const kNCOCSAPIVersion = @"/ocs/v2.php";
NSString * const kNCSpreedAPIVersion = @"/apps/spreed/api/v1";
NSString * const NCRoomCreatedNotification = @"NCRoomCreatedNotification";
@interface NCAPIController () <NSURLSessionTaskDelegate, NSURLSessionDelegate>
{
NSString *_serverUrl;

Просмотреть файл

@ -44,7 +44,7 @@ typedef void (^GetUserIdForSessionIdCompletionBlock)(NSString *userId, NSError *
@property (nonatomic, strong) NSMutableArray *renderers;
- (instancetype)initWithDelegate:(id<NCCallControllerDelegate>)delegate;
- (instancetype)initWithDelegate:(id<NCCallControllerDelegate>)delegate forAudioOnlyCall:(BOOL)audioOnly;
- (void)startCall;
- (void)leaveCall;
- (void)toggleCamera;

Просмотреть файл

@ -29,6 +29,7 @@ static NSString * const kNCVideoTrackKind = @"video";
@interface NCCallController () <NCPeerConnectionDelegate, NCSignalingControllerObserver>
@property (nonatomic, assign) BOOL isAudioOnly;
@property (nonatomic, assign) BOOL leavingCall;
@property (nonatomic, strong) NSTimer *pingTimer;
@property (nonatomic, strong) AVAudioRecorder *recorder;
@ -46,12 +47,13 @@ static NSString * const kNCVideoTrackKind = @"video";
@implementation NCCallController
- (instancetype)initWithDelegate:(id<NCCallControllerDelegate>)delegate
- (instancetype)initWithDelegate:(id<NCCallControllerDelegate>)delegate forAudioOnlyCall:(BOOL)audioOnly
{
self = [super init];
if (self) {
_delegate = delegate;
_isAudioOnly = audioOnly;
_peerConnectionFactory = [[RTCPeerConnectionFactory alloc] init];
_connectionsDict = [[NSMutableDictionary alloc] init];
_usersInRoom = [[NSArray alloc] init];
@ -285,7 +287,9 @@ static NSString * const kNCVideoTrackKind = @"video";
RTCMediaStream *localMediaStream = [_peerConnectionFactory mediaStreamWithStreamId:kNCMediaStreamId];
self.localStream = localMediaStream;
[self createLocalAudioTrack];
[self createLocalVideoTrack];
if (!_isAudioOnly) {
[self createLocalVideoTrack];
}
}
#pragma mark - Audio session configuration
@ -334,7 +338,7 @@ static NSString * const kNCVideoTrackKind = @"video";
NSLog(@"Creating a peer for %@", sessionId);
NSArray *iceServers = [_signalingController getIceServers];
peerConnectionWrapper = [[NCPeerConnection alloc] initWithSessionId:sessionId andICEServers:iceServers];
peerConnectionWrapper = [[NCPeerConnection alloc] initWithSessionId:sessionId andICEServers:iceServers forAudioOnlyCall:_isAudioOnly];
peerConnectionWrapper.delegate = self;
// TODO: Try to get display name here
[peerConnectionWrapper.peerConnection addStream:_localStream];
@ -542,7 +546,7 @@ static NSString * const kNCVideoTrackKind = @"video";
}
// Send current video state
if (self.isVideoEnabled) {
if (self.isVideoEnabled && !_isAudioOnly) {
NSLog(@"Send videoOn");
[peerConnection sendDataChannelMessageOfType:@"videoOn" withPayload:nil];
} else {

Просмотреть файл

@ -46,6 +46,7 @@
@property (nonatomic, copy) NSString *peerId;
@property (nonatomic, copy) NSString *peerName;
@property (nonatomic, assign) BOOL isAudioOnly;
@property (nonatomic, strong) RTCPeerConnection *peerConnection;
@property (nonatomic, strong) RTCDataChannel *localDataChannel;
@property (nonatomic, strong) RTCDataChannel *remoteDataChannel;
@ -54,7 +55,7 @@
@property (nonatomic, strong, readonly) NSMutableArray *queuedRemoteCandidates;
@property (nonatomic, strong) RTCMediaStream *remoteStream;
- (instancetype)initWithSessionId:(NSString *)sessionId andICEServers:(NSArray *)iceServers;
- (instancetype)initWithSessionId:(NSString *)sessionId andICEServers:(NSArray *)iceServers forAudioOnlyCall:(BOOL)audioOnly;
- (void)addICECandidate:(RTCIceCandidate *)candidate;
- (void)setRemoteDescription:(RTCSessionDescription *)sessionDescription;
- (void)sendOffer;

Просмотреть файл

@ -27,7 +27,7 @@
@implementation NCPeerConnection
- (instancetype)initWithSessionId:(NSString *)sessionId andICEServers:(NSArray *)iceServers
- (instancetype)initWithSessionId:(NSString *)sessionId andICEServers:(NSArray *)iceServers forAudioOnlyCall:(BOOL)audioOnly
{
self = [super init];
@ -47,6 +47,7 @@
_peerConnection = peerConnection;
_peerId = sessionId;
_isAudioOnly = audioOnly;
}
return self;
@ -376,6 +377,13 @@
@"OfferToReceiveVideo" : @"true"
};
if (_isAudioOnly) {
mandatoryConstraints = @{
@"OfferToReceiveAudio" : @"true",
@"OfferToReceiveVideo" : @"false"
};
}
NSDictionary *optionalConstraints = @{
@"internalSctpDataChannels": @"true",
@"DtlsSrtpKeyAgreement": @"true"

Просмотреть файл

@ -23,7 +23,8 @@ extern NSString * const kNCPNTypeCallKey;
extern NSString * const kNCPNTypeRoomKey;
extern NSString * const kNCPNTypeChatKey;
extern NSString * const NCPushNotificationJoinCallAcceptedNotification;
extern NSString * const NCPushNotificationJoinAudioCallAcceptedNotification;
extern NSString * const NCPushNotificationJoinVideoCallAcceptedNotification;
@interface NCPushNotification : NSObject

Просмотреть файл

@ -19,7 +19,8 @@ NSString * const kNCPNTypeCallKey = @"call";
NSString * const kNCPNTypeRoomKey = @"room";
NSString * const kNCPNTypeChatKey = @"chat";
NSString * const NCPushNotificationJoinCallAcceptedNotification = @"NCPushNotificationJoinCallAcceptedNotification";
NSString * const NCPushNotificationJoinAudioCallAcceptedNotification = @"NCPushNotificationJoinAudioCallAcceptedNotification";
NSString * const NCPushNotificationJoinVideoCallAcceptedNotification = @"NCPushNotificationJoinVideoCallAcceptedNotification";
+ (instancetype)pushNotificationFromDecryptedString:(NSString *)decryptedString

Просмотреть файл

@ -106,22 +106,33 @@
message:@"Do you want to join this call?"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *joinButton = [UIAlertAction
actionWithTitle:@"Join call"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:pushNotification forKey:@"pushNotification"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCPushNotificationJoinCallAcceptedNotification
object:self
userInfo:userInfo];
}];
UIAlertAction *joinAudioButton = [UIAlertAction
actionWithTitle:@"Join call (audio only)"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:pushNotification forKey:@"pushNotification"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCPushNotificationJoinAudioCallAcceptedNotification
object:self
userInfo:userInfo];
}];
UIAlertAction *joinVideoButton = [UIAlertAction
actionWithTitle:@"Join call with video"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:pushNotification forKey:@"pushNotification"];
[[NSNotificationCenter defaultCenter] postNotificationName:NCPushNotificationJoinVideoCallAcceptedNotification
object:self
userInfo:userInfo];
}];
UIAlertAction* cancelButton = [UIAlertAction
actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
[alert addAction:joinButton];
[alert addAction:joinAudioButton];
[alert addAction:joinVideoButton];
[alert addAction:cancelButton];
// Do not show join call dialog until we don't handle 'hangup current call'/'join new one' properly.

Просмотреть файл

@ -10,6 +10,7 @@
#import "AFNetworking.h"
#import "CallViewController.h"
#import "ContactsTableViewController.h"
#import "AddParticipantsTableViewController.h"
#import "RoomTableViewCell.h"
#import "CCCertificate.h"
@ -58,10 +59,12 @@ typedef void (^FetchRoomsCompletionBlock)(BOOL success);
self.tabBarController.tabBar.tintColor = [UIColor colorWithRed:0.00 green:0.51 blue:0.79 alpha:1.0]; //#0082C9
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(serverCapabilitiesReceived:) name:NCServerCapabilitiesReceivedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinCallAccepted:) name:NCPushNotificationJoinCallAcceptedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinAudioCallAccepted:) name:NCPushNotificationJoinAudioCallAcceptedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(joinVideoCallAccepted:) name:NCPushNotificationJoinVideoCallAcceptedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appStateHasChanged:) name:NCAppStateHasChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionStateHasChanged:) name:NCConnectionStateHasChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(roomHasBeenCreated:) name:NCRoomCreatedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userSelectedContactForVoiceCall:) name:NCSelectedContactForVoiceCallNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userSelectedContactForVideoCall:) name:NCSelectedContactForVideoCallNotification object:nil];
}
- (void)dealloc
@ -95,10 +98,16 @@ typedef void (^FetchRoomsCompletionBlock)(BOOL success);
}
}
- (void)joinCallAccepted:(NSNotification *)notification
- (void)joinAudioCallAccepted:(NSNotification *)notification
{
NCPushNotification *pushNotification = [notification.userInfo objectForKey:@"pushNotification"];
[self joinCallWithCallId:pushNotification.pnId];
[self joinCallWithCallId:pushNotification.pnId audioOnly:YES];
}
- (void)joinVideoCallAccepted:(NSNotification *)notification
{
NCPushNotification *pushNotification = [notification.userInfo objectForKey:@"pushNotification"];
[self joinCallWithCallId:pushNotification.pnId audioOnly:NO];
}
- (void)appStateHasChanged:(NSNotification *)notification
@ -113,10 +122,16 @@ typedef void (^FetchRoomsCompletionBlock)(BOOL success);
[self adaptInterfaceForConnectionState:connectionState];
}
- (void)roomHasBeenCreated:(NSNotification *)notification
- (void)userSelectedContactForVoiceCall:(NSNotification *)notification
{
NSString *roomToken = [notification.userInfo objectForKey:@"token"];
[self joinCallWithCallToken:roomToken];
[self joinCallWithCallToken:roomToken audioOnly:YES];
}
- (void)userSelectedContactForVideoCall:(NSNotification *)notification
{
NSString *roomToken = [notification.userInfo objectForKey:@"token"];
[self joinCallWithCallToken:roomToken audioOnly:NO];
}
#pragma mark - Interface Builder Actions
@ -477,6 +492,39 @@ typedef void (^FetchRoomsCompletionBlock)(BOOL success);
}];
}
- (void)presentJoinCallOptionsForRoomAtIndexPath:(NSIndexPath *)indexPath
{
NCRoom *room = [_rooms objectAtIndex:indexPath.row];
UIAlertController *optionsActionSheet =
[UIAlertController alertControllerWithTitle:room.displayName
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *callAction = [UIAlertAction actionWithTitle:@"Call"
style:UIAlertActionStyleDefault
handler:^void (UIAlertAction *action) {
[self startCallInRoom:room audioOnly:YES];
}];
UIAlertAction *videocallAction = [UIAlertAction actionWithTitle:@"Videocall"
style:UIAlertActionStyleDefault
handler:^void (UIAlertAction *action) {
[self startCallInRoom:room audioOnly:NO];
}];
[optionsActionSheet addAction:callAction];
[optionsActionSheet addAction:videocallAction];
[optionsActionSheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
// Presentation on iPads
optionsActionSheet.popoverPresentationController.sourceView = self.tableView;
optionsActionSheet.popoverPresentationController.sourceRect = [self.tableView rectForRowAtIndexPath:indexPath];
[self presentViewController:optionsActionSheet animated:YES completion:nil];
}
#pragma mark - Public Calls
- (void)startRoomCreationFlowForPublicRoom:(BOOL)public
@ -550,37 +598,37 @@ typedef void (^FetchRoomsCompletionBlock)(BOOL success);
return room;
}
- (void)startCallInRoom:(NCRoom *)room
- (void)startCallInRoom:(NCRoom *)room audioOnly:(BOOL)audioOnly
{
CallViewController *callVC = [[CallViewController alloc] initCallInRoom:room asUser:[[NCSettingsController sharedInstance] ncUserDisplayName]];
CallViewController *callVC = [[CallViewController alloc] initCallInRoom:room asUser:[[NCSettingsController sharedInstance] ncUserDisplayName] audioOnly:audioOnly];
[[NCUserInterfaceController sharedInstance] presentCallViewController:callVC];
}
- (void)joinCallWithCallId:(NSInteger)callId
- (void)joinCallWithCallId:(NSInteger)callId audioOnly:(BOOL)audioOnly
{
NCRoom *room = [self getRoomForId:callId];
if (room) {
[self startCallInRoom:room];
[self startCallInRoom:room audioOnly:audioOnly];
} else {
//TODO: Show spinner?
[[NCAPIController sharedInstance] getRoomWithId:callId withCompletionBlock:^(NCRoom *room, NSError *error) {
if (!error) {
[self startCallInRoom:room];
[self startCallInRoom:room audioOnly:audioOnly];
}
}];
}
}
- (void)joinCallWithCallToken:(NSString *)token
- (void)joinCallWithCallToken:(NSString *)token audioOnly:(BOOL)audioOnly
{
NCRoom *room = [self getRoomForToken:token];
if (room) {
[self startCallInRoom:room];
[self startCallInRoom:room audioOnly:audioOnly];
} else {
//TODO: Show spinner?
[[NCAPIController sharedInstance] getRoomWithToken:token withCompletionBlock:^(NCRoom *room, NSError *error) {
if (!error) {
[self startCallInRoom:room];
[self startCallInRoom:room audioOnly:audioOnly];
}
}];
}
@ -783,8 +831,7 @@ typedef void (^FetchRoomsCompletionBlock)(BOOL success);
if ([NCConnectionController sharedInstance].connectionState == kConnectionStateDisconnected) {
[[NCUserInterfaceController sharedInstance] presentOfflineWarningAlert];
} else {
NCRoom *room = [_rooms objectAtIndex:indexPath.row];
[self startCallInRoom:room];
[self presentJoinCallOptionsForRoomAtIndexPath:indexPath];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}