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

Revision 826, 11.4 KB checked in by pablo, 3 years ago (diff)
  • Clean up for locking scenarios
  • MediaEvent.JWPLAYER_MEDIA_VOLUME event not sent on every item load (only sent in response to a setVolume() call)
  • DisplayComponent dispatches PLAY and PAUSE events, in addition to DISPLAY_CLICK
  • PlaylistComponent dispatches ITEM events
  • Debug options visible in right-click menu when "debug" flashvar is set, even when using the non-debug Flash Player
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 _bandwidthTimeout:Number = 2000;
52               
53                /** Constructor; sets up the connection and display. **/
54                public function HTTPMediaProvider() {
55                        super('http');
56                }
57
58
59                public override function initializeMediaProvider(cfg:PlayerConfig):void {
60                        super.initializeMediaProvider(cfg);
61                        _connection = new NetConnection();
62                        _connection.connect(null);
63                        _stream = new NetStream(_connection);
64                        _stream.checkPolicyFile = true;
65                        _stream.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
66                        _stream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
67                        _stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
68                        _stream.bufferTime = config.bufferlength;
69                        _stream.client = new NetClient(this);
70                        _transformer = new SoundTransform();
71                        _video = new Video(320, 240);
72                        _video.smoothing = config.smoothing;
73                        _video.attachNetStream(_stream);
74                }
75
76
77                /** Convert seekpoints to keyframes. **/
78                protected function convertSeekpoints(dat:Object):Object {
79                        var kfr:Object = new Object();
80                        kfr.times = new Array();
81                        kfr.filepositions = new Array();
82                        for (var j:String in dat) {
83                                kfr.times[j] = Number(dat[j]['time']);
84                                kfr.filepositions[j] = Number(dat[j]['offset']);
85                        }
86                        return kfr;
87                }
88
89                /** Catch security errors. **/
90                protected function errorHandler(evt:ErrorEvent):void {
91                        error(evt.text);
92                }
93
94                /** Bandwidth is checked as long the stream hasn't completed loading. **/
95                private function checkBandwidth(lastLoaded:Number):void {
96                        var currentLoaded:Number = _stream.bytesLoaded;
97                        var bandwidth:Number = Math.ceil((currentLoaded - lastLoaded) / 1024) * 8 / (_bandwidthTimeout / 1000);
98                       
99                        if (currentLoaded < _stream.bytesTotal) {
100                                if (bandwidth > 0) {
101                                        config.bandwidth = bandwidth;
102                                        var obj:Object = {bandwidth:bandwidth};
103                                        if (item.duration > 0) {
104                                                obj.bitrate = Math.ceil(_stream.bytesTotal / 1024 * 8 / item.duration);
105                                        }
106                                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: obj});
107                                }
108                                if (_bandwidthSwitch) {
109                                        _bandwidthSwitch = false;
110                                        if (item.currentLevel != item.getLevel(config.bandwidth, config.width)) {
111                                                load(item);
112                                                return;
113                                        }
114                                }
115                                setTimeout(checkBandwidth, _bandwidthTimeout, currentLoaded);
116                        }
117                }
118               
119                /** Return a keyframe byteoffset or timeoffset. **/
120                protected function getOffset(pos:Number, tme:Boolean=false):Number {
121                        if (!_keyframes) {
122                                return 0;
123                        }
124                        for (var i:Number = 0; i < _keyframes.times.length - 1; i++) {
125                                if (_keyframes.times[i] <= pos && _keyframes.times[i + 1] >= pos) {
126                                        break;
127                                }
128                        }
129                        if (tme == true) {
130                                return _keyframes.times[i];
131                        } else {
132                                return _keyframes.filepositions[i];
133                        }
134                }
135
136
137                /** Create the video request URL. **/
138                protected function getURL():String {
139                        var url:String = item.file;
140                        var off:Number = _byteoffset;
141                        if (getConfigProperty('startparam') as String) {
142                                _startparam = getConfigProperty('startparam');
143                        }
144                        if (item.streamer) {
145                                if (item.streamer.indexOf('/') > 0) {
146                                        url = item.streamer;
147                                        url = getURLConcat(url, 'file', item.file);
148                                } else {
149                                        _startparam = item.streamer;
150                                }
151                        }
152                        if (_mp4 || _startparam == 'starttime') {
153                                off = _timeoffset;
154                        }
155                        if (!_mp4 || off > 0) {
156                                url = getURLConcat(url, _startparam, off);
157                        }
158                        if (config['token'] || item['token']) {
159                                url = getURLConcat(url, 'token', item['token'] ? item['token'] : config['token']);
160                        }
161                        return url;
162                }
163
164
165                /** Concatenate a parameter to the url. **/
166                private function getURLConcat(url:String, prm:String, val:*):String {
167                        if (url.indexOf('?') > -1) {
168                                return url + '&' + prm + '=' + val;
169                        } else {
170                                return url + '?' + prm + '=' + val;
171                        }
172                }
173
174
175                /** Load content. **/
176                override public function load(itm:PlaylistItem):void {
177                        _item = itm;
178                        _position = _timeoffset;
179                        _bufferFull = false;
180                        _bufferingComplete = false;
181                        _bandwidthChecked = false;
182                        _bandwidthSwitch = true;
183                       
184                        if (item.levels.length > 0) { item.setLevel(item.getLevel(config.bandwidth, config.width)); }
185                       
186                        if (_stream.bytesLoaded + _byteoffset < _stream.bytesTotal) {
187                                _stream.close();
188                        }
189                        media = _video;
190                        _stream.play(getURL());
191                       
192                        clearInterval(_positionInterval);
193                        _positionInterval = setInterval(positionInterval, 100);
194                       
195                        setState(PlayerState.BUFFERING);
196                        sendBufferEvent(0, 0);
197                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
198                        streamVolume(config.mute ? 0 : config.volume);
199                }
200
201                /** Get metadata information from netstream class. **/
202                public function onClientData(dat:Object):void {
203                        if (dat.width) {
204                                _video.width = dat.width;
205                                _video.height = dat.height;
206                                resize(_width, _height);
207                        }
208                        if (dat['duration'] && item.duration <= 0) {
209                                item.duration = dat['duration'];
210                        }
211                        if (dat['type'] == 'metadata' && !_meta) {
212                                _meta = true;
213                                if (dat['seekpoints']) {
214                                        _mp4 = true;
215                                        _keyframes = convertSeekpoints(dat['seekpoints']);
216                                } else {
217                                        _mp4 = false;
218                                        _keyframes = dat['keyframes'];
219                                }
220                                if (item.start > 0) {
221                                        seek(item.start);
222                                }
223                        }
224                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: dat});
225                }
226
227
228                /** Pause playback. **/
229                override public function pause():void {
230                        _stream.pause();
231                        super.pause();
232                }
233
234
235                /** Resume playing. **/
236                override public function play():void {
237                        _stream.resume();
238                        if (!_positionInterval) {
239                                _positionInterval = setInterval(positionInterval, 100);
240                        }
241                        super.play();
242                }
243
244
245                /** Interval for the position progress **/
246                protected function positionInterval():void {
247                        _position = Math.round(_stream.time * 10) / 10;
248                        var percentoffset:Number;
249                        if (_mp4) {
250                                _position += _timeoffset;
251                        }
252                       
253                        var bufferPercent:Number;
254                        var bufferFill:Number;
255                        if (item.duration > 0) {
256                                percentoffset =  Math.round(_timeoffset /  item.duration * 100);
257                                bufferPercent = (_stream.bytesLoaded / _stream.bytesTotal) * (1 - _timeoffset / item.duration) * 100;
258                                var bufferTime:Number = _stream.bufferTime < (item.duration - position) ? _stream.bufferTime : Math.round(item.duration - position);
259                                bufferFill = _stream.bufferTime == 0 ? 0 : Math.ceil(_stream.bufferLength / bufferTime * 100);
260                        } else {
261                                percentoffset = 0;
262                                bufferPercent = 0;
263                                bufferFill = _stream.bufferLength/_stream.bufferTime * 100;
264                        }
265       
266                        if (!_bandwidthChecked && _stream.bytesLoaded > 0 && _stream.bytesLoaded < _stream.bytesTotal) {
267                                _bandwidthChecked = true;
268                                setTimeout(checkBandwidth, _bandwidthTimeout, _stream.bytesLoaded);
269                        }
270                       
271                        if (bufferFill < 25 && state == PlayerState.PLAYING) {
272                                _bufferFull = false;
273                                _stream.pause();
274                                setState(PlayerState.BUFFERING);
275                        } else if (bufferFill > 95 && state == PlayerState.BUFFERING && _bufferFull == false) {
276                                _bufferFull = true;
277                                sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
278                        }
279
280                        if (!_bufferingComplete) {
281                                if ((bufferPercent + percentoffset) == 100 && _bufferingComplete == false) {
282                                        _bufferingComplete = true;
283                                }
284                                sendBufferEvent(bufferPercent, _timeoffset);
285                        }
286                       
287                        if (_position < item.duration) {
288                                if (state == PlayerState.PLAYING && _position >= 0) {
289                                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: _position, duration: item.duration, offset: _timeoffset});
290                                }
291                        } else if (item.duration > 0) {
292                                // Playback completed
293                                complete();
294                        }
295                }
296
297                /** Handle a resize event **/
298                override public function resize(width:Number, height:Number):void {
299                        super.resize(width, height);
300                        if (item.levels.length > 0 && item.getLevel(config.bandwidth, config.width) != item.currentLevel) {
301                                _byteoffset = getOffset(position);
302                                _timeoffset = _position = getOffset(position,true);
303                                load(item);
304                        }
305                }
306
307                /** Seek to a specific second. **/
308                override public function seek(pos:Number):void {
309                        var off:Number = getOffset(pos);
310                        super.seek(pos);
311                        clearInterval(_positionInterval);
312                        _positionInterval = undefined;
313                        if (off < _byteoffset || off >= _byteoffset + _stream.bytesLoaded) {
314                                _timeoffset = _position = getOffset(pos, true);
315                                _byteoffset = off;
316                                load(item);
317                        } else {
318                                if (state == PlayerState.PAUSED) {
319                                        _stream.resume();
320                                }
321                                if (_mp4) {
322                                        _stream.seek(getOffset(_position - _timeoffset, true));
323                                } else {
324                                        _stream.seek(getOffset(_position, true));
325                                }
326                                play();
327                        }
328                }
329
330
331                /** Receive NetStream status updates. **/
332                protected function statusHandler(evt:NetStatusEvent):void {
333                        switch (evt.info.code) {
334                                case "NetStream.Play.Stop":
335                                        if (state != PlayerState.BUFFERING) {
336                                                complete();
337                                        }
338                                        break;
339                                case "NetStream.Play.StreamNotFound":
340                                        stop();
341                                        error('Video not found: ' + item.file);
342                                        break;
343                                case 'NetStream.Buffer.Full':
344                                        if (!_bufferFull) {
345                                                _bufferFull = true;
346                                                sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
347                                        }
348                                        break;
349                        }
350                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {status: evt.info.code}});
351                }
352
353
354                /** Destroy the HTTP stream. **/
355                override public function stop():void {
356                        if (_stream.bytesLoaded + _byteoffset < _stream.bytesTotal) {
357                                _stream.close();
358                        } else {
359                                _stream.pause();
360                        }
361                        clearInterval(_positionInterval);
362                        _positionInterval = undefined;
363                        _position = _byteoffset = _timeoffset = 0;
364                        _keyframes = undefined;
365                        _meta = false;
366                        super.stop();
367                }
368
369
370                /** Set the volume level. **/
371                override public function setVolume(vol:Number):void {
372                        streamVolume(vol);
373                        super.setVolume(vol);
374                }
375
376                /** Set the stream's volume, without sending a volume event **/
377                protected function streamVolume(level:Number):void {
378                        _transformer.volume = level / 100;
379                        if (_stream) {
380                                _stream.soundTransform = _transformer;
381                        }
382                }
383
384        }
385}
Note: See TracBrowser for help on using the repository browser.