EladElrom.com

Deep Dive Into Technology

Update HTML document layout (width & height) using Javascript after browser resize

To adjust the page’s layout you need to listen to change in browser size event. You can do that in jQuery easily and the method in jQuery already taking care of a lot of the logic needed, however if you want to just use Javascript you can. JS has everything you need to get it working.

var oldWidth;
var oldHeight

function resizeHandler()
{
	var newWidth;
	var newHeight;

	if( typeof( window.innerWidth ) == 'number' )  //Non-IE
	{
		newWidth = window.innerWidth;
		newHeight = window.innerHeight;
	}
	else if( document.documentElement &&
	( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) //IE 6+
	{
		newWidth = document.documentElement.clientWidth;
		newHeight = document.documentElement.clientHeight;
	}
	else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) )  // IE4
	{
		newWidth = document.body.clientWidth;
		newHeight = document.body.clientHeight;
	}

	if ( newWidth != oldWidth )
	{
               // implement -- do something

		oldWidth = newWidth;
		oldHeight = oldHeight;
	}
}

Notice that I am checking if the actual size was changed since often the browser will call this method several times as the user resize it’s browser. Also notice that this code is set to take into account different types of browsers.

Than you can set the body to call this method onload

<body onload='parent.resizeHandler()'>

I also noticed that sometimes the event is called before the page completed the layout process so I added a timer:

function callChatAfterOnLoadCompleted()
{
	var t2=setTimeout("parent.resizeHandler()", 500);
}

And on window resize:

window.onresize = function(event)
{
	parent.resizeHandler()
}

Flash Player Settings Camera and Microphone access window "Allow" button unclickable

I came across an issue the other day. The Adobe Flash Player Settings Camera and Microphone access window “Allow” button was unclickable.

When trying to initiate the camera and microphone access for a domain for some odd reason the button to allow was unclickable and you couldn’t press it. The keyboard worked and I was able to tab but when I hit the “enter” key it still wont work and couldn’t grant access to my microphone and camera. Another issue I noticed is that once I moved my window to a second screen you couldn’t even see the warning window.

I Google it for a good 15 mins and couldn’t find an answer, some people were talking about changes in the player but no solutions about the issues I am seeing.

It appears that there couple of things to know about that warning window which can cause issues:

  • Application size – your application can’t be smaller than the size of the warning window otherwise that window wont be clickable and you wont be able to accept the permission to access your mic and camera
  • SWF Object – I notice that the warning is centering so in case you swf object is not set to the correct size of the application and it floats it will create you issues and you may not see the window in certain cases
  • Upgrading Flash Player – the security folders can get corrupted so you may need to clean them at the Global Privacy Settings panel

Keep in mind that you can also go to the Website Privacy Settings panel and always allow a site to access your camera and mic.

Organizing your HTML page's structure using iframe

HTML doesn’t have that OOP structure we all used to when developing high level languages and often I wonder how can I adjust my pages to be organized well. You can create a structure using templates or have a server side script to load pieces and create the document, however sometime you want a more straight forward simple approach. One approach is creating a document and using iframe to hold pieces of the document. for instance: left navigation, middle page, footer etc and than have a document that stitch these together.

For instance, look at the example below:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href="style.css" rel="stylesheet" type="text/css">
<title>Untitled Document</title>
</head>

<body>
    <div class="boxborder">
    	<div class="LeftPanel">
	        <iframe id="leftframe" name="leftframe" vspace=0 hspace=0 marginwidth=0 marginheight=0 align="left" scrolling="no"
            	frameborder="0" height="500px" src="leftpage.html"></iframe>
        </div>
        <div class="RightPanel">
	        <iframe id="rightframe" name="rightframe" align="left" vspace=0 hspace=0 marginwidth=0 marginheight=0 scrolling="no"
            	frameborder="0" width="500px" height="500px" src="rightpage.html"></iframe>
        </div>
    <div>
</body>
</html>

Using the following CSS style sheet:

@charset "utf-8";
/* CSS Document */

