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

Revision 45, 8.1 KB checked in by jeroen, 5 years ago (diff)

fixed a string of bugs (see trac), notably yt-nosound, no metadata = no video and rtmp folder structures

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