ActionScript.org Flash, Flex and ActionScript Resources - http://www.actionscript.org/resources
Writing a Custom YouTube Player for a Google Gadget
http://www.actionscript.org/resources/articles/995/1/Writing-a-Custom-YouTube-Player-for-a-Google-Gadget/Page1.html
Haik Sahakian
Web developer living in New York. Familiar with Galaga and ZX81 BASIC. Haik Sahakian
 
By Haik Sahakian
Published on March 23, 2010
 
How to build a custom YouTube player in Flash CS4 for a Google Gadget.

Google Gadgets that play video need to ensure that large video file sizes don't overload the capacity of their web servers. Gadgets can host their video files on YouTube, but YouTube's default player is too large for most gadgets, and its controls can't be customized. This article describes how to build a custom YouTube player in Flash CS4.

The player's Flash CS4 and XML source code can be downloaded here, and the Gadget can be viewed running in iGoogle.

Contents

  1. Google Gadget Basics
  2. Playing a YouTube Video
  3. Embedding a SWF into a Gadget
  4. Placing images on Google Sites
  5. Example Code
  6. Tools

Google Gadget Basics

Google Gadgets are fragments of HTML wrapped in custom Google XML tags. You can include JavaScript in the HTML, and most gadgets are mostly JavaScript. Each gadget has a ModulePrefs tag (one of Google's custom XML tags) that includes its title, description, and author information. Another custom tag called UserPref can save a user's individual settings.

Here is a simple "Hello, World" example, that replaces the word "World" with the user's name if it has been entered into the gadget's settings dialog box:

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="hello world example" />
<UserPref name="userName" display_name="Your Name" default_value="world" />
<Content type="html"><![CDATA[
Hello,
<script>
    var thePrefs = new gadgets.Prefs();
    var userName = thePrefs.getBool("userName");
    document.write(userName);
</script>
]]></Content>
</Module>

A user can enter their name through the settings dialog box. The link to the dialog box ("Edit Settings") is created automatically by iGoogle when it detects that a UserPref tag is present.

Playing a YouTube video

YouTube videos can be played in a Google Gadget by using the YouTube player itself. It's easy to generate the HTML to embed the standard YouTube player from any YouTube page. But if you'd like your player to have a different set of controls from YouTube's player you will need to build a custom Flash video player.

Playing a video from YouTube introduces two challenges for a custom Flash video player:

  1. The URL to a YouTube FLV changes hourly.
  2. The query string in a YouTube FLV URL confuses Flash's FLVPlayback component into thinking it points to an XML playlist rather than a video.

Here's how to work around these two issues.

Obtaining a URL to a YouTube video's FLV file

The URL to a YouTube video's underlying video file changes hourly. This is to prevent YouTube from being used as a simple file storage server. Google owns YouTube and makes the following technique available for Google Gadget developers to reliably obtain a URL to a YouTube video.

In a gadget's JavaScript, the token below can be used in place of the FLV's URL. The iGoogle container will replace the token with a valid URL at run time. The token's name is __YOUTUBE_VIDEO_URL(videoID)__, where videoID is the YouTube's ID of your uploaded video. You can see this ID in the page's URL when you play a video on YouTube.com.

For example, here is a YouTube video page's URL and the video's corresponding ID:
Video URL: http://www.youtube.com/watch?v=3HrSN7176XI
Video ID: 3HrSN7176XI

And here is the JavaScript that uses the token and ID to set a gadget's variable to this YouTube video's URL:
var YouTubeURL = '__YOUTUBE_VIDEO_URL(3HrSN7176XI)__';

Using the FLVPlayback component with URLs containing query strings

As we have just seen, YouTube video URLs include a query string as a security mechanism. The FLVPlayback component in Flash, which is typically used to play video in a Flash application, assumes that Flash video files end in extensions such as ".flv" and do not contain query strings. URLs containing query strings are assumed to point to XML playlists. As a result, without modification the FLVPlayback component will think that a YouTube dynamic URL points to an XML playlist rather than an FLV file, and will try to parse the FLV file as XML. This will cause a Flash runtime error.

To update the FLVPlayback component to properly accept YouTube URLs as video files you must modify its source code. Download and place the latest Adobe source code for FLVPlayback into your project. When you download FLVPlayback into your project, your directory structure will typically look like this:


After downloading FLVPlayback, you will need to update the part of it that manages its network connections. Open NCManager.as and make the following change to the "connectToURL" function (line 665 in the file as of February 2010):

Change
if ( name.indexOf("?") < 0 &&
to
if ( name.indexOf(".flv") > 0 &&

Below is an example of the new NCManager.as class, with this change having been made. The 2 changed lines are highlighted in yellow.


There are many other ways to update the FLVPlayback component to work with YouTube URLs, but this is one of the fastest. NCManager is changed from thinking that any URL containing a question mark is an XML file, to thinking that any URL including the ".flv" extension anywhere within it is an FLV file. Now add a dummy name-value pair to the end of the YouTube URL to include the extension. For example, the following JavaScript creates a variable for a YouTube FLV's URL, together with its dummy name-value pair so that it still ends in the ".FLV" extension:

var YouTubeURL = '__YOUTUBE_VIDEO_URL(3HrSN7176XI)__';
YouTubeURL += '&dummy=1.flv';

You must now tell the your Flash application to use this customized version of NCManager.as. Add the following lines to your FLA's ActionScript just before you create an FLVPlayback component:

var _forceNCManager:fl.video.NCManager;
flash.video.VideoPlayer.iNCManagerClass = flash.video.NCManager;

Here is the change in my example FLA's ActionScript, highlighted in yellow. (The package name is different from the lines above as I saved the FLVPlayback component in a directory called "fl" rather than "flash".)


After making this change your Flash video player will be able to play back files from YouTube.

Embedding a SWF into a Gadget

Google provides the embedFlash() helper function to embed a Flash file into a gadget. It works similarly to SWFObject, and automatically caches your SWF on Google's servers to improve performance. It appears that previous version of iGoogle offered the embedCachedFlash() function to embed a cached copy of your SWF, but this function is no longer necessary or available as caching is now always enabled.

One thing to watch out for in the embedFlash() function is that it cannot pass parameters from the gadget's html to the SWF via the FlashVars variable. To work around this, pass them as name-value pairs on the URL to the SWF itself.

Placing images on Google Sites

Just as your gadget's videos can be served by YouTube, your gadget's images can be hosted by Google Sites, so that your web server will not need to handle your gadget's image requests. To do this, create a web page on Google Sites, and place images that you wish to use in your gadget into the page. You can use the URL to these images on your Google Site page directly in your Google Gadget as well. For example, this image comes from this web page on Google Sites.

Example Code

The Flash source code for the custom YouTube video player example can be downloaded here. Its ActionScript Code follows on page 2 of this article. The gadget built with it is viewable on iGoogle, and its source code is on page 3.

Tools

The Google Gadget Editor is a basic text editor that can upload and publish gadgets onto google.com. It's good for writing quick gadgets, and convenient for uploading written gadgets onto iGoogle for testing. It's browser-based and is a gadget itself.

The Google Developer Gadget allows you to configure which gadgets on your iGoogle page are cached. iGoogle usually caches gadgets which can be a problem when you want to view recently made changes. To keep your gadget uncached, install the "My Gadget" developer gadget and uncheck caching for gadgets you are working on. Without the developer gadget you'll have to save the gadget under a new name to work around this.

Actionscript Source Code

See the code below....

// main flash action script file
import fl.video.*;
import flash.display.Sprite;

stop();
debugText.visible = false;
log("Real Fireplace Video Player v0.1");

// switch to a custom ncmanager
var _forceNCManager:fl.video.NCManager;
fl.video.VideoPlayer.iNCManagerClass = fl.video.NCManager;

// use videoplayer instead of an flvplayback component (we have no controls so don't need it)
var theMovie:VideoPlayer;
theMovie = new VideoPlayer();
theMovie.height = 240;
theMovie.width = 328;
theMovie.x = 0;
theMovie.y = y;
addChild(theMovie);

// get parameters from flash vars
var theParameters:Object = LoaderInfo(this.root.loaderInfo).parameters;
var playMusic:Boolean = (theParameters["playMusic"] == "true");
var musicURL:String = theParameters["musicURL"];
var clickURL:String = theParameters["clickURL"];
var movieURL:String = theParameters["movieURL"];

// output parameters to debug log
var theName:String;
var theValue:String;
var theParams:Object = LoaderInfo(this.root.loaderInfo).parameters;
for (theName in theParams)
{
    theValue = String(theParams[theName]);
    log("Received "+theName+"="+theValue);
}

// parameter validation
if ( ( (musicURL == null) || (musicURL.length < 1) ) && (playMusic) )
{
    musicURL = "With_The_Sea_low.mp3";
    log("musicURL not passed; using default");
}
if ( (clickURL == null) || (clickURL.length < 1) )
{
    clickURL = "http://www.amazon.com/Real-Fireplace-Beau-Blazer/dp/B001S2Q5QW";
    log("clickURL not passed; using default");
}
if ( (movieURL == null) || (movieURL.length < 1) )
{
    movieURL = "realsmallfireplace.f4v";
    log("movieURL not passed; using default");
}
log("Validated playMusic:"+playMusic+"; musicURL:"+musicURL+"; clickURL:"+clickURL+"; movieURL:"+movieURL);

// set up click handler
function clickMovie(event:MouseEvent):void
{
    log("in clickMovie()");

    // show debug text on shift click
    if (event.shiftKey)
    {
        debugText.visible = (!(debugText.visible));
        return;
    }

    // open window on normal click
    var theRequest:URLRequest = new URLRequest(clickURL);
    try
    {
        navigateToURL(theRequest, '_blank');
    }
    catch (e:Error)
    {
        log("I couldn't open a new browser window for " + clickURL);
    }
}

// create a transparent overlay over the video as mouse click events cannot be detected on the movie itself
movieOverlayInstance.alpha = 0;
movieOverlayInstance.addEventListener(MouseEvent.MOUSE_DOWN, clickMovie);

// set up movie loop
function loopMovie(event:Object)
{
    theMovie.play();
}
theMovie.autoRewind = true;
theMovie.bufferTime = 0;
theMovie.addEventListener("complete", loopMovie);

// set up movie URL and play
theMovie.play(movieURL);

// set up and play sound loop
var theSound:Sound;
var theChannel:SoundChannel;
if (playMusic)
{
    log("About to play music");
    theSound = new Sound(new URLRequest(musicURL));
    theChannel = theSound.play(0,100); // repeat a hundred times
}

function log(theText:String)
{
    // write to console window
    trace(theText);

    // write to swf text field
    if (debugText.text.length < 2)
        debugText.text = theText + "; ";
    else
        debugText.text += theText + "; ";
}


Google Gadget Source Code
Again, see the code below ...

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs author="Your name here"
author_link="A link to your web site"
author_email="Your email address"
screenshot="http://sites.google.com/site/fireplacegadget/_/rsrc/1257258844722/home/screenshot_better.jpg" category="lifestyle"
thumbnail="http://sites.google.com/site/fireplacegadget/_/rsrc/1257258844722/home/screenshot_better.jpg" width="300" height="230" scaling="false"
title="Real Fireplace" directory_title="Real Fireplace"
title_url="http://www.amazon.com/Real-Fireplace-Beau-Blazer/dp/B001S2Q5QW"
description="All the sights and sounds of an authentic Boston fireplace, with an optional piano accompaniment. Music by Kevin MacLeod (www.incompetech.com).">
<Require feature="flash" />
</ModulePrefs>

<UserPref name="playMusic" display_name="Piano Music" datatype="bool" default_value="false"/>
<UserPref name="musicURL" display_name="Custom MP3" datatype="string" default_value=""/>
<Content type="html">
<![CDATA[

<div id="flashcontainer"><img src="http://sites.google.com/site/fireplacegadget/_/rsrc/1257258844722/home/screenshot_better.jpg" width="328" height="240" /></div>

<script language="JavaScript" type="text/javascript">
// Create the link to the FLV files on YouTube
var flvNoPianoShortURL = '__YOUTUBE_VIDEO_URL(EK9h6RJTyr8)__';
var flvPianoLongURL = '__YOUTUBE_VIDEO_URL(hCD0XBRAJp0)__';
flvNoPianoShortURL += '&dummy=1.flv';
flvPianoLongURL += '&dummy=1.flv';

// parameters from igoogle
var thePrefs = new gadgets.Prefs();
var playMusic = thePrefs.getBool("playMusic");
var musicURL = thePrefs.getString("musicURL");

// default parameters
var theSWF = "http://www.haik.org/fireplace/gadget/realfireplace.swf";
var theF4V = "";
var theMP3 = "";
var theLink = "http://www.amazon.com/Real-Fireplace-Beau-Blazer/dp/B001S2Q5QW";

// choose URLs for video and audio depending on parameters
var useYouTubeForPiano = true;
theF4V = flvNoPianoShortURL;
if (playMusic)
{
if (useYouTubeForPiano)
{
// if no mp3 url, let the video handle the music
if (musicURL == "")
{
playMusic = false;
theF4V = flvPianoLongURL;
}
}
}
playMusic = (playMusic) ? "true" : "false";

// construct flashvars string
var theFlashvars = "";

function htmlEncode(theString)
{
return escape(theString);
}

function addToFlashvars(theName, theValue)
{
var tempString = "";
var tempSeperator = "";
if (theFlashvars.length > 1) tempSeperator = "&";
tempString = tempSeperator + theName + "=" + htmlEncode(theValue);
theFlashvars += tempString;
}

addToFlashvars("movieURL", theF4V);
addToFlashvars("clickURL", theLink);
addToFlashvars("musicURL", musicURL);
addToFlashvars("playMusic", playMusic);

// hack to work around iGoogle bug with not passing flashvars via object
theSWF = theSWF + "?" + theFlashvars;

// embed the flash swf
var success = gadgets.flash.embedFlash(theSWF, "flashcontainer", {wmode: "window", width:328, height: 240, flashvars: theFlashvars});
log(theFlashvars);

// or not
// if (!success) document.getElementById("flashcontainer").innerHTML = "Please <a href="http://get.adobe.com/flashplayer/">upgrade</a> Flash";
</script>

]]>
</Content>
</Module>