Say What? Live video chat between iOS & WebRTC with Twilio & IBM Watson Cognitive Computing in Real Time

What I’m about to show you might seem like science fiction from the future, but I can assure you it is not. Actually, every piece of this is available for you to use as a service.  Today.

Yesterday Twilio, an IBM partner whose services are available via IBM Bluemix, announced several new SDKs, including live video chat as a service.  This makes live video very easy to integrate into your native mobile or web based applications, and gives you the power to do some very cool things. For example, what if you could add video chat capabilities between your mobile and web clients? Now, what if you could take things a step further, and add IBM Watson cognitive computing capabilities to add real-time transcription and analysis?

Check out this video from yesterday’s Twilio Signal conference keynote, where fellow IBM’ers Damion Heredia and Jeff Sloyer demonstrate exactly this scenario; the integration of the new Twilio video SDK between iOS native and WebRTC client with IBM Watson cognitive computing services providing realtime transcription and sentiment analysis.

If it doesn’t automatically jump to the IBM Bluemix Demo, skip ahead to 2 hours, 15 min, and 20 seconds.

Jeff and Damion did an awesome job showing of both the new video service and the power of IBM Watson. I can also say first-hand that the new Twilio video services are pretty easy to integrate into your own projects (I helped them integrate these services into the native iOS client (physician’s app) shown in the demo)!  You just pull in the SDK, add your app tokens, and instantiate a video chat.   Jeff is pulling the audio stream from the WebRTC client and pushing it up to Watson in real time for the transcription and sentiment analysis services.

Bidirectional Communication Between An Apple Watch Extension and the Host App

In this entry we’re going to focus on building Apple Watch apps that can communicate back and forth with the host application running on the iPhone.  This is extremely important since the Apple Watch provides a second screen/peripheral complimentary experience to the main app running on the iOS device – be it a remote control, or quick view/glance into whats happening within the bigger picture.

In my last post I showed how to setup remote logging and instrumentation/analytics in an Apple Watch app using IBM MobileFirst Platform Foundation server.   I used the methods described below for communicating between the WatchKit and host apps in the sample app from that previous post.

When we’re talking about bidirectional communication, we’re talking about sending data two ways:

  1. Sending data from the host app to the WatchKit app
  2. Sending data to the WatchKit app from the host app

At first thought, you might think “oh that’s easy, just use NSNotificationCenter to communicate between the separate classes of the application”, but things aren’t exactly that simple.

An Apple Watch app is really made of 3 parts: 1) the main iOS application binary, 2) the user interface on the Apple Watch, and 3) the WatchKit extension binary (on the iOS device).

Apple Watch App - Architectural Components
Apple Watch App – Architectural Components

Yep, you read that correctly, the WatchKit extension (which controls all of the logic inside the Apple Watch UI and resides on the iOS device) is a separate binary from the “main” iOS application binary.  These are separate processes, so objects in memory in the main app are not the same objects in memory in the extension, and as a result, these processes do not communicate directly. NSNotificationCenter isn’t going to work.

However there are definitely ways you can make this type of a scenario work.

First, WatchKit has methods to invoke actions on the host application from the WatchKit extension.  WatchKit’s openParentApplication or handleWatchKitExtensionRequest methods both provide the ability to invoke actions and pass data in the containing app, and provide a mechanism to invoke a “reply” code block back in the WatchKit extension after the code in the host application has been completed.

For example, in the WatchKit extension, this will invoke an action in the host application and handle the reply:

[objc][WKInterfaceController openParentApplication:@{@"action":@"toggleStatus"} reply:^(NSDictionary *replyInfo, NSError *error) {
[logger trace:@"toggleStatus reply"];
[self updateUIFromHost:replyInfo];
}];[/objc]

Inside the host application we have access to the userInfo NSDictionary that was passed, and we can respond to it accordingly. For example, in the code below I am setting a string value on the userInfo instance, and taking appropriate actions based upon the value of that string.

[objc]- (void)application:(UIApplication *)application
handleWatchKitExtensionRequest:(NSDictionary *)userInfo
reply:(void (^)(NSDictionary *replyInfo))reply {

//handle this as a background task
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"backgroundTask"
expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];

NSString *action = (NSString*) [userInfo valueForKey:@"action"];
[logger trace:@"incoming request from WatchKit: ", action];

LocationManager * locationManager = [LocationManager sharedInstance];

NSMutableDictionary *result = [[NSMutableDictionary alloc] init];

if ([action isEqualToString:@"toggleStatus"]) {
//toggle whether or not we’re actually tracking the location
[locationManager toggleTracking];
} else if ([action isEqualToString:@"stopTracking"]) {
[locationManager stopTracking];
} else if ([action isEqualToString:@"currentStatus"]) {
//do nothing for now
}

NSString *trackingString = [NSString stringWithFormat:@"%s", locationManager.trackingActive ? "true" : "false"];
[result setValue:trackingString forKey:@"tracking"];
reply(result);

dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1 ), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
} );
}[/objc]

This covers the “pull” scenario, and is great if you want to invoke actions within your host app from your WatchKit extension, and then handle the responses back in the WatchKit extension to update your Apple Watch UI accordingly.

What about the “push” scenario?  The previous scenario only covers requests that originate inside the WatchKit extension.  What happens if you have a process running inside of your host app, and have updates that you want to push to the WatchKit extension without an originating request?

There is no shared memory, and it is not a shared process, so neither NSNotificationCenter or direct method invocation will work. However, you *can* use Darwin notifications (which work across seprate processes by using CFNotificationCenter).  This enables near-realtime interactions across processes, and you can share data as attributes of a CFdictionary object based between processes. You can also share larger amounts of data using access groups, and notify the separate processes using the CFNotificationCenter implementation.

Note: CFNotificationCenter is C syntax, not Objective-C syntax.

First you’ll need to subscribe for the notifications in the WatchKitExtension. Pay attention to the static id instance “staticSelf”… you’ll need this later when invoking Objective-C methods from the C notification callback.

[objc]static id staticSelf;

– (void)awakeWithContext:(id)context {
[super awakeWithContext:context];

//add your initialization stuff here

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), didReceiveTrackingStatusNotificaiton, CFSTR("TrackingStatusUpdate"), NULL, CFNotificationSuspensionBehaviorDrop);

staticSelf = self;
}[/objc]

From within your host app you can invoke CFNotificationCenterPostNotification to invoke the Darwin Notification.

