def _NH_DNSLookupDidSucceed(self, notification):
     notification.center.remove_observer(self, sender=notification.sender)
     if self._ended:
         return
     routes = notification.data.result
     if not routes:
         self._terminate(failure_reason="Destination not found")
         return
     self.session = Session(self.account)
     self.stream = FileTransferStream(self._file_selector,
                                      'recvonly',
                                      transfer_id=self.ft_info.transfer_id)
     self.handler = self.stream.handler
     notification.center.add_observer(self, sender=self.stream)
     notification.center.add_observer(self, sender=self.handler)
     self.session.connect(ToHeader(self.target_uri), routes, [self.stream])
Esempio n. 2
0
 def _NH_DNSLookupDidSucceed(self, notification):
     notification.center.remove_observer(self, sender=notification.sender)
     if self._ended:
         self.log_info("File transfer was already cancelled")
         return
     routes = notification.data.result
     if not routes:
         self._terminate(failure_reason="Destination not found")
         return
     self.session = Session(self.account)
     self.stream = FileTransferStream(self._file_selector,
                                      'sendonly',
                                      transfer_id=self.ft_info.transfer_id)
     self.handler = self.stream.handler
     notification.center.add_observer(self, sender=self.stream)
     notification.center.add_observer(self, sender=self.handler)
     self.log_info("Sending push file transfer request via %s..." %
                   routes[0])
     self.session.connect(ToHeader(self.target_uri), routes, [self.stream])
Esempio n. 3
0
    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)
Esempio n. 4
0
	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()
Esempio n. 5
0
    def _NH_BlinkFileTransferDidComputeHash(self, sender, data):
        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        self.stream = FileTransferStream(self.account, self.file_selector,
                                         'sendonly')
        self.session = Session(self.account)

        notification_center.add_observer(self, sender=self.session)
        notification_center.add_observer(self, sender=self.stream)

        self.status = "Offering File..."
        self.ft_info.status = "proposing"

        BlinkLogger().log_info(u"Initiating DNS Lookup of %s to %s" %
                               (self.account, self.target_uri))
        lookup = DNSLookup()
        notification_center.add_observer(self, sender=lookup)

        if isinstance(self.account,
                      Account) and self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
            BlinkLogger().log_info(
                u"Initiating DNS Lookup for SIP routes of %s (through proxy %s)"
                % (self.target_uri, uri))
        elif isinstance(self.account,
                        Account) and self.account.sip.always_use_my_proxy:
            uri = SIPURI(host=self.account.id.domain)
            BlinkLogger().log_info(
                u"Initiating DNS Lookup for SIP routes of %s (through account %s proxy)"
                % (self.target_uri, self.account.id))
        else:
            uri = self.target_uri
            BlinkLogger().log_info(
                u"Initiating DNS Lookup for SIP routes of %s" %
                self.target_uri)
        notification_center.post_notification(
            "BlinkFileTransferInitiated",
            self,
            data=TimestampedNotificationData())
        lookup.lookup_sip_proxy(uri, settings.sip.transport_list)
