RTMPCluster.as 9.31 KB
Newer Older
Anssi Piirainen's avatar
Anssi Piirainen committed
1
2
3
4
5
6
7
8
9
10
11
12
/*
 * This file is part of Flowplayer, http://flowplayer.org
 *
 * By: Daniel Rossi, <electroteque@gmail.com>, Anssi Piirainen Flowplayer Oy
 * Copyright (c) 2008-2011 Flowplayer Oy
 *
 * Released under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 */

package org.flowplayer.cluster
{
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    import flash.events.TimerEvent;
    import flash.net.SharedObject;
    import flash.utils.Timer;

    //import mx.utils.URLUtil;

    import org.flowplayer.flow_internal;
    //import org.flowplayer.model.PluginModelImpl;
    import org.flowplayer.util.Log;

    import org.flowplayer.util.URLUtil;

    use namespace flow_internal;

    public class RTMPCluster {
        protected var _hosts:Array;
        protected var _timer:Timer;
        protected var _hostIndex:int = 0;
        //protected var _hostCount:int = 0;
        protected var _connectCount:int = 0;
        protected var _reConnectCount:int = 0;
        protected var _connectTimeout:int = 2000;
        //protected var _loadBalanceServers:Boolean = false;
        protected var _liveHosts:Array;
        //protected var _liveRandomServers:Array = [];
        //private var _startAfterConnect:Boolean;
        protected var _failureExpiry:int = 0;
Anssi Piirainen's avatar
Anssi Piirainen committed
40
        protected var _reconnectFailureExpiry:int = 0;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
        private var _config:*;
        //private var _dispatcher:PluginModelImpl;
        private var log:Log = new Log(this);
        private var _reconnectListener:Function;
        private var _failureListener:Function;
        private var _currentHost:Object;


        public function RTMPCluster(config:*)
        {
            _config = config;
            // there can be several hosts, or we are just using one single netConnectionUrl
            initHosts(_config.hosts, config.netConnectionUrl);

            _connectCount = config.connectCount;
            _connectTimeout = config.connectTimeout;
            _failureExpiry = config.failureExpiry;
            _currentHost = _hosts ? _hosts[0] : null;
Anssi Piirainen's avatar
Anssi Piirainen committed
59
60
        }

61
62
63
64
65
66
67
68
69
70
71
72
73
        private function initHosts(hosts:Array, fallback:String):void {
            log.debug("initHosts()");
            var myHosts:Array = [];
            if (! hosts || hosts.length == 0) {
                if (! fallback) {
                    throw new Error("A hosts array or a netConnectionUrl must be configured");
                }
                myHosts.push({ 'host': fallback});
            } else {
                for (var i:int = 0; i < hosts.length; i++) {
                    myHosts.push(hosts[i] is String ? { 'host': hosts[i] } : hosts[i]);
                }
            }
Anssi Piirainen's avatar
Anssi Piirainen committed
74

75
76
77
78
            _hosts = myHosts;
            _liveHosts = _hosts;
            log.debug("initHosts(), we have " + _liveHosts.length + " live hosts initially");
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
79
80


81
82
83
        public function onReconnected(listener:Function):void {
            _reconnectListener = listener;
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
84

85
86
87
        public function onFailed(listener:Function):void {
            _failureListener = listener;
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
88

89
90
91
92
        public function get currentHosts():Array
        {
            return _hosts.filter(_checkLiveHost);
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
93

94
        public function get hosts():Array
Anssi Piirainen's avatar
Anssi Piirainen committed
95
        {
96
97
98
            return _hosts;
        }

99
100
101
102
103
104
105
106
107
108
        public function get hostIndex():int
        {
            return _hostIndex;
        }

        public function updateCurrentHost(host:String):void
        {
            _hosts[_hostIndex] = {host: host};
        }

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
        public function get nextHost():String
        {
            if (hasMultipleHosts())
            {
                _liveHosts = currentHosts;
                if (_liveHosts.length == 0) {
                    log.error("no live hosts available");
                    if (_failureListener != null) {
                        _failureListener();
                        return null;
                    }
                }
                if (_config.loadBalance)
                {
                    _hostIndex = getRandomIndex();
                    log.debug("Load balanced index " + _hostIndex);
                }
                if (_liveHosts.length > _hostIndex) {
                    log.debug("cluster has multiple hosts");
                    _currentHost = _liveHosts[_hostIndex];
                    return _currentHost["host"];
Anssi Piirainen's avatar
Anssi Piirainen committed
130
131
                }
            }
132
133
134
135
136
137
138
139
            log.error("no hosts available");
            return null;
        }

        public function start():void
        {
            if (_timer && _timer.running) {
                _timer.stop();
Anssi Piirainen's avatar
Anssi Piirainen committed
140
            }
141
142
143
144
145
            _timer = new Timer(_connectTimeout, _liveHosts.length);
            _timer.addEventListener(TimerEvent.TIMER , tryFallBack);
            if (hasMultipleHosts()) {
                log.debug("starting connection timeout timer, with a delay of " + _connectTimeout);
                _timer.start();
Anssi Piirainen's avatar
Anssi Piirainen committed
146
147
            }
        }
148
149
150
151
152
153
154
155
156

        public function stop():void
        {
            if (_timer != null && _timer.running) _timer.stop();
        }

        public function hasMultipleHosts():Boolean
        {
            return _liveHosts.length > 0;
Anssi Piirainen's avatar
Anssi Piirainen committed
157
        }
158
159
160

        public function getRandomIndex():uint {
            return Math.round(Math.random() * (_liveHosts.length - 1));
Anssi Piirainen's avatar
Anssi Piirainen committed
161
        }
162
163

        public function get liveServers():Array
Anssi Piirainen's avatar
Anssi Piirainen committed
164
        {
165
166
            return _liveHosts;
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
167

168
169
170
        private function _checkLiveHost(element:*, index:int, arr:Array):Boolean
        {
            return _isLiveServer(element);
Anssi Piirainen's avatar
Anssi Piirainen committed
171
        }
172
173

        private function _getFailedServerSO(host:String):SharedObject
Anssi Piirainen's avatar
Anssi Piirainen committed
174
        {
175
176
177
178
179
180
181
182
            return SharedObject.getLocal(getDomain(host),"/");
        }

        private function getDomain(url:String):String {
            var schemeEnd:int = url.indexOf("//") + 2;
            var domain:String = url.substr(schemeEnd);
            var endPos:int = domain.indexOf("/");
            return domain.substr(0, endPos);
183
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
184

185
186
187
188
189
190
        public function setFailedServer(host:String):void
        {
            log.debug("Setting Failed Server: " + host);
            var server:SharedObject = _getFailedServerSO(host);
            server.data.failureTimestamp = new Date();
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
191

192
193
194
195
196
197
198
199
200
201
202
203
204
        public function set connectCount(count:Number):void
        {
            _connectCount = count;
        }

        internal function hasMoreHosts():Boolean
        {
            if (_failureExpiry == 0)
                _hostIndex++
            else
                _hostIndex = 0;

            _liveHosts = currentHosts;
Anssi Piirainen's avatar
Anssi Piirainen committed
205

206
            if (_hostIndex >= _liveHosts.length)
Anssi Piirainen's avatar
Anssi Piirainen committed
207
            {
208
209
210
211
212
213
214
215
216
217
218
219
220
221
                //
                _reConnectCount++;
                if (_reConnectCount < _connectCount)
                {
                    log.debug("Restarting Connection Attempts");
                    _hostIndex = 0;
                        //#427 when reconnecting to the max reconnect count, clear the failure expiry and reset the live hosts to enable to try again ?
                        _reconnectFailureExpiry = 0;
                        _liveHosts = currentHosts;

                }
                } else {
                    //#427 set the normal failure expiry during retries.
                    _reconnectFailureExpiry = _failureExpiry;
Anssi Piirainen's avatar
Anssi Piirainen committed
222
            }
223
224
            log.debug("Host Index: " + _hostIndex + " LiveServers: " + _liveHosts.length);
            return (_hostIndex <= _liveHosts.length && _liveHosts[_hostIndex]);
Anssi Piirainen's avatar
Anssi Piirainen committed
225
226
        }

227
        private function _isLiveServer(element:*):Boolean
Anssi Piirainen's avatar
Anssi Piirainen committed
228
        {
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
            var host:String = element.host;
            var server:SharedObject = _getFailedServerSO(host);
            // Server is failed, determine if the failure expiry interval has been reached and clear it
            if (server.data.failureTimestamp)
            {
                var date:Date = new Date();

                // Determine the failure offset
                var offset:int = date.getTime() - server.data.failureTimestamp.getTime();

                log.debug("Failed Server Remaining Expiry: " + offset + " Start Time: " + server.data.failureTimestamp.getTime() + " Current Time: " + date.getTime());

                // Failure offset has reached the failureExpiry setting, clear it from the list to allow a connection
                    //#427 failure expiry was not being reset to honour connect retry.
                    if (offset >= _reconnectFailureExpiry && _reConnectCount < _connectCount)
                {
                    log.debug("Clearing Failure Period " + _config.failureExpiry);
                    server.clear();
                    return true;
                }
                return false;
Anssi Piirainen's avatar
Anssi Piirainen committed
250
            }
251
252
            return true;
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
253

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
        protected function tryFallBack(e:TimerEvent):void
        {
            // Check if there is more hosts to attempt reconnection to
            if (hasMoreHosts())
            {
                log.debug("invoking reconnect listener");
                if (_reconnectListener != null) {
                    _reconnectListener();
                }

            } else {
                // we have reached the end of the hosts list stop reconnection attempts and send a failed event
                stop();
                if (_failureListener != null) {
                    _failureListener();
                }
Anssi Piirainen's avatar
Anssi Piirainen committed
270
271
272
            }
        }

273
274
275
        public function get currentHost():String {
            return _currentHost["host"];
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
276

277
278
279
        public function get currentHostIndex():int {
            return _hosts.indexOf(_currentHost);
        }
Anssi Piirainen's avatar
Anssi Piirainen committed
280
281
282
283
}


}