Volver a las novedades para desarrolladores

How-To: Implementing hideFlashCallback to support "wmode=window"

23 de enero de 2012DeConstantin Koumouzelis

One of the Pro-Tips we mentioned late last year in our Games Tutorial to Developers creating Flash based Apps was to use wmode=opaque whenever possible. Setting wmode to any value other than "opaque" or "transparent" prevents any HTML content from being displayed at a higher z index than the Flash object. This results in Dialogs, Notifications and Ticker Flyouts being displayed under the Flash object and creates a pretty poor user experience on Canvas.

As a result when Canvas Apps set wmode to "window" or "direct" Facebook automatically hides the Flash object when any Dialogs, Notifications or Ticker Flyouts are opened. To help improve the user experience we have recently introduced a new parameter for FB.init in the JavaScript SDK called hideFlashCallback . This allows developers to specify their own behavior for the auto hiding of Flash objects with wmode=window or direct, so long as the custom behavior completes in 200ms or less. After 200ms we will hide the Flash Object automatically regardless.

This How-To will walk you through the process of creating a temporary dynamic screenshot of your Flash App and replacing the Flash Object with this image within 200ms. This allows Dialogs to display correctly and creates a more pleasing user experience.

The How-To is divided into the following sections:

  1. Creating a Dynamic Screenshot in Flash
  2. Enabling ActionScript to JavaScript Communication
  3. Using a Data URI
  4. Setting up the hideFlashCallback Functionality
  5. Source Code for the Example App

Developers are expected to be familiar with JavaScript and ActionScript 3.0 in order to continue.  

Creating a Dynamic Screenshot in Flash

The first step will be to implement a method within your Flash App that creates a dynamic screenshot of the Stage object, compresses this into a JPEG format and then Base64 encodes this string. Fortunately there are public libraries available that will take care of most of these steps. For this example we will use: http://www.blooddy.by/en/crypto/ or feel free to use any lib that you prefer.

Next we will create the ActionScript exportScreenshot method that will return our screenshot data string, ready for use.

ActionScript

import by.blooddy.crypto.Base64;
import by.blooddy.crypto.image.JPEGEncoder;

...

private function exportScreenshot():String {
  var scale:Number = 0.25;
  var result:String = null;
  var blurFilter:BlurFilter = new BlurFilter(3, 3, BitmapFilterQuality.HIGH);

  var bData:BitmapData = new BitmapData(stage.stageWidth * scale,
    stage.stageHeight * scale,false,0x0);
  var matrix:Matrix = new Matrix();
  matrix.scale(scale, scale);

  bData.draw(stage, matrix);
  bData.applyFilter(bData, bData.rect, new Point(0, 0), blurFilter);

  var jpgBytes:ByteArray = JPEGEncoder.encode(bData,80);
  if (jpgBytes) {
    var screenshotBase64:String = Base64.encode(jpgBytes);
    if (screenshotBase64) {
      result = screenshotBase64;
    }
  }

  return result;
}

Walking through the code line by line you will notice that we are scaling the image to 25% of the full Stage dimensions. This is to reduce the size of our screenshot image and increase overall performance. Next we apply a BlurFilter to smooth out the jagged edges that result. Finally we encode our Bitmap as a JPEG, base64 encode it and then return the string.  

Enabling ActionScript to JavaScript Communication

Now that we have a method in our Flash App that can create a dynamic screenshot image we will need to support JavaScript communication from within the Flash Object. This will allow us to create a screenshot dynamically through a JavaScript call.

To do this we will need to import the ExternalInterface class in our Flash App and register a callback method.

ActionScript

import flash.external.ExternalInterface

...

ExternalInterface.addCallback('exportScreenshot', exportScreenshot);

The code registers a callback method called exportScreenshot in JavaScript allowing us to call our ActionScript method of the same name from JavaScript directly.  

Using a Data URI

