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

Revision 109, 8.2 KB checked in by jeroen, 5 years ago (diff)

fixed YT proxy, a playlist-rollover bug, rtmp mp4 file extraction. Added first iteration of BrightCove API

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