html, body {
		border: 0px;
		margin: 0px;
}
.boxborder{border:1px black solid;float:left;margin:100px 0 0 100px;}
.LeftPanel{
	float:left;
	width:200px;
}
.RightPanel{
	float:left;
	width:500px;
}
.RightBody {
	background-color:#CCCCCC;
	height:500px;
	text-align:center;
	font-size:18px;
	font-weight:bold;
	font-family:Arial, Helvetica, sans-serif;
	padding:10px 0 0 0;
}
.LeftBody {
	background-color:#666666;
	height:500px;
	color:#FFFFFF;
	font-size:18px;
	font-weight:bold;
	font-family:Arial, Helvetica, sans-serif;
	padding:10px 0 0 55px;
}

You can than create the left page:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href="style.css" rel="stylesheet" type="text/css">
<title>Untitled Document</title>
</head>

<body>
	<div class="LeftBody">
    	Left Page
    </div>
</body>
</html>

Creating the best possible quality live stream using FMS in Flash or Flex applications

When it comes to creating an agent/client live stream using FMS there are few parameters to keep in mind:

1. Live feed width
2. Live feed height
3. Camera resolution width
4. Camera resolution height
5. FPS (Frame Per Second)
6. Quality
7. Bandwidth
8. Microphone options

This article is just the tip of the iceberg, but it should give you a good idea and much information needed to build a high quality live stream application for Flash or Flex.

Let’s start with the video stream.

Adobe’s docs talks about this to some extent here:
http://livedocs.adobe.com/flashmediaserver/3.0/hpdocs/help.html?content=00000139.html

I often hear client asking for live stream feed with “porn” quality. The quality is really setting these variables and granular adjusting these variables until you find what’s work best and not just choosing the best CDN out there.

You cannot record a live stream video in high resolution, top quality, high FPS and high quality audio stream on an ok bandwidth and expact it stream great.

I recommend setting these variables at the beginning of the class so you can keep adjusting them until you found what suits you.

private static const LIVE_FEED_WIDTH:int = 220;
private static const LIVE_FEED_HEIGHT:int = 147;
private static const CAM_FEED_WIDTH:int = 80;
private static const CAM_FEED_HEIGHT:int = 53;
private static const FPS:Number = 15; // recommended between 15-25
private static const QUALITY:int = 95; // values from 0-100
private static const BANDWIDTH:int = 0; // when set as 0 will adjust to highest bw based on user's settings

Than when you setup your video and stream you can use these variables:

// setup cameras
cameraVideo = new Video(CAM_FEED_WIDTH,CAM_FEED_HEIGHT);
liveFeedVideo = new Video(LIVE_FEED_WIDTH,LIVE_FEED_HEIGHT);

// Cameras & quality
camera.setMode(LIVE_FEED_WIDTH*0.90,LIVE_FEED_WIDTH*0.90,FPS);
camera.setQuality(BANDWIDTH,QUALITY);

I also recommend setting the smoothing so the stream will display better:

cameraVideo.smoothing = liveFeedVideo.smoothing = true;

The FMS server can be configured as well and you can create your custom client and adjust properties manually when the Bandwidth changes:

netConnection = new NetConnection();
netConnection.client = new Client();

package
{
	public class Client
	{
		public function onBWCheck(... rest):Number
		{
			return 0;
		}

		public function onBWDone(... rest):void
		{
			var p_bw:Number;
			if (rest.length > 0) p_bw = rest[0];
			// your application should do something here
			// when the bandwidth check is complete
			//trace("bandwidth = " + p_bw + " Kbps.");
		}

		public function onFCSubscribe(info:Object):void
		{
			// do something
		}
	}
}

In addition, using out of the box solution such as the build in mx:VideoDisplay, s:VideoDisplay or OSMF can help you set the best values without understanding too much or adjusting too many variables, however without adjusting the default behavior you may be disappointed with the quality.

