def query_stat(self): try: if self._admin_ws_client is None: self._admin_ws_client = WSClient(self.admin_url, self._recv_msg_cbk, None, protocols=['janus-admin-protocol']) common_args = {} if self._admin_secret: common_args['admin_secret'] = self._admin_secret response = self.send_request(self._admin_ws_client, create_janus_msg('list_sessions', **common_args)) sessions = response.get('sessions', []) handles = [] for session_id in sessions: response = self.send_request(self._admin_ws_client, create_janus_msg('list_handles', session_id=session_id, **common_args)) handles.extend(response.get('handles', [])) self.set_stat(session_num=len(sessions), handle_num=len(handles)) except Exception as e: if self._has_destroy: return log.warning('Calculate stat of janus server({}) failed: {}'.format(self.admin_url, e)) self.set_stat(session_num=-1, handle_num=-1) # stop post statistic if self._admin_ws_client: try: self._admin_ws_client.close() except Exception: pass self._admin_ws_client = None
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 detach(self): """ detach this handle from the session return: no value note: no exception would be raised """ if self._has_detach: return self._has_detach = True # stop async event greenlet if not self._async_event_queue.full(): self._async_event_queue.put(stop_message) self._async_event_greenlet = None if self._session: self._session.on_handle_detached(self.handle_id) try: detach_message = create_janus_msg('detach', handle_id=self.handle_id) self._session.send_request(detach_message) except Exception: log.exception('Detach backend handle {} error'.format(self.handle_id)) self._session = None
def pingpong(self): try: if self._ws_client is None: self._ws_client = WSClient(self.url, self._recv_msg_cbk, self._close_cbk, protocols=['janus-protocol']) ping_start_ts = get_monotonic_time() self.send_request(self._ws_client, create_janus_msg('ping')) ping_end_ts = get_monotonic_time() ping_latency = ping_end_ts - ping_start_ts if self._hwm_threshold and ping_latency > self._hwm_threshold: self.set_status(JANUS_SERVER_STATUS_HWM) else: self.set_status(JANUS_SERVER_STATUS_NORMAL) except Exception as e: if self._has_destroy: return log.warning('Poll janus server({}) failed: {}'.format(self.url, e)) self.set_status(JANUS_SERVER_STATUS_ABNORMAL) if self._ws_client: try: self._ws_client.close() except Exception: pass self._ws_client = 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_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 _push_event(self, method, transaction=None, **kwargs): if self._has_destroy: return event = create_janus_msg(method, self._session.session_id, transaction, **kwargs) event['sender'] = self.handle_id if self.opaque_id: event['opaque_id'] = self.opaque_id self._session.notify_event(event)
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 _kick_timeout_sessions(self, session): session_id = session.session_id transport = session.ts session.notify_event(create_janus_msg('timeout', session_id)) session.destroy() if transport: # notify the transport transport.session_over(session_id, True, False)
def _send_plugin_event(self, to_user, data, jsep=None): params = dict() params['plugindata'] = { 'plugin': self.plugin_package_name, 'data': data } if jsep: params['jsep'] = jsep event = create_janus_msg('event', **params) self._send_aync_event(to_user, event)
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 _handle_message(self, request): message_params_schema = Schema({ 'body': dict, Optional('jsep'): dict, AutoDel(str): object # for all other key we don't care }) params = message_params_schema.validate(request.message) # dispatch to plugin handle handle = self._get_plugin_handle(request) result, content = handle.handle_message(request.transaction, **params) if result == JANUS_PLUGIN_OK: if content is None or not isinstance(content, dict): raise JanusCloudError( "Plugin didn't provide any content for this synchronous response" if content is None else "Plugin returned an invalid JSON response", JANUS_ERROR_PLUGIN_MESSAGE) response = create_janus_msg('success', request.session_id, request.transaction, sender=request.handle_id) if handle.opaque_id: response['opaque_id'] = handle.opaque_id response['plugindata'] = { 'plugin': handle.plugin_package_name, 'data': content } elif result == JANUS_PLUGIN_OK_WAIT: response = create_janus_msg('ack', request.session_id, request.transaction) if content: response['hint'] = content else: raise JanusCloudError('Plugin returned a severe (unknown) error', JANUS_ERROR_PLUGIN_MESSAGE) return response
def _handle_attach(self, request): session = self._get_session(request) attach_params_schema = Schema({ 'plugin': StrVal(max_len=64), Optional('opaque_id'): StrVal(max_len=64), AutoDel(str): object # for all other key we don't care }) params = attach_params_schema.validate(request.message) handle = session.attach_handle(**params) return create_janus_msg('success', request.session_id, request.transaction, data={'id': handle.handle_id})
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 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 handle_trickle(self, candidate=None, candidates=None): log.debug('handle_trickle for p2pcall handle {}.candidate:{} candidates:{}'. format(self.handle_id, candidate, candidates)) if candidate: if candidates: candidates.append(candidate) else: candidates = [candidate] if self.p2pcall_user is None or self.p2pcall_user.incall is False or self._trickle_holding: self._pending_candidates.extend(candidates) else: trickle_msg = create_janus_msg('trickle', candidates=candidates) self._send_aync_event(self.p2pcall_user.peer_name, trickle_msg)
def async_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) self._session.async_send_request(message)
def _handle_create(self, request): create_params_schema = Schema({ Optional('id'): IntVal(min=1, max=9007199254740992), AutoDel(str): object # for all other key we don't care }) params = create_params_schema.validate(request.message) session_id = params.get('id', 0) session = self._frontend_session_mgr.create_new_session( session_id, request.transport) return create_janus_msg('success', 0, request.transaction, data={'id': session.session_id})
def _keepalive_routine(self): gevent.sleep(self._keepalive_interval) keepalive_msg = create_janus_msg('keepalive') while self.state == BACKEND_SESSION_STATE_ACTIVE: try: # if there is no handle existed and auto destroy is enabled, just schedule the destroy route if not self._handles: if self._auto_destroy and self._auto_destroy_greenlet is None: self._auto_destroy_greenlet = gevent.spawn_later( self._auto_destroy, self._auto_destroy_routine) self.send_request(keepalive_msg, ignore_ack=False) except Exception as e: log.exception('Keepalive failed for backend session {}'.format( self.url)) self.destroy() else: gevent.sleep(self._keepalive_interval)
def detach(self): if self._has_destroy: return self._has_destroy = True # stop async message greenlet if not self._async_message_queue.full(): self._async_message_queue.put(stop_message) self._async_message_greenlet = None # send detach event event = create_janus_msg('detached', self._session.session_id) event['sender'] = self.handle_id if self.opaque_id: event['opaque_id'] = self.opaque_id self._session.notify_event(event) log.info('handle {} is detach from plugin {}'.format(self.handle_id, self.plugin_package_name))
def _handle_info(self, request): reply = create_janus_msg('server_info', 0, request.transaction) reply['name'] = 'Janus-Cloud Proxy' reply['author'] = 'OpenSight' reply['email'] = '*****@*****.**' reply['website'] = 'https://github.com/OpenSight/janus-cloud' reply['server_name'] = self._proxy_conf.get('general', {}).get('server_name', '') reply['session-timeout'] = self._proxy_conf.get('general', {}).get( 'session_timeout', 60) plugin_info_list = {} for plugin in get_plugin_list(): plugin_info = { 'version_string': plugin.get_version_string(), 'description': plugin.get_description(), 'author': plugin.get_author(), 'name': plugin.get_name(), 'version': plugin.get_version(), } plugin_info_list[plugin.get_package()] = plugin_info reply['plugins'] = plugin_info_list return reply
def _handle_hangup(self, request): handle = self._get_plugin_handle(request) handle.handle_hangup() return create_janus_msg('success', request.session_id, request.transaction)
def _handle_detach(self, request): self._get_plugin_handle(request) # check handle exist session = self._get_session(request) session.detach_handle(request.handle_id) return create_janus_msg('success', request.session_id, request.transaction)
def _handle_ping(self, request): return create_janus_msg('pong', 0, request.transaction)
def _handle_async_message(self, transaction, body, jsep): try: result = None request = body.get('request') if request is None: raise JanusCloudError( 'Request {} format invalid'.format(body), JANUS_P2PCALL_ERROR_INVALID_ELEMENT) if request == 'list': username_list = self._plugin.user_dao.get_username_list() result = {'list': username_list} elif request == 'register': if self.p2pcall_user: raise JanusCloudError( 'Already registered ({})'.format( self.p2pcall_user.username), JANUS_P2PCALL_ERROR_ALREADY_REGISTERED) body = username_schema.validate(body) username = body['username'] # valid, register this new user api_url = self._plugin.api_base_url + '/' + username new_p2pcall_user = P2PCallUser(username, handle=self, api_url=api_url) self._plugin.user_dao.add(new_p2pcall_user) self.p2pcall_user = new_p2pcall_user result = {'event': 'registered', 'username': username} elif request == 'call': if self.p2pcall_user is None: raise JanusCloudError('Register a username first', JANUS_P2PCALL_ERROR_REGISTER_FIRST) if self.p2pcall_user.incall: raise JanusCloudError('Already in a call', JANUS_P2PCALL_ERROR_ALREADY_IN_CALL) body = username_schema.validate(body) username = body['username'] if username == self.p2pcall_user.username: raise JanusCloudError( 'You can\'t call yourself... use the EchoTest for that', JANUS_P2PCALL_ERROR_USE_ECHO_TEST) peer = self._plugin.user_dao.get_by_username(username) if peer is None: raise JanusCloudError( 'Username \'{}\' doesn\'t exist'.format(username), JANUS_P2PCALL_ERROR_NO_SUCH_USERNAME) if jsep is None: raise JanusCloudError('Missing SDP', JANUS_P2PCALL_ERROR_MISSING_SDP) if peer.incall: log.debug('{} is busy'.format(username)) result = { 'event': 'hangup', 'username': self.p2pcall_user.username, 'reason': 'User busy' } else: # start the calling process try: self.p2pcall_user.peer_name = username self.p2pcall_user.incall = True self.p2pcall_user.utime = time.time() self._trickle_holding = True # buffer the trickle candidates util # peer receiving incoming call event # update the user dao self._plugin.user_dao.update(self.p2pcall_user) # send the imcomingcall event to the peer call = { 'videocall': 'event', 'result': { 'event': 'incomingcall', 'username': self.p2pcall_user.username } } self._send_plugin_event(username, call, jsep) self._trickle_holding = False # send the buffer candidates if len(self._pending_candidates) > 0: candidates = self._pending_candidates self._pending_candidates.clear() trickle_msg = create_janus_msg( 'trickle', candidates=candidates) self._send_aync_event(self.p2pcall_user.peer_name, trickle_msg) result = {'event': 'calling'} except Exception: self.p2pcall_user.peer_name = '' self.p2pcall_user.incall = False self._trickle_holding = False # update the user dao self._plugin.user_dao.update(self.p2pcall_user) raise elif request == 'accept': if self.p2pcall_user is None or self.p2pcall_user.incall is False \ or self.p2pcall_user.peer_name == '': raise JanusCloudError('No incoming call to accept', JANUS_P2PCALL_ERROR_NO_CALL) if jsep is None: raise JanusCloudError('Missing SDP', JANUS_P2PCALL_ERROR_MISSING_SDP) # send the accepted event to the peer call = { 'videocall': 'event', 'result': { 'event': 'accepted', 'username': self.p2pcall_user.username } } self._send_plugin_event(self.p2pcall_user.peer_name, call, jsep) result = {'event': 'accepted'} elif request == 'set': if self.p2pcall_user is None or self.p2pcall_user.incall is False \ or self.p2pcall_user.peer_name == '': raise JanusCloudError('Not in call', JANUS_P2PCALL_ERROR_NO_CALL) if jsep is None: raise JanusCloudError('Missing SDP', JANUS_P2PCALL_ERROR_MISSING_SDP) # send the accepted event to the peer call = { 'videocall': 'event', 'result': { 'event': 'update', } } self._send_plugin_event(self.p2pcall_user.peer_name, call, jsep) result = {'event': 'set'} elif request == 'hangup': reason = str(body.get('reason', 'We did the hangup')) if self.p2pcall_user and self.p2pcall_user.incall: peer_name = self.p2pcall_user.peer_name self.p2pcall_user.peer_name = '' self.p2pcall_user.incall = False self.p2pcall_user.utime = time.time() self._plugin.user_dao.update(self.p2pcall_user) try: call = { 'videocall': 'event', 'result': { 'event': 'hangup', 'username': self.p2pcall_user.username, 'reason': reason } } self._send_plugin_event(peer_name, call, jsep) except Exception: log.warning( 'fail to hangup to \'{}\''.format(peer_name)) log.debug("{} is hanging up the call with {}".format( self.p2pcall_user.username, peer_name)) else: log.warning('No call to hangup') if self.p2pcall_user: username = self.p2pcall_user.username else: username = '******' result = { 'event': 'hangup', 'username': username, 'reason': 'Explicit hangup' } else: log.error('unknown request {}'.format(request)) raise JanusCloudError('Unknown request {{}}'.format(request), JANUS_P2PCALL_ERROR_INVALID_REQUEST) # Process successfully data = { 'videocall': 'event', } if result: data['result'] = result self._push_plugin_event(data, transaction=transaction) if result and result.get('event') == 'accepted': self._push_event('webrtcup') except JanusCloudError as e: log.exception( 'Fail to handle async message ({}) for handle {}'.format( body, self.handle_id)) self._push_plugin_event( { 'videocall': 'event', 'error_code': e.code, 'error': str(e), }, transaction=transaction) except SchemaError as e: log.exception('invalid message format ({}) for handle {}'.format( body, self.handle_id)) self._push_plugin_event( { 'videocall': 'event', 'error_code': JANUS_P2PCALL_ERROR_INVALID_ELEMENT, 'error': str(e), }, transaction=transaction) except Exception as e: log.exception( 'Fail to handle async message ({}) for handle {}'.format( body, self.handle_id)) self._push_plugin_event( { 'videocall': 'event', 'error_code': JANUS_ERROR_BAD_GATEWAY, 'error': str(e), }, transaction=transaction)
def _handle_keepalive(self, request): log.debug('Got a keep-alive on session {0}'.format(request.session_id)) session = self._get_session(request) return create_janus_msg('ack', request.session_id, request.transaction)
def handle_hangup(self): log.debug('handle_hangup for p2pcall Handle {}'.format(self.handle_id)) if self.p2pcall_user and self.p2pcall_user.incall: hangup_msg = create_janus_msg('hangup', reason='Peer Hangup') self._send_aync_event(self.p2pcall_user.peer_name, hangup_msg)
def _handle_claim(self, request): session = self._get_session(request) session.transport_claim(request.transport) return create_janus_msg('success', request.session_id, request.transaction)