Esempio n. 6
0
class OutgoingPullFileTransferHandler(FileTransfer):
    direction = 'outgoing'

    def __init__(self, account, target_uri, filename, hash):
        self.account = account
        self._file_selector = FileSelector(name=os.path.basename(filename),
                                           hash=hash)
        self.remote_identity = format_identity_to_string(target_uri)
        self.target_uri = SIPURI.new(target_uri)
        self._ended = False

    @property
    def target_text(self):
        f = NSLocalizedString("From %s", "Label") % self.target_uri
        t = NSLocalizedString("to account %s", "Label") % self.account.id
        return f + " " + t

    @property
    def progress_text(self):
        return self.status

    def start(self):
        notification_center = NotificationCenter()
        file_path = self._file_selector.name.decode() if isinstance(
            self._file_selector.name, bytes) else self._file_selector.name
        self.ft_info = FileTransferInfo(
            transfer_id=str(uuid.uuid4()),
            direction='incoming',
            local_uri=format_identity_to_string(self.account)
            if self.account is not BonjourAccount() else 'bonjour.local',
            file_size=0,
            remote_uri=self.remote_identity,
            file_path=file_path)

        self.log_info("Pull File Transfer Request started %s" % file_path)

        self.status = NSLocalizedString("Requesting File...", "Label")
        self.ft_info.status = "requesting"

        self.log_info("Initiating DNS Lookup of %s to %s" %
                      (self.account, self.target_uri))
        lookup = DNSLookup()
        notification_center.add_observer(self, sender=lookup)

        if isinstance(self.account,
                      Account) and self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
            self.log_info("Initiating DNS Lookup for %s (through proxy %s)" %
                          (self.target_uri, uri))
        elif isinstance(self.account,
                        Account) and self.account.sip.always_use_my_proxy:
            uri = SIPURI(host=self.account.id.domain)
            self.log_info(
                "Initiating DNS Lookup for %s (through account %s proxy)" %
                (self.target_uri, self.account.id))
        else:
            uri = self.target_uri
            self.log_info("Initiating DNS Lookup for %s" % self.target_uri)

        settings = SIPSimpleSettings()
        tls_name = None
        if isinstance(self.account, Account):
            tls_name = self.account.sip.tls_name or self.account.id.domain
        lookup.lookup_sip_proxy(uri,
                                settings.sip.transport_list,
                                tls_name=tls_name)

        notification_center.post_notification("BlinkFileTransferNewOutgoing",
                                              self)

    def end(self):
        if self._ended:
            return
        self._ended = True
        if self.session is not None:
            self.session.end()
        else:
            status = NSLocalizedString("Cancelled", "Label")
            self._terminate(failure_reason="Cancelled", failure_status=status)
            self.log_info("File Transfer has been cancelled")

    def _NH_DNSLookupDidSucceed(self, notification):
        notification.center.remove_observer(self, sender=notification.sender)
        if self._ended:
            return
        routes = notification.data.result
        if not routes:
            self._terminate(failure_reason="Destination not found")
            return
        self.session = Session(self.account)
        self.stream = FileTransferStream(self._file_selector,
                                         'recvonly',
                                         transfer_id=self.ft_info.transfer_id)
        self.handler = self.stream.handler
        notification.center.add_observer(self, sender=self.stream)
        notification.center.add_observer(self, sender=self.handler)
        self.log_info("Sending pull file transfer request via %s..." %
                      routes[0])
        self.session.connect(ToHeader(self.target_uri), routes, [self.stream])

    def _NH_DNSLookupDidFail(self, notification):
        notification.center.remove_observer(self, sender=notification.sender)
        if self._ended:
            return
        self.log_info("DNS Lookup for SIP routes failed: '%s'" %
                      notification.data.error)
        status = NSLocalizedString("DNS Lookup failed", "Label")
        self._terminate(failure_reason=notification.data.error,
                        failure_status=status)
Esempio n. 7
0
class OutgoingPushFileTransferHandler(FileTransfer):
    direction = 'outgoing'

    def __init__(self, account, target_uri, file_path):
        self.account = account
        self._file_selector = FileSelector.for_file(file_path)
        self.remote_identity = format_identity_to_string(target_uri)
        self.target_uri = target_uri
        self._ended = False

    @property
    def target_text(self):
        t = NSLocalizedString("To %s", "Label") % self.remote_identity
        f = NSLocalizedString("from account %s", "Label") % self.account.id
        return t + " " + f

    @property
    def progress_text(self):
        return self.status

    def retry(self):
        self._ended = False
        self._file_selector = FileSelector.for_file(self._file_selector.name)
        self.bytes = 0
        self.total_bytes = 0
        self.progress = None
        self.last_rate_pos = 0
        self.last_rate_time = None
        self.rate_history = None
        self.session = None
        self.stream = None
        self.handler = None
        self.transfer_rate = None
        self.start(restart=True)

    def start(self, restart=False):
        notification_center = NotificationCenter()
        file_path = self._file_selector.name.decode() if isinstance(
            self._file_selector.name, bytes) else self._file_selector.name

        self.ft_info = FileTransferInfo(
            transfer_id=str(uuid.uuid4()),
            direction='outgoing',
            file_size=self._file_selector.size,
            local_uri=format_identity_to_string(self.account)
            if self.account is not BonjourAccount() else 'bonjour.local',
            remote_uri=self.remote_identity,
            file_path=file_path)

        self.status = NSLocalizedString("Offering File...", "Label")
        self.ft_info.status = "proposing"

        self.log_info("Initiating DNS Lookup of %s to %s" %
                      (self.account, self.target_uri))
        lookup = DNSLookup()
        notification_center.add_observer(self, sender=lookup)

        if isinstance(self.account,
                      Account) and self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
            self.log_info("Initiating DNS Lookup for %s (through proxy %s)" %
                          (self.target_uri, uri))
        elif isinstance(self.account,
                        Account) and self.account.sip.always_use_my_proxy:
            uri = SIPURI(host=self.account.id.domain)
            self.log_info(
                "Initiating DNS Lookup for %s (through account %s proxy)" %
                (self.target_uri, self.account.id))
        else:
            uri = self.target_uri
            self.log_info("Initiating DNS Lookup for %s" % self.target_uri)

        settings = SIPSimpleSettings()

        tls_name = None
        if isinstance(self.account, Account):
            tls_name = self.account.sip.tls_name or self.account.id.domain

        lookup.lookup_sip_proxy(uri,
                                settings.sip.transport_list,
                                tls_name=tls_name)

        if restart:
            notification_center.post_notification(
                "BlinkFileTransferWillRestart", self)
        else:
            notification_center.post_notification(
                "BlinkFileTransferNewOutgoing", sender=self)

    def end(self):
        if self._ended:
            return
        self._ended = True
        if self.session is not None:
            self.session.end()
        else:
            status = NSLocalizedString("Cancelled", "Label")
            self._terminate(failure_reason="Cancelled", failure_status=status)
            self.log_info("File Transfer has been cancelled")

    def _NH_DNSLookupDidSucceed(self, notification):
        notification.center.remove_observer(self, sender=notification.sender)
        if self._ended:
            self.log_info("File transfer was already cancelled")
            return
        routes = notification.data.result
        if not routes:
            self._terminate(failure_reason="Destination not found")
            return
        self.session = Session(self.account)
        self.stream = FileTransferStream(self._file_selector,
                                         'sendonly',
                                         transfer_id=self.ft_info.transfer_id)
        self.handler = self.stream.handler
        notification.center.add_observer(self, sender=self.stream)
        notification.center.add_observer(self, sender=self.handler)
        self.log_info("Sending push file transfer request via %s..." %
                      routes[0])
        self.session.connect(ToHeader(self.target_uri), routes, [self.stream])

    def _NH_DNSLookupDidFail(self, notification):
        self.log_info("DNS Lookup failed: '%s'" % notification.data.error)
        notification.center.remove_observer(self, sender=notification.sender)
        if self._ended:
            return
        status = NSLocalizedString("DNS Lookup failed", "Label")
        self._terminate(failure_reason=notification.data.error,
                        failure_status=status)

    def _NH_FileTransferHandlerHashProgress(self, notification):
        progress = int(notification.data.processed * 100 /
                       notification.data.total)
        if self.progress is None or progress > self.progress:
            self.progress = progress
            notification.center.post_notification(
                'BlinkFileTransferHashProgress',
                sender=self,
                data=NotificationData(progress=progress))
