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

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

fixed Bitgravity FLV streaming and some additional mini quirks

  • 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. **/
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 + byteoffset;
150                var off:Number = byteoffset;
151                model.sendEvent(ModelEvent.LOADED,{loaded:ldd,total:ttl,offset:off});
152                if(ldd+off >= ttl && ldd > 0) {
153                        clearInterval(loadinterval);
154                }
155                if(!loadtimer) {
156                        loadtimer = setTimeout(loadTimeout,3000);
157                }
158        };
159
160
161        /** timeout for checking the bitrate. **/
162        protected function loadTimeout():void {
163                var obj:Object = new Object();
164                obj['bandwidth'] = Math.round(stream.bytesLoaded/1024/3*8);
165                if(item['duration']) {
166                        obj['bitrate'] = Math.round(stream.bytesTotal/1024*8/item['duration']);
167                }
168                model.sendEvent('META',obj);
169        };
170
171
172        /** Get metadata information from netstream class. **/
173        public function onData(dat:Object):void {
174                if(dat.width) {
175                        video.width = dat.width;
176                        video.height = dat.height;
177                }
178                if(dat['type'] == 'metadata' && !meta) {
179                        meta = true;
180                        if(dat.seekpoints) {
181                                mp4 = true;
182                                keyframes = convertSeekpoints(dat.seekpoints);
183                        } else {
184                                mp4 = false;
185                                keyframes = dat.keyframes;
186                        }
187                        if(item['start'] > 0) {
188                                seek(item['start']);
189                        }
190                }
191                model.sendEvent(ModelEvent.META,dat);
192        };
193
194
195        /** Pause playback. **/
196        override public function pause():void {
197                stream.pause();
198                clearInterval(interval);
199                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PAUSED});
200        };
201
202
203        /** Resume playing. **/
204        override public function play():void {
205                stream.resume();
206                interval = setInterval(positionInterval,100);
207                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
208        };
209
210
211        /** Interval for the position progress **/
212        protected function positionInterval():void {
213                iterator++;
214                if(iterator > 10) {
215                        position = Math.round(stream.time*10)/10;
216                        if (mp4) {
217                                position += timeoffset;
218                        }
219                }
220                var bfr:Number = Math.round(stream.bufferLength/stream.bufferTime*100);
221                if(bfr < 95 && position < Math.abs(item['duration']-stream.bufferTime-1)) {
222                        model.sendEvent(ModelEvent.BUFFER,{percentage:bfr});
223                        if(model.config['state'] != ModelStates.BUFFERING && bfr < 25) {
224                                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.BUFFERING});
225                        }
226                } else if (bfr > 95 && model.config['state'] != ModelStates.PLAYING) {
227                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
228                }
229                if(position < item['duration']) {
230                        model.sendEvent(ModelEvent.TIME,{position:position,duration:item['duration']});
231                } else if (item['duration'] > 0 && model.config['respectduration']) {
232                        pause();
233                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED});
234                }
235        };
236
237
238        /** Seek to a specific second. **/
239        override public function seek(pos:Number):void {
240                var off:Number = getOffset(pos);
241                clearInterval(interval);
242                if(off < byteoffset || off >= byteoffset+stream.bytesLoaded) {
243                        timeoffset = position = getOffset(pos,true);
244                        byteoffset = off;
245                        load(item);
246                } else {
247                        position = pos;
248                        if(mp4) {
249                                stream.seek(getOffset(position-timeoffset,true));
250                        } else {
251                                stream.seek(getOffset(position,true));
252                        }
253                        interval = setInterval(positionInterval,100);
254                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.PLAYING});
255                }
256        };
257
258
259        /** Receive NetStream status updates. **/
260        protected function statusHandler(evt:NetStatusEvent):void {
261                switch (evt.info.code) {
262                        case "NetStream.Play.Stop":
263                                if(model.config['state'] != ModelStates.COMPLETED) {
264                                        clearInterval(interval);
265                                        model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.COMPLETED});
266                                }
267                                break;
268                        case "NetStream.Play.StreamNotFound":
269                                stop();
270                                model.sendEvent(ModelEvent.ERROR,{message:'Video not found: '+item['file']});
271                                break;
272                }
273                model.sendEvent(ModelEvent.META,{info:evt.info.code});
274        };
275
276
277        /** Destroy the HTTP stream. **/
278        override public function stop():void {
279                if(stream.bytesLoaded+byteoffset < stream.bytesTotal) {
280                        stream.close();
281                } else {
282                        stream.pause();
283                }
284                clearInterval(interval);
285                clearInterval(loadinterval);
286                byteoffset = timeoffset = position = 0;
287                keyframes = undefined;
288                meta = false;
289                model.sendEvent(ModelEvent.STATE,{newstate:ModelStates.IDLE});
290        };
291
292
293        /** Set the volume level. **/
294        override public function volume(vol:Number):void {
295                transform.volume = vol/100;
296                stream.soundTransform = transform;
297        };
298
299
300};
301
302
303}
Note: See TracBrowser for help on using the repository browser.