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

Revision 154, 7.1 KB checked in by jeroen, 4 years ago (diff)

fixed repeating issue with video playback

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