This app is built using accelerator-core-ios and the following accelerator packs:
Use CocoaPods to install the project files and dependencies.
- Install CocoaPods as described in CocoaPods Getting Started.
- In Terminal,
cd
to your project directory and typepod install
. (Sometimes,pod update
is magical) - Reopen your project in Xcode using the new
*.xcworkspace
file.
Configure the sample app code. Then, build and run the app.
-
The application requires values for API Key, Session ID, and Token. In the sample, you can get these values at the OpenTok Developer Dashboard. For production deployment, you must generate the Session ID and Token values using one of the OpenTok Server SDKs.
-
Replace the following empty strings with the corresponding API Key, Session ID, and Token values:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.acceleratorSession = [[OTAcceleratorSession alloc] initWithOpenTokApiKey:<#apikey#> sessionId:<#sessionid#> token:<#token#>]; return YES; }
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool { session = OTAcceleratorSession.init(openTokApiKey: <#apikey#>, sessionId: <#sessionid#>, token: <#token#>) return true }
-
Use Xcode to build and run the app on an iOS simulator or device.
This section shows you how to prepare, build, and run the sample application. Example code is added in Objective-C and Swift. With the sample application you can:
For details about developing with the SDK and the APIs this sample uses, see the OpenTok iOS SDK Requirements and the OpenTok iOS SDK Reference.
NOTE: This sample app collects anonymous usage data for internal TokBox purposes only. Please do not modify or remove any logging code from this sample application.
###Call
When the call button is pressed OTMultiPartyCommunicator
initiates the connection to the OpenTok session and sets up the listeners for the publisher and subscriber streams:
// start call
[SVProgressHUD show];
__weak MainViewController *weakSelf = self;
[self.multipartyCommunicator connectWithHandler:^(OTCommunicationSignal signal, OTMultiPartyRemote *subscriber, NSError *error) {
if (!error) {
[weakSelf handleCommunicationSignal:signal remote:subscriber];
}
else {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}
}];
// start call
SVProgressHUD.show()
multipartyCommunicator.connect {
[unowned self] (signal, remote, error) in
guard error == nil else {
SVProgressHUD.showError(withStatus: error!.localizedDescription)
return
}
self.handleCommunicationSignal(signal, remote: remote)
}
The remote connection to the subscriber is handled according to the signal obtained:
- (void)handleCommunicationSignal:(OTCommunicationSignal)signal
remote:(OTMultiPartyRemote *)remote {
switch (signal) {
case OTPublisherCreated: { // join a call
[SVProgressHUD popActivity];
self.multipartyCommunicator.publisherView.showAudioVideoControl = NO;
[self.mainView enableControlButtonsForCall:YES];
[self.mainView connectCallHolder:self.multipartyCommunicator.isCallEnabled];
[self.mainView addPublisherView:self.multipartyCommunicator.publisherView];
break;
}
case OTSubscriberCreated: { // one participant is ready to join
[SVProgressHUD show];
}
case OTSubscriberReady: { // one participant joins
[SVProgressHUD popActivity];
if (![self.subscribers containsObject:remote]) {
[self.subscribers addObject:remote];
[self.mainView updateSubscriberViews:self.subscribers
publisherView:self.multipartyCommunicator.publisherView];
}
break;
}
case OTSubscriberDestroyed:{ // one participant leaves
if ([self.subscribers containsObject:remote]) {
[self.subscribers removeObject:remote];
[self.mainView updateSubscriberViews:self.subscribers
publisherView:self.multipartyCommunicator.publisherView];
}
break;
}
...
}
}
fileprivate func handleCommunicationSignal(_ signal: OTCommunicationSignal, remote: OTMultiPartyRemote?) {
switch signal {
case .publisherCreated: // join a call
guard let multipartyCommunicator = multipartyCommunicator else {break}
SVProgressHUD.popActivity()
multipartyCommunicator.publisherView.showAudioVideoControl = false
mainView.enableControlButtonsForCall(enabled: true)
mainView.connectCallHolder(connected: multipartyCommunicator.isCallEnabled)
mainView.addPublisherView(multipartyCommunicator.publisherView)
case .subscriberReady: // one participant joins
SVProgressHUD.popActivity()
if let remote = remote, subscribers.firstIndex(of: remote) == nil {
subscribers.append(remote)
mainView.updateSubscriberViews(subscribers, publisherView: multipartyCommunicator?.publisherView)
}
case .subscriberDestroyed: // one participant leaves
if let remote = remote, let index = subscribers.firstIndex(of: remote) {
subscribers.remove(at: index)
mainView.updateSubscriberViews(subscribers, publisherView: multipartyCommunicator?.publisherView)
}
...
}
}
The TextCHat feature is built using accelerator-textchat-ios. When the text message button is pressed the view changes to present the chat UI:
- (IBAction)textMessageButtonPressed:(id)sender {
[self presentViewController:[[TextChatTableViewController alloc] init] animated:YES completion:nil]; //When the text message button is pressed the view changes to present the chat UI
}
The textchat logic and UI is pre-configured, you can also change properties like textChatNavigationBar.topItem.title
and alias
in TextChatTableViewController
:
- (void)viewDidLoad {
[super viewDidLoad];
self.textChat = [[OTTextChat alloc] init];
self.textChat.dataSource = self;
self.textChat.alias = @"Tokboxer";
self.textMessages = [[NSMutableArray alloc] init];
self.textChatNavigationBar.topItem.title = self.textChat.alias;
self.tableView.textChatTableViewDelegate = self;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.textChatInputView.textField.delegate = self;
__weak TextChatTableViewController *weakSelf = self;
[self.textChat connectWithHandler:^(OTTextChatConnectionEventSignal signal, OTConnection *connection, NSError *error) {
if (signal == OTTextChatConnectionEventSignalDidConnect) {
NSLog(@"Text Chat starts");
}
else if (signal == OTTextChatConnectionEventSignalDidDisconnect) {
NSLog(@"Text Chat stops");
}
} messageHandler:^(OTTextChatMessageEventSignal signal, OTTextMessage *message, NSError *error) {
if (signal == OTTextChatMessageEventSignalDidSendMessage || signal == OTTextChatMessageEventSignalDidReceiveMessage) {
if (!error) {
[weakSelf.textMessages addObject:message];
[weakSelf.tableView reloadData];
weakSelf.textChatInputView.textField.text = nil;
[weakSelf scrollTextChatTableViewToBottom];
}
}
}];
[self.textChatInputView.sendButton addTarget:self action:@selector(sendTextMessage) forControlEvents:UIControlEventTouchUpInside];
}
override func viewDidLoad() {
super.viewDidLoad()
textChat = OTTextChat()
textChat?.dataSource = self
textChat?.alias = "Toxboxer"
textChatNavigationBar.topItem?.title = textChat?.alias
tableView.textChatTableViewDelegate = self
tableView.separatorStyle = .none
textChatInputView.textField.delegate = self
textChat?.connect(handler: { (signal, connection, error) in
guard error == nil else {
SVProgressHUD.showError(withStatus: error!.localizedDescription)
return
}
if signal == .didConnect {
print("Text Chat starts")
}
else if signal == .didDisconnect {
print("Text Chat stops")
}
}) { [unowned self](signal, message, error) in
guard error == nil, let message = message else {
SVProgressHUD.showError(withStatus: error!.localizedDescription)
return
}
self.textMessages.append(message)
self.tableView.reloadData()
self.textChatInputView.textField.text = nil
self.scrollTextChatTableViewToBottom()
}
textChatInputView.sendButton.addTarget(self, action: #selector(sendTextMessage), for: .touchUpInside)
}
The screen share features shares images from your camera roll using the ScreenShareViewController
class which publishes the content.
- (void)startScreenSharing {
self.multipartyScreenSharer = [[OTMultiPartyCommunicator alloc] initWithView:self.annotationView];
self.multipartyScreenSharer.dataSource = self;
// publishOnly here is to avoid subscripting to those who already subscribed
self.multipartyScreenSharer.publishOnly = YES;
__weak ScreenShareViewController *weakSelf = self;
[self.multipartyScreenSharer connectWithHandler:^(OTCommunicationSignal signal, OTMultiPartyRemote *subscriber, NSError *error) {
if (error) {
[weakSelf dismissViewControllerAnimated:YES completion:^(){
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}];
return;
}
if (signal == OTPublisherCreated) {
weakSelf.multipartyScreenSharer.publishAudio = NO;
[weakSelf startAnnotation];
}
}];
}
fileprivate func startScreenSharing() {
multipartyScreenSharer = OTMultiPartyCommunicator.init(view: annotationView)
multipartyScreenSharer?.dataSource = self
// publishOnly here is to avoid subscripting to those who already subscribed
multipartyScreenSharer?.isPublishOnly = true
multipartyScreenSharer?.connect {
[unowned self](signal, remote, error) in
guard error == nil else {
self.dismiss(animated: true) {
SVProgressHUD.showError(withStatus: error!.localizedDescription)
}
return
}
if signal == .publisherCreated {
self.multipartyScreenSharer?.isPublishAudio = false
self.startAnnotation()
}
}
}
The ScreenShareViewController
class also handles local annotation:
The beta version is unstable when it comes to work with cross-platform, it's much stable if two canvas has same aspect ratio.
- (void)startAnnotation {
self.annotator = [[OTAnnotator alloc] init];
self.annotator.dataSource = self;
__weak ScreenShareViewController *weakSelf = self;
[self.annotator connectWithCompletionHandler:^(OTAnnotationSignal signal, NSError *error) {
if (error) {
[weakSelf dismissViewControllerAnimated:YES completion:^(){
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}];
return;
}
if (signal == OTAnnotationSessionDidConnect) {
// using frame and self.view to contain toolbarView is for having more space to interact with color picker
[weakSelf.annotator.annotationScrollView initializeToolbarView];
weakSelf.annotator.annotationScrollView.toolbarView.toolbarViewDataSource = self;
weakSelf.annotator.annotationScrollView.toolbarView.frame = weakSelf.annotationToolbarView.frame;
[weakSelf.view addSubview:weakSelf.annotator.annotationScrollView.toolbarView];
weakSelf.annotator.annotationScrollView.frame = weakSelf.annotationView.bounds;
weakSelf.annotator.annotationScrollView.scrollView.contentSize = CGSizeMake(CGRectGetWidth(weakSelf.annotator.annotationScrollView.bounds), CGRectGetHeight(weakSelf.annotator.annotationScrollView.bounds));
[weakSelf.annotationView addSubview:weakSelf.annotator.annotationScrollView];
weakSelf.annotator.annotationScrollView.annotatable = NO;
}
}];
}
fileprivate func startAnnotation() {
annotator = OTAnnotator()
annotator?.dataSource = self
annotator?.connect {
[unowned self] (signal, error) in
guard error == nil else {
self.dismiss(animated: true) {
SVProgressHUD.showError(withStatus: error!.localizedDescription)
}
return
}
if signal == .sessionDidConnect {
guard let annotator = self.annotator,
let toolbarView = annotator.annotationScrollView.toolbarView else {
print("Error on launching annotation")
return
}
// using frame and self.view to contain toolbarView is for having more space to interact with color picker
self.annotator?.annotationScrollView.initializeToolbarView()
toolbarView.toolbarViewDataSource = self
toolbarView.frame = self.annotationToolbarView.frame
self.view.addSubview(toolbarView)
annotator.annotationScrollView.frame = self.annotationView.bounds;
annotator.annotationScrollView.scrollView.contentSize = CGSize(width: CGFloat(annotator.annotationScrollView.bounds.width), height: CGFloat(annotator.annotationScrollView.bounds.height))
self.annotationView.addSubview(annotator.annotationScrollView)
annotator.annotationScrollView.isAnnotatable = false
}
}
}
Interested in contributing? We ❤️ pull requests! See the Contribution guidelines.
We love to hear from you so if you have questions, comments or find a bug in the project, let us know! You can either:
- Open an issue on this repository
- See https://support.tokbox.com/ for support options
- Tweet at us! We're @VonageDev on Twitter
- Or join the Vonage Developer Community Slack
- Check out the Developer Documentation at https://tokbox.com/developer/