source: trunk/as3/com/jeroenwijering/models/HTTPModel.as @ 8

Revision 8, 8.4 KB checked in by jeroen, 5 years ago (diff)

fixed a lot of small issues with playlist-playback by allowing each model to continue to exist

  • Property svn:executable set to *
Line 
1/**
2* Wrapper for playback of progressively downloaded video.
3**/
4package com.jeroenwijering.models {
5
6
7import com.jeroenwijering.events.*;
8import com.jeroenwijering.models.ModelInterface;
9import com.jeroenwijering.player.Model;
10import flash.events.*;
11import flash.display.DisplayObject;
12import flash.media.SoundTransform;
13import flash.media.Video;
14import flash.net.*;
15import flash.utils.clearInterval;
16import flash.utils.setInterval;
17
18
19public class HTTPModel implements ModelInterface {
20
21
22        /** reference to the model. **/
23        private var model:Model;
24        /** Video object to be instantiated. **/
25        private var video:Video;
26        /** NetConnection object for setup of the video stream. **/
27        private var connection:NetConnection;
28        /** NetStream instance that handles the stream IO. **/
29        private var stream:NetStream;
30        /** Sound control object. **/
31        private var transform:SoundTransform;
32        /** Interval ID for the time. **/
33        private var timeinterval:Number;
34        /** Interval ID for the loading. **/
35        private var loadinterval:Number;
36        /** Object with keyframe times and positions. **/
37        private var keyframes:Object;
38        /** Offset byteposition to start streaming. **/
39        private var offset:Number;
40        /** Offset timeposition for lighttpd streaming. **/
41        private var timeoffset:Number;
42        /** switch for h264 streaming **/
43        private var h264:Boolean;
44        /** Byteposition to which the file has been loaded. **/
45        private var loaded:Number;
46
47
48        /** Constructor; sets up the connection and display. **/
49        public function HTTPModel(mod:Model) {
50                model = mod;
51                connection = new NetConnection();
52                connection.addEventListener(NetStatusEvent.NET_STATUS,statusHandler);
53                connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR,errorHandler);
54                connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR,errorHandler);
55                connection.connect(null);
56                stream = new NetStream(connection);
57                stream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler);
58                stream.addEventListener(IOErrorEvent.IO_ERROR,errorHandler);
59                stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR,errorHandler);
60                stream.bufferTime = model.config['bufferlength'];
61                stream.client = this;
62                video = new Video(320,240);
63                transform = new SoundTransform();
64                stream.soundTransform = transform;
65                model.config['mute'] == true ? volume(0): volume(model.config['volume']);
66                quality(model.config['quality']);
67                offset = timeoffset = 0;
68        };
69
70
71        /** Catch security errors. **/
72        private function errorHandler(evt:ErrorEvent) {
73                model.sendEvent(ModelEvent.ERROR,{message:evt.text});
74        };
75
76
77        /** Return a keyframe byteoffset or timeoffset. **/
78        private function getOffset(pos:Number,tme:Boolean=false):Number {
79                var off = 0;
80                if(keyframes === null) {
81                        errorHandler(new ErrorEvent(ErrorEvent.ERROR,false,false,"This file has no seekpoints metadata."));
82                        return 0;
83                }
84                for (var i=0; i< keyframes.times.length; i++) {
85                        if((keyframes.times[i] <= pos || i ==0) && (keyframes.times[i+1] >= pos || !keyframes.times[i+1])) {
86                                if(tme == true) {
87                                        off = keyframes.times[i];
88                                } else {
89                                        off = keyframes.filepositions[i];
90                                }
91                                break;
92                        }
93                }
94                trace(off);
95                return off;
96        };
97
98
99        /** Load content. **/
100        public function load() {
101                video.attachNetStream(stream);
102                stream.close();
103                var url = model.playlist[model.config['item']]['file'];
104                if(model.config["streamscript"] == "lighttpd") {
105                        if(h264) {
106                                url +='?start='+timeoffset;
107                        } else {
108                                url += '?start='+offset;
109                        }
110                } else {
111                        if(model.config["streamscript"].indexOf('?') > -1) {
112                                url = model.config["streamscript"]+"&file="+url+'&start='+offset;
113                        } else {
114                                url = model.config["streamscript"]+"?file="+url+'&start='+offset;
115                        }
116                }
117                url += '&width='+model.config['width'];
118                url += '&client='+encodeURI(model.config['client']);
119                url += '&version='+encodeURI(model.config['version']);
120                stream.play(url);
121                clearInterval(loadinterval);
122                clearInterval(timeinterval);
123                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING});
124                loadinterval = setInterval(loadHandler,100);
125                timeinterval = setInterval(timeHandler,100);
126        };
127
128
129        /** Interval for the loading progress **/
130        private function loadHandler() {
131                loaded = stream.bytesLoaded;
132                var ttl = stream.bytesTotal;
133                model.sendEvent(ModelEvent.LOADED,{loaded:loaded,total:ttl+offset,offset:offset});
134                if(loaded >= ttl && loaded > 0) {
135                        clearInterval(loadinterval);
136                }
137        };
138
139
140        /** Get textdata from netstream. **/
141        public function onImageData(info:Object) {
142                var dat = new Object();
143                for(var i in info) {
144                        dat[i] = info[i];
145                }
146                model.sendEvent(ModelEvent.META,dat);
147        };
148
149
150        /** Handler for onLastSecond call. **/
151        public function onLastSecond(info:Object) { };
152
153
154        /** Get metadata information from netstream class. **/
155        public function onMetaData(info:Object) {
156                if(h264) { return; }
157                if(info.width) {
158                        video.width = info.width;
159                        video.height = info.height;
160                        model.mediaHandler(video);
161                } else {
162                        model.mediaHandler();
163                }
164                if(info.seekpoints) {
165                        h264 = true;
166                        keyframes = new Object();
167                        keyframes.times = new Array();
168                        keyframes.filepositions = new Array();
169                        for (var j in info.seekpoints) {
170                                keyframes.times.push(Number(info.seekpoints[j]['time']));
171                                keyframes.filepositions.push(Number(info.seekpoints[j]['offset']));
172                        }
173                } else if(info.keyframes) {
174                        keyframes = info.keyframes;
175                }
176                var dat = new Object();
177                for(var i in info) {
178                        dat[i] = info[i];
179                }
180                delete dat.seekpoints;
181                dat.keyframes = '';
182                for(var k=0; k<keyframes.times.length; k++) {
183                        dat['keyframes'] += ','+keyframes.times[k]+':'+keyframes.filepositions[k];
184                }
185                model.sendEvent(ModelEvent.META,dat);
186                if(model.playlist[model.config['item']]['start'] > 0) {
187                        seek(model.playlist[model.config['item']]['start']);
188                }
189        };
190
191
192        /** Get textdata from netstream. **/
193        public function onTextData(info:Object) {
194                var dat = new Object();
195                for(var i in info) {
196                        dat[i] = info[i];
197                }
198                model.sendEvent(ModelEvent.META,dat);
199        };
200
201
202        /** Pause playback. **/
203        public function pause() {
204                clearInterval(timeinterval);
205                stream.pause();
206                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PAUSED});
207        };
208
209
210        /** Resume playing. **/
211        public function play() {
212                stream.resume();
213                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
214                timeinterval = setInterval(timeHandler,100);
215        };
216
217
218        /** Change the smoothing mode. **/
219        public function seek(pos:Number) {
220                clearInterval(timeinterval);
221                var off = getOffset(pos);
222                if(off < offset || off > offset+loaded) {
223                        offset = off;
224                        timeoffset = getOffset(pos,true);
225                        load();
226                        trace('===LOADING-===');
227                } else {
228                        trace('===SEEKING===');
229                        if(h264) {
230                                stream.seek(pos-timeoffset);
231                        } else {
232                                stream.seek(pos)
233                        }
234                        play();
235                }
236        };
237
238
239        /** Change the smoothing mode. **/
240        public function quality(qua:Boolean) {
241                if(qua == true) {
242                        video.smoothing = true;
243                        video.deblocking = 4;
244                } else {
245                        video.smoothing = false;
246                        video.deblocking = 1;
247                }
248        };
249
250
251        /** Receive NetStream status updates. **/
252        private function statusHandler(evt:NetStatusEvent) {
253                if(evt.info.code == "NetStream.Play.Stop") {
254                        if(model.config['state'] != ModelStates.COMPLETED) {
255                                clearInterval(timeinterval);
256                                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED});
257                        }
258                } else if(evt.info.code == "NetStream.Play.StreamNotFound") {
259                        stop();
260                        model.sendEvent(ModelEvent.ERROR,{message:"Video stream not found: " +
261                                model.playlist[model.config['item']]['file']});
262                } else {
263                        model.sendEvent(ModelEvent.META,{info:evt.info.code});
264                }
265        };
266
267
268        /** Destroy the HTTP stream. **/
269        public function stop() {
270                clearInterval(loadinterval);
271                clearInterval(timeinterval);
272                stream.close();
273                offset = timeoffset = 0;
274        };
275
276
277        /** Interval for the position progress **/
278        private function timeHandler() {
279                var bfr = Math.round(stream.bufferLength/stream.bufferTime*100);
280                var pos = Math.round(stream.time*10)/10;
281                if (h264) { pos += timeoffset; }
282                var dur = model.playlist[model.config['item']]['duration'];
283                if(bfr < 100 && pos < Math.abs(dur-stream.bufferTime-1)) {
284                        model.sendEvent(ModelEvent.BUFFER,{percentage:bfr});
285                        if(model.config['state'] != ModelStates.BUFFERING) {
286                                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING});
287                        }
288                } else if (model.config['state'] == ModelStates.BUFFERING) {
289                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
290                }
291                if(dur > 0) {
292                        model.sendEvent(ModelEvent.TIME,{position:pos,duration:dur});
293                }
294        };
295
296
297        /** Set the volume level. **/
298        public function volume(vol:Number) {
299                transform.volume = vol/100;
300                stream.soundTransform = transform;
301        };
302
303
304};
305
306
307}
Note: See TracBrowser for help on using the repository browser.