End Point

News

Welcome to End Point's blog

Ongoing observations by End Point people.

Custom helper subs in Dancer templates

I recently was writing some code using the Dancer Perl web framework, and had a set of HTML links in the template:

<a href="/">Home</a> |
<a href="/contact">Contact</a> |
[etc.]

Since it's possible this app could be relocated to a different path, say, /something/deeper instead of merely /, I wanted to use Dancer's handy uri_for() routine to get the full URL, which would include any path relocation. (This concept will be familiar to Interchange 5 users from its [area] and [page] tags.)

The uri_for function isn't available in templates. The easiest way to cope would be to just use it in my route sub where it works fine, and store the results in the template tokens as strings. But then for any new URL needed I would have to update the route sub and the template, and this feels like a quintessential template concern.

I found this blog post explaining how to add custom functions to be used in templates, and it worked great. Now my template can look like this:

<a href="<% uri_for('/') %>">Home</a> |
<a href="<% uri_for('contact') %>">Contact</a> |
[etc.]

And the URLs are output fully qualified:

http://localhost:3000/
http://localhost:3000/contact

Which is not always what I'd want, but in this case is.

The only final concern is that I am using Dancer version 1.3111 and I got this warning upon using the before_template setup mentioned in the blog:

Dancer::before_template has been deprecated since version 1.3080. use hooks!
But use hook 'before_template' => sub {} now instead.

So I updated my code, and the final result looks like this:

hook 'before_template' => sub {
    my $tokens = shift;
    $tokens->{uri_for} = \&uri_for;
};

And that made both Dancer and me happy.

Paper Source: Case Study with Google Maps API


Basic Google map with location markers.

Recently, I've been working with the Google Maps API on Paper Source. Paper Source is one of our Interchange clients who has over 40 physical stores throughout the US. On their website, they had previously been managing static HTML pages for these 40 physical stores to share store information, location, and hours. They wanted to move in the direction of something more dynamic with interactive maps. After doing a bit of research on search options out there, I decided to go with the Google Maps API. This article discusses basic implementation of map rendering, search functionality, as well as interesting edge case behavior.

Basic Map Implementation

In it's most simple form, the markup required for adding a basic map with markers is the shown below. Read more at Google Maps Documentation.

HTML

<div id="map"></div>

CSS

#map {
  height: 500px;
  width: 500px;
}

JavaScript

//mapOptions defined here
var mapOptions = {
  center: new google.maps.LatLng(40, -98),
  zoom: 3,
  mapTypeId: google.maps.MapTypeId.ROADMAP
};

//map is the HTML DOM element ID where it will be rendered
var map = new google.maps.Map(document.getElementById("map"), mapOptions);

//all locations is a JSON object representing locations,
//where each location has a latitude and longitude
$.each(all_locations, function(i, loc) {
  var marker = new google.maps.Marker({
    map: map,
    position: new google.maps.LatLng(loc.latitude, loc.longitude)
  });
})

Building Search Functionality


Search interface. Search results are listed on the left, and map with markers is shown on the right.

Next up, I needed to build out search functionality. Google has its own geocoder to allow address searches. Here is the basic markup for running a search:

