def recibeVideo(self):
		connection = Application.getInstance().getConnection()
		while not self.closedInterface.is_set() and connection != None:
			start = time.time()
			frameTime = 1/self.fpsOutput

			if connection.isRunning():
				try: 
					msg = connection.rcv()
					frame = msg['image']
					self.fpsInput = msg['fps']
					self.inputResolution = msg['resolution']
					img_tk = ImageTk.PhotoImage(frame)
					# Lo mostramos en el GUI
					self.app.setImageData('receivedVideoImage', img_tk, fmt = 'PhotoImage')
					self.app.setImageWidth('receivedVideoImage', self.inputResolution[0])
					self.app.setImageHeight('receivedVideoImage', self.inputResolution[1])

					self.app.setStatusbar('Recibiendo a {}x{}/{} fps.'.format(self.inputResolution[0], self.inputResolution[1], msg['fps']), field=0)
					self.app.setStatusbar('Retraso de {:+.3f} segundos.'.format(time.time() - msg['timestamp']), field=1)
					self.app.setStatusbar('Duracion de la llamada: {} segundos'.format(utils.timedeltaToStr(time.time() - self.timeCallStarted)), field=2)

				except BlockingIOError as e:
					# No recibimos datos
					self.app.setImage('receivedVideoImage', 'imgs/user_icon.gif')
				except TypeError as e:
					# timeCallStarted es None por haber sido cambiado en otro hilo al cerrarse la llamada
					pass

			# Pausamos el tiempo necesario para cuadrar los fps
			pauseTime = frameTime + start - time.time() # frameTime - (time.time() - start)
			if pauseTime < 0:
				continue
			time.sleep(pauseTime)
			connection = Application.getInstance().getConnection()
	def login(self, button):
		# Nos registramos / logeamos en el servidor de descubrimiento
		password = self.app.getEntry('loginPasswordEntry')
		if not password:
			self.notifyLogin('Es necesario que introduzcas una contraseña.')
		
		self.notifyLogin('Consultando información con el servidor.')
		# Deshabilitamos los botones mientras consultamos en el servidor para evitar 
		# posibles errores al pulsar varias veces
		self.app.queueFunction(self.app.disableButton, 'Register user')
		self.app.queueFunction(self.app.disableButton, 'Login')

		user = getUserInfo()
		if user == None:
			self.notifyLogin('Ha habido un error, el usuario no esta almacenado. Es necesario que se registre')
			self.app.queueFunction(self.app.hideLabelFrame, 'Login')
			return
			
		tmp = identityGestion.registerUser(user.nick, user.ip, user.port, password, '#'.join(user.protocols))
		if tmp != None:
			# La contraseña es correcta, acabamos
			Application.getInstance().setUser(tmp)
			Application.getInstance().startControlConnection(tmp.ip, tmp.port)
			# La conexión de control se encarga de mostrar o no la pantalla principal
		else:
			# Volvemos a habilitar los botones
			self.app.queueFunction(self.app.enableButton, 'Register user')
			self.app.queueFunction(self.app.enableButton, 'Login')
			self.notifyLogin('La contraseña es incorrecta o el usuario ya no existe.')
	def stop(self, signal=None, _=None):
		# Al hacer sys.exit(0), es posible que el manejador de Ctrl+C llame a la funcion
		# En ese caso, ya se habrá cerrado todo. Así evitamos fallos.
		if self.closedInterface.is_set():
			return True

		print('Cerramos la aplicacion')
		self.closedInterface.set()
		try:
			# Dejamos tiempo para que los hilos de mostrar video acaben
			self.notify('Cerrando la aplicación.')
			time.sleep(1)

			# Liberamos el archivo de video usado
			if not self.usingCamera.is_set():
				self.cap.release()

			connection = Application.getInstance().getConnection()
			if connection != None:
				user = connection.dstUser
				# Colgamos la llamada con el usuario
				self.notify('Finalizando la llamada.')
				Application.getInstance().getControlConnection().endCall()
				self.closeCallWindow()

			self.notify('Cerrando la conexión de control.')
			Application.getInstance().getControlConnection().quit()
			self.app.queueFunction(sys.exit, 0)
			return True
		except Exception as e:
			self.logger.exception('VideoClient - Error al cerrar: '+str(e))
			self.app.queueFunction(sys.exit, 0)
	def buttonsCallback(self, button):
		self.logger.debug('VideoClient - Pressed {} button.'.format(button))
		if button == 'Salir':
			# Salimos de la aplicación
			self.app.thread(self.stop)

		if button == 'Colgar':
			# Avisamos a la conexion de control, que se encarga automáticamente de cerrar la conexion
			# Como el proceso de detener la conexion puede tardar, lo hacemos en otro hilo
			self.app.thread(Application.getInstance().getControlConnection().endCall)
			self.closeCallWindow()
			self.app.enableButton('Conectar')
			self.app.disableButton('Colgar')

		elif button == 'Conectar':
			self.app.thread(self.populateSearchNickWindow)
			self.app.showSubWindow('searchNickWindow')

		elif button == 'Aceptar':
			Application.getInstance().getControlConnection().answerRequest('CALL_ACCEPTED')

		elif button == 'Rechazar':
			Application.getInstance().getControlConnection().answerRequest('CALL_DENIED')
			self.app.hideSubWindow('videoWindow')

		elif button == 'Cargar video':
			if self.usingCamera.is_set():
				path = self.app.openBox(title='Load video', fileTypes=[('video', '.mp4'), ('video', '.avi')], asFile=False, parent=None)
				if isinstance(path, str):
					# Si no se selecciona un archivo, devuelve una tupla
					self.app.thread(self.capturaVideoArchivo, path)
			else:
				# Al hacer set de usingCamera, la funcion de mandar el archivo termina e inicia la de la webcam
				self.usingCamera.set()

		elif button == 'pauseResumeButton':
			connection = Application.getInstance().getConnection()
			if connection == None:
				self.logger.error('VideoClient - Connection is none on Pause/Resume button click.')
				return

			if connection.isRunning():
				self.logger.debug('VideoClient - Paused connection with user {}'.format(connection.dstUser))
				self.app.queueFunction(Application.getInstance().getControlConnection().holdCall)
				self.setPauseResumeButton('Reanudar')

			elif connection.isPaused():
				self.logger.debug('VideoClient - Resumed connection with user {}'.format(connection.dstUser))
				self.app.queueFunction(Application.getInstance().getControlConnection().resumeCall)
				self.setPauseResumeButton('Pausar')

		elif button == 'filtersOptionBox':
			self.imageFilter = filters.FILTERS[self.app.getOptionBox('filtersOptionBox')]
	def populateSearchNickWindow(self):
		self.logger.debug('VideoClient - Populating search nick window.')
		users = identityGestion.listUsers()
		users = list(map(lambda u: u.nick, users))
		# Eliminamos nuestro usuario de la lista
		nick = Application.getInstance().getUser().nick
		if nick in users:
			users.remove(nick)

		self.app.queueFunction(self.app.updateListBox, 'usersListBox', users)
