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

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