[objc]-(void) postTrackingStatusNotificationToWatchKit {

NSString *trackingString = [NSString stringWithFormat:@"%s", self.trackingActive ? "true" : "false"];

NSDictionary *payload = @{@"tracking":trackingString};
CFDictionaryRef cfPayload = (__bridge CFDictionaryRef)payload;

CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("TrackingStatusUpdate"), (__bridge const void *)(self), cfPayload, TRUE);
}[/objc]

Then in the WatchKit extension, handle the notification, and update your WatchKit extension accordingly.

[objc]void didReceiveTrackingStatusNotificaiton() {
[staticSelf respondToPostFromHostApp];
}[/objc]

We’ve now covered scenarios where you you can request data or actions in the host application *from* the WatchKit extension, and also how you can push data from the host application to the WatchKit extension.

Now, what if there was a library that encapsulated some of this, and made it even easier for the developer?  When I wrote the app in my previous post, I used the methods described above. However, I recently stumbled across the open source MMWormhole, which wraps the Darwin Notifications method (above) for ease of use.  I’m pretty sure I’ll be using this in my next WatchKit app.

Helpful Links for inter-process communication between WatchKit and host apps:
Series on Apple WatchKit Apps powered by IBM MobileFirst:

 

Powering Apple Watch Apps with IBM MobileFirst – Part 1

This is the first entry in a multipart series on powering native iPhone and Apple Watch apps using the IBM MobileFirst Platform.  In this entry we will cover how to setup the MobileFirst Platform for use within Apple WatchKit apps and leverage the operational analytics and remote logging features.

So, let’s first take a look at the app we’re going to build in this video:

The app is a simple location tracker.  Think of something like a much simpler version of Run Keeper that will allow you to track your location path over a period of time, and show your location on a map.  We’re also building a WatchKit app that enables you to quickly start or stop tracking your location without ever having to pull your iPhone out of your pocket.  All of this powered by IBM MobileFirst.

WatchKit apps are essentially 3 parts:

  • The native iOS App on the phone
  • The watch app user interface
  • The WatchKit extension, which is a binary that runs *on the phone* but controls all of the logic for the watch interface

This means that when you run Apple Watch apps, they’re really no different than a native iOS app because all of the logic is executed on the Phone.

So… Setting up the MobileFirst Platform for WatchKit is really no different than setting it up for a native iOS app, with a few exceptions.

Full instructions how to setup MobileFirst Platform Foundation server with a native iOS app are available in the platform documentation.  Specifically, see the Configuring a Native iOS Application entry.

When you’re setting up your WatchKit app, you need to follow the exact same steps that you did for the native app target, just apply them to your WatchKit extension target.

First you need to add the required frameworks and dependencies (full list here, also be sure to include libWorklightStaticLibProjectNative.a that is inside the iOS API):

Add MobileFirst Frameworks and Dependencies
Add MobileFirst Frameworks and Dependencies

Next, add the “-ObjC” linker flag:

Add Linker Flag
Add Linker Flag

Then make sure that worklight.plist (which is inside of the MobileFirst API you generated from either the CLI or Eclipse Studio) so that it is included in both the native app and WatchKit extension.

Worklight.plist Target Membership
Worklight.plist Target Membership

 

This allows you to take advantage of MobileFirst APIs within your WatchKit extension, complete with operational analytics.  You cansave remote logs, you can access data adapters, and more. The server-side security mechanisms also work, so if you want to shut down your API for specific versions, you have that ability.

I mentioned earlier, it’s just like a native iOS app, but with a few exceptions.  The most important and notable exception is that the UI elements (modal dialogs, alerts, etc…) that you would normally see in the native phone interface do not appear in the WatchKit interface.  You don’t get errors – you just don’t see the notification.  So, you need to work around any scenarios that rely on this, and make sure you handle errors accordingly.

To invoke MobileFirst APIs, you call them as you wold normally in either Objective-C or Swift.  For example:

[objc]//InterfaceController for WatchKit app
– (void)awakeWithContext:(id)context {
[super awakeWithContext:context];

//setup MobileFirst remote logging
logger = [OCLogger getInstanceWithPackage:@"WatchKit: InterfaceController"];
[logger trace:@"InterfaceController awakeWithContext"];

//connect to MobileFirst server
[[WLClient sharedInstance] wlConnectWithDelegate: self];
}[/objc]

Once your app is connected, you’ll be able to access the operational analytics, remote logs, push notification management, etc… from the MobileFirst Platform Foundation server.

For example, the operational analytics dashboard showing app activity:

MobileFirst Operation Analytics with the WatchKit App
MobileFirst Operation Analytics with the WatchKit App

… and the remote log search capability, including logs from the WatchKit extension:

MobileFirst Remote Logging with the WatchKit App
MobileFirst Remote Logging with the WatchKit App

That’s all that you need to get started!

Stay tuned!  Full source code will be released on my github account in a subsequent post. Also be sure to stay tuned for future entries that cover the MobileFirst platform with offline data, persisting data to the server, push notifications, geo notifications, bidirectional communication between the watch and host app, background processing, and more! I will update this post to links to each subsequent post as it is made available.

Wondering what IBM MobileFirst is?  It’s a platform that enables you to deliver and maintain mobile applications throughout their entire lifecycle.  This includes tools to easily manage data, offline storage, push notifications, user authentication, and more, plus you get operational analytics and remote logging to keep an eye on things once you’ve deployed it to the real world, and its available as either cloud or on-premise solutions.

Helpful Links for MobileFirst Platform

Helpful Links for WatchKit apps:

Also, did I mention, writing apps for the Apple Watch is *really* fun!

watchkit

Series on Apple WatchKit Apps powered by IBM MobileFirst:

 

Complete Walkthrough and Source Code for “Building Offline Apps”

I recently put together some content on building “Apps that Work as Well Offline as they do Online” using IBM MobileFirst and Bluemix (cloud services).  There was the original blog post, I used the content in a presentation at ApacheCon, and now I’ve opened everything up for anyone use or learn from.

The content now lives on the IBM Bluemix github account, and includes code for the native iOS app, code for the web (Node.js) endpoint, a comprehensive script that walks through every step of of the process configuring the application, and also a video walkthrough of the entire process from backend creation to a complete solution.

Key concepts demonstrated in these materials:

  • User authentication using the Bluemix Advanced Mobile Access service
  • Remote app logging and instrumentation using the Bluemix Advanced Mobile Access service
  • Using a local data store for offline data access
  • Data replication (synchronization) to a remote data store
  • Building a web based endpoint on the Node.js infrastructure