For instance even the old school mx:VideoDisplay has a build in features such as the autoBandWidthDetection=”true”. When these sets as true the VideoDisplay control should use the built-in automatic bandwidth detection feature. When false, you do not require a main.asc file on Flash Media Server (FMS) 2 to connect to FMS. When true, you need to implement a main.asc and store it in the directory structure of FMS.

As noted in the ASDOC the main.asc file must define the following functions:

      application.onConnect = function(p_client, p_autoSenseBW)
      {
        //Add security code here.
        this.acceptConnection(p_client);
        if (p_autoSenseBW)
          this.calculateClientBw(p_client);
        else
          p_client.call("onBWDone");
      }
      application.calculateClientBw = function(p_client)
      {
        // Add code to set the clients BandWidth.
        // Use p_client.getStats() which returns bytes_in
        // and bytes_Out and check your bandWidth using
        // p_client.call("onBWCheck", result, p_client.payload).
        p_client.call("onBWDone");
      }

      Client.prototype.getStreamLength = function(p_streamName)
      {
        return Stream.length(p_streamName);
      }

Now what about CDN? Do they have anything to do with live stream quality? The answer is yes. Let’s assume you are connecting 1:1 or 1 to many. Connections to feeds from users from same continents number of connections can all effect your stream quality and should be taken into account when selecting your CDN. Akamai and Limelight are the leaders in term of reputation and service, however when you start streaming and don’t publish too many feeds, setting Amazon EC2 may be all that you need to get top notch live stream and once you scale up and you can start taking advantage of CDNs that support more features.

