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

Revision 171, 8.2 KB checked in by jeroen, 4 years ago (diff)

made HTTP and NginX also MP4 compatible

  • 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.NetClient;
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 (since it takes some time before stream.time is restarted) **/
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.addEventListener(NetStatusEvent.NET_STATUS,statusHandler);
56                stream.addEventListener(IOErrorEvent.IO_ERROR,errorHandler);
57                stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR,errorHandler);
58                stream.bufferTime = model.config['bufferlength'];
59                stream.checkPolicyFile = true;
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                model.config['mute'] == true ? volume(0): volume(model.config['volume']);
66                byteoffset = timeoffset = 0;
67        };
68
69
70        /** Convert seekpoints to keyframes. **/
71        protected function convertSeekpoints(dat:Object):Object {
72                var kfr:Object = new Object();
73                kfr.times = new Array();
74                kfr.filepositions = new Array();
75                for (var j in dat) {
76                        kfr.times[j] = Number(dat[j]['time']);
77                        kfr.filepositions[j] = Number(dat[j]['offset']);
78                }
79                return kfr;
80        };
81
82
83        /** Catch security errors. **/
84        protected function errorHandler(evt:ErrorEvent):void {
85                stop();
86                model.sendEvent(ModelEvent.ERROR,{message:evt.text});
87        };
88
89
90        /** Return a keyframe byteoffset or timeoffset. **/
91        protected function getOffset(pos:Number,tme:Boolean=false):Number {
92                if(!keyframes) {
93                        return 0;
94                }
95                for (var i:Number=0; i < keyframes.times.length - 1; i++) {
96                        if(keyframes.times[i] <= pos && keyframes.times[i+1] >= pos) {
97                                break;
98                        }
99                }
100                if(tme == true) {
101                        return keyframes.times[i];
102                } else {
103                        return keyframes.filepositions[i];
104                }
105        };
106
107
108        /** Create the video request URL. **/
109        protected function getURL():String {
110                var url:String = item['streamer'];
111                var off:Number  = byteoffset;
112                if(mp4) {
113                        off = timeoffset;
114                }
115                if(url.indexOf('?') > -1) {
116                        url += "&file="+item['file']+'&start='+off;
117                } else {
118                        url += "?file="+item['file']+'&start='+off;
119                }
120                return url;
121        };
122
123
124        /** Load content. **/
125        override public function load(itm:Object):void {
126                item = itm;
127                position = timeoffset;
128                if(stream.bytesLoaded + byteoffset < stream.bytesTotal) {
129                        stream.close();
130                }
131                model.mediaHandler(video);
132                stream.play(getURL());
133                iterator = 0;
134                clearInterval(interval);
135                interval = setInterval(positionInterval,100);
136                clearInterval(loadinterval);
137                loadinterval = setInterval(loadHandler,200);
138                model.sendEvent(ModelEvent.BUFFER,{percentage:0});
139                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING});
140        };
141
142
143        /** Interval for the loading progress **/
144        protected function loadHandler():void {
145                var ldd:Number = stream.bytesLoaded;
146                var ttl:Number = stream.bytesTotal + byteoffset;
147                var off:Number = byteoffset;
148                model.sendEvent(ModelEvent.LOADED,{loaded:ldd,total:ttl,offset:off});
149                if(ldd+off >= ttl && ldd > 0) {
150                        clearInterval(loadinterval);
151                }
152                if(!loadtimer) {
153                        loadtimer = setTimeout(loadTimeout,3000);
154                }
155        };
156
157
158        /** timeout for checking the bitrate. **/
159        protected function loadTimeout():void {
160                var obj:Object = new Object();
161                obj['bandwidth'] = Math.round(stream.bytesLoaded/1024/3*8);
162                if(item['duration']) {
163                        obj['bitrate'] = Math.round(stream.bytesTotal/1024*8/item['duration']);
164                }
165                model.sendEvent('META',obj);
166        };
167
168
169        /** Get metadata information from netstream class. **/
170        public function onData(dat:Object):void {
171                if(dat.width) {
172                        video.width = dat.width;
173                        video.height = dat.height;
174                }
175                if(dat['type'] == 'metadata' && !meta) {
176                        meta = true;
177                        if(dat.seekpoints) {
178                                mp4 = true;
179                                keyframes = convertSeekpoints(dat.seekpoints);
180                        } else {
181                                mp4 = false;
182                                keyframes = dat.keyframes;
183                        }
184                        if(item['start'] > 0) {
185                                seek(item['start']);
186                        }
187                }
188                model.sendEvent(ModelEvent.META,dat);
189        };
190
191
192        /** Pause playback. **/
193        override public function pause():void {
194                stream.pause();
195                clearInterval(interval);
196                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PAUSED});
197        };
198
199
200        /** Resume playing. **/
201        override public function play():void {
202                stream.resume();
203                interval = setInterval(positionInterval,100);
204                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
205        };
206
207
208        /** Interval for the position progress **/
209        protected function positionInterval():void {
210                iterator++;
211                if(iterator > 10) {
212                        position = Math.round(stream.time*10)/10;
213                        if (mp4) {
214                                position += timeoffset;
215                        }
216                }
217                var bfr:Number = Math.round(stream.bufferLength/stream.bufferTime*100);
218                if(bfr < 95 && position < Math.abs(item['duration']-stream.bufferTime-1)) {
219                        model.sendEvent(ModelEvent.BUFFER,{percentage:bfr});
220                        if(model.config['state'] != ModelStates.BUFFERING && bfr < 25) {
221                                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING});
222                        }
223                } else if (bfr > 95 && model.config['state'] != ModelStates.PLAYING) {
224                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
225                }
226                if(position < item['duration']) {
227                        model.sendEvent(ModelEvent.TIME,{position:position,duration:item['duration']});
228                } else if (item['duration'] > 0 && model.config['respectduration']) {
229                        pause();
230                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED});
231                }
232        };
233
234
235        /** Seek to a specific second. **/
236        override public function seek(pos:Number):void {
237                var off:Number = getOffset(pos);
238                clearInterval(interval);
239                if(off < byteoffset || off >= byteoffset+stream.bytesLoaded) {
240                        timeoffset = position = getOffset(pos,true);
241                        byteoffset = off;
242                        load(item);
243                } else {
244                        position = pos;
245                        if(mp4) {
246                                stream.seek(getOffset(position-timeoffset,true));
247                        } else {
248                                stream.seek(getOffset(position,true));
249                        }
250                        interval = setInterval(positionInterval,100);
251                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
252                }
253        };
254
255
256        /** Receive NetStream status updates. **/
257        protected function statusHandler(evt:NetStatusEvent):void {
258                switch (evt.info.code) {
259                        case "NetStream.Play.Stop":
260                                if(model.config['state'] != ModelStates.COMPLETED) {
261                                        clearInterval(interval);
262                                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED});
263                                }
264                                break;
265                        case "NetStream.Play.StreamNotFound":
266                                stop();
267                                model.sendEvent(ModelEvent.ERROR,{message:'Video not found: '+item['file']});
268                                break;
269                }
270                model.sendEvent(ModelEvent.META,{info:evt.info.code});
271        };
272
273
274        /** Destroy the HTTP stream. **/
275        override public function stop():void {
276                if(stream.bytesLoaded+byteoffset < stream.bytesTotal) {
277                        stream.close();
278                } else {
279                        stream.pause();
280                }
281                clearInterval(interval);
282                clearInterval(loadinterval);
283                byteoffset = timeoffset = position = 0;
284                keyframes = undefined;
285                meta = false;
286                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.IDLE});
287        };
288
289
290        /** Set the volume level. **/
291        override public function volume(vol:Number):void {
292                transform.volume = vol/100;
293                stream.soundTransform = transform;
294        };
295
296
297};
298
299
300}
Note: See TracBrowser for help on using the repository browser.