def __init__(self): def _action_continue(val): return val not in [ GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED ] def _action_accum(previous, val): # we want to always keep any errors propagated if val in [ GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED ]: return val if previous in [ GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED ]: return previous # we want to always prefer async returns if previous in [ GstValidate.ActionReturn.ASYNC, GstValidate.ActionReturn.INTERLACED ]: return previous if val in [ GstValidate.ActionReturn.ASYNC, GstValidate.ActionReturn.INTERLACED ]: return val return val self.action = Signal(_action_continue, _action_accum)
def __init__(self, server): # server string to connect to. Passed directly to websockets.connect() self.server = server # fired after we have connected to the signalling server self.wss_connected = Signal() # fired every time we receive a message from the signalling server self.message = Signal() self._init_async()
class WebRTCSignallingClient(SignallingClientThread): """ Signalling client implementation. Deals wit session management over the signalling protocol. Sends and receives from a peer. """ def __init__(self, server, id_): super().__init__(server) self.wss_connected.connect(self._on_connection) self.message.connect(self._on_message) self.state = SignallingState.NEW self._state_observer = StateObserver(self, "state", threading.Condition()) self.id = id_ self._peerid = None # fired when the hello has been received self.connected = Signal() # fired when the signalling server responds that the session creation is ok self.session_created = Signal() # fired on an error self.error = Signal() # fired when the peer receives some json data self.have_json = Signal() def _update_state(self, new_state): self._state_observer.update (new_state) def wait_for_states(self, states): return self._state_observer.wait_for (states) def hello(self): self.send('HELLO ' + str(self.id)) l.info("sent HELLO") self.wait_for_states([SignallingState.HELLO]) def create_session(self, peerid): self._peerid = peerid self.send('SESSION {}'.format(self._peerid)) l.info("sent SESSION") self.wait_for_states([SignallingState.SESSION]) def _on_connection(self): self._update_state (SignallingState.OPEN) def _on_message(self, message): l.debug("received: " + message) if message == 'HELLO': self._update_state (SignallingState.HELLO) self.connected.fire() elif message == 'SESSION_OK': self._update_state (SignallingState.SESSION) self.session_created.fire() elif message.startswith('ERROR'): self._update_state (SignallingState.ERROR) self.error.fire(message) else: msg = json.loads(message) self.have_json.fire(msg) return False
def __init__(self, server, id_): super().__init__(server) self.wss_connected.connect(self._on_connection) self.message.connect(self._on_message) self.state = SignallingState.NEW self._state_observer = StateObserver(self, "state", threading.Condition()) self.id = id_ self._peerid = None # fired when the hello has been received self.connected = Signal() # fired when the signalling server responds that the session creation is ok self.session_created = Signal() # fired on an error self.error = Signal() # fired when the peer receives some json data self.have_json = Signal()
def __init__(self, element): WebRTCObserver.__init__(self) self.element = element self.signal_handlers = [] self.signal_handlers.append( element.connect("on-negotiation-needed", self._on_negotiation_needed)) self.signal_handlers.append( element.connect("on-ice-candidate", self._on_ice_candidate)) self.signal_handlers.append( element.connect("pad-added", self._on_pad_added)) self.signal_handlers.append( element.connect("on-new-transceiver", self._on_new_transceiver)) self.signal_handlers.append( element.connect("on-data-channel", self._on_data_channel)) self.negotiation_needed = 0 self._negotiation_needed_observer = StateObserver( self, "negotiation_needed", threading.Condition()) self.on_negotiation_needed = Signal() self.on_ice_candidate = Signal() self.on_pad_added = Signal() self.on_new_transceiver = Signal()
def __init__(self, name, max_threads): super().__init__(name=name) self.on_progress = Signal('on progress') self.on_finish = Signal('on finish') self.on_new_task = Signal('new task') self.__limit = max_threads if max_threads <= cpu_count( ) else cpu_count() self.__tasks = Queue() self.__size = 0 self.__active_tasks = [] self.__internal_state = Event() self.__abort = False self.__quite = False self.__hard_exit = False self.start()
class TaskSheduler(Thread): def __init__(self, name, max_threads): super().__init__(name=name) self.on_progress = Signal('on progress') self.on_finish = Signal('on finish') self.on_new_task = Signal('new task') self.__limit = max_threads if max_threads <= cpu_count( ) else cpu_count() self.__tasks = Queue() self.__size = 0 self.__active_tasks = [] self.__internal_state = Event() self.__abort = False self.__quite = False self.__hard_exit = False self.start() def add_task(self, task): self.__tasks.put(task) self.__size += 1 self.__internal_state.set() def add_tasks(self, tasks): for task in tasks: self.__tasks.put(task) self.__size += len(tasks) self.__internal_state.set() def abort(self): self.__abort = True def quite(self): self.__quite = True self.__internal_state.set() def hard_exit(self): self.__hard_exit = True self.__internal_state.set() def run(self): while True: self.__internal_state.wait() while self.__tasks.unfinished_tasks > 0: if self._start_new(): self._start_new_task() self.__active_tasks = self._get_active_tasks() if self.__abort or self.__hard_exit: self.__abort = False break self.__size = 0 self.__tasks = Queue() self.__internal_state.clear() self.on_finish.fire('Returning to idle') if self.__hard_exit or self.__quite: break def _start_new_task(self): task = self.__tasks.get_nowait() task.start() self.on_new_task.fire(started=task) self.__active_tasks.append(task) def _get_active_tasks(self): active = [] finished = [] for task in self.__active_tasks: if task.is_alive(): active.append(task) else: finished.append(task) self.__tasks.task_done() if finished: self.on_progress.fire(total=self.__size, pending=self.__tasks.unfinished_tasks, queue_size=self.__tasks.qsize(), finished=finished) return active def _start_new(self): if len(self.__active_tasks) <= self.__limit and not self.__tasks.empty( ): return True return False def __repr__(self): return '<{}(name={}, max_threads={}) at {}>'.format( self.__class__.__name__, self.name, self.__limit, hex(id(self)))
class SignallingClientThread(object): """ Connect to a signalling server """ def __init__(self, server): # server string to connect to. Passed directly to websockets.connect() self.server = server # fired after we have connected to the signalling server self.wss_connected = Signal() # fired every time we receive a message from the signalling server self.message = Signal() self._init_async() def _init_async(self): self._running = False self.conn = None self._loop = asyncio.new_event_loop() self._thread = AsyncIOThread(self._loop) self._thread.start() self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(self._a_loop())) async def _a_connect(self): # connect to the signalling server assert not self.conn sslctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) self.conn = await websockets.connect(self.server, ssl=sslctx) async def _a_loop(self): self._running = True l.info('loop started') await self._a_connect() self.wss_connected.fire() assert self.conn async for message in self.conn: self.message.fire(message) l.info('loop exited') def send(self, data): # send some information to the peer async def _a_send(): await self.conn.send(data) self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_a_send())) def stop(self): if self._running == False: return cond = threading.Condition() # asyncio, why you so complicated to stop ? tasks = asyncio.all_tasks(self._loop) async def _a_stop(): if self.conn: await self.conn.close() self.conn = None to_wait = [t for t in tasks if not t.done()] if to_wait: l.info('waiting for ' + str(to_wait)) done, pending = await asyncio.wait(to_wait) with cond: l.error('notifying cond') cond.notify() self._running = False with cond: self._loop.call_soon_threadsafe(lambda: asyncio.ensure_future(_a_stop())) l.error('cond waiting') cond.wait() l.error('cond waited') self._thread.stop_thread() self._thread.join() l.error('thread joined')
class ActionObserver(object): def __init__(self): def _action_continue(val): return val not in [ GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED ] def _action_accum(previous, val): # we want to always keep any errors propagated if val in [ GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED ]: return val if previous in [ GstValidate.ActionReturn.ERROR, GstValidate.ActionReturn.ERROR_REPORTED ]: return previous # we want to always prefer async returns if previous in [ GstValidate.ActionReturn.ASYNC, GstValidate.ActionReturn.INTERLACED ]: return previous if val in [ GstValidate.ActionReturn.ASYNC, GstValidate.ActionReturn.INTERLACED ]: return val return val self.action = Signal(_action_continue, _action_accum) def _action(self, scenario, action): l.debug('executing action: ' + str(action.structure)) return self.action.fire(Actions(action.structure.get_name()), action) def register_action_types(observer): if not isinstance(observer, ActionObserver): raise TypeError GstValidate.register_action_type( Actions.CREATE_OFFER.value, "webrtc", observer._action, None, "Instruct a create-offer to commence", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type(Actions.CREATE_ANSWER.value, "webrtc", observer._action, None, "Create answer", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type( Actions.WAIT_FOR_NEGOTIATION_STATE.value, "webrtc", observer._action, None, "Wait for a specific negotiation state to be reached", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type(Actions.ADD_STREAM.value, "webrtc", observer._action, None, "Add a stream to the webrtcbin", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type( Actions.ADD_DATA_CHANNEL.value, "webrtc", observer._action, None, "Add a data channel to the webrtcbin", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type( Actions.SEND_DATA_CHANNEL_STRING.value, "webrtc", observer._action, None, "Send a message using a data channel", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type( Actions.WAIT_FOR_DATA_CHANNEL_STATE.value, "webrtc", observer._action, None, "Wait for data channel to reach state", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type(Actions.CLOSE_DATA_CHANNEL.value, "webrtc", observer._action, None, "Close a data channel", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type(Actions.WAIT_FOR_DATA_CHANNEL.value, "webrtc", observer._action, None, "Wait for a data channel to appear", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type( Actions.WAIT_FOR_DATA_CHANNEL_STRING.value, "webrtc", observer._action, None, "Wait for a data channel to receive a message", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type( Actions.WAIT_FOR_NEGOTIATION_NEEDED.value, "webrtc", observer._action, None, "Wait for a the on-negotiation-needed signal to fire", GstValidate.ActionTypeFlags.NONE) GstValidate.register_action_type(Actions.SET_WEBRTC_OPTIONS.value, "webrtc", observer._action, None, "Set some webrtc options", GstValidate.ActionTypeFlags.NONE)
class WebRTCBinObserver(WebRTCObserver): """ Observe a webrtcbin element. """ def __init__(self, element): WebRTCObserver.__init__(self) self.element = element self.signal_handlers = [] self.signal_handlers.append( element.connect("on-negotiation-needed", self._on_negotiation_needed)) self.signal_handlers.append( element.connect("on-ice-candidate", self._on_ice_candidate)) self.signal_handlers.append( element.connect("pad-added", self._on_pad_added)) self.signal_handlers.append( element.connect("on-new-transceiver", self._on_new_transceiver)) self.signal_handlers.append( element.connect("on-data-channel", self._on_data_channel)) self.negotiation_needed = 0 self._negotiation_needed_observer = StateObserver( self, "negotiation_needed", threading.Condition()) self.on_negotiation_needed = Signal() self.on_ice_candidate = Signal() self.on_pad_added = Signal() self.on_new_transceiver = Signal() def _on_negotiation_needed(self, element): self.negotiation_needed += 1 self._negotiation_needed_observer.update(self.negotiation_needed) self.on_negotiation_needed.fire() def _on_ice_candidate(self, element, mline, candidate): self.on_ice_candidate.fire(mline, candidate) def _on_pad_added(self, element, pad): self.on_pad_added.fire(pad) def _on_description_set(self, promise, desc): new_state = self._update_negotiation_from_description_state(desc) if new_state == NegotiationState.OFFER_SET: self.on_offer_set.fire(desc) elif new_state == NegotiationState.ANSWER_SET: self.on_answer_set.fire(desc) def _on_new_transceiver(self, element, transceiver): self.on_new_transceiver.fire(transceiver) def _on_data_channel(self, element, channel): observer = WebRTCBinDataChannelObserver(channel, channel.props.label, 'remote') self.add_channel(observer) def _update_negotiation_from_description_state(self, desc): new_state = None if desc.type == GstWebRTC.WebRTCSDPType.OFFER: new_state = NegotiationState.OFFER_SET elif desc.type == GstWebRTC.WebRTCSDPType.ANSWER: new_state = NegotiationState.ANSWER_SET assert new_state is not None self._update_negotiation_state(new_state) return new_state def _deepcopy_session_description(self, desc): # XXX: passing 'offer' to both a promise and an action signal without # a deepcopy will segfault... new_sdp = GstSdp.SDPMessage.new()[1] GstSdp.sdp_message_parse_buffer(bytes(desc.sdp.as_text().encode()), new_sdp) return GstWebRTC.WebRTCSessionDescription.new(desc.type, new_sdp) def _on_offer_created(self, promise, element): self._update_negotiation_state(NegotiationState.OFFER_CREATED) reply = promise.get_reply() offer = reply['offer'] new_offer = self._deepcopy_session_description(offer) promise = Gst.Promise.new_with_change_func(self._on_description_set, new_offer) new_offer = self._deepcopy_session_description(offer) self.element.emit('set-local-description', new_offer, promise) self.on_offer_created.fire(offer) def _on_answer_created(self, promise, element): self._update_negotiation_state(NegotiationState.ANSWER_CREATED) reply = promise.get_reply() offer = reply['answer'] new_offer = self._deepcopy_session_description(offer) promise = Gst.Promise.new_with_change_func(self._on_description_set, new_offer) new_offer = self._deepcopy_session_description(offer) self.element.emit('set-local-description', new_offer, promise) self.on_answer_created.fire(offer) def create_offer(self, options=None): promise = Gst.Promise.new_with_change_func(self._on_offer_created, self.element) self.element.emit('create-offer', options, promise) def create_answer(self, options=None): promise = Gst.Promise.new_with_change_func(self._on_answer_created, self.element) self.element.emit('create-answer', options, promise) def set_remote_description(self, desc): promise = Gst.Promise.new_with_change_func(self._on_description_set, desc) self.element.emit('set-remote-description', desc, promise) def add_ice_candidate(self, mline, candidate): self.element.emit('add-ice-candidate', mline, candidate) def add_data_channel(self, ident): channel = self.element.emit('create-data-channel', ident, None) observer = WebRTCBinDataChannelObserver(channel, ident, 'local') self.add_channel(observer) def wait_for_negotiation_needed(self, generation): self._negotiation_needed_observer.wait_for((generation, ))