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

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