1/*
2 * jPlayer Plugin for jQuery JavaScript Library
3 * http://www.jplayer.org
4 *
5 * Copyright (c) 2009 - 2014 Happyworm Ltd
6 * Licensed under the MIT license.
7 * http://opensource.org/licenses/MIT
8 *
9 * Author: Mark J Panaghiston
10 * Version: 2.8.0
11 * Date: 11th November 2014
12 *
13 * FlashVars expected: (AS3 property of: loaderInfo.parameters)
14 *	id: 	(URL Encoded: String) Id of jPlayer instance
15 *	vol:	(Number) Sets the initial volume
16 *	muted:	(Boolean in a String) Sets the initial muted state
17 *	jQuery:	(URL Encoded: String) Sets the jQuery var name. Used with: someVar = jQuery.noConflict(true); The someVar name must contain jQuery in it.
18 *
19 * Compiled using: Adobe Flex Compiler (mxmlc) Version 4.6
20 *  mxmlc Jplayer.as -static-link-runtime-shared-libraries=true
21 */
22
23package {
24	import flash.system.Security;
25	import flash.external.ExternalInterface;
26
27	import flash.utils.Timer;
28	import flash.events.TimerEvent;
29
30	import flash.text.TextField;
31	import flash.text.TextFormat;
32
33	import flash.events.KeyboardEvent;
34
35	import flash.display.Sprite;
36	import happyworm.jPlayer.*;
37
38	import flash.display.StageAlign;
39	import flash.display.StageScaleMode;
40	import flash.events.Event;
41	import flash.net.LocalConnection;
42	import flash.events.StatusEvent;
43
44	import flash.events.MouseEvent;
45
46	import flash.ui.ContextMenu;
47	import flash.ui.ContextMenuItem;
48	import flash.events.ContextMenuEvent;
49	import flash.net.URLRequest;
50	import flash.net.navigateToURL;
51	import flash.media.Video;
52
53	public class Jplayer extends Sprite {
54		private var jQuery:String;
55		private var sentNumberFractionDigits:uint = 2;
56
57		public var commonStatus:JplayerStatus = new JplayerStatus(); // Used for inital ready event so volume is correct.
58
59		private var myInitTimer:Timer = new Timer(100, 0);
60
61		private var myMp3Player:JplayerMp3;
62		private var myMp4Player:JplayerMp4;
63
64		private var myRtmpPlayer:JplayerRtmp;
65
66		private var isRtmp:Boolean = false;
67		private var isMp4:Boolean = false;
68
69		private var isMp3:Boolean = false;
70		private var isVideo:Boolean = false;
71
72		private var securityIssue:Boolean = false; // On direct access and when SWF parameters contain illegal characters
73
74		private var contextMenuFix:Sprite = new Sprite(); // A sprite that will be on top and fix the content menu over video bug. (A Flash plugin bug.)
75
76		private var txLog:TextField;
77		private var debug:Boolean = false; // Set debug to false for release compile!
78
79		public function Jplayer() {
80
81			flash.system.Security.allowDomain("*");
82			flash.system.Security.allowInsecureDomain("*");
83
84			// Fix to the security exploit reported by Jason Calvert http://appsec.ws/
85			checkFlashVars(loaderInfo.parameters);
86
87			stage.scaleMode = StageScaleMode.NO_SCALE;
88			stage.align = StageAlign.TOP_LEFT;
89
90			if(!securityIssue) {
91				// The jQuery param is the primary cause of security concerns.
92				jQuery = loaderInfo.parameters.jQuery + "('#" + loaderInfo.parameters.id + "').jPlayer";
93				commonStatus.volume = Number(loaderInfo.parameters.vol);
94				commonStatus.muted = loaderInfo.parameters.muted == "true";
95
96				stage.addEventListener(Event.RESIZE, resizeHandler);
97				stage.addEventListener(MouseEvent.CLICK, clickHandler);
98
99				var initialVolume:Number = commonStatus.volume;
100				if(commonStatus.muted) {
101					initialVolume = 0;
102				}
103
104				myMp3Player = new JplayerMp3(initialVolume);
105				addChild(myMp3Player);
106
107				myMp4Player = new JplayerMp4(initialVolume);
108				addChild(myMp4Player);
109
110				myRtmpPlayer = new JplayerRtmp(initialVolume);
111				addChild(myRtmpPlayer);
112
113				switchType("mp3"); // set default state to mp3
114			}
115
116			// The ContextMenu only partially works. The menu select events never occur.
117			// Investigated and it is something to do with the way jPlayer inserts the Flash on the page.
118			// A simple test inserting the Jplayer.swf on a page using: 1) SWFObject 2.2 works. 2) AC_FL_RunContent() works.
119			// jPlayer Flash insertion is based on SWFObject 2.2 and the resaon behind this failure is not clear. The Flash insertion HTML on the page looks similar.
120			var myContextMenu:ContextMenu = new ContextMenu();
121			myContextMenu.hideBuiltInItems();
122			var menuItem_jPlayer:ContextMenuItem = new ContextMenuItem("jPlayer " + JplayerStatus.VERSION);
123			var menuItem_happyworm:ContextMenuItem = new ContextMenuItem("© 2009-2014 Happyworm Ltd", true);
124			menuItem_jPlayer.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuSelectHandler_jPlayer);
125			menuItem_happyworm.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuSelectHandler_happyworm);
126			myContextMenu.customItems.push(menuItem_jPlayer, menuItem_happyworm);
127			contextMenu = myContextMenu;
128
129			// Log console for dev compile option: debug
130			if(debug || securityIssue) {
131				txLog = new TextField();
132				txLog.x = 5;
133				txLog.y = 5;
134				txLog.width = stage.stageWidth - 10;
135				txLog.height = stage.stageHeight - 10;
136				txLog.backgroundColor = 0xEEEEFF;
137				txLog.border = true;
138				txLog.background = true;
139				txLog.multiline = true;
140				txLog.text = "jPlayer " + JplayerStatus.VERSION;
141
142				if(securityIssue) {
143					txLog.visible = true;
144				} else if(debug) {
145					txLog.visible = false;
146				}
147
148				this.addChild(txLog);
149
150				if(debug && !securityIssue) {
151					this.stage.addEventListener(KeyboardEvent.KEY_UP, keyboardHandler);
152
153					myMp3Player.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler);
154					myMp4Player.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler);
155					myRtmpPlayer.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler);
156				}
157			}
158
159			// Known Flash problem with contextMenu over video player.
160			// Add a transparent rectangle into the sprite.
161			contextMenuFix.graphics.beginFill(0x000000, 0); // Transparent black
162			contextMenuFix.graphics.drawRect(0, 0, 10, 10); // Arbitary rectangle
163			contextMenuFix.graphics.endFill();
164			addChild(contextMenuFix); // Put the sprite on the top layer.
165
166			if(!securityIssue) {
167				// Delay init() because Firefox 3.5.7+ developed a bug with local testing in Firebug.
168				myInitTimer.addEventListener(TimerEvent.TIMER, init);
169				myInitTimer.start();
170			}
171		}
172
173		private function switchType(playType:String):void {
174			switch(playType) {
175				case "rtmpa":
176				isRtmp=true;
177				isMp3=false;
178				isMp4=false;
179				isVideo=false;
180				break;
181			case "rtmpv":
182				isRtmp=true;
183				isMp3=false;
184				isMp4=false;
185				isVideo=true;
186				break;
187			case "mp3":
188				isRtmp=false;
189				isMp3=true;
190				isMp4=false;
191				isVideo=false;
192				break;
193			case "mp4":
194				isRtmp=false;
195				isMp3=false;
196				isMp4=true;
197				isVideo=false;
198				break;
199			case "m4v":
200				isRtmp=false;
201				isMp3=false;
202				isMp4=true;
203				isVideo=true;
204				break;
205			}
206
207			listenToMp3(isMp3);
208			listenToMp4(isMp4);
209			listenToRtmp(isRtmp);
210		}
211
212		private function init(e:TimerEvent):void {
213			myInitTimer.stop();
214			if(ExternalInterface.available && !securityIssue) {
215				ExternalInterface.addCallback("fl_setAudio_mp3", fl_setAudio_mp3);
216				ExternalInterface.addCallback("fl_setAudio_m4a", fl_setAudio_m4a);
217				ExternalInterface.addCallback("fl_setVideo_m4v", fl_setVideo_m4v);
218				ExternalInterface.addCallback("fl_setAudio_rtmp", fl_setAudio_rtmp);
219				ExternalInterface.addCallback("fl_setVideo_rtmp", fl_setVideo_rtmp);
220				ExternalInterface.addCallback("fl_clearMedia", fl_clearMedia);
221				ExternalInterface.addCallback("fl_load", fl_load);
222				ExternalInterface.addCallback("fl_play", fl_play);
223				ExternalInterface.addCallback("fl_pause", fl_pause);
224				ExternalInterface.addCallback("fl_play_head", fl_play_head);
225				ExternalInterface.addCallback("fl_volume", fl_volume);
226				ExternalInterface.addCallback("fl_mute", fl_mute);
227
228				ExternalInterface.call(jQuery, "jPlayerFlashEvent", JplayerEvent.JPLAYER_READY, extractStatusData(commonStatus)); // See JplayerStatus() class for version number.
229			}
230		}
231		private function checkFlashVars(p:Object):void {
232			// Check for direct access. Inspired by mediaelement.js - Also added name to HTML object for non-IE browsers.
233			if(ExternalInterface.objectID != null && ExternalInterface.objectID.toString() != "") {
234				for each (var s:String in p) {
235					if(illegalChar(s)) {
236						securityIssue = true; // Found a security concern.
237					}
238				}
239				if(!securityIssue) {
240					if(jQueryIllegal(p.jQuery)) {
241						securityIssue = true; // Found a security concern.
242					}
243				}
244			} else {
245				securityIssue = true; // Direct access disables the callbacks, which were a security concern.
246			}
247		}
248		private function illegalChar(s:String):Boolean {
249			// A whitelist of accepted chars.
250			var validParam:RegExp = /^[-A-Za-z0-9_.]+$/;
251			return !validParam.test(s);
252		}
253		private function jQueryIllegal(s:String):Boolean {
254			// Check param contains the term jQuery.
255			var validParam:RegExp = /(jQuery)/;
256			return !validParam.test(s);
257		}
258		// switchType() here
259		private function listenToMp3(active:Boolean):void {
260			if(active) {
261				myMp3Player.addEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
262				myMp3Player.addEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
263				myMp3Player.addEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
264				myMp3Player.addEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
265
266				myMp3Player.addEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
267				myMp3Player.addEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
268				myMp3Player.addEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
269
270				myMp3Player.addEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
271				myMp3Player.addEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
272
273				myMp3Player.addEventListener(JplayerEvent.JPLAYER_WAITING, jPlayerFlashEvent); // only MP3 atm
274				myMp3Player.addEventListener(JplayerEvent.JPLAYER_PLAYING, jPlayerFlashEvent); // only MP3 atm
275
276				myMp3Player.addEventListener(JplayerEvent.JPLAYER_CANPLAY, jPlayerFlashEvent); // only MP3 atm
277				myMp3Player.addEventListener(JplayerEvent.JPLAYER_CANPLAYTHROUGH, jPlayerFlashEvent); // only MP3 atm
278
279				myMp3Player.addEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerFlashEvent);
280				myMp3Player.addEventListener(JplayerEvent.JPLAYER_DURATIONCHANGE, jPlayerFlashEvent); // only MP3 atm
281			} else {
282				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
283				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
284				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
285				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
286
287				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
288				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
289				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
290
291				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
292				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
293
294				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_WAITING, jPlayerFlashEvent); // only MP3 atm
295				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_PLAYING, jPlayerFlashEvent); // only MP3 atm
296
297				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_CANPLAY, jPlayerFlashEvent); // only MP3 atm
298				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_CANPLAYTHROUGH, jPlayerFlashEvent); // only MP3 atm
299
300				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerFlashEvent);
301				myMp3Player.removeEventListener(JplayerEvent.JPLAYER_DURATIONCHANGE, jPlayerFlashEvent); // only MP3 atm
302			}
303		}
304		private function listenToMp4(active:Boolean):void {
305			if(active) {
306				myMp4Player.addEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
307				myMp4Player.addEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
308				myMp4Player.addEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
309				myMp4Player.addEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
310
311				myMp4Player.addEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
312				myMp4Player.addEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
313				myMp4Player.addEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
314
315				myMp4Player.addEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
316				myMp4Player.addEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
317
318				myMp4Player.addEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerMetaDataHandler); // Note the unique handler
319			} else {
320				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
321				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
322				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
323				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
324
325				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
326				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
327				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
328
329				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
330				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
331
332				myMp4Player.removeEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerMetaDataHandler); // Note the unique handler
333			}
334		}
335
336		private function listenToRtmp(active:Boolean):void {
337			if(active) {
338				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
339				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
340				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
341				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
342
343				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
344				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
345				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
346
347				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_CANPLAY, jPlayerFlashEvent);
348
349				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
350				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
351
352				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerMetaDataHandler); // Note the unique handler
353			} else {
354				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
355				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
356				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
357				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
358
359				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
360				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
361				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
362
363				myRtmpPlayer.addEventListener(JplayerEvent.JPLAYER_CANPLAY, jPlayerFlashEvent);
364
365				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
366				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
367
368				myRtmpPlayer.removeEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerMetaDataHandler); // Note the unique handler
369			}
370		}
371
372		private function fl_setAudio_mp3(src:String):Boolean {
373			if (src != null) {
374				log("fl_setAudio_mp3: "+src);
375				switchType("mp3");
376				myMp4Player.clearFile();
377				myRtmpPlayer.clearFile();
378				myMp3Player.setFile(src);
379				return true;
380			} else {
381				log("fl_setAudio_mp3: null");
382				return false;
383			}
384		}
385		private function fl_setAudio_rtmp(src:String):Boolean {
386			if (src != null) {
387				log("fl_setAudio_rtmp: "+src);
388				switchType("rtmpa");
389				myMp4Player.clearFile();
390				myMp3Player.clearFile();
391				myRtmpPlayer.setFile(src,false);
392				return true;
393			} else {
394				log("fl_setAudio_rtmp: null");
395				return false;
396			}
397		}
398
399		private function fl_setVideo_rtmp(src:String):Boolean {
400			if (src != null) {
401				log("fl_setVideo_rtmp: "+src);
402				switchType("rtmpv");
403				myMp4Player.clearFile();
404				myMp3Player.clearFile();
405				myRtmpPlayer.setFile(src,true);
406				return true;
407			} else {
408				log("fl_setVideo_rtmp: null");
409				return false;
410			}
411		}
412		private function fl_setAudio_m4a(src:String):Boolean {
413			if (src != null) {
414				log("fl_setAudio_m4a: "+src);
415				switchType("mp4")
416				myMp3Player.clearFile();
417				myRtmpPlayer.clearFile();
418				myMp4Player.setFile(src);
419				return true;
420			} else {
421				log("fl_setAudio_m4a: null");
422				return false;
423			}
424		}
425		private function fl_setVideo_m4v(src:String):Boolean {
426			if (src != null) {
427				log("fl_setVideo_m4v: "+src);
428				switchType("m4v");
429				myMp3Player.clearFile();
430				myRtmpPlayer.clearFile();
431				myMp4Player.setFile(src);
432				return true;
433			} else {
434				log("fl_setVideo_m4v: null");
435				return false;
436			}
437		}
438		private function fl_clearMedia():void {
439			log("clearMedia.");
440			myMp3Player.clearFile();
441			myMp4Player.clearFile();
442			myRtmpPlayer.clearFile();
443		}
444
445		private function getType():Object {
446			var returnType:Object;
447			if(isMp3) {
448				returnType=myMp3Player;
449			}
450			if(isRtmp) {
451				returnType=myRtmpPlayer;
452			}
453			if(isMp4) {
454				returnType=myMp4Player;
455			}
456			return returnType;
457		}
458
459		private function fl_load():Boolean {
460			log("load.");
461			var returnType:Object = getType();
462			return returnType.load();
463		}
464		private function fl_play(time:Number = NaN):Boolean {
465			log("play: time = " + time);
466			var returnType:Object = getType();
467			return returnType.play(time * 1000); // Flash uses milliseconds
468		}
469		private function fl_pause(time:Number = NaN):Boolean {
470			log("pause: time = " + time);
471			var returnType:Object = getType();
472			return returnType.pause(time * 1000); // Flash uses milliseconds
473		}
474		private function fl_play_head(percent:Number):Boolean {
475			log("play_head: "+percent+"%");
476			var returnType:Object = getType();
477			return returnType.playHead(percent);
478		}
479		private function fl_volume(v:Number):void {
480			log("volume: "+v);
481			commonStatus.volume = v;
482			if(!commonStatus.muted) {
483				myMp3Player.setVolume(v);
484				myMp4Player.setVolume(v);
485				myRtmpPlayer.setVolume(v);
486			}
487		}
488		private function fl_mute(mute:Boolean):void {
489			log("mute: "+mute);
490			commonStatus.muted = mute;
491			if(mute) {
492				myMp3Player.setVolume(0);
493				myMp4Player.setVolume(0);
494				myRtmpPlayer.setVolume(0);
495			} else {
496				myMp3Player.setVolume(commonStatus.volume);
497				myMp4Player.setVolume(commonStatus.volume);
498				myRtmpPlayer.setVolume(commonStatus.volume);
499			}
500		}
501		private function jPlayerFlashEvent(e:JplayerEvent):void {
502			log("jPlayer Flash Event: " + e.type + ": " + e.target);
503			if(ExternalInterface.available && !securityIssue) {
504				ExternalInterface.call(jQuery, "jPlayerFlashEvent", e.type, extractStatusData(e.data));
505			}
506		}
507
508		private function extractStatusData(data:JplayerStatus):Object {
509			var myStatus:Object = {
510				version: JplayerStatus.VERSION,
511				src: data.src,
512				paused: !data.isPlaying, // Changing this name requires inverting all assignments and conditional statements.
513				srcSet: data.srcSet,
514				seekPercent: data.seekPercent,
515				currentPercentRelative: data.currentPercentRelative,
516				currentPercentAbsolute: data.currentPercentAbsolute,
517				currentTime: data.currentTime / 1000, // JavaScript uses seconds
518				duration: data.duration / 1000, // JavaScript uses seconds
519				videoWidth: data.videoWidth,
520				videoHeight: data.videoHeight,
521				volume: commonStatus.volume,
522				muted: commonStatus.muted
523			};
524			log("extractStatusData: sp="+myStatus.seekPercent+" cpr="+myStatus.currentPercentRelative+" cpa="+myStatus.currentPercentAbsolute+" ct="+myStatus.currentTime+" d="+myStatus.duration);
525			return myStatus;
526		}
527		private function jPlayerMetaDataHandler(e:JplayerEvent):void {
528			log("jPlayerMetaDataHandler:" + e.target);
529			if(ExternalInterface.available && !securityIssue) {
530				resizeHandler(new Event(Event.RESIZE));
531				ExternalInterface.call(jQuery, "jPlayerFlashEvent", e.type, extractStatusData(e.data));
532			}
533		}
534		private function resizeHandler(e:Event):void {
535			log("resizeHandler: stageWidth = " + stage.stageWidth + " | stageHeight = " + stage.stageHeight);
536
537			var mediaX:Number = 0;
538			var mediaY:Number = 0;
539			var mediaWidth:Number = 0;
540			var mediaHeight:Number = 0;
541
542			var aspectRatioStage:Number = 0;
543			var aspectRatioVideo:Number = 0;
544
545			var videoItem:*;
546
547			if(isRtmp) {
548				videoItem = myRtmpPlayer;
549			}
550			if(isMp4) {
551				videoItem = myMp4Player;
552			}
553
554			if(videoItem) {
555				if(stage.stageWidth > 0 && stage.stageHeight > 0 && videoItem.myVideo.width > 0 && videoItem.myVideo.height > 0) {
556					aspectRatioStage = stage.stageWidth / stage.stageHeight;
557					aspectRatioVideo = videoItem.myVideo.width / videoItem.myVideo.height;
558					if(aspectRatioStage < aspectRatioVideo) {
559						mediaWidth = stage.stageWidth;
560						mediaHeight = stage.stageWidth / aspectRatioVideo;
561						mediaX = 0;
562						mediaY = (stage.stageHeight - mediaHeight) / 2;
563					} else {
564						mediaWidth = stage.stageHeight * aspectRatioVideo;
565						mediaHeight = stage.stageHeight;
566						mediaX = (stage.stageWidth - mediaWidth) / 2;
567						mediaY = 0;
568					}
569					resizeEntity(videoItem, mediaX, mediaY, mediaWidth, mediaHeight);
570				}
571			}
572			if((debug || securityIssue) && stage.stageWidth > 20 && stage.stageHeight > 20) {
573				txLog.width = stage.stageWidth - 10;
574				txLog.height = stage.stageHeight - 10;
575			}
576			// Resize the sprite so it covers the entire stage area
577			contextMenuFix.x = 0;
578			contextMenuFix.y = 0;
579			contextMenuFix.width = stage.stageWidth;
580			contextMenuFix.height = stage.stageHeight;
581		}
582		private function resizeEntity(entity:Sprite, mediaX:Number, mediaY:Number, mediaWidth:Number, mediaHeight:Number):void {
583			entity.x = mediaX;
584			entity.y = mediaY;
585			entity.width = mediaWidth;
586			entity.height = mediaHeight;
587		}
588		private function clickHandler(e:MouseEvent):void {
589			// This needs to work with RTMP format too!
590			if(isMp3) {
591				jPlayerFlashEvent(new JplayerEvent(JplayerEvent.JPLAYER_CLICK, myMp3Player.myStatus, "click"))
592			} else {
593				jPlayerFlashEvent(new JplayerEvent(JplayerEvent.JPLAYER_CLICK, myMp4Player.myStatus, "click"))
594			}
595		}
596		// Handlers for context menu
597		private function menuSelectHandler_jPlayer(e:ContextMenuEvent):void {
598			navigateToURL(new URLRequest("http://jplayer.org/"), "_blank");
599		}
600		private function menuSelectHandler_happyworm(e:ContextMenuEvent):void {
601			navigateToURL(new URLRequest("http://happyworm.com/"), "_blank");
602		}
603		private function log(t:String):void {
604			if(debug) {
605				txLog.text = t + "\n" + txLog.text;
606				if(ExternalInterface.available && !securityIssue) {
607					ExternalInterface.call("console.log", t);
608				}
609			}
610		}
611		private function debugMsgHandler(e:JplayerEvent):void {
612			log(e.msg);
613		}
614		private function keyboardHandler(e:KeyboardEvent):void {
615			log("keyboardHandler: e.keyCode = " + e.keyCode);
616			switch(e.keyCode) {
617				case 68 : // d
618					txLog.visible = !txLog.visible;
619					log("Toggled log display: " + txLog.visible);
620					break;
621				case 76 : // l
622					if(e.ctrlKey && e.shiftKey) {
623						txLog.text = "Cleared log.";
624					}
625					break;
626			}
627		}
628	}
629}
630