You can download or fork any of the code at:

The repo contains:

  • Complete step-by-step instructions to guide through the entire app configuration and deployment process
  • Client-side Objective-C code (you can do this in either hybrid or other native platforms too, but I just wrote it for iOS).  The “iOS-native” folder contains the source code for a complete sample application leveraging this workflow. The “GeoPix-complete” folder contains a completed project (still needs you to walk through backend configuration). The “GeoPix-starter” folder contains a starter application, with all MobileFirst/Bluemix code commented out. You can follow the steps inside of the “Step By Step Instructions.pdf” file to setup the backend infrastructure on Bluemix, and setup all code within the “GeoPix-starter” project.
  • Backend Node.js app for serving up the web experience.

 

Significant Advances in the Consumer Drone Market

Lately I’ve been so focused on mobile, apps, development, conferences, and more that I haven’t posted much besides IBM work news and projects.  Well, I’m taking a break for just a moment…

If you’ve followed my blog for a while, then you already know that I’m pretty much obsessed with “drones”.  It is by far the most fun and exciting recreation that I’ve taken up in a very long time. Not only are they fun to fly, but they get you into some amazing views that were previously inacessible, and have applications far beyond just taking pictures.  I’ve written how-tos for aerial photography and videography, taken tons of pictures for fun, and even shot some indoor footage for TV commercials.

I’m always following the news feeds, watching the advances in technology, watching prices drop, and am continually blown away by what the industry is offering.  The last week to ten days have been nothing short of amazing.


First let’s start with the latest from DJI, who announced the Phantom 3 –  a consumer drone with some very impressive specs and performance.

The Phantom 3  is an easy to fly copter that sports a 3-axis gimbal (camera stabilizer), up to 4K video footage, an integrated rectilinear (flat) lens camera, live HD first-person view, integrated iOS and Android apps, a vision positioning system (for stabilized indoor flights) and up to a 1.2 mile flight range.  All for a cost of under $1300 USD.  That’s one heck of a package, and officially makes my old Phantom look like a dinosaur.


3 Days later, 3D Robotics announced the Solo, a direct competitor to the Phantom. The Solo is also very impressive, and has already won an award for Best Drone at NAB in Las Vegas.

The Solo also has a 3-axis gimbal for stabilized footage, and is designed to work with GoPro cameras.  In fact, it is the only copter that integrates with the camera controller and can control the GoPro remotely. The Solo also has dual processors (one in the controller, one in the copter), HD first person view, and has an upgradeable system that can have new camera systems or payloads configured.  It doesn’t have an optical stabilization system built in, but that can be added to the expansion bay.  What really sets the Solo apart is the intelligent auto-pilot sytem that appears to make complex shots very easy. All of this with a price tag starting at $1000 USD.

I currently own DJI products, but this has gotten me seriously considering a purchase.


Both of these are small aircraft targeting consumers, but from the look of it they are definitely capable of high end applications.  Their small size make them extremely portable, and a potential add in many industries and use cases.  Larger copters are always available for larger scale applications.


Let’s not forget drones for the enterprise…  Last week Airware launched their drone operating system.  Business can now license their operating system for commercial applications and data collection.


Meanwhile, people everywhere still freak out over drones as a political debate, ignoring their utility and positive value. The rules for commercial use continue to shake out, but oh man, it’s an exciting time.

Data Management for Apps that Work as Well Offline as They Do Online

Earlier this week I had the privilege of speaking at ApacheCon in Austin, TX on the topic of data management for apps that work as well offline as they do online.  This is an important topic for mobile apps, since, as we all painfully know already, there is never a case when you are always online on your mobile devices.  There always ends up being a time when you need your device/app, but you can’t get online to get the information you need.  Well, this doesn’t always have to be the case. There are strategies you can employ to build apps that work just as well offline as they do online, and the strategy I’d like to highlight today is based upon data management using the IBM Cloudant NoSQL database as a service, which is based upon Apache CouchDB.

Here’s a link to the presentation slides (built using reveal.js) – just use the space bar to advance the presentation slides:

The “couch” in CouchDB is actually an acronym for Cluster of Unreliable Commodity Hardware. At the core of this cluster is the concept of replication, which in the most basic of terms means that  data is shared between multiple sources.  Replication is used to share information between nodes of the cluster, which provides for cluster reliability and fault tolerance.

Replication between Nodes
Replication between Nodes (source)

If you’d like to learn more about replication in Cloudant and CouchDB, you can read more using the links below:

Cloudant is a clustered NoSQL database services that provides an extremely powerful and searchable data store.  It is designed to power the web and mobile apps, and all information is exposed via REST services. Since the IBM Cloudant service is based on CouchDB (and not so coincidentally, IBM is a major contributor to the CouchDB project), replication is also core the the Cloudant service.

With replication, you only have to write your data/changes to a single node in the cluster, and replication takes care of propagating these changes across the cluster.

If you are building apps for the web or mobile, there are options to extend the data replication locally either on the device or in the browser.   This means that you can have a local data store that automatically pushes and/or pulls data from the remote store using replication, and it can be done either via native languages, or using JavaScript.

If you want to have local replication in either a web or hybrid (Cordova/PhoneGap) app, you can use PouchDB.  PouchDB is a local JavaScript database modeled after CouchDB and implements that CouchDB replication API.  So, you can store your data in the browser’s local storage, and those changes will automatically be replicated to the remote Cloudant store.  This works in the browser, in a hybrid (web view) app, or even inside of a Node.js instance. Granted, if you’re in-browser you’ll need to leverage the HTML5 cache to have your app cached locally.

If you are building a native app, don’t worry, you can take advantage of the Cloudant Sync API to leverage the local data store with replication.  This is available for iOS and Android, and implements the CouchDB replication API.

The sample app that I showed in the presentation is a native iOS application based on the GeoPix MobileFirst sample app that I detailed in a previous post.  The difference is that in this case I showed it using the Cloudant Sync API, instead of the MobileFirst data wrapper classes, even though it was pointing at the exact same Cloudant database instance.  You can see a video of the app in action below.

All that you have to do is create a local data store instance, and then use replication to synchronize data between the local store and a remote store.

Replication be either one-way (push or pull), or two-way.  So, any changes between the local and remote stores are replicated across the cluster.  Essentially, the local data store just becomes a node in the cluster.  This provides complete access to the local data, even if there is no network available.  Just save your data to the local store, and replication takes care of the rest.

