root/audio-player/trunk/source/classes/Application.as

Revision 322, 21.6 KB (checked in by doryphores, 2 years ago)

Fixed rtl option back to false

Line 
1import net.onepixelout.audio.*;
2import flash.external.ExternalInterface;
3
4class Application
5{
6        // Audio Player
7        private static var _player:Player;
8       
9        // UI Elements
10        private static var masked_mc:MovieClip;
11        private static var background_mc:MovieClip;
12        private static var progress_mc:MovieClip;
13        private static var loading_mc:MovieClip;
14        private static var next_mc:MovieClip;
15        private static var previous_mc:MovieClip;
16        private static var mask_mc:MovieClip;
17        private static var display_mc:MovieClip;
18        private static var control_mc:MovieClip;
19        private static var volume_mc:MovieClip;
20       
21        // State variables
22        private static var _state:Number;
23       
24        private static var CLOSED:Number = 0;
25        private static var CLOSING:Number = 1;
26        private static var OPENING:Number = 2;
27        private static var OPEN:Number = 3;
28
29        // Interval ID for animation
30        private static var _clearID:Number;
31       
32        // List of color keys
33        private static var _colorKeys:Array = ["bg","leftbg","lefticon","voltrack","volslider","rightbg","rightbghover","righticon","righticonhover","text","track","border","loader","tracker","skip"];
34       
35        // Holds the current colour scheme (initialise with default colour scheme)
36        private static var _colorScheme:Object = {
37                bg:0xE5E5E5,
38                leftbg:0xCCCCCC,
39                lefticon:0x333333,
40                voltrack:0xF2F2F2,
41                volslider:0x666666,
42                rightbg:0xB4B4B4,
43                rightbghover:0x999999,
44                righticon:0x333333,
45                righticonhover:0xFFFFFF,
46                skip:0x666666,
47                text:0x333333,
48                track:0xFFFFFF,
49                border:0xCCCCCC,
50                loader:0x009900,
51                tracker:0xDDDDDD       
52        };
53       
54        // Options structure
55        private static var _options:Object = {
56                playerID:"",
57                encode:false,
58                autostart:false,
59                loop:false,
60                animation:true,
61                remaining:false,
62                noinfo:false,
63                killdownload:false,
64                checkpolicy:false,
65                demomode:false,
66                bufferTime:5,
67                volume:60,
68                rtl:false
69        };
70       
71        // A list for javascript listeners
72        private static var listeners:Array = [];
73
74        /**
75        * Starts app
76        * @param sourceFile a list of mp3 files to play
77        * @param options a structure of options
78        */
79        public static function start(sourceFile:String, options:Object):Void
80        {
81                if(options != undefined) _setOptions(options);
82               
83                if(!_options.demomode && _options.encode) sourceFile = _sixBitDecode(sourceFile);
84               
85                if(!_options.demomode) {
86                        var playerParams:Object = new Object();
87       
88                        playerParams.initialVolume = _options.volume;
89                        playerParams.bufferTime = _options.bufferTime;
90                        playerParams.enableCycling = _options.loop;
91                        playerParams.playerID = _options.playerID;
92                        playerParams.checkPolicy = _options.checkpolicy;
93
94                        // Create audio player instance and load playlist
95                        _player = new Player(playerParams);
96                       
97                        var trackTitles:String = (_options.titles != undefined) ? _options.titles : "";
98                        var trackArtists:String = (_options.artists != undefined) ? _options.artists : "";
99                        _player.loadPlaylist(sourceFile, trackTitles, trackArtists);
100               
101                        if(!_options.demomode) _player.addListener(Application);
102                }
103               
104                // Initial player state
105                _state = CLOSED;
106                if(_options.demomode || !_options.animation || _options.autostart) _state = OPEN;
107               
108                _setStage();
109               
110                _setColors(true);
111               
112                if (ExternalInterface.available) {
113                        ExternalInterface.addCallback("load", Application, Application.ei_loadFile);
114                        ExternalInterface.addCallback("close", Application, Application.ei_closePlayer);
115                        ExternalInterface.addCallback("open", Application, Application.ei_openPlayer);
116                        ExternalInterface.addCallback("setVolume", Application, Application.ei_setVolume);
117                       
118                        //add event listeners for js
119                        ExternalInterface.addCallback("addListener", Application, Application.addJSListener);
120                        ExternalInterface.addCallback("removeListener", Application, Application.removeJSListener);
121                       
122                       
123                        // Ask any other existing players for the current volume
124                        var newVolume:Number = Number(ExternalInterface.call("AudioPlayer.getVolume", _options.playerID));
125                        if (newVolume > -1) {
126                                _player.setVolume(newVolume, true);
127                        }
128                }
129
130
131                // Start player automatically if requested
132                if(!_options.demomode && _options.autostart) onPlay();
133               
134                setInterval(_update, 100);
135               
136                // Notification that the app has loaded//started
137                if (ExternalInterface.available) {
138                        ExternalInterface.call("onWPPlayerReady", _options.playerID);
139                }
140        }
141       
142        /**
143        * Writes options object to internal options struct
144        * @param        options
145        */
146        private static function _setOptions(options:Object):Void
147        {
148                for(var key:String in options) _options[key] = options[key];
149        }
150       
151        /**
152        * Initial stage setup
153        * Adds elements to stage and links up various listeners
154        */
155        private static function _setStage():Void
156        {
157                // Align UI to left and make sure it isn't scaled
158                Stage.align = "L";
159                Stage.scaleMode = "noScale";
160
161                // Add elements to stage
162               
163                // Depth counter
164                var nextDepth:Number = _root.getNextHighestDepth();
165               
166                // Masked elements
167                masked_mc = _root.createEmptyMovieClip("masked_mc", nextDepth++);
168                background_mc = masked_mc.attachMovie("Background", "background_mc", 0);
169                progress_mc = masked_mc.attachMovie("Progress", "progress_mc", 1);
170                progress_mc.addListener(Application);
171                loading_mc = masked_mc.attachMovie("Loading", "loading_mc", 2);
172               
173                // Next and previous buttons (if needed)
174                if(_options.demomode || _player.getTrackCount() > 1)
175                {
176                        next_mc = masked_mc.attachMovie("Toggle", "next_mc", 3);
177                        previous_mc = masked_mc.attachMovie("Toggle", "previous_mc", 4);
178                        // Make it point the other way
179                        previous_mc._rotation = -180;
180                       
181                        if(!_options.demomode) {
182                                // Add event handlers
183                                next_mc.onRelease = function() {
184                                        Application._player.next();
185                                        // Reset time display
186                                        Application.display_mc.setTime(0);
187                                };
188                                previous_mc.onRelease = function() {
189                                        Application._player.previous();
190                                        // Reset time display
191                                        Application.display_mc.setTime(0);
192                                };
193                        }
194                }
195               
196                // Mask
197                mask_mc = _root.attachMovie("Mask", "mask_mc", nextDepth++);
198                masked_mc.setMask(mask_mc);
199                mask_mc._width = 8;
200               
201                // Text display
202                display_mc = _root.attachMovie("Display", "display_mc", nextDepth++);
203                if(_state == CLOSED) display_mc._visible = false;
204               
205                // Volume control
206                volume_mc = _root.attachMovie("Volume", "volume_mc", nextDepth++);
207                volume_mc.addListener(Application);
208
209                // Play/pause control
210                control_mc = _root.attachMovie("Control", "control_mc", nextDepth++, { state:_options.autostart ? "pause" : "play" });
211                control_mc.addListener(Application);
212               
213                // Align and resize elements to the stage
214                _alignAndResize();
215
216                if(_options.demomode) {
217                        control_mc.toggle();
218                        volume_mc.toggleControl(true);
219                        volume_mc.update(_options.volume);
220                        progress_mc.updateProgress(0.3);
221                        loading_mc.update(0.6);
222                        display_mc.setText("1 Pixel Out: Demo Mode", 0, true);
223                        display_mc.setTime(356560, _options.remaining);
224                        previous_mc._alpha = 50;
225                }
226               
227                // Set stage listener in case the stage is resized
228                Stage.addListener(Application);
229        }
230       
231        /**
232        * Positions and resizes elements on the stage
233        */
234        private static function _alignAndResize():Void
235        {
236                // ------------------------------------------------------------
237                // Align elements
238                background_mc._x = volume_mc.realWidth - 7;
239               
240                var trackCount = _player.getTrackCount();
241               
242                progress_mc._x = volume_mc.realWidth + 4;
243                if(_options.demomode || trackCount > 1) progress_mc._x += 8;
244                progress_mc._y = 2;
245               
246                loading_mc._x = volume_mc.realWidth + 4;
247                if(_options.demomode || trackCount > 1) loading_mc._x += 8;
248                loading_mc._y = 20;
249               
250                if(_options.demomode || trackCount > 1)
251                {
252                        next_mc._x = Stage.width - 43;
253                        next_mc._y = 12;
254                        previous_mc._x = volume_mc.realWidth + 6;
255                        previous_mc._y = 12;
256                }
257               
258                mask_mc._x = volume_mc.realWidth - 7;
259               
260                display_mc._x = volume_mc.realWidth + 6;
261                if(_options.demomode || trackCount > 1) display_mc._x += 8;
262                display_mc._y = 2;
263               
264                // Control element alignment depends on whether player is open or closed
265                if(_state == CLOSED) control_mc._x = volume_mc.realWidth - 6;
266                else control_mc._x = Stage.width - control_mc.realWidth;
267               
268                // RTL
269                if(_options.rtl)
270                {
271                        control_mc.flip();
272                        volume_mc.flip();
273                       
274                        volume_mc._x = Stage.width - volume_mc.realWidth;
275                       
276                        background_mc._x = control_mc.realWidth - 7;
277                       
278                        progress_mc._x = control_mc.realWidth + 4;
279                        if(_options.demomode || trackCount > 1) progress_mc._x += 4;
280                        progress_mc._y = 2;
281                       
282                        loading_mc._x = control_mc.realWidth + 4;
283                        if(_options.demomode || trackCount > 1) loading_mc._x += 4;
284                        loading_mc._y = 20;
285                       
286                        if(_options.demomode || trackCount > 1)
287                        {
288                                next_mc._x = Stage.width - 52;
289                                next_mc._y = 12;
290                                previous_mc._x = control_mc.realWidth + 2;
291                                previous_mc._y = 12;
292                        }
293                       
294                        mask_mc._rotation = 180;
295                        mask_mc._y = mask_mc._height;
296                        mask_mc._x = volume_mc._x + 7;
297                       
298                        display_mc._x = control_mc.realWidth + 6;
299                        if(_options.demomode || trackCount > 1) display_mc._x += 4;
300                        display_mc._y = 2;
301                       
302                        // Control element alignment depends on whether player is open or closed
303                        if(_state == CLOSED) control_mc._x = volume_mc._x - control_mc.realWidth + 6;
304                        else control_mc._x = 0;
305                }
306               
307                // ------------------------------------------------------------
308                // Resize elements
309               
310                // Available space between volume and control elements
311                var availSpace:Number = Stage.width - (control_mc.realWidth + volume_mc.realWidth);
312
313                background_mc._width = availSpace + 14;
314                // Only resize mask if player is open
315                if(_state == OPEN) mask_mc._width = availSpace + 14;
316               
317                if(_options.demomode || trackCount > 1) availSpace -= 12;
318
319                // Call resize methods on composite elements
320                progress_mc.resize(availSpace - 8);
321                loading_mc.resize(availSpace - 8);
322                display_mc.resize(availSpace - 12);
323        }
324       
325        /**
326        * Applies colour scheme to player
327        * @param        force if true, don't check _root.setcolors
328        */
329        private static function _setColors(force:Boolean):Void
330        {
331                var i:Number;
332                var colorValue:String;
333               
334                if (!force && !_root.setcolors) {
335                        return;
336                }
337               
338                // Update colour scheme from root variables (set via javascript)
339                for (i = 0;i < _colorKeys.length;i++)
340                {
341                        if (_root[_colorKeys[i]] != undefined) {
342                                // Handle missing '0x' prefixes
343                                colorValue = _root[_colorKeys[i]];
344                                if (colorValue.indexOf("0x") == -1) {
345                                        colorValue = "0x" + colorValue;
346                                }
347                                _colorScheme[_colorKeys[i]] = colorValue;
348                        }
349                }
350               
351                _root.setcolors = 0;
352               
353                // Map colours to player elements
354                var colorTransforms:Array = [
355                        { target:background_mc, color:_colorScheme.bg },
356                        { target:volume_mc.background_mc, color:_colorScheme.leftbg },
357                        { target:volume_mc.icon_mc, color:_colorScheme.lefticon },
358                        { target:volume_mc.control_mc.track_mc, color:_colorScheme.voltrack },
359                        { target:volume_mc.control_mc.bar_mc, color:_colorScheme.volslider },
360                        { target:control_mc.background_mc.normal_mc, color:_colorScheme.rightbg },
361                        { target:control_mc.background_mc.hover_mc, color:_colorScheme.rightbghover },
362                        { target:control_mc.play_mc.normal_mc, color:_colorScheme.righticon },
363                        { target:control_mc.play_mc.hover_mc, color:_colorScheme.righticonhover },
364                        { target:control_mc.pause_mc.normal_mc, color:_colorScheme.righticon },
365                        { target:control_mc.pause_mc.hover_mc, color:_colorScheme.righticonhover },
366                        { target:loading_mc.bar_mc, color:_colorScheme.loader },
367                        { target:loading_mc.track_mc, color:_colorScheme.track },
368                        { target:progress_mc.track_mc, color:_colorScheme.track },
369                        { target:progress_mc.bar_mc, color:_colorScheme.tracker },
370                        { target:progress_mc.border_mc, color:_colorScheme.border },
371                        { target:next_mc, color:_colorScheme.skip },
372                        { target:previous_mc, color:_colorScheme.skip },
373                        { target:display_mc.message_txt, color:_colorScheme.text },
374                        { target:display_mc.time_txt, color:_colorScheme.text }
375                ];
376               
377                // Apply colours
378                var tempColor:Color;
379                for(i = 0;i<colorTransforms.length;i++)
380                {
381                        if(typeof(colorTransforms[i].target) == "movieclip")
382                        {
383                                tempColor = new Color(colorTransforms[i].target);
384                                tempColor.setRGB(colorTransforms[i].color);
385                        } else colorTransforms[i].target.textColor = colorTransforms[i].color;
386                }
387        }
388       
389        // ------------------------------------------------------------
390        // Event handlers
391       
392        /**
393        * onResize event handler
394        */
395        public static function onResize():Void
396        {
397                _alignAndResize();
398        }
399       
400        /**
401        * onPlay event handler
402        */
403        public static function onPlay(trackIndex:Number):Void
404        {
405                _player.play(trackIndex, false);
406
407                // Tell any other players to stop playing (don't want no cacophony do we?)
408                if (ExternalInterface.available) {
409                        ExternalInterface.call("AudioPlayer.activate", _options.playerID, _player.getState());
410                }
411               
412                // forward any relevent calls to js listeners
413                var tempObj:Object = _player.getState();
414                forward("PLAY", tempObj.trackInfo);
415               
416                // Show volume control
417                volume_mc.toggleControl(true);
418               
419                // If player is closed and animation is enabled, open the player
420                if(_state < OPENING && _options.animation) openPlayer();
421        }
422       
423        /**
424        * onStop event handler
425        */
426        public static function onStop():Void
427        {
428                // If player is open and animation is enabled, close the player
429                if(_options.animation && _state > CLOSING) closePlayer();
430               
431                // Hide volume control
432                volume_mc.toggleControl(false);
433               
434                // Toggle play button state (only if it's in pause state)
435                if (control_mc.state == "pause") control_mc.toggle();
436               
437                // forward any relevent calls to js listeners
438                forward("STOP");
439        }
440       
441        /**
442        * onPause event handler
443        */
444        public static function onPause():Void
445        {
446                // Tell any other players to stop playing (don't want no cacophony do we?)
447                if (ExternalInterface.available) {
448                        ExternalInterface.call("AudioPlayer.onStop", _options.playerID);
449                }
450               
451                _player.pause();
452               
453                // Hide volume control
454                volume_mc.toggleControl(false);
455               
456                // If player is open and animation is enabled, close the player
457                if(_state > CLOSING && _options.animation) closePlayer();
458               
459                // forward any relevent calls to js listeners
460                forward("PAUSE");
461        }
462
463        /**
464        * onMoveHead event handler
465        * @param        newPositon number form 0 to 1
466        */
467        public static function onMoveHead(newPosition:Number):Void
468        {
469                _player.moveHead(newPosition);
470        }
471       
472        /**
473         * onSetVolume event handler
474         */
475        public static function onSetVolume(volume:Number, final:Boolean):Void
476        {
477                if(final == undefined) final = true;
478                // Set the volume and force a broadcast of the changed volume
479                _player.setVolume(volume, final);
480               
481                // Tell any other players that the volume has changed
482                if (ExternalInterface.available && final) {
483                        ExternalInterface.call("AudioPlayer.syncVolumes", _options.playerID, volume);
484                }
485        }
486       
487        /**
488        * onMetaData event handler
489        * called from Player.as by broadcastmessage
490        */
491        public static function onMetaData():Void
492        {
493                var tempObj:Object = _player.getState();
494                forward("METADATA", tempObj.trackInfo);
495        }
496
497        // ------------------------------------------------------------
498        // Open / close animation
499       
500        /**
501        * Starts open animation
502        */
503        public static function openPlayer():Void
504        {
505                _state = OPENING;
506               
507                var targetPosition:Number = _options.rtl ? 0 : Stage.width - control_mc.realWidth;
508               
509                if(_clearID != null) clearInterval(_clearID);
510                _clearID = setInterval(_animate, 40, targetPosition);
511        }
512
513        /**
514        * Starts close animation
515        */
516        public static function closePlayer():Void
517        {
518                _state = CLOSING;
519               
520                // Hide text display (doesn't work under a mask)
521                display_mc._visible = false;
522
523                var targetPosition:Number = _options.rtl ? volume_mc._x - control_mc.realWidth + 6 : volume_mc.realWidth - 6;
524               
525                if(_clearID != null) clearInterval(_clearID);
526                _clearID = setInterval(_animate, 40, targetPosition);
527        }
528       
529        /**
530        * Moves control element to the given target position (with easing)
531        * @param        targetX target position of control element
532        */
533        private static function _animate(targetX:Number):Void
534        {
535                var dx:Number = targetX - control_mc._x;
536                var speed:Number = 0.5;
537
538                dx = dx * speed;
539
540                // Stop animation when we are at less than a pixel from the target
541                if(Math.abs(dx) < 1)
542                {
543                        // Position the control element to the exact target position
544                        control_mc._x = targetX;
545                        if(_options.rtl) mask_mc._width -= (dx*2);
546                        else mask_mc._width += (dx*2);
547                        clearInterval(_clearID);
548                        if(_state == OPENING)
549                        {
550                                // Show text display
551                                display_mc._visible = true;
552                                _state = OPEN;
553                        }
554                        else{
555                                _state = CLOSED;
556                        }
557                        return;
558                }
559               
560                control_mc._x += dx;
561                if(_options.rtl) mask_mc._width -= dx;
562                else mask_mc._width += dx;
563        }
564       
565        // ------------------------------------------------------------
566        // Periodical update method
567
568        /**
569        * General periodical update method. It performs the following:
570        * Updates various UI element states (volume, control, progress bar and loading bar)
571        */
572        private static function _update():Void
573        {
574                // Set colour scheme at runtime
575                _setColors(false);
576
577                if(_options.demomode) {
578                        return;
579                }
580               
581                // Get player state (head positions, stats etc)
582                var playerState:Object = _player.getState();
583               
584                // Update volume control state
585                volume_mc.update(playerState.volume);
586
587                // Enable / disable control button
588                control_mc.enabled = true;
589               
590                // Update progress bar if necessary
591                if(playerState.state != Player.PAUSED) progress_mc.updateProgress(playerState.played);
592               
593                // Tell progress bar how far it can go
594                progress_mc.setMaxValue(playerState.loaded);
595
596                // Update loading bar state
597                loading_mc.update(playerState.loaded);
598               
599                if(playerState.trackCount > 1)
600                {
601                        next_mc.enabled = playerState.hasNext;
602                        previous_mc.enabled = playerState.hasPrevious;
603                        if(playerState.hasNext) next_mc._alpha = 100;
604                        else next_mc._alpha = 50;
605                        if(playerState.hasPrevious) previous_mc._alpha = 100;
606                        else previous_mc._alpha = 50;
607                }
608               
609                var trackNumber:String = "";
610               
611                // Update text display
612                switch(playerState.state)
613                {
614                        case Player.NOTFOUND:
615                                if(playerState.trackCount > 1) trackNumber = (playerState.trackIndex + 1) + " - ";
616                                display_mc.setText(trackNumber + "File not found", 0);
617                                display_mc.setTime(0);
618                                break;
619                        default:
620                                var message = "";
621                                if(playerState.connecting) message = "Connecting...";
622                                else
623                                {
624                                        if(!_options.noinfo && playerState.trackCount > 1) message = (playerState.trackIndex + 1) + ": ";
625                                        if(playerState.buffering) message += "Buffering...";
626                                        else if(!_options.noinfo) {
627                                                if(playerState.trackInfo.artist.length > 0 || playerState.trackInfo.title.length > 0)
628                                                {
629                                                        if(playerState.trackInfo.artist.length > 0) message += playerState.trackInfo.artist + " - ";
630                                                        message += playerState.trackInfo.title;
631                                                }
632                                                else message = "Track #" + (playerState.trackIndex + 1);
633                                        }
634                                }
635                                display_mc.setText(message, 0, true);
636                                display_mc.setTime(_options.remaining ? playerState.duration - playerState.position : playerState.position, _options.remaining);
637                                break;
638                }
639        }
640       
641        /**
642        * Decodes a 6-bit encoded string
643        * Thanks to mattiasdh (mattias_d@excite.com) for this
644        * http://modxcms.com/forums/index.php/topic,9340.0.html
645        * @param        source the string to decode
646        * @return       the decoded string
647        */
648        private static function _sixBitDecode(sourceStr)
649        {
650                var ntexto = "";
651                var nntexto = "";
652                var codeKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
653                var charCode, charChar, charCodeBin, i;
654                for(i=0; i<sourceStr.length; i++)
655                {
656                        charCode = codeKey.indexOf(sourceStr.substr(i,1)); // char index
657                        charCodeBin = ("000000" + charCode.toString(2)).substr(-6,6); // char index in binary, 6 bits
658                        ntexto += charCodeBin;
659                }
660                for (i=0; i< ntexto.length; i+=8) {
661                        charCodeBin = ntexto.substr(i, 8); // char code in binary
662                        charCode = parseInt(charCodeBin, 2);
663                        charChar = String.fromCharCode(charCode);
664                        nntexto += charChar;
665                }
666                return (nntexto);
667        }
668       
669        private static function ei_closePlayer():Void
670        {
671                var playerState:Object = _player.getState();
672                // Another player has asked us to stop
673                if(playerState.state == Player.PLAYING || playerState.state == Player.NOTFOUND)
674                {
675                        // If the track is still loading, stop everything including the download
676                        if(playerState.loaded < 1 || _options.killdownload) {
677                                _player.stop(false);
678                        }
679                        // Otherwise, just pause the track
680                        else _player.pause();
681                        onStop();
682                }
683        }
684       
685        private static function ei_openPlayer(trackIndex:Number):Void
686        {
687                if (_state == CLOSED) control_mc.toggle();
688                onPlay(trackIndex);
689        }
690
691       
692        private static function ei_setVolume(newVolume:Number):Void
693        {
694                _player.setVolume(newVolume);
695        }
696       
697        private static function ei_loadFile(sourceFile:String, titles:String, artists:String):Void
698        {
699                _player.stop(false);
700                onStop();
701               
702                _player.loadPlaylist(sourceFile, titles, artists);
703        }
704       
705        // new ei calls to add remove listeners and forward messages
706        private static function addJSListener(typ:String, fcn:String):Boolean
707        {
708                var upperCaseType:String = typ.toUpperCase()
709                listeners.push({type:upperCaseType, callee:fcn});
710                return true;
711        }
712       
713        private static function removeJSListener(typ:String, fcn:String):Boolean
714        {
715                for(var i:Number=0; i<listeners.length; i++) {
716                        if( listeners[i]['type'] == typ && listeners[i]['callee'] == fcn) {
717                                listeners.splice(i,1);
718                                return;
719                        }
720                }
721               
722                return
723        };
724       
725        // Send event to listeners.
726        private static function forward(typ:String, dat:Object):Void
727        {
728                if (!dat) { dat = new Object(); }
729                dat.playerID = _options.playerID
730               
731                for (var itm:String in listeners) {
732                        if (listeners[itm]['type'] == typ) {
733                                ExternalInterface.call(listeners[itm]['callee'], dat);
734                        }
735                }
736        };
737}
Note: See TracBrowser for help on using the browser.