def __init__(self, session, audio_stream): self.session = session self.stream = audio_stream self.start_time = None notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.stream) self.beep = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get('answering_machine_tone.wav')) notification_center.add_observer(self, sender=self.beep) message_wav = SIPSimpleSettings().answering_machine.unavailable_message if message_wav: self.unavailable_message = WavePlayer( SIPApplication.voice_audio_mixer, message_wav.sound_file.path, message_wav.sound_file.volume, 1, 2, False) notification_center.add_observer(self, sender=self.unavailable_message) self.stream.bridge.add(self.unavailable_message) else: self.unavailable_message = None self.stream.bridge.add(self.beep) self.stream.device.input_muted = True
def _NH_AudioStreamGotDTMF(self, notification): digit_map = {'*': 'star'} filename = 'sounds/dtmf_%s_tone.wav' % digit_map.get(notification.data.digit, notification.data.digit) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get(filename)) notification.center.add_observer(self, sender=player) SIPApplication.voice_audio_bridge.add(player) player.start()
def capture(self): try: self.image = self.vncclient.image.copy() except AttributeError: pass else: player = WavePlayer(SIPApplication.alert_audio_bridge.mixer, Resources.get('sounds/screenshot.wav'), volume=30) SIPApplication.alert_audio_bridge.add(player) player.start()
def play_audio_welcome(self, welcome_prompt): try: audio_stream = next(stream for stream in self.session.streams if stream.type == 'audio') except StopIteration: return player = WavePlayer(audio_stream.mixer, '', pause_time=1, initial_delay=1, volume=50) audio_stream.bridge.add(player) try: if welcome_prompt: file = Resources.get('sounds/co_welcome_conference.wav') self.play_file_in_player(player, file, 1) user_count = len({ str(s.remote_identity.uri) for s in self.room.sessions if s.remote_identity.uri != self.session.remote_identity.uri and any(stream for stream in s.streams if stream.type == 'audio') }) if user_count == 0: file = Resources.get('sounds/co_only_one.wav') self.play_file_in_player(player, file, 0.5) elif user_count == 1: file = Resources.get('sounds/co_there_is_one.wav') self.play_file_in_player(player, file, 0.5) elif user_count < 100: file = Resources.get('sounds/co_there_are.wav') self.play_file_in_player(player, file, 0.2) if user_count <= 24: file = Resources.get('sounds/bi_%d.wav' % user_count) self.play_file_in_player(player, file, 0.1) else: file = Resources.get('sounds/bi_%d0.wav' % (user_count / 10)) self.play_file_in_player(player, file, 0.1) file = Resources.get('sounds/bi_%d.wav' % (user_count % 10)) self.play_file_in_player(player, file, 0.1) file = Resources.get('sounds/co_more_participants.wav') self.play_file_in_player(player, file, 0) file = Resources.get('sounds/connected_tone.wav') self.play_file_in_player(player, file, 0.1) except proc.ProcExit: # No need to remove the bridge from the stream, it's done automatically pass else: audio_stream.bridge.remove(player) self.room.audio_conference.add(audio_stream) self.room.audio_conference.unhold() finally: player.stop()
def play_hangup(self): settings = SIPSimpleSettings() if settings.audio.silent: return if time.time() - self.last_hangup_tone_time > HANGUP_TONE_THROTLE_DELAY: hangup_tone = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get('hangup_tone.wav'), volume=30) NotificationCenter().add_observer(self, sender=hangup_tone, name="WavePlayerDidEnd") SIPApplication.voice_audio_bridge.add(hangup_tone) hangup_tone.start() self.last_hangup_tone_time = time.time()
def _NH_SIPSessionDidStart(self, notification): log.msg('Session started') session = notification.sender audio_stream = session.streams[0] prompt = os.path.realpath(os.path.join(os.path.dirname(__file__), 'jamesbond.wav')) player = WavePlayer(audio_stream.mixer, prompt, pause_time=1, initial_play=False) audio_stream.bridge.add(player) try: player.play().wait() except WavePlayerError: pass audio_stream.bridge.remove(player) session.end()
def ringtone(self): if 'ringtone' not in self.__dict__: if set(self.proposed_streams).intersection(['audio', 'video', 'desktop-sharing']): sound_file = self.session.account.sounds.inbound_ringtone if sound_file is not None and sound_file.path is DefaultPath: settings = SIPSimpleSettings() sound_file = settings.sounds.inbound_ringtone ringtone = WavePlayer(SIPApplication.alert_audio_mixer, sound_file.path, volume=sound_file.volume, loop_count=0, pause_time=2.7) if sound_file is not None else Null ringtone.bridge = SIPApplication.alert_audio_bridge else: ringtone = WavePlayer(SIPApplication.alert_audio_mixer, Resources.get('sounds/beeping_ringtone.wav'), volume=70, loop_count=0, pause_time=5) ringtone.bridge = SIPApplication.alert_audio_bridge self.__dict__['ringtone'] = ringtone return self.__dict__['ringtone']
def _cleanup(self): if self.timer.running: self.timer.stop() self.notification_center.remove_observer(self, sender=self.session) for k in self.streams.keys(): self._set_stream(k, None) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get('sounds/hangup_tone.wav'), volume=60) self.notification_center.add_observer(self, sender=player) SIPApplication.voice_audio_bridge.add(player) player.start() self.notification_center.post_notification('SessionItemDidEnd', sender=self)
def send_dtmf(self, digit): if self.audio_stream is not None: try: self.audio_stream.send_dtmf(digit) except RuntimeError: pass else: digit_map = {'*': 'star'} filename = 'sounds/dtmf_%s_tone.wav' % digit_map.get(digit, digit) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get(filename)) self.notification_center.add_observer(self, sender=player) if self.session.account.rtp.inband_dtmf: self.audio_stream.bridge.add(player) SIPApplication.voice_audio_bridge.add(player) player.start()
def _play(self): config = get_config('%s@%s' % (self.session.request_uri.user, self.session.request_uri.host)) if config is None: config = get_config('%s' % self.session.request_uri.user) try: audio_stream = next(stream for stream in self.session.streams if stream.type=='audio') except StopIteration: self.proc = None return player = WavePlayer(audio_stream.mixer, config.file) audio_stream.bridge.add(player) log.msg(u"Playing file %s for session %s" % (config.file, self.session.call_id)) try: player.play().wait() except (ValueError, WavePlayerError), e: log.warning(u"Error playing file %s: %s" % (config.file, e))
def play_hangup(self): settings = SIPSimpleSettings() if settings.audio.silent: return if time.time( ) - self.last_hangup_tone_time > HANGUP_TONE_THROTLE_DELAY: hangup_tone = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get('hangup_tone.wav'), volume=30) NotificationCenter().add_observer(self, sender=hangup_tone, name="WavePlayerDidEnd") SIPApplication.voice_audio_bridge.add(hangup_tone) hangup_tone.start() self.last_hangup_tone_time = time.time()
def _NH_SIPSessionDidStart(self, notification): log.msg('Session started') session = notification.sender audio_stream = session.streams[0] prompt = os.path.realpath( os.path.join(os.path.dirname(__file__), 'jamesbond.wav')) player = WavePlayer(audio_stream.mixer, prompt, pause_time=1, initial_play=False) audio_stream.bridge.add(player) try: player.play().wait() except WavePlayerError: pass audio_stream.bridge.remove(player) session.end()
def play_audio_welcome(self, welcome_prompt): try: audio_stream = next(stream for stream in self.session.streams if stream.type == 'audio') except StopIteration: return player = WavePlayer(audio_stream.mixer, '', pause_time=1, initial_delay=1, volume=50) audio_stream.bridge.add(player) try: if welcome_prompt: file = Resources.get('sounds/co_welcome_conference.wav') self.play_file_in_player(player, file, 1) user_count = len({str(s.remote_identity.uri) for s in self.room.sessions if s.remote_identity.uri != self.session.remote_identity.uri and any(stream for stream in s.streams if stream.type == 'audio')}) if user_count == 0: file = Resources.get('sounds/co_only_one.wav') self.play_file_in_player(player, file, 0.5) elif user_count == 1: file = Resources.get('sounds/co_there_is_one.wav') self.play_file_in_player(player, file, 0.5) elif user_count < 100: file = Resources.get('sounds/co_there_are.wav') self.play_file_in_player(player, file, 0.2) if user_count <= 24: file = Resources.get('sounds/bi_%d.wav' % user_count) self.play_file_in_player(player, file, 0.1) else: file = Resources.get('sounds/bi_%d0.wav' % (user_count / 10)) self.play_file_in_player(player, file, 0.1) file = Resources.get('sounds/bi_%d.wav' % (user_count % 10)) self.play_file_in_player(player, file, 0.1) file = Resources.get('sounds/co_more_participants.wav') self.play_file_in_player(player, file, 0) file = Resources.get('sounds/connected_tone.wav') self.play_file_in_player(player, file, 0.1) except proc.ProcExit: # No need to remove the bridge from the stream, it's done automatically pass else: audio_stream.bridge.remove(player) self.room.audio_conference.add(audio_stream) self.room.audio_conference.unhold() finally: player.stop()
def _play(self): config = get_config( '%s@%s' % (self.session.request_uri.user, self.session.request_uri.host)) if config is None: config = get_config('%s' % self.session.request_uri.user) try: audio_stream = next(stream for stream in self.session.streams if stream.type == 'audio') except StopIteration: self.proc = None return player = WavePlayer(audio_stream.mixer, config.file) audio_stream.bridge.add(player) log.msg(u"Playing file %s for session %s" % (config.file, self.session.call_id)) try: player.play().wait() except (ValueError, WavePlayerError), e: log.warning(u"Error playing file %s: %s" % (config.file, e))
def _set_stream(self, stream_type, stream): old_stream = self.streams.get(stream_type, None) self.streams[stream_type] = stream if old_stream is not None: self.notification_center.remove_observer(self, sender=old_stream) if stream_type == 'audio': self.hold_tone = Null if stream is not None: self.notification_center.add_observer(self, sender=stream) if stream_type == 'audio': self.hold_tone = WavePlayer(stream.bridge.mixer, Resources.get('sounds/hold_tone.wav'), loop_count=0, pause_time=45, volume=30) stream.bridge.add(self.hold_tone)
def __init__(self, session, audio_stream): self.session = session self.stream = audio_stream self.start_time = None notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.stream) self.beep = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get('answering_machine_tone.wav')) notification_center.add_observer(self, sender=self.beep) message_wav = SIPSimpleSettings().answering_machine.unavailable_message if message_wav: self.unavailable_message = WavePlayer(SIPApplication.voice_audio_mixer, message_wav.sound_file.path, message_wav.sound_file.volume, 1, 2, False) notification_center.add_observer(self, sender=self.unavailable_message) self.stream.bridge.add(self.unavailable_message) else: self.unavailable_message = None self.stream.bridge.add(self.beep) self.stream.device.input_muted = True
def _play(self): config = get_config( '%s@%s' % (self.session.request_uri.user, self.session.request_uri.host)) if config is None: config = get_config('%s' % self.session.request_uri.user) try: audio_stream = next(stream for stream in self.session.streams if stream.type == 'audio') except StopIteration: self.proc = None return player = WavePlayer(audio_stream.mixer, config.file) audio_stream.bridge.add(player) log.info(u'Playing file %s for session %s' % (config.file, self.session.call_id)) try: player.play().wait() except (ValueError, WavePlayerError) as e: log.warning(u'Error playing file %s: %s' % (config.file, e)) except proc.ProcExit: pass finally: player.stop() self.proc = None audio_stream.bridge.remove(player) self.session.end() self.session = None
def _NH_DNSLookupDidSucceed(self, notification): settings = SIPSimpleSettings() notification.center.remove_observer(self, sender=notification.sender) if self.pending_removal: return if self.audio_stream: outbound_ringtone = settings.sounds.outbound_ringtone if outbound_ringtone: self.outbound_ringtone = WavePlayer(self.audio_stream.mixer, outbound_ringtone.path, outbound_ringtone.volume, loop_count=0, pause_time=5) self.audio_stream.bridge.add(self.outbound_ringtone) routes = notification.data.result self._tls = routes[0].transport=='tls' if routes else False self.status = u'Connecting...' self.session.connect(ToHeader(self.uri), routes, self.streams.values())
def call(self, account_name, callee, wave_file, length=None): logging.info("calling from: %s - to: %s", account_name, callee) # Setup wave playback self.player = WavePlayer(SIPApplication.voice_audio_mixer, wave_file, loop_count=0, initial_play=False) # configure callee and route to him/her callee_header = ToHeader(SIPURI.parse(callee)) routes = [Route("62.220.31.184", 5060, "udp")] # locate caller account = self.accounts.get(account_name, None) if account is None: raise Exception("No account with that name found") # finally make the call session = Session(account) session.connect(callee_header, routes, [AudioStream()]) # if we got a length, end the call after it if not length is None: time.sleep(length) session.end()
class SimpleCallApplication(SIPApplication): def __init__(self): SIPApplication.__init__(self) # events used through out lifecycle self.ended = Event() self.started = Event() self.registering = Event() # normal properties self.accounts = {} self.active_sessions = [] # configure notifications notification_center = NotificationCenter() notification_center.add_observer(self) # lets get this thing rolling self.start(MemoryStorage()) # wait for it to finish self.started.wait() def add_account(self, name, username, password): if name in self.accounts: raise Exception("Already got account with that name") logging.info("adding account: %s", name) # clear the event, in case something went wrong self.registering.clear() # register/create the account new_account = Account(name) new_account.auth.username = username new_account.auth.password = password new_account.sip.register_interval = 30 new_account.enabled = True new_account.save() # wait for it to be completly created self.registering.wait() # remember our account self.accounts[name] = new_account def call(self, account_name, callee, wave_file, length=None): logging.info("calling from: %s - to: %s", account_name, callee) # Setup wave playback self.player = WavePlayer(SIPApplication.voice_audio_mixer, wave_file, loop_count=0, initial_play=False) # configure callee and route to him/her callee_header = ToHeader(SIPURI.parse(callee)) routes = [Route("62.220.31.184", 5060, "udp")] # locate caller account = self.accounts.get(account_name, None) if account is None: raise Exception("No account with that name found") # finally make the call session = Session(account) session.connect(callee_header, routes, [AudioStream()]) # if we got a length, end the call after it if not length is None: time.sleep(length) session.end() # ---------------------------- # Application Events # ---------------------------- @run_in_green_thread def _NH_SIPApplicationDidStart(self, notification): logging.info("Application started") self.started.set() def _NH_SIPApplicationDidEnd(self, notification): logging.info("Application ended") self.ended.set() # ---------------------------- # Account Events # ---------------------------- def _NH_SIPAccountWillActivate(self, notification): logging.info("Activating!") def _NH_SIPAccountDidActivate(self, notification): logging.info("Activated!") self.registering.set() # ---------------------------- # Session Events # ---------------------------- def _NH_SIPSessionGotRingIndication(self, notification): logging.info('Ringing!') def _NH_SIPSessionDidStart(self, notification): logging.info('Session started!') session = notification.sender audio_stream = session.streams[0] audio_stream.bridge.add(self.player) self.player.play() def _NH_SIPSessionDidFail(self, notification): logging.info('Failed to connect') self.stop() def _NH_SIPSessionWillEnd(self, notification): logging.info("Session will end") session = notification.sender audio_stream = session.streams[0] self.player.stop() audio_stream.bridge.remove(self.player) def _NH_SIPSessionDidEnd(self, notification): logging.info('Session ended') def _NH_SIPSessionDidProcessTransaction(self, notification): logging.info('Transaction processed - method: %s - code: %s - reason: %s' % (notification.data.method, notification.data.code, notification.data.reason))
class AnsweringMachine(object): implements(IObserver) def __init__(self, session, audio_stream): self.session = session self.stream = audio_stream self.start_time = None notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.stream) self.beep = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get('answering_machine_tone.wav')) notification_center.add_observer(self, sender=self.beep) message_wav = SIPSimpleSettings().answering_machine.unavailable_message if message_wav: self.unavailable_message = WavePlayer(SIPApplication.voice_audio_mixer, message_wav.sound_file.path, message_wav.sound_file.volume, 1, 2, False) notification_center.add_observer(self, sender=self.unavailable_message) self.stream.bridge.add(self.unavailable_message) else: self.unavailable_message = None self.stream.bridge.add(self.beep) self.stream.device.input_muted = True def mute_output(self): BlinkLogger().log_info(u"Mute output of Answering Machine") self.stream.device.output_muted = True def unmute_output(self): BlinkLogger().log_info(u"Unmute output of Answering Machine") self.stream.device.output_muted = False def start(self): if self.unavailable_message: self.unavailable_message.start() else: self.beep.start() @property def duration(self): return (datetime.datetime.now() - self.start_time).seconds if self.start_time else None def stop(self): # Stop the answering machine and allow user to take the call notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.stream) notification_center.remove_observer(self, sender=self.beep) self.beep.stop() self.beep = None if self.unavailable_message: notification_center.remove_observer(self, sender=self.unavailable_message) self.unavailable_message.stop() self.unavailable_message = None self.stream.device.input_muted = False if self.stream.recording_active: self.stream.stop_recording() @allocate_autorelease_pool def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_WavePlayerDidEnd(self, notification): if notification.sender is self.unavailable_message: # once message is played, beep self.stream.bridge.remove(self.unavailable_message) self.stream.bridge.add(self.beep) self.beep.start() elif notification.sender is self.beep: # start recording after the beep settings = SIPSimpleSettings() self.stream.bridge.remove(self.beep) direction = self.session.direction remote = "%s@%s" % (self.session.remote_identity.uri.user, self.session.remote_identity.uri.host) filename = "%s-%s-%s.wav" % (datetime.datetime.now().strftime("%Y%m%d-%H%M%S"), remote, direction) path = os.path.join(settings.audio.directory.normalized, self.session.account.id) self.stream.start_recording(os.path.join(path, filename)) self.start_time = datetime.datetime.now() def _NH_MediaStreamDidFail(self, notification): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.stream) notification_center.remove_observer(self, sender=self.beep) self.beep.stop() self.beep = None if self.unavailable_message: notification_center.remove_observer(self, sender=self.unavailable_message) self.unavailable_message.stop() self.unavailable_message = None def _NH_MediaStreamWillEnd(self, notification): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.beep) self.beep.stop() self.beep = None if self.unavailable_message: notification_center.remove_observer(self, sender=self.unavailable_message) self.unavailable_message.stop() self.unavailable_message = None def _NH_MediaStreamDidEnd(self, notification): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.stream) def _NH_AudioStreamDidStartRecordingAudio(self, notification): BlinkLogger().log_info(u"Recording message from %s" % self.session.remote_identity) def _NH_AudioStreamDidStopRecordingAudio(self, notification): BlinkLogger().log_info(u"Message from %s finished recording (duration: %s seconds)" % (self.session.remote_identity, self.duration)) self.addAnsweringMachineRecordingToHistory(notification.data.filename, self.duration) data = NotificationData(filename=notification.data.filename) NotificationCenter().post_notification("AnsweringMachineRecordingDidEnd", sender=self.session, data=data) def addAnsweringMachineRecordingToHistory(self, filename, duration): message = "<h3>Answering Machine Recording</h3>" message += "<p>%s" % filename message += "<br>Duration: %s seconds" % duration message += "<p><audio src='%s' controls='controls'>" % urllib.quote(filename) media_type = 'voicemail' local_uri = format_identity_to_string(self.session.account) remote_uri = format_identity_to_string(self.session.remote_identity) direction = 'incoming' status = 'delivered' cpim_from = format_identity_to_string(self.session.remote_identity) cpim_to = format_identity_to_string(self.session.remote_identity) timestamp = str(ISOTimestamp.now()) self.add_to_history(media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status) @run_in_green_thread def add_to_history(self, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status): try: controller = (controller for controller in NSApp.delegate().contactsWindowController.sessionControllersManager.sessionControllers if controller.session == self.session).next() except StopIteration: history_id = str(uuid.uuid1()) else: history_id = controller.history_id ChatHistory().add_message(history_id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, "html", "0", status)
class SessionItem(object): implements(IObserver) def __init__(self): self.name = None self.uri = None self.session = None self.streams = {} self.initialized = False self.timer = LoopingCall(self._timer_fired) self.outbound_ringtone = Null self.offer_in_progress = False self.local_hold = False self.remote_hold = False self._active = False self._codec_info = u'' self._tls = False self._srtp = False self._duration = timedelta(0) self._latency = 0 self._packet_loss = 0 self.status = None self.notification_center = NotificationCenter() def init_incoming(self, session, streams): self.name = session.remote_identity.display_name self.uri = session.remote_identity.uri self.session = session for stream in streams: self._set_stream(stream.type, stream) self.initialized = True self.notification_center.add_observer(self, sender=self.session) self.notification_center.post_notification('SessionItemNewIncoming', sender=self) def init_outgoing(self, name, uri, streams, account): self.name = name self.uri = uri self.session = Session(account) for stream in streams: self._set_stream(stream.type, stream) self.initialized = True self.notification_center.add_observer(self, sender=self.session) self.notification_center.post_notification('SessionItemNewOutgoing', sender=self) def _set_stream(self, stream_type, stream): old_stream = self.streams.get(stream_type, None) self.streams[stream_type] = stream if old_stream is not None: self.notification_center.remove_observer(self, sender=old_stream) if stream_type == 'audio': self.hold_tone = Null if stream is not None: self.notification_center.add_observer(self, sender=stream) if stream_type == 'audio': self.hold_tone = WavePlayer(stream.bridge.mixer, Resources.get('sounds/hold_tone.wav'), loop_count=0, pause_time=45, volume=30) stream.bridge.add(self.hold_tone) @property def audio_stream(self): return self.streams.get('audio', None) @property def video_stream(self): return self.streams.get('video', None) @property def codec_info(self): return self._codec_info @property def tls(self): return self._tls @property def srtp(self): return self._srtp @property def duration(self): return self._duration @property def latency(self): return self._latency @property def packet_loss(self): return self._packet_loss def _get_status(self): return self.__dict__.get('status', None) def _set_status(self, value): old_value = self.__dict__.get('status', None) if old_value == value: return self.__dict__['status'] = value self.notification_center.post_notification('SessionItemDidChange', sender=self) status = property(_get_status, _set_status) @property def pending_removal(self): return not bool(self.streams.values()) def _get_active(self): return self._active def _set_active(self, value): value = bool(value) if self._active == value: return self._active = value if self.audio_stream: self.audio_stream.device.output_muted = not value if value: self.unhold() self.notification_center.post_notification('SessionItemDidActivate', sender=self) else: self.hold() self.notification_center.post_notification('SessionItemDidDeactivate', sender=self) active = property(_get_active, _set_active) del _get_active, _set_active def connect(self): self.offer_in_progress = True account = self.session.account settings = SIPSimpleSettings() if isinstance(account, Account): if account.sip.outbound_proxy is not None: proxy = account.sip.outbound_proxy uri = SIPURI(host=proxy.host, port=proxy.port, parameters={'transport': proxy.transport}) elif account.sip.always_use_my_proxy: uri = SIPURI(host=account.id.domain) else: uri = self.uri else: uri = self.uri self.status = u'Looking up destination' lookup = DNSLookup() self.notification_center.add_observer(self, sender=lookup) lookup.lookup_sip_proxy(uri, settings.sip.transport_list) def hold(self): if not self.pending_removal and not self.local_hold: self.local_hold = True self.session.hold() self.hold_tone.start() def unhold(self): if not self.pending_removal and self.local_hold: self.local_hold = False self.session.unhold() def send_dtmf(self, digit): if self.audio_stream is not None: try: self.audio_stream.send_dtmf(digit) except RuntimeError: pass else: digit_map = {'*': 'star'} filename = 'sounds/dtmf_%s_tone.wav' % digit_map.get(digit, digit) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get(filename)) self.notification_center.add_observer(self, sender=player) if self.session.account.rtp.inband_dtmf: self.audio_stream.bridge.add(player) SIPApplication.voice_audio_bridge.add(player) player.start() def end(self): if self.session.state is None: self.status = u'Call canceled' self._cleanup() else: self.session.end() def _cleanup(self): if self.timer.running: self.timer.stop() self.notification_center.remove_observer(self, sender=self.session) for k in self.streams.keys(): self._set_stream(k, None) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get('sounds/hangup_tone.wav'), volume=60) self.notification_center.add_observer(self, sender=player) SIPApplication.voice_audio_bridge.add(player) player.start() self.notification_center.post_notification('SessionItemDidEnd', sender=self) def _reset_status(self): if self.pending_removal or self.offer_in_progress: return if self.local_hold: self.status = u'On hold' elif self.remote_hold: self.status = u'Hold by remote' else: self.status = None def _set_codec_info(self): codecs = [] if self.video_stream is not None: desc = 'HD Video' if self.video_stream.bit_rate/1024 >= 512 else 'Video' codecs.append('[%s] %s %dkbit' % (desc, self.video_stream.codec, self.video_stream.bit_rate/1024)) if self.audio_stream is not None: desc = 'HD Audio' if self.audio_stream.sample_rate/1000 >= 16 else 'Audio' codecs.append('[%s] %s %dkHz' % (desc, self.audio_stream.codec, self.audio_stream.sample_rate/1000)) self._codec_info = ', '.join(codecs) def _timer_fired(self): if self.audio_stream is not None: stats = self.audio_stream.statistics if stats is not None: self._latency = stats['rtt']['avg'] / 1000 self._packet_loss = int(stats['rx']['packets_lost']*100.0/stats['rx']['packets']) if stats['rx']['packets'] else 0 self._duration += timedelta(seconds=1) self.notification_center.post_notification('SessionItemDidChange', sender=self) def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_AudioStreamICENegotiationStateDidChange(self, notification): if notification.data.state == 'GATHERING': self.status = u'Gathering ICE candidates' elif notification.data.state == 'NEGOTIATING': self.status = u'Negotiating ICE' elif notification.data.state == 'RUNNING': self.status = u'Connecting...' elif notification.data.state == 'FAILED': self.status = u'ICE failed' def _NH_AudioStreamGotDTMF(self, notification): digit_map = {'*': 'star'} filename = 'sounds/dtmf_%s_tone.wav' % digit_map.get(notification.data.digit, notification.data.digit) player = WavePlayer(SIPApplication.voice_audio_bridge.mixer, Resources.get(filename)) notification.center.add_observer(self, sender=player) SIPApplication.voice_audio_bridge.add(player) player.start() def _NH_DNSLookupDidSucceed(self, notification): settings = SIPSimpleSettings() notification.center.remove_observer(self, sender=notification.sender) if self.pending_removal: return if self.audio_stream: outbound_ringtone = settings.sounds.outbound_ringtone if outbound_ringtone: self.outbound_ringtone = WavePlayer(self.audio_stream.mixer, outbound_ringtone.path, outbound_ringtone.volume, loop_count=0, pause_time=5) self.audio_stream.bridge.add(self.outbound_ringtone) routes = notification.data.result self._tls = routes[0].transport=='tls' if routes else False self.status = u'Connecting...' self.session.connect(ToHeader(self.uri), routes, self.streams.values()) def _NH_DNSLookupDidFail(self, notification): notification.center.remove_observer(self, sender=notification.sender) if self.pending_removal: return self.status = u'Destination not found' self._cleanup() def _NH_SIPSessionGotRingIndication(self, notification): self.status = u'Ringing...' self.outbound_ringtone.start() def _NH_SIPSessionWillStart(self, notification): self.outbound_ringtone.stop() def _NH_SIPSessionDidStart(self, notification): if self.audio_stream not in notification.data.streams: self._set_stream('audio', None) if self.video_stream not in notification.data.streams: self._set_stream('video', None) # TODO: rework the above code if not self.local_hold: self.offer_in_progress = False if not self.pending_removal: self.timer.start(1) self._set_codec_info() self.status = u'Connected' self._srtp = all(stream.srtp_active for stream in (self.audio_stream, self.video_stream) if stream is not None) self._tls = self.session.transport == 'tls' reactor.callLater(1, self._reset_status) else: self.status = u'Ending...' self._cleanup() def _NH_SIPSessionDidFail(self, notification): self.offer_in_progress = False if notification.data.failure_reason == 'user request': if notification.data.code == 487: reason = u'Call canceled' else: reason = unicode(notification.data.reason) else: reason = notification.data.failure_reason self.status = reason self.outbound_ringtone.stop() self._cleanup() def _NH_SIPSessionDidEnd(self, notification): self.offer_in_progress = False self.status = u'Call ended' if notification.data.originator=='local' else u'Call ended by remote' self._cleanup() def _NH_SIPSessionDidChangeHoldState(self, notification): if notification.data.originator == 'remote': self.remote_hold = notification.data.on_hold if self.local_hold: if not self.offer_in_progress: self.status = u'On hold' elif self.remote_hold: if not self.offer_in_progress: self.status = u'Hold by remote' self.hold_tone.start() else: self.status = None self.hold_tone.stop() self.offer_in_progress = False def _NH_SIPSessionGotAcceptProposal(self, notification): if self.audio_stream not in notification.data.proposed_streams and self.video_stream not in notification.data.proposed_streams: return if self.audio_stream in notification.data.proposed_streams and self.audio_stream not in notification.data.streams: self._set_stream('audio', None) if self.video_stream in notification.data.proposed_streams and self.video_stream not in notification.data.streams: self._set_stream('video', None) # TODO: rework the above self.offer_in_progress = False if not self.pending_removal: self._set_codec_info() self.status = u'Connected' reactor.callLater(1, self._reset_status) else: self.status = u'Ending...' self._cleanup() def _NH_SIPSessionGotRejectProposal(self, notification): if self.audio_stream not in notification.data.streams and self.video_stream not in notification.data.streams: return if self.audio_stream in notification.data.streams: self._set_stream('audio', None) if self.video_stream in notification.data.streams: self._set_stream('video', None) # TODO: rework the above self.offer_in_progress = False self.status = u'Stream refused' if not self.pending_removal: self._set_codec_info() reactor.callLater(1, self._reset_status) else: self._cleanup() def _NH_SIPSessionDidRenegotiateStreams(self, notification): if notification.data.action != 'remove': # TODO: what? return if self.audio_stream not in notification.data.streams and self.video_stream not in notification.data.streams: return if self.audio_stream in notification.data.streams: self._set_stream('audio', None) if self.video_stream in notification.data.streams: self._set_stream('video', None) # TODO: rework the above self.offer_in_progress = False self.status = u'Stream removed' if not self.pending_removal: self._set_codec_info() reactor.callLater(1, self._reset_status) else: self._cleanup() def _NH_WavePlayerDidFail(self, notification): notification.center.remove_observer(self, sender=notification.sender) def _NH_WavePlayerDidEnd(self, notification): notification.center.remove_observer(self, sender=notification.sender)
def beeping_ringtone(self): if 'beeping_ringtone' not in self.__dict__: ringtone = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get('sounds/beeping_ringtone.wav'), volume=70, loop_count=0, pause_time=10) ringtone.bridge = SIPApplication.voice_audio_bridge self.__dict__['beeping_ringtone'] = ringtone return self.__dict__['beeping_ringtone']
def update_ringtones(self, account=None): settings = SIPSimpleSettings() if account is None: account = AccountManager().default_account app = SIPApplication() def change_tone(name, new_tone): current = getattr(self, name) if current and current.is_active: current.stop() if new_tone: new_tone.start() setattr(self, name, new_tone) change_tone( "initial_hold_tone", WavePlayer(app.voice_audio_mixer, Resources.get('hold_tone.wav'), volume=10)) app.voice_audio_bridge.add(self.initial_hold_tone) change_tone( "secondary_hold_tone", WavePlayer(app.voice_audio_mixer, Resources.get('hold_tone.wav'), loop_count=0, pause_time=45, volume=10, initial_delay=45)) app.voice_audio_bridge.add(self.secondary_hold_tone) if account: audio_primary_ringtone = account.sounds.audio_inbound.sound_file if account.sounds.audio_inbound is not None else None else: audio_primary_ringtone = None if audio_primary_ringtone and not settings.audio.silent: # Workaround not to use same device from two bridges. -Saul if settings.audio.alert_device is not None and app.alert_audio_mixer.real_output_device == app.voice_audio_mixer.real_output_device: new_tone = WavePlayer(app.voice_audio_mixer, audio_primary_ringtone.path, loop_count=0, pause_time=6) app.voice_audio_bridge.add(new_tone) else: new_tone = WavePlayer(app.alert_audio_mixer, audio_primary_ringtone.path, loop_count=0, pause_time=6) app.alert_audio_bridge.add(new_tone) else: new_tone = None change_tone("audio_primary_ringtone", new_tone) if audio_primary_ringtone and not settings.audio.silent: new_tone = WavePlayer(app.voice_audio_mixer, Resources.get('ring_tone.wav'), loop_count=0, pause_time=6) app.voice_audio_bridge.add(new_tone) else: new_tone = None change_tone("audio_secondary_ringtone", new_tone) if audio_primary_ringtone and not settings.audio.silent: # Workaround not to use same device from two bridges. -Saul if settings.audio.alert_device is not None and app.alert_audio_mixer.real_output_device == app.voice_audio_mixer.real_output_device: new_tone = WavePlayer(app.voice_audio_mixer, Resources.get('ring_tone.wav'), loop_count=0, pause_time=6) app.voice_audio_bridge.add(new_tone) else: new_tone = WavePlayer(app.alert_audio_mixer, Resources.get('ring_tone.wav'), loop_count=0, pause_time=6) app.alert_audio_bridge.add(new_tone) else: new_tone = None change_tone("chat_primary_ringtone", new_tone) if audio_primary_ringtone and not settings.audio.silent: new_tone = WavePlayer(app.voice_audio_mixer, Resources.get('ring_tone.wav'), loop_count=0, pause_time=6) app.voice_audio_bridge.add(new_tone) else: new_tone = None change_tone("chat_secondary_ringtone", new_tone) chat_message_outgoing_sound = settings.sounds.message_sent if chat_message_outgoing_sound and not settings.audio.silent: new_tone = WavePlayer(app.voice_audio_mixer, chat_message_outgoing_sound.path, volume=chat_message_outgoing_sound.volume) app.voice_audio_bridge.add(new_tone) else: new_tone = None change_tone("chat_message_outgoing_sound", new_tone) chat_message_incoming_sound = settings.sounds.message_received if chat_message_incoming_sound and not settings.audio.silent: new_tone = WavePlayer(app.voice_audio_mixer, chat_message_incoming_sound.path, volume=chat_message_incoming_sound.volume) app.voice_audio_bridge.add(new_tone) else: new_tone = None change_tone("chat_message_incoming_sound", new_tone) file_transfer_outgoing_sound = settings.sounds.file_sent if file_transfer_outgoing_sound and not settings.audio.silent: new_tone = WavePlayer(app.voice_audio_mixer, file_transfer_outgoing_sound.path, volume=file_transfer_outgoing_sound.volume) app.voice_audio_bridge.add(new_tone) else: new_tone = None change_tone("file_transfer_outgoing_sound", new_tone) file_transfer_incoming_sound = settings.sounds.file_received if file_transfer_incoming_sound and not settings.audio.silent: new_tone = WavePlayer(app.voice_audio_mixer, file_transfer_incoming_sound.path, volume=file_transfer_incoming_sound.volume) app.voice_audio_bridge.add(new_tone) else: new_tone = None change_tone("file_transfer_incoming_sound", new_tone)
class AnsweringMachine(object): implements(IObserver) def __init__(self, session, audio_stream): self.session = session self.stream = audio_stream self.start_time = None notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.stream) self.beep = WavePlayer(SIPApplication.voice_audio_mixer, Resources.get('answering_machine_tone.wav')) notification_center.add_observer(self, sender=self.beep) message_wav = SIPSimpleSettings().answering_machine.unavailable_message if message_wav: self.unavailable_message = WavePlayer( SIPApplication.voice_audio_mixer, message_wav.sound_file.path, message_wav.sound_file.volume, 1, 2, False) notification_center.add_observer(self, sender=self.unavailable_message) self.stream.bridge.add(self.unavailable_message) else: self.unavailable_message = None self.stream.bridge.add(self.beep) self.stream.device.input_muted = True def start(self): if self.unavailable_message: self.unavailable_message.start() else: self.beep.start() @property def duration(self): return (datetime.datetime.now() - self.start_time).seconds if self.start_time else None def stop(self): # Stop the answering machine and allow user to take the call notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.stream) notification_center.remove_observer(self, sender=self.beep) self.beep.stop() self.beep = None if self.unavailable_message: notification_center.remove_observer( self, sender=self.unavailable_message) self.unavailable_message.stop() self.unavailable_message = None self.stream.device.input_muted = False if self.stream.recording_active: self.stream.stop_recording() @allocate_autorelease_pool def handle_notification(self, notification): handler = getattr(self, '_NH_%s' % notification.name, Null) handler(notification) def _NH_WavePlayerDidEnd(self, notification): if notification.sender is self.unavailable_message: # once message is played, beep self.stream.bridge.remove(self.unavailable_message) self.stream.bridge.add(self.beep) self.beep.start() elif notification.sender is self.beep: # start recording after the beep settings = SIPSimpleSettings() self.stream.bridge.remove(self.beep) direction = self.session.direction remote = "%s@%s" % (self.session.remote_identity.uri.user, self.session.remote_identity.uri.host) filename = "%s-%s-%s.wav" % (datetime.datetime.now().strftime( "%Y%m%d-%H%M%S"), remote, direction) path = os.path.join(settings.audio.directory.normalized, self.session.account.id) self.stream.start_recording(os.path.join(path, filename)) self.start_time = datetime.datetime.now() def _NH_MediaStreamDidFail(self, notification): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.stream) notification_center.remove_observer(self, sender=self.beep) self.beep.stop() self.beep = None if self.unavailable_message: notification_center.remove_observer( self, sender=self.unavailable_message) self.unavailable_message.stop() self.unavailable_message = None def _NH_MediaStreamWillEnd(self, notification): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.beep) self.beep.stop() self.beep = None if self.unavailable_message: notification_center.remove_observer( self, sender=self.unavailable_message) self.unavailable_message.stop() self.unavailable_message = None def _NH_MediaStreamDidEnd(self, notification): notification_center = NotificationCenter() notification_center.remove_observer(self, sender=self.stream) def _NH_AudioStreamDidStartRecordingAudio(self, notification): BlinkLogger().log_info(u"Recording message from %s" % self.session.remote_identity) def _NH_AudioStreamDidStopRecordingAudio(self, notification): BlinkLogger().log_info( u"Message from %s finished recording (duration: %s seconds)" % (self.session.remote_identity, self.duration)) self.addAnsweringMachineRecordingToHistory(notification.data.filename, self.duration) def addAnsweringMachineRecordingToHistory(self, filename, duration): message = "<h3>Answering Machine Recording</h3>" message += "<p>%s" % filename message += "<br>Duration: %s seconds" % duration message += "<p><audio src='%s' controls='controls'>" % urllib.quote( filename) media_type = 'voicemail' local_uri = format_identity_to_string(self.session.account) remote_uri = format_identity_to_string(self.session.remote_identity) direction = 'incoming' status = 'delivered' cpim_from = format_identity_to_string(self.session.remote_identity) cpim_to = format_identity_to_string(self.session.remote_identity) timestamp = str(Timestamp(datetime.datetime.now(tzlocal()))) self.add_to_history(media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status) @run_in_green_thread def add_to_history(self, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status): ChatHistory().add_message(str(uuid.uuid1()), media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, "html", "0", status)