Here’s an example code that can be used as the foundation to building a live stream:

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   minWidth="955" minHeight="600"
			   creationComplete="init()">
	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.core.FlexGlobals;
			import mx.core.UIComponent;

			private static const LIVE_FEED_WIDTH:int = 220;
			private static const LIVE_FEED_HEIGHT:int = 147;
			private static const CAM_FEED_WIDTH:int = 80;
			private static const CAM_FEED_HEIGHT:int = 53;
			private static const FPS:Number = 15; // recommended between 15-25
			private static const QUALITY:int = 95; // values from 0-100
			private static const BANDWIDTH:int = 0; // when set as 0 will adjust to highest bw based on user's settings

			// settings
			private var userType:int;
			private var sessionId:String = "1";
			private var debugMode:Boolean = true;
			private var rtmpEndPoint:String = "rtmp://[server URL]/live";

			// video variables
			private var camera:Camera;
			private var mic:Microphone;
			private var netConnection:NetConnection;
			private var cameraNetStream:NetStream;
			private var liveFeedNetStream:NetStream;
			private var cameraVideo:Video;
			private var liveFeedVideo:Video;

			// feeds ids
			private var camFeedId:String = "";
			private var liveFeedId:String = "";

			private function init():void
			{
				setURLParams();

				// setup cameras
				cameraVideo = new Video(CAM_FEED_WIDTH,CAM_FEED_HEIGHT);
				liveFeedVideo = new Video(LIVE_FEED_WIDTH,LIVE_FEED_HEIGHT);

				cameraVideo.smoothing = liveFeedVideo.smoothing = true;

				connectToFMS();
			}

			private function setURLParams():void
			{
				var params:Object = FlexGlobals.topLevelApplication.parameters;
				userType = int(params.userType);

				if ( params.userType == "undefined" || params.sessionId == "undefined" || params.rtmpEndPoint == "underfined")
				{
					// debug mode
					userTypeDropDownList.visible = true;
				}
				else
				{
					// live mode
					rtmpEndPoint = params.rtmpEndPoint;
					sessionId = params.sessionId;
					debugMode = false;
				}
			}

			private function setSettingBasedOnUserType():void
			{
				if (userType == UserType.AGENT)
				{
					camFeedId = "agent" + sessionId;
					liveFeedId = "client"  + sessionId;

				}
				else // CLIENT
				{
					camFeedId = "client" + sessionId;
					liveFeedId = "agent" + sessionId;
				}

				startCamFeedCamera();
				startLiveFeedCamera();
			}

			private function connectToFMS():void
			{
				netConnection = new NetConnection();
				netConnection.client = {};

				netConnection.addEventListener(NetStatusEvent.NET_STATUS, onNetConnectStatus);
				netConnection.connect(rtmpEndPoint);
			}

			private function onNetConnectStatus(e:NetStatusEvent):void
			{
				trace("Connected to FMS Successfully!");

				if (!debugMode)
					setSettingBasedOnUserType();
			}

			private function setCamera():void
			{
				camera = Camera.getCamera();
				mic = Microphone.getMicrophone();

				// mic settings
				mic.setLoopBack(false);
				mic.setUseEchoSuppression(true);

				// Cameras & quality
				camera.setMode(LIVE_FEED_WIDTH*0.90,LIVE_FEED_WIDTH*0.90,FPS);
				camera.setQuality(BANDWIDTH,QUALITY);

				cameraNetStream = new NetStream(netConnection);
			}

			private function addMask(circleMask:UIComponent, feed:Image, radius:int):void
			{
				if (circleMask == null)
				{
					circleMask = createCircle(radius);
					this.addElement( circleMask );
				}

				feed.mask = circleMask;
			}

			private function createCircle(r:Number):UIComponent
			{
				var circle:Sprite = new Sprite();
				circle.graphics.lineStyle(1, 0x000000);
				circle.graphics.beginFill(0x000000);
				circle.graphics.drawCircle(r, r, r);
				circle.graphics.endFill();

				var uicom:UIComponent = new UIComponent();

				uicom.addChild(circle);
				uicom.x = uicom.y = 0;
				uicom.width = uicom.height = 100;

				return uicom;
			}

			private function startCamFeedCamera():void
			{
				setCamera();

				cameraVideo.attachCamera(camera);

				cameraNetStream.attachCamera(camera);
				cameraNetStream.attachAudio(mic);
				camFeed.addChild( cameraVideo );

				// publish to FMS
				cameraNetStream.publish(getCameraFeedId, "live");

			}

			private function startLiveFeedCamera():void
			{
				// bring from FMS
				liveFeedNetStream = new NetStream(netConnection);
				liveFeedNetStream.play(getLiveFeedId);
				liveFeedVideo.attachNetStream(liveFeedNetStream);
				liveFeed.addChild( liveFeedVideo );

				// mask
				// addMask(circleMaskLiveFeed, liveFeed, 100);
			}

			private function get getLiveFeedId():String
			{
				return liveFeedId;
			}

			private function get getCameraFeedId():String
			{
				return camFeedId;
			}

		]]>
	</fx:Script>

	<mx:UIComponent id="liveFeed" width="{LIVE_FEED_WIDTH}" height="{LIVE_FEED_HEIGHT}" y="0" x="0" />
	<mx:UIComponent id="camFeed" width="{CAM_FEED_WIDTH}" height="{CAM_FEED_HEIGHT}" />

	<mx:Image id="recordButton" x="0" y="230" click="recordButton.source='assets/images/record_on_icon.gif'"
			  source="assets/images/record_icon.gif"/>
	<mx:Image id="stopButton" y="230" x="41" click="recordButton.source='assets/images/record_icon.gif'"
			  source="assets/images/stop_icon.gif"/>

	<s:DropDownList id="userTypeDropDownList" x="170" y="0"
					width="140" selectedIndex="0"
					change="{userType=userTypeDropDownList.selectedIndex-1; setSettingBasedOnUserType();}" visible="false">
		<mx:ArrayCollection>
			<fx:String>---</fx:String>
			<fx:String>Agent</fx:String>
			<fx:String>Client</fx:String>
		</mx:ArrayCollection>
	</s:DropDownList>

</s:Application>

In regards to the microphone, you can use the Microphone’s setUseEchoSuppression(true). setLoopBack(false) for removing mic’s echo and feedback but that is usually not enough to get top quality audio stream.