示例#6
0
    def endCall(self):
        origUser = Application.getInstance().getUser()
        if origUser == None:
            self.logger.error(
                'ControlConnection - Usuario origen no loggeado.')
            return

        connection = Application.getInstance().getConnection()
        if connection == None:
            self.logger.error(
                'ControlConnection - Connection is none on call end.')
            return

        dstUser = connection.dstUser

        message = 'CALL_END {}'.format(origUser.nick)
        response = utils.sendTCPMessageOnly(message, dstUser.ip, dstUser.port)
        # Cerramos la conexion
        connection.quit()
        Application.getInstance().setConnection(None)
	def showControlConnectionSuccess(self):
		self.correctlyLogged.set()
		user = Application.getInstance().getUser()
		if user == None:
			self.logger.error('Una vez iniciada la conexion de control User is None')
			return
		self.notify('Logeado correctamente como '+str(user.nick))
		self.app.queueFunction(self.app.hideSubWindow, 'loginWindow')
		# Iniciamos la captura de video una vez estamos logeados.
		self.app.thread(self.capturaVideo)
		self.app.queueFunction(self.app.show)
示例#8
0
    def resumeCall(self):
        origUser = Application.getInstance().getUser()
        if origUser == None:
            self.logger.error(
                'ControlConnection - Usuario origen no loggeado.')
            return

        connection = Application.getInstance().getConnection()
        if connection == None:
            self.logger.error(
                'ControlConnection - Connection is none on call resume.')
            return

        dstUser = connection.dstUser

        message = 'CALL_RESUME {}'.format(origUser.nick)
        response = utils.sendTCPMessageOnly(message, dstUser.ip, dstUser.port)
        # Reanudamos la conexion
        connection = Application.getInstance().getConnection()
        if connection != None:
            connection.resume()
	def connectTo(self, nick, entry):
		if nick == []:
			# Al iniciarse por primera vez
			return

		if entry == 'ListBox':
			nick = nick[0]

		self.app.queueFunction(self.app.hideSubWindow, 'searchNickWindow')
		self.notify('Buscando al usuario en el servidor')
		user = identityGestion.quertyUser(nick)

		if user == None:
			self.notify('Error al obtener los datos del usuario {}'.format(nick))
		else:
			# Comenzamos la llamada
			self.notify('Llamando al usuario {}'.format(user))
			# Deshabilitamos el boton de conectar hasta que se haya finalizado la llamada
			self.app.queueFunction(self.app.disableButton, 'Conectar')
			self.app.queueFunction(self.app.enableButton, 'Colgar')
			Application.getInstance().getControlConnection().connectWith(user)
	def register(self, button):
		# Nos registramos / logeamos en el servidor de descubrimiento
		nick = self.app.getEntry('registerNickEntry')
		if not nick:
			self.notifyLogin('Es necesario que introduzcas un nick.')
			return

		password = self.app.getEntry('registerPasswordEntry')
		if not password:
			self.notifyLogin('Es necesario que introduzcas una contraseña.')
			return

		ip = self.app.getEntry('registerIPEntry')
		if not ip or not utils.isIPv4(ip):
			self.notifyLogin('Es necesario que introduzcas una IPv4 válida.')
			return

		port = self.app.getEntry('registerPortEntry')
		if not port or not utils.isInteger(port) or int(port)<0 or int(port)>65535:
			self.notifyLogin('Es necesario que introduzcas un puerto válido.')
			return

		self.notifyLogin('Registrandote en el servidor.')
		# Deshabilitamos los botones mientras consultamos en el servidor para evitar 
		# posibles errores al pulsar varias veces
		self.app.queueFunction(self.app.disableButton, 'Register user')
		self.app.queueFunction(self.app.disableButton, 'Login')

		tmp = identityGestion.registerUser(nick, ip, int(port), password, '#'.join(Application.protocols))
		if tmp != None:
			# La contraseña es correcta, acabamos
			saveUserInfo(tmp)
			Application.getInstance().setUser(tmp)
			Application.getInstance().startControlConnection(tmp.ip, tmp.port)
			# La conexión de control se encarga de mostrar o no la pantalla principal
		else:
			# Volvemos a habilitar los botones
			self.app.queueFunction(self.app.enableButton, 'Register user')
			self.app.queueFunction(self.app.enableButton, 'Login')
			self.notifyLogin('Ya hay un usuario con este nick.')
	def enviarFrame(self, frame, hres, vres, fps):
		connection = Application.getInstance().getConnection()
		if connection != None and connection.isRunning():

			encode_param = [cv2.IMWRITE_JPEG_QUALITY,50] #50% calidad. Cambiar mas adelante
			result,encimg = cv2.imencode('.jpg', frame, encode_param)
			if result == False:
				self.logger.error('VideoClient - Error al codificar la imagen a enviar')
				return

			imgData = encimg.tobytes() # Codificamos la imagen
			data = '{}#{}#{}#{}#'.format(str(self.sentFrames), str(time.time()), '{}x{}'.format(hres, vres), str(fps))
			data = data.encode()+imgData

			connection.send(data)
			self.sentFrames += 1
	def closeCallWindow(self):
		self.logger.debug('VideoClient - Connection stopped.')
		self.sentFrames = 0
		self.timeCallStarted = None
		self.app.queueFunction(self.app.hideSubWindow, 'videoWindow')
		self.app.queueFunction(self.app.enableButton, 'Conectar')
		self.app.queueFunction(self.app.disableButton, 'Colgar')

		# Reseteamos la imagen de llamada entrante para futuras llamadas
		self.app.queueFunction(self.app.setImage, 'receivedVideoImage', 'imgs/user_icon.gif')

		# Reseteamos los campos de la statusbar
		self.app.queueFunction(self.app.setStatusbar, '', field=0)
		self.app.queueFunction(self.app.setStatusbar, '', field=1)
		self.app.queueFunction(self.app.setStatusbar, '', field=2)

		self.notify('Logeado correctamente como '+str(Application.getInstance().getUser().nick))