In the native Objective-C code, you just need to setup the CDTDatastore manager, and initialize your datastore instance.

[objc]self.manager = [[CDTDatastoreManager alloc] initWithDirectory:path error:nil];
self.datastore = [self.manager datastoreNamed:@"geopix" error:nil];[/objc]

Once your datastore is created, you can read/write/modify any data in the local store.  In this case I am creating a generic data object (basically  like a JSON object), and creating a document containing this data.  A document is a record within the data store.

You can add attachments to the document or modify the document as your app needs.  In the code below, I add a JPG atttachment to the document.

[objc]//create a document revision
CDTMutableDocumentRevision *rev = [CDTMutableDocumentRevision revision];
rev.body = @{
@"sort": [NSNumber numberWithDouble:[now timeIntervalSince1970]],
@"clientDate": dateString,
@"latitude": [NSNumber numberWithFloat:location.coordinate.latitude],
@"longitude": [NSNumber numberWithFloat:location.coordinate.longitude],
@"altitude": [NSNumber numberWithFloat:location.altitude],
@"course": [NSNumber numberWithFloat:location.course],
@"type": @"com.geopix.entry"
};

//add the jpg attachment
NSData *imageData = UIImageJPEGRepresentation(image, 0.1);
[imageData writeToFile:imagePath atomically:YES];

CDTUnsavedFileAttachment *att1 = [[CDTUnsavedFileAttachment alloc]
initWithPath:imagePath
name:imageName
type:@"image/jpeg"];

rev.attachments = @{ imageName: att1 };

//create a new document from the revision
NSError *error = nil;
CDTDocumentRevision *doc = [self.datastore createDocumentFromRevision:rev error:&error];

if (doc == nil) {
[logger logErrorWithMessages:@"Error creating document: %@", error.localizedDescription];
}

[logger logDebugWithMessages:@"Document created ID: %@", doc.docId];[/objc]

Replication is a fire-and-forget process.  You simply need to initialize the replication process, and any changes to the local data store will be replicated to the remote store automatically when the device is online.

[objc]//initialize the replicator factory with the local data store manager
CDTReplicatorFactory *replicatorFactory =
[[CDTReplicatorFactory alloc] initWithDatastoreManager:self.manager];

NSURL *remoteDatabaseURL = [NSURL URLWithString:REMOTE_DATABASE_URL];

//setup push replication for local->remote changes
NSError *error = nil;
CDTPushReplication *pushReplication =
[CDTPushReplication replicationWithSource:self.datastore target:remoteDatabaseURL];

//create the replicator instance
self.replicator = [replicatorFactory oneWay:pushReplication error:&error];
if (!self.replicator) {
[logger logErrorWithMessages:@"An error occurred: %@", error.localizedDescription];
}

//assign the replicator delegate
self.replicator.delegate = self;

//auto start replication
error = nil;
if (![self.replicator startWithError:&error]) {
[logger logErrorWithMessages:@"An error occurred: %@", error.localizedDescription];
}[/objc]

By assigning a replicator delegate class (as shown above), your app can monitor and respond to changes in replication state.  For example, you can update status if replication is in progress, complete, or if an error condition was encountered.

[objc]- (void)replicatorDidChangeState:(CDTReplicator *)replicator {
[logger logDebugWithMessages:@"Replicator changed State: %@", [CDTReplicator stringForReplicatorState:replicator.state]];
}

– (void)replicatorDidChangeProgress:(CDTReplicator *)replicator {
[logger logDebugWithMessages:@"Replicator progress: %d/%d", replicator.changesProcessed, replicator.changesTotal];

NSDictionary *userInfo = @{ @"status":[NSString stringWithFormat:@"%d/%d", replicator.changesProcessed, replicator.changesTotal] };

[[NSNotificationCenter defaultCenter]
postNotificationName:@"ReplicationStatus"
object:self
userInfo:userInfo];
}

– (void)replicatorDidError:(CDTReplicator *)replicator info:(NSError *)info {
[logger logErrorWithMessages:@"An error occurred: %@", info.localizedDescription];
self.replicator = nil;

[[NSNotificationCenter defaultCenter]
postNotificationName:@"ReplicationError"
object:self];
}

– (void)replicatorDidComplete:(CDTReplicator *)replicator {
[logger logDebugWithMessages:@"Replication completed"];
self.replicator = nil;

[[NSNotificationCenter defaultCenter]
postNotificationName:@"ReplicationComplete"
object:self];
}[/objc]

If you want to access data from the local store, it is always available within the app, regardless of whether or not the device has an active internet connection.  For example, this method will return all documents within the local data store.

[objc]-(NSArray*) getLocalData {

NSArray *docs = [self.datastore getAllDocuments];
return docs;
}[/objc]

Be sure to review the documentation and/or Cloudant Synch API source code for complete details.

Helpful Links

Video: The Next Generation of Native Apps Built with IBM MobileFirst

Last month I had the opportunity to speak at the DevNexus developer conference in Atlanta on building native iOS apps IBM MobileFirst. DevNexus is a great event, and it is always a privilege to attend – I highly recommend it for next year.   If you weren’t able to make it, no worries!  Most of the sessions were recorded and are available for viewing online via dzone.

The recording of my session is embedded below.  It covers everything you need to know to get started building apps with the MobielFirst platform.

This session focuses mainly on native iOS, but the exact sample concepts apply to MobileFirst apps built for other platforms or hybrid apps.  It covers both the MobileFirst for Bluemix (Cloud) and on-premise MobileFirst Platform Foundation Server solutions.

Here’s the “official” session description:

Once your app goes live in the app store you will have just entered into an iterative cycle of updates, improvements, and releases. Each successively building on features (and defects) from previous versions. IBM MobileFirst Foundation gives you the tools you need to manage every aspect of this cycle, so you can deliver the best possible product to your end user. In this session, we’ll cover the process of integrating a native iOS application with IBM MobileFirst Foundation to leverage all of the capabilities the platform has to offer.

Video originally shared by @dzone.

IBM Watson QA + Speech Recognition + Speech Synthesis = A Conversation With Your Computer

Back in November I released a demo application here on my blog showing the IBM Watson QA Service for cognitive/natural language computing connected to the Web Speech API in Google Chrome to have real conversational interaction with a web application.  It’s a nice demo, but it always drove me nuts that it only worked in Chrome.  Last month the IBM Watson team released 5 new services, and guess what… Speech Recognition and Speech Synthesis are included!