in FP 10.3, AIR 2.7 and up Adobe introduced enhanced audio, which includes acoustic echo cancellation and noise suppression etc.

there is a good article on Adobe’s ADC that takes about the new features and best practices:
http://www.adobe.com/devnet/flashplayer/articles/acoustic-echo-cancellation.html

In a nutshell here’s a method that will set the user’s mic with best possible settings:


			private function setMicOptions():Microphone
			{
				var options:MicrophoneEnhancedOptions = new MicrophoneEnhancedOptions();
				var microphone:Microphone = Microphone.getEnhancedMicrophone();

				// settings
				microphone.setLoopBack(false);
				microphone.setUseEchoSuppression(true); // being ignored but setting it anyway
				microphone.encodeQuality = 8;
				microphone.enableVAD = true;
				microphone.codec = SoundCodec.SPEEX;
				microphone.framesPerPacket = 1;
				microphone.setSilenceLevel(0, 2000);
				microphone.gain = 50;
				microphone.setSilenceLevel(0);
				microphone.rate = 16;

				microphone.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
				microphone.addEventListener(StatusEvent.STATUS, statusHandler);

				// options
				options.mode = MicrophoneEnhancedMode.FULL_DUPLEX;
				options.echoPath = 128; // can be increased to 256 for less echo
				options.nonLinearProcessing = true;

				microphone.enhancedOptions = options;

				return microphone;
			}

You can fine tune and adjust these settings but that should give you better results than the default built in settings. Notice that I am using the Speex codec (available from FP 10 and up). Speex is an open-source, royalty-free codec. Flash supports Speex encoding at 16 kHz.

When you use Speex transmission delay is minimized. FP uses an adaptive Speex jitter buffer when playing out messages. FP also using the Speex noise suppression and voice activity detection, which lower transmission’s bandwidth during silence.

Getting information about a page in an iframe that lives on a separate domain

Recently I created an iframe and the page inside of the iframe was redirecting to another page on a different domain. One would think that this is a simple task (just as you can do if they both lives on the same domain) and you can just pull the new URL out of the page using ajax once a change was made in the page or use the pseudo code standard as explained on wikipedia: http://en.wikipedia.org/wiki/XMLHttpRequest.

However these techniques didn’t work since there are cross domain restrictions. The browsers wont allow your parent page to access a different domain’s information since it can be abused by hackers.

HTML5 added the option to use the “postMessage” method and call the parent document. That will allow other domains to access the iframe page, so you would need permission from the parent domain and they can call a method in the parent domain that will let your parent document know that there have been change in the iframe,

For instance, a web master can create a wrapper for his page and place it in a wrapper and than use HTML5 new method “postMessage” to call the parent page and restrict it to a domain or just leave it as a wild card “*”

<?php
	$iframeStartURL = "http://[someURL]";

?>
<html>
<head>
	<title>Wrapper</title>
</head>
<body>

    <form id="frm1" name="frm1">
    	<input type="text" id="txtURL" value="<?php echo $iframeStartURL; ?>" style="visibility:hidden" />
    </form>

    <script type="text/javascript">
        var myURL = document.getElementById("txtURL");
        function GetSRC() {
			try {
				ajax.receive();
			}catch (err){
				alert("Error : " + err );
			}
        }

        var ajax =
        {
            send: function (urlstring) {
                if (!this.ifram) {
                    this.ifram = document.createElement('iframe');
                    this.ifram.style.display = 'none';
                    if (this.ifram.addEventListener) this.ifram.addEventListener('load', ajax.receive, false);
                    else if (this.ifram.attachEvent) this.ifram.attachEvent('onload', ajax.receive);
                    document.body.appendChild(this.ifram);
                }
                this.ifram.setAttribute('src', urlstring);
            },
            receive: function () {
				var newURL = ajax.ifram.getAttribute("src");
				if (newURL != null)
				{
					// send the new URL to parent window
					parent.postMessage(ajax.ifram.contentWindow.location.href, "http://[domain you allow]");
				}

                return;
            }
        };

		ajax.send(myURL.value);

    </script>
