class Streamer(object): """ the server will be the "host" of the screen share, basically, will share his screen to all the clients. """ def __init__(self): """ The constructor function of the main server. """ self.streamer_socket = socket.socket() self.online_users = {} # {socket: display resolution}. self.communicator = Communicator() self.screenshot = Screenshot() self.running = True def bind_socket(self): """ The function binds the server socket and starts listening for connections. """ self.streamer_socket.bind((SERVER_IP, SERVER_PORT)) self.streamer_socket.listen(1) print('================================================') print(f'[STREAMER] Starting server on: {SERVER_IP}:{str(SERVER_PORT)})) print('================================================') def new_user(self): """ The function accepts a new connection, adds the user's socket to the online users dictionary and starts sending him screenshots. """ client_socket, client_address = self.streamer_socket.accept() client_ip_address = client_address[0] self.online_users[client_socket] = DEFAULT_DISPLAY_RESOLUTION Thread(target=self.send_screenshots, args=[client_socket]).start() print('[STREAMER] New Online User [' + str(client_ip_address) + ']') def send_screenshots(self, user_socket): """ :param user_socket: the socket to send screenshots to. the function sends screenshots to a user's socket. if there's no online user with that socket the function returns. """ if user_socket in self.online_users.keys(): while self.running: try: user_display_resolution = self.online_users[user_socket] image_data = self.screenshot.get_screenshot_data(image_resize=user_display_resolution) + IMAGE_SUFFIX self.communicator.send_enc_message(image_data, False, user_socket) except (KeyError, socket.error): self.remove_user(user_socket) break else: return None def remove_user(self, user_socket): """ :param user_socket: socket to remove from the online users dictionary. the function removes a user from the online users dictionary. """ try: del self.online_users[user_socket] user_socket.close() print('[STREAMER] User has been disconnected.') except(KeyError, socket.error): pass def define_message_type(self, message, user_socket): """ :param message: a new message the was received. :param user_socket: the socket that the message was received from. the function defines the message type and calls the wanted function. """ if isinstance(message, DisplayResolutionChange): self.online_users[user_socket] = message.screen_resolution def run_server(self): while self.running: rlist, wlist, xlist = select(list(self.online_users.keys()) + [self.streamer_socket], [], []) for user_socket in rlist: if user_socket is self.streamer_socket: self.new_user() else: try: message = self.communicator.get_dec_message(user_socket) self.define_message_type(message, user_socket) except: self.remove_user(user_socket)
class Client(object): """ Client object define an observer. He can see the streamer's screen share.(multiple clients can see one stream). """ def __init__(self): """ the constructor function of the client. """ self.client_socket = socket.socket() # the socket of the client. self.communicator = Communicator() self.events_handler = EventsHandler(self.client_socket) self.running = True self.display_resolution = DEFAULT_DISPLAY_RESOLUTION self.screen = self.get_display() def connect_to_server(self): """ The function connects to the server. """ self.client_socket.connect((SERVER_IP, SERVER_PORT)) print('[CLIENT] connected to streamer.') def get_image(self): """ the function gets a new screenshot from the streamer. :return: a new screenshot. """ try: message = self.communicator.get_dec_message(self.client_socket) output = StringIO.StringIO(message) image = pygame.image.load(output) return image except pygame.error: return None except socket.error: self.close_connection() def change_image_on_screen(self, image): """ :param image: PIL image of the streamer's screen. the function updated the display image to the new image. """ try: self.screen.blit(image, (0, 0)) pygame.display.flip() except pygame.error: pass def get_display(self): """ :return: a pygame screen(where the screen will be shown) """ pygame.init() screen = pygame.display.set_mode(self.display_resolution, pygame.RESIZABLE) pygame.display.set_caption(SCREEN_NAME, SCREEN_NAME) return screen def run_client(self): while self.running: try: image = self.get_image() if image is not None: self.change_image_on_screen(image) self.events_handler.display_events(self.screen) except pygame.error: self.running = False self.client_socket.close() def close_connection(self): """ The function stops the client functions and disconnects from the stream. """ self.running = False self.client_socket.close()