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

Revision 103, 12.8 KB checked in by jeroen, 5 years ago (diff)

fixed model abstraction and added duration flashvar respectance

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