Category Archives: javascript

Using Flash to shim a webcam to Canvas

A couple of weeks ago I posted my work on developing virtual keypad using HTML5 video. That worked surprisingly well, but had some unfortunate requirements in that HTML5 doesn’t really support access to webcams.

Flash Does.

I’ve developed a Flash shim, which gets access to your webcam, and copies frames to data urls which can be used in HTML DOM images.

This works well, within some limits:

  • The performance of the Flash->Javascript interface is adequate for normal use, but drops off as the data being copied across increases in size. This means that you need to have a postage stamp sized (80×60) video in flash to get adequate performance in HTML (~25 fps).
  • Chrome (and Safari?) leaks memory when new data URLs are created. (See bug). Apart from that, this technique works on both Chrome & Firefox, but my virtual keypad is Firefox only ATM
  • I don’t really know Flash. I downloaded the Flex SDK a few days ago and started hacking, so I’m pretty sure my Flash code sucks.

Here’s a demo. Take a look at the previous post to see how you are supposed to use it. I started work on making a proper Javascript API for it, but yeah… The code is on GitHub, though: http://github.com/nlothian/FlashCamShim

Cross domain, cross window Javascript communication

The scenario: A typical meeting, they want to pop up our search screen, select an item and have it post back via Javascript the selected item. “Sure, no problem” I say. “Can you do cross domain Javascript like that” their dev asks, and I think for a second about all the IFrame proxy hacks I’ve seen and say “yeah, no problems”.

Doh.

So I get back to work, and talk to Ben-out-local-Javascript-god, who points me at the location-fragment for communication hack (see, for example Cross Domain Frame Communication with Fragment Identifiers (for Comet?)) and away I go.

I quickly discover that all the documentation on that is for use with frames, not windows, and windows work slightly different. For example, the parent window can’t poll the child for it’s location.hash, nor can the child set the parent’s location.hash.

Doh Doh.

Anyway to cut a long story short and to document it for anyone else, you can make it work. The child window needs to contain a iframe from the parent domain, then proxy communication though that. Modern non IE browsers can use Window.postMessage to do the same thing (Actually, IE8 supports Window.postMessage, too, but I couldn’t get it to work properly).

The core of the IE specific code looks like this. The child window uses this object:

/*
  XDCommsChild - Cross Domain Communication, Child Object.

   Create this in the child window with the address of the xdomaincomms.html file on the same domain as the parent window
*/
var XDCommsChild = function(xdomaincommsAddress) {
    this.xdomaincommsAddress = xdomaincommsAddress;

    this.isIe = isInternetExplorer();


    if (isIe) {
        // need hidden frame for communication. IE8 is supposed to support postMessage, but I can't get it to work properly
        document.writeln("")
    }

    this.postBack = function(data) {
        if (isIe) {
            // this method tested against IE6,7 & 8
            window.open(this.xdomaincommsAddress + '#data=' + data, 'xdcomms'); // MUST use window.open. frame.src or frame.location both fail
        } else {
            // for everything else - tested against Firefox 3, Chrome, Safari
            window.opener.postMessage(data, '*'); // should really restrict the domain data can come from here
        }
    }

    this.postBackAndCloseWindow = function(data){
        this.postBack(data);
        setTimeout("window.close()", 200); // need to use a timeout to make sure the javascript in the frame has executed if we are using that communication model
    }
}

Then the included proxy frame (deployed on the parent domain) looks like this:

          
           var hash = null;

            function sendData() {
                hash = location.hash;
                if (hash != null && hash.length > 1) {
                    window.parent.opener.location.hash=hash;
                    location.hash = '#';
                }
            }

            setInterval("sendData()", 50);

The the parent window uses this object:

/*
  XDCommsParent - Cross Domain Communication, Parent Object.

   Create this in the parent window with a callback function that will recieve data.
*/
var XDCommsParent = function(dataRecievedCallback) {
    this.dataRecievedCallback = dataRecievedCallback;
    
    this.isIe = isInternetExplorer();

    this.receiveMessage = function(event) {
        dataRecievedCallback(event.data);
    }

    if (this.isIe) {
        window.setInterval("pollForData()", 200);
    } else {
        window.addEventListener("message", this.receiveMessage, false);
    }

}

The final piece of the solution is calling it from the child window. I do this:

<button onclick="javascript:xDCommsChild.postBackAndCloseWindow(document.getElementById('thevalue').value);">Send to Parent Window</button>

DWR Callback closures inside loops

I’ve been doing a fair bit of Javascript lately. Like anytime I learn a new language I look back at code I wrote a week ago and thing OMG… what was I thinking!?

One useful thing I learnt today was how to use the DWR closure callback pattern inside a loop.

My original code looked like this:


var taggedLinks = $$(".taggedlink");

for(var i=0; i < taggedlinks.length; i++) {
	RatingApi.getRating(taggedLinks[i].href, {
		callback: function(data) {
			displayRating(data, taggedLinks[i]);
		}
	});
}

Where RatingApi.getRating(..) is a DWR AJAX call. It’s fairly clear what the problem is: the displayRating function is called when RatingApi.getRating returns. Obviously the value of i will be not what was expected.

The solution looks like this (thanks Ben!):


var taggedLinks = $$(".taggedlink");

for(var i=0; i < taggedlinks.length; i++) {
	var remoteCall = {
		index : i,
		callback: function(data) {
			displayRating(data, taggedLinks[this.index]);
		}
	};	

	RatingApi.getRating(taggedLinks[i].href, remoteCall);
}