These two services enable you to quickly add Text-To-Speech or Speech-To-Text capability to any application.  What’s a better way to show them off than by updating my existing app to leverage the new speech services?

So here it is: watsonhealthqa.mybluemix.net!

By leveraging the Watson services it can now run in any browser that supports getUserMedia (for speech recognition) and HTML5 <Audio> (for speech playback).

(Full source code available at the bottom of this post)

You can check out a video of it in action below:

If your browser doesn’t support the getUserMedia API or HTML5 <Audio>, then your mileage may vary.  You can check where these features are supported with these links: <Audio>getUserMedia

Warning: This is targeting desktop browsers – HTML5 Audio is a mess on mobile devices due to limited codec support and immature APIs.

So how does this all work?

Just like the QA service, the new Text To Speech and Speech To Text services are now available in IBM Bluemix, so you can create a new application that leverages any of these services, or you can add them to any existing application.

I simply added the Text To Speech and Speech To Text services to my existing Healthcare QA application that runs on Bluemix:

bluemix-dashboard
IBM Bluemix Dashboard

 

These services are available via a REST API. Once you’ve added them to your application, you can consume them easily within any of your applications.

I updated the code from my previous example in 2 ways: 1) take advantage of the Watson Node.js Wrapper that makes interacting with Watson a lot easier and 2) to take advantage of these new services services.

Watson Node.js Wrapper

Using the Watson Node.js Wrapper, you can now easily instantiate Watson services in a single line of code.  For example:

[js]var watson = require(‘watson-developer-cloud’);
var question_and_answer_healthcare = watson.question_and_answer(QA_CREDENTIALS);
var speechToText = watson.speech_to_text(STT_CREDENTIALS);[/js]

The credentials come from your environment configuration, then you just create instances of whichever services that you want to consume.

QA Service

The code for consuming a service is now much simpler than the previous version.  When we want to submit a question to the Watson QA service, you can now simply call the “ask” method on the QA service instance.

Below is my server-side code from app.js that accepts a POST submission from the browser, delegates the question to Watson, and takes the result and renders HTML using a Jade template. See the Getting Started Guide for the Watson QA Service to learn more about the wrappers for Node or Java.

[js]// Handle the form POST containing the question
app.post(‘/ask’, function(req, res){

// delegate to Watson
question_and_answer_healthcare.ask({ text: req.body.questionText}, function (err, response) {
if (err)
console.log(‘error:’, err);
else {
var response = extend({ ‘answers’: response[0] },req.body);

// render the template to HTML and send it to the browser
return res.render(‘response’, response);
}
});
});[/js]

Compare this to the previous version, and you’ll quickly see that it is much simpler.

Speech Synthesis

At this point, we already have a functional service that can take natural language text, submit it to Watson,  and return a search result as text.  The next logical step for me was to add speech synthesis using the Watson Text To Speech Service (TTS).  Again, the Watson Node Wrapper and Watson’s REST services make this task very simple.  On the client side you just need to set the src of an <audio> instance to the URL for the TTS service:

[html]<audio controls="" autoplay="" src="/synthesize?text=The text that should generate the audio goes here"></audio>[/html]

On the server you just need to synthesize the audio from the data in the URL query string.  Here’s an example how to invoke the text to speech service directly from the Watson TTS sample app:

[js]var textToSpeech = new watson.text_to_speech(credentials);

// handle get requests
app.get(‘/synthesize’, function(req, res) {

// make the request to Watson to synthesize the audio file from the query text
var transcript = textToSpeech.synthesize(req.query);

// set content-disposition header if downloading the
// file instead of playing directly in the browser
transcript.on(‘response’, function(response) {
console.log(response.headers);
if (req.query.download) {
response.headers[‘content-disposition’] = ‘attachment; filename=transcript.ogg’;
}
});

// pipe results back to the browser as they come in from Watson
transcript.pipe(res);
});[/js]

The Watson TTS service supports .ogg and .wav file formats.  I modified this sample is setup only with .ogg files.  On the client side, these are played using the HTML5 <audio> tag.

Speech Recognition

Now that we’re able to process natural language and generate speech, that last part of the solution is to recognize spoken input and turn it into text.  The Watson Speech To Text (STT) service handles this for us.  Just like the TTS service, the Speech To Text service also has a sample app, complete with source code to help you get started.

This service uses the browser’s getUserMedia (streaming) API with socket.io on Node to stream the data back to the server with minimal latency. The best part is that you don’t have to setup any of this on your own. Just leverage the code from the sample app. Note: the getUserMedia API isn’t supported everywhere, so be advised.

On the client side you just need to create an instance of the SpeechRecognizer class in JavaScript and handle the result:

[js]var recognizer = new SpeechRecognizer({
ws: ”,
model: ‘WatsonModel’
});

recognizer.onresult = function(data) {

//get the transcript from the service result data
var result = data.results[data.results.length-1];
var transcript = result.alternatives[0].transcript;

// do something with the transcript
search( transcript, result.final );
}[/js]

On the server, you need to create an instance of the Watson Speech To Text service, and setup handlers for the post request to receive the audio stream.

[js]// create an instance of the speech to text service
var speechToText = watson.speech_to_text(STT_CREDENTIALS);

// Handle audio stream processing for speech recognition
app.post(‘/’, function(req, res) {
var audio;

if(req.body.url && req.body.url.indexOf(‘audio/’) === 0) {
// sample audio stream
audio = fs.createReadStream(__dirname + ‘/../public/’ + req.body.url);
} else {
// malformed url
return res.status(500).json({ error: ‘Malformed URL’ });
}

// use Watson to generate a text transcript from the audio stream
speechToText.recognize({audio: audio, content_type: ‘audio/l16; rate=44100’}, function(err, transcript) {
if (err)
return res.status(500).json({ error: err });
else
return res.json(transcript);
});
});[/js]

Source Code

You can interact with a live instance of this application at watsonhealthqa.mybluemix.net, and complete client and server side code is available at github.com/triceam/IBMWatson-QA-Speech.

Just setup your Bluemix app, clone the sample code, run NPM install and deploy your app to Bluemix using the Cloud Foundry CLI.

Helpful Links

GeoPix: A sample iOS app powered by IBM MobileFirst for Bluemix

