source: trunk/as3/com/jeroenwijering/player/Controller.as @ 116

Revision 116, 13.2 KB checked in by jeroen, 5 years ago (diff)

centralized plugin/layout management in SPLoader

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