1 /*
2  *   Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
16  *
17  */
18 
19 /*
20  * Plays an external FLV video
21  * Should be used with the MovieTester to test if the video decoder works.
22  *
23  * Expected behaviour:
24  *
25  *   Shows a 54x54 pixels red square moving from left to right over
26  *   a 152x120 yellow background. The whole thing rotated by 45 degrees clockwise.
27  */
28 
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <ming.h>
32 
33 #include "ming_utils.h"
34 
35 #define OUTPUT_VERSION 7
36 #define OUTPUT_FILENAME "NetStream-SquareTest.swf"
37 
38 const char* mediadir=".";
39 char filename[256];
40 char filename2[256];
41 char filename3[256];
42 
43 
44 int
main(int argc,char ** argv)45 main(int argc, char** argv)
46 {
47   SWFMovie mo;
48   SWFMovieClip dejagnuclip;
49   SWFVideoStream stream;
50   SWFDisplayItem item;
51   SWFAction a;
52   SWFAction b;
53   char buffer_a[2024];
54   char buffer_b[2024];
55   char buffer_c[2024];
56 
57   // This is different from the real video width to make sure that
58   // Video.width returns the actual width (128).
59   int video_width = 130;
60   int video_height = 96;
61 
62   if ( argc>1 ) mediadir=argv[1];
63   else
64   {
65     fprintf(stderr, "Usage: %s <mediadir>\n", argv[0]);
66     return 1;
67   }
68 
69   sprintf(filename, "%s/square.flv", mediadir);
70   sprintf(filename2, "%s/square.ogg", mediadir);
71   sprintf(filename3, "%s/audio_timewarp.flv", mediadir);
72 
73   // Some online examples...
74   //
75   // 'ffdec_vp6f'
76   //sprintf(filename, "http://www.helpexamples.com/flash/video/water.flv");
77   //sprintf(filename, "http://www.helpexamples.com/flash/video/clouds.flv");
78   //sprintf(filename, "http://www.helpexamples.com/flash/video/typing_long.flv");
79   //
80   // This ones work
81   //sprintf(filename, "http://www.helpexamples.com/flash/video/caption_video.flv");
82   //sprintf(filename, "http://www.helpexamples.com/flash/video/sheep.flv");
83   //
84   //
85 
86   sprintf(buffer_a,
87   	"note(System.capabilities.version);"
88   	"note('SWF version %d');"
89   	"nc=new NetConnection();"
90 	"check(!nc.isConnected, 'newly created NetConnection is not connected');"
91 	"nc.connect(null);"
92 	"check(nc.isConnected, 'NetConnection is connected after .connect(null)');"
93 	"check(!NetStream.prototype.hasOwnProperty('currentFPS'));" // version 7 here
94 	"check(!NetStream.prototype.hasOwnProperty('currentFps'));"
95 	"stream = new NetStream();"
96 	"check_equals ( typeof(stream.bytesTotal), 'undefined' );" // not connected..
97 	"stream.play('fake');" // just test not to segfault..
98 	"stream = new NetStream(nc);"
99 	"check_equals ( typeof(stream.bytesTotal), 'number' );"
100 	"stream.bytesTotal = 'string';"
101 	"check_equals ( typeof(stream.bytesTotal), 'number' );"
102 	"stream2 = new NetStream(nc);"
103 	"stream3 = new NetStream(nc);"
104 	, OUTPUT_VERSION);
105 
106   sprintf(buffer_b,
107   	// bytesTotal (read-only)
108 	"MovieClip.prototype.addBytesLoadedProgress = function(v, s) {"
109 	"	var nam = 'blprogress_'+v;"
110 	"	var dep = this.getNextHighestDepth();"
111 	"	var pc = this.createEmptyMovieClip(nam, dep);"
112 	"	pc.stream = s;"
113 	"	pc.video = v;"
114 	"	var pcp = pc.createEmptyMovieClip('bar', pc.getNextHighestDepth());"
115 	"	var x = v._x;"
116 	"	var y = v._y+v._height+10;"
117 	"	var w = v._width;"
118 	"	var h = 10;"
119 	"	with(pcp) {"
120 	"		_x = x;"
121 	"		_y = y;"
122 	"		moveTo(0,0);"
123 	"		beginFill(0xFF0000,50);"
124 	"		lineTo(0, h);"
125 	"		lineTo(w, h);"
126 	"		lineTo(w, 0);"
127 	"		lineTo(0, 0);"
128 	"		endFill();"
129 	"	};"
130 	"	pc.onEnterFrame = function() {"
131 	"		pcp._xscale = 100*(this.stream.bytesLoaded/this.stream.bytesTotal);"
132 	"	};"
133 	"};");
134 
135     sprintf(buffer_c,
136 	"MovieClip.prototype.addBufferLoadedProgress = function(v, s) {"
137 	"	var nam = 'blprogress_'+v;"
138 	"	var dep = this.getNextHighestDepth();"
139 	"	var pc = this.createEmptyMovieClip(nam, dep);"
140 	"	pc.stream = s;"
141 	"	pc.video = v;"
142 	"	var pcp = pc.createEmptyMovieClip('bar', pc.getNextHighestDepth());"
143 	"	var x = v._x;"
144 	"	var y = v._y+v._height+22;"
145 	"	var w = v._width;"
146 	"	var h = 10;"
147 	"	with(pcp) {"
148 	"		_x = x;"
149 	"		_y = y;"
150 	"		moveTo(0,0);"
151 	"		beginFill(0x00FF00,50);"
152 	"		lineTo(0, h);"
153 	"		lineTo(w, h);"
154 	"		lineTo(w, 0);"
155 	"		lineTo(0, 0);"
156 	"		endFill();"
157 	"	};"
158 	"	pc.onEnterFrame = function() {"
159 	//"		_root.note(this.video._x+': bufferloaded: '+this.stream.bufferLength+'/'+this.stream.bufferTime);"
160 	"		pcp._xscale = 100*(this.stream.bufferLength/this.stream.bufferTime);"
161 	// 		when bufferLength > bufferTime (common):
162 	"		if ( pcp._xscale > 100 ) {"
163 	"			pcp._xscale = 100;"
164 	"			pcp._alpha = 100;"
165 	"		} else {"
166 	"			pcp._alpha = 50;"
167 	"		}"
168 	"	};"
169 	"};"
170 	"stream.play('%s');"
171 	"stream2.play('%s');"
172 	"stream3.play('%s');"
173 	"stream.pause(true);"
174 	"stream.paused=true;"
175 	"_root.metadataNotified=0;"
176 	"_root.startNotified=0;"
177 	"_root.stopNotified=0;"
178 	"stop();",
179 	filename, filename2, filename3);
180 
181   Ming_init();
182   Ming_useSWFVersion (OUTPUT_VERSION);
183 
184 
185   mo = newSWFMovie();
186   SWFMovie_setDimension(mo, 800, 600);
187 
188   // We also want to test that 1FPS of SWF rate doesn't influence
189   // rate of video playback, but for now it's more useful to actually
190   // have something rendered, so check_pixel can eventually do something,
191   // so we run fast...
192   SWFMovie_setRate(mo, 32);
193 
194   dejagnuclip = get_dejagnu_clip((SWFBlock)get_default_font(mediadir), 10, 0, 0, 800, 600);
195   item = SWFMovie_add(mo, (SWFBlock)dejagnuclip);
196   SWFDisplayItem_moveTo(item, 0, 250);
197   SWFMovie_nextFrame(mo);
198 
199   stream = newSWFVideoStream();
200   SWFVideoStream_setDimension(stream, video_width, video_height);
201   item = SWFMovie_add(mo, (SWFBlock)stream);
202   /* SWFDisplayItem_moveTo(item, 0, 200); */
203   SWFDisplayItem_setName(item, "video");
204 
205   stream = newSWFVideoStream();
206   SWFVideoStream_setDimension(stream, video_width, video_height);
207   item = SWFMovie_add(mo, (SWFBlock)stream);
208   SWFDisplayItem_moveTo(item, 400, 0);
209   SWFDisplayItem_setName(item, "video2");
210 
211   stream = newSWFVideoStream();
212   SWFVideoStream_setDimension(stream, video_width, video_height);
213   item = SWFMovie_add(mo, (SWFBlock)stream);
214   SWFDisplayItem_setName(item, "video3");
215 
216   a = newSWFAction(buffer_a);
217   if(a == NULL) return -1;
218   SWFMovie_add(mo, (SWFBlock)a);
219 
220   /* Check that properties exist here
221      See actionscript.all/NetStream.as for checks before
222      NetStream is attached to a connection */
223 
224   check(mo, "NetStream.prototype.hasOwnProperty('currentFps')"); // currentFps
225   check(mo, "NetStream.prototype.hasOwnProperty('bufferLength')"); // check bufferLength
226   check(mo, "NetStream.prototype.hasOwnProperty('bufferTime')"); // check bufferTime
227   check(mo, "NetStream.prototype.hasOwnProperty('liveDelay')"); // check liveDelay
228   check(mo, "NetStream.prototype.hasOwnProperty('time')"); // check time
229   check(mo, "NetStream.prototype.hasOwnProperty('bytesLoaded')"); // check bytesLoaded
230   check(mo, "NetStream.prototype.hasOwnProperty('bytesTotal')"); // check bytesTotal
231 
232   //-----------------------------------------
233   // Dynamic volume tests using Sound object
234   //-----------------------------------------
235 
236   // create a movieclip to use as sound controller
237   add_actions(mo, "createEmptyMovieClip('dv1', 1);");
238 
239   // attach Sound s1 to DisplayObject dv1
240   add_actions(mo, "s1 = new Sound(dv1);");
241   // Sound.getVolume fetches volume from dv1 (see s2 below)
242   check_equals(mo, "s1.getVolume()", "100");
243 
244   // Change volume of Sound s1 (will change volume of attached DisplayObject, see below)
245   add_actions(mo, "s1.setVolume(1000);");
246   check_equals(mo, "s1.getVolume()", "1000");
247 
248   // attach Sound s2 to DisplayObject dv2
249   add_actions(mo, "s2 = new Sound(dv1);");
250   // Sound s2 finds volume of dv1 being 1000
251   check_equals(mo, "s2.getVolume()", "1000");
252 
253   // This shows that setVolume/getVolume make callbacks
254   // to attached DisplayObject as changing volume of one Sound
255   // influence the other attached sound...
256   add_actions(mo, "s2.setVolume(5);");
257   check_equals(mo, "s2.getVolume()", "5");
258   check_equals(mo, "s1.getVolume()", "5");
259   add_actions(mo, "s1.setVolume(80);");
260   check_equals(mo, "s2.getVolume()", "80");
261   check_equals(mo, "s1.getVolume()", "80");
262   // btw, negative volume is fine..
263   add_actions(mo, "s1.setVolume(-20);");
264   check_equals(mo, "s2.getVolume()", "-20");
265   check_equals(mo, "s1.getVolume()", "-20");
266 
267   // If the attached-to DisplayObject gets unloaded,
268   // getVolume returns undefined...
269   add_actions(mo, "dv1.removeMovieClip();");
270   check_equals(mo, "typeof(s2.getVolume())", "'undefined'");
271   check_equals(mo, "typeof(s1.getVolume())", "'undefined'");
272   // even if you reset it explicitly
273   add_actions(mo, "s2.setVolume(50);");
274   check_equals(mo, "typeof(s2.getVolume())", "'undefined'");
275   check_equals(mo, "typeof(s1.getVolume())", "'undefined'");
276   // but you get it back when you create a replacement..
277   add_actions(mo, "createEmptyMovieClip('dv1', 2);");
278   check_equals(mo, "s2.getVolume()", "100");
279   check_equals(mo, "s1.getVolume()", "100");
280   add_actions(mo, "s1.setVolume(80);");
281   check_equals(mo, "s2.getVolume()", "80");
282   check_equals(mo, "s1.getVolume()", "80");
283 
284   // And you can attach a Sound to any DisplayObject, not
285   // just MovieClip ones.
286   // This is against a video instance
287   add_actions(mo, "s1 = new Sound(video); s2 = new Sound(video);");
288   check_equals(mo, "s2.getVolume()", "100");
289   check_equals(mo, "s1.getVolume()", "100");
290   add_actions(mo, "s1.setVolume(80);");
291   check_equals(mo, "s2.getVolume()", "80");
292   check_equals(mo, "s1.getVolume()", "80");
293 
294   // Here's a 3 level volume controller. Our square.flv doesn't have sound
295   // so you can't really see if it's working or not... Anyway, change
296   // the code to load a movie with sound and see what happens changing the volumes
297   // below
298   add_actions(mo,
299 		"dv1.createEmptyMovieClip('dv2', 1);"
300 		"dv1.dv2.createEmptyMovieClip('dv3', 1);"
301                 "dv1.dv2.dv3.attachAudio(stream);" // attach stream to 3rd level
302                 "s1 = new Sound(dv1);"
303                 "s2 = new Sound(dv1.dv2);"
304                 "s3 = new Sound(dv1.dv2.dv3);"
305 		"s3.setVolume(50);" // ---------- level1 volume (change me for tests)
306 		"s1.setVolume(50);" // ---------- level2 volume (change me for tests)
307 		"s2.setVolume(400);" // --------- level3 volume (change me for tests)
308                 );
309 
310   //------------------------------------------
311   // Now attach video to the video DisplayObjects
312   //------------------------------------------
313   check(mo, "Video.prototype.hasOwnProperty('attachVideo')");
314   check(mo, "Video.prototype.hasOwnProperty('smoothing')");
315   check(mo, "Video.prototype.hasOwnProperty('deblocking')");
316   check(mo, "Video.prototype.hasOwnProperty('clear')");
317   check(mo, "Video.prototype.hasOwnProperty('height')");
318   check(mo, "Video.prototype.hasOwnProperty('width')");
319   check_equals(mo, "video.height", "0");
320   check_equals(mo, "video.width", "0");
321   check_equals(mo, "video2.height", "0");
322   check_equals(mo, "video2.width", "0");
323 
324   add_actions(mo, "video.attachVideo(stream);");
325   add_actions(mo, "video2.attachVideo(stream2);");
326   add_actions(mo, "video3.attachVideo(stream3);");
327 
328   check_equals(mo, "video.height", "0");
329   check_equals(mo, "video.width", "0");
330   check_equals(mo, "video2.height", "0");
331   check_equals(mo, "video2.width", "0");
332 
333   // currentFps (read-only)
334   check_equals (mo, "typeof(stream.currentFps)", "'number'" );
335   add_actions(mo, "stream.currentFps = 'string';");
336   check_equals (mo, "typeof(stream.currentFps)", "'number'" );
337   add_actions(mo, "stream.currentFps = false;");
338   check_equals (mo, "typeof(stream.currentFps)", "'number'" );
339 
340   // bufferLength (read-only)
341   check_equals (mo, "typeof(stream.bufferLength)", "'number'" );
342   add_actions(mo, "stream.bufferLength = 'string';");
343   check_equals (mo, "typeof(stream.bufferLength)", "'number'" );
344   add_actions(mo, "stream.bufferLength = false;");
345   check_equals (mo, "typeof(stream.bufferLength)", "'number'" );
346 
347   // bufferTime
348   check_equals (mo, "typeof(stream.bufferTime)", "'number'" );
349   add_actions(mo, "stream.setBufferTime(2);");
350   check_equals (mo, "stream.bufferTime", "2");
351   add_actions(mo,"stream.bufferTime = 20;");
352   check_equals (mo, "stream.bufferTime", "2");
353   add_actions(mo,"stream.setBufferTime = 30;");
354   check_equals (mo, "stream.bufferTime", "2");
355   add_actions(mo,"stream.setBufferTime(false);");
356   check_equals (mo, "stream.bufferTime", "2");
357   add_actions(mo,"stream.setBufferTime('string');");
358   check_equals (mo, "stream.bufferTime", "2");
359   add_actions(mo,"stream.setBufferTime('5');");
360   check_equals (mo, "stream.bufferTime", "2");
361   add_actions(mo,"stream.setBufferTime(10);");  // can't change it once set ...
362   check_equals (mo, "stream.bufferTime", "2");
363 
364   // liveDelay (read-only)
365   xcheck_equals (mo, "typeof(stream.liveDelay)", "'number'");
366   add_actions(mo, "stream.liveDelay = 'string';");
367   xcheck_equals (mo, "typeof(stream.liveDelay)", "'number'");
368 
369   // time (read-only)
370   check_equals (mo, "typeof(stream.time)", "'number'" );
371   add_actions(mo, "stream.time = 'string';");
372   check_equals (mo, "typeof(stream.time)", "'number'" );
373 
374   // bytesLoaded (read-only)
375   check_equals (mo, "typeof(stream.bytesLoaded)", "'number'" );
376   add_actions(mo, "stream.bytesLoaded = 'string';");
377   check_equals (mo, "typeof(stream.bytesLoaded)", "'number'" );
378 
379   check_equals (mo, "stream.currentFps", "0" );
380 
381   /* Play video */
382   b = newSWFAction(buffer_b);
383   if(b == NULL) return -1;
384   SWFMovie_add(mo, (SWFBlock)b);
385   b = newSWFAction(buffer_c);
386   if(b == NULL) return -1;
387   SWFMovie_add(mo, (SWFBlock)b);
388 
389   check_equals (mo, "stream.currentFps", "0" );
390 
391   /* Publisher Methods */
392 
393   // These are documented as player methods for connections
394   // to a media server.
395   // Possibly they are only defined if the connection is to
396   // such a server; at this point, they should be undefined in
397   // any case.
398 
399   check_equals (mo, "typeof(stream.attachAudio())", "'undefined'");
400   check_equals (mo, "typeof(stream.attachVideo())", "'undefined'");
401   check_equals (mo, "typeof(stream.publish())", "'undefined'");
402   check_equals (mo, "typeof(stream.send())", "'undefined'");
403   check_equals (mo, "typeof(stream.receiveAudio())", "'undefined'");
404   check_equals (mo, "typeof(stream.receiveVideo())", "'undefined'");
405 
406   /* Video checks */
407 
408   check_equals(mo, "video._xscale", "100");
409   check_equals(mo, "video._yscale", "100");
410   check_equals(mo, "video._rotation", "0");
411   check_equals(mo, "video._target", "'/video'");
412   check_equals(mo, "video.height", "0");
413   check_equals(mo, "video.width", "0");
414   check_equals(mo, "video2.height", "0");
415   check_equals(mo, "video2.width", "0");
416 
417   check(mo, "Video.prototype.hasOwnProperty('attachVideo')");
418   check(mo, "Video.prototype.hasOwnProperty('smoothing')");
419   check(mo, "Video.prototype.hasOwnProperty('deblocking')");
420   check(mo, "Video.prototype.hasOwnProperty('clear')");
421   check(mo, "Video.prototype.hasOwnProperty('height')");
422   check(mo, "Video.prototype.hasOwnProperty('width')");
423 
424   check(mo, "!Video.prototype.hasOwnProperty('_alpha')");
425   check(mo, "!Video.prototype.hasOwnProperty('_height')");
426   check(mo, "!Video.prototype.hasOwnProperty('_name')");
427   check(mo, "!Video.prototype.hasOwnProperty('_parent')");
428   check(mo, "!Video.prototype.hasOwnProperty('_rotation')");
429   check(mo, "!Video.prototype.hasOwnProperty('_visible')");
430   check(mo, "!Video.prototype.hasOwnProperty('_width')");
431   check(mo, "!Video.prototype.hasOwnProperty('_x')");
432   check(mo, "!Video.prototype.hasOwnProperty('_xmouse')");
433   check(mo, "!Video.prototype.hasOwnProperty('_xscale')");
434   check(mo, "!Video.prototype.hasOwnProperty('_y')");
435   check(mo, "!Video.prototype.hasOwnProperty('_ymouse')");
436   check(mo, "!Video.prototype.hasOwnProperty('_yscale')");
437   check(mo, "!Video.prototype.hasOwnProperty('_xmouse')");
438 
439   check(mo, "!video.hasOwnProperty('_alpha')");
440   check(mo, "!video.hasOwnProperty('_height')");
441   check(mo, "!video.hasOwnProperty('_name')");
442   check(mo, "!video.hasOwnProperty('_parent')");
443   check(mo, "!video.hasOwnProperty('_rotation')");
444   check(mo, "!video.hasOwnProperty('_visible')");
445   check(mo, "!video.hasOwnProperty('_width')");
446   check(mo, "!video.hasOwnProperty('_x')");
447   check(mo, "!video.hasOwnProperty('_xmouse')");
448   check(mo, "!video.hasOwnProperty('_xscale')");
449   check(mo, "!video.hasOwnProperty('_y')");
450   check(mo, "!video.hasOwnProperty('_ymouse')");
451   check(mo, "!video.hasOwnProperty('_yscale')");
452   check(mo, "!video.hasOwnProperty('_xmouse')");
453 
454   add_actions(mo,
455 		"video._x = 100;"
456 		"video._xscale = 120;"
457 		"video._yscale = 120;"
458 		"video._rotation = 45;"
459 		"_root.addBytesLoadedProgress(video, stream);"
460 		"_root.addBytesLoadedProgress(video2, stream2);"
461 		"_root.addBufferLoadedProgress(video, stream);"
462 		"_root.addBufferLoadedProgress(video2, stream2);"
463 	);
464 
465   check_equals(mo, "video._x", "100")	;
466   check_equals(mo, "Math.round(video._xscale*100)/100", "120");
467   check_equals(mo, "Math.round(video._yscale*100)/100", "120");
468   check_equals(mo, "Math.round(video._rotation*100)/100", "45");
469 
470 
471   // How can I test props here ?
472   check_equals(mo, "typeof(video.hitTest)", "'undefined'");
473   check_equals(mo, "typeof(video.getBounds)", "'undefined'");
474 
475   SWFMovie_add(mo, (SWFBlock)newSWFAction(
476         "note('The video on the right is an OGG theora stream. It will appear "
477         "in Gnash, but not the Adobe player');"
478 		"_root.onKeyDown = function() {"
479 		" 	_root.note(' bufferLength:'+stream.bufferLength+"
480 		" 		' bytesLoaded:'+stream.bytesLoaded+"
481 		"		' currentFps:'+stream.currentFps+' time:'+stream.time);"
482 		"	var ascii = Key.getAscii();"
483 		"	trace('Key down: '+ascii);"
484 		"	if ( ascii == 32 ) {" // ' ' - pause(toggle)
485 		"		stream.paused = !stream.paused;"
486 		"		stream.pause();" // stream.paused);"
487 		"	}"
488 		"	else if ( ascii == 112 ) {" // 'p' - play()
489 		"		stream.play();"
490 		"               _root.note(\"2. Verify video hasn't started, then press space to continue.\");"
491 		"	}"
492 		"	else if ( ascii == 99 ) {" // 'c' - play()
493 		"		_root.check(nc.isConnected, 'NetConnection is connected');"
494 		"		_root.nc.close();"
495 		"               _root.note(\"Closed netconnection\");"
496 		"		_root.check(!nc.isConnected, 'NetConnection is not connected');"
497 		"	}"
498 		"};"
499 		"Key.addListener(_root);"
500 
501 		"\n"
502 
503 		"stream.onStatus = function(info) {"
504 
505 		"  if ( ! _root.enumerableStatusInfoChecked ) {"
506 		"    _root.check(info.code != undefined);"
507 		"    _root.check(info.level != undefined);"
508 		"    var tmp = new Array();"
509 		"    for (var e in info) tmp.push(e);"
510 		"    tmp.sort();"
511 		"    _root.check_equals(tmp.length, 2);"
512 		"    _root.check_equals(tmp[0], 'code');"
513 		"    _root.check_equals(tmp[1], 'level');"
514 		"    _root.enumerableStatusInfoChecked=true;"
515 		"    var backup = info.code; info.code = 65;"
516 		"    _root.check_equals(info.code, 65);"
517 		"    _root.check(delete info.code);"
518 		"    _root.check_equals(info.code, undefined);"
519 		"    info.code = backup;"
520 		"    var backup = info.level; info.level = 66;"
521 		"    _root.check_equals(info.level, 66);"
522 		"    _root.check(delete info.level);"
523 		"    _root.check_equals(info.level, undefined);"
524 		"    info.level = backup;"
525 		"  }"
526 
527 		"\n"
528 
529 		// Ignore Buffer.Flush for now
530 		"  if ( info.code == 'NetStream.Buffer.Flush' ) return; "
531 
532 		// Print some info
533 		" _root.note('onStatus('+info.code+') called'); "
534 
535 		"\n"
536 
537 		" if ( info.code == 'NetStream.Play.Start' )"
538 		" {"
539 		"	check(!_root.startNotified, 'No duplicated Play.Start notification');"
540 		"	_root.startNotified++;"
541 		" }"
542 
543 		" else if ( info.code == 'NetStream.Play.Stop' )"
544 		" {"
545 		" 	if ( ! _root.stopNotified )"
546 		" 	{"
547 		" 		_root.stopNotified++;"
548 		" 		stream.seek(0);"
549 		" 	} else {"
550 		"		_root.stopNotified++;"
551 		" 		_root.check(this instanceOf NetStream); "
552 		" 		_root.check_equals(this.bufferTime, 2); "
553 		" 		_root.check_equals(this.bytesTotal, 21482); "
554 		"		_root.nextFrame();"
555 		"	}"
556 		" }"
557 #if 0
558 		" else if ( info.code == 'NetStream.Buffer.Empty' && this.stopNotified ) "
559 	        " {"
560 		"	check ( this.stopNotified )"
561 		"	this.close();"
562 		"	_root.nextFrame();"
563 		" }"
564 #endif
565 
566 		"};"
567 
568 		"\n"
569 
570 		"stream.onCuePoint = function(info) {"
571 		" _root.note('onCuePoint('+info+') called'); "
572 		"};"
573 		"stream.onMetaData = function(info) {"
574 
575 		// debugging
576 		" var s='';"
577 		" for (e in info) { "
578 		"  s += e+':'+info[e]+' ';"
579 		" }"
580 		" _root.note('onMetaData: '+s);"
581 
582 
583 		" _root.metadataNotified++;"
584 
585 		// don't run other tests if already done
586 		// BTW: should be called once, gnash (gst) calls this twice
587 		//      and would succeed in composition checking in second call.
588 		" if ( _root.metadataNotified > 1 ) return;"
589 
590 		" check(_root.startNotified, 'onMetaData should be notified after Play.Start');"
591 		" check_equals(arguments.length, 1, 'single argument');"
592 		" check(info instanceof Array, 'onMetaData argument sent from square.flv should be instanceof Array');"
593 		" check_equals(info.length, 11);"
594 
595 		// Test enumeration
596 		" var enu = new Array;"
597 		" for (e in info) { "
598 		"  enu.push(e);"
599 		" }"
600 		" check_equals(enu.length, 11);" // this is actual (not virtual) composition
601 
602 		"\n"
603 
604 
605 		// Test composision
606 
607 		" check(info.hasOwnProperty('length'), 'metadata has length');" // it's an array...
608 
609 		" check(info.hasOwnProperty('filesize'), 'metadata has filesize');"
610 		" check_equals(typeof(info.filesize), 'number', 'filesize is a number');"
611 		" check_equals(info.filesize, '21482', 'actual filesize');"
612 		" info.filesize = 'changed';"
613 		" check_equals(info.filesize, 'changed');" // can be overridden
614 		" delete info.filesize;"
615 		" check(!info.hasOwnProperty('filesize'), 'metadata filesize can be deleted');"
616 
617 		" check(info.hasOwnProperty('audiocodecid'), 'metadata has audiocodecid');"
618 		" check_equals(typeof(info.audiocodecid), 'number', 'audiocodecid is a number');"
619 		" check_equals(info.audiocodecid, 2, 'actual audiocodecid');"
620 		" info.audiocodecid = 'changed';"
621 		" check_equals(info.audiocodecid, 'changed');" // can be overridden
622 		" delete info.audiocodecid;"
623 		" check(!info.hasOwnProperty('audiocodecid'), 'metadata audiocodecid can be deleted');"
624 
625 		"\n"
626 
627 		" check(info.hasOwnProperty('stereo'), 'metadata has stereo');"
628 		" check_equals(typeof(info.stereo), 'boolean', 'stereo is boolean');"
629 		" check_equals(info.stereo, false, 'actual stereo');"
630 		" info.stereo = 'changed';"
631 		" check_equals(info.stereo, 'changed');" // can be overridden
632 		" delete info.stereo;"
633 		" check(!info.hasOwnProperty('stereo'), 'metadata stereo can be deleted');"
634 
635 		" check(info.hasOwnProperty('audiosamplesize'), 'metadata has audiosamplesize');"
636 		" check_equals(typeof(info.audiosamplesize), 'number', 'audiosamplesize is a number');"
637 		" check_equals(info.audiosamplesize, 16, 'actual audiosamplesize');"
638 		" info.audiosamplesize = 'changed';"
639 		" check_equals(info.audiosamplesize, 'changed');" // can be overridden
640 		" delete info.audiosamplesize;"
641 		" check(!info.hasOwnProperty('audiosamplesize'), 'metadata audiosamplesize can be deleted');"
642 
643 		"\n"
644 
645 		" check(info.hasOwnProperty('audiosamplerate'), 'metadata has audiosamplerate');"
646 		" check_equals(typeof(info.audiosamplerate), 'number', 'audiosamplerate is a number');"
647 		" check_equals(info.audiosamplerate, 44100, 'actual audiosamplerate');"
648 		" info.audiosamplerate = 'changed';"
649 		" check_equals(info.audiosamplerate, 'changed');" // can be overridden
650 		" delete info.audiosamplerate;"
651 		" check(!info.hasOwnProperty('audiosamplerate'), 'metadata audiosamplerate can be deleted');"
652 
653 		" check(info.hasOwnProperty('videocodecid'), 'metadata has videocodecid');"
654 		" check_equals(typeof(info.videocodecid), 'number', 'videocodecid is a number');"
655 		" check_equals(info.videocodecid, 2, 'actual videocodecid');"
656 		" info.videocodecid = 'changed';"
657 		" check_equals(info.videocodecid, 'changed');" // can be overridden
658 		" delete info.videocodecid;"
659 		" check(!info.hasOwnProperty('videocodecid'), 'metadata videocodecid can be deleted');"
660 
661 		"\n"
662 
663 		" check(info.hasOwnProperty('height'), 'metadata has height');"
664 		" check_equals(typeof(info.height), 'number', 'height is a number');"
665 		" check_equals(info.height, 96, 'actual height');"
666 		" info.height = 'changed';"
667 		" check_equals(info.height, 'changed');" // can be overridden
668 		" delete info.height;"
669 		" check(!info.hasOwnProperty('height'), 'metadata height can be deleted');"
670 
671 		" check(info.hasOwnProperty('width'), 'metadata has width');"
672 		" check_equals(typeof(info.width), 'number', 'width is a number');"
673 		" check_equals(info.width, 128, 'actual width');"
674 		" info.width = 'changed';"
675 		" check_equals(info.width, 'changed');" // can be overridden
676 		" delete info.width;"
677 		" check(!info.hasOwnProperty('width'), 'metadata width can be deleted');"
678 
679 		"\n"
680 
681 		" check(info.hasOwnProperty('duration'), 'metadata has duration');"
682 		" check_equals(typeof(info.duration), 'number', 'duration is a number');"
683 		" check_equals(info.duration, 2.299, 'actual duration');" // seconds, rounded to milliseconds
684 		" info.duration = 'changed';"
685 		" check_equals(info.duration, 'changed');" // can be overridden
686 		" delete info.duration;"
687 		" check(!info.hasOwnProperty('duration'), 'metadata duration can be deleted');"
688 		" _root.note('1. Press \"p\" key');"
689 		"};"
690 		));
691 
692   SWFMovie_nextFrame(mo);
693 
694   check_equals(mo, "video.height", "96");
695   check_equals(mo, "video.width", "128");
696 
697 
698   // See https://savannah.gnu.org/bugs/?26687
699   SWFMovie_add(mo, (SWFBlock)newSWFAction(
700   //"note('VM.time:'+getTimer()+' stream3.time:'+stream3.time);"
701   "check(stream3.time > 4150, 'stream3.time '+stream3.time+' > 4150');"
702   ));
703 
704 
705   //add_actions(mo, "note('This is an OGG stream, so the Adobe player "
706   //    "will fail the next two tests.');");
707   // gstreamer also fails, so let's not test this.
708   //check_equals(mo, "video2.height", "96");
709   //check_equals(mo, "video2.width", "128");
710 
711   check_equals(mo, "metadataNotified", "1");
712   check_equals(mo, "stopNotified", "2");
713   check_equals(mo, "startNotified", "1");
714   SWFMovie_add(mo, (SWFBlock)newSWFAction("totals(197); stop(); end_of_test=true;"));
715 
716   SWFMovie_nextFrame(mo);
717 
718 
719   /* Output movie */
720   puts("Saving " OUTPUT_FILENAME );
721   SWFMovie_save(mo, OUTPUT_FILENAME);
722 
723   return 0;
724 }
725