In this post I’d like to show a fairly simple application that I put together which shows off some of the rich capabilities for IBM MobileFirst for Bluemix that you get out of the box – All with an absolute minimal amount of your own developer effort.  Bluemix, of course, being IBM’s platform as a service offering.

GeoPix is a sample application leveraging IBM MobileFirst for Bluemix to capture data and images on a mobile device, persist that data locally (offline), and replicate that data to the cloud. Since it’s built with IBM MobileFirst, we get lots of things out of the box, including operational analytics, user authentication, and much more.

(full source code at the bottom of this post)

Here’s what the application currently does:

  • User can take a picture or select an image from the device
  • App captures geographic location when the image is captured
  • App saves both the image and metadata to a local data store on the device.
  • App uses asynchronous replication to automatically save any data in local store up to the remote store whenever the network is available
  • Oh yeah, can’t forget, the user auth is via Facebook
  • MobileFirst provides all the analytics we need.  Bluemix provides the cloud based server and Cloudant NoSQL data store.
  • All captured data is available on a web based front-end powered by Node.js

Here’s a video of it in action:

… and you can check out the web interface at geopix.mybluemix.net.

(full source code at the bottom of this post)

This is powered by the iOS 8 MobileFirst application boilerplate on Bluemix.  With this application template you can have your backend infrastructure setup within minutes, and it includes:

  • User authentication
  • Usage/operational analytics
  • Cloudant NoSQL DB
  • Simplified Push Notifications
  • Node.js backend

In this sample I’m using everything but the Push Notifications service.  I’m using user authentication, the Cloudant DB (offline/local store and remote/cloud store), and the node.js backend.  You get the operational analytics automatically.

To get started, you just need to create a new iOS 8 mobile application on Bluemix.  See my video series on Getting Started with IBM MobileFirst for Bluemix for a complete walkthrough of creating a new app using MobileFirst for Bluemix, or check out the Getting Started Guide in the official docs.

You need to initialize your app, and make sure you have setup the Facebook identity provider.  You can create your Facebook authentication at https://developers.facebook.com/.  Once the user is authenticated, the client app is fully functional.

The app UI is very simple, basically just two buttons for capturing images (the last captured image shows up in the background):

App's main UI
App’s main UI

There’s also a gallery for viewing local images:

Local gallery view
Local gallery view

Capturing Location

Capturing data is very straightforward.  The geographic location is captured using Apple’s Core Location framework.  We just need to implement the CLLocationManagerDelegate protocol:

[objc]- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations {

self.currentLocation = [locations lastObject];
NSDate* eventDate = self.currentLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (abs(howRecent) < 15.0) {
// If the event is recent, do something with it.
locationLabel.text = [NSString stringWithFormat:@" Lat: %+.5f, Lon: %+.5f\n",
self.currentLocation.coordinate.latitude,
self.currentLocation.coordinate.longitude];
}
}[/objc]

Then initialize CLLocationManager using our class as the location manager’s delegate:

[objc]if (self.locationManager == nil)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.pausesLocationUpdatesAutomatically = YES;[/objc]

Capturing Images

Capturing images from the device is also very straightforward.  In the app I leverage Apple’s UIImagePickerController to allow the user to either upload an existing image or capture a new image.  See the presentImagePicker and didFinishPickingMediaWithInfo below. All of this standard practice using Apple’s developer SDK:

[objc]- (void) presentImagePicker:(UIImagePickerControllerSourceType) sourceType {
if ( sourceType == UIImagePickerControllerSourceTypeCamera && ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[logger logErrorWithMessages:@"device has no camera"];
UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:@"Error"
message:@"Device has no camera"
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles: nil];
[myAlertView show];
};

if ( sourceType != UIImagePickerControllerSourceTypeCamera || [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] ){
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = NO;
picker.sourceType = sourceType;

[self presentViewController:picker animated:YES completion:NULL];
}
}

– (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

[logger logDebugWithMessages:@"didFinishPickingMediaWithInfo"];
UIImage *image = info[UIImagePickerControllerOriginalImage];
currentImage.image = image;
[[DataManager sharedInstance] saveImage:image withLocation:self.currentLocation];
[picker dismissViewControllerAnimated:YES completion:nil];
}[/objc]

Persisting Data

If you notice in the didFinishPickingMediaWithInfo method above, there is a call to the DataManager’s saveImage withLocation method. This is where we save data locally and rely on Cloudant’s replication to automatically save data from the local data store up to the Cloudant NoSQL database.  This is powered by the iOS 8 Data service from Bluemix.

The first thing that we will need to do is initialize the local and remote data stores. Below you can see my init method from my DataManager class. In this, you can see the local data store is initialized, then the remote data store is initialized. If either data store already exists, the existing store will be used, otherwise it is created.

[objc]-(id) init {
self = [super init];

if ( self ) {
logger = [IMFLogger loggerForName:NSStringFromClass([self class])];
[logger logDebugWithMessages:@"initializing local datastore ‘geopix’…"];

// initialize an instance of the IMFDataManager
self.manager = [IMFDataManager sharedInstance];

NSError *error = nil;
//create a local data store
self.datastore = [self.manager localStore:@"geopix" error:&error];

if (error) {
[logger logErrorWithMessages:@"Error creating local data store %@",error.description];
}

//create a remote data store
[self.manager remoteStore:@"geopix" completionHandler:^(CDTStore *store, NSError *error) {
if (error) {
[logger logErrorWithMessages:@"Error creating remote data store %@",error.description];
} else {
[self.manager setCurrentUserPermissions:DB_ACCESS_GROUP_MEMBERS forStoreName:@"geopix" completionHander:^(BOOL success, NSError *error) {
if (error) {
[logger logErrorWithMessages:@"Error setting permissions for user with error %@",error.description];
}

[self replicate];
}];
}
}];

//start replication
[self replicate];
}

return self;
} [/objc]

Once the data stores are created, you can see that the replicate method is invoked.  This starts up the replication process to automatically push changesfrom the local data store to the remote data store “in the cloud”.

Therefore, if you’re collecting data when the app is offline, then you have nothing to worry about.  All of the data will be stored locally and pushed up to the cloud whenever you’re back online – all with no additional effort on your part.  When using replication with the Cloudant SDK, you just have to start the replication process and let it do it’s thing… fire and forget.

In my replicate function, I setup CDTPushReplication for pushing changes to the remote data store.  You could also setup two-way replication to automatically pull new changes from the remote store.