Next we will create a image HTML element that will contain our dynamic screenshot and replace the Flash Object temporarily. To implement this we will use a Data URI allowing us to define the image data inline without having to make any requests back to a web server, increasing our performance.

Note: This is only available in modern browsers, Internet Explorer 8 and above. For IE7 and below you will have to fallback to a static image and cannot use the method described in this How-To.

In the HTML below we define a div HTML element that will contain our Flash content and directly underneath, a div HTML element that contains our img element that will hold our screenshot image. Finally we set both of these elements to have absolute position within a relative positioned parent. This allows us to place the img element beneath our Flash Object.

HTML

<div id="allContent" style="position:relative;">
  <div id="flashContent" style="position:absolute">
    <div id="flashHolder"></div>
  </div>
<div id="imageContent" style="position:absolute; top: -10000px;">
  <img id="screenshotObject"
    src="blank.gif"
    style="margin: 0 auto; display:block;" />
</div>

After we call the exportScreenshot method from JavaScript to get our dynamic screenshot, we set the dimensions of our img element to match the Flash Object, move the Flash Object out of the display area and put the screenshot image in it's place.

JavaScript

// Call the Flash Actionscript method to create the dynamic screenshot
var screenshotData =
  document.getElementById('flashObject').exportScreenshot();

// Set the screenshot image data as a base64 encoded data URI
// in the img src attribute
document.getElementById('screenshotObject').src = 'data:image/jpeg;base64,'
  + screenshotData;

// Set the screenshot img dimensions to match the Flash object tag.
document.getElementById('screenshotObject').width = flashObject.width;
document.getElementById('screenshotObject').height = flashObject.height;
  
// Move the Flash object off the screen and put the screenshot in it's place
document.getElementById('flashContent').style.top = '-10000px';
document.getElementById('imageContent').style.top = '';

 

Setting up the hideFlashCallback Functionality

The last step is putting all of this code together and executing it based on the hideFlashCallback callback. To do this, we wrap our JavaScript code (above) into a function called displayFlashScreenshot and create a new method hideFlashScreenshot which will do the reverse: hide our screenshot and then display our Flash Object again. Basically, when a Dialog is opened we want to execute displayFlashScreenshot to display our screenshot and when the Dialog is closed we want to execute hideFlashScreenshot to display our Flash content.

Next we create a new method onFlashHide to handle the open/close state and assign it to the hideFlashCallback parameter in FB.init. In onFlashHide we check the info.state property and then call the corresponding JavaScript method.

JavaScript

function displayFlashScreenshot() {
  // Call the Flash Actionscript method to create the dynamic screenshot data
  var screenshotData =
    document.getElementById('flashObject').exportScreenshot();

  // Set the screenshot image data as a base64 encoded data URI
  // in the img src attribute
  document.getElementById('screenshotObject').src = 'data:image/jpeg;base64,'
    + screenshotData;

  // Set the screenshot img dimensions to match the Flash object tag.
  document.getElementById('screenshotObject').width = flashObject.width;
  document.getElementById('screenshotObject').height = flashObject.height;
        
  // Move the Flash object off the screen and place the screenshot img
  document.getElementById('flashContent').style.top = '-10000px';
  document.getElementById('imageContent').style.top = '';
}

function hideFlashScreenshot() {
  // Move the screenshot img off the screen and place the Flash object
  document.getElementById('flashContent').style.top = '';
  document.getElementById('imageContent').style.top = '-10000px';
}

function onFlashHide(info) {
  if(info.state == 'opened') {
    displayFlashScreenshot();
  } else {
    hideFlashScreenshot();
  }
}

FB.init({
  appId  : 'APP_ID',
  hideFlashCallback : onFlashHide
});

For more information on the hideFlashCallback parameter see the JavaScript SDK Docs.  

Full Source Code for the Example App

Download the source code for the Example App which includes the HTML, JavaScript and ActionScript code referenced in the examples above.


Etiquetas: