Исправлена обработка зависания VLC.
[vpproxy.git] / vlcclient / vlcclient.py
1 '''
2 Minimal VLC VLM client for AceProxy. Client class.
3 '''
4
5 import gevent
6 import gevent.event
7 import gevent.coros
8 import telnetlib
9 import logging
10 from vlcmessages import *
11 import time
12
13 class VlcException(Exception):
14
15     '''
16     Exception from VlcClient
17     '''
18     pass
19
20
21 class VlcClient(object):
22
23     '''
24     VLC Client class
25     '''
26
27     def __init__(
28         self, host='127.0.0.1', port=4212, password='admin', connect_timeout=10,
29             result_timeout=10, out_port=8081):
30         # Receive buffer
31         self._recvbuffer = None
32         # Output port
33         self._out_port = out_port
34         # VLC socket
35         self._socket = None
36         # Result timeout
37         self._resulttimeout = result_timeout
38         # Shutting down flag
39         self._shuttingDown = gevent.event.Event()
40         # Authentication done event
41         self._auth = gevent.event.AsyncResult()
42         # Request lock
43         self._resultlock = gevent.coros.RLock()
44         # Request result
45         self._result = gevent.event.AsyncResult()
46         # VLC version string
47         self._vlcver = None
48         # Saving password
49         self._password = password
50
51         # Logger
52         logger = logging.getLogger('VlcClient_init')
53
54         # Streams
55         self.streams=dict()
56
57         # Making connection
58         try:
59             self._socket = telnetlib.Telnet(host, port, connect_timeout)
60             logger.info("Successfully connected with VLC socket!")
61         except Exception as e:
62             raise VlcException(
63                 "Socket creation error! VLC is not running? ERROR: " + repr(e))
64
65         # Spawning recvData greenlet
66         gevent.spawn(self._recvData)
67         gevent.sleep()
68
69         # Waiting for authentication event
70         try:
71             if self._auth.get(timeout=self._resulttimeout) == False:
72                 errmsg = "Authentication error"
73                 logger.error(errmsg)
74                 raise VlcException(errmsg)
75         except gevent.Timeout:
76             errmsg = "Authentication timeout"
77             logger.error(errmsg)
78             raise VlcException(errmsg)
79
80     def __del__(self):
81         # Destructor just calls destroy() method
82         self.destroy()
83
84     def destroy(self):
85         # Logger
86         logger = logging.getLogger("VlcClient_destroy")
87
88         if self._shuttingDown.isSet():
89             # Already in the middle of destroying
90             return
91
92         # If socket is still alive (connected)
93         if self._socket:
94             try:
95                 logger.info("Destroying VlcClient...")
96                 self._write(VlcMessage.request.SHUTDOWN)
97                 # Set shuttingDown flag for recvData
98                 self._shuttingDown.set()
99             except:
100                 # Ignore exceptions on destroy
101                 pass
102
103     def _write(self, message):
104
105         logger = logging.getLogger("VlcClient_write")
106
107         # Return if in the middle of destroying
108         if self._shuttingDown.isSet():
109             return
110
111         try:
112             # Write message
113             logger.info('VLC command: ' + message)
114             self._socket.write(message + "\r\n")
115         except EOFError as e:
116             raise VlcException("Vlc Write error! ERROR: " + repr(e))
117
118     def _broadcast(self, brtype, stream_name, input=None, muxer='ts', pre_access='', qtype='default'):
119         if self._shuttingDown.isSet():
120             return
121
122         # Start/stop broadcast with VLC
123         # Logger
124         if brtype == True:
125             broadcast = 'startBroadcast'
126         else:
127             broadcast = 'stopBroadcast'
128
129         logger = logging.getLogger("VlcClient_" + broadcast)
130         # Clear AsyncResult
131         self._result = gevent.event.AsyncResult()
132         # Get lock
133         self._resultlock.acquire()
134         # Write message to VLC socket
135         if brtype == True:
136             msg = VlcMessage.request.startBroadcast(stream_name, input, self._out_port, muxer, pre_access, qtype)
137             self._write(msg)
138         else:
139             if stream_name not in self.streams:
140                 self._resultlock.release()
141                 logger.error("Attempting to delete not existing stream %s" % stream_name)
142                 return
143             self._write(VlcMessage.request.stopBroadcast(stream_name))
144
145         try:
146             gevent.sleep()
147             result = self._result.get(timeout=self._resulttimeout)
148             if result == False:
149                 logger.error(broadcast + " error")
150                 raise VlcException(broadcast + " error")
151         except gevent.Timeout:
152             logger.error(broadcast + " result timeout")
153             raise VlcException(broadcast + " result timeout")
154         finally:
155             logger.info("working with %s stream: %s" % (stream_name,broadcast))
156             if brtype == True:
157                 self.streams[stream_name]=time.time()
158             else:
159                 del self.streams[stream_name]
160             self._resultlock.release()
161             logger.info("worked with %s stream: %s" % (stream_name,broadcast))
162
163         if brtype == True:
164             logger.info("Broadcast started")
165         else:
166             logger.info("Broadcast stopped")
167
168     def startBroadcast(self, stream_name, input, muxer='ts', pre_access='', qtype='default'):
169         logger = logging.getLogger("VlcClient_startBroadcast")
170         logger.debug("Starting broadcast......")
171         return self._broadcast(True, stream_name, input, muxer, pre_access, qtype)
172
173     def stopBroadcast(self, stream_name):
174         return self._broadcast(False, stream_name)
175
176     def mark(self,stream_name):
177       self.streams[stream_name]=time.time()
178
179     def clean_streams(self,timeout=15):
180       self._resultlock.acquire()
181       to_stop=set()
182       for stream,lasttime in self.streams.iteritems():
183         if time.time()-lasttime>timeout:
184           to_stop.add(stream)
185       for stream in to_stop:    
186         self.stopBroadcast(stream)
187       self._resultlock.release()
188
189     def check_stream(self,stream_name):
190       if stream_name in self.streams:
191         self.streams[stream_name]=time.time()
192         return True
193       else:
194         return False  
195
196     def pauseBroadcast(self, stream_name):
197         return self._write(VlcMessage.request.pauseBroadcast(stream_name))
198
199     def playBroadcast(self, stream_name):
200         return self._write(VlcMessage.request.playBroadcast(stream_name))
201
202     def _recvData(self):
203         # Logger
204         logger = logging.getLogger("VlcClient_recvData")
205
206         while True:
207             gevent.sleep()
208             try:
209                 self._recvbuffer = self._socket.read_until("\n")
210                 # Stripping "> " from VLC
211                 self._recvbuffer = self._recvbuffer.lstrip("> ")
212             except:
213                 # If something happened during read, abandon reader
214                 if not self._shuttingDown.isSet():
215                     logger.error("Exception at socket read")
216                     self._shuttingDown.set()
217                 return
218
219             # Parsing everything only if the string is not empty
220             if self._recvbuffer:
221                 if not self._vlcver:
222                     # First line (VLC version)
223                     self._vlcver = self._recvbuffer.strip()
224                     # Send password here since PASSWORD doesn't have \n
225                     self._write(self._password)
226
227                 elif self._recvbuffer.startswith(VlcMessage.response.SHUTDOWN):
228                     # Exit from this loop
229                     logger.debug("Got SHUTDOWN from VLC")
230                     return
231
232                 elif self._recvbuffer.startswith(VlcMessage.response.WRONGPASS):
233                     # Wrong password
234                     logger.error("Wrong VLC password!")
235                     self._auth.set(False)
236                     return
237
238                 elif self._recvbuffer.startswith(VlcMessage.response.AUTHOK):
239                     # Authentication OK
240                     logger.info("Authentication successful")
241                     self._auth.set(True)
242
243                 elif VlcMessage.response.BROADCASTEXISTS in self._recvbuffer:
244                     # Broadcast already exists
245                     logger.error("Broadcast already exists!")
246                     self._result.set(False)
247
248                 elif VlcMessage.response.STOPERR in self._recvbuffer:
249                     # Media unknown (stopping non-existent stream)
250                     logger.error("Broadcast does not exist!")
251                     self._result.set(False)
252
253                 # Do not move this before error handlers!
254                 elif self._recvbuffer.startswith(VlcMessage.response.STARTOK):
255                     # Broadcast started
256                     logger.info("Broadcast started")
257                     self._result.set(True)
258
259                 elif self._recvbuffer.startswith(VlcMessage.response.STOPOK):
260                     # Broadcast stopped
261                     logger.info("Broadcast stopped")
262                     self._result.set(True)