source: trunk/fl5/src/com/longtailvideo/jwplayer/media/RTMPMediaProvider.as @ 796

Revision 796, 11.5 KB checked in by pablo, 3 years ago (diff)
Line 
1/**
2 * Wrapper for playback of _video streamed over RTMP.
3 *
4 * All playback functionalities are cross-server (FMS, Wowza, Red5), with the exception of:
5 * - The SecureToken functionality (Wowza).
6 * - getStreamLength / checkBandwidth (FMS3).
7 **/
8package com.longtailvideo.jwplayer.media {
9        import com.jeroenwijering.events.*;
10        import com.longtailvideo.jwplayer.events.MediaEvent;
11        import com.longtailvideo.jwplayer.model.PlayerConfig;
12        import com.longtailvideo.jwplayer.model.PlaylistItem;
13        import com.longtailvideo.jwplayer.player.PlayerState;
14        import com.longtailvideo.jwplayer.utils.NetClient;
15        import com.longtailvideo.jwplayer.utils.TEA;
16       
17        import flash.events.*;
18        import flash.media.*;
19        import flash.net.*;
20        import flash.utils.*;
21       
22        public class RTMPMediaProvider extends MediaProvider {
23                /** Video object to be instantiated. **/
24                protected var _video:Video;
25                /** NetConnection object for setup of the video stream. **/
26                protected var _connection:NetConnection;
27                /** Loader instance that loads the XML file. **/
28                private var _loader:URLLoader;
29                /** NetStream instance that handles the stream IO. **/
30                protected var _stream:NetStream;
31                /** Sound control object. **/
32                protected var _transformer:SoundTransform;
33                /** Save the location of the XML redirect. **/
34                private var _smil:String;
35                /** ID for the position positionInterval. **/
36                protected var _positionInterval:Number;
37                /** Save that a file is unpublished. **/
38                protected var _unpublished:Boolean;
39                /** Whether the buffer has filled **/
40                private var _bufferFull:Boolean;
41                /** Save that the video has been started. **/
42                protected var _streamStarted:Boolean;
43                /** Whether the stream is active **/
44                private var _streamActive:Boolean;
45               
46                public function RTMPMediaProvider() {
47                        super('rtmp');
48                }
49
50
51                /** Constructor; sets up the connection and display. **/
52                public override function initializeMediaProvider(cfg:PlayerConfig):void {
53                        super.initializeMediaProvider(cfg);
54                        _connection = new NetConnection();
55                        _connection.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
56                        _connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
57                        _connection.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
58                        _connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
59                        _connection.objectEncoding = ObjectEncoding.AMF0;
60                        _connection.client = new NetClient(this);
61                        _loader = new URLLoader();
62                        _loader.addEventListener(Event.COMPLETE, loaderHandler);
63                        _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
64                        _loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
65                        _video = new Video(320, 240);
66                        _video.smoothing = config.smoothing;
67                        _transformer = new SoundTransform();
68                }
69
70
71                /** Catch security errors. **/
72                protected function errorHandler(evt:ErrorEvent):void {
73                        error(evt.text);
74                }
75
76
77                /** Extract the correct rtmp syntax from the file string. **/
78                protected function getID(url:String):String {
79                        var ext:String = url.substr(-4);
80                        if (ext == '.mp3') {
81                                return 'mp3:' + url.substr(0, url.length - 4);
82                        } else if (ext == '.mp4' || ext == '.mov' || ext == '.aac' || ext == '.m4a' || ext == '.f4v' || ext == '.m4v') {
83                                return 'mp4:' + url;
84                        } else if (ext == '.flv') {
85                                return url.substr(0, url.length - 4);
86                        } else {
87                                return url;
88                        }
89                }
90
91
92                /** Load content. **/
93                override public function load(itm:PlaylistItem):void {
94                        _item = itm;
95                        _position = 0;
96                        _bufferFull = false;
97                        _streamStarted = false;
98                        _streamActive = false;
99                        setState(PlayerState.BUFFERING);
100                        sendBufferEvent(0);
101                        if (getConfigProperty('loadbalance') as Boolean == true) {
102                                _smil = item.file;
103                                _loader.load(new URLRequest(_smil));
104                        } else {
105                                finishLoad();
106                        }
107                }
108
109
110                /** Get the streamer / file from the loadbalancing XML. **/
111                private function loaderHandler(evt:Event):void {
112                        var xml:XML = XML(evt.currentTarget.data);
113                        item.streamer = xml.children()[0].children()[0].@base.toString();
114                        item.file = xml.children()[1].children()[0].@src.toString();
115                        finishLoad();
116                }
117
118
119                /** Finalizes the loading process **/
120                private function finishLoad():void {
121                        var ext:String = item.file.substr(-4);
122                        if (ext == '.mp3'){
123                                media = null;
124                        } else if (!media) {
125                                media = _video;
126                        }
127                        _connection.connect(item.streamer);
128                        config.mute == true ? setVolume(0) : setVolume(config.volume);
129                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
130                }
131
132
133                /** Get metadata information from netstream class. **/
134                public function onData(dat:Object):void {
135                        if (dat.width) {
136                                _video.width = dat.width;
137                                _video.height = dat.height;
138                                resize(_width, _height);
139                        }
140                        if (dat.duration && item.duration <= 0) {
141                                item.duration = dat.duration;
142                        }
143                        if (dat.type == 'complete') {
144                                complete();
145                        } else if (dat.type == 'close') {
146                                stop();
147                        }
148                        if (config.ignoremeta != true) {
149                                sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: dat});
150                        }
151                }
152
153               
154                /** Determines if the stream is a live stream **/
155                private function get livestream():Boolean {
156                        // We assume it's a livestream until we hear otherwise.
157                        return !(item.duration > 0);
158                }
159
160                /** Pause playback. **/
161                override public function pause():void {
162                        _streamActive = false;
163                        _stream.pause();
164                        clearInterval(_positionInterval);
165                        _positionInterval = undefined;
166                        super.pause();
167                        if (livestream) {
168                                stop();
169                        }
170                }
171
172
173                /** Resume playing. **/
174                override public function play():void {
175                        /*
176                        * Livestreams will reset their buffer if _stream.resume is called,
177                        * so we suppress them after the intial call
178                        */
179                        if (!livestream && !_streamActive) {
180                                _streamActive = true;
181                                _stream.resume();
182                        }
183                        if (!_streamStarted) {
184                                _streamStarted = true;
185                                if (!livestream) {
186                                        seek(item.start);
187                                }
188                        }
189                       
190                        if (!_positionInterval) {
191                                _positionInterval = setInterval(positionInterval, 100);
192                        }
193                        super.play();
194                }
195
196
197                /** Interval for the position progress. **/
198                protected function positionInterval():void {
199                        _position = Math.round(_stream.time * 10) / 10;
200
201                        var bfr:Number;
202                        if (!livestream) {
203                                var bufferTime:Number = _stream.bufferTime < (item.duration - position) ? _stream.bufferTime : (item.duration - position);
204                                bfr = Math.round(_stream.bufferLength / bufferTime * 100);
205                        } else if (_stream.bufferLength > 0) {
206                                bfr = Math.round(_stream.bufferLength / _stream.bufferTime * 100);
207                        } else if (_streamActive){
208                                bfr = 100;
209                        }
210                       
211                       
212                        if (bfr < 95 && position < Math.abs(item.duration - _stream.bufferTime - 1)) {
213                                if (state == PlayerState.PLAYING && bfr < 20) {
214                                        _bufferFull = false;
215                                        _streamActive = false;
216                                        _stream.pause();
217                                        setState(PlayerState.BUFFERING);
218                                        _stream.bufferTime = config.bufferlength;
219                                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {bufferlength: config.bufferlength}});
220                                }
221                        } else if (bfr > 95 && state == PlayerState.BUFFERING) {
222                                //_stream.bufferTime = config.bufferlength * 4;
223                                //sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {bufferlength: config.bufferlength * 4}});
224                                if (!_bufferFull){
225                                        _bufferFull = true;
226                                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
227                                }
228                        }
229
230                        if (state == PlayerState.BUFFERING || state == PlayerState.PAUSED) {
231                                //TODO: This works, but it looks weird, as the bufferTime is changing
232                                /*
233                                if (!_bufferingComplete){
234                                        sendBufferEvent(_stream.bufferLength / _stream.bufferTime * item.duration);
235                                }
236                                */
237                        } else if (position < item.duration) {
238                                if (state == PlayerState.PLAYING && position >= 0) {
239                                        //TODO: This works, but it looks weird, as the bufferTime is changing
240                                        //sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: position, duration: item.duration, bufferLength: _stream.bufferLength / _stream.bufferTime * item.duration});
241                                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: position, duration: item.duration});
242                                }
243                        } else if (!isNaN(position) && item.duration > 0) {
244                                complete();
245                        }
246                }
247
248
249                /** Seek to a new position. **/
250                override public function seek(pos:Number):void {
251                        _position = pos;
252                        clearInterval(_positionInterval);
253                        _positionInterval = undefined;
254                        _stream.seek(position);
255                        if (!_positionInterval) {
256                                _positionInterval = setInterval(positionInterval, 100);
257                        }
258                }
259
260
261                /** Start the netstream object. **/
262                protected function setStream():void {
263                        _stream = new NetStream(_connection);
264                        _stream.checkPolicyFile = true;
265                        _stream.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
266                        _stream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
267                        _stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
268                        _stream.bufferTime = config.bufferlength;
269                        _stream.client = new NetClient(this);
270                        _video.attachNetStream(_stream);
271                        if (!_positionInterval) {
272                                _positionInterval = setInterval(positionInterval, 100);
273                        }
274                        _stream.play(getID(item.file));
275                }
276
277
278                /** Receive NetStream status updates.
279                 *
280                 * For more info see http://help.adobe.com/en_US/Flex/4.0/langref/flash/events/NetStatusEvent.html#info
281                 **/
282                protected function statusHandler(evt:NetStatusEvent):void {
283                        switch (evt.info.code) {
284                                case 'NetConnection.Connect.Success':
285                                        if (evt.info.secureToken != undefined) {
286                                                _connection.call("secureTokenResponse", null, TEA.decrypt(evt.info.secureToken, config.token));
287                                        }
288                                        setStream();
289                                        var res:Responder = new Responder(streamlengthHandler);
290                                        _connection.call("getStreamLength", res, getID(item.file));
291                                        _connection.call("checkBandwidth", null);
292                                        break;
293                                case 'NetStream.Play.Start':
294                                        break;
295                                case 'NetStream.Seek.Notify':
296                                        clearInterval(_positionInterval);
297                                        _positionInterval = undefined;
298                                        if (!_positionInterval) {
299                                                _positionInterval = setInterval(positionInterval, 100);
300                                        }
301                                        break;
302                                case 'NetConnection.Connect.Rejected':
303                                        try {
304                                                if (evt.info.ex.code == 302) {
305                                                        item.streamer = evt.info.ex.redirect;
306                                                        setTimeout(load, 100, item);
307                                                        return;
308                                                }
309                                        } catch (err:Error) {
310                                                var msg:String = evt.info.code;
311                                                if (evt.info['description']) {
312                                                        msg = evt.info['description'];
313                                                }
314                                                error(msg);
315                                        }
316                                        break;
317                                case 'NetStream.Failed':
318                                case 'NetStream.Play.StreamNotFound':
319                                        if (_unpublished) {
320                                                onData({type: 'complete'});
321                                                _unpublished = false;
322                                        } else {
323                                                error("Stream not found: " + item.file);
324                                        }
325                                        break;
326                                case 'NetConnection.Connect.Failed':
327                                        error("Server not found: " + item.streamer);
328                                        break;
329                                case 'NetStream.Play.UnpublishNotify':
330                                        _unpublished = true;
331                                        break;
332                                case 'NetStream.Buffer.Full':
333                                        if (!_bufferFull) {
334                                                _bufferFull = true;
335                                                sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
336                                        }
337                                        break;
338                                case 'NetStream.Play.Reset':
339                                        break;
340                        }
341                        sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: evt.info});
342                }
343
344
345                /** Destroy the _stream. **/
346                override public function stop():void {
347                        _streamStarted = false;
348                        _streamActive = false;
349                        if (_stream && _stream.time) {
350                                _stream.close();
351                        }
352                        _connection.close();
353                        clearInterval(_positionInterval);
354                        _positionInterval = undefined;
355                        super.stop();
356                        if (_smil) {
357                                item.file = _smil;
358                        }
359                }
360
361
362                /** Get the streamlength returned from the _connection. **/
363                private function streamlengthHandler(len:Number):void {
364                        if (len > 0) {
365                                onData({type: 'streamlength', duration: len});
366                        }
367                }
368
369
370                /** Set the volume level. **/
371                override public function setVolume(vol:Number):void {
372                        _transformer.volume = vol / 100;
373                        if (_stream) {
374                                try {
375                                        _stream.soundTransform = _transformer;
376                                        super.setVolume(vol);
377                                } catch (err:Error) {
378
379                                }
380                        }
381                }
382        }
383}
Note: See TracBrowser for help on using the repository browser.