source: trunk/fl5/src/com/longtailvideo/jwplayer/player/JavascriptAPI.as @ 2393

Revision 2393, 17.4 KB checked in by pablo, 9 months ago (diff)
  • Addresses cross-site security issue with links containing base-64 encoded JavaScript (1585)
  • Addresses JavaScript API event flow
Line 
1package com.longtailvideo.jwplayer.player {
2        import com.longtailvideo.jwplayer.events.ComponentEvent;
3        import com.longtailvideo.jwplayer.events.InstreamEvent;
4        import com.longtailvideo.jwplayer.events.MediaEvent;
5        import com.longtailvideo.jwplayer.events.PlayerEvent;
6        import com.longtailvideo.jwplayer.events.PlayerStateEvent;
7        import com.longtailvideo.jwplayer.events.PlaylistEvent;
8        import com.longtailvideo.jwplayer.events.ViewEvent;
9        import com.longtailvideo.jwplayer.model.IInstreamOptions;
10        import com.longtailvideo.jwplayer.model.InstreamOptions;
11        import com.longtailvideo.jwplayer.model.Playlist;
12        import com.longtailvideo.jwplayer.model.PlaylistItem;
13        import com.longtailvideo.jwplayer.plugins.AbstractPlugin;
14        import com.longtailvideo.jwplayer.plugins.IPlugin;
15        import com.longtailvideo.jwplayer.utils.JavascriptSerialization;
16        import com.longtailvideo.jwplayer.utils.Logger;
17        import com.longtailvideo.jwplayer.utils.RootReference;
18        import com.longtailvideo.jwplayer.utils.Strings;
19        import com.longtailvideo.jwplayer.view.interfaces.IControlbarComponent;
20        import com.longtailvideo.jwplayer.view.interfaces.IDisplayComponent;
21        import com.longtailvideo.jwplayer.view.interfaces.IDockComponent;
22        import com.longtailvideo.jwplayer.view.interfaces.IPlayerComponent;
23        import com.longtailvideo.jwplayer.view.interfaces.IPlaylistComponent;
24       
25        import flash.events.Event;
26        import flash.events.TimerEvent;
27        import flash.external.ExternalInterface;
28        import flash.utils.Timer;
29        import flash.utils.setTimeout;
30       
31        public class JavascriptAPI {
32                protected var _player:IPlayer;
33                protected var _playerBuffer:Number = 0;
34                protected var _playerPosition:Number = 0;
35               
36                protected var _listeners:Object;
37                protected var _queuedEvents:Array = [];
38               
39                protected var _lockPlugin:IPlugin;
40                protected var _instream:IInstreamPlayer;
41                protected var _isItem:PlaylistItem;
42                protected var _isConfig:IInstreamOptions;
43               
44                protected var _destroyed:Boolean = false;
45               
46                public function JavascriptAPI(player:IPlayer) {
47                        _listeners = {};
48                        _lockPlugin = new AbstractPlugin();
49                       
50                        _player = player;
51                        _player.addEventListener(PlayerEvent.JWPLAYER_READY, playerReady);
52
53                        setupPlayerListeners();
54                        setupJSListeners();
55                        _player.addGlobalListener(queueEvents);
56                       
57                }
58               
59                /** Delay the response to PlayerReady to allow the external interface to initialize in some browsers **/
60                protected function playerReady(evt:PlayerEvent):void {
61                        var timer:Timer = new Timer(50, 1);
62                       
63                        timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(timerEvent:TimerEvent):void {
64                                _player.removeGlobalListener(queueEvents);
65                                var callbacks:String = _player.config.playerready ? _player.config.playerready + "," + "playerReady" : "playerReady"; 
66                                if (ExternalInterface.available) {
67                                        for each (var callback:String in callbacks.replace(/\s/,"").split(",")) {
68                                                try {
69                                                        if (callback.toLowerCase().search(/[\{\}\(\)]/) < 0) {
70                                                                callJS(callback,{
71                                                                        id:evt.id,
72                                                                        client:evt.client,
73                                                                        version:evt.version
74                                                                });
75                                                        }
76                                                } catch (e:Error) {}
77                                        }
78                                       
79                                        clearQueuedEvents();
80                                }
81                               
82
83                        });
84                        timer.start();
85                }
86
87                protected function queueEvents(evt:Event):void {
88                        _queuedEvents.push(evt);
89                }
90               
91                protected function clearQueuedEvents():void {
92                        for each (var queuedEvent:Event in _queuedEvents) {
93                                listenerCallback(queuedEvent);
94                        }
95                        _queuedEvents = null;
96                }
97               
98                protected function setupPlayerListeners():void {
99                        _player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, resetPosition);
100                        _player.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, updatePosition);
101                        _player.addEventListener(MediaEvent.JWPLAYER_MEDIA_BUFFER, updateBuffer);
102                        _player.addEventListener(MediaEvent.JWPLAYER_MEDIA_VOLUME, updateVolumeCookie);
103                        _player.addEventListener(MediaEvent.JWPLAYER_MEDIA_MUTE, updateMuteCookie);
104                }
105               
106                protected function resetPosition(evt:PlaylistEvent):void {
107                        _playerPosition = 0;
108                        _playerBuffer = 0;
109                }
110               
111                protected function updatePosition(evt:MediaEvent):void {
112                        _playerPosition = evt.position;
113                }
114
115                protected function updateBuffer(evt:MediaEvent):void {
116                        _playerBuffer = evt.bufferPercent;
117                }
118               
119                protected function updateVolumeCookie(evt:MediaEvent):void {
120                        callJS("function(vol) { try { jwplayer.utils.saveCookie('volume', vol) } catch(e) {} }", evt.volume.toString());
121                }
122
123                protected function updateMuteCookie(evt:MediaEvent):void {
124                        callJS("function(state) { try { jwplayer.utils.saveCookie('mute', state) } catch(e) {} }", evt.mute.toString());
125                }
126
127                protected function setupJSListeners():void {
128                        try {
129                                // Event handlers
130                                ExternalInterface.addCallback("jwAddEventListener", js_addEventListener);
131                                ExternalInterface.addCallback("jwRemoveEventListener", js_removeEventListener);
132                               
133                                // Getters
134                                ExternalInterface.addCallback("jwGetBuffer", js_getBuffer);
135                                ExternalInterface.addCallback("jwGetDuration", js_getDuration);
136                                ExternalInterface.addCallback("jwGetFullscreen", js_getFullscreen);
137                                ExternalInterface.addCallback("jwGetHeight", js_getHeight);
138                                ExternalInterface.addCallback("jwGetMute", js_getMute);
139                                ExternalInterface.addCallback("jwGetPlaylist", js_getPlaylist);
140                                ExternalInterface.addCallback("jwGetPlaylistIndex", js_getPlaylistIndex);
141                                ExternalInterface.addCallback("jwGetPosition", js_getPosition);
142                                ExternalInterface.addCallback("jwGetState", js_getState);
143                                ExternalInterface.addCallback("jwGetWidth", js_getWidth);
144                                ExternalInterface.addCallback("jwGetVersion", js_getVersion);
145                                ExternalInterface.addCallback("jwGetVolume", js_getVolume);
146
147                                // Player API Calls
148                                ExternalInterface.addCallback("jwPlay", js_play);
149                                ExternalInterface.addCallback("jwPause", js_pause);
150                                ExternalInterface.addCallback("jwStop", js_stop);
151                                ExternalInterface.addCallback("jwSeek", js_seek);
152                                ExternalInterface.addCallback("jwLoad", js_load);
153                                ExternalInterface.addCallback("jwPlaylistItem", js_playlistItem);
154                                ExternalInterface.addCallback("jwPlaylistNext", js_playlistNext);
155                                ExternalInterface.addCallback("jwPlaylistPrev", js_playlistPrev);
156                                ExternalInterface.addCallback("jwSetMute", js_mute);
157                                ExternalInterface.addCallback("jwSetVolume", js_volume);
158                                ExternalInterface.addCallback("jwSetFullscreen", js_fullscreen);
159                               
160                                // Player Controls APIs
161                                ExternalInterface.addCallback("jwControlbarShow", js_showControlbar);
162                                ExternalInterface.addCallback("jwControlbarHide", js_hideControlbar);
163
164                                ExternalInterface.addCallback("jwDisplayShow", js_showDisplay);
165                                ExternalInterface.addCallback("jwDisplayHide", js_hideDisplay);
166                               
167                                ExternalInterface.addCallback("jwDockHide", js_hideDock);
168                                ExternalInterface.addCallback("jwDockSetButton", js_dockSetButton);
169                                ExternalInterface.addCallback("jwDockShow", js_showDock);
170                               
171                                // Instream API
172                                ExternalInterface.addCallback("jwLoadInstream", js_loadInstream);
173
174                                // The player shouldn't send any events if it's been destroyed
175                                ExternalInterface.addCallback("jwDestroyAPI", js_destroyAPI);
176
177                                // UNIMPLEMENTED
178                                //ExternalInterface.addCallback("jwGetBandwidth", js_getBandwidth);
179                                //ExternalInterface.addCallback("jwGetLevel", js_getLevel);
180                                //ExternalInterface.addCallback("jwGetLockState", js_getLockState);
181                               
182                        } catch(e:Error) {
183                                Logger.log("Could not initialize JavaScript API: "  + e.message);
184                        }
185                       
186                }
187
188               
189                /***********************************************
190                 **              EVENT LISTENERS              **
191                 ***********************************************/
192               
193                protected function js_addEventListener(eventType:String, callback:String):void {
194                        if (!_listeners[eventType]) {
195                                _listeners[eventType] = [];
196                                _player.addEventListener(eventType, listenerCallback);
197                        }
198                        (_listeners[eventType] as Array).push(callback);
199                }
200               
201                protected function js_removeEventListener(eventType:String, callback:String):void {
202                        var callbacks:Array = _listeners[eventType];
203                        if (callbacks) {
204                                var callIndex:Number = callbacks.indexOf(callback);
205                                if (callIndex > -1) {
206                                        callbacks.splice(callIndex, 1);
207                                }
208                        }
209                }
210               
211               
212               
213                protected function listenerCallback(evt:Event):void {
214                        var args:Object = {};
215                       
216                        if (evt is MediaEvent)
217                                args = listenerCallbackMedia(evt as MediaEvent);
218                        else if (evt is PlayerStateEvent)
219                                args = listenerCallbackState(evt as PlayerStateEvent);
220                        else if (evt is PlaylistEvent)
221                                args = listenerCallbackPlaylist(evt as PlaylistEvent);
222                        else if (evt is ComponentEvent)
223                                args = listenerCallbackComponent(evt as ComponentEvent);
224                        else if (evt is ViewEvent && (evt as ViewEvent).data != null)
225                                args = { data: JavascriptSerialization.stripDots((evt as ViewEvent).data) };
226                        else if (evt is PlayerEvent) {
227                                args = { message: (evt as PlayerEvent).message };
228                        }
229                       
230                        args.type = evt.type;
231                        var callbacks:Array = _listeners[evt.type] as Array;
232                       
233                       
234                        if (callbacks) {
235                                // These events should be sent immediately to JavaScript
236                                var synchronousEvents:Array = [
237                                        MediaEvent.JWPLAYER_MEDIA_COMPLETE,
238                                        MediaEvent.JWPLAYER_MEDIA_BEFOREPLAY,
239                                        MediaEvent.JWPLAYER_MEDIA_BEFORECOMPLETE,
240                                        PlaylistEvent.JWPLAYER_PLAYLIST_LOADED,
241                                        PlaylistEvent.JWPLAYER_PLAYLIST_ITEM,
242                                        PlayerStateEvent.JWPLAYER_PLAYER_STATE
243                                ];
244                               
245                                for each (var call:String in callbacks) {
246                                        // Not a great workaround, but the JavaScript API competes with the Controller when dealing with certain events
247                                        if (synchronousEvents.indexOf(evt.type) >= 0) {
248                                                callJS(call, args);
249                                        } else {
250                                                //Insert 1ms delay to allow all Flash listeners to complete before notifying JavaScript
251                                                setTimeout(function():void {
252                                                        callJS(call, args);
253                                                }, 0);
254                                        }
255                                }
256                        }
257                       
258                }
259               
260                protected function merge(obj1:Object, obj2:Object):Object {
261                        var newObj:Object = {};
262                       
263                        for (var key:String in obj1) {
264                                newObj[key] = obj1[key];
265                        }
266                       
267                        for (key in obj2) {
268                                newObj[key] = obj2[key];
269                        }
270                       
271                        return newObj;
272                }
273               
274                protected function listenerCallbackMedia(evt:MediaEvent):Object {
275                        var returnObj:Object = {};
276
277                        if (evt.bufferPercent >= 0)             returnObj.bufferPercent = evt.bufferPercent;
278                        if (evt.duration >= 0)                          returnObj.duration = evt.duration;
279                        if (evt.message)                                        returnObj.message = evt.message;
280                        if (evt.metadata != null)                       returnObj.metadata = JavascriptSerialization.stripDots(evt.metadata);
281                        if (evt.offset > 0)                                     returnObj.offset = evt.offset;
282                        if (evt.position >= 0)                          returnObj.position = evt.position;
283
284                        if (evt.type == MediaEvent.JWPLAYER_MEDIA_MUTE)
285                                returnObj.mute = evt.mute;
286                       
287                        if (evt.type == MediaEvent.JWPLAYER_MEDIA_VOLUME)
288                                returnObj.volume = evt.volume;
289
290                        return returnObj;
291                }
292               
293               
294                protected function listenerCallbackState(evt:PlayerStateEvent):Object {
295                        if (evt.type == PlayerStateEvent.JWPLAYER_PLAYER_STATE) {
296                                return { newstate: evt.newstate, oldstate: evt.oldstate };
297                        } else return {};
298                }
299
300                protected function listenerCallbackPlaylist(evt:PlaylistEvent):Object {
301                        if (evt.type == PlaylistEvent.JWPLAYER_PLAYLIST_LOADED) {
302                                var list:Array = JavascriptSerialization.playlistToArray(_player.playlist);
303                                list = JavascriptSerialization.stripDots(list) as Array;
304                                return { playlist: list };
305                        } else if (evt.type == PlaylistEvent.JWPLAYER_PLAYLIST_ITEM) {
306                                return { index: _player.playlist.currentIndex };
307                        } else return {};
308                }
309
310                protected function listenerCallbackComponent(evt:ComponentEvent):Object {
311                        var obj:Object = {};
312                        if (evt.component is IControlbarComponent)
313                                obj.component = "controlbar";
314                        else if (evt.component is IPlaylistComponent)
315                                obj.component = "playlist";
316                        else if (evt.component is IDisplayComponent)
317                                obj.component = "display";
318                        else if (evt.component is IDockComponent)
319                                obj.component = "dock";
320                       
321                        obj.boundingRect = {
322                                x: evt.boundingRect.x,
323                                y: evt.boundingRect.y,
324                                width: evt.boundingRect.width,
325                                height: evt.boundingRect.height
326                        };
327
328                        return obj;
329                }
330
331                /***********************************************
332                 **                 GETTERS                   **
333                 ***********************************************/
334               
335                protected function js_getBandwidth():Number {
336                        return _player.config.bandwidth;
337                }
338
339                protected function js_getBuffer():Number {
340                        return _playerBuffer;
341                }
342               
343                protected function js_getDuration():Number {
344                        return _player.playlist.currentItem ? _player.playlist.currentItem.duration : 0;
345                }
346               
347                protected function js_getFullscreen():Boolean {
348                        return _player.config.fullscreen;
349                }
350
351                protected function js_getHeight():Number {
352                        return RootReference.stage.stageHeight;
353                }
354               
355                protected function js_getLevel():Number {
356                        return _player.playlist.currentItem ? _player.playlist.currentItem.currentLevel : 0;
357                }
358               
359                protected function js_getLockState():Boolean {
360                        return _player.locked;
361                }
362               
363                protected function js_getMute():Boolean {
364                        return _player.config.mute;
365                }
366               
367                protected function js_getPlaylist():Array {
368                        var playlistArray:Array = JavascriptSerialization.playlistToArray(_player.playlist);
369                        for (var i:Number=0; i < playlistArray.length; i++) {
370                                playlistArray[i] = JavascriptSerialization.stripDots(playlistArray[i]);
371                        }
372                        return playlistArray;
373                }
374
375               
376                protected function js_getPlaylistIndex():Number {
377                        return _player.playlist.currentIndex;
378                }
379               
380               
381                protected function js_getPosition():Number {
382                        return _playerPosition;
383                }
384               
385                protected function js_getState():String {
386                        return _player.state;
387                }
388
389                protected function js_getWidth():Number {
390                        return RootReference.stage.stageWidth;
391                }
392
393                protected function js_getVersion():String {
394                        return _player.version;
395                }
396
397                protected function js_getVolume():Number {
398                        return _player.config.volume;
399                }
400
401                /***********************************************
402                 **                 PLAYBACK                  **
403                 ***********************************************/
404
405                protected function js_dockSetButton(name:String,click:String=null,out:String=null,over:String=null):void {
406                    _player.controls.dock.setButton(name,click,out,over);
407                };
408       
409                protected function js_play(playstate:*=null):void {
410                        if (playstate == null){
411                                playToggle();
412                        } else {
413                                if (String(playstate).toLowerCase() == "true"){
414                                        _player.play();
415                                } else {
416                                        _player.pause();
417                                }
418                        }
419                }
420               
421               
422                protected function js_pause(playstate:*=null):void {
423                        if (playstate == null){
424                                playToggle();
425                        } else {
426                                if (String(playstate).toLowerCase() == "true"){
427                                        _player.pause();
428                                } else {
429                                        _player.play();
430                                }
431                        }
432                }
433               
434                protected function playToggle():void {
435                        if (_player.state == PlayerState.IDLE || _player.state == PlayerState.PAUSED) {
436                                _player.play();
437                        } else {
438                                _player.pause();
439                        }
440                }
441               
442                protected function js_stop():void {
443                        _player.stop();
444                }
445               
446                protected function js_seek(position:Number=0):void {
447                        _player.seek(position);
448                }
449               
450                protected function js_load(toLoad:*):void {
451                        _player.load(toLoad);
452                }
453               
454                protected function js_playlistItem(item:Number):void {
455                        _player.playlistItem(item);
456                }
457
458                protected function js_playlistNext():void {
459                        _player.playlistNext();
460                }
461
462                protected function js_playlistPrev():void {
463                        _player.playlistPrev();
464                }
465
466                protected function js_mute(mutestate:*=null):void {
467                        if (mutestate == null){
468                                _player.mute(!_player.config.mute);
469                        } else {
470                                if (String(mutestate).toLowerCase() == "true") {
471                                        _player.mute(true);
472                                } else {
473                                        _player.mute(false);
474                                }
475                        }
476                }
477
478                protected function js_volume(volume:Number):void {
479                        _player.volume(volume);
480                }
481
482                protected function js_fullscreen(fullscreenstate:*=null):void {
483                        if (fullscreenstate == null){
484                                fullscreenstate = !_player.config.fullscreen;
485                        }
486                       
487                        if (String(fullscreenstate).toLowerCase() == "true") {
488                                // This won't ever work - Flash fullscreen mode can't be set from JavaScript
489                                Logger.log("Can't activate Flash fullscreen mode from JavaScript API");
490                                return;
491                        } else {
492                                _player.fullscreen(false);
493                        }
494                }
495               
496                protected function js_loadInstream(item:Object, config:Object):void {
497                        _isItem = new PlaylistItem(item);
498                        _isConfig = new InstreamOptions(config);
499
500                        if (!_isConfig.autoload) {
501                                _player.lock(_lockPlugin, function():void {
502                                        _instream = _player.loadInstream(_lockPlugin, _isItem, _isConfig);
503                                        beginInstream();
504                                });
505                        } else {
506                                _instream = _player.loadInstream(_lockPlugin, _isItem, _isConfig);
507                                beginInstream();
508                        }
509                }
510               
511                protected function beginInstream():void {
512                        if (_instream) {
513                                _instream.addEventListener(InstreamEvent.JWPLAYER_INSTREAM_DESTROYED, function(evt:InstreamEvent):void {
514                                        _player.unlock(_lockPlugin);
515                                });
516                                new JavascriptInstreamAPI(_instream, _isConfig, _player, _lockPlugin); 
517                        }       
518                }
519               
520                protected function setComponentVisibility(component:IPlayerComponent, state:Boolean):void {
521                        state ? component.show() : component.hide();
522                }
523
524                protected function js_showControlbar():void {
525                        setComponentVisibility(_player.controls.controlbar, true);
526                }
527               
528                protected function js_hideControlbar():void {
529                        setComponentVisibility(_player.controls.controlbar, false);
530                }
531
532                protected function js_showDock():void {
533                        setComponentVisibility(_player.controls.dock, true);
534                }
535               
536                protected function js_hideDock():void {
537                        setComponentVisibility(_player.controls.dock, false);
538                }
539
540                protected function js_showDisplay():void {
541                        setComponentVisibility(_player.controls.display, true);
542                }
543               
544                protected function js_hideDisplay():void {
545                        setComponentVisibility(_player.controls.display, false);
546                }
547
548                protected function js_destroyAPI():void {
549                        _destroyed = true;
550                }
551
552                protected function callJS(functionName:String, args:*):void {
553                        if (!_destroyed && ExternalInterface.available) {
554                                ExternalInterface.call(functionName, args);
555                        }
556                }
557
558               
559        }
560
561}
Note: See TracBrowser for help on using the repository browser.