[objc]-(void) replicate {
if ( self.replicator == nil ) {
[logger logDebugWithMessages:@"attempting replication to remote datastore…"];

__block NSError *replicationError;
CDTPushReplication *push = [self.manager pushReplicationForStore: @"geopix"];
self.replicator = [self.manager.replicatorFactory oneWay:push error:&replicationError];
if(replicationError){
// Handle error
[logger logErrorWithMessages:@"An error occurred: %@", replicationError.localizedDescription];
}

self.replicator.delegate = self;

replicationError = nil;
[logger logDebugWithMessages:@"starting replication"];
[self.replicator startWithError:&replicationError];
if(replicationError){
[logger logErrorWithMessages:@"An error occurred: %@", replicationError.localizedDescription];
}else{
[logger logDebugWithMessages:@"replication start successful"];
}
}
else {
[logger logDebugWithMessages:@"replicator already running"];
}
}[/objc]

Once we’ve setup the remote and local data stores and setup replication, we now are ready to save the data the we’re capturing within our app.

Next is my saveImage withLocation method.  Here you can see that it creates a new CDTMutableDocumentRevision object (this is a generic object for the Cloudant NoSQL database), and populates it with the location data and timestamp.   It then creates a jpg image from the UIImage (passed in from the UIImagePicker above) and adds the jpg as an attachment to the document revision.  Once the document is created, it is saved to the local data store.   We then let replication take care of persisting this data to the back end.

[objc]-(void) saveImage:(UIImage*)image withLocation:(CLLocation*)location {

[logger logDebugWithMessages:@"saveImage withLocation"];

//save in background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {

[logger logDebugWithMessages:@"creating document…"];

NSDate *now = [NSDate date];
NSString *dateString = [NSDateFormatter localizedStringFromDate:now
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterFullStyle];

// Create a document
CDTMutableDocumentRevision *rev = [CDTMutableDocumentRevision revision];
rev.body = @{
@"sort": [NSNumber numberWithDouble:[now timeIntervalSince1970]],
@"clientDate": dateString,
@"latitude": [NSNumber numberWithFloat:location.coordinate.latitude],
@"longitude": [NSNumber numberWithFloat:location.coordinate.longitude],
@"altitude": [NSNumber numberWithFloat:location.altitude],
@"course": [NSNumber numberWithFloat:location.course],
@"type": @"com.geopix.entry"
};

[logger logDebugWithMessages:@"creating image attachment…"];

NSDate *date = [NSDate date];
NSString *imageName = [NSString stringWithFormat:@"image%f.jpg", [date timeIntervalSince1970]];

NSString *tempDirectory = NSTemporaryDirectory();
NSString *imagePath = [tempDirectory stringByAppendingPathComponent:imageName];

[logger logDebugWithMessages:@"saving image to temporary location: %@", imagePath];

NSData *imageData = UIImageJPEGRepresentation(image, 0.1);
[imageData writeToFile:imagePath atomically:YES];

CDTUnsavedFileAttachment *att1 = [[CDTUnsavedFileAttachment alloc]
initWithPath:imagePath
name:imageName
type:@"image/jpeg"];

rev.attachments = @{ imageName: att1 };

[self.datastore save:rev completionHandler:^(id savedObject, NSError *error) {
if(error) {
[logger logErrorWithMessages:@"Error creating document: %@", error.localizedDescription];
}
[logger logDebugWithMessages:@"Document created: %@", savedObject];
}];

[self replicate];
});
}[/objc]

If we want to query data from either the remote or local data stores, we can just use the performQuery method on the data store. Below you can see a method for retrieving data for all of the images in the local data store.

[objc]-(void) getLocalData:(void (^)(NSArray *results, NSError *error)) completionHandler {

NSPredicate *queryPredicate = [NSPredicate predicateWithFormat:@"(type = ‘com.geopix.entry’)"];
CDTCloudantQuery *query = [[CDTCloudantQuery alloc] initWithPredicate:queryPredicate];

[self.datastore performQuery:query completionHandler:^(NSArray *results, NSError *error) {

completionHandler( results, error );
}];
}[/objc]

At this point we’ve now captured an image, captured the geographic location, saved that data in our local offline store, and then use replication to save that data up to the cloud whenever it is available.

AND…

We did all of this without writing a single line of server-side logic.   Since this is built on top of MobileFirst for Bluemix, all the backend infrastructure is setup for us, and we get operational analytics to monitor everything that is happening.

With the operational analytics we get:

  • App usage
  • Active Devices
  • Network Usage
  • Authentications
  • Data Storage
  • Device Logs (yes, complete debug/crash logs from devices out in the field)
  • Push Notification Usage

Sharing on the web

Up until this point we haven’t had to write any back-end code. However the mobile app boilerplate on Bluemix comes with a Node.js server.  We might as well take advantage of it.

I exposed the exact same data captured within the app on the Node.js service, which you can see at http://geopix.mybluemix.net/.

Web UI
Web UI

The Node.js back end comes preconfigured to leverage the express.js framework for building web applications.  I added the jade template engine and Leaflet for web-mapping, and was able to crank this out ridiculously quickly.

The first thing we need to do is make sure  we have our configuration variables for accessing the Cloudant service from our node app.  These are environment vars that you get automatcilly if you’re running on Bluemix, but you need to set these for your local dev environment:

[js]var credentials = {};

if (process.env.hasOwnProperty("VCAP_SERVICES")) {
// Running on Bluemix. Parse out the port and host that we’ve been assigned.
var env = JSON.parse(process.env.VCAP_SERVICES);
var host = process.env.VCAP_APP_HOST;
var port = process.env.VCAP_APP_PORT;

credentials = env[‘cloudantNoSQLDB’][0].credentials;
}
else {

//for local node.js server instance
credentials.username = "cloudant username here";
credentials.password = "cloudant password here";
credentials.url = "cloudant url here";
}[/js]

Next we’ll add our URL/content mappings:

[js]app.get(‘/’, function(req, res){
prepareData(res, ‘map’);
});

app.get(‘/list’, function(req, res){
prepareData(res, ‘list’);
});[/js]

Next you’ll se the logic for querying the Cloudant data store and preparing the data for our UI templates. You can customize this however you want – caching for performance, refactoring for abstraction, or whatever you want. All interactions with Cloudant are powered by the Cloudant Node.js Client