示例#13
0
    def start(self):
        # Creamos el socket
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Lo asociamos a la IP y puerto correctos
        try:
            self.server.bind((self.ip, self.port))
        except Exception as e:
            print('{}:{}'.format(self.ip, self.port))
            self.logger.info(
                'ControlConnection - El puerto o la dirección IP de la conexion de control no son válidos.'
            )
            Application.getInstance().getInterface().notify(
                'Error al iniciar la conexión de control en la ip y puerto especificados.'
            )
            Application.getInstance().getInterface(
            ).showControlConnectionError()
            return

        Application.getInstance().getInterface().showControlConnectionSuccess()

        # Indicamos el numero de conexiones
        self.server.listen(self.maxConnections)
        self.logger.debug(
            'ControlConnection - Conexión de control inicializada en {}:{}'.
            format(self.ip, self.port))
        self.server.settimeout(1)

        # Establecemos el Event
        self.runningEvent.set()

        while self.runningEvent.is_set():
            try:
                client_sock, address = self.server.accept()
                self.logger.debug(
                    'ControlConnection - Aceptada conexion de {}:{}.'.format(
                        address[0], address[1]))
                # Un único para así saber siempre quien es el usuario que nos llama, por ejemplo
                self.conexionHandler(client_sock)
            except socket.error as e:
                continue