var geocoder = new google.maps.Geocoder();
//search is a variable representing the user search, such as a zip code, city name, or state name
geocoder.geocode({ 'address' : search }, function(results, status) {
  var search_center = results[0].geometry.bounds.getCenter();

  var mapOptions = {
    center: search_center,
    zoom: 10,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("map"), mapOptions);

  $.each(all_locations, function(i, loc) {
    var marker = new google.maps.Marker({
      map: map,
      position: new google.maps.LatLng(loc.latitude, loc.longitude)
    });
  })
}

In the above code, the search term is passed into the Geocoder object and a map with all locations marked is rendered. To determine which markers are in the visible map boundaries, the following map.getBounds().contains() method would be leveraged:

var visible_locations = [];
$.each(all_locations, function(i, loc) {
  if(map.getBounds().contains(new google.maps.LatLng(loc.latitude, loc.longitude))) {
    visible_locations.push(loc);
  }
});
//render visible locations to the left of the map

One final step here is to add a listener to the map, so that visible locations are updated when the user zooms in and out. This is accomplished with the following listener:

google.maps.event.addListener(map, 'zoom_changed', function() {
  //call method to rerender visible locations
});

Handling Zero Results

What happens if your Geocoder object can't find the address? A simple conditional can be used:

geocoder.geocode({ 'address' : search }, function(results, status) {
  if(status == "ZERO_RESULTS") {
    //notify customer that no results have been found
  } else {
    //got results, render location
  }
}

Calculate and Sort by Distance

The next layer of logic I needed to add was the ability to determine the distance between the search address and sort the results by distance. To calculate distance, I did some research and settled on the following code:

var R = 6371;
$.each(all_locations, function(i, loc) {
  var loc_position = new google.maps.LatLng(loc.latitude, loc.longitude);
  var dLat  = locations.rad(loc.latitude - search_center.lat());
  var dLong = locations.rad(loc.longitude - search_center.lng());

  //calculate spherical distance between search position and location
  var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
  Math.cos(locations.rad(search_center.lat())) *
    Math.cos(locations.rad(search_center.lat())) * 
    Math.sin(dLong/2) * Math.sin(dLong/2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = R * c;
  loc.distance = d;

  //convert distance to miles
  loc.readable_distance = 
    (google.maps.geometry.spherical.computeDistanceBetween(search_center, loc_position) * 
    0.000621371).toFixed(2);
});

To sort the locations by distance, I leverage jQuery sort:

var sort_by_distance = function(obj) {
  return obj.sort(function(a, b) {
    if(a.distance > b.distance) {
      return 1;
    } else {
      return -1;
    }
  })
};
var sorted_locations = sort_by_distance(all_locations);

Adjust Map Boundaries to Include Specific Markers

Another interesting use case I needed to handle was forcing the map to zoom out to include stores within 100 miles if there was nothing in the initial map boundaries, e.g.:


The search for "27103" doesn't return any nearby stores, so the map is extended to include stores within 100 miles.

To accomplish this functionality, I added a bit of code to extend the map boundaries:

geocoder.geocode({ 'address' : search }, function(results, status) {
  var search_center = results[0].geometry.bounds.getCenter();

  var mapOptions = {
    center: search_center,
    zoom: 10,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById("map"), mapOptions);
  var current_bounds = results[0].geometry.bounds;

  $.each(all_locations, function(i, loc) {
    var loc_position = new google.maps.LatLng(loc.latitude, loc.longitude);
    var dLat  = locations.rad(loc.latitude - search_center.lat());
    var dLong = locations.rad(loc.longitude - search_center.lng());

    //calculate spherical distance between search position and location
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(locations.rad(search_center.lat())) *
      Math.cos(locations.rad(search_center.lat())) * 
      Math.sin(dLong/2) * Math.sin(dLong/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    loc.distance = d;

    //convert distance to miles
    loc.readable_distance = 
      (google.maps.geometry.spherical.computeDistanceBetween(search_center, loc_position) * 
      0.000621371).toFixed(2);

    var marker = new google.maps.Marker({
      map: map,
      position: new google.maps.LatLng(loc.latitude, loc.longitude)
    });

    if(loc.readable_distance < 100) {
      current_bounds.extend(loc_position);
    }
  });

  //Google map method to fit map boundaries to desired boundaries
  map.fitBounds(current_bounds);
}

Disable Scroll and Zoom on Mobile-Sized Devices

One final behavior needed was to disable map zooming and scrolling on mobile devices, to improve the usability on mobile/touch interfaces. Here's how this was accomplished:

var options_listener = google.maps.event.addListener(map, "idle", function() {
  if($(window).width() < 656) {
    map.setOptions({
      draggable: false,
      zoomControl: false,
      scrollwheel: false,
      disableDoubleClickZoom: true,
      streetViewControl: false
    });
  }
  google.maps.event.removeListener(options_listener);
});

Conclusion

With all this code, the final location search functionality at Paper Source includes:

  • Basic United States map rendering to display all physical store locations.
  • Search by location which shows stores within 100 miles, and allows users to zoom in and out to adjust their search. Search lists results sorted by distance.
  • "Saved" or "Quick" searches by states, which displays all physical stores by state.
  • Adjustment of mobile display map options.

Google Sitemap rapid deployment

I was going to call this "Quick and Dirty Sitemaps", but "Rapid Deployment" sounds more buzz-word-worthy. This is how to get a Google sitemap up and running quickly, using the Google sitemap generator and the Web Developer Firefox plug-in.

I had occasion to set up a sitemap using the Google sitemap generator for a site recently. Here's what I did:

Download the generator using the excellent documentation found at the previous link. Unpack it into a convenient location and copy the example_config.xml file to something else, e.g., www.mysite.com_config.xml. Edit the new configuration file and:

  1. Modify the "base_url" setting to your site;
  2. Change the "store_into" setting to a file in your site's document root;
  3. Add a pointer to a file that will contain your list-of-links, e.g.,
    
    
    I would locate this in the same path as your new configuration file.

Now, if you don't already have Web Developer, give yourself a demerit and go install it.

...

Okay, you'll thank me for that. Now pick a few pages from your site: good choices, depending on your site's design, are the home page, the sitemap (if you have one), and any of the top-level "nav links" you may have set up.

Visit each of those pages in turn. Use Web Developer to assemble the links from the pages, clicking:

  1. Tools menu
  2. Web Developer extension
  3. Information
  4. View link information

Copy and paste each informational list-of-links and append it to a text file. You can clean it up a bit when you are done, removing any links you don't want in the sitemap, or you can let the sitemap generator tell you which ones to remove while testing.

You can sort and de-duplicate the file with something like this:

$ sort site_urls.txt | uniq > site_urls.out
Inspect the site_urls.out file and when you're happy with it, rename it to "site_urls.txt".

You're ready to run the sitemap generator:

$ python sitemap_gen.py --config=www.mysite.com_config.xml --testing

Check the output for warnings, adjust the configuration and/or the site_urls.txt file, and eventually you can run this without the --testing flag. Now you just need to add it to a crontab where it will be run appropriately, and you're done!

Generating PDF documents in the browser

What you will learn.

  • How to generate PDF documents in the browser with JavaScript
  • How to generate them out of normal HTML
  • How to open those PDFs in new windows
  • How to render them inline inside the DOM

Introduction

Every once in a while as web developers we face the challenge of providing PDF documents for the data we have persisted in the database.

The usual approach is to:

  • generate the document with a library / DSL in the language of the backend
  • or - generate normal html view and use a utility like wkhtmltopdf

That works nice, but what if you’re developing an SPA app which only consumes JSON data from its backend? Imagine a scenario when the backend isn’t capable of producing responses other than JSON data. What could you do there?

The solution

Thanks to some very bright folks behind the jsPDF library we have both above mentioned options right inside the browser.

I strongly encourage you to visit their website. There is a nice live coding editor set up which reflects in real time the PDF your code is producing.

Using the DSL

The whole process looks like this:

# 1. create jsPDF object:
doc = new jsPDF()

# 2. put something interesting in there:
doc.setFontSize(22)
doc.text(20, 20, 'This is a title')
doc.setFontSize(16)
doc.text(20, 30, 'This is some normal sized text underneath.')

# 3. choose some useful outlet:
doc.output('dataurlnewwindow', {})

Where is the documentation?

One thing I didn’t like about the experience which was really great otherwise, was the fact that you could browse the Internet for hours without luck, looking for some useful docs for the library.

For all of you who don’t know the shortcut yet, here it is:

git clone https://github.com/MrRio/jsPDF.git

And in the doc directory you will find nicely generated docs out of the project sources - very neat!

Can I generate it just from HTML?

Yes you can. On the project’s website the authors are warning us that this feature is still experimental. But I have to say that last time I used it, all went well and the code produces nice PDF to this day (yay!).

doc.fromHTML $('.report_web').get(0), 10, 10,
  'width': 170,
  'elementHandlers': 
    'LI': (el, renderer) =>
      if renderer.y > 250
        doc.addPage()
        renderer.y = 10
      else
        renderer.y += 10
      false
    'H1': (el, renderer) =>
      doc.setFontSize(14)
      doc.setFontStyle('bold')
      doc.text($(el).text(), 10, renderer.y)
      doc.setFontSize(12)
      doc.setFontStyle('normal')
      true
doc.output 'dataurlnewwindow', {}

Take a look at the code above. #fromHtml method takes the DOM element, margins, and a hash of options. From my experience the elementHandlers attribute seems to be mandatory (I got errors without it).

Every element handler is just a pair of capitalized tag name and the function which takes DOM element and the renderer context.

The renderer variable contains data of the current paragraph, the pdf internal object, settings object and x and y. You have to remember to change x and y variables yourself after creating some content.

One last thing to remember is that the handler can return either true or false. If it returns true, then the engine will not create content out of the given DOM element on its own. Otherwise, after your handler finishes its execution, the engine will generate the content for you. So you can use those handlers to munge a few things in the output document or you can take over the whole control and do it your way.

Open PDF output in a new window

You may have notice in one of above examples, that we were simply creating new windows with PDF documents there.

To recap, opening PDF documents in new window:

doc.output 'dataurlnewwindow', {}

Render PDFs in the DOM

Nesting the PDF document inside the DOM seems way cooler. To do so:

data = doc.output 'dataurlstring', {}
$('#report').html "<iframe src='#{data}'></iframe>"

You will notice that the #output method takes a string which tells it how you’d like to generate the PDF. If you specify the ‘dataurlstring’ it will return a string containing the url data you use in your iframe. That is how the last example works.

There is much more to jsPDF than shown in this short post. If you’re interested, you can browse the docs included in the projects repository. There are a lot of useful methods for creating almost any type of document you would want.

NoSQL benchmark of Cassandra, HBase, MongoDB

We're excited to have recently worked on an interesting benchmarking project for DataStax, the key company supporting the Cassandra "NoSQL" database for large horizontally-scalable data stores. This was done over the course of about 2 months.

This benchmark compares the performance of MongoDB, HBase, and Cassandra on the widely-used Amazon Web Services (AWS) EC2 cloud instances with local storage in RAID, in configurations ranging from 1-32 database nodes. The software stack included 64-bit Ubuntu 12.04 LTS AMIs, Oracle Java 1.6, and YCSB (Yahoo! Cloud Serving Benchmark) for its lowest-common-denominator NoSQL database performance testing features. Seven different test workloads were used to get a good mix of read, write, modify, and combined scenarios.

Because cloud computing resources are subject to "noisy neighbor" situations of degraded CPU or I/O performance, the tests were run 3 times each on 3 different days, with different EC2 instances to minimize any AWS-related variance.

The project involved some interesting automation challenges for repeatedly spinning up the correct numbers and types of nodes, configuring the node software, running tests, and gathering and collating results data. We kept the AWS costs more reasonable by using Spot Instances for most instances.

You can read more at DataStax's white paper page and see all the details in the white paper itself.

Streaming Live with Red5 Media Server:Two-Way

I already wrote about the basics of publishing and broadcasting with Red5 Media Server. Let's fast forward to the advanced topics and create a video conference now!

Getting Ready

First, a word about the technology stack: a little bit of Java6/Java EE will be used for the server-side work (Red5 is written in Java), ActionScript2/Adobe Flash CS6 will be the primary tool for the client side development, and OS X Mountain Lion is my operating system.

Red5 Server comes with the set of sample applications that provide the source code for about everything you may want to achieve. The primary challenge is to unleash the power of it, since the samples fall extremely short of documentation! The "fitcDemo" application will serve as a base for all our customization.

Originally I made all the development in Red5 RC 1.0 version where fitcDemo was present. Unfortunately, when I downloaded the latest Red5 1.0.1 release yesterday it was simply not there! The source code was still in the repo, just outdated and not working. Well, I did all the work for Red5 team, so you can just download fitcDemo.war from my repo and drop it into the "webapps" directory of Red5 1.0.1 installation - and you are good!

You will then find the video conference demo at http://localhost:5080/demos/videoConference.html

Here is how it looks out of the box:

Here is how we want it to look!

The goal is to make our Red5 conference look as neat as Google Hangout.

Sleek Subscribers

Default video conference has five subscribers statically positioned on the stage. It's way more fun to have the subscribers added and removed on the fly as they connect to the server. So let's do that! I have the complete tutorial code based on Red5 1.0.1 The final version is in my Github repo, so I will be explaining parts of it further. Open videoConference.fla in Flash. I used CS6 for all the FLA/ActionScript editing in the tutorial.


Edit the VideoConference clip in the Library, to remove everything in it and add a ScrollPane with the following properties:


Edit the VideoPool clip, to remove everything in it as well and drag a Broadcaster clip to the top left corner:


Broadcaster and Subscriber clips should be modified too and have only the video component visible. You may look into the complete source code for details. Don't forget to change the Publish Settings to Flash Player 8; ActionScript2 and publish.

Modify Connector.as to point to the Red5 server. It is important to specify the IP address of the host machine rather than just "localhost", so other computers can join the hangout over network.

public static var red5URI:String = "rtmp://192.168.0.5/fitcDemo";

Make a slight change to configUI() to make it auto connect:

public function configUI():Void 
 {
  connection  = new Connection();
        
  connection.addEventListener("success", Delegate.create(this, onComplete));
  connection.addEventListener("onSetID", this);
  
  connection.addEventListener("newStream", this);
  connection.addEventListener("close", Delegate.create(this, onComplete));
  connected = connection.connect(red5URI, getTimer());
  dispatchEvent({type:"connectionChange", connected: connected});
 }
 
 private function onComplete(evtObj:Object):Void
 {
  dispatchEvent({type:"connectionChange", connected: evtObj.connected});
 }

Get rid of all the default Subscriber variables in the class and change configUI():

private function configUI():Void 
{
  subscriberList = [];
}

Open VideoPool.as and modify getVideoContainer() so that every subscriber will be dynamically created and positioned on stage. The row will have 4 streams with the broadcaster stream (your computer's camera) being the first in the first row, and more rows will be created in the scroll pane to accommodate more streams.

private function getVideoContainer(p_id:Number):Subscriber
{  
  var d:Number = 1;
  if (subscriberList.length > 0) {
    d = subscriberList[subscriberList.length - 1].getDepth() + 1;
  }
  
  var positionX:Number = broadcaster._x + ((subscriberList.length + 1)%4) * broadcaster._width;
  var positionY:Number = broadcaster._y + (broadcaster._height * Math.floor((subscriberList.length + 1)/4));
  
  attachMovie("org.red5.samples.livestream.videoconference.Subscriber", "subscriber_" + p_id, d, {_x:positionX, _y:positionY});
  
  var s:Subscriber = _level0.instance1.scrollPane.content["subscriber_" + p_id];
  subscriberList.push(s);
  return s;
}

The new Subscriber instance for the particular stream will be generated when subscribe() method is called.

public function subscribe(p_id:Number):Void
{
  if(p_id == "undefined" || isNaN(p_id) || p_id == "") return;
  
  var s:Subscriber = getVideoContainer(p_id);
  
  s.subscribe("videoStream_" + p_id, connection);  
}

If the subscriber is added, at some point it may need to be removed! Add removeSubscriber() function that will delete the disconnected subscriber and reposition all the other subscribers and invalidate ScrollPane.

public function removeSubscriber(s:Subscriber): Void {
  for(var i= 0; i < subscriberList.length; i++) { 
    if (Subscriber(subscriberList[i]).videoStream == s.videoStream) {
      subscriberList.removeItemAt(i);
    }
  } 
  for (var i= 0; i < subscriberList.length; i++) {
    var positionX:Number = broadcaster._x + (subscriberList.length%4) * broadcaster._width;
    var positionY:Number = broadcaster._y + (broadcaster._height * Math.floor(subscriberList.length/4));
    var mc:Subscriber = Subscriber(subscriberList[i]); 
    mc._x = positionX;
    mc._y = positionY;
  }
  s.removeMovieClip();
  _level0.instance1.scrollPane.invalidate();
}

Look into Subscriber.as. When the subscriber disconnects the stream fires an "unpublishNotify" event, that eventually makes a call to the removeSubscriber() function.

public function subscribe(p_subscriptionID:String, p_connection:Connection):Void 
{
  ...
  stream.addEventListener("unpublishNotify", Delegate.create(this, streamStop));
  ...
}

public function streamStop(evtObj:Object):Void
{
  videoPool.removeSubscriber(this);
  publish_video.clear();
  stream.close();
}

Finally open VideoConference.as. This is a controller class, the Red Queen of all the above classes! It manages all the incoming subscribing streams and the broadcasting stream. When your computer is ready to broadcast, the camera is up and the broadcasting stream received its id, VideoConference sends requests to get all the publishing streams to the url on the server and process them as the subscribers.

private function configUI():Void 
{
  ...
  videoPool.broadcaster.addEventListener("onSetID", this);
  ...
}

private function setID(p_id:Number, p_connection:Connection):Void
{
  //set local videoID
  videoID = Number(p_id);

  // set connection
  connection = p_connection;

  getStreams();
}

private function getStreams():Void
{
  connection.call("getStreams", this.result);
}

Since VideoPool with all the streams loads into the ScrollPane component now, VideoConference needs to be updated with the correct VideoPool reference:

private var videoPool:VideoPool;
private var scrollPane:ScrollPane;
...
private function configUI():Void 
{
  ... 
  scrollPane.setStyle("borderStyle", "none");
  videoPool = VideoPool(scrollPane.content); 
  
  videoPool.broadcaster.registerController(this);
  videoPool.broadcaster.addEventListener("connected", this);
  videoPool.broadcaster.addEventListener("disconnected", this);
  videoPool.broadcaster.addEventListener("onSetID", this);
}

We could wrap the last four lines in some kind of configUI() function on VideoPool, but haven't I told you? Refactoring will be your homework assignment!

When the new subscriber shows up, we add him or her and update ScrollPane.

private function processQue():Void
{
  if(streamQue.length <= 0) 
  {
    clearInterval(si);
    return;
  }
  
  var id:Number = Number(streamQue.shift().split("_")[1]);

  videoPool.subscribe(id);

  scrollPane.invalidate();
}

Almost ready! Publish the swf file and copy to the website folder. If you want to re-compile ActionScript classes only, use MTASC compiler and MX libraries from my Github repo:

> cp videoConference.swf classes/videoConference.swf
> cd classes
> export PATH="$HOME/mtasc-mx/bin:$HOME/mtasc-mx/bin/std:$PATH"
> mtasc -version 8 -cp "." -swf videoConference.swf -mx org/red5/samples/livestream/videoConference/videoConference.as -v
> cp videoConference.swf $HOME/Sites/red5-hangout/videoConference.swf

Include into the webpage:


Stylish Spotlight

The "Spotlight" component is the larger video of the "talking" person that shows up when one of the smaller previews is clicked. simpleSubscriber.fla is the ideal base for this component:

Subscriber and Broadcaster will respond to the "click" event and call JavaScript function with the video stream name as a parameter. The video stream name is just a string like "videoStream_12" denoting the 12th stream accepted by "fitcDemo" application.

In Broadcaster.as:

private function configUI():Void 
{   
  this.onRelease = function(){
    _global.tt('Broadcaster ' + this.videoStream + ' clicked.'); 
    if (ExternalInterface.available) {
      ExternalInterface.call("spotlight", this.videoStream);
    }
  }
} 

In Subscriber.as

public function configUI():Void 
{
  this.onRelease = function() {
    ExternalInterface.call("spotlight",this.videoStream);
  }
};

JavaScript will embed the SimpleSubscriber clip and pass along the video stream name that should be played via flashvars.

On the page


In SimpleSubscriber Main.as:

private var streamName:String;
...
private function configUI():Void 
  { 
    streamName = String(_level0.streamName); 
    ... 
  }
private function connectionChange(evtObj:Object):Void
{  
  if(evtObj.connected) 
  {
   
    stream = new Stream(evtObj.connection);
   
    stream.play(streamName, -1);
   
    publish_video.attachVideo(stream); 
  }
}

Publish simpleSubsciber.swf and place it into the website folder. To recompile the ActionScript part only:

> mtasc -version 8 -cp "." -swf simpleSubscriber.swf -mx org/red5/samples/livestream/subscriber/Main.as -v
> cp simpleSubscriber.swf $HOME/Sites/red5-hangout/simpleSubscriber.swf

By the way the -version 8 flag for mtasc was added specifically to compile ExternalInterface, otherwise, these libraries would not be found.

Chat in the absence of Sound

One thing I really appreciate about the Google Hangout architecture is that it does not just make the whole page a bulky <embed> or <object> and lock everything into the external component. I love how they use the familiar and friendly JavaScript and HTML to add some interactive features. That's why I decided to break the single VideoConference component into pieces as well and tie them together on a web page.

The original conference had its audio support commented out with the note about performance issues. Bummer! This will surely need to be addressed, but for now I decided to use the existing chat and make it separate. Create a blank document chat.fla and drag Chat clip from the VideoConference Library to the stage.

Remove any reference to VideoConference and VideoPool from Chat.as, create its own GlobalObject and Connection and paste the sound settings from VideoConference.as:

private var red5URI = "rtmp://192.168.0.5/fitcDemo";
...
public function configUI():Void 
{  
  streamID = Number(_level0.streamName.split("_")[1]);
    
  ...
  
  loadProfile("videoConference");
  
  var my_nc:Connection = new Connection();
  my_nc.connect(red5URI);

  chatID = "videoConferenceChat";
  connected = so.connect(chatID, my_nc, false);
  
  ...

  sndTarget = this.createEmptyMovieClip("sndTarget", this.getNextHighestDepth());
  snd = new Sound (sndTarget);
  snd.attachSound("newChatMessage");
  snd.setVolume(80);
   
  soundPlay._visible = false;
  soundMute.addEventListener("click", Delegate.create(this, updateMute));
  soundPlay.addEventListener("click", Delegate.create(this, updateMute));
  soundMute.tooltip = "Mute new chat sound";
  soundPlay.tooltip = "Un-mute new chat sound";
}

Initialize the chat in Broadcaster.as after the stream is initialized:

private function onSetID(evtObj:Object):Void
{
  ...
  ExternalInterface.call("chat", this.videoStream);
} 

Publish or recompile:

> mtasc -version 8 -cp "." -swf chat.swf -mx org/red5/samples/livestream/videoConference/Chat.as -v
> cp chat.swf $HOME/Sites/red5-hangout/chat.swf

Add the chat to the web page:


<div id="chat" style="display:none">
</div>

And enjoy chatting with yourself for a while in different browser tabs:

Final result

After a bit of styling and herding my test participants, here is what I got:

Questions, questions

There are problems to be addressed yet.

First, there is no audio. It's commented out in the original code with the note about performance issues. Silence may be golden, but the grimace-enhanced chat experience is totally not acceptable for production!

Second, the whole performance talk brings up the other important questions: How many subscribers can this setup handle? Is it possible to achieve the better video quality? Why is the video stream choppy at times?

Finally, every application needs a mobile presence. Will it be possible to port the client to mobile devices using AIR? How to port to iPads and iPhones?

The flash source code in its entirety is located in my Github repo. Please, keep in mind, that the code is not perfect, and the logic can be better organized between classes with event handlers. The demo application can be found here. MTASC compiler and MX libraries for compilation are available here.

I would love to hear your feedback on this!

Data binding in web applications

Ever since JavaScript was introduced - the world of web programming was constantly been visited by creative minds, ready to solve burning problems web developers were experiencing.

Navigating through some of the oldest web-dev articles, we still can feel the pain of manipulating the DOM, the-vanilla-way and creating ever so clever hacks to make the code work on all of the web browsers.

Then, the JS-frameworks epidemy started to disseminate. As web developers - we started to have some powerful tools. The productivity of average nerdy Joe raised immediately as he started using Prototype.js, Mootools or jQuery.

Evolution of UI programming for browsers

And so was the start of our global evolution, towards making the Web - our main means of interacting with users. Few with big enough imagination were already seeing HTML, CSS & JS as the future standard of creating user interfaces for data-rich applications. Of course, there were toolkits like ExtJS which aimed at nothing but this - but who would have known back then, that we will be able to build apps for smartphones the way we are building ones for the web?

I have my little observation in life — all hot stuff in the world of technology, has their phases.. That is:

  • they don’t exist, but the problems they solve do and they are painful
  • they show up, and there is massive excitement
  • people discover new pains brought on by these solutions
  • next pain and the beginning of the "next big thing"

At the beginning it was painful to write all the vanilla JS code without shooting yourself in your own foot (well let’s just say it - JS is Pandora’s Box of pain). Then you realize you’ve got your sweet & cool jQuery with which you can rapidly impress your boss/clients/friends.

Your next surprise is that jQuery isn’t enough - ever wondered why frameworks like Backbone.js were created?

Data binding

Here is the challenge for you: write GMail web interface clone using just jQuery. Here is the spoiler: you’d get lost in managing your app’s state - and syncing UI to reflect it. In fact - even small todo app in vanilla jQuery could end up being quite complicated. That’s just not the way other folks are dealing with UI programming. A long time ago they were - but they got smarter. We can learn from their mistakes.

When I was beginning my journey into the world of professional programming, I was given a job of creating small business app with C# and Windows Forms. Despite the fact that in the world of desktop apps you have to be mindful of much more things than in the web/browsers world (like updating your UI only on the main thread), the whole experience was pretty much the same as with vanilla jQuery:

  1. create inputs
  2. subscribe to its events
  3. write the code that updates inner state based on the state of those inputs/controls

This quickly gets very cumbersome as the needs grows. You could have a requirement that one set of inputs is dependent on another, which results in hours of coding and more hours in debugging.

The way smart devs are tackling this issue is called data binding. This simply means that you tell specific portion of UI to be bound to some value from your business layer.

One nice example of the framework that employs data binding is Apple’s Cocoa. The XCode even has nice visual tools to bind controls values with the data model. Another example of the framework that uses data binding extensively is WPF (Windows Presentation Foundation). The concept widely used there is the MVVM pattern (Model-View-ViewModel). Which basically says that you have your view model object and UI which is bound to the view model and all you have to do is to manage the view model.

That’s the core concept here: managing objects is much easier than managing UI

Data binding on web

As the need came with more and more amazing web apps - we now have quite a nice choice when it comes to employing data binding:

Angular.js
Angular.js is one of the most impressive front-end JS frameworks there are. It has view templates mechanism allowing you to specify your binding statements directly there.

Ember.js
Ember.js gains popularity every day. Just like Angular.js, it has its own nice templating language (handlebars) which allows you to have your binding statements in the markup too.

Knockout.js
If you don’t want to use any of those frameworks, that's a great library that implements just the data binding part.
It uses the concept I described before: the MVVM pattern. You are simply defining your view model and telling your UI where it has to get values from and that's it.

Backbone.js
But what if you’d like to use Backbone.js? It’s true that it’s amazing and is one neat piece of technology you can use to build your next ‘cool app’. There is one caveat though: you have to set up all the niceties that comes along with Angular.js or Ember.js yourself, including all the sweetness of data binding.

Backbone.js + Knockout.js

Here is a simple way of using Knockout.js with Backbone.js:

  • for every Backbone.js view, define complementary view model
  • put ‘ko.applyBindings’ at the end of your ‘render’ method
  • do not use any logic in your view templates - rely solely on data binding

So for example:

# app/views/photos.coffee

module.exports = class PhotosView extends Backbone.View
   template: require "./templates/photos/index"

   initialize: () ->
       @view = new PhotosViewModel()
       @render()

   render: () ->
       $(@el).html(@template())
       ko.applyBindings(@view, @el)

   setData: (photos) =>
       @view.photos(photos)

class PhotosViewModel
   constructor: ->
       @photos = ko.observableArray []

# app/views/templates/photos/index.eco

Now - with this, you can manipulate the photos array however you like, and the UI will get auto-updated on its own. But this idea shines the most when it comes to implementing forms with it. Take a look at the following example:

# app/views/company.coffee

module.exports = class EditCompanyView extends Backbone.View
   template: require "./templates/companies/edit"

   initialize: () ->
       @view = new EditCompanyViewModel()
       @render()

   render: () ->
       $(@el).html(@template())
       ko.applyBindings(@view, @el)

   setData: (company) =>
       @view.company(company)

class EditCompanyViewModel
   constructor: ->
       # let’s instantiate it with default company for bindings to work
       @company = ko.observable(new Company())
       @companyName = ko.computed
           read: => @company.get('name')
           write: (value) => @company.set('name': value)
       @companyState = ko.computed
           read: => @company.get('state')
           write: (value) => @company.set('state': value)
       @companyWebsite = ko.computed
           read: => @company.get('url')
           write: (value) => @company.set('url': value)
       @saveCompany = () =>
           @company.save()

# app/views/templates/companies/edit.eco

Name: State: Website:

Other solutions

These are quite contrived examples to give you an idea of how it can be done. The beauty of Backbone is that it allows you to structure your code however you’d like. And there are plenty of other solutions for this. Most notable: