This tutorial shows how to use the Appcelerator Titanium platform on iOS to connect to an OpenTok session, publish a video stream, and subscribe to a video stream.
Download the completed App | View code on GitHub
Titanium Hello World Tutorial from TokBox, Inc. on Vimeo.
Once you've opened Titanium Studio, click 'Create Project' in the App Explorer view on the left side. In the dialog that appears, select 'Titanium Mobile Project' and hit the Next button.
In the Project Name field, type 'HelloWorld' (without the quotes). In the 'App Id' field, type a new identifier in the form of a reverse URL (eg. com.example.helloworld). In the 'Deployment Targets' section, make sure that only iPhone and/or iPad are selected. This App will not work in Android, Mobile Web, or Blackberry. Uncheck the 'Automatically cloud-enable this application' box and hit the Next button.
In the Available Templates, select the Single Window Application and then press the Finish button.
You begin using OpenTok by installing the module into your Titanium SDK directory. The latest version can be downloaded from the Appcelerator Marketplace, or directly from the project on GitHub.
Once you have the zip file, you can install it by moving it into the Titanium SDK directory. The directory is typically located at ~/Library/Application Support/Titanium.
On the first build of your project, Titanium Studio will unpack the zip file to install the module.
Now all we have to do is create a reference to the module inside the HelloWorld project. Back in Titanium Studio, double-click on tiapp.xml in the App Explorer to open the file.
At the bottom of the editor, click the tab labeled tiapp.xml (next to Overview). Remove the line that contains <modules/> and replace it with the following:
<modules>
<module version="0.2.1">com.tokbox.ti.opentok</module>
</modules>
<ios>
<plist>
<dict>
<key>OTAssetBundlePath</key>
<string>modules/com.tokbox.ti.opentok</string>
</dict>
</plist>
</ios>
Now the HelloWorld project will be able to find and include the module when it is compiled.
The template chosed during the creation of the project gives us a few files to begin with. By default the first view placed on screen is called FirstView. We will replace that with our own view called HelloView.
Inside the App Explorer, navigate to the file Resources/ui/handheld/ApplicationWindow.js and double-click to open it. We will change the ApplicationWindow function to create an HelloView instead of a FirstView.
//Application Window Component Constructor
function ApplicationWindow() {
//load component dependencies
var HelloView = require('ui/common/HelloView');
//create component instance
var self = Ti.UI.createWindow({
backgroundColor:'#ffffff'
});
//construct UI
var helloView = new HelloView();
self.add(helloView);
return self;
}
Now create a new file inside Resources/ui/common called HelloView.js. Start the file by filling in the following code:
var self;
// HelloView Component Constructor
function HelloView() {
// create object instance, a parasitic subclass of Observable
self = Ti.UI.createView({
height : 'auto',
width : 'auto'
});
// create labels
self.publisherLabel = Ti.UI.createLabel({
top : 20,
text : 'Publisher'
});
self.subscriberLabel = Ti.UI.createLabel({
top : 20,
text : 'Subscriber'
});
// show connecting spinner
self.connectingSpinner = Ti.UI.createActivityIndicator({
color: 'white',
message: 'Connecting...',
style: Titanium.UI.iPhone.ActivityIndicatorStyle.BIG,
height: 200,
width: 200,
backgroundColor : 'black',
borderRadius: 10
});
self.add(self.connectingSpinner);
self.connectingSpinner.show();
return self;
}
module.exports = HelloView;
This is the constructor of HelloView. In it we create a generic View by calling Ti.UI.createView() with an object that initializes the width and height of the view to 'auto'. This allows the view to take up the whole screen. Next we initialized a few labels by calling Ti.UI.createLabel() again with an object to initialize their properties. For each of these labels, we save a reference to them by attaching them to self. Finally we create an ActivityIndicator by calling Ti.UI.createActivityIndicator() with initialization properties. This will be shown on the screen while we are waiting to connect to OpenTok. Since this is the first thing we will be doing, we add this to the view by calling self.add(self.connectingSpinner) right away, and then also call the show() method on the ActivityIndicator.
If you haven't already done so, please get an OpenTok API Key now. Once you receive a confirmation email, you can use the Dashboard page to create a session ID and generate a token.
We will now create a new file inResources called config.js. Place the following code inside config.js and fill in the placeholders with your created API Key, Session ID and Token:
var CONFIG = {
apiKey : '{API_KEY}',
token : '{TOKEN}',
sessionId : '{YOUR_SESSION_ID}'
}
module.exports = CONFIG
Now this file can be included into HelloView by placing this one line at the top of HelloView.js:
var CONFIG = require('config');
Now that we have this information, we can create a Session object and use it to connect. The Session object is created from the module. Place the following code inside the HelloView constructor right before the return statement:
function HelloView() {
...
// initialize OpenTok state
self.opentok = require('com.tokbox.ti.opentok');
self.session = self.opentok.createSession({ sessionId : CONFIG.sessionId });
self.session.connect(CONFIG.apiKey, CONFIG.token);
return self;
}
First we got a reference to the opentok module and saved it to the variable self.opentok by using the require() function again. The module has a createSession function which takes an object with initialization properties. In this case we are setting its sessionId property to the Session ID we saved in config.js. We save this Session object in the variable self.session. Lastly, we call the connect() method of the Session object and pass it the apiKey and token stored in config.js.
In order to find out when a connection has been made, we need to listen for an event that the Session object will fire. To do this, we add a line right before we call the connect method.
self.session.addEventListener("sessionConnected", sessionConnectedHandler);
Now, once the session has connected, another function called sessionConnectedHandler is called. Place this method below the HelloView constructor in HelloView.js
function sessionConnectedHandler(event) {
// Dismiss spinner
self.connectingSpinner.hide();
self.remove(self.connectingSpinner);
}
Now you can run the project in the Simulator by pressing the button with the arrow in a green circle in the App Explorer and choosing iPhone Simulator from the dropdown menu. You should be able to see the "Connecting..." view disappear after a few seconds
Publishing requires a camera, and since the Simulator does not have access to your Mac's camera, it will require putting the App on an iOS device. This means you must have signed up as a Developer in Apple's iOS Developer Program and signed into your account in Xcode.
We start by placing some code in the constructor that helps identify when we are on an actual device. Place the following line inside the HelloView constructor:
// publishing only works from device: let's find out where we are self.onDevice = (Ti.Platform.architecture === 'arm');
Now we have saved a Boolean value on self.onDevice so we can test this value in our code in other functions. Let's start publishing! Place the following code after the existing code in the sessionConnectedHandler() function:
self.layout = 'vertical';
// Start publishing from my camera
if (self.onDevice) {
self.publisher = self.session.publish();
self.publisherView = self.publisher.createView({
width : 200,
height : 150,
top : 20
});
self.add(self.publisherLabel);
self.add(self.publisherView);
}
This code first sets up HelloView to lay out multiple views vertically. Then it creates a Publisher object by calling the publish() method of the Session object. Next we create a view for this publisher by calling createView() on that Publisher object which takes some initialization properties. The last two lines just place the label we created in the constructor and this PublisherView onto the HelloView.
We also want to make sure we clean up any of these views if we are no longer connected to the session. To do this we first add a new event handler in the HelloView constructor. Place this code before the call to connect():
self.session.addEventListener("sessionDisconnected", sessionDisconnectedHandler);
Next we write the sessionDisconnectedHandler() function:
function sessionDisconnectedHandler(event) {
// Remove publisher and its label
self.remove(self.publisherLabel);
self.remove(self.publisherView);
}
This code just removes the PublisherView and the Label for it from HelloView. Now you can run the code once again to see yourself publish. Click the Run button in the App Explorer and select iOS Device. Select the appropriate settings in the Dialog including your developer certificate, you Provisioning Profile (you can use the Team Provisioning Profile), and an SDK Version of 5.1.
You may see an error in your console on the first attempt to run the App on the device. It is because Titanium by default attempts to compile for armv6
arhitecture. The quickest way to launch the App on the device is to open Finder and to navigate to the build/iphone directory inside the
project directory and open HelloWorld.xcodeproj in Xcode. In the File Navigator to the left, click on the HelloWorld project. Next click on the first target
'HelloWorld' in the left pane of the Editor. Click the Build Settings tab at the top of the Editor. Make sure that the All bubble is selected (not Basic).
Next to the setting called Architectures it says 'armv6 armv7'; click that field so a drop-down appears. Change the setting to 'Standard (armv7)'. Do the same
for anywhere 'armv6 armv7' appears inside the Architectures setting (Debug, Developer, Release). Now make sure you have an iOS device selected on the Scheme
chooser above. Click the Play button to run your App on the device.
The next step is to find a stream that is in the OpenTok Session, and to subscribe to it to place that video on screen. This can be done inside the Simulator, but since the only stream in the current Session comes from the device, we will need it to publish and so we will also be testing this on the device.
In order to subscribe to a stream, we must listen to another event that the Session object fires: "streamCreated". Place the following line above the call to the connect() method in the HelloView constructor:
self.session.addEventListener("streamCreated", streamCreatedHandler);
Next we create the streamCreatedHandler() function that will be called when a stream gets created in the Session. Place the following function below your other event handlers:
function streamCreatedHandler(event) {
// Subscribe to only my own stream
if ( event.stream.connection.connectionId === self.session.connection.connectionId ) {
self.subscriber = self.session.subscribe(event.stream);
self.subscriberView = self.subscriber.createView({
width : 200,
height : 150,
top : 20
});
self.add(self.subscriberLabel);
self.add(self.subscriberView);
}
}
This code first checks to see if stream that caused the event is a stream that comes from its own connection. This is done by comparing the connectionId property of the Session object's connection property to the same property of the Stream object. The Stream object is attached to the event property 'stream'. Next we create a Subscriber object by calling the subscribe() method on the Session. This method takes a Stream object, so we provide event.stream. Similar to when we created the PublisherView, we create a SubscriberView from the Subscriber object. Then we add the Label and the SubscriberView to HelloView.
Let's also update our cleanup code inside the sessionDisconnectedHandler() function.
function sessionDisconnectedHandler(event) {
// Remove publisher, subscriber, and their labels
self.remove(self.publisherLabel);
self.remove(self.publisherView);
self.remove(self.subscriberLabel);
self.remove(self.subscriberView);
}
You can now run on the device again to publish a video stream and then subscribe to the same stream. You should see yourself twice. Say 'Hello'!
Since OpenTok allows you to use the same Sessions across the web and mobile worlds, we can actually overcome the limitation of not being able to publish from within the Simulator by instead publishing from the web using the JavaScript SDK. Then we can still subscribe to the same Stream inside the simulator.
In order to do this, I've made a view that will help you Copy a URL from inside the Simulator and the Paste it in your browser. The page that loads will
allow you to publish to the same Session as the Simulator is connected to. Download the CopyingView.js file from here and include it in your
project in the directory Resources/ui/simulator (create it if you have to). Now add the following code to the HelloView construtor:
// create copying view
if (!self.onDevice) {
var CopyingView = require('ui/simulator/CopyingView');
self.copyingView = new CopyingView({
width : 300,
height : 50,
top : 20,
initialMessage : 'Publish from your Browser',
lastMessage : 'Open a Browser, Paste (⌘+V) the URL',
copyText : CONFIG.webUrl
});
}
This code will create the CopyingView and save it in the variable self.copyingView. Later when the session has connected and we are not on a device, we can add the CopyingView to the screen with the following code inside the sessionConnectedHandler:
// Start publishing from my camera
if (self.onDevice) {
...
} else {
self.publisherLabel.text = 'Cannot Publish from Simulator';
self.publisherLabel.color = 'red';
self.add(self.publisherLabel);
self.add(self.copyingView);
}
The CopyingView needs to know what URL to send you browser to, and for that we can create the URL in config.js since it depends on Session ID, Token, and API Key you are using. Add the baseURL and webURL properties to config.js as follows:
var CONFIG = {
apiKey : '{API_KEY}',
token : '{TOKEN}',
sessionId : '{YOUR_SESSION_ID}',
baseUrl : 'http:/opentok.github.com/opentok-titanium-mobile/simulator-test.html'
}
CONFIG.webUrl = CONFIG.baseUrl + '#apiKey=' + encodeURIComponent(CONFIG.apiKey) +
'&token=' + encodeURIComponent(CONFIG.token) +
'&sessionId=' + encodeURIComponent(CONFIG.sessionId);
module.exports = CONFIG
The last change we need to make is to change the condition from which we pick a stream to subscribe to. Instead of only picking a Stream that comes from the device, we want to test that if we are not running in the device we allow just the first stream to come into the Session. Update the streamCreatedHandler() function by changing the if statement to the following:
// Subscribe to first stream if I am not on a device;
// Subscribe to only my own stream if I am.
if ( (!self.onDevice && !self.subscriber ) ||
(event.stream.connection.connectionId === self.session.connection.connectionId)) {
...
}
Now we can test the App in the Simulator like we initially did, but after connecting you will see a button that says 'Publish from your Browser'. Tap that button. Next you will be instructed to use the keyboard shortcut for Copy (⌘+C) which will copy a URL to your clipboard. Tap the button again. Lastly you will be told that you are ready to Paste the URL into your browser. Open a browser and Paste the URL into the address bar.
The page will load a Publisher and ask you for permission to use the camera, click Allow. Now when the video begins streaming from the browser, you should also see a Subscriber appear in the Simulator that subscribes to that stream. Say 'Hello'!
Now that you have set up a Titanium project, imported the opentok module, connected to an OpenTok session, published a stream and subscribed to it, you are well on your way to developing your own App. To find out how to use other parts of the module, see the module reference. If you have any questions or find any bugs be sure to file an issue on the GitHub Issues page.