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

Revision 220, 8.3 KB checked in by jeroen, 4 years ago (diff)

string of small bugfixes

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