[js]var prepareData = function(res, template) {
var results = [];

//create the index if it doesn’t already exist
var sort_index = {name:’sort’, type:’json’, index:{fields:[‘sort’]}};
geopix.index(sort_index, function(er, response) {
if (er) {
throw er;
}

//perform the search
//we’re just pulling back all
//data captured ("sort" will be numeric)
var selector = {sort:{"$gt":0}};
geopix.find({selector:selector, sort:["sort"]}, function(er, result) {
if (er) {
throw er;
}

//prepare data for template
for (var x=0; x<result.docs.length; x++) {
var obj = result.docs[x];

for (var key in obj._attachments) {
obj.image = credentials.url + "/" + database + "/" + obj._id +"/" + key;
break;
}

results.push( obj );
}
res.render(template, { results:results});
});
});
};[/js]

After the prepareData method has prepared data for formatting in the UI, the template is rendered by invoking Jade’s render method:

[js]res.render(template, { results:results});[/js]

This will render whichever template was passed in – I have two: map.jade (the map template) and list.jade (the list template). You can check out the list template below, and see it in action here: http://geopix.mybluemix.net/list

[html]html
head
title GeoPix – powered by Bluemix
link(href=’//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css’ rel=’stylesheet’)
link(href=’/public/css/index.css’ rel=’stylesheet’)
meta(name="viewport" content="width=device-width, initial-scale=1")
body
div(class=’well’)
h1 GeoPix – Powered by Bluemix
p
a(href=’/’) Map
&nbsp;|&nbsp;
a(href=’/list’) List
div(class="container-fluid")
each val, index in results
div(class="col-md-6")
div(class="panel panel-default")
div(class="panel-heading")
h3= val.clientDate
div(class="panel-body")
img(src=val.image)
p= ‘latitude: ‘ + val.latitude + ", longitude:" + val.longitude + ", altitude:" + val.altitude[/html]

In the map view I used the Leaflet map engine and Open Street Map data, along with the Leaflet Marker Cluster plugin for displaying clustered results.

Source Code

You can check out the web interface live at: http://geopix.mybluemix.net/.  If you want to setup the environment on your own, you can grab the complete source code at:

Helpful Links

Ready to start building your own apps on IBM Bluemix?  Just head over to http://bluemix.net and get a free developer trial today!

Updated: Parallax Effects in Hybrid/Web Apps

A while back I wrote about adding parallax effects to your HTML/JS experiences to make them feel a bit richer and closer to a native experience.  I’ve just added this subtle (key word *subtle*) effect to a new project and made a few changes I wanted to share here.

If you are wondering what I am talking about with “parallax effects” – Parallax movement is where objects in the background move at a different rate than objects in the foreground, thus causing the perception of depth.  Read more about it if you’re interested.

First, here’s a quick video of this latest app in action.  It’s a hybrid MobileFirst app, but this technique could be used in any Cordova/PhoneGap/MobileFirst/mobile web app experience.  The key is to keep it subtle and not too much “in your face”, and yes, it is very subtle in this video.  You have to watch closely.

The techniques that I wrote about in the previous post still apply – I’ve just added a bit more to cover more use cases.

First let’s look at the CSS.

[css]body {
background-image: url(‘../images/cloud_advisor_bkgd.png’);
background-attachment: fixed;
background-position: center;
background-size: auto 120%;
}[/css]

This sets the background image and default position.  The distinct change here is that I set the background size to “auto” width and 120% height.  In this case, you can have a huge image that shrinks down to just slightly larger than the window size, or a small image that scales up to a larger window size.  This way you don’t end up with seams in a repeated background or a background that is too big to highlight the parallax effect effectively.

Next let’s take a quick look at the JS involved.

[js]var position = "center";
var lastPosition = "center";
var contentCSS = "";
var body = $("body");
var content = $(".content");
window.suspendAnimation = false;

var xMovement = 15;
var yMovement = 30;
var halfX = xMovement/2;
var halfY = yMovement/2;

window.ondeviceorientation = function(event) {
var gamma = event.gamma/90;
var beta = event.beta/180;

var temp = 0;

//fix for holding the device upside down
if ( gamma >= 1 ) {
gamma = 2- gamma;
} else if ( gamma <= -1) {
gamma = -2 – gamma;
}

// shift values/motion based upon device orientation
switch (window.orientation) {
case 90:
temp = gamma;
gamma = beta;
beta = temp;
break;
case -90:
temp = -gamma;
gamma = beta;
beta = temp;
break;

}

// update positions to be used for CSS
var yPosition = -yMovement – (beta * yMovement);
var xPosition = -xMovement – (gamma * xMovement);
var contentX = (-xMovement – xPosition)/2;
var contentY = (-yMovement – yPosition)/2;

// generate css styles
position = xPosition.toFixed(1) + "px " + yPosition.toFixed(1) + "px";
contentCSS = "translate3d( " + (contentX.toFixed(1)) + "px, " + (contentY.toFixed(1)) + "px, " + " 0px)";
}

function updateBackground() {

if (!window.suspendAnimation) {
if ( position.valueOf() != lastPosition.valueOf() ) {

body.css( "background-position", position);
content.css( "-webkit-transform", contentCSS);
lastPosition = position;
}
} else {
lastPosition = "translate3d(0px, 0px, 0px)";;
}

window.requestAnimationFrame(updateBackground);
}

window.requestAnimationFrame(updateBackground);[/js]

The html where this would be used would be something like this:

[html]<body>
<div class="content">put your foreground stuff here.</div>
</body>[/html]

There are some subtle but important changes here:

  1. In the requestAnimationFrame loop, it only applies changes *if* there are changes to apply.  This prevents needless calls to apply CSS even if the CSS styles hadn’t changed.  In this, I also truncate the numeric CSS string so that it isn’t reapplying CSS if the position should shift by 0.01 pixels. Side note: If you aren’t using requestAnimationFrame for HTML animations, you should learn about it.
  2. If you used my old code and were holding the device upside down, it wouldn’t work.  Not even a little bit.  This has that fixed (see comments inline above).
  3. This moves the background in CSS, which doesn’t cause browser reflow operations, and moves the foreground content (inside of a div) using translate3d, which also doesn’t cause browser reflow operations.  This helps keep animations smooth and the UX performing optimally.
  4. I also added a global variable to turn parallax on and off very quickly, if you need it.

The result is a faster experience that is more efficient and less of a strain on CPU and battery.  Feel free to test this technique out on your own.

If you use the code above, you can modify the xMovement and yMovement variables to exaggerate the parallax effect.