November 30, 2010

Android: Phonegap issue with HTML5 video

We've been trying to implement HTML5 video inside a WebView in an Android Phonegap app, and there's a big difference between the embedded web browser and the native web browser on the Samsung Galaxy Tab (and likely other Android devices). In the native browser, an HTML5 <video> player will pop the video into the native media player, and should play fine if you've used an appropriate codec, and jumped through the right hoops. But, in an embedded browser in a Phonegap app, the video won't play at all. We resorted to using an <a> link with a _blank target to pop you completely out of the app. This was the only solution we could come up with. It's a pretty sad story dealing with the "standards" of HTML5 video across all the platforms and browsers that supposedly support it. I'm not sure there's a good way to deal with all of the different platforms/scenarios...

November 29, 2010

Javascript: red plasma experiment

I ported an old Processing experiment of mine today, as an exercise to get myself back into coding after a couple weeks of vacation. Copy and paste the code into your own html file to try it out:
<!DOCTYPE html>
<html>
  <head>
    <title>Plasma</title>

    <script type="text/javascript">
        function initPlasma()
        {
            /* MATH FUNCTIONS ------------------------------ */
            
            function MathUtil() {}

            MathUtil.getDistance = function ( a, b ) {
                return Math.abs( Math.sqrt(a*a + b*b) );
            };
            
            MathUtil.randRangeDecimel = function ( min, max ) {  
                return Math.random() * ( max - min ) + min;
            };
            
            /* GRID CELL CLASS ------------------------------ */
            
            var Cell = function( x, y, w, h ) {
                this.x = x;
                this.y = y;
                this.w = w;
                this.h = h;
            }
            
            Cell.prototype.update = function( r, g, b ) {
                this.r = r;
                this.g = g;
                this.b = b;
                                
                this.draw();
            };
            
            Cell.prototype.draw = function() {
                if( !plasma ) return;
                
                // get color, based on distance 
                var ctrlPt1 = MathUtil.getDistance( this.x - plasma.controlPoints[0].x, this.y - plasma.controlPoints[0].y );
                var ctrlPt2 = MathUtil.getDistance( this.x - plasma.controlPoints[1].x, this.y - plasma.controlPoints[1].y );
                var ctrlPt3 = MathUtil.getDistance( this.x - plasma.controlPoints[2].x, this.y - plasma.controlPoints[2].y );
                
                var rVal = .5+.5*Math.sin(this.r) * Math.cos(ctrlPt1/100) * Math.cos(ctrlPt2/100) * Math.sin(ctrlPt3/100);
                var gVal = .2+.5*Math.sin(this.g) * Math.sin(ctrlPt1/100) * Math.sin(ctrlPt2/100) * Math.sin(ctrlPt3/100);
                var bVal = .2+.5*Math.cos(this.b) * Math.sin(ctrlPt1/100) * Math.cos(ctrlPt2/100) * Math.sin(ctrlPt3/100)
                
                // draw pixel to canvas
                plasma.context.fillStyle = "rgb("+ Math.round( 127 + rVal * 255 ) +","+ Math.round( 127 + gVal * 255 ) +","+ Math.round( 127 + bVal * 255 ) +")"; 
                plasma.context.fillRect ( this.x, this.y, this.w, this.h );  
            };
            
            
            /* CONTROL POINT CLASS ------------------------------ */
            
            var ControlPoint = function( canvasW, canvasH ) {
                // create random x,y starting point 
                this.incX = MathUtil.randRangeDecimel( 0, 2 * Math.PI );
                this.incY = MathUtil.randRangeDecimel( 0, 2 * Math.PI );
                // create random x,y oscillating speed 
                this.incXSpeed = MathUtil.randRangeDecimel( .01, .1 );
                this.incYSpeed = MathUtil.randRangeDecimel( .01, .1 );
                // store center point to oscillate around
                this.centerX = canvasW / 2;
                this.centerY = canvasH / 2;
            }
            
            ControlPoint.prototype.update = function() {
                // increment oscillating based on randomly-calculated speed
                this.incX += this.incXSpeed;
                this.incY += this.incYSpeed;
                // update coordinate
                this.x = this.centerX + this.centerX * Math.sin( this.incX );
                this.y = this.centerY + this.centerY * Math.sin( this.incY );
            };
            
            
            /* PLASMA CLASS ------------------------------ */
            
            var Plasma = function() {
                this.COLS = 50;
                this.ROWS = 50;
                this.CANVAS_W = 500;
                this.CANVAS_H = 500;
                this.FPS = 1000/30;
                this.NUM_CONTROL_POINTS = 3;
                
                this.startR = MathUtil.randRangeDecimel(0,2*Math.PI);
                this.startG = MathUtil.randRangeDecimel(0,2*Math.PI);
                this.startB = MathUtil.randRangeDecimel(0,2*Math.PI);
                this.startIncR = MathUtil.randRangeDecimel(.001,.05);
                this.startIncG = MathUtil.randRangeDecimel(.001,.05);
                this.startIncB = MathUtil.randRangeDecimel(.001,.05);
                this.incR = MathUtil.randRangeDecimel(.0001,.001);
                this.incG = MathUtil.randRangeDecimel(.0001,.001);
                this.incB = MathUtil.randRangeDecimel(.0001,.001);
                
                this.canvas;
                this.context;
                this.grid;
                
                this.buildStage();
                this.createGrid();
                this.createControlPoints();
                this.addSaveFunctionality();
                
                var self = this;
                setInterval( function(){ self.update(); }, this.FPS );
            };
        
            Plasma.prototype.buildStage = function() {
                // create and attach canvas element
                this.canvas = document.createElement('canvas');
                this.canvas.width = this.CANVAS_W;
                this.canvas.height = this.CANVAS_H;
                document.body.appendChild( this.canvas );
                
                // store graphical context
                this.context = this.canvas.getContext("2d");
            };
            
            Plasma.prototype.createGrid = function() {
                // calculate "pixel" size
                var boxW = this.CANVAS_W / this.COLS;
                var boxH = this.CANVAS_H / this.ROWS;
                
                // create 2D array of grid cells
                this.grid = new Array( this.COLS );
                for( var i = 0; i < this.COLS; i++ ) {
                    this.grid[ i ] = new Array( this.ROWS )
                    for( var j = 0; j < this.ROWS; j++ ) {
                        this.grid[ i ][ j ] = new Cell( i * boxW, j * boxH, boxW, boxH );
                    }
                }
            };
            
            Plasma.prototype.createControlPoints = function() {
                this.controlPoints = [];
                for ( var i = 0; i < this.NUM_CONTROL_POINTS; i++ ) {
                    this.controlPoints.push( new ControlPoint( this.CANVAS_W, this.CANVAS_H ) );
                }
            };
            
            Plasma.prototype.addSaveFunctionality = function() {
                var self = this;
                this.canvas.addEventListener("click", function(e) { 
                    window.open( self.canvas.toDataURL("image/jpeg") ); 
                }, false);
            };
            
            Plasma.prototype.update = function() {
                
                // increment the starting colors
                this.startR += this.startIncR;
                var curR = this.startR;
                this.startG += this.startIncG;
                var curG = this.startG;
                this.startB += this.startIncB;
                var curB = this.startB;
                
                // update control points
                for ( var i = 0; i < this.NUM_CONTROL_POINTS; i++ ) {
                    this.controlPoints[i].update();
                }
                
                // increment grid cells and draw to canvas
                for (var i = 0; i < this.COLS; i++) {
                    for (var j = 0; j < this.ROWS; j++) {
                        // send new base color to cells
                        this.grid[i][j].update( curR, curG, curB );
                        
                        // increment color as we traverse the grid
                        curR += this.incR;
                        curG += this.incG * 3;
                        curB += this.incB;
                    }
                }
            };
            
            
            // kick off the plasma controller
            var plasma = new Plasma();     
        }
    </script>
        
    <style>
        body, html {
            background-color:black;
        }
    </style>
  </head>
  <body onload="initPlasma();"></body>
</html>

And the result:

November 4, 2010

Android: Phonegap 0.9.2 doesn't work with Prototype.js 1.6.1

This morning I upgraded to Phonegap 0.9.2 on an HTML-based native app for Android, in order to use the new notification.confirm() function. After moving my project to this new version, I started getting this error:
Error initializing PhoneGap: JSON error
I was using Prototype.js 1.6.1, and after a bunch of investigation, I determined that it was Prototype that was causing the error. I replaced 1.6.1 with 1.7_rc3, and magically, everything works. I looked into why this might be happening, but decided to move on with my life and just go with 1.7_rc3 :)

Hopefully this post saves someone a little madness.