root/trunk/as3/com/jeroenwijering/player/Controller.as

Revision 385, 12.8 kB (checked in by jeroen, 2 months ago)

fixed subscribed livestreams, httpmodel bandwidth checks and livestream stopping, muting and channel assignment

  • Property svn:executable set to *
Line 
1/**
2* Process all input from the views and modifies the model.
3**/
4package com.jeroenwijering.player {
5
6
7import com.jeroenwijering.events.*;
8import com.jeroenwijering.parsers.*;
9import com.jeroenwijering.utils.*;
10
11import flash.display.MovieClip;
12import flash.events.*;
13import flash.geom.*;
14import flash.net.*;
15import flash.system.Capabilities;
16
17
18public class Controller extends EventDispatcher {
19
20
21        /** Configuration object **/
22        private var config:Object;
23        /** Reference to the skin (for stage access). **/
24        private var skin:MovieClip;
25        /** Reference to the SPLoader (for layouting plugins). **/
26        private var sploader:SPLoader;
27        /** Playlist of the player. **/
28        public var playlist:Array;
29        /** Reference to the player's model. **/
30        private var model:Model;
31        /** Reference to the player's view. **/
32        private var view:View;
33        /** Object that manages loading of XML playlists. **/
34        private var loader:URLLoader;
35        /** object that provides randomization. **/
36        private var randomizer:Randomizer;
37        /** File extensions of all supported mediatypes. **/
38        private var EXTENSIONS:Object = {
39                '3g2':'video',
40                '3gp':'video',
41                'aac':'video',
42                'f4b':'video',
43                'f4p':'video',
44                'f4v':'video',
45                'flv':'video',
46                'gif':'image',
47                'jpg':'image',
48                'm4a':'video',
49                'm4v':'video',
50                'mov':'video',
51                'mp3':'sound',
52                'mp4':'video',
53                'png':'image',
54                'rbs':'sound',
55                'sdp':'video',
56                'swf':'image',
57                'vp6':'video'
58        };
59        /** Elements of config that are part of the playlist. **/
60        private var ELEMENTS:Object = {
61                author:undefined,
62                date:undefined,
63                description:undefined,
64                duration:0,
65                file:undefined,
66                image:undefined,
67                link:undefined,
68                start:0,
69                streamer:undefined,
70                tags:undefined,
71                title:undefined,
72                type:undefined
73        };
74        /** A list with legacy CDN classes that are now redirected to buit-in ones. **/
75        private var REDIRECTS:Object = {
76                bitgravity:{flashvar:'startparam',model:'http',value:'starttime'},
77                edgecast:{flashvar:'startparam',model:'http',value:'ec_seek'},
78                fcsubscribe:{flashvar:'subscribe',model:'rtmp',value:true},
79                flvseek:{flashvar:'startparam',model:'http',value:'fs'},
80                highwinds:{flashvar:'loadbalance',model:'rtmp',value:true},
81                lighttpd:{flashvar:'startparam',model:'http',value:'start'},
82                vdox:{flashvar:'loadbalance',model:'rtmp',value:true}
83        };
84
85
86        /** Constructor, set up stage and playlist listeners. **/
87        public function Controller(cfg:Object,skn:MovieClip,ldr:SPLoader):void {
88                config = cfg;
89                skin = skn;
90                sploader = ldr;
91                loader = new URLLoader();
92                loader.addEventListener(Event.COMPLETE,loaderHandler);
93                loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,errorHandler);
94                loader.addEventListener(IOErrorEvent.IO_ERROR,errorHandler);
95        };
96
97
98        /** Register view and model with controller, start loading playlist. **/
99        public function closeMVC(mdl:Model,vie:View):void {
100                model= mdl;
101                model.addEventListener(ModelEvent.STATE,stateHandler);
102                view = vie;
103                view.addEventListener(ViewEvent.FULLSCREEN,fullscreenHandler);
104                view.addEventListener(ViewEvent.ITEM,itemHandler);
105                view.addEventListener(ViewEvent.LINK,linkHandler);
106                view.addEventListener(ViewEvent.LOAD,loadHandler);
107                view.addEventListener(ViewEvent.MUTE,muteHandler);
108                view.addEventListener(ViewEvent.NEXT,nextHandler);
109                view.addEventListener(ViewEvent.PLAY,playHandler);
110                view.addEventListener(ViewEvent.PREV,prevHandler);
111                view.addEventListener(ViewEvent.REDRAW,redrawHandler);
112                view.addEventListener(ViewEvent.SEEK,seekHandler);
113                view.addEventListener(ViewEvent.STOP,stopHandler);
114                view.addEventListener(ViewEvent.VOLUME,volumeHandler);
115        };
116
117
118        /** Catch errors dispatched by the playlister. **/
119        private function errorHandler(evt:ErrorEvent):void {
120                sendEvent(ControllerEvent.ERROR,{message:evt.text});
121        };
122
123
124        /** Switch fullscreen state. **/
125        private function fullscreenHandler(evt:ViewEvent):void {
126                if(skin.stage['displayState'] == 'fullScreen') {
127                        skin.stage['displayState'] = 'normal';
128                } else {
129                        try { fullscreenrect(); } catch (err:Error) {}
130                        skin.stage['displayState'] = 'fullScreen';
131                }
132        };
133
134
135        /** Set the fullscreen rectangle **/
136        private function fullscreenrect():void {
137                var pnt:Point = skin.parent.localToGlobal(new Point(skin.x,skin.y));
138                skin.stage["fullScreenSourceRect"] = new Rectangle(
139                        pnt.x,
140                        pnt.y,
141                        Capabilities.screenResolutionX,
142                        Capabilities.screenResolutionY
143                );
144        };
145
146
147        /** Return the type of specific playlistitem (the Model to play it with) **/
148        private function getModelType(itm:Object,sub:Boolean):String {
149                if(!itm['file']) {
150                        return null;
151                } else if(REDIRECTS[itm['type']]) {
152                        var typ:Object = REDIRECTS[itm['type']];
153                        config[typ['model']+'.'+typ['flashvar']] = typ['value'];
154                        return typ['model'];
155                } else if(itm['type']) {
156                        return itm['type'];
157                } else if (itm['streamer'] && sub) {
158                        if(itm['streamer'].substr(0,4) == 'rtmp') {
159                                return 'rtmp';
160                        } else if(itm['streamer'].indexOf('/') != -1) {
161                                return 'http';
162                        } else {
163                                return itm['streamer'];
164                        }
165                // this is a small hack to enable youtube links to work by default.
166                } else if(itm['file'].indexOf('youtube.com/w') > -1 || itm['file'].indexOf('youtube.com/v') > -1) {
167                        return 'youtube';
168                } else if(itm['file'].indexOf('livestream.com/') > -1 ) {
169                        return 'livestream';
170                } else {
171                         return EXTENSIONS[itm['file'].substr(-3).toLowerCase()];
172                }
173        };
174
175
176        /** Jump to a userdefined item in the playlist. **/
177        private function itemHandler(evt:ViewEvent):void {
178                var itm:Number = evt.data.index;
179                if(playlist && playlist[itm]) {
180                        if (itm < 0) {
181                                playItem(0);
182                        } else if (itm > playlist.length-1) {
183                                playItem(playlist.length-1);
184                        } else if (!isNaN(itm)) {
185                                playItem(itm);
186                        }
187                }
188        };
189
190
191        /** Jump to the link of a playlistitem. **/
192        private function linkHandler(evt:ViewEvent=null):void {
193                var itm:Number = config['item'];
194                if(evt && evt.data.index) { itm = evt.data.index; }
195                var lnk:String = playlist[itm]['link'];
196                if(lnk != null) {
197                        navigateToURL(new URLRequest(lnk),config['linktarget']);
198                }
199                playHandler(new ViewEvent(ViewEvent.PLAY,{state:false}));
200        };
201
202
203        /** Load a new playlist. **/
204        private function loadHandler(evt:ViewEvent):void {
205                if(config['state'] != 'IDLE') {
206                        stopHandler();
207                }
208                var obj:Object = new Object();
209                if(typeof(evt.data.object) == 'array') {
210                        playlistHandler(evt.data.object);
211                } else if(typeof(evt.data.object) == 'string') {
212                        obj['file'] = evt.data.object;
213                } else if (evt.data.object['file']) {
214                        for(var itm:String in ELEMENTS) {
215                                if(evt.data.object[itm]) {
216                                        obj[itm] = Strings.serialize(evt.data.object[itm]);
217                                }
218                        }
219                }
220                if(obj['file']) {
221                        if(getModelType(obj,false) == null) {
222                                loader.load(new URLRequest(obj['file']));
223                                return;
224                        } else {
225                                playlistHandler(new Array(obj));
226                        }
227                } else {
228                        var arr:Array = new Array();
229                        for each (var ent:Object in evt.data.object) {
230                                arr.push(ent);
231                        }
232                        playlistHandler(arr);
233                }
234        };
235
236
237        /** Translate the XML object to the feed array. **/
238        private function loaderHandler(evt:Event):void {
239                try {
240                        var dat:XML = XML(evt.target.data);
241                        var fmt:String = dat.localName().toLowerCase();
242                } catch (err:Error) {
243                        sendEvent(ControllerEvent.ERROR,{message:'This playlist is not a valid XML file.'});
244                        return;
245                }
246                switch (fmt) {
247                        case 'rss':
248                                playlistHandler(RSSParser.parse(dat));
249                                break;
250                        case 'playlist':
251                                playlistHandler(XSPFParser.parse(dat));
252                                break;
253                        case 'asx':
254                                playlistHandler(ASXParser.parse(dat));
255                                break;
256                        case 'smil':
257                                playlistHandler(SMILParser.parse(dat));
258                                break;
259                        case 'feed':
260                                playlistHandler(ATOMParser.parse(dat));
261                                break;
262                        default:
263                                sendEvent(ControllerEvent.ERROR,{message:'Unknown playlist format: '+fmt});
264                                return;
265                }
266        };
267
268
269        /** Save new state of the mute switch and send volume. **/
270        private function muteHandler(evt:ViewEvent):void {
271                if(evt.data.state == true || evt.data.state == false) {
272                        if(evt.data.state == config['mute']) {
273                                return;
274                        } else {
275                                config['mute'] = evt.data.state;
276                        }
277                } else {
278                        config['mute'] = !config['mute'];
279                }
280                Configger.saveCookie('mute',config['mute']);
281                sendEvent(ControllerEvent.MUTE,{state:config['mute']});
282        };
283
284
285        /** Jump to the next item in the playlist. **/
286        private function nextHandler(evt:ViewEvent=null):void {
287                if(playlist && config['shuffle'] == true) {
288                        playItem(randomizer.pick());
289                } else if (playlist && config['item'] == playlist.length-1) {
290                        playItem(0);
291                } else if (playlist) {
292                        playItem(config['item']+1);
293                }
294        };
295
296
297        /** Change the playback state. **/
298        private function playHandler(evt:ViewEvent):void {
299                if(playlist) {
300                        if(evt.data.state != false && config['state'] == ModelStates.PAUSED) {
301                                sendEvent(ControllerEvent.PLAY,{state:true});
302                        } else if (evt.data.state != false && config['state'] == ModelStates.COMPLETED) {
303                                sendEvent(ControllerEvent.SEEK,{position:playlist[config['item']]['start']});
304                        } else if(evt.data.state != false && config['state'] == ModelStates.IDLE) {
305                                playItem();
306                        } else if (evt.data.state != true &&
307                                (config['state'] == ModelStates.PLAYING || config['state'] == ModelStates.BUFFERING)) {
308                                sendEvent(ControllerEvent.PLAY,{state:false});
309                        }
310                }
311        };
312
313
314        /** Direct the model to play a new item. **/
315        private function playItem(nbr:Number=undefined):void {
316                if(!isNaN(nbr)) {
317                        config['item'] = nbr;
318                }
319                sendEvent(ControllerEvent.ITEM,{index:config['item']});
320        };
321
322
323        /** Check new playlist for playeable files and setup randomizing/autostart. **/
324        private function playlistHandler(ply:Array):void {
325                for(var i:Number = ply.length-1; i > -1; i--) {
326                        if(!ply[i]['duration'] || isNaN(ply[i]['duration'])) { ply[i]['duration'] = 0; }
327                        if(!ply[i]['start'] || isNaN(ply[i]['start'])) { ply[i]['start'] = 0; }
328                        if(!ply[i]['streamer'] && config['streamer']) {
329                                ply[i]['streamer'] = config['streamer'];
330                        }
331                        ply[i]['type'] = getModelType(ply[i],true);
332                        if(!ply[i]['type']) { ply.splice(i,1); }
333                }
334                if(ply.length > 0) {
335                        playlist = ply;
336                } else {
337                        sendEvent(ControllerEvent.ERROR,{message:"No valid media file(s) found."});
338                        return;
339                }
340                if(config['shuffle'] == true) {
341                        randomizer = new Randomizer(playlist.length);
342                        config['item'] = randomizer.pick();
343                } else if (config['item'] >= playlist.length) {
344                        config['item'] = playlist.length-1;
345                }
346                sendEvent(ControllerEvent.PLAYLIST,{playlist:playlist});
347                if(config['autostart'] == true) {
348                        playItem();
349                }
350        };
351
352
353        /** Jump to the previous item in the playlist. **/
354        private function prevHandler(evt:ViewEvent):void {
355                if (config['item'] == 0) {
356                        playItem(playlist.length-1);
357                } else {
358                        playItem(config['item']-1);
359                }
360        };
361
362
363        /** Manage a resizing of the stage. **/
364        private function redrawHandler(evt:ViewEvent=null):void {
365                try {
366                        var dps:String = skin.stage['displayState'];
367                } catch (err:Error) {}
368                if(dps == 'fullScreen') {
369                        config['fullscreen'] = true;
370                        sploader.layoutFullscreen();
371                } else {
372                        config['width'] = skin.stage.stageWidth;
373                        config['height'] = skin.stage.stageHeight;
374                        config['fullscreen'] = false;
375                        sploader.layoutNormal();
376                }
377                sendEvent(ControllerEvent.RESIZE,{
378                        fullscreen:config['fullscreen'],width:config['width'],height:config['height']});
379        };
380
381
382        /** Seek to a specific part in a mediafile. **/
383        private function seekHandler(evt:ViewEvent):void {
384                if(config['state'] != ModelStates.IDLE && playlist[config['item']]['duration'] > 0) {
385                        var pos:Number = evt.data.position;
386                        if(pos < 1) {
387                                pos = 0;
388                        } else if (pos > playlist[config['item']]['duration']-1) {
389                                pos = playlist[config['item']]['duration']-1;
390                        }
391                        sendEvent(ControllerEvent.SEEK,{position:pos});
392                }
393        };
394
395
396        /** Log and dispatch events. **/
397        private function sendEvent(typ:String,dat:Object=undefined):void {
398                Logger.log(dat,typ);
399                dispatchEvent(new ControllerEvent(typ,dat));
400        };
401
402
403        /** Stop all playback and buffering. **/
404        private function stopHandler(evt:ViewEvent=undefined):void {
405                sendEvent(ControllerEvent.STOP);
406        };
407
408
409        /** Manage playback state changes. **/
410        private function stateHandler(evt:ModelEvent):void {
411                if(evt.data.newstate == ModelStates.COMPLETED) {
412                        switch (config['repeat']) {
413                                case 'single':
414                                        playHandler(new ViewEvent(ViewEvent.PLAY,{state:true}));
415                                        break;
416                                case 'always':
417                                        if(playlist.length == 1) {
418                                                playHandler(new ViewEvent(ViewEvent.PLAY,{state:true}));
419                                        } else {
420                                                nextHandler();
421                                        }
422                                        break;
423                                case 'list':
424                                        if((config['shuffle'] == true && randomizer.length > 0) ||
425                                                (config['shuffle'] == false && config['item'] < playlist.length-1)) {
426                                                nextHandler();
427                                        }
428                                        break;
429                        }
430                }
431        };
432
433
434        /** Save new state of the mute switch and send volume. **/
435        private function volumeHandler(evt:ViewEvent):void {
436                var vol:Number = evt.data.percentage;
437                if (vol < 1) {
438                        muteHandler(new ViewEvent(ViewEvent.MUTE,{state:true}));
439                } else if (!isNaN(vol) && vol < 101) {
440                        if(config['mute'] == true) {
441                                muteHandler(new ViewEvent(ViewEvent.MUTE,{state:false}));
442                        }
443                        config['volume'] = vol;
444                        Configger.saveCookie('volume',config['volume']);
445                        sendEvent(ControllerEvent.VOLUME,{percentage:vol});
446                }
447        };
448
449
450}
451
452
453}
Note: See TracBrowser for help on using the browser.