Mobile Payments

A few weeks ago, I was supposed to be on a panel about this two weeks ago. I got sick and couldn’t do it but I did a bunch of digging around/thinking about it.

A friend just asked me why no-one has risen through the noise. I didn’t think about that particular issue but here are some thoughts I did have:

NFC is not new – Nokia for a while – I don’t think it will make a big difference in the use of mobile payments. (Contactless payments aren’t new either, Citibank/MTA, EZ Pass and so on have been using them for a while)

Overall Issues – trust – Who do people want to put their trust in: carriers? banks? manufacturers?

Big winner likely: Amazon – will retailers trust – consumers say they are the most reputable company in the world.

Other Players:
Isis – carrirers – T-Mobile, AT&T and Verizon
Google – android – probably open – Citibank and Mastercard
Apple – ios – probably will take a big cut
Apple is probably required – as much as I hate to say it – not friendly to small developers – donations

What works now: – points the way towards what will work in the future
Square – fits with what people already do – friendly to small merchants – attaches directly to phone – simple pricing
Starbucks – App linked to CC – Just scan it and the backend system will charge your CC – Control in hands of consumer

Samsung – Olympics – London 2012 big trial

Business Card Transfers
I wonder/fear it will look pretty much like how we exchange contact information now (via paper business cards).
Standards didn’t catch on – security warnings about bluetooth – same will happen with NFC??

NFC will have to be the most secure tech in the world. There is nothing more juicy for a cracker to hack something that is used to transfer money. People will react with viscerally as well. The local news channels will run piece after piece. The wikipedia page is already filled with potential exploits: eavesdropping, data modification, relay attack, lost property, …

Live Experimental Interactive Television

The students from my class (Live Experimental Interactive Television) at ITP are on again tonight. This week it is Unitv:
Unitv Logo

UnItv is an exciting new venture in live television. Performed as a local news broadcast, the ‘news team’ is given real-time user submitted content (images, questions, lyrics, and youtube videos) on which they improv the news, weather, a celebrity interview and a performance with a special musical guest. The home television audience is provided with a two-screen setup where they use our website to become part of the show. In the spirit of true improvisation, our cast is dependent upon user suggestions to make up the show as they string one joke along to the next. UnItv was created through the Interactive Telecommunications Program (ITP) at NYU in collaboration with Manhattan Neighborhood Network and airs on the Manhattan Neighborhood Network Tuesday April 20th at 8:30pm. For more information, check out http://unitv.me.

If you are in Manhattan, you can watch on MNN at 8:30 PM:
Time Warner Cable channel 34
RCN channel 82
Verizon FiOS channel 33
(all in Manhattan only)

If you aren’t in Manhattan, there *should be* a live stream of the program on http://unitv.me

Hope you catch it!

Motion JPEG in Flash and Java

I recently had the opportunity to develop a solution in both Java and Flash for pulling Motion JPEG streams from IP cameras and thought it might be nice to document a bit.

Motion JPEG is generally served via HTTP from IP cameras as a single file. Meaning, the connection stays open and the camera just keeps sending individual JPEG images down the pipe. The images should start with a MIME boundary message such as:


--myboundary
Content-Type: image/jpeg
Content-Length: 22517

or

--randomstring
Content-Type: image/jpeg
Content-Length: 22598

The key in the development is to find the boundary and save the bytes between each and treat that as a JPEG image. Neither of these snippets are great or even complete but they should give you a bit of a start.

Java:

package com.mobvcasting.mjpegparser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;