</body>
</html>

the parent window will be able to receive the message:

<html>
<head>
	<title></title>
</head>
<body>
    <iframe src="http://[some domain]"
        width="0%" height="0%" style="visibility:hidden">
    </iframe>

    <script>
		window.onload = function()
		{
			window.addEventListener( "message", function (evt) {

				if (evt.origin !== "http://eladelrom.com/blog/") {
					alert("cross domain issue");
				}

				alert(evt.data);
			}, false);
		};
	</script>
</body>
</html>

Other option is to use the “Access-Control-Allow-Origin” to the domain that you are trying to access to allow the parent document from a different domain to access the page.

However, sometimes you are using a page for your iframe that you don’t own or have access to and wont have the ability to use the HTML 5 method, plus the HTML 5 method wont work on all browsers so legacy browsers will suffer.

What you can do is create a server side script that will go and fetch a domain recognize the redirect or change in the page and send the result back to your parent page. For instance, here is how it can be done in PHP:

	$ch = curl_init("http://[domain]/[url]");

	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
	curl_setopt($ch, CURLOPT_HEADER, TRUE);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

	$result = curl_exec($ch);
	$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_close($ch);

	if($code == 307) {
	  // Get the url from the location header
	  $regexp="/Location: (.*)$/m";
	  preg_match($regexp, $result, $matches);
	  $match1 = $matches[1];
	}

Centering horizontal and vertical elements using CSS for HTML div elements

Sounds trivial but took some time to find something that works and even than I had to modify the code. I was trying to center vertically and horizontally an HTML element using div element as well as position elements on different corners of the the screen.

Here’s the CSS code:

#horizental-vertical-center
{
	background-image:url('images/form_background.gif');
	position:absolute;
	top:50%;
	left:50%;
	margin-top:-230px;/* half elements height*/
	margin-left:-207.5px;/* half elements width*/
	width:420px;
	height:415px;
	overflow:auto;/* allow content to scroll inside element */
	text-align:left;
}

div.bottom-left
{
	display:block;

	position:absolute;
	bottom:30px;
	left:22px;
	width:350px;
}

div.bottom-right
{
	background-image:url('images/suitecase.gif');

	position:absolute;
    bottom:0;
    right:0;
    width:119px;
	height:192px;
}

div.top-left
{
	background-image:url('images/airplane.gif');

    position:absolute;
    top:20px;
    left:20px;
    width:255px;
	height:231px;
}

And here’s the HTML code:

<body>
	<div class="top-left" />

        <div id="horizental-vertical-center">
        </div>
</body>

Passing variables through URL using flashvars Flex 4

Using Flash builder 4.6 HTML page generator it took me 5 mins to figure out how to pass variable since Adobe’s page (http://kb2.adobe.com/cps/164/tn_16417.html) is not updated to show how it’s being done using Flex 4 and on the latest index.template.html page.

To add the variables you would need to add the variables as describe here:
http://code.google.com/p/swfobject/wiki/documentation

var flashvars = {
  name1: "hello",
  name2: "world",
  name3: "foobar"
};
var params = {
  menu: "false"
};
var attributes = {
  id: "myDynamicContent",
  name: "myDynamicContent"
};

In your Flex application you will be able to read there easily:

var params:Object = FlexGlobals.topLevelApplication.parameters;

In case you want to use Javascript and pass the variable through the URL you can add the following script to the HTML document:

        <script type="text/javascript">

			function getHeaderUrlVariables() {
				var vars = {};
				var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
					vars[key] = value;
				});
				return vars;
			}

			var sessionId = getHeaderUrlVariables()["var1"];
			// alert(sessionId);

			var flashvars = new Object();
			flashvars.var1 = var1;

           </script>

Remember to remove the other flashvars tag: var flashvars = {}

And once you pass the variable through the URL you will be able to read it:

var params:Object = FlexGlobals.topLevelApplication.parameters;
Alert.show(params.var1);