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

Revision 135, 7.3 KB checked in by jeroen, 4 years ago (diff)

several bugfixes and the ability to restrict files from start to duration

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