def _connect_backend(self, server_url): if self.backend_handle is not None: raise JanusCloudError('Already connected', JANUS_ERROR_INTERNAL_ERROR) if self.videocall_user is None: raise JanusCloudError('Register a username first', JANUS_VIDEOCALL_ERROR_REGISTER_FIRST) backend_session = get_backend_session(server_url, auto_destroy=BACKEND_SESSION_AUTO_DESTROY_TIME) backend_handle = backend_session.attach_handle(JANUS_VIDEOCALL_PACKAGE, handle_listener=self) # register try: body = { 'request': 'register', 'username': self.videocall_user.username } self._send_backend_meseage(backend_handle, body) if self._pending_set_body: self._send_backend_meseage(backend_handle, self._pending_set_body) if len(self._pending_candidates) > 0: backend_handle.send_trickle(candidates=self._pending_candidates) except Exception: backend_handle.detach() raise # connect & setup successfully if self._pending_set_body: self._pending_set_body = None if len(self._pending_candidates) > 0: self._pending_candidates.clear() self.backend_handle = backend_handle
def _send_aync_event(self, to_user, event_msg): if self.p2pcall_user is None: raise JanusCloudError('Register a username first', JANUS_P2PCALL_ERROR_REGISTER_FIRST) from_user = self.p2pcall_user.username peer = self._plugin.user_dao.get_by_username(to_user) if peer is None: raise JanusCloudError('Username \'{}\' doesn\'t exist'.format(to_user), JANUS_P2PCALL_ERROR_NO_SUCH_USERNAME) if peer.handle: # if dest user is handled by the same proxy, send to him directly log.debug('An async event ({}) is sent from \'{}\' to \'{}\' at local proxy'.format( event_msg, from_user, to_user )) peer.handle.on_async_event(from_user, event_msg) elif peer.api_url: # if dest user is handled by the other proxy, send to him by RESTful api log.debug('An async event ({}) is sent from \'{}\' to \'{}\' by {}'.format( event_msg, from_user, to_user, peer.api_url )) r = requests.post(peer.api_url, json={ 'from_user': from_user, 'async_event': event_msg }) if r.status_code != requests.codes.ok: try: text = r.json()['info'] except Exception: text = r.text raise JanusCloudError(text, r.status_code) else: raise JanusCloudError('Username \'{}\' doesn\'t exist'.format(to_user), JANUS_P2PCALL_ERROR_NO_SUCH_USERNAME)
def incoming_request(self, request): """ handle the request from the transport module Args: request: the request to handle Returns: a dict to response to the initial client """ try: log.debug('Request ({}) is incoming to handle'.format( request.message)) handler = getattr(self, '_handle_' + request.janus) if handler is None or self._frontend_session_mgr is None: raise JanusCloudError( 'Unknown request \'{0}\''.format(request.janus), JANUS_ERROR_UNKNOWN_REQUEST) # check secret valid if self._api_secret and request.janus not in {'ping', 'info'}: if self._api_secret != request.apisecret: raise JanusCloudError( "Unauthorized request (wrong or missing secret/token)", JANUS_ERROR_UNAUTHORIZED) response = handler(request) log.debug('Response ({}) is to return'.format(response)) return response except Exception as e: log.warn('Request ({}) processing failed'.format(request.message), exc_info=True) return error_to_janus_msg(request.session_id, request.transaction, e)
def _handle_trickle(self, request): trickle_params_schema = Schema({ Optional('candidate'): dict, Optional('candidates'): [dict], AutoDel(str): object # for all other key we don't care }) params = trickle_params_schema.validate(request.message) candidate = params.get('candidate') candidates = params.get('candidates') if candidate is None and candidates is None: raise JanusCloudError( 'Missing mandatory element (candidate|candidates)', JANUS_ERROR_MISSING_MANDATORY_ELEMENT) if candidate and candidates: raise JanusCloudError('Can\'t have both candidate and candidates', JANUS_ERROR_INVALID_JSON) # dispatch to plugin handle handle = self._get_plugin_handle(request) handle.handle_trickle(candidate=candidate, candidates=candidates) return create_janus_msg('ack', request.session_id, request.transaction)
def send_trickle(self, candidate=None, candidates=None): if self._has_detach: raise JanusCloudError('backend handle {} has been destroyed'.format(self.handle_id), JANUS_ERROR_PLUGIN_DETACH) if candidate is None and candidates is None: raise JanusCloudError('Missing mandatory element (candidate|candidates)', JANUS_ERROR_MISSING_MANDATORY_ELEMENT) if candidate and candidates: raise JanusCloudError('Can\'t have both candidate and candidates', JANUS_ERROR_INVALID_JSON) params = {} if candidate: params['candidate'] = candidate if candidates: params['candidates'] = candidates trickle_msg = create_janus_msg('trickle', handle_id=self.handle_id, **params) response = self._session.send_request(trickle_msg, ignore_ack=False) if response['janus'] == 'ack': pass # successful elif response['janus'] == 'error': raise JanusCloudError(response['error']['reason'], response['error']['code']) else: raise JanusCloudError( 'unknown backend response {}'.format(response), JANUS_ERROR_BAD_GATEWAY)
def send_request(self, client, msg, ignore_ack=True): if self._has_destroy: raise JanusCloudError('Janus server already destory', JANUS_ERROR_SERVICE_UNAVAILABLE) if client is None: raise JanusCloudError('websocket client not ready', JANUS_ERROR_SERVICE_UNAVAILABLE) transaction_id = self._generate_new_tid() send_msg = dict.copy(msg) send_msg['transaction'] = transaction_id transaction = BackendTransaction(transaction_id, send_msg, url=client.url, ignore_ack=ignore_ack) try: self._transactions[transaction_id] = transaction log.debug('Send Request {} to Janus server: {}'.format( send_msg, client.url)) client.send_message(send_msg) response = transaction.wait_response(timeout=self._request_timeout) log.debug('Receive Response {} from Janus server: {}'.format( response, client.url)) if response['janus'] == 'error': raise JanusCloudError(response['error']['reason'], response['error']['code']) return response finally: self._transactions.pop(transaction_id, None)
def send_message(self, body, jsep=None): if self._has_detach: raise JanusCloudError( 'backend handle {} has been destroyed'.format(self.handle_id), JANUS_ERROR_PLUGIN_DETACH) params = dict() params['body'] = body if jsep: params['jsep'] = jsep message = create_janus_msg('message', handle_id=self.handle_id, **params) response = self._session.send_request(message) if response['janus'] == 'event' or response['janus'] == 'success': data = response['plugindata']['data'] reply_jsep = response.get('jsep') return data, reply_jsep elif response['janus'] == 'error': raise JanusCloudError(response['error']['reason'], response['error']['code']) else: raise JanusCloudError( 'unknown backend response {}'.format(response), JANUS_ERROR_BAD_GATEWAY)
def handle_incoming_call(self, caller_username, backend_server_url): if self.videocall_user is None: raise JanusCloudError('Register a username first', JANUS_VIDEOCALL_ERROR_REGISTER_FIRST) if self.videocall_user.incall: raise JanusCloudError( 'User {} busy'.format(self.videocall_user.username), JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL) self.videocall_user.incall = True self.videocall_user.peer_name = caller_username self.videocall_user.utime = time.time() try: self._connect_backend(backend_server_url) self._plugin.user_dao.update(self.videocall_user) except Exception: self._disconnect_backend() self.videocall_user.peer_name = '' self.videocall_user.incall = False raise # if incoming_call event cannot be received in INCOMMING_CALL_TIMEOUT(10) seconds, # auto disconnect the backend server if self._auto_disconnect_greenlet is None: self._auto_disconnect_greenlet = gevent.spawn_later( INCOMMING_CALL_TIMEOUT, self._auto_disconnect_routine)
def call_peer(self, peer_username, caller_username, backend_server_url, backend_keepalive_interval=10): peer = self.user_dao.get_by_username(peer_username) if peer is None: raise JanusCloudError( 'Username \'{}\' doesn\'t exist'.format(peer_username), JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME) if peer.handle: # the peer is handled by self peer.handle.handle_incoming_call(caller_username, backend_server_url) elif peer.api_url: # the peer is handled by the other janus-proxy caller = self.user_dao.get_by_username(caller_username) if caller is None or caller.handle is None: raise JanusCloudError('Not support relay http request', JANUS_VIDEOCALL_ERROR_INVALID_REQUEST) r = requests.post(peer.api_url, data={ 'caller_username': caller_username, 'backend_server_url': backend_server_url, 'backend_keepalive_interval': str(backend_keepalive_interval) }) if r.status_code != requests.codes.ok: try: text = r.json()['info'] except Exception: text = r.text raise JanusCloudError(text, r.status_code)
def _send_backend_meseage(backend_handle, body, jsep=None): if backend_handle is None: raise JanusCloudError('Not connected', JANUS_ERROR_INTERNAL_ERROR) data, reply_jsep = backend_handle.send_message(body=body, jsep=jsep) if 'error_code' in data: raise JanusCloudError(data.get('error','unknown'), data['error_code']) elif 'result' not in data: raise JanusCloudError('Invalid Response payload: {}'.format(data), JANUS_ERROR_BAD_GATEWAY) return data['result'], reply_jsep
def _send_backend_message(backend_handle, body, jsep=None): if backend_handle is None: raise JanusCloudError('Not connected', JANUS_ERROR_INTERNAL_ERROR) data, reply_jsep = backend_handle.send_message(body=body, jsep=jsep) if 'error_code' in data: raise JanusCloudError( data.get('error', 'unknown'), data.get('error_code', JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR)) return data, reply_jsep
def handle_async_event(self, to_user, from_user, async_event): p2pcall_user = self.user_dao.get_by_username(to_user) if p2pcall_user is None: raise JanusCloudError('Username \'{}\' doesn\'t exist'.format(to_user), JANUS_P2PCALL_ERROR_NO_SUCH_USERNAME) if p2pcall_user.handle is None: raise JanusCloudError('Not support relay http request', JANUS_P2PCALL_ERROR_INVALID_REQUEST) log.debug('an async event ({}) from \'{}\' to \'{}\' is received by http API'. format(async_event, from_user, to_user)) p2pcall_user.handle.on_async_event(from_user, async_event)
def _get_session(self, request): if request.session_id == 0: raise JanusCloudError( "Unhandled request '{}' at this path".format(request.janus), JANUS_ERROR_INVALID_REQUEST_PATH) session = self._frontend_session_mgr.find_session(request.session_id) if session is None: raise JanusCloudError( 'No such session {}'.format(request.session_id), JANUS_ERROR_SESSION_NOT_FOUND) session.activate() return session
def _get_plugin_handle(self, request): session = self._get_session(request) if request.handle_id == 0: raise JanusCloudError( "Unhandled request '{}' at this path".format(request.janus), JANUS_ERROR_INVALID_REQUEST_PATH) handle = session.get_handle(request.handle_id) if handle is None: raise JanusCloudError( "No such handle {} in session {}".format( request.handle_id, request.session_id), JANUS_ERROR_HANDLE_NOT_FOUND) return handle
def _create_janus_session(self): response = self.send_request(create_janus_msg('create')) if response['janus'] == 'success': return response['data']['id'] elif response['janus'] == 'error': raise JanusCloudError( 'Create session error for Janus server {} with reason {}'. format(self.url, response['error']['reason']), response['error']['code']) else: raise JanusCloudError( 'Create session error for Janus server: {} with invalid response {}' .format(self.url, response), JANUS_ERROR_BAD_GATEWAY)
def _get_session_timeout(self): response = self.send_request(create_janus_msg('info')) if response['janus'] == 'server_info': return response.get('session-timeout', 30) elif response['janus'] == 'error': raise JanusCloudError( 'Create session error for Janus server {} with reason {}'. format(self.url, response['error']['reason']), response['error']['code']) else: raise JanusCloudError( 'Create session error for Janus server: {} with invalid response {}' .format(self.url, response), JANUS_ERROR_BAD_GATEWAY)
def attach_handle(self, plugin_package_name, opaque_id=None, handle_listener=None): """ :param plugin_pacakge_name: str plugin package name :param opaque_id: str opaque id :param handle_listener: handle related callback listener which cannot block :return: BackendHandle object """ if self.state == BACKEND_SESSION_STATE_DESTROYED: raise JanusCloudError( 'Session has destroy for Janus server: {}'.format(self.url), JANUS_ERROR_SERVICE_UNAVAILABLE) attach_request_msg = create_janus_msg('attach', plugin=plugin_package_name) if opaque_id: attach_request_msg['opaque_id'] = opaque_id response = self.send_request(attach_request_msg) # would block for IO if response['janus'] == 'success': handle_id = response['data']['id'] elif response['janus'] == 'error': raise JanusCloudError( 'attach error for Janus server {} with reason {}'.format( self.url, response['error']['reason']), response['error']['code']) else: raise JanusCloudError( 'attach error for Janus server: {} with invalid response {}'. format(self.url, response), JANUS_ERROR_BAD_GATEWAY) # check again when wake up from block IO if self.state == BACKEND_SESSION_STATE_DESTROYED: raise JanusCloudError( 'Session has destroy for Janus server: {}'.format(self.url), JANUS_ERROR_SERVICE_UNAVAILABLE) handle = BackendHandle(handle_id, plugin_package_name, self, opaque_id=opaque_id, handle_listener=handle_listener) self._handles[handle_id] = handle if self._auto_destroy_greenlet: gevent.kill(self._auto_destroy_greenlet) self._auto_destroy_greenlet = None return handle
def send_hangup(self): if self._has_detach: raise JanusCloudError('backend handle {} has been destroyed'.format(self.handle_id), JANUS_ERROR_PLUGIN_DETACH) hangup_msg = create_janus_msg('hangup', handle_id=self.handle_id) response = self._session.send_request(hangup_msg) if response['janus'] == 'success': pass # successful elif response['janus'] == 'error': raise JanusCloudError(response['error']['reason'], response['error']['code']) else: raise JanusCloudError( 'unknown backend response {}'.format(response), JANUS_ERROR_BAD_GATEWAY)
def find_session(self, session_id): session = self._sessions.get(session_id) if session is None: log.error("Couldn't find any session {}".format(session_id)) raise JanusCloudError('No such session {}'.format(session_id), JANUS_ERROR_SESSION_NOT_FOUND) return session
def update(self, videocall_user): org_videocall_user = self._users_by_name.get(videocall_user.username) if not org_videocall_user: raise JanusCloudError( 'server {} NOT found'.format(videocall_user.username), JANUS_ERROR_NOT_FOUND) org_videocall_user.__dict__.update(videocall_user.__dict__)
def wait_response(self, timeout=None): ready = self._response_ready.wait(timeout=timeout) if not ready: raise JanusCloudError( 'Request {} Timeout for backend Janus server: {}'.format( self.request_msg, self._url), JANUS_ERROR_GATEWAY_TIMEOUT) return self._response
def send_request(self, msg, ignore_ack=True, timeout=30): if self.state == BACKEND_SESSION_STATE_DESTROYED: raise JanusCloudError( 'Session has destroy for Janus server: {}'.format(self.url), JANUS_ERROR_SERVICE_UNAVAILABLE) transaction_id = self._genrate_new_tid() send_msg = dict.copy(msg) send_msg['session_id'] = self.session_id send_msg['transaction'] = transaction_id if self._api_secret: send_msg['apisecret'] = self._api_secret transaction = BackendTransaction(transaction_id, send_msg, url=self.url, ignore_ack=ignore_ack) try: self._transactions[transaction_id] = transaction log.debug('Send Request {} to Janus server: {}'.format( send_msg, self.url)) self._ws_client.send_message(send_msg) response = transaction.wait_response(timeout=timeout) log.debug('Receive Response {} from Janus server: {}'.format( response, self.url)) return response finally: self._transactions.pop(transaction_id, None)
def post(self): data = { 'name': self._janus_server.server_name, 'url': self._janus_server.public_url, 'status': self._janus_server.status, 'start_time': self._janus_server.start_time, 'expire': self.expire, } if self._janus_server.session_num >= 0: data['session_num'] = int(self._janus_server.session_num) if self._janus_server.handle_num >= 0: data['handle_num'] = int(self._janus_server.handle_num) for i in range(len(self.post_urls)): url = self.post_urls[self._cur_index] self._cur_index += 1 if self._cur_index >= len(self.post_urls): self._cur_index = 0 try: r = self._post_session.post(url, data=data, timeout=self._http_timeout) if r.status_code == requests.codes.ok: return True else: raise JanusCloudError( 'HTTP Return error (Status code: {}, text: {})'.format( r.status_code, r.text), r.status_code) except Exception as e: log.warning('Http post failed for url {}: {}'.format(url, e)) pass return False
def start(self): """ start the watcher and launch its process """ if self._started: return if self._popen is not None: # other greenlet is stopping this watcher raise JanusCloudError("ProcWatcher (%d) in Stopping" % self.wid, 500) try: self._launch_process() # start up the process self.auto_restart_count = 0 self._started = True # spawn a poll greenlet to watch it self._poll_greenlet = gevent.spawn(self._polling_run, weakref.ref(self)) except Exception: self._started = False self._poll_greenlet = None if self._popen is not None: self._popen.kill() self._on_process_terminate(-1) raise
def handle_hangup(self): if self.backend_handle is None: raise JanusCloudError('backend handle invalid', JANUS_ERROR_BAD_GATEWAY) # log.info('handle_hangup for echotest Handle {}'.format(self.handle_id)) self.backend_handle.send_hangup()
def _handle_async_message(self, transaction, body, jsep): try: if self.backend_handle is None: raise JanusCloudError('backend handle invalid', JANUS_ERROR_BAD_GATEWAY) data, reply_jsep = self.backend_handle.send_message(body=body, jsep=jsep) self._push_plugin_event(data, reply_jsep, transaction) except JanusCloudError as e: log.exception('Fail to send message to backend handle {}'.format( self.backend_handle.handle_id)) self._push_plugin_event( { 'echotest': 'event', 'error_code': e.code, 'error': str(e), }, transaction=transaction) except Exception as e: log.exception('Fail to send message to backend handle {}'.format( self.backend_handle.handle_id)) self._push_plugin_event( { 'echotest': 'event', 'error_code': JANUS_ERROR_BAD_GATEWAY, 'error': str(e), }, transaction=transaction)
def add(self, video_call_user): if video_call_user.username in self._users_by_name: raise JanusCloudError( 'videocall user {} already in repo'.format( video_call_user.name), JANUS_ERROR_CONFLICT) self._users_by_name[video_call_user.username] = copy.copy( video_call_user)
def handle_trickle(self, candidate=None, candidates=None): if self.backend_handle is None: raise JanusCloudError('backend handle invalid', JANUS_ERROR_BAD_GATEWAY) # log.debug('handle_trickle for echotest handle {}.candidate:{} candidates:{}'. # format(self.handle_id, candidate, candidates)) self.backend_handle.send_trickle(candidate=candidate, candidates=candidates)
def _handle_destroy(self, request): if request.session_id == 0: raise JanusCloudError( "Unhandled request '{}' at this path".format(request.janus), JANUS_ERROR_INVALID_REQUEST_PATH) self._frontend_session_mgr.destroy_session(request.session_id) return create_janus_msg('success', request.session_id, request.transaction)
def add_poster(janus_server, post_type, name='', *args, **kwargs): poster_class = _poster_types.get(post_type) if poster_class is None: raise JanusCloudError('poster type {} not register'.format(post_type), JANUS_ERROR_NOT_IMPLEMENTED) poster = poster_class(janus_server, post_type, name, *args, **kwargs) _posters.append(poster) return poster