Esempio n. 8
0
    def start(self):
        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        download_folder = unicodedata.normalize(
            'NFC',
            NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory,
                                                NSUserDomainMask, True)[0])
        for name in self.filename_generator(
                os.path.join(download_folder, self.file_name)):
            if not os.path.exists(name) and not os.path.exists(name +
                                                               ".download"):
                self.file_path = name + '.download'
                break

        BlinkLogger().log_info(u"File will be written to %s" % self.file_path)
        self.file_selector.fd = open(self.file_path, "w+")

        self.ft_info = FileTransferInfo(
            transfer_id=self.transfer_id,
            direction='incoming',
            local_uri=format_identity_to_string(self.account)
            if self.account is not BonjourAccount() else 'bonjour',
            file_size=self.file_size,
            remote_uri=self.remote_identity,
            file_path=self.file_path)

        BlinkLogger().log_info("Pull File Transfer Request started %s" %
                               self.file_path)

        self.stream = FileTransferStream(self.account, self.file_selector,
                                         'recvonly')
        self.session = Session(self.account)

        notification_center.add_observer(self, sender=self)
        notification_center.add_observer(self, sender=self.session)
        notification_center.add_observer(self, sender=self.stream)

        self.status = "Requesting File..."
        self.ft_info.status = "requesting"

        BlinkLogger().log_info(u"Initiating DNS Lookup of %s to %s" %
                               (self.account, self.target_uri))
        lookup = DNSLookup()
        notification_center.add_observer(self, sender=lookup)

        if isinstance(self.account,
                      Account) and self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
            BlinkLogger().log_info(
                u"Initiating DNS Lookup for SIP routes of %s (through proxy %s)"
                % (self.target_uri, uri))
        elif isinstance(self.account,
                        Account) and self.account.sip.always_use_my_proxy:
            uri = SIPURI(host=self.account.id.domain)
            BlinkLogger().log_info(
                u"Initiating DNS Lookup for SIP routes of %s (through account %s proxy)"
                % (self.target_uri, self.account.id))
        else:
            uri = self.target_uri
            BlinkLogger().log_info(
                u"Initiating DNS Lookup for SIP routes of %s" %
                self.target_uri)
        notification_center.post_notification(
            "BlinkFileTransferInitializing",
            self,
            data=TimestampedNotificationData())
        notification_center.post_notification(
            "BlinkFileTransferInitiated",
            self,
            data=TimestampedNotificationData())
        lookup.lookup_sip_proxy(uri, settings.sip.transport_list)
Esempio n. 9
0
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)