public class MJPEGParser {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MJPEGParser mp = new MJPEGParser("http://192.168.1.10/mjpg/video.mjpg", "username", "password");
	}

	public MJPEGParser(String mjpeg_url)
	{
		this(mjpeg_url,null,null);
	}
	
	public MJPEGParser(String mjpeg_url, String username, String password)
	{
		int imageCount = 0;
		
		try {
			if (username != null && password != null)
			{
			    Authenticator.setDefault(new HTTPAuthenticator(username, password));
			}
			
            URL url = new URL(mjpeg_url);
            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
            String inputLine;
            int lineCount = 0;
            boolean lineCountStart = false;
            boolean saveImage = false;
            while ((inputLine = in.readLine()) != null) {
            	// Should be checking just for "--" probably
            	if (inputLine.lastIndexOf("--myboundary") > -1)
            	{
            		// Got an image boundary, stop last image
            		// Start counting lines to get past:
            		// Content-Type: image/jpeg
            		// Content-Length: 22517

            		saveImage = false;
            		lineCountStart = true;
            		
            		System.out.println("Got a new boundary");
            		System.out.println(inputLine);
            	}
            	else if (lineCountStart)
            	{
            		lineCount++;
            		if (lineCount >= 2)
            		{
            			lineCount = 0;
            			lineCountStart = false;
            			imageCount++;
            			saveImage = true;
                		System.out.println("Starting a new image");

            		}
            	}
            	else if (saveImage)
            	{
            		System.out.println("Saving an image line");
            	}
            	else {
            		
            		System.out.println("What's this:");
            		System.out.println(inputLine);
            	}
            }
            in.close();            
        } catch (IOException e) {
            e.printStackTrace();
        }		
		
	}
	
	
	static class HTTPAuthenticator extends Authenticator {
	    private String username, password;

	    public HTTPAuthenticator(String user, String pass) {
	      username = user;
	      password = pass;
	    }

	    protected PasswordAuthentication getPasswordAuthentication() {
	      System.out.println("Requesting Host  : " + getRequestingHost());
	      System.out.println("Requesting Port  : " + getRequestingPort());
	      System.out.println("Requesting Prompt : " + getRequestingPrompt());
	      System.out.println("Requesting Protocol: "
	          + getRequestingProtocol());
	      System.out.println("Requesting Scheme : " + getRequestingScheme());
	      System.out.println("Requesting Site  : " + getRequestingSite());
	      return new PasswordAuthentication(username, password.toCharArray());
	    }
	  }	
}

ActionScript 3

import flash.display.Sprite;
    import flash.errors.*;
    import flash.events.*;
    import flash.net.URLRequest;
    import flash.net.URLStream;
	import flash.utils.ByteArray;

	var stream:URLStream;
	var mjpegBuffer:ByteArray = new ByteArray();
	// The actual image
	var imageBytes:ByteArray; // = new ByteArray();
	// The chars at the end of the image
	var endPos:String = "\n--myboundary";

	// Started to find, finished finding
	var done:Boolean = false;
	var started:Boolean = false;
			
	// Don't know why I have to save these to a ByteArray to do the comparisons but it seems I do
	var startBytes:ByteArray = new ByteArray();
	var startByte:int = 0xFF;
	var secondByte:int = 0xD8;
	startBytes.writeByte(0xFF);
	startBytes.writeByte(0xD8);			
	trace(startBytes.length);
	var startNum:int = startBytes[0];
	trace(startNum);
	var nextNum:int = startBytes[1];
	trace(nextNum);

	// Open the stream			
	stream = new URLStream();
    var request:URLRequest = new URLRequest("http://192.168.1.10/mjpg/video.mjpg?resolution=160x90&fps=1");
    configureListeners(stream);
    try {
    	stream.load(request);
    } catch (error:Error) {
    	trace("Unable to load requested URL.");
    }

	function configureListeners(dispatcher:EventDispatcher):void {
            dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
    }

         function progressHandler(event:Event):void 
		 {
		 	trace("Running");
			stream.readBytes(mjpegBuffer,mjpegBuffer.length,stream.bytesAvailable);
			for (var i:int = 0; i < mjpegBuffer.length; i++)
			{
				var currentByte:int = mjpegBuffer[i];
				var nextByte:int = mjpegBuffer[i+1];
				var thirdByte:int = mjpegBuffer[i+2];
				var fourthByte:int = mjpegBuffer[i+3];

				//var randNum:Number = Math.random();
				//if (randNum > .5 && randNum < .6) { trace(currentByte); }
				
				if (!started)
				{
					if (currentByte == startNum && nextByte == nextNum)
					{
							trace("Started");
							started = true;
							imageBytes = new ByteArray();
							imageBytes.writeByte(currentByte);
							//imageBytes.writeByte(0xD8); // Gets written in the else
					}
				}
				else
				{
					if (currentByte == endPos.charCodeAt(0) && nextByte == endPos.charCodeAt(1) && thirdByte == endPos.charCodeAt(2) && fourthByte == endPos.charCodeAt(3))
					{
						trace("done");
						trace(imageBytes);
						done = true;
						started = false;
						
						var loader:Loader = new Loader();
						loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onByteArrayLoaded)
						loader.loadBytes(imageBytes);
						//stream.close();
					}
					else
					{
						imageBytes.writeByte(currentByte);
					}
				}
			}
        }
		
		function onByteArrayLoaded(e:Event):void  
		{
			var loader:Loader = Loader(e.target.loader);
			loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onByteArrayLoaded);

			var bitmapData:BitmapData = Bitmap(e.target.content).bitmapData;
			
			//sprLoaded.graphics.clear();
			graphics.beginBitmapFill(bitmapData);
			graphics.drawRect(0,0,bitmapData.width, bitmapData.height);
			graphics.endFill();
			
			
			
		}