示例#14
0
    def startCallV1(self, nick, ip, port):
        ports = utils.getAvailablePort(self.ip, nports=2)
        origVideoPort, origAudioPort = ports[0], ports[1]

        origUser = Application.getInstance().getUser()
        if origUser == None:
            self.logger.error(
                'ControlConnection - Usuario origen no loggeado.')
            return

        message = 'CALLING_V1 {} {} {}'.format(origUser.nick, origVideoPort,
                                               origAudioPort)
        response = utils.sendTCPMessage(message,
                                        ip,
                                        port,
                                        timeout=self.waitCallTimeout)
        if response == None:
            self.logger.error(
                'ControlConnection - Respuesta no recibida al contactar: {}-{}:{} para llamada con audio.'
                .format(nick, ip, port))
            Application.getInstance().getInterface().closeCallWindow()
            Application.getInstance().getInterface().notify(
                'El usuario {} no está disponible ahora mismo.'.format(nick))
            return

        response = response.split(' ')

        self.logger.debug('ControlConnection - {} \n\tGot response: {}'.format(
            message, response))
        if response[0] == 'CALL_ACCEPTED_V1':
            nick = response[1]
            dstVideoPort = int(response[2])
            dstAudioPort = int(response[3])
            user = identityGestion.quertyUser(nick)
            if user == None:
                self.logger.error(
                    'ControlConnection - Usuario {} no encontrado. No se puede aceptar la llamada con audio entrante.'
                    .format(nick))
                return

            conn = udp.VideoAudioCallConnection(self.ip, user.ip,
                                                origVideoPort, dstVideoPort,
                                                origAudioPort, dstAudioPort,
                                                user, self.logger)
            if conn.start():
                Application.getInstance().setConnection(conn)
                Application.getInstance().getInterface().startCallWindow(user)
                Application.getInstance().getInterface().notify(
                    'Comenzando la llamada con audio con el usuario {}'.format(
                        nick))
            else:
                Application.getInstance().getInterface().notify(
                    'Error al comenzar la llamada con audio con el usuario {}'.
                    format(nick))

        elif response[0] == 'CALL_DENIED':
            nick = response[1]
            self.logger.debug(
                'ControlConnection - El usuario {} no encontrado ha denegado la llamada con audio.'
                .format(nick))
            Application.getInstance().getInterface().closeCallWindow()
            Application.getInstance().getInterface().notify(
                'El usuario {} ha colgado.'.format(nick))

        elif response[0] == 'CALL_BUSY':
            self.logger.debug(
                'ControlConnection - El usuario {} está ocupado para una llamada con audio.'
                .format(nick))
            Application.getInstance().getInterface().closeCallWindow()
            Application.getInstance().getInterface().notify(
                'El usuario {} está ocupado en estos momentos.'.format(nick))
        else:
            self.logger.error(
                'ControlConnection - Respuesta incorrecta a llamada con audio: {}.'
                .format(response))
