/* 
 *   Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */ 

/*
 * Plays an external FLV video
 * Should be used with the MovieTester to test if the video decoder works.
 *
 * Expected behaviour:
 *
 *   Shows a 54x54 pixels red square moving from left to right over
 *   a 152x120 yellow background. The whole thing rotated by 45 degrees clockwise.
 */

#include <stdlib.h>
#include <stdio.h>
#include <ming.h>

#include "ming_utils.h"

#define OUTPUT_VERSION 7
#define OUTPUT_FILENAME "NetStream-SquareTest.swf"

const char* mediadir=".";
char filename[256];
char filename2[256];
char filename3[256];


int
main(int argc, char** argv)
{
  SWFMovie mo;
  SWFMovieClip dejagnuclip;
  SWFVideoStream stream;
  SWFDisplayItem item;
  SWFAction a;
  SWFAction b;
  char buffer_a[2024];
  char buffer_b[2024];
  char buffer_c[2024];

  // This is different from the real video width to make sure that
  // Video.width returns the actual width (128).
  int video_width = 130;
  int video_height = 96;

  if ( argc>1 ) mediadir=argv[1];
  else
  {
    fprintf(stderr, "Usage: %s <mediadir>\n", argv[0]);
    return 1;
  }
	
  sprintf(filename, "%s/square.flv", mediadir);
  sprintf(filename2, "%s/square.ogg", mediadir);
  sprintf(filename3, "%s/audio_timewarp.flv", mediadir);

  // Some online examples...
  //
  // 'ffdec_vp6f' 
  //sprintf(filename, "http://www.helpexamples.com/flash/video/water.flv");
  //sprintf(filename, "http://www.helpexamples.com/flash/video/clouds.flv");
  //sprintf(filename, "http://www.helpexamples.com/flash/video/typing_long.flv");
  //
  // This ones work
  //sprintf(filename, "http://www.helpexamples.com/flash/video/caption_video.flv");
  //sprintf(filename, "http://www.helpexamples.com/flash/video/sheep.flv");
  //
  //
  
  sprintf(buffer_a,
  	"note(System.capabilities.version);"
  	"note('SWF version %d');"
  	"nc=new NetConnection();"
	"check(!nc.isConnected, 'newly created NetConnection is not connected');"
	"nc.connect(null);"
	"check(nc.isConnected, 'NetConnection is connected after .connect(null)');"
	"check(!NetStream.prototype.hasOwnProperty('currentFPS'));" // version 7 here
	"check(!NetStream.prototype.hasOwnProperty('currentFps'));"
	"stream = new NetStream();"
	"check_equals ( typeof(stream.bytesTotal), 'undefined' );" // not connected..
	"stream.play('fake');" // just test not to segfault..
	"stream = new NetStream(nc);"
	"check_equals ( typeof(stream.bytesTotal), 'number' );"
	"stream.bytesTotal = 'string';"
	"check_equals ( typeof(stream.bytesTotal), 'number' );"
	"stream2 = new NetStream(nc);"
	"stream3 = new NetStream(nc);"
	, OUTPUT_VERSION);

  sprintf(buffer_b,
  	// bytesTotal (read-only)
	"MovieClip.prototype.addBytesLoadedProgress = function(v, s) {"
	"	var nam = 'blprogress_'+v;"
	"	var dep = this.getNextHighestDepth();"
	"	var pc = this.createEmptyMovieClip(nam, dep);"
	"	pc.stream = s;"
	"	pc.video = v;"
	"	var pcp = pc.createEmptyMovieClip('bar', pc.getNextHighestDepth());"
	"	var x = v._x;"
	"	var y = v._y+v._height+10;"
	"	var w = v._width;"
	"	var h = 10;"
	"	with(pcp) {"
	"		_x = x;"
	"		_y = y;"
	"		moveTo(0,0);"
	"		beginFill(0xFF0000,50);"
	"		lineTo(0, h);"
	"		lineTo(w, h);"
	"		lineTo(w, 0);"
	"		lineTo(0, 0);"
	"		endFill();"
	"	};"
	"	pc.onEnterFrame = function() {"
	"		pcp._xscale = 100*(this.stream.bytesLoaded/this.stream.bytesTotal);"
	"	};"
	"};");

    sprintf(buffer_c,
	"MovieClip.prototype.addBufferLoadedProgress = function(v, s) {"
	"	var nam = 'blprogress_'+v;"
	"	var dep = this.getNextHighestDepth();"
	"	var pc = this.createEmptyMovieClip(nam, dep);"
	"	pc.stream = s;"
	"	pc.video = v;"
	"	var pcp = pc.createEmptyMovieClip('bar', pc.getNextHighestDepth());"
	"	var x = v._x;"
	"	var y = v._y+v._height+22;"
	"	var w = v._width;"
	"	var h = 10;"
	"	with(pcp) {"
	"		_x = x;"
	"		_y = y;"
	"		moveTo(0,0);"
	"		beginFill(0x00FF00,50);"
	"		lineTo(0, h);"
	"		lineTo(w, h);"
	"		lineTo(w, 0);"
	"		lineTo(0, 0);"
	"		endFill();"
	"	};"
	"	pc.onEnterFrame = function() {"
	//"		_root.note(this.video._x+': bufferloaded: '+this.stream.bufferLength+'/'+this.stream.bufferTime);"
	"		pcp._xscale = 100*(this.stream.bufferLength/this.stream.bufferTime);"
	// 		when bufferLength > bufferTime (common):
	"		if ( pcp._xscale > 100 ) {"
	"			pcp._xscale = 100;"
	"			pcp._alpha = 100;"
	"		} else {"
	"			pcp._alpha = 50;"
	"		}"
	"	};"
	"};"
	"stream.play('%s');"
	"stream2.play('%s');"
	"stream3.play('%s');"
	"stream.pause(true);" 
	"stream.paused=true;"
	"_root.metadataNotified=0;"
	"_root.startNotified=0;"
	"_root.stopNotified=0;"
	"stop();",
	filename, filename2, filename3);

  Ming_init();
  Ming_useSWFVersion (OUTPUT_VERSION);


  mo = newSWFMovie();
  SWFMovie_setDimension(mo, 800, 600);

  // We also want to test that 1FPS of SWF rate doesn't influence
  // rate of video playback, but for now it's more useful to actually
  // have something rendered, so check_pixel can eventually do something,
  // so we run fast...
  SWFMovie_setRate(mo, 32);

  dejagnuclip = get_dejagnu_clip((SWFBlock)get_default_font(mediadir), 10, 0, 0, 800, 600);
  item = SWFMovie_add(mo, (SWFBlock)dejagnuclip);
  SWFDisplayItem_moveTo(item, 0, 250);
  SWFMovie_nextFrame(mo); 
 
  stream = newSWFVideoStream();
  SWFVideoStream_setDimension(stream, video_width, video_height);
  item = SWFMovie_add(mo, (SWFBlock)stream);
  /* SWFDisplayItem_moveTo(item, 0, 200); */
  SWFDisplayItem_setName(item, "video");

  stream = newSWFVideoStream();
  SWFVideoStream_setDimension(stream, video_width, video_height);
  item = SWFMovie_add(mo, (SWFBlock)stream);
  SWFDisplayItem_moveTo(item, 400, 0);
  SWFDisplayItem_setName(item, "video2");

  stream = newSWFVideoStream();
  SWFVideoStream_setDimension(stream, video_width, video_height);
  item = SWFMovie_add(mo, (SWFBlock)stream);
  SWFDisplayItem_setName(item, "video3");

  a = newSWFAction(buffer_a);
  if(a == NULL) return -1;
  SWFMovie_add(mo, (SWFBlock)a);

  /* Check that properties exist here
     See actionscript.all/NetStream.as for checks before
     NetStream is attached to a connection */

  check(mo, "NetStream.prototype.hasOwnProperty('currentFps')"); // currentFps
  check(mo, "NetStream.prototype.hasOwnProperty('bufferLength')"); // check bufferLength
  check(mo, "NetStream.prototype.hasOwnProperty('bufferTime')"); // check bufferTime
  check(mo, "NetStream.prototype.hasOwnProperty('liveDelay')"); // check liveDelay
  check(mo, "NetStream.prototype.hasOwnProperty('time')"); // check time
  check(mo, "NetStream.prototype.hasOwnProperty('bytesLoaded')"); // check bytesLoaded
  check(mo, "NetStream.prototype.hasOwnProperty('bytesTotal')"); // check bytesTotal

  //-----------------------------------------
  // Dynamic volume tests using Sound object
  //-----------------------------------------

  // create a movieclip to use as sound controller
  add_actions(mo, "createEmptyMovieClip('dv1', 1);");

  // attach Sound s1 to DisplayObject dv1
  add_actions(mo, "s1 = new Sound(dv1);");
  // Sound.getVolume fetches volume from dv1 (see s2 below)
  check_equals(mo, "s1.getVolume()", "100");

  // Change volume of Sound s1 (will change volume of attached DisplayObject, see below)
  add_actions(mo, "s1.setVolume(1000);");
  check_equals(mo, "s1.getVolume()", "1000"); 

  // attach Sound s2 to DisplayObject dv2
  add_actions(mo, "s2 = new Sound(dv1);");
  // Sound s2 finds volume of dv1 being 1000
  check_equals(mo, "s2.getVolume()", "1000");

  // This shows that setVolume/getVolume make callbacks
  // to attached DisplayObject as changing volume of one Sound
  // influence the other attached sound...
  add_actions(mo, "s2.setVolume(5);");
  check_equals(mo, "s2.getVolume()", "5"); 
  check_equals(mo, "s1.getVolume()", "5"); 
  add_actions(mo, "s1.setVolume(80);");
  check_equals(mo, "s2.getVolume()", "80"); 
  check_equals(mo, "s1.getVolume()", "80");
  // btw, negative volume is fine..
  add_actions(mo, "s1.setVolume(-20);");
  check_equals(mo, "s2.getVolume()", "-20"); 
  check_equals(mo, "s1.getVolume()", "-20");

  // If the attached-to DisplayObject gets unloaded,
  // getVolume returns undefined...
  add_actions(mo, "dv1.removeMovieClip();");
  check_equals(mo, "typeof(s2.getVolume())", "'undefined'"); 
  check_equals(mo, "typeof(s1.getVolume())", "'undefined'");
  // even if you reset it explicitly
  add_actions(mo, "s2.setVolume(50);");
  check_equals(mo, "typeof(s2.getVolume())", "'undefined'"); 
  check_equals(mo, "typeof(s1.getVolume())", "'undefined'");
  // but you get it back when you create a replacement..
  add_actions(mo, "createEmptyMovieClip('dv1', 2);");
  check_equals(mo, "s2.getVolume()", "100"); 
  check_equals(mo, "s1.getVolume()", "100");
  add_actions(mo, "s1.setVolume(80);");
  check_equals(mo, "s2.getVolume()", "80"); 
  check_equals(mo, "s1.getVolume()", "80");

  // And you can attach a Sound to any DisplayObject, not
  // just MovieClip ones.
  // This is against a video instance
  add_actions(mo, "s1 = new Sound(video); s2 = new Sound(video);");
  check_equals(mo, "s2.getVolume()", "100"); 
  check_equals(mo, "s1.getVolume()", "100");
  add_actions(mo, "s1.setVolume(80);");
  check_equals(mo, "s2.getVolume()", "80"); 
  check_equals(mo, "s1.getVolume()", "80");

  // Here's a 3 level volume controller. Our square.flv doesn't have sound
  // so you can't really see if it's working or not... Anyway, change
  // the code to load a movie with sound and see what happens changing the volumes
  // below
  add_actions(mo, 
		"dv1.createEmptyMovieClip('dv2', 1);"
		"dv1.dv2.createEmptyMovieClip('dv3', 1);"
                "dv1.dv2.dv3.attachAudio(stream);" // attach stream to 3rd level
                "s1 = new Sound(dv1);"
                "s2 = new Sound(dv1.dv2);"
                "s3 = new Sound(dv1.dv2.dv3);"
		"s3.setVolume(50);" // ---------- level1 volume (change me for tests)
		"s1.setVolume(50);" // ---------- level2 volume (change me for tests)
		"s2.setVolume(400);" // --------- level3 volume (change me for tests)
                );

  //------------------------------------------
  // Now attach video to the video DisplayObjects
  //------------------------------------------
  check(mo, "Video.prototype.hasOwnProperty('attachVideo')");
  check(mo, "Video.prototype.hasOwnProperty('smoothing')");
  check(mo, "Video.prototype.hasOwnProperty('deblocking')");
  check(mo, "Video.prototype.hasOwnProperty('clear')");
  check(mo, "Video.prototype.hasOwnProperty('height')");
  check(mo, "Video.prototype.hasOwnProperty('width')");
  check_equals(mo, "video.height", "0");
  check_equals(mo, "video.width", "0");
  check_equals(mo, "video2.height", "0");
  check_equals(mo, "video2.width", "0");

  add_actions(mo, "video.attachVideo(stream);"); 
  add_actions(mo, "video2.attachVideo(stream2);"); 
  add_actions(mo, "video3.attachVideo(stream3);"); 
  
  check_equals(mo, "video.height", "0");
  check_equals(mo, "video.width", "0");
  check_equals(mo, "video2.height", "0");
  check_equals(mo, "video2.width", "0");

  // currentFps (read-only)
  check_equals (mo, "typeof(stream.currentFps)", "'number'" );
  add_actions(mo, "stream.currentFps = 'string';");
  check_equals (mo, "typeof(stream.currentFps)", "'number'" );
  add_actions(mo, "stream.currentFps = false;");
  check_equals (mo, "typeof(stream.currentFps)", "'number'" );

  // bufferLength (read-only)
  check_equals (mo, "typeof(stream.bufferLength)", "'number'" );
  add_actions(mo, "stream.bufferLength = 'string';");
  check_equals (mo, "typeof(stream.bufferLength)", "'number'" );
  add_actions(mo, "stream.bufferLength = false;");
  check_equals (mo, "typeof(stream.bufferLength)", "'number'" );

  // bufferTime
  check_equals (mo, "typeof(stream.bufferTime)", "'number'" );
  add_actions(mo, "stream.setBufferTime(2);");
  check_equals (mo, "stream.bufferTime", "2");
  add_actions(mo,"stream.bufferTime = 20;");
  check_equals (mo, "stream.bufferTime", "2");
  add_actions(mo,"stream.setBufferTime = 30;");
  check_equals (mo, "stream.bufferTime", "2");
  add_actions(mo,"stream.setBufferTime(false);");
  check_equals (mo, "stream.bufferTime", "2");
  add_actions(mo,"stream.setBufferTime('string');");
  check_equals (mo, "stream.bufferTime", "2");
  add_actions(mo,"stream.setBufferTime('5');");
  check_equals (mo, "stream.bufferTime", "2");
  add_actions(mo,"stream.setBufferTime(10);");  // can't change it once set ...
  check_equals (mo, "stream.bufferTime", "2");

  // liveDelay (read-only)
  xcheck_equals (mo, "typeof(stream.liveDelay)", "'number'");
  add_actions(mo, "stream.liveDelay = 'string';");
  xcheck_equals (mo, "typeof(stream.liveDelay)", "'number'");

  // time (read-only)
  check_equals (mo, "typeof(stream.time)", "'number'" );
  add_actions(mo, "stream.time = 'string';");
  check_equals (mo, "typeof(stream.time)", "'number'" );

  // bytesLoaded (read-only)
  check_equals (mo, "typeof(stream.bytesLoaded)", "'number'" );
  add_actions(mo, "stream.bytesLoaded = 'string';");
  check_equals (mo, "typeof(stream.bytesLoaded)", "'number'" ); 

  check_equals (mo, "stream.currentFps", "0" );

  /* Play video */
  b = newSWFAction(buffer_b);
  if(b == NULL) return -1;
  SWFMovie_add(mo, (SWFBlock)b);
  b = newSWFAction(buffer_c);
  if(b == NULL) return -1;
  SWFMovie_add(mo, (SWFBlock)b);
 
  check_equals (mo, "stream.currentFps", "0" );
 
  /* Publisher Methods */

  // These are documented as player methods for connections
  // to a media server.
  // Possibly they are only defined if the connection is to
  // such a server; at this point, they should be undefined in
  // any case.

  check_equals (mo, "typeof(stream.attachAudio())", "'undefined'");
  check_equals (mo, "typeof(stream.attachVideo())", "'undefined'");
  check_equals (mo, "typeof(stream.publish())", "'undefined'");
  check_equals (mo, "typeof(stream.send())", "'undefined'");
  check_equals (mo, "typeof(stream.receiveAudio())", "'undefined'");
  check_equals (mo, "typeof(stream.receiveVideo())", "'undefined'");

  /* Video checks */ 
  
  check_equals(mo, "video._xscale", "100");
  check_equals(mo, "video._yscale", "100");
  check_equals(mo, "video._rotation", "0");
  check_equals(mo, "video._target", "'/video'");
  check_equals(mo, "video.height", "0");
  check_equals(mo, "video.width", "0");
  check_equals(mo, "video2.height", "0");
  check_equals(mo, "video2.width", "0");

  check(mo, "Video.prototype.hasOwnProperty('attachVideo')");
  check(mo, "Video.prototype.hasOwnProperty('smoothing')");
  check(mo, "Video.prototype.hasOwnProperty('deblocking')");
  check(mo, "Video.prototype.hasOwnProperty('clear')");
  check(mo, "Video.prototype.hasOwnProperty('height')");
  check(mo, "Video.prototype.hasOwnProperty('width')");

  check(mo, "!Video.prototype.hasOwnProperty('_alpha')");
  check(mo, "!Video.prototype.hasOwnProperty('_height')");
  check(mo, "!Video.prototype.hasOwnProperty('_name')");
  check(mo, "!Video.prototype.hasOwnProperty('_parent')");
  check(mo, "!Video.prototype.hasOwnProperty('_rotation')");
  check(mo, "!Video.prototype.hasOwnProperty('_visible')");
  check(mo, "!Video.prototype.hasOwnProperty('_width')");
  check(mo, "!Video.prototype.hasOwnProperty('_x')");
  check(mo, "!Video.prototype.hasOwnProperty('_xmouse')");
  check(mo, "!Video.prototype.hasOwnProperty('_xscale')");
  check(mo, "!Video.prototype.hasOwnProperty('_y')");
  check(mo, "!Video.prototype.hasOwnProperty('_ymouse')");
  check(mo, "!Video.prototype.hasOwnProperty('_yscale')");
  check(mo, "!Video.prototype.hasOwnProperty('_xmouse')");

  check(mo, "!video.hasOwnProperty('_alpha')");
  check(mo, "!video.hasOwnProperty('_height')");
  check(mo, "!video.hasOwnProperty('_name')");
  check(mo, "!video.hasOwnProperty('_parent')");
  check(mo, "!video.hasOwnProperty('_rotation')");
  check(mo, "!video.hasOwnProperty('_visible')");
  check(mo, "!video.hasOwnProperty('_width')");
  check(mo, "!video.hasOwnProperty('_x')");
  check(mo, "!video.hasOwnProperty('_xmouse')");
  check(mo, "!video.hasOwnProperty('_xscale')");
  check(mo, "!video.hasOwnProperty('_y')");
  check(mo, "!video.hasOwnProperty('_ymouse')");
  check(mo, "!video.hasOwnProperty('_yscale')");
  check(mo, "!video.hasOwnProperty('_xmouse')");

  add_actions(mo,
		"video._x = 100;"
		"video._xscale = 120;"
		"video._yscale = 120;"
		"video._rotation = 45;"
		"_root.addBytesLoadedProgress(video, stream);"
		"_root.addBytesLoadedProgress(video2, stream2);"
		"_root.addBufferLoadedProgress(video, stream);"
		"_root.addBufferLoadedProgress(video2, stream2);"
	);

  check_equals(mo, "video._x", "100")	;
  check_equals(mo, "Math.round(video._xscale*100)/100", "120");
  check_equals(mo, "Math.round(video._yscale*100)/100", "120");
  check_equals(mo, "Math.round(video._rotation*100)/100", "45");


  // How can I test props here ?
  check_equals(mo, "typeof(video.hitTest)", "'undefined'");
  check_equals(mo, "typeof(video.getBounds)", "'undefined'");

  SWFMovie_add(mo, (SWFBlock)newSWFAction(
        "note('The video on the right is an OGG theora stream. It will appear "
        "in Gnash, but not the Adobe player');"
		"_root.onKeyDown = function() {"
		" 	_root.note(' bufferLength:'+stream.bufferLength+"
		" 		' bytesLoaded:'+stream.bytesLoaded+"
		"		' currentFps:'+stream.currentFps+' time:'+stream.time);"
		"	var ascii = Key.getAscii();"
		"	trace('Key down: '+ascii);"
		"	if ( ascii == 32 ) {" // ' ' - pause(toggle)
		"		stream.paused = !stream.paused;"
		"		stream.pause();" // stream.paused);"
		"	}"
		"	else if ( ascii == 112 ) {" // 'p' - play()
		"		stream.play();"
		"               _root.note(\"2. Verify video hasn't started, then press space to continue.\");"
		"	}"
		"	else if ( ascii == 99 ) {" // 'c' - play()
		"		_root.check(nc.isConnected, 'NetConnection is connected');"
		"		_root.nc.close();"
		"               _root.note(\"Closed netconnection\");"
		"		_root.check(!nc.isConnected, 'NetConnection is not connected');"
		"	}"
		"};"
		"Key.addListener(_root);"

		"\n"

		"stream.onStatus = function(info) {"

		"  if ( ! _root.enumerableStatusInfoChecked ) {"
		"    _root.check(info.code != undefined);"
		"    _root.check(info.level != undefined);"
		"    var tmp = new Array();"
		"    for (var e in info) tmp.push(e);"
		"    tmp.sort();"
		"    _root.check_equals(tmp.length, 2);"
		"    _root.check_equals(tmp[0], 'code');"
		"    _root.check_equals(tmp[1], 'level');"
		"    _root.enumerableStatusInfoChecked=true;"
		"    var backup = info.code; info.code = 65;"
		"    _root.check_equals(info.code, 65);"
		"    _root.check(delete info.code);"
		"    _root.check_equals(info.code, undefined);"
		"    info.code = backup;"
		"    var backup = info.level; info.level = 66;"
		"    _root.check_equals(info.level, 66);"
		"    _root.check(delete info.level);"
		"    _root.check_equals(info.level, undefined);"
		"    info.level = backup;"
		"  }"

		"\n"

		// Ignore Buffer.Flush for now
		"  if ( info.code == 'NetStream.Buffer.Flush' ) return; "

		// Print some info
		" _root.note('onStatus('+info.code+') called'); "

		"\n"

		" if ( info.code == 'NetStream.Play.Start' )"
		" {"
		"	check(!_root.startNotified, 'No duplicated Play.Start notification');"
		"	_root.startNotified++;"
		" }"

		" else if ( info.code == 'NetStream.Play.Stop' )"
		" {"
		" 	if ( ! _root.stopNotified )"
		" 	{"
		" 		_root.stopNotified++;"
		" 		stream.seek(0);"
		" 	} else {"
		"		_root.stopNotified++;"
		" 		_root.check(this instanceOf NetStream); "
		" 		_root.check_equals(this.bufferTime, 2); "
		" 		_root.check_equals(this.bytesTotal, 21482); "
		"		_root.nextFrame();"
		"	}"
		" }"
#if 0
		" else if ( info.code == 'NetStream.Buffer.Empty' && this.stopNotified ) "
	        " {"
		"	check ( this.stopNotified )"
		"	this.close();"
		"	_root.nextFrame();"
		" }"
#endif

		"};"

		"\n"

		"stream.onCuePoint = function(info) {"
		" _root.note('onCuePoint('+info+') called'); "
		"};"
		"stream.onMetaData = function(info) {"

		// debugging
		" var s='';"
		" for (e in info) { "
		"  s += e+':'+info[e]+' ';"
		" }"
		" _root.note('onMetaData: '+s);"


		" _root.metadataNotified++;"

		// don't run other tests if already done
		// BTW: should be called once, gnash (gst) calls this twice
		//      and would succeed in composition checking in second call.
		" if ( _root.metadataNotified > 1 ) return;"

		" check(_root.startNotified, 'onMetaData should be notified after Play.Start');"
		" check_equals(arguments.length, 1, 'single argument');"
		" check(info instanceof Array, 'onMetaData argument sent from square.flv should be instanceof Array');"
		" check_equals(info.length, 11);" 

		// Test enumeration
		" var enu = new Array;"
		" for (e in info) { "
		"  enu.push(e);"
		" }"
		" check_equals(enu.length, 11);" // this is actual (not virtual) composition

		"\n"


		// Test composision

		" check(info.hasOwnProperty('length'), 'metadata has length');" // it's an array...

		" check(info.hasOwnProperty('filesize'), 'metadata has filesize');"
		" check_equals(typeof(info.filesize), 'number', 'filesize is a number');"
		" check_equals(info.filesize, '21482', 'actual filesize');"
		" info.filesize = 'changed';"
		" check_equals(info.filesize, 'changed');" // can be overridden
		" delete info.filesize;"
		" check(!info.hasOwnProperty('filesize'), 'metadata filesize can be deleted');"

		" check(info.hasOwnProperty('audiocodecid'), 'metadata has audiocodecid');"
		" check_equals(typeof(info.audiocodecid), 'number', 'audiocodecid is a number');"
		" check_equals(info.audiocodecid, 2, 'actual audiocodecid');"
		" info.audiocodecid = 'changed';"
		" check_equals(info.audiocodecid, 'changed');" // can be overridden
		" delete info.audiocodecid;"
		" check(!info.hasOwnProperty('audiocodecid'), 'metadata audiocodecid can be deleted');"

		"\n"

		" check(info.hasOwnProperty('stereo'), 'metadata has stereo');"
		" check_equals(typeof(info.stereo), 'boolean', 'stereo is boolean');" 
		" check_equals(info.stereo, false, 'actual stereo');"
		" info.stereo = 'changed';"
		" check_equals(info.stereo, 'changed');" // can be overridden
		" delete info.stereo;"
		" check(!info.hasOwnProperty('stereo'), 'metadata stereo can be deleted');"

		" check(info.hasOwnProperty('audiosamplesize'), 'metadata has audiosamplesize');"
		" check_equals(typeof(info.audiosamplesize), 'number', 'audiosamplesize is a number');"
		" check_equals(info.audiosamplesize, 16, 'actual audiosamplesize');"
		" info.audiosamplesize = 'changed';"
		" check_equals(info.audiosamplesize, 'changed');" // can be overridden
		" delete info.audiosamplesize;"
		" check(!info.hasOwnProperty('audiosamplesize'), 'metadata audiosamplesize can be deleted');"

		"\n"

		" check(info.hasOwnProperty('audiosamplerate'), 'metadata has audiosamplerate');"
		" check_equals(typeof(info.audiosamplerate), 'number', 'audiosamplerate is a number');"
		" check_equals(info.audiosamplerate, 44100, 'actual audiosamplerate');"
		" info.audiosamplerate = 'changed';"
		" check_equals(info.audiosamplerate, 'changed');" // can be overridden
		" delete info.audiosamplerate;"
		" check(!info.hasOwnProperty('audiosamplerate'), 'metadata audiosamplerate can be deleted');"

		" check(info.hasOwnProperty('videocodecid'), 'metadata has videocodecid');"
		" check_equals(typeof(info.videocodecid), 'number', 'videocodecid is a number');"
		" check_equals(info.videocodecid, 2, 'actual videocodecid');"
		" info.videocodecid = 'changed';"
		" check_equals(info.videocodecid, 'changed');" // can be overridden
		" delete info.videocodecid;"
		" check(!info.hasOwnProperty('videocodecid'), 'metadata videocodecid can be deleted');"

		"\n"

		" check(info.hasOwnProperty('height'), 'metadata has height');"
		" check_equals(typeof(info.height), 'number', 'height is a number');"
		" check_equals(info.height, 96, 'actual height');"
		" info.height = 'changed';"
		" check_equals(info.height, 'changed');" // can be overridden
		" delete info.height;"
		" check(!info.hasOwnProperty('height'), 'metadata height can be deleted');"

		" check(info.hasOwnProperty('width'), 'metadata has width');"
		" check_equals(typeof(info.width), 'number', 'width is a number');"
		" check_equals(info.width, 128, 'actual width');"
		" info.width = 'changed';"
		" check_equals(info.width, 'changed');" // can be overridden
		" delete info.width;"
		" check(!info.hasOwnProperty('width'), 'metadata width can be deleted');"

		"\n"

		" check(info.hasOwnProperty('duration'), 'metadata has duration');"
		" check_equals(typeof(info.duration), 'number', 'duration is a number');"
		" check_equals(info.duration, 2.299, 'actual duration');" // seconds, rounded to milliseconds
		" info.duration = 'changed';"
		" check_equals(info.duration, 'changed');" // can be overridden
		" delete info.duration;"
		" check(!info.hasOwnProperty('duration'), 'metadata duration can be deleted');"
		" _root.note('1. Press \"p\" key');"
		"};"
		));

  SWFMovie_nextFrame(mo);

  check_equals(mo, "video.height", "96");
  check_equals(mo, "video.width", "128");


  // See https://savannah.gnu.org/bugs/?26687
  SWFMovie_add(mo, (SWFBlock)newSWFAction(
  //"note('VM.time:'+getTimer()+' stream3.time:'+stream3.time);"
  "check(stream3.time > 4150, 'stream3.time '+stream3.time+' > 4150');"
  ));


  //add_actions(mo, "note('This is an OGG stream, so the Adobe player "
  //    "will fail the next two tests.');");
  // gstreamer also fails, so let's not test this.
  //check_equals(mo, "video2.height", "96");
  //check_equals(mo, "video2.width", "128");

  check_equals(mo, "metadataNotified", "1");
  check_equals(mo, "stopNotified", "2");
  check_equals(mo, "startNotified", "1");
  SWFMovie_add(mo, (SWFBlock)newSWFAction("totals(197); stop(); end_of_test=true;"));

  SWFMovie_nextFrame(mo);


  /* Output movie */
  puts("Saving " OUTPUT_FILENAME );
  SWFMovie_save(mo, OUTPUT_FILENAME);

  return 0;
}
