source: trunk/fl5/src/com/longtailvideo/jwplayer/media/HTTPMediaProvider.as @ 1201

Revision 1201, 12.0 KB checked in by jeroen, 3 years ago (diff)

fixed 3 issues in HTTP model: starttime=0 offset should not be sent, timeoffsets should be ceiled to 1/100 second to prevent server from returning previous keyframe, code that checks whether video is buffering should ignore last second (where buffer naturally depletes)

Line 
1/**
2 * Manages playback of http streaming flv.
3 **/
4package com.longtailvideo.jwplayer.media {
5        import com.longtailvideo.jwplayer.events.MediaEvent;
6        import com.longtailvideo.jwplayer.model.PlayerConfig;
7        import com.longtailvideo.jwplayer.model.PlaylistItem;
8        import com.longtailvideo.jwplayer.player.PlayerState;
9        import com.longtailvideo.jwplayer.utils.NetClient;
10       
11        import flash.events.*;
12        import flash.media.*;
13        import flash.net.*;
14        import flash.utils.*;
15
16
17        public class HTTPMediaProvider extends MediaProvider {
18                /** NetConnection object for setup of the video stream. **/
19                protected var _connection:NetConnection;
20                /** NetStream instance that handles the stream IO. **/
21                protected var _stream:NetStream;
22                /** Video object to be instantiated. **/
23                protected var _video:Video;
24                /** Sound control object. **/
25                protected var _transformer:SoundTransform;
26                /** ID for the _position interval. **/
27                protected var _positionInterval:uint;
28                /** Save whether metadata has already been sent. **/
29                protected var _meta:Boolean;
30                /** Object with keyframe times and positions. **/
31                protected var _keyframes:Object;
32                /** Offset in bytes of the last seek. **/
33                protected var _byteoffset:Number = 0;
34                /** Offset in seconds of the last seek. **/
35                protected var _timeoffset:Number = 0;
36                /** Boolean for mp4 / flv streaming. **/
37                protected var _mp4:Boolean;
38                /** Variable that takes reloading into account. **/
39                protected var _iterator:Number;
40                /** Start parameter. **/
41                private var _startparam:String = 'start';
42                /** Whether the buffer has filled **/
43                private var _bufferFull:Boolean;
44                /** Whether the enitre video has been buffered **/
45                private var _bufferingComplete:Boolean;
46                /** Whether we have checked the bandwidth. **/
47                private var _bandwidthSwitch:Boolean = true;
48                /** Whether we have checked bandwidth **/
49                private var _bandwidthChecked:Boolean;
50                /** Bandwidth check delay **/
51                private var _bandwidthDelay:Number = 2000;
52                /** Bandwidth timeout id **/
53                private var _bandwidthTimeout:uint;
54               
55                /** Constructor; sets up the connection and display. **/
56                public function HTTPMediaProvider() {
57                        super('http');
58                }
59
60
61                public override function initializeMediaProvider(cfg:PlayerConfig):void {
62                        super.initializeMediaProvider(cfg);
63                        _connection = new NetConnection();
64                        _connection.connect(null);
65                        _stream = new NetStream(_connection);
66                        _stream.checkPolicyFile = true;
67                        _stream.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
68                        _stream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
69                        _stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
70                        _stream.bufferTime = config.bufferlength;
71                        _stream.client = new NetClient(this);
72                        _transformer = new SoundTransform();
73                        _video = new Video(320, 240);
74                        _video.smoothing = config.smoothing;
75                        _video.attachNetStream(_stream);
76                }
77
78
79                /** Convert seekpoints to keyframes. **/
80                protected function convertSeekpoints(dat:Object):Object {
81                        var kfr:Object = new Object();
82                        kfr.times = new Array();
83                        kfr.filepositions = new Array();
84                        for (var j:String in dat) {
85                                kfr.times[j] = Number(dat[j]['time']);
86                                kfr.filepositions[j] = Number(dat[j]['offset']);
87                        }
88                        return kfr;
89                }
90
91                /** Catch security errors. **/
92                protected function errorHandler(evt:ErrorEvent):void {
93                        error(evt.text);
94                }
95
96                /** Bandwidth is checked as long the stream hasn't completed loading. **/
97                private function checkBandwidth(lastLoaded:Number):void {
98                        var currentLoaded:Number = _stream.bytesLoaded;
99                        var bandwidth:Number = Math.ceil((currentLoaded - lastLoaded) / 1024) * 8 / (_bandwidthDelay / 1000);
100                       
101                        if (currentLoaded < _stream.bytesTotal) {
102                                if (bandwidth > 0) {
103                                        config.bandwidth = bandwidth;
104                                        var obj:Object = {bandwidth:bandwidth};
105                                        if (item.duration > 0) {
106                                                obj.bitrate = Math.ceil(_stream.bytesTotal / 1024 * 8 / item.duration);
107                                        }
108                                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: obj});
109                                }
110                                if (_bandwidthSwitch) {
111                                        _bandwidthSwitch = false;
112                                        _bandwidthChecked = false;
113                                        if (item.currentLevel != item.getLevel(config.bandwidth, config.width)) {
114                                                load(item);
115                                                return;
116                                        }
117                                }
118                                clearTimeout(_bandwidthTimeout);
119                                _bandwidthTimeout = setTimeout(checkBandwidth, _bandwidthDelay, currentLoaded);
120                        }
121                }
122               
123                /** Return a keyframe byteoffset or timeoffset. **/
124                protected function getOffset(pos:Number, tme:Boolean=false):Number {
125                        if (!_keyframes) {
126                                return 0;
127                        }
128                        for (var i:Number = 0; i < _keyframes.times.length - 1; i++) {
129                                if (_keyframes.times[i] <= pos && _keyframes.times[i + 1] >= pos) {
130                                        break;
131                                }
132                        }
133                        if (tme == true) {
134                                return _keyframes.times[i];
135                        } else {
136                                return _keyframes.filepositions[i];
137                        }
138                }
139
140
141                /** Create the video request URL. **/
142                protected function getURL():String {
143                        var url:String = item.file;
144                        var off:Number = _byteoffset;
145                        if (getConfigProperty('startparam') as String) {
146                                _startparam = getConfigProperty('startparam');
147                        }
148                        if (item.streamer) {
149                                if (item.streamer.indexOf('/') > 0) {
150                                        url = item.streamer;
151                                        url = getURLConcat(url, 'file', item.file);
152                                } else {
153                                        _startparam = item.streamer;
154                                }
155                        }
156                        url = encodeURI(url);
157                        if (_mp4 || _startparam == 'starttime') {
158                                off = Math.ceil(_timeoffset*100)/100;
159                                _mp4 = true;
160                        }
161                        if (!_mp4 || off > 0) {
162                                url = getURLConcat(url, _startparam, off);
163                        }
164                        if (config['token'] || item['token']) {
165                                url = getURLConcat(url, 'token', item['token'] ? item['token'] : config['token']);
166                        }
167                        return url;
168                }
169
170
171                /** Concatenate a parameter to the url. **/
172                private function getURLConcat(url:String, prm:String, val:*):String {
173                        if (url.indexOf('?') > -1) {
174                                return url + '&' + prm + '=' + val;
175                        } else {
176                                return url + '?' + prm + '=' + val;
177                        }
178                }
179
180
181                /** Load content. **/
182                override public function load(itm:PlaylistItem):void {
183                        _item = itm;
184                        _position = _timeoffset;
185                        _bufferFull = false;
186                        _bufferingComplete = false;
187                        _bandwidthSwitch = true;
188                       
189                        if (item.levels.length > 0) { item.setLevel(item.getLevel(config.bandwidth, config.width)); }
190                       
191                        media = _video;
192                        _stream.play(getURL());
193                       
194                        clearInterval(_positionInterval);
195                        _positionInterval = setInterval(positionInterval, 100);
196                       
197                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
198                        setState(PlayerState.BUFFERING);
199                        sendBufferEvent(0, 0);
200                        streamVolume(config.mute ? 0 : config.volume);
201                }
202
203                /** Get metadata information from netstream class. **/
204                public function onClientData(dat:Object):void {
205                        if (!dat) return;
206                        if (dat.width) {
207                                _video.width = dat.width;
208                                _video.height = dat.height;
209                                resize(_width, _height);
210                        }
211                        if (dat['duration'] && item.duration <= 0) {
212                                item.duration = dat['duration'];
213                        }
214                        if (dat['type'] == 'metadata' && !_meta) {
215                                _meta = true;
216                                if (dat['seekpoints']) {
217                                        _mp4 = true;
218                                        _keyframes = convertSeekpoints(dat['seekpoints']);
219                                } else {
220                                        _mp4 = false;
221                                        _keyframes = dat['keyframes'];
222                                }
223                                if (item.start > 0) {
224                                        seek(item.start);
225                                }
226                        }
227                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: dat});
228                }
229
230
231                /** Pause playback. **/
232                override public function pause():void {
233                        _stream.pause();
234                        super.pause();
235                }
236
237
238                /** Resume playing. **/
239                override public function play():void {
240                        _stream.resume();
241                        if (!_positionInterval) {
242                                _positionInterval = setInterval(positionInterval, 100);
243                        }
244                        super.play();
245                }
246
247
248                /** Interval for the position progress **/
249                protected function positionInterval():void {
250                        var pos:Number = Math.round(_stream.time * 10) / 10;
251                        var percentoffset:Number;
252                        if (_mp4) {
253                                pos += _timeoffset;
254                        }
255                       
256                        var bufferFill:Number;
257                        if (item.duration > 0 && _stream && _stream.bytesTotal > 0) {
258                                percentoffset =  _timeoffset /  item.duration * 100;
259                                var bufferTime:Number = _stream.bufferTime < (item.duration - pos) ? _stream.bufferTime : Math.round(item.duration - pos);
260                                bufferFill = _stream.bufferTime ? Math.ceil(_stream.bufferLength / bufferTime * 100) : 0;
261                        } else {
262                                percentoffset = 0;
263                                bufferFill = _stream.bufferTime ? _stream.bufferLength/_stream.bufferTime * 100 : 0;
264                        }
265       
266                        var bufferPercent:Number = _stream.bytesTotal ? (_stream.bytesLoaded / _stream.bytesTotal) * (1 - percentoffset/100) * 100 : 0;
267
268                        if (!_bandwidthChecked && _stream.bytesLoaded > 0 && _stream.bytesLoaded < _stream.bytesTotal) {
269                                _bandwidthChecked = true;
270                                clearTimeout(_bandwidthTimeout);
271                                _bandwidthTimeout = setTimeout(checkBandwidth, _bandwidthDelay, _stream.bytesLoaded);
272                        }
273
274                        if (bufferFill < 50 && state == PlayerState.PLAYING && item.duration - pos > _stream.bufferTime) {
275                                _bufferFull = false;
276                                _stream.pause();
277                                setState(PlayerState.BUFFERING);
278                        } else if (bufferFill > 95 && state == PlayerState.BUFFERING && _bufferFull == false) {
279                                _bufferFull = true;
280                                sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
281                        }
282
283                        if (!_bufferingComplete) {
284                                if ((bufferPercent + percentoffset) == 100 && _bufferingComplete == false) {
285                                        _bufferingComplete = true;
286                                }
287                                sendBufferEvent(bufferPercent, _timeoffset, {loaded:_stream.bytesLoaded, total:_stream.bytesTotal, offset:_timeoffset});
288                        }
289                       
290                        if (state != PlayerState.PLAYING) {
291                                return;
292                        }
293                       
294                               
295                        if (pos < item.duration) {
296                                _position = pos;
297                                if (_position >= 0) {
298                                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: _position, duration: item.duration, offset: _timeoffset});
299                                }
300                        } else if (item.duration > 0) {
301                                // Playback completed
302                                complete();
303                        }
304                }
305
306                /** Handle a resize event **/
307                override public function resize(width:Number, height:Number):void {
308                        super.resize(width, height);
309                        if (item.levels.length > 0 && item.getLevel(config.bandwidth, config.width) != item.currentLevel) {
310                                _byteoffset = getOffset(position);
311                                _timeoffset = _position = getOffset(position,true);
312                                load(item);
313                        }
314                }
315
316                /** Seek to a specific second. **/
317                override public function seek(pos:Number):void {
318                        var off:Number = getOffset(pos);
319                        super.seek(pos);
320                        clearInterval(_positionInterval);
321                        _positionInterval = undefined;
322                        if (off < _byteoffset || off >= _byteoffset + _stream.bytesLoaded) {
323                                _timeoffset = _position = getOffset(pos, true);
324                                _byteoffset = off;
325                                load(item);
326                        } else {
327                                if (state == PlayerState.PAUSED) {
328                                        _stream.resume();
329                                }
330                                if (_mp4) {
331                                        _stream.seek(getOffset(_position - _timeoffset, true));
332                                } else {
333                                        _stream.seek(getOffset(_position, true));
334                                }
335                                play();
336                        }
337                }
338
339
340                /** Receive NetStream status updates. **/
341                protected function statusHandler(evt:NetStatusEvent):void {
342                        switch (evt.info.code) {
343                                case "NetStream.Play.Stop":
344                                        complete();
345                                        break;
346                                case "NetStream.Play.StreamNotFound":
347                                        stop();
348                                        error('Video not found: ' + item.file);
349                                        break;
350                                case 'NetStream.Buffer.Full':
351                                        if (!_bufferFull) {
352                                                _bufferFull = true;
353                                                sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
354                                        }
355                                        break;
356                        }
357                        // sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {status: evt.info.code}});
358                }
359
360
361                /** Destroy the HTTP stream. **/
362                override public function stop():void {
363                        if (_stream.bytesLoaded < _stream.bytesTotal || _byteoffset > 0) {
364                                _stream.close();
365                        } else {
366                                _stream.pause();
367                        }
368                        clearInterval(_positionInterval);
369                        _positionInterval = undefined;
370                        _position = _byteoffset = _timeoffset = 0;
371                        _keyframes = undefined;
372                        _bandwidthChecked = false;
373                        _meta = false;
374                        super.stop();
375                }
376               
377                override protected function complete():void {
378                        if (state != PlayerState.IDLE) {
379                                clearInterval(_positionInterval);
380                                _positionInterval = undefined;
381                                _position = _byteoffset = _timeoffset = 0;
382                                _keyframes = undefined;
383                                _bandwidthChecked = false;
384                                _meta = false;
385                                super.stop();
386                                sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_COMPLETE);
387                        }
388                }
389
390
391                /** Set the volume level. **/
392                override public function setVolume(vol:Number):void {
393                        streamVolume(vol);
394                        super.setVolume(vol);
395                }
396
397                /** Set the stream's volume, without sending a volume event **/
398                protected function streamVolume(level:Number):void {
399                        _transformer.volume = level / 100;
400                        if (_stream) {
401                                _stream.soundTransform = _transformer;
402                        }
403                }
404
405        }
406}
Note: See TracBrowser for help on using the repository browser.