示例#15
0
    def conexionHandler(self, socket):
        try:
            request = socket.recv(1024).decode()
            self.logger.debug(
                'ControlConnection -  Socket {}. Recibido: {}'.format(
                    socket.getpeername(), request))
            request = request.split(' ')
            # Recibimos una llamada entrante
            if request[0] == 'CALLING':
                nick = request[1]
                dstUDPport = int(request[2])
                user = identityGestion.quertyUser(nick)
                if user == None:
                    self.logger.error(
                        'ControlConnection - Usuario {} no encontrado. No se puede empezar la llamada.'
                        .format(nick))
                    return

                # Si ya estamos en una llamada, avisamos de que estamos ocupados y notificamos por la interfaz
                if Application.getInstance().getConnection() != None:
                    socket.send('CALL_BUSY {}'.format(
                        Application.getInstance().getUser().nick).encode())
                    Application.getInstance().getInterface().notify(
                        'Llamada recibida del usuario {}'.format(nick))

                Application.getInstance().getInterface().showIncomingCall(
                    user, True)
                # Tenemos que esperar a que el usuario pulse el botón
                self.waitEvent.clear()
                self.waitEvent.wait(timeout=self.waitCallTimeout)

                if not self.waitEvent.is_set():
                    # El usuario no ha respondido a la llamada
                    Application.getInstance().getInterface().closeCallWindow()
                    Application.getInstance().getInterface().notify(
                        'Llamada perdida del usuario {}'.format(nick))

                # El usuario decice aceptar la llamada
                if self.interfaceResponse == 'CALL_ACCEPTED':
                    # Iniciamos la conexion UDP
                    origPort = utils.getAvailablePort(self.ip)
                    socket.send('CALL_ACCEPTED {} {}'.format(
                        Application.getInstance().getUser().nick,
                        origPort).encode())

                    conn = udp.VideoConnection(self.ip, user.ip, origPort,
                                               dstUDPport, user, self.logger)
                    if conn.start():
                        Application.getInstance().setConnection(conn)
                        Application.getInstance().getInterface(
                        ).startCallWindow(user)
                        Application.getInstance().getInterface().notify(
                            'Comenzando la llamada con el usuario {}'.format(
                                nick))
                    else:
                        Application.getInstance().getInterface().notify(
                            'Error al comenzar la llamada con el usuario {}'.
                            format(nick))

                # El usuario decice rechazar la llamada
                elif self.interfaceResponse == 'CALL_DENIED':
                    # Colgamos la llamada
                    socket.send('CALL_DENIED {}'.format(
                        Application.getInstance().getUser().nick).encode())
                # El usuario indica que esta ocupado - De momento no se puede, no le vemos sentido
                elif self.interfaceResponse == 'CALL_BUSY':
                    socket.send('CALL_BUSY {}'.format(
                        Application.getInstance().getUser().nick).encode())
                else:
                    self.logger.error(
                        'ControlConnection - Respuesta incorrecta por parte del usuario a CALLING: {}'
                        .format(self.interfaceResponse))

            # Recibimos una llamada con audio entrante
            if request[0] == 'CALLING_V1':
                nick = request[1]
                dstVideoPort = int(request[2])
                dstAudioPort = int(request[3])
                user = identityGestion.quertyUser(nick)
                if user == None:
                    self.logger.error(
                        'ControlConnection - Usuario {} no encontrado. No se puede empezar la llamada.'
                        .format(nick))
                    return

                # Si ya estamos en una llamada, avisamos de que estamos ocupados y notificamos por la interfaz
                if Application.getInstance().getConnection() != None:
                    socket.send('CALL_BUSY {}'.format(
                        Application.getInstance().getUser().nick).encode())
                    Application.getInstance().getInterface().notify(
                        'Llamada recibida del usuario {}'.format(nick))

                Application.getInstance().getInterface().showIncomingCall(
                    user, True)
                # Tenemos que esperar a que el usuario pulse el botón
                self.waitEvent.clear()
                self.waitEvent.wait(timeout=self.waitCallTimeout)

                if not self.waitEvent.is_set():
                    # El usuario no ha respondido a la llamada
                    Application.getInstance().getInterface().closeCallWindow()
                    Application.getInstance().getInterface().notify(
                        'Llamada perdida del usuario {}'.format(nick))

                # El usuario decice aceptar la llamada
                if self.interfaceResponse == 'CALL_ACCEPTED':
                    # Iniciamos la conexion UDP
                    ports = utils.getAvailablePort(self.ip, nports=2)
                    origVideoPort, origAudioPort = ports[0], ports[1]

                    socket.send('CALL_ACCEPTED_V1 {} {} {}'.format(
                        Application.getInstance().getUser().nick,
                        origVideoPort, origAudioPort).encode())

                    conn = udp.VideoAudioCallConnection(
                        self.ip, user.ip, origVideoPort, dstVideoPort,
                        origAudioPort, dstAudioPort, user, self.logger)
                    if conn.start():
                        Application.getInstance().setConnection(conn)
                        Application.getInstance().getInterface(
                        ).startCallWindow(user)
                        Application.getInstance().getInterface().notify(
                            'Comenzando la llamada con el usuario {}'.format(
                                nick))
                    else:
                        Application.getInstance().getInterface().notify(
                            'Error al comenzar la llamada con el usuario {}'.
                            format(nick))

                # El usuario decice rechazar la llamada
                elif self.interfaceResponse == 'CALL_DENIED':
                    # Colgamos la llamada
                    socket.send('CALL_DENIED {}'.format(
                        Application.getInstance().getUser().nick).encode())
                # El usuario indica que esta ocupado - De momento no se puede, no le vemos sentido
                elif self.interfaceResponse == 'CALL_BUSY':
                    socket.send('CALL_BUSY {}'.format(
                        Application.getInstance().getUser().nick).encode())
                else:
                    self.logger.error(
                        'ControlConnection - Respuesta incorrecta por parte del usuario a CALLING: {}'
                        .format(self.interfaceResponse))

            # Si es una peticion sobre una conexion ya iniciada
            else:
                connection = Application.getInstance().getConnection()
                if connection == None:
                    self.logger.error(
                        'ControlConnection - Recibido: {} cuando no habia conexion UDP iniciada'
                        .format(request))
                    return

                nick = request[1]
                if connection.dstUser.nick != nick:
                    self.logger.error(
                        'ControlConnection - El usuario {} intenta infiltrarse en nuestra conexion con {}'
                        .format(nick, connection.dstUser.nick))
                    return

                # Recibimos una peticion para pausar la llamada
                if request[0] == 'CALL_HOLD':
                    # Pausamos la conexion
                    connection.pause()
                    Application.getInstance().getInterface(
                    ).setPauseResumeButton('Reanudar')
                    Application.getInstance().getInterface().notify(
                        'El usuario {} ha pausado la conexion.'.format(nick))

                # Recibimos una peticion para reanudar la llamada
                elif request[0] == 'CALL_RESUME':
                    # Reanudamos la conexion
                    connection.resume()
                    Application.getInstance().getInterface(
                    ).setPauseResumeButton('Pausar')
                    Application.getInstance().getInterface().notify(
                        'El usuario {} ha reanudado la conexion.'.format(nick))

                # Recibimos una peticion para detener definitivamente la llamada
                elif request[0] == 'CALL_END':
                    # Eliminamos definitivamente la conexion
                    Application.getInstance().getInterface().closeCallWindow()
                    Application.getInstance().setConnection(None)
                    connection.quit()
        finally:
            socket.close()
示例#16
0
import utils

from user import getUserInfo

#
# Inicializa el logger, la conexión de control, la interfaz y el Application en sí.
# Lanza el Application.
#
if __name__ == '__main__':

    # Iniciamos un logger para obtener informacion ante posibles fallos
    logger = logging.getLogger(utils.NAME)
    logger.setLevel(logging.DEBUG)
    formatter = logging.Formatter(
        fmt='%(asctime)s %(levelname)-8s %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S')
    handler = RotatingFileHandler(utils.LOG_FILE,
                                  maxBytes=256000,
                                  backupCount=10)
    handler.setFormatter(formatter)
    handler.setLevel(logging.DEBUG)
    logger.addHandler(handler)

    vc = VideoClient(utils.WIN_SIZE, logger)
    cc = ControlConnection(10, logger)

    app = Application.getInstance(None, vc, cc, logger)
    # Inicializa la aplicación. Es bloqueante
    app.start()

    logging.shutdown()