예제 #1
0
파일: log.py 프로젝트: yejinlei/sylkserver
 def stop(self):
     if not self.started:
         return
     notification_center = NotificationCenter()
     notification_center.discard_observer(self)
     self.notification_map.clear()
     for logger in self.loggers:
         logger.stop()
     self.started = False
예제 #2
0
    def notificationsCheckboxClicked_(self, sender):
        settings = SIPSimpleSettings()
        settings.logs.trace_notifications_in_gui = bool(sender.state())
        settings.logs.trace_notifications = settings.logs.trace_notifications_in_gui or settings.logs.trace_notifications_to_file
        settings.save()

        notification_center = NotificationCenter()

        if settings.logs.trace_notifications_in_gui:
            notification_center.add_observer(self)
        else:
            notification_center.discard_observer(self)
예제 #3
0
 def stop(self):
     if self._stopped:
         return
     self._stopped = True
     self.janus_logger.stop()
     self.factory.stopTrying()
     notification_center = NotificationCenter()
     notification_center.discard_observer(self, name='JanusBackendConnected')
     notification_center.discard_observer(self, name='JanusBackendDisconnected')
     if self.connector is not None:
         self.connector.disconnect()
         self.connector = None
     if self.connection is not None:
         self.connection.disconnect()
         self.connection = Null
예제 #4
0
 def stop(self):
     if self._stopped:
         return
     self._stopped = True
     self.factory.stopTrying()
     notification_center = NotificationCenter()
     notification_center.discard_observer(self,
                                          name='JanusBackendConnected')
     notification_center.discard_observer(self,
                                          name='JanusBackendDisconnected')
     if self.connector is not None:
         self.connector.disconnect()
         self.connector = None
     if self.protocol is not None:
         self.protocol.disconnect()
         self.protocol = Null
예제 #5
0
    def sipRadioClicked_(self, sender):
        notification_center = NotificationCenter()
        trace = sender.selectedCell().tag()
        settings = SIPSimpleSettings()
        settings.logs.trace_sip_in_gui = trace
        if trace == Disabled:
            notification_center.discard_observer(self, name="DNSLookupTrace")
            settings.logs.trace_sip = settings.logs.trace_sip_to_file
        elif trace == Simplified:
            notification_center.add_observer(self, name="DNSLookupTrace")
            settings.logs.trace_sip = True
        elif trace == Full:
            notification_center.add_observer(self, name="DNSLookupTrace")
            settings.logs.trace_sip = True

        settings.save()
예제 #6
0
 def end(self):
     if self._done:
         return
     self._done = True
     notification_center = NotificationCenter()
     if not self._initialize_done:
         # we are in the middle of initialize()
         try:
             msrp_connector = self.msrp_connector
             if self.greenlet is not None:
                 api.kill(self.greenlet)
             if msrp_connector is not None:
                 msrp_connector.cleanup()
         finally:
             notification_center.post_notification(
                 'MediaStreamDidNotInitialize',
                 sender=self,
                 data=NotificationData(reason='Interrupted'))
             notification_center.discard_observer(self, sender=self)
             self.msrp_connector = None
             self.greenlet = None
     else:
         notification_center.post_notification('MediaStreamWillEnd',
                                               sender=self)
         msrp = self.msrp
         msrp_session = self.msrp_session
         msrp_connector = self.msrp_connector
         try:
             if self.greenlet is not None:
                 api.kill(self.greenlet)
             if msrp_session is not None:
                 msrp_session.shutdown()
             elif msrp is not None:
                 msrp.loseConnection(wait=False)
             if msrp_connector is not None:
                 msrp_connector.cleanup()
         finally:
             notification_center.post_notification(
                 'MediaStreamDidEnd',
                 sender=self,
                 data=NotificationData(error=self._failure_reason))
             notification_center.remove_observer(self, sender=self)
             self.msrp = None
             self.msrp_session = None
             self.msrp_connector = None
             self.session = None
             self.greenlet = None
예제 #7
0
    def _cleanup(self):
        notification_center = NotificationCenter()

        notification_center.remove_observer(self, sender=self.session)
        self.session = None

        if self.audio_stream is not None:
            notification_center.discard_observer(self, sender=self.audio_stream)
            self.audio_stream = None

        if self.chat_stream is not None:
            notification_center.discard_observer(self, sender=self.chat_stream)
            self.chat_stream = None

        if self.end_timer is not None and self.end_timer.active():
            self.end_timer.cancel()
        self.end_timer = None
예제 #8
0
    def _cleanup(self):
        notification_center = NotificationCenter()

        notification_center.remove_observer(self, sender=self.session)
        self.session = None

        if self.audio_stream is not None:
            notification_center.discard_observer(self, sender=self.audio_stream)
            self.audio_stream = None

        if self.chat_stream is not None:
            notification_center.discard_observer(self, sender=self.chat_stream)
            self.chat_stream = None

        if self.end_timer is not None and self.end_timer.active():
            self.end_timer.cancel()
        self.end_timer = None
예제 #9
0
    def xcapRadioClicked_(self, sender):
        notification_center = NotificationCenter()
        trace = sender.selectedCell().tag()
        settings = SIPSimpleSettings()
        settings.logs.trace_xcap_in_gui = trace
        if trace == Disabled:
            notification_center.discard_observer(self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.discard_observer(self, name="XCAPSubscriptionGotNotify")
            notification_center.discard_observer(self, name="XCAPManagerDidChangeState")
            settings.logs.trace_xcap = settings.logs.trace_xcap_to_file
        elif trace == Simplified:
            notification_center.add_observer(self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.add_observer(self, name="XCAPManagerDidChangeState")
            settings.logs.trace_xcap = True
        elif trace == Full:
            notification_center.add_observer(self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.add_observer(self, name="XCAPManagerDidChangeState")
            notification_center.add_observer(self, name="XCAPSubscriptionGotNotify")
            settings.logs.trace_xcap = True

        settings.save()
예제 #10
0
    def dealloc(self):
        # Observers added in init
        NSNotificationCenter.defaultCenter().removeObserver_(self)
        notification_center = NotificationCenter()
        notification_center.add_observer(self, name="SIPSessionDidStart")
        notification_center.add_observer(
            self, name="SIPSessionDidRenegotiateStreams")

        # Observers added when settings change
        notification_center.discard_observer(self, name="SIPEngineSIPTrace")
        notification_center.discard_observer(self, name="DNSLookupTrace")
        notification_center.discard_observer(self, name="MSRPLibraryLog")
        notification_center.discard_observer(self, name="MSRPTransportTrace")
        notification_center.discard_observer(
            self, name="XCAPManagerDidDiscoverServerCapabilities")
        notification_center.discard_observer(self,
                                             name="XCAPSubscriptionGotNotify")
        notification_center.discard_observer(self,
                                             name="XCAPManagerDidChangeState")
        notification_center.discard_observer(self, name="SIPEngineLog")
        notification_center.discard_observer(self)

        super(DebugWindow, self).dealloc()
예제 #11
0
    def dealloc(self):
        # Observers added in init
        NSNotificationCenter.defaultCenter().removeObserver_(self)
        notification_center = NotificationCenter()
        notification_center.discard_observer(self, name="SIPSessionDidStart")
        notification_center.discard_observer(self, name="SIPSessionDidRenegotiateStreams")
        notification_center.discard_observer(self, name="AudioSessionHasQualityIssues")
        notification_center.discard_observer(self, name="AudioStreamICENegotiationDidSucceed")
        notification_center.discard_observer(self, name="AudioStreamICENegotiationDidFail")

        # Observers added when settings change
        notification_center.discard_observer(self, name="SIPEngineSIPTrace")
        notification_center.discard_observer(self, name="DNSLookupTrace")
        notification_center.discard_observer(self, name="MSRPLibraryLog")
        notification_center.discard_observer(self, name="MSRPTransportTrace")
        notification_center.discard_observer(self, name="XCAPManagerDidDiscoverServerCapabilities")
        notification_center.discard_observer(self, name="XCAPSubscriptionGotNotify")
        notification_center.discard_observer(self, name="XCAPManagerDidChangeState")
        notification_center.discard_observer(self, name="SIPEngineLog")
        notification_center.discard_observer(self)

        super(DebugWindow, self).dealloc()
예제 #12
0
class SessionInfoController(NSObject):

    window = objc.IBOutlet()

    sessionBox = objc.IBOutlet()
    audioBox = objc.IBOutlet()
    videoBox = objc.IBOutlet()
    chatBox = objc.IBOutlet()

    remote_party = objc.IBOutlet()
    account = objc.IBOutlet()
    duration = objc.IBOutlet()
    remote_ua = objc.IBOutlet()
    status = objc.IBOutlet()
    remote_endpoint = objc.IBOutlet()
    tls_lock = objc.IBOutlet()

    audio_status = objc.IBOutlet()
    audio_codec = objc.IBOutlet()
    audio_remote_endpoint = objc.IBOutlet()
    audio_ice_negotiation = objc.IBOutlet()
    audio_rtt = objc.IBOutlet()
    audio_rtt_graph = objc.IBOutlet()
    audio_packet_loss_rx = objc.IBOutlet()
    audio_packet_loss_tx = objc.IBOutlet()
    audio_packet_loss_rx_graph = objc.IBOutlet()
    audio_packet_loss_tx_graph = objc.IBOutlet()
    audio_srtp_lock = objc.IBOutlet()
    rx_speed_graph = objc.IBOutlet()
    rx_speed = objc.IBOutlet()
    tx_speed_graph = objc.IBOutlet()
    tx_speed = objc.IBOutlet()

    video_status = objc.IBOutlet()
    video_codec = objc.IBOutlet()
    video_remote_endpoint = objc.IBOutlet()
    video_ice_negotiation = objc.IBOutlet()
    video_srtp_lock = objc.IBOutlet()
    video_rx_speed_graph = objc.IBOutlet()
    video_rx_speed = objc.IBOutlet()
    video_tx_speed_graph = objc.IBOutlet()
    video_tx_speed = objc.IBOutlet()

    chat_remote_endpoint = objc.IBOutlet()
    chat_connection_mode = objc.IBOutlet()
    chat_tls_lock = objc.IBOutlet()

    def __new__(cls, *args, **kwargs):
        return cls.alloc().init()

    def __init__(self, sessionController):

        self.notification_center = NotificationCenter()
        self.notification_center.add_observer(
            self, name='CFGSettingsObjectDidChange')

        self.sessionController = None
        self.audio_stream = None
        self.video_stream = None
        self.chat_stream = None

        self.add_session(sessionController)
        self.add_audio_stream()
        self.add_video_stream()
        self.add_chat_stream()

        self.timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(
            1.0, self, "updateTimer:", None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer,
                                                     NSModalPanelRunLoopMode)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer,
                                                     NSDefaultRunLoopMode)
        NSBundle.loadNibNamed_owner_("SessionInfoPanel", self)

        sessionBoxTitle = NSAttributedString.alloc(
        ).initWithString_attributes_(
            NSLocalizedString("SIP Session", "Label"),
            NSDictionary.dictionaryWithObject_forKey_(
                NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.sessionBox.setTitle_(sessionBoxTitle)

        audioBoxTitle = NSAttributedString.alloc().initWithString_attributes_(
            NSLocalizedString("Audio Stream", "Label"),
            NSDictionary.dictionaryWithObject_forKey_(
                NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.audioBox.setTitle_(audioBoxTitle)

        videoBoxTitle = NSAttributedString.alloc().initWithString_attributes_(
            NSLocalizedString("Video Stream", "Label"),
            NSDictionary.dictionaryWithObject_forKey_(
                NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.videoBox.setTitle_(videoBoxTitle)

        chatBoxTitle = NSAttributedString.alloc().initWithString_attributes_(
            NSLocalizedString("Chat Stream", "Label"),
            NSDictionary.dictionaryWithObject_forKey_(
                NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.chatBox.setTitle_(chatBoxTitle)

        settings = SIPSimpleSettings()

        self.audio_rtt_graph.setLineWidth_(1.0)
        self.audio_rtt_graph.setLineSpacing_(1.0)
        self.audio_rtt_graph.setAboveLimit_(
            settings.gui.rtt_threshold)  # if higher show red color
        self.audio_rtt_graph.setMinimumHeigth_(settings.gui.rtt_threshold)

        self.audio_packet_loss_rx_graph.setLineWidth_(1.0)
        self.audio_packet_loss_rx_graph.setLineSpacing_(1.0)
        self.audio_packet_loss_rx_graph.setAboveLimit_(
            3)  # if higher than 3% show red color
        self.audio_packet_loss_rx_graph.setLineColor_(NSColor.greenColor())
        self.audio_packet_loss_rx_graph.setMinimumHeigth_(5)

        self.audio_packet_loss_tx_graph.setLineWidth_(1.0)
        self.audio_packet_loss_tx_graph.setLineSpacing_(1.0)
        self.audio_packet_loss_tx_graph.setAboveLimit_(
            3)  # if higher than 3% show red color
        self.audio_packet_loss_tx_graph.setLineColor_(NSColor.greenColor())
        self.audio_packet_loss_tx_graph.setMinimumHeigth_(5)

        self.rx_speed_graph.setLineWidth_(1.0)
        self.rx_speed_graph.setLineSpacing_(0.0)
        self.rx_speed_graph.setLineColor_(NSColor.greenColor())
        self.rx_speed_graph.setMinimumHeigth_(100000)
        self.rx_speed_graph.setAboveLimit_(120000)

        self.tx_speed_graph.setLineWidth_(1.0)
        self.tx_speed_graph.setLineSpacing_(0.0)
        self.tx_speed_graph.setLineColor_(NSColor.blueColor())
        self.tx_speed_graph.setMinimumHeigth_(100000)
        self.tx_speed_graph.setAboveLimit_(120000)

        self.video_rx_speed_graph.setLineWidth_(1.0)
        self.video_rx_speed_graph.setLineSpacing_(0.0)
        self.video_rx_speed_graph.setLineColor_(NSColor.greenColor())
        self.video_rx_speed_graph.setMinimumHeigth_(100000)
        self.video_rx_speed_graph.setAboveLimit_(99999999)

        self.video_tx_speed_graph.setLineWidth_(1.0)
        self.video_tx_speed_graph.setLineSpacing_(0.0)
        self.video_tx_speed_graph.setLineColor_(NSColor.blueColor())
        self.video_tx_speed_graph.setMinimumHeigth_(100000)
        self.video_tx_speed_graph.setAboveLimit_(99999999)

        self.resetSession()
        self.updatePanelValues()

    @objc.python_method
    def add_session(self, sessionController):
        if self.sessionController is None:
            self.sessionController = sessionController
            self.notification_center.add_observer(
                self, sender=self.sessionController)

    @objc.python_method
    def remove_session(self):
        if self.sessionController is not None:
            self.notification_center.remove_observer(
                self, sender=self.sessionController)
            self.notification_center.remove_observer(
                self, name='CFGSettingsObjectDidChange')
            self.sessionController = None
        self.remove_audio_stream()
        self.remove_video_stream()
        self.remove_chat_stream()

    @objc.python_method
    def add_audio_stream(self):
        if self.sessionController is not None and self.sessionController.hasStreamOfType(
                "audio") and self.audio_stream is None:
            self.audio_stream = self.sessionController.streamHandlerOfType(
                "audio")
            self.notification_center.add_observer(self,
                                                  sender=self.audio_stream)
            self.notification_center.add_observer(
                self, sender=self.audio_stream.stream)

    @objc.python_method
    def remove_audio_stream(self):
        if self.audio_stream is not None:
            self.notification_center.discard_observer(self,
                                                      sender=self.audio_stream)
            self.notification_center.discard_observer(
                self, sender=self.audio_stream.stream)
            self.audio_stream = None
            self.updateAudioStatus()

    @objc.python_method
    def add_video_stream(self):
        if self.sessionController is not None and self.sessionController.hasStreamOfType(
                "video") and self.video_stream is None:
            self.video_stream = self.sessionController.streamHandlerOfType(
                "video")
            self.notification_center.add_observer(self,
                                                  sender=self.video_stream)
            self.notification_center.add_observer(
                self, sender=self.video_stream.stream)

    @objc.python_method
    def remove_video_stream(self):
        if self.video_stream is not None:
            self.notification_center.discard_observer(self,
                                                      sender=self.video_stream)
            self.notification_center.discard_observer(
                self, sender=self.video_stream.stream)
            self.video_stream = None
            self.resetVideo()
            self.updateVideoStatus()

    @objc.python_method
    def add_chat_stream(self):
        if self.sessionController is not None and self.sessionController.hasStreamOfType(
                "chat") and self.chat_stream is None:
            self.chat_stream = self.sessionController.streamHandlerOfType(
                "chat")

    @objc.python_method
    def remove_chat_stream(self):
        if self.chat_stream is not None:
            self.chat_stream = None

    @objc.python_method
    def resetSession(self):
        self.remote_endpoint.setStringValue_('')
        self.remote_ua.setStringValue_('')
        self.status.setStringValue_('')
        self.duration.setStringValue_('')

        self.resetAudio()
        self.resetVideo()
        self.resetChat()

    @objc.python_method
    def resetAudio(self):
        self.audio_status.setStringValue_('')
        self.audio_codec.setStringValue_('')
        self.audio_remote_endpoint.setStringValue_('')
        self.audio_ice_negotiation.setStringValue_('')
        self.audio_rtt.setStringValue_('')
        self.audio_packet_loss_rx.setStringValue_('')
        self.audio_packet_loss_tx.setStringValue_('')
        self.rx_speed.setStringValue_('')
        self.tx_speed.setStringValue_('')

    @objc.python_method
    def resetVideo(self):
        self.video_status.setStringValue_('')
        self.video_codec.setStringValue_('')
        self.video_remote_endpoint.setStringValue_('')
        self.video_ice_negotiation.setStringValue_('')
        self.video_rx_speed.setStringValue_('')
        self.video_tx_speed.setStringValue_('')

    @objc.python_method
    def resetChat(self):
        self.chat_remote_endpoint.setStringValue_('')
        self.chat_connection_mode.setStringValue_('')

    @objc.python_method
    def updatePanelValues(self):
        self.updateSession()

    @objc.python_method
    def updateSession(self):
        if self.sessionController is None:
            self.resetSession()
        else:
            self.updateSessionStatus()
            self.remote_party.setStringValue_(self.sessionController.titleLong)
            self.account.setStringValue_(str(
                self.sessionController.account.id))
            if self.sessionController.conference_info is not None and self.sessionController.remote_focus:
                pass
            if hasattr(
                    self.sessionController.session, 'remote_user_agent'
            ) and self.sessionController.session.remote_user_agent is not None:
                self.remote_ua.setStringValue_(
                    self.sessionController.session.remote_user_agent)

            if self.sessionController.session is not None:
                if self.sessionController.session.transport is not None:
                    transport = self.sessionController.session.transport
                    if self.sessionController.session.peer_address is not None:
                        self.remote_endpoint.setStringValue_(
                            '%s:%s' %
                            (transport,
                             self.sessionController.session.peer_address))
                        self.tls_lock.setHidden_(False if transport ==
                                                 'tls' else True)
                    elif self.sessionController.routes:
                        route = self.sessionController.routes[0]
                        self.remote_endpoint.setStringValue_(
                            '%s:%s:%s' %
                            (route.transport, route.address, route.port))
                        self.tls_lock.setHidden_(False if route.transport ==
                                                 'tls' else True)
            elif self.sessionController.routes:
                route = self.sessionController.routes[0]
                self.remote_endpoint.setStringValue_(
                    '%s:%s:%s' % (route.transport, route.address, route.port))
                self.tls_lock.setHidden_(False if route.transport ==
                                         'tls' else True)

        self.updateAudio()
        self.updateVideo()
        self.updateChat()

    @objc.python_method
    def updateSessionStatus(self, sub_state=None):
        if self.sessionController.state is None:
            self.status.setStringValue_("")
            return

        if sub_state is None:
            sub_state = self.sessionController.session.state if self.sessionController.session is not None else 'none'

        sub_state = re.sub("_", " ",
                           sub_state.title()) if sub_state is not None else ''
        state = self.sessionController.state.title()

        self.status.setStringValue_('%s (%s)' %
                                    (state, sub_state) if state != sub_state
                                    and sub_state != 'None' else state)

    @objc.python_method
    def updateAudio(self):
        if self.audio_status.stringValue() and (
                self.sessionController is None or self.audio_stream is None
                or self.audio_stream.stream is None):
            self.resetAudio()
        elif (self.sessionController is not None
              and self.audio_stream is not None
              and self.audio_stream.stream is not None):
            self.updateAudioStatus()

            self.audio_rtt_graph.setDataQueue_needsDisplay_(
                self.audio_stream.rtt_history,
                True if self.window.isVisible() else False)
            self.rx_speed_graph.setDataQueue_needsDisplay_(
                self.audio_stream.rx_speed_history,
                True if self.window.isVisible() else False)
            self.tx_speed_graph.setDataQueue_needsDisplay_(
                self.audio_stream.tx_speed_history,
                True if self.window.isVisible() else False)
            self.audio_packet_loss_rx_graph.setDataQueue_needsDisplay_(
                self.audio_stream.loss_rx_history,
                True if self.window.isVisible() else False)
            self.audio_packet_loss_tx_graph.setDataQueue_needsDisplay_(
                self.audio_stream.loss_tx_history,
                True if self.window.isVisible() else False)

            rtt = self.audio_stream.statistics['rtt']
            if rtt > 1000:
                text = '%.1f s' % (float(rtt) / 1000.0)
            elif rtt > 100:
                text = '%d ms' % rtt
            elif rtt:
                text = '%d ms' % rtt
            else:
                text = ''

            self.rx_speed.setStringValue_('Rx %s/s' % format_size(
                self.audio_stream.statistics['rx_bytes'], bits=True))
            self.tx_speed.setStringValue_('Tx %s/s' % format_size(
                self.audio_stream.statistics['tx_bytes'], bits=True))

            self.audio_rtt.setStringValue_(text)

            if self.audio_stream.statistics['loss_rx'] > 3:
                self.audio_packet_loss_rx.setStringValue_(
                    'Local: %.1f %%' % self.audio_stream.statistics['loss_rx'])
            else:
                self.audio_packet_loss_rx.setStringValue_('')

            if self.audio_stream.statistics['loss_tx'] > 3:
                self.audio_packet_loss_tx.setStringValue_(
                    'Remote: %.1f %%' %
                    self.audio_stream.statistics['loss_tx'])
            else:
                self.audio_packet_loss_tx.setStringValue_('')

            if self.audio_stream.stream.codec and self.audio_stream.stream.sample_rate:
                codec = beautify_audio_codec(self.audio_stream.stream.codec)

                try:
                    settings = SIPSimpleSettings()
                    sample_rate = self.audio_stream.stream.sample_rate / 1000
                    codec = codec + " %0.fkHz" % sample_rate
                except TypeError:
                    pass

                self.audio_codec.setStringValue_(codec)
                self.audio_srtp_lock.setHidden_(
                    False if self.audio_stream.encryption_active else True)

                if self.audio_stream.encryption_active:
                    if self.audio_stream.zrtp_active:
                        self.audio_srtp_lock.setImage_(
                            NSImage.imageNamed_("locked-green") if self.
                            audio_stream.zrtp_verified else NSImage.
                            imageNamed_("locked-red"))
                    else:
                        self.audio_srtp_lock.setImage_(
                            NSImage.imageNamed_("srtp"))
            else:
                self.audio_codec.setStringValue_('')
                self.audio_srtp_lock.setHidden_(True)

            self.audio_remote_endpoint.setStringValue_(
                '%s:%s' % (self.audio_stream.stream.remote_rtp_address,
                           self.audio_stream.stream.remote_rtp_port) if self.
                audio_stream.stream.remote_rtp_address else '')

            if self.audio_stream.stream.ice_active:
                ice_status = self.audio_stream.ice_negotiation_status if self.audio_stream.ice_negotiation_status is not None else ''
                if self.audio_stream.stream.ice_active:
                    if self.audio_stream.stream.local_rtp_candidate and self.audio_stream.stream.remote_rtp_candidate:
                        if self.audio_stream.stream.local_rtp_candidate.type.lower(
                        ) != 'relay' and self.audio_stream.stream.remote_rtp_candidate.type.lower(
                        ) != 'relay':
                            if self.audio_stream.stream.local_rtp_candidate.type.lower(
                            ) == 'host' and self.audio_stream.stream.remote_rtp_candidate.type.lower(
                            ) == 'host':
                                ice_status = NSLocalizedString(
                                    "Host to Host", "Label")
                            else:
                                ice_status = NSLocalizedString(
                                    "Peer to Peer", "Label")
                        else:
                            ice_status = NSLocalizedString(
                                "Server Relayed", "Label")
            else:
                ice_status = self.audio_stream.ice_negotiation_status if self.audio_stream.ice_negotiation_status is not None else ''
                if ice_status == b"All ICE checklists failed (PJNATH_EICEFAILED)":
                    ice_status = NSLocalizedString("Probing Failed", "Label")
                elif ice_status == b"Remote answer doesn't support ICE":
                    ice_status = NSLocalizedString("Not Supported", "Label")

            self.audio_ice_negotiation.setStringValue_(ice_status)

    @objc.python_method
    def updateVideo(self):
        if self.video_status.stringValue() and (
                self.sessionController is None or self.video_stream is None
                or self.video_stream.stream is None):
            self.resetVideo()
        elif (self.sessionController is not None
              and self.video_stream is not None
              and self.video_stream.stream is not None):
            self.updateVideoStatus()

            self.video_rx_speed_graph.setDataQueue_needsDisplay_(
                self.video_stream.rx_speed_history,
                True if self.window.isVisible() else False)
            self.video_tx_speed_graph.setDataQueue_needsDisplay_(
                self.video_stream.tx_speed_history,
                True if self.window.isVisible() else False)

            rtt = self.video_stream.statistics['rtt']
            if rtt > 1000:
                text = '%.1f s' % (float(rtt) / 1000.0)
            elif rtt > 100:
                text = '%d ms' % rtt
            elif rtt:
                text = '%d ms' % rtt
            else:
                text = ''

            self.video_rx_speed.setStringValue_('Rx %s/s' % format_size(
                self.video_stream.statistics['rx_bytes'], bits=True))
            self.video_tx_speed.setStringValue_('Tx %s/s' % format_size(
                self.video_stream.statistics['tx_bytes'], bits=True))

            if self.video_stream.stream.codec and self.video_stream.stream.sample_rate:
                codec = beautify_video_codec(self.video_stream.stream.codec)

                try:
                    settings = SIPSimpleSettings()
                    sample_rate = self.video_stream.stream.sample_rate / 1000
                    codec = codec + " @%d fps" % self.video_stream.statistics[
                        'fps']

                except TypeError:
                    pass

                self.video_codec.setStringValue_(codec)
                self.video_srtp_lock.setHidden_(
                    False if self.video_stream.encryption_active else True)
                if self.video_stream.encryption_active:
                    if self.video_stream.zrtp_active:
                        self.video_srtp_lock.setImage_(
                            NSImage.imageNamed_("locked-green") if self.
                            video_stream.zrtp_verified else NSImage.
                            imageNamed_("locked-red"))
                    else:
                        self.video_srtp_lock.setImage_(
                            NSImage.imageNamed_("srtp"))

            else:
                self.video_codec.setStringValue_('')
                self.video_srtp_lock.setHidden_(True)

            self.video_remote_endpoint.setStringValue_(
                '%s:%s' % (self.video_stream.stream.remote_rtp_address,
                           self.video_stream.stream.remote_rtp_port) if self.
                video_stream.stream.remote_rtp_address else '')

            if self.video_stream.stream.ice_active:
                ice_status = self.video_stream.ice_negotiation_status if self.video_stream.ice_negotiation_status is not None else ''
                if self.video_stream.stream.ice_active:
                    if self.video_stream.stream.local_rtp_candidate and self.video_stream.stream.remote_rtp_candidate:
                        if self.video_stream.stream.local_rtp_candidate.type.lower(
                        ) != 'relay' and self.video_stream.stream.remote_rtp_candidate.type.lower(
                        ) != 'relay':
                            if self.video_stream.stream.local_rtp_candidate.type.lower(
                            ) == 'host' and self.video_stream.stream.remote_rtp_candidate.type.lower(
                            ) == 'host':
                                ice_status = NSLocalizedString(
                                    "Host to Host", "Label")
                            else:
                                ice_status = NSLocalizedString(
                                    "Peer to Peer", "Label")
                        else:
                            ice_status = NSLocalizedString(
                                "Server Relayed", "Label")

            else:
                ice_status = self.video_stream.ice_negotiation_status if self.video_stream.ice_negotiation_status is not None else ''
                if ice_status == "All ICE checklists failed (PJNATH_EICEFAILED)":
                    ice_status = NSLocalizedString("Probing Failed", "Label")
                elif ice_status == "Remote answer doesn't support ICE":
                    ice_status = NSLocalizedString("Not Supported", "Label")

            self.video_ice_negotiation.setStringValue_(ice_status)

    @objc.python_method
    def updateChat(self):
        if self.sessionController is None or self.chat_stream is None or self.chat_stream.stream is None or self.chat_stream.stream.msrp is None:
            self.resetChat()
        else:
            if self.chat_stream and self.chat_stream.stream and self.chat_stream.stream.msrp:
                if len(self.chat_stream.stream.msrp.full_local_path) > 1:
                    self.chat_remote_endpoint.setStringValue_(
                        str(self.chat_stream.stream.msrp.full_local_path[0]))
                else:
                    self.chat_remote_endpoint.setStringValue_(
                        str(self.chat_stream.stream.msrp.full_remote_path[0]))

            if self.chat_stream and self.chat_stream.stream:
                self.chat_connection_mode.setStringValue_(
                    self.chat_stream.stream.local_role.title())

    def updateTimer_(self, timer):
        if self.sessionController is not None:
            self.updateDuration()
            self.updateAudio()
            self.updateVideo()

    @objc.python_method
    def updateDuration(self):
        if self.sessionController is not None and self.sessionController.session is not None:
            if self.sessionController.session.end_time:
                now = self.sessionController.session.end_time
            else:
                now = ISOTimestamp.now()

            if self.sessionController.session.start_time and now >= self.sessionController.session.start_time:
                elapsed = now - self.sessionController.session.start_time
                h = elapsed.days * 24 + elapsed.seconds / (60 * 60)
                m = (elapsed.seconds / 60) % 60
                s = elapsed.seconds % 60
                text = "%02i:%02i:%02i" % (h, m, s)
                self.duration.setStringValue_(text)
            else:
                self.duration.setStringValue_('')

    @objc.python_method
    def updateAudioStatus(self):
        if self.sessionController is None or self.audio_stream is None:
            self.audio_status.setStringValue_("")
        else:
            if self.audio_stream.holdByLocal:
                self.audio_status.setStringValue_(
                    NSLocalizedString("On Hold", "Label"))
            elif self.audio_stream.holdByRemote:
                self.audio_status.setStringValue_(
                    NSLocalizedString("Hold by Remote", "Label"))
            elif self.audio_stream.status == STREAM_CONNECTED:
                if self.audio_stream.encryption_active:
                    title = '%s (%s)' % (
                        self.audio_stream.stream.encryption.type,
                        self.audio_stream.stream.encryption.cipher)
                else:
                    title = NSLocalizedString("Not Encrypted", "Label")

                self.audio_status.setStringValue_(title)
            else:
                self.audio_status.setStringValue_("")

    @objc.python_method
    def updateVideoStatus(self):
        if self.sessionController is None or self.video_stream is None:
            self.video_status.setStringValue_("")
        else:
            if self.audio_stream and self.audio_stream.holdByLocal:
                self.video_status.setStringValue_(
                    NSLocalizedString("On Hold", "Label"))
            elif self.audio_stream and self.audio_stream.holdByRemote:
                self.video_status.setStringValue_(
                    NSLocalizedString("Hold by Remote", "Label"))
            elif self.video_stream.status == STREAM_CONNECTED:
                if self.video_stream.encryption_active:
                    title = '%s (%s)' % (
                        self.video_stream.stream.encryption.type,
                        self.video_stream.stream.encryption.cipher)
                else:
                    title = NSLocalizedString("Not Encrypted", "Label")
                self.video_status.setStringValue_(title)
            else:
                self.video_status.setStringValue_("")

    @objc.python_method
    @run_in_gui_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    @objc.python_method
    def _NH_BlinkDidRenegotiateStreams(self, notification):
        for stream in notification.data.removed_streams:
            if stream.type == 'audio':
                self.remove_audio_stream()
            elif stream.type == 'chat':
                self.remove_chat_stream()
            elif stream.type == 'video':
                self.remove_video_stream()

        for stream in notification.data.added_streams:
            if stream.type == 'audio':
                self.add_audio_stream()
            elif stream.type == 'chat':
                self.add_chat_stream()
            elif stream.type == 'video':
                self.add_video_stream()

        self.updatePanelValues()

    @objc.python_method
    def _NH_CFGSettingsObjectDidChange(self, notification):
        settings = SIPSimpleSettings()
        if "gui.rtt_threshold" in notification.data.modified:
            self.audio_rtt_graph.setAboveLimit_(settings.gui.rtt_threshold)
            self.audio_rtt_graph.setMinimumHeigth_(settings.gui.rtt_threshold)

    @objc.python_method
    def _NH_BlinkSessionGotRingIndication(self, notification):
        self.updateSessionStatus(
            sub_state=NSLocalizedString("Ringing...", "Label"))

    @objc.python_method
    def _NH_BlinkSessionGotProvisionalResponse(self, notification):
        self.updateSessionStatus(sub_state=notification.data.reason)

    @objc.python_method
    def _NH_BlinkSessionDidProcessTransaction(self, notification):
        self.updateSessionStatus()

    @objc.python_method
    def _NH_BlinkSentAddProposal(self, notification):
        self.updateSessionStatus()

    @objc.python_method
    def _NH_BlinkSentRemoveProposal(self, notification):
        self.updateSessionStatus()

    @objc.python_method
    def _NH_BlinkGotProposal(self, notification):
        self.updateSessionStatus()

    @objc.python_method
    def _NH_BlinkProposalGotRejected(self, notification):
        self.updateSessionStatus()

    @objc.python_method
    def _NH_BlinkStreamHandlersChanged(self, notification):
        self.updatePanelValues()

    @objc.python_method
    def _NH_BlinkSessionWillStart(self, notification):
        self.updatePanelValues()

    @objc.python_method
    def _NH_BlinkSessionDidStart(self, notification):
        self.add_audio_stream()
        self.add_video_stream()
        self.add_chat_stream()
        self.updatePanelValues()

    @objc.python_method
    def _NH_BlinkSessionDidEnd(self, notification):
        self.stopTimer()

    @objc.python_method
    def _NH_BlinkConferenceGotUpdate(self, notification):
        if self.sessionController is not None and self.sessionController.session is not None and hasattr(
                notification.data, 'conference_info'):
            pass

    # todo: the ICE negotiation notification handlers below are never executed (check observers), yet the values in the GUI are updated (from somewhere else?) -Dan

    @objc.python_method
    def _NH_RTPStreamICENegotiationDidFail(self, notification):
        if notification.sender.type == 'audio' and self.audio_stream is not None:
            self.audio_ice_negotiation.setStringValue_(
                self.audio_stream.ice_negotiation_status if self.audio_stream.
                ice_negotiation_status is not None else '')
        elif notification.sender.type == 'video' and self.video_stream is not None:
            self.video_ice_negotiation.setStringValue_(
                self.video_stream.ice_negotiation_status if self.video_stream.
                ice_negotiation_status is not None else '')

    @objc.python_method
    def _NH_RTPStreamICENegotiationDidSucceed(self, notification):
        if notification.sender.type == 'audio' and self.audio_stream is not None:
            self.audio_ice_negotiation.setStringValue_(
                self.audio_stream.ice_negotiation_status if self.audio_stream.
                ice_negotiation_status is not None else '')
        elif notification.sender.type == 'video' and self.video_stream is not None:
            self.video_ice_negotiation.setStringValue_(
                self.video_stream.ice_negotiation_status if self.video_stream.
                ice_negotiation_status is not None else '')

    @objc.python_method
    def _NH_RTPStreamDidChangeHoldState(self, notification):
        self.updateAudioStatus()

    @objc.python_method
    def show(self):
        self.window.orderFront_(None)

    @objc.python_method
    def hide(self):
        self.window.orderOut_(None)

    @objc.python_method
    def toggle(self):
        if self.window.isVisible():
            self.hide()
        else:
            self.show()

    def windowShouldClose_(self, sender):
        self.window.orderOut_(None)

    @objc.python_method
    def close(self):
        self.stopTimer()
        self.remove_session()
        self.window.orderOut_(None)

    @objc.python_method
    def stopTimer(self):
        if self.timer:
            self.timer.invalidate()
            self.timer = None

    def dealloc(self):
        self.audio_packet_loss_rx_graph.removeFromSuperview()
        self.audio_packet_loss_tx_graph.removeFromSuperview()
        self.audio_rtt_graph.removeFromSuperview()
        self.rx_speed_graph.removeFromSuperview()
        self.tx_speed_graph.removeFromSuperview()
        self.video_rx_speed_graph.removeFromSuperview()
        self.video_tx_speed_graph.removeFromSuperview()
        objc.super(SessionInfoController, self).dealloc()
예제 #13
0
    def _CH_publish(self, command):
        if command.state is None or self._publication is None and command.state is SameState:
            command.signal()
            return

        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        if self._publication_timer is not None and self._publication_timer.active(
        ):
            self._publication_timer.cancel()
        self._publication_timer = None

        if self._publication is None:
            duration = command.refresh_interval or self.account.sip.publish_interval
            from_header = FromHeader(self.account.uri,
                                     self.account.display_name)
            self._publication = Publication(
                from_header,
                self.event,
                self.payload_type.content_type,
                credentials=self.account.credentials,
                duration=duration,
                extra_headers=self.extra_headers)
            notification_center.add_observer(self, sender=self._publication)
            notification_center.post_notification(
                self.__class__.__name__ + 'WillPublish',
                sender=self,
                data=NotificationData(state=command.state, duration=duration))
        else:
            notification_center.post_notification(
                self.__class__.__name__ + 'WillRefresh',
                sender=self,
                data=NotificationData(state=command.state))

        try:
            if Host.default_ip is None:
                raise PublicationError('No IP address', retry_after=60)

            # Lookup routes
            valid_transports = self.__transports__.intersection(
                settings.sip.transport_list)
            if self.account.sip.outbound_proxy is not None and self.account.sip.outbound_proxy.transport in valid_transports:
                uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                             port=self.account.sip.outbound_proxy.port,
                             parameters={
                                 'transport':
                                 self.account.sip.outbound_proxy.transport
                             })
            else:
                uri = SIPURI(host=self.account.id.domain)

            lookup = DNSLookup()
            try:
                routes = lookup.lookup_sip_proxy(
                    uri, valid_transports,
                    tls_name=self.account.sip.tls_name).wait()
            except DNSLookupError as e:
                retry_after = random.uniform(self._dns_wait,
                                             2 * self._dns_wait)
                self._dns_wait = limit(2 * self._dns_wait, max=30)
                raise PublicationError('DNS lookup failed: %s' % e,
                                       retry_after=retry_after)
            else:
                self._dns_wait = 1

            body = None if command.state is SameState else command.state.toxml(
            )

            # Publish by trying each route in turn
            publish_timeout = time() + 30
            for route in routes:
                if Host.default_ip is None:
                    raise PublicationError('No IP address', retry_after=60)

                remaining_time = publish_timeout - time()
                if remaining_time > 0:
                    try:
                        try:
                            self._publication.publish(body,
                                                      RouteHeader(route.uri),
                                                      timeout=limit(
                                                          remaining_time,
                                                          min=1,
                                                          max=10))
                        except (
                                ValueError, AttributeError
                        ) as e:  # this happens for an initial PUBLISH with body=None
                            raise PublicationError(str(e), retry_after=0)
                        except PublicationETagError:
                            state = self.state  # access self.state only once to avoid race conditions
                            if state is not None:
                                self._publication.publish(
                                    state.toxml(),
                                    RouteHeader(route.uri),
                                    timeout=limit(remaining_time,
                                                  min=1,
                                                  max=10))
                            else:
                                command.signal()
                                return
                    except SIPCoreError:
                        raise PublicationError('Internal error', retry_after=5)

                    try:
                        while True:
                            notification = self._data_channel.wait()
                            if notification.name == 'SIPPublicationDidSucceed':
                                break
                            if notification.name == 'SIPPublicationDidEnd':
                                raise PublicationError(
                                    'Publication expired',
                                    retry_after=random.uniform(60, 120)
                                )  # publication expired while we were trying to re-publish
                    except SIPPublicationDidFail as e:
                        if e.data.code == 407:
                            # Authentication failed, so retry the publication in some time
                            raise PublicationError('Authentication failed',
                                                   retry_after=random.uniform(
                                                       60, 120))
                        elif e.data.code == 412:
                            raise PublicationError(
                                'Conditional request failed', retry_after=0)
                        elif e.data.code == 423:
                            # Get the value of the Min-Expires header
                            if e.data.min_expires is not None and e.data.min_expires > self.account.sip.publish_interval:
                                refresh_interval = e.data.min_expires
                            else:
                                refresh_interval = None
                            raise PublicationError(
                                'Interval too short',
                                retry_after=random.uniform(60, 120),
                                refresh_interval=refresh_interval)
                        elif e.data.code in (405, 406, 489):
                            raise PublicationError(
                                'Method or event not supported',
                                retry_after=3600)
                        else:
                            # Otherwise just try the next route
                            continue
                    else:
                        self.publishing = True
                        self._publish_wait = 1
                        command.signal()
                        break
            else:
                # There are no more routes to try, reschedule the publication
                retry_after = random.uniform(self._publish_wait,
                                             2 * self._publish_wait)
                self._publish_wait = limit(self._publish_wait * 2, max=30)
                raise PublicationError('No more routes to try',
                                       retry_after=retry_after)
        except PublicationError as e:
            self.publishing = False
            notification_center.discard_observer(self,
                                                 sender=self._publication)

            def publish(e):
                if self.active:
                    self._command_channel.send(
                        Command('publish',
                                event=command.event,
                                state=self.state,
                                refresh_interval=e.refresh_interval))
                else:
                    command.signal()
                self._publication_timer = None

            self._publication_timer = reactor.callLater(
                e.retry_after, publish, e)
            self._publication = None
            notification_center.post_notification(
                self.__nickname__ + 'PublicationDidFail',
                sender=self,
                data=NotificationData(reason=e.error))
        else:
            notification_center.post_notification(self.__nickname__ +
                                                  'PublicationDidSucceed',
                                                  sender=self)
class SessionInfoController(NSObject):
    implements(IObserver)

    window = objc.IBOutlet()

    sessionBox = objc.IBOutlet()
    audioBox = objc.IBOutlet()
    videoBox = objc.IBOutlet()
    chatBox = objc.IBOutlet()

    remote_party = objc.IBOutlet()
    account = objc.IBOutlet()
    duration = objc.IBOutlet()
    remote_ua = objc.IBOutlet()
    status = objc.IBOutlet()
    remote_endpoint = objc.IBOutlet()
    tls_lock = objc.IBOutlet()

    audio_status = objc.IBOutlet()
    audio_codec = objc.IBOutlet()
    audio_remote_endpoint = objc.IBOutlet()
    audio_ice_negotiation = objc.IBOutlet()
    audio_ice_local_candidate = objc.IBOutlet()
    audio_ice_remote_candidate = objc.IBOutlet()
    audio_rtt = objc.IBOutlet()
    audio_packet_loss = objc.IBOutlet()
    audio_rtt_graph = objc.IBOutlet()
    audio_packet_loss_graph = objc.IBOutlet()
    audio_srtp_lock = objc.IBOutlet()
    rx_speed_graph = objc.IBOutlet()
    rx_speed = objc.IBOutlet()
    tx_speed_graph = objc.IBOutlet()
    tx_speed = objc.IBOutlet()

    video_status = objc.IBOutlet()
    video_codec = objc.IBOutlet()
    video_remote_endpoint = objc.IBOutlet()
    video_ice_negotiation = objc.IBOutlet()
    video_ice_local_candidate = objc.IBOutlet()
    video_ice_remote_candidate = objc.IBOutlet()
    video_srtp_lock = objc.IBOutlet()
    video_rx_speed_graph = objc.IBOutlet()
    video_rx_speed = objc.IBOutlet()
    video_tx_speed_graph = objc.IBOutlet()
    video_tx_speed = objc.IBOutlet()

    chat_remote_endpoint = objc.IBOutlet()
    chat_connection_mode = objc.IBOutlet()
    chat_tls_lock = objc.IBOutlet()

    def __new__(cls, *args, **kwargs):
        return cls.alloc().init()

    def __init__(self, sessionController):

        self.notification_center = NotificationCenter()
        self.notification_center.add_observer(self, name='CFGSettingsObjectDidChange')

        self.sessionController = None
        self.audio_stream = None
        self.video_stream = None
        self.chat_stream = None

        self.add_session(sessionController)
        self.add_audio_stream()
        self.add_video_stream()
        self.add_chat_stream()

        self.timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(1.0, self, "updateTimer:", None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer, NSModalPanelRunLoopMode)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer, NSDefaultRunLoopMode)
        NSBundle.loadNibNamed_owner_("SessionInfoPanel", self)

        sessionBoxTitle = NSAttributedString.alloc().initWithString_attributes_(NSLocalizedString("SIP Session", "Label"), NSDictionary.dictionaryWithObject_forKey_(NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.sessionBox.setTitle_(sessionBoxTitle)

        audioBoxTitle = NSAttributedString.alloc().initWithString_attributes_(NSLocalizedString("Audio Stream", "Label"), NSDictionary.dictionaryWithObject_forKey_(NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.audioBox.setTitle_(audioBoxTitle)

        videoBoxTitle = NSAttributedString.alloc().initWithString_attributes_(NSLocalizedString("Video Stream", "Label"), NSDictionary.dictionaryWithObject_forKey_(NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.videoBox.setTitle_(videoBoxTitle)

        chatBoxTitle = NSAttributedString.alloc().initWithString_attributes_(NSLocalizedString("Chat Stream", "Label"), NSDictionary.dictionaryWithObject_forKey_(NSColor.orangeColor(), NSForegroundColorAttributeName))
        self.chatBox.setTitle_(chatBoxTitle)

        settings = SIPSimpleSettings()

        self.audio_rtt_graph.setLineWidth_(1.0)
        self.audio_rtt_graph.setLineSpacing_(1.0)
        self.audio_rtt_graph.setAboveLimit_(settings.gui.rtt_threshold) # if higher show red color
        self.audio_rtt_graph.setMinimumHeigth_(settings.gui.rtt_threshold)

        self.audio_packet_loss_graph.setLineWidth_(1.0)
        self.audio_packet_loss_graph.setLineSpacing_(1.0)
        self.audio_packet_loss_graph.setAboveLimit_(3) # if higher than 3% show red color
        self.audio_packet_loss_graph.setLineColor_(NSColor.greenColor())
        self.audio_packet_loss_graph.setMinimumHeigth_(5)

        self.rx_speed_graph.setLineWidth_(1.0)
        self.rx_speed_graph.setLineSpacing_(0.0)
        self.rx_speed_graph.setLineColor_(NSColor.greenColor())
        self.rx_speed_graph.setMinimumHeigth_(100000)
        self.rx_speed_graph.setAboveLimit_(120000)

        self.tx_speed_graph.setLineWidth_(1.0)
        self.tx_speed_graph.setLineSpacing_(0.0)
        self.tx_speed_graph.setLineColor_(NSColor.blueColor())
        self.tx_speed_graph.setMinimumHeigth_(100000)
        self.tx_speed_graph.setAboveLimit_(120000)

        self.video_rx_speed_graph.setLineWidth_(1.0)
        self.video_rx_speed_graph.setLineSpacing_(0.0)
        self.video_rx_speed_graph.setLineColor_(NSColor.greenColor())
        self.video_rx_speed_graph.setMinimumHeigth_(100000)
        self.video_rx_speed_graph.setAboveLimit_(1200000)

        self.video_tx_speed_graph.setLineWidth_(1.0)
        self.video_tx_speed_graph.setLineSpacing_(0.0)
        self.video_tx_speed_graph.setLineColor_(NSColor.blueColor())
        self.video_tx_speed_graph.setMinimumHeigth_(100000)
        self.video_tx_speed_graph.setAboveLimit_(1200000)

        self.resetSession()
        self.updatePanelValues()

    def add_session(self, sessionController):
        if self.sessionController is None:
            self.sessionController = sessionController
            self.notification_center.add_observer(self, sender=self.sessionController)

    def remove_session(self):
        if self.sessionController is not None:
            self.notification_center.remove_observer(self, sender=self.sessionController)
            self.notification_center.remove_observer(self, name='CFGSettingsObjectDidChange')
            self.sessionController = None
        self.remove_audio_stream()
        self.remove_video_stream()
        self.remove_chat_stream()

    def add_audio_stream(self):
        if self.sessionController is not None and self.sessionController.hasStreamOfType("audio") and self.audio_stream is None:
            self.audio_stream = self.sessionController.streamHandlerOfType("audio")
            self.notification_center.add_observer(self, sender=self.audio_stream)
            self.notification_center.add_observer(self, sender=self.audio_stream.stream)

    def remove_audio_stream(self):
        if self.audio_stream is not None:
            self.notification_center.discard_observer(self, sender=self.audio_stream)
            self.notification_center.discard_observer(self, sender=self.audio_stream.stream)
            self.audio_stream = None
            self.updateAudioStatus()

    def add_video_stream(self):
        if self.sessionController is not None and self.sessionController.hasStreamOfType("video") and self.video_stream is None:
            self.video_stream = self.sessionController.streamHandlerOfType("video")
            self.notification_center.add_observer(self, sender=self.video_stream)
            self.notification_center.add_observer(self, sender=self.video_stream.stream)

    def remove_video_stream(self):
        if self.video_stream is not None:
            self.notification_center.discard_observer(self, sender=self.video_stream)
            self.notification_center.discard_observer(self, sender=self.video_stream.stream)
            self.video_stream = None
            self.resetVideo()
            self.updateVideoStatus()

    def add_chat_stream(self):
        if self.sessionController is not None and self.sessionController.hasStreamOfType("chat") and self.chat_stream is None:
            self.chat_stream = self.sessionController.streamHandlerOfType("chat")

    def remove_chat_stream(self):
        if self.chat_stream is not None:
            self.chat_stream = None

    def _NH_BlinkDidRenegotiateStreams(self, notification):
        for stream in notification.data.removed_streams:
            if stream.type == 'audio':
                self.remove_audio_stream()
            elif stream.type == 'chat':
                self.remove_chat_stream()
            elif stream.type == 'video':
                self.remove_video_stream()

        for stream in notification.data.added_streams:
            if stream.type == 'audio':
                self.add_audio_stream()
            elif stream.type == 'chat':
                self.add_chat_stream()
            elif stream.type == 'video':
                self.add_video_stream()

        self.updatePanelValues()

    def _NH_CFGSettingsObjectDidChange(self, notification):
        settings = SIPSimpleSettings()
        if notification.data.modified.has_key("gui.rtt_threshold"):
            self.audio_rtt_graph.setAboveLimit_(settings.gui.rtt_threshold)
            self.audio_rtt_graph.setMinimumHeigth_(settings.gui.rtt_threshold)

    @allocate_autorelease_pool
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def resetSession(self):
        self.remote_endpoint.setStringValue_('')
        self.remote_ua.setStringValue_('')
        self.status.setStringValue_('')
        self.duration.setStringValue_('')

        self.resetAudio()
        self.resetVideo()
        self.resetChat()

    def resetAudio(self):
        self.audio_status.setStringValue_('')
        self.audio_codec.setStringValue_('')
        self.audio_remote_endpoint.setStringValue_('')
        self.audio_ice_negotiation.setStringValue_('')
        self.audio_ice_local_candidate.setStringValue_('')
        self.audio_ice_remote_candidate.setStringValue_('')
        self.audio_rtt.setStringValue_('')
        self.audio_packet_loss.setStringValue_('')
        self.rx_speed.setStringValue_('')
        self.tx_speed.setStringValue_('')

    def resetVideo(self):
        self.video_status.setStringValue_('')
        self.video_codec.setStringValue_('')
        self.video_remote_endpoint.setStringValue_('')
        self.video_ice_negotiation.setStringValue_('')
        self.video_ice_local_candidate.setStringValue_('')
        self.video_ice_remote_candidate.setStringValue_('')
        self.video_rx_speed.setStringValue_('')
        self.video_tx_speed.setStringValue_('')

    def resetChat(self):
        self.chat_remote_endpoint.setStringValue_('')
        self.chat_connection_mode.setStringValue_('')

    def updatePanelValues(self):
        self.updateSession()

    def updateSession(self):
        if self.sessionController is None:
            self.resetSession()
        else:
            self.updateSessionStatus()
            self.remote_party.setStringValue_(self.sessionController.getTitleFull())
            self.account.setStringValue_(str(self.sessionController.account.id))
            if self.sessionController.conference_info is not None and self.sessionController.remote_focus:
                pass
            if hasattr(self.sessionController.session, 'remote_user_agent') and self.sessionController.session.remote_user_agent is not None:
                self.remote_ua.setStringValue_(self.sessionController.session.remote_user_agent)

            if self.sessionController.session is not None:
                if self.sessionController.session.transport is not None:
                    transport = self.sessionController.session.transport
                    if self.sessionController.session.peer_address is not None:
                        self.remote_endpoint.setStringValue_('%s:%s' % (transport, str(self.sessionController.session.peer_address)))
                        self.tls_lock.setHidden_(False if transport == 'tls' else True)
                    elif self.sessionController.routes:
                        route = self.sessionController.routes[0]
                        self.remote_endpoint.setStringValue_('%s:%s:%s' % (route.transport, route.address, route.port))
                        self.tls_lock.setHidden_(False if route.transport == 'tls' else True)
            elif self.sessionController.routes:
                route = self.sessionController.routes[0]
                self.remote_endpoint.setStringValue_('%s:%s:%s' % (route.transport, route.address, route.port))
                self.tls_lock.setHidden_(False if route.transport == 'tls' else True)

        self.updateAudio()
        self.updateVideo()
        self.updateChat()

    def updateSessionStatus(self, sub_state=None):
        if self.sessionController.state is None:
            self.status.setStringValue_("")
            return

        if sub_state is None:
            sub_state = self.sessionController.session.state if self.sessionController.session is not None else 'none'

        sub_state = re.sub("_", " ", sub_state.encode('utf-8').title()) if sub_state is not None else ''
        state = self.sessionController.state.title()

        self.status.setStringValue_('%s (%s)' % (state, sub_state) if state != sub_state and sub_state != 'None' else state)

    def updateAudio(self):
        if self.audio_status.stringValue() and (self.sessionController is None or self.audio_stream is None or self.audio_stream.stream is None):
            self.resetAudio()
        elif (self.sessionController is not None and self.audio_stream is not None and self.audio_stream.stream is not None):
            self.updateAudioStatus()

            self.audio_rtt_graph.setDataQueue_needsDisplay_(self.audio_stream.rtt_history, True if self.window.isVisible() else False)
            self.rx_speed_graph.setDataQueue_needsDisplay_(self.audio_stream.rx_speed_history, True if self.window.isVisible() else False)
            self.tx_speed_graph.setDataQueue_needsDisplay_(self.audio_stream.tx_speed_history, True if self.window.isVisible() else False)
            self.audio_packet_loss_graph.setDataQueue_needsDisplay_(self.audio_stream.loss_history, True if self.window.isVisible() else False)

            rtt = self.audio_stream.statistics['rtt']
            if rtt > 1000:
                text = '%.1f s' % (float(rtt)/1000.0)
            elif rtt > 100:
                text = '%d ms' % rtt
            elif rtt:
                text = '%d ms' % rtt
            else:
                text = ''

            self.rx_speed.setStringValue_('Rx %s/s' % format_size(self.audio_stream.statistics['rx_bytes'], bits=True))
            self.tx_speed.setStringValue_('Tx %s/s' % format_size(self.audio_stream.statistics['tx_bytes'], bits=True))

            self.audio_rtt.setStringValue_(text)
            self.audio_packet_loss.setStringValue_('%.1f %%' % self.audio_stream.statistics['loss'] if self.audio_stream.statistics['loss'] else '')

            if self.audio_stream.stream.codec and self.audio_stream.stream.sample_rate:
                codec = beautify_audio_codec(self.audio_stream.stream.codec)

                try:
                    settings = SIPSimpleSettings()
                    sample_rate = self.audio_stream.stream.sample_rate/1000
                    codec = codec + " %0.fkHz" % sample_rate
                except TypeError:
                    pass

                self.audio_codec.setStringValue_(codec)
                self.audio_srtp_lock.setHidden_(False if self.audio_stream.stream.srtp_active else True)
            else:
                self.audio_codec.setStringValue_('')
                self.audio_srtp_lock.setHidden_(True)

            self.audio_remote_endpoint.setStringValue_('%s:%s' % (self.audio_stream.stream.remote_rtp_address, self.audio_stream.stream.remote_rtp_port) if self.audio_stream.stream.remote_rtp_address else '')

            if self.audio_stream.stream.ice_active:
                if self.audio_stream.stream.local_rtp_candidate is not None:
                    try:
                        candidate = ice_candidates[self.audio_stream.stream.local_rtp_candidate.type.lower()]
                    except KeyError:
                        candidate = self.audio_stream.stream.local_rtp_candidate.type.capitalize()
                else:
                    candidate = ''

                self.audio_ice_local_candidate.setStringValue_(candidate)

                if self.audio_stream.stream.remote_rtp_candidate is not None:
                    try:
                        candidate = ice_candidates[self.audio_stream.stream.remote_rtp_candidate.type.lower()]
                    except KeyError:
                        candidate = self.audio_stream.stream.remote_rtp_candidate.type.capitalize()
                else:
                    candidate = ''

                self.audio_ice_remote_candidate.setStringValue_(candidate)

                ice_status = self.audio_stream.ice_negotiation_status if self.audio_stream.ice_negotiation_status is not None else ''
                if self.audio_stream.stream.ice_active:
                    if self.audio_stream.stream.local_rtp_candidate and self.audio_stream.stream.remote_rtp_candidate:
                        if self.audio_stream.stream.local_rtp_candidate.type.lower() != 'relay' and self.audio_stream.stream.remote_rtp_candidate.type.lower() != 'relay':
                            ice_status += ' ('+ NSLocalizedString("Peer to Peer", "Label")+')'
                        else:
                            ice_status += ' ('+ NSLocalizedString("Server Relayed", "Label") + ')'

            else:
                self.audio_ice_local_candidate.setStringValue_('')
                self.audio_ice_remote_candidate.setStringValue_('')
                ice_status = self.audio_stream.ice_negotiation_status if self.audio_stream.ice_negotiation_status is not None else ''

            self.audio_ice_negotiation.setStringValue_(ice_status)

    def updateVideo(self):
        if self.video_status.stringValue() and (self.sessionController is None or self.video_stream is None or self.video_stream.stream is None):
            self.resetVideo()
        elif (self.sessionController is not None and self.video_stream is not None and self.video_stream.stream is not None):
            self.updateVideoStatus()

            self.video_rx_speed_graph.setDataQueue_needsDisplay_(self.video_stream.rx_speed_history, True if self.window.isVisible() else False)
            self.video_tx_speed_graph.setDataQueue_needsDisplay_(self.video_stream.tx_speed_history, True if self.window.isVisible() else False)

            rtt = self.video_stream.statistics['rtt']
            if rtt > 1000:
                text = '%.1f s' % (float(rtt)/1000.0)
            elif rtt > 100:
                text = '%d ms' % rtt
            elif rtt:
                text = '%d ms' % rtt
            else:
                text = ''

            self.video_rx_speed.setStringValue_('Rx %s/s' % format_size(self.video_stream.statistics['rx_bytes'], bits=True))
            self.video_tx_speed.setStringValue_('Tx %s/s' % format_size(self.video_stream.statistics['tx_bytes'], bits=True))

            if self.video_stream.stream.codec and self.video_stream.stream.clock_rate:
                codec = beautify_video_codec(self.video_stream.stream.codec)

                try:
                    settings = SIPSimpleSettings()
                    sample_rate = self.video_stream.stream.clock_rate/1000
                    codec = codec + " %0.fkHz" % sample_rate
                except TypeError:
                    pass

                self.video_codec.setStringValue_(codec)
                self.video_srtp_lock.setHidden_(False if self.video_stream.stream.srtp_active else True)
            else:
                self.video_codec.setStringValue_('')
                self.video_srtp_lock.setHidden_(True)

            self.video_remote_endpoint.setStringValue_('%s:%s' % (self.video_stream.stream.remote_rtp_address, self.video_stream.stream.remote_rtp_port) if self.video_stream.stream.remote_rtp_address else '')

            if self.video_stream.stream.ice_active:
                if self.video_stream.stream.local_rtp_candidate is not None:
                    try:
                        candidate = ice_candidates[self.video_stream.stream.local_rtp_candidate.type.lower()]
                    except KeyError:
                        candidate = self.video_stream.stream.local_rtp_candidate.type.capitalize()
                else:
                    candidate = ''

                self.video_ice_local_candidate.setStringValue_(candidate)

                if self.video_stream.stream.remote_rtp_candidate is not None:
                    try:
                        candidate = ice_candidates[self.video_stream.stream.remote_rtp_candidate.type.lower()]
                    except KeyError:
                        candidate = self.video_stream.stream.remote_rtp_candidate.type.capitalize()
                else:
                    candidate = ''

                self.video_ice_remote_candidate.setStringValue_(candidate)

                ice_status = self.video_stream.ice_negotiation_status if self.video_stream.ice_negotiation_status is not None else ''
                if self.video_stream.stream.ice_active:
                    if self.video_stream.stream.local_rtp_candidate and self.video_stream.stream.remote_rtp_candidate:
                        if self.video_stream.stream.local_rtp_candidate.type.lower() != 'relay' and self.video_stream.stream.remote_rtp_candidate.type.lower() != 'relay':
                            ice_status += ' ('+ NSLocalizedString("Peer to Peer", "Label")+')'
                        else:
                            ice_status += ' ('+ NSLocalizedString("Server Relayed", "Label") + ')'

            else:
                self.video_ice_local_candidate.setStringValue_('')
                self.video_ice_remote_candidate.setStringValue_('')
                ice_status = self.video_stream.ice_negotiation_status if self.video_stream.ice_negotiation_status is not None else ''

            self.video_ice_negotiation.setStringValue_(ice_status)

    def updateChat(self):
        if self.sessionController is None or self.chat_stream is None or self.chat_stream.stream is None or self.chat_stream.stream.msrp is None:
            self.resetChat()
        else:
            if self.chat_stream and self.chat_stream.stream and self.chat_stream.stream.msrp:
                if len(self.chat_stream.stream.msrp.full_local_path) > 1:
                    self.chat_remote_endpoint.setStringValue_(str(self.chat_stream.stream.msrp.full_local_path[0]))
                else:
                    self.chat_remote_endpoint.setStringValue_(str(self.chat_stream.stream.msrp.full_remote_path[0]))

            if self.chat_stream and self.chat_stream.stream:
                self.chat_connection_mode.setStringValue_(self.chat_stream.stream.local_role.title())

    def updateTimer_(self, timer):
        if self.sessionController is not None:
            self.updateDuration()
            self.updateAudio()
            self.updateVideo()

    def updateDuration(self):
        if self.sessionController is not None and self.sessionController.session is not None:
            if self.sessionController.session.end_time:
                now = self.sessionController.session.end_time
            else:
                now = datetime.datetime(*time.localtime()[:6])

            if self.sessionController.session.start_time and now >= self.sessionController.session.start_time:
                elapsed = now - self.sessionController.session.start_time
                h = elapsed.days * 24 + elapsed.seconds / (60*60)
                m = (elapsed.seconds / 60) % 60
                s = elapsed.seconds % 60
                text = u"%02i:%02i:%02i"%(h,m,s)
                self.duration.setStringValue_(text)
            else:
                self.duration.setStringValue_('')

    def updateAudioStatus(self):
        if self.sessionController is None or self.audio_stream is None:
            self.audio_status.setStringValue_("")
        else:
            if self.audio_stream.holdByLocal:
                self.audio_status.setStringValue_(NSLocalizedString("On Hold", "Label"))
            elif self.audio_stream.holdByRemote:
                self.audio_status.setStringValue_(NSLocalizedString("Hold by Remote", "Label"))
            elif self.audio_stream.status == STREAM_CONNECTED:
                title = NSLocalizedString("Active", "Label")
                if self.audio_stream.stream.srtp_active:
                    title = title + ' ' + NSLocalizedString("Encrypted", "Label")
                    title = title + ' (SDES)'
                self.audio_status.setStringValue_(title)
            else:
                self.audio_status.setStringValue_("")

    def updateVideoStatus(self):
        if self.sessionController is None or self.video_stream is None:
            self.video_status.setStringValue_("")
        else:
            if self.audio_stream and self.audio_stream.holdByLocal:
                self.video_status.setStringValue_(NSLocalizedString("On Hold", "Label"))
            elif self.audio_stream and self.audio_stream.holdByRemote:
                self.video_status.setStringValue_(NSLocalizedString("Hold by Remote", "Label"))
            elif self.video_stream.status == STREAM_CONNECTED:
                title = NSLocalizedString("Active", "Label")
                if self.video_stream.stream.srtp_active:
                    title = title + ' ' + NSLocalizedString("Encrypted", "Label")
                    title = title + ' (SDES)'
                self.video_status.setStringValue_(title)
            else:
                self.video_status.setStringValue_("")

    def _NH_BlinkSessionGotRingIndication(self, notification):
        self.updateSessionStatus(sub_state=NSLocalizedString("Ringing...", "Label"))

    def _NH_BlinkSessionGotProvisionalResponse(self, notification):
        self.updateSessionStatus(sub_state=notification.data.reason)

    def _NH_BlinkSessionDidProcessTransaction(self, notification):
        self.updateSessionStatus()

    def _NH_BlinkSentAddProposal(self, notification):
        self.updateSessionStatus()

    def _NH_BlinkSentRemoveProposal(self, notification):
        self.updateSessionStatus()

    def _NH_BlinkGotProposal(self, notification):
        self.updateSessionStatus()

    def _NH_BlinkProposalGotRejected(self, notification):
        self.updateSessionStatus()

    def _NH_BlinkStreamHandlersChanged(self, notification):
        self.updatePanelValues()

    def _NH_BlinkSessionWillStart(self, notification):
        self.updatePanelValues()

    def _NH_BlinkSessionDidStart(self, notification):
        self.add_audio_stream()
        self.add_video_stream()
        self.add_chat_stream()
        self.updatePanelValues()

    def _NH_BlinkSessionDidEnd(self, notification):
        self.stopTimer()

    def _NH_BlinkConferenceGotUpdate(self, notification):
        if self.sessionController is not None and self.sessionController.session is not None and hasattr(notification.data, 'conference_info'):
             pass

    @run_in_gui_thread
    def _NH_AudioStreamICENegotiationDidFail(self, notification):
        if self.audio_stream is not None:
            self.audio_ice_negotiation.setStringValue_(self.audio_stream.ice_negotiation_status if self.audio_stream.ice_negotiation_status is not None else '')

    @run_in_gui_thread
    def _NH_AudioStreamICENegotiationDidSucceed(self, notification):
        if self.audio_stream is not None:
            self.audio_ice_negotiation.setStringValue_(self.audio_stream.ice_negotiation_status if self.audio_stream.ice_negotiation_status is not None else '')

    @run_in_gui_thread
    def _NH_VideoStreamICENegotiationDidFail(self, notification):
        if self.video_stream is not None:
            self.video_ice_negotiation.setStringValue_(self.video_stream.ice_negotiation_status if self.video_stream.ice_negotiation_status is not None else '')

    @run_in_gui_thread
    def _NH_VideoStreamICENegotiationDidSucceed(self, notification):
        if self.video_stream is not None:
            self.video_ice_negotiation.setStringValue_(self.video_stream.ice_negotiation_status if self.video_stream.ice_negotiation_status is not None else '')

    def _NH_AudioStreamDidChangeHoldState(self, notification):
        self.updateAudioStatus()

    def show(self):
        self.window.orderFront_(None)

    def hide(self):
        self.window.orderOut_(None)

    def toggle(self):
        if self.window.isVisible():
            self.hide()
        else:
            self.show()

    def windowShouldClose_(self, sender):
        self.window.orderOut_(None)

    def close(self):
        self.stopTimer()
        self.remove_session()
        self.window.orderOut_(None)

    def stopTimer(self):
        if self.timer:
            self.timer.invalidate()
            self.timer = None

    def dealloc(self):
        self.audio_packet_loss_graph.removeFromSuperview()
        self.audio_rtt_graph.removeFromSuperview()
        self.rx_speed_graph.removeFromSuperview()
        self.tx_speed_graph.removeFromSuperview()
        self.video_rx_speed_graph.removeFromSuperview()
        self.video_tx_speed_graph.removeFromSuperview()
        super(SessionInfoController, self).dealloc()
예제 #15
0
    def userDefaultsDidChange_(self, notification):
        userdef = NSUserDefaults.standardUserDefaults()
        notification_center = NotificationCenter()
        trace = userdef.integerForKey_("SIPTrace")
        if trace == Disabled:
            notification_center.discard_observer(self,
                                                 name="SIPEngineSIPTrace")
            notification_center.discard_observer(self, name="DNSLookupTrace")
            self.sipTraceType = None
        elif trace == Simplified:
            notification_center.add_observer(self, name="SIPEngineSIPTrace")
            notification_center.add_observer(self, name="DNSLookupTrace")
            self.sipTraceType = "simple"
        elif trace == Full:
            notification_center.add_observer(self, name="SIPEngineSIPTrace")
            notification_center.add_observer(self, name="DNSLookupTrace")
            self.sipTraceType = "full"

        trace = userdef.integerForKey_("MSRPTrace")
        if trace == Disabled:
            notification_center.discard_observer(self, name="MSRPLibraryLog")
            notification_center.discard_observer(self,
                                                 name="MSRPTransportTrace")
            self.msrpTraceType = None
        elif trace == Simplified:
            notification_center.add_observer(self, name="MSRPLibraryLog")
            notification_center.add_observer(self, name="MSRPTransportTrace")
            self.msrpTraceType = "simple"
        elif trace == Full:
            notification_center.add_observer(self, name="MSRPLibraryLog")
            notification_center.add_observer(self, name="MSRPTransportTrace")
            self.msrpTraceType = "full"

        trace = userdef.integerForKey_("XCAPTrace")
        if trace == Disabled:
            notification_center.discard_observer(
                self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.discard_observer(
                self, name="XCAPSubscriptionGotNotify")
            notification_center.discard_observer(
                self, name="XCAPManagerDidChangeState")
            self.xcapTraceType = None
        elif trace == Simplified:
            notification_center.add_observer(
                self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.add_observer(self,
                                             name="XCAPManagerDidChangeState")
            self.xcapTraceType = "simple"
        elif trace == Full:
            notification_center.add_observer(
                self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.add_observer(self,
                                             name="XCAPManagerDidChangeState")
            notification_center.add_observer(self,
                                             name="XCAPSubscriptionGotNotify")
            self.xcapTraceType = "full"

        trace = userdef.boolForKey_("EnablePJSIPTrace")
        if trace:
            notification_center.add_observer(self, name="SIPEngineLog")
        else:
            notification_center.discard_observer(self, name="SIPEngineLog")

        trace = userdef.boolForKey_("EnableNotificationsTrace")
        if trace:
            notification_center.add_observer(self)
        else:
            notification_center.discard_observer(self)
예제 #16
0
    def _CH_register(self, command):
        notification_center = NotificationCenter()
        settings = SIPSimpleSettings()

        if self._registration_timer is not None and self._registration_timer.active():
            self._registration_timer.cancel()
        self._registration_timer = None

        try:
            if Host.default_ip is None:
                raise RegistrationError('No IP address', retry_after=60)

            # Initialize the registration
            if self._registration is None:
                duration = command.refresh_interval or self.account.sip.register_interval
                try:
                    self._registration = Registration(FromHeader(self.account.uri, self.account.display_name), 
                                                  credentials=self.account.credentials, 
                                                  duration=duration,
                                                  extra_headers=[Header('Supported', 'gruu')])
                except Exception as e:
                    raise RegistrationError('Cannot create registration: %s' % str(e), retry_after=120)

                notification_center.add_observer(self, sender=self._registration)
                notification_center.post_notification('SIPAccountWillRegister', sender=self.account)
            else:
                notification_center.post_notification('SIPAccountRegistrationWillRefresh', sender=self.account)

            # Lookup routes
            if self.account.sip.outbound_proxy is not None and self.account.sip.outbound_proxy.transport in settings.sip.transport_list:
                uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, parameters={'transport': self.account.sip.outbound_proxy.transport})
            else:
                uri = SIPURI(host=self.account.id.domain)
            lookup = DNSLookup()
            try:
                routes = lookup.lookup_sip_proxy(uri, settings.sip.transport_list, tls_name=self.account.sip.tls_name).wait()
            except DNSLookupError as e:
                retry_after = int(random.uniform(self._dns_wait, 2*self._dns_wait))
                self._dns_wait = limit(2*self._dns_wait, max=30)
                raise RegistrationError('DNS lookup failed: %s' % e, retry_after=retry_after)
            else:
                self._dns_wait = 1

            # Register by trying each route in turn
            register_timeout = time() + 30
            i = 0
            for route in routes:
                i += 1
                remaining_time = register_timeout-time()
                if remaining_time > 0:
                    try:
                        contact_uri = self.account.contact[NoGRUU, route]
                    except KeyError:
                        continue
                    contact_header = ContactHeader(contact_uri)
                    instance_id = '"<%s>"' % settings.instance_id
                    
                    contact_header.parameters[b"+sip.instance"] = instance_id.encode()
                    if self.account.nat_traversal.use_ice:
                        contact_header.parameters[b"+sip.ice"] = None
                    route_header = RouteHeader(route.uri)
                    try:
                        self._registration.register(contact_header, route_header, timeout=limit(remaining_time, min=1, max=10))
                    except SIPCoreError:
                        raise RegistrationError('Internal error', retry_after=5)
                    try:
                        while True:
                            notification = self._data_channel.wait()
                            if notification.name == 'SIPRegistrationDidSucceed':
                                break
                            if notification.name == 'SIPRegistrationDidEnd':
                                raise RegistrationError('Registration expired', retry_after=int(random.uniform(60, 120)))  # registration expired while we were trying to re-register
                    except SIPRegistrationDidFail as e:
                        notification_data = NotificationData(code=e.data.code, reason=e.data.reason, registration=self._registration, registrar=route)
                        notification_center.post_notification('SIPAccountRegistrationGotAnswer', sender=self.account, data=notification_data)
                        if e.data.code == 401:
                            # Authentication failed, so retry the registration in some time
                            raise RegistrationError('Authentication failed', retry_after=int(random.uniform(60, 120)))
                        elif e.data.code == 408:
                            # Timeout
                            raise RegistrationError('Request timeout', retry_after=int(random.uniform(15, 40)))
                        elif e.data.code == 423:
                            # Get the value of the Min-Expires header
                            if e.data.min_expires is not None and e.data.min_expires > self.account.sip.register_interval:
                                refresh_interval = e.data.min_expires
                            else:
                                refresh_interval = None
                            raise RegistrationError('Interval too short', retry_after=int(random.uniform(60, 120)), refresh_interval=refresh_interval)
                        else:
                            if i == len(routes):
                                raise RegistrationError(e.data.reason, retry_after=int(random.uniform(15, 40)))
                            else:
                                # Otherwise just try the next route
                                continue
                    else:
                        notification_data = NotificationData(code=notification.data.code, reason=notification.data.reason, registration=self._registration, registrar=route)
                        notification_center.post_notification('SIPAccountRegistrationGotAnswer', sender=self.account, data=notification_data)
                        self.registered = True
                        # Save GRUU
                        try:
                            header = next(header for header in notification.data.contact_header_list if header.parameters.get('+sip.instance', '').strip('"<>') == settings.instance_id)
                        except StopIteration:
                            self.account.contact.public_gruu = None
                            self.account.contact.temporary_gruu = None
                        else:
                            public_gruu = header.parameters.get('pub-gruu', None)
                            temporary_gruu = header.parameters.get('temp-gruu', None)
                            try:
                                self.account.contact.public_gruu = SIPURI.parse(public_gruu.strip('"'))
                            except (AttributeError, SIPCoreError):
                                self.account.contact.public_gruu = None
                            try:
                                self.account.contact.temporary_gruu = SIPURI.parse(temporary_gruu.strip('"'))
                            except (AttributeError, SIPCoreError):
                                self.account.contact.temporary_gruu = None
                        notification_data = NotificationData(contact_header=notification.data.contact_header,
                                                             contact_header_list=notification.data.contact_header_list,
                                                             expires=notification.data.expires_in, registrar=route)
                        notification_center.post_notification('SIPAccountRegistrationDidSucceed', sender=self.account, data=notification_data)
                        self._register_wait = 1
                        command.signal()
                        break
            else:
                # There are no more routes to try, reschedule the registration
                retry_after = int(random.uniform(self._register_wait, 2*self._register_wait))
                self._register_wait = limit(self._register_wait*2, max=30)
                raise RegistrationError('No more routes to try', retry_after=retry_after)
        except RegistrationError as e:
            self.registered = False
            notification_center.discard_observer(self, sender=self._registration)
            notification_center.post_notification('SIPAccountRegistrationDidFail', sender=self.account, data=NotificationData(error=e.error, retry_after=e.retry_after))
            def register(e):
                if self.active:
                    self._command_channel.send(Command('register', command.event, refresh_interval=e.refresh_interval))
                self._registration_timer = None
            self._registration_timer = reactor.callLater(e.retry_after, register, e)
            self._registration = None
            self.account.contact.public_gruu = None
            self.account.contact.temporary_gruu = None
예제 #17
0
class VideoController(MediaStream):
    implements(IObserver)
    type = "video"
    ended = False
    started = False
    previous_rx_bytes = 0
    previous_tx_bytes = 0
    previous_tx_packets = 0
    previous_rx_packets = 0
    all_rx_bytes = 0
    statistics_timer = None
    last_stats = None
    initial_full_screen = False
    paused = False
    # TODO: set zrtp_supported from a Media notification to enable zRTP UI elements -adi
    zrtp_supported = False          # stream supports zRTP
    zrtp_active = False             # stream is engaging zRTP
    zrtp_verified = False           # zRTP peer has been verified
    zrtp_is_ok = True               # zRTP is encrypted ok
    zrtp_show_verify_phrase = False # show verify phrase

    @classmethod
    def createStream(self):
        return VideoStream()

    def resetStream(self):
        self.sessionController.log_debug(u"Reset stream %s" % self)
        self.notification_center.discard_observer(self, sender=self.stream)
        self.stream = VideoStream()
        self.started = False
        self.previous_rx_bytes = 0
        self.previous_tx_bytes = 0
        self.all_rx_bytes = 0
        self.initial_full_screen = False
        self.paused = False
        self.notification_center.add_observer(self, sender=self.stream)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification.sender, notification.data)

    def initWithOwner_stream_(self, sessionController, stream):
        self = super(VideoController, self).initWithOwner_stream_(sessionController, stream)
        self.notification_center = NotificationCenter()
        sessionController.log_debug(u"Init %s" % self)
        self.videoWindowController = VideoWindowController(self)

        self.statistics = {'loss': 0, 'rtt':0 , 'jitter':0 , 'rx_bytes': 0, 'tx_bytes': 0}
        # 5 minutes of history data for Session Info graphs
        self.loss_history = deque(maxlen=300)
        self.rtt_history = deque(maxlen=300)
        self.jitter_history = deque(maxlen=300)
        self.rx_speed_history = deque(maxlen=300)
        self.tx_speed_history = deque(maxlen=300)
        self.ice_negotiation_status = u'Disabled' if not self.sessionController.account.nat_traversal.use_ice else None

        return self

    def updateStatisticsTimer_(self, timer):
        if not self.stream:
            return

        stats = self.stream.statistics
        if stats is not None and self.last_stats is not None:
            jitter = stats['rx']['jitter']['last'] / 1000.0 + stats['tx']['jitter']['last'] / 1000.0
            rtt = stats['rtt']['last'] / 1000 / 2
            rx_packets = stats['rx']['packets'] - self.last_stats['rx']['packets']
            self.all_rx_bytes =+ stats['rx']['bytes']
            rx_lost_packets = stats['rx']['packets_lost'] - self.last_stats['rx']['packets_lost']
            loss = 100.0 * rx_lost_packets / rx_packets if rx_packets else 0
            self.statistics['loss'] = loss
            self.statistics['jitter'] = jitter
            self.statistics['rtt'] = rtt

            rx_overhead = (stats['rx']['packets'] - self.previous_rx_packets) * RTP_PACKET_OVERHEAD
            tx_overhead = (stats['tx']['packets'] - self.previous_tx_packets) * RTP_PACKET_OVERHEAD

            if self.previous_rx_packets:
                self.statistics['rx_bytes'] = stats['rx']['bytes']/STATISTICS_INTERVAL - self.previous_rx_bytes + rx_overhead

            if self.previous_tx_packets:
                self.statistics['tx_bytes'] = stats['tx']['bytes']/STATISTICS_INTERVAL - self.previous_tx_bytes + tx_overhead

            if self.statistics['rx_bytes'] < 0:
                self.statistics['rx_bytes'] = 0

            if self.statistics['tx_bytes'] < 0:
                self.statistics['tx_bytes'] = 0

            self.previous_rx_bytes = stats['rx']['bytes'] if stats['rx']['bytes'] >=0 else 0
            self.previous_tx_bytes = stats['tx']['bytes'] if stats['tx']['bytes'] >=0 else 0

            self.previous_rx_packets = stats['rx']['packets']
            self.previous_tx_packets = stats['tx']['packets']

            # summarize statistics
            jitter = self.statistics['jitter']
            rtt = self.statistics['rtt']
            loss = self.statistics['loss']

            if self.jitter_history is not None:
                self.jitter_history.append(jitter)
            if self.rtt_history is not None:
                self.rtt_history.append(rtt)
            if self.loss_history is not None:
                self.loss_history.append(loss)
            if self.rx_speed_history is not None:
                self.rx_speed_history.append(self.statistics['rx_bytes'] * 8)
            if self.tx_speed_history is not None:
                self.tx_speed_history.append(self.statistics['tx_bytes'] * 8)

        self.last_stats = stats

        if self.all_rx_bytes > 200000 and not self.initial_full_screen:
            settings = SIPSimpleSettings()
            if settings.video.full_screen_after_connect:
                self.initial_full_screen = True
                self.videoWindowController.goToFullScreen()

    @run_in_green_thread
    def togglePause(self):
        if self.stream is None:
            return

        if self.status != STREAM_CONNECTED:
            return

        if self.paused:
            self.sessionController.log_debug("Resume Video")
            self.paused = False
            self.stream.resume()
        else:
            self.paused = True
            self.sessionController.log_debug("Pause Video")
            self.stream.pause()

    def show(self):
        self.videoWindowController.show()

    def hide(self):
        self.videoWindowController.hide()

    def goToFullScreen(self):
        self.videoWindowController.goToFullScreen()

    def startOutgoing(self, is_update):
        self.ended = False
        self.notification_center.add_observer(self, sender=self.stream)
        self.notification_center.add_observer(self, sender=self.sessionController)
        if is_update and self.sessionController.canProposeMediaStreamChanges():
            self.changeStatus(STREAM_PROPOSING)
        else:
            self.changeStatus(STREAM_WAITING_DNS_LOOKUP)

    def startIncoming(self, is_update):
        self.ended = False
        self.notification_center.add_observer(self, sender=self.stream)
        self.notification_center.add_observer(self, sender=self.sessionController)
        self.changeStatus(STREAM_PROPOSING if is_update else STREAM_INCOMING)

    def dealloc(self):
        self.sessionController.log_debug(u"Dealloc %s" % self)
        self.videoWindowController = None
        self.stream = None
        self.notification_center = None
        self.sessionController = None
        super(VideoController, self).dealloc()

    def deallocTimer_(self, timer):
        self.release()

    def end(self):
        if self.ended:
            return

        self.sessionController.log_debug(u"End %s" % self)

        self.ended = True

        NSApp.delegate().contactsWindowController.hideLocalVideoWindow()
        status = self.status
        if status in [STREAM_IDLE, STREAM_FAILED]:
            self.changeStatus(STREAM_IDLE)
        elif status == STREAM_PROPOSING:
            self.sessionController.cancelProposal(self.stream)
            self.changeStatus(STREAM_CANCELLING)
        else:
            self.sessionController.endStream(self)
            self.changeStatus(STREAM_IDLE)

        self.removeFromSession()
        self.videoWindowController.close()
        self.notification_center.discard_observer(self, sender=self.sessionController)

        dealloc_timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(5.0, self, "deallocTimer:", None, False)
        NSRunLoop.currentRunLoop().addTimer_forMode_(dealloc_timer, NSRunLoopCommonModes)
        NSRunLoop.currentRunLoop().addTimer_forMode_(dealloc_timer, NSEventTrackingRunLoopMode)

    @run_in_gui_thread
    def changeStatus(self, newstate, fail_reason=None):
        self.status = newstate
        MediaStream.changeStatus(self, newstate, fail_reason)
        if newstate in (STREAM_IDLE, STREAM_FAILED):
            self.end()

    @run_in_gui_thread
    def _NH_MediaStreamDidInitialize(self, sender, data):
        if self.sessionController.session.direction == 'outgoing':
            self.videoWindowController.initLocalVideoWindow()

    def _NH_VideoStreamICENegotiationDidFail(self, sender, data):
        self.sessionController.log_info(u'Video ICE negotiation failed: %s' % data.reason)
        self.ice_negotiation_status = data.reason
        self.stopTimers()

    def _NH_VideoStreamICENegotiationDidSucceed(self, sender, data):
        self.sessionController.log_info(u'Video ICE negotiation succeeded')
        self.sessionController.log_info(u'Video RTP endpoints: %s:%d (%s) <-> %s:%d (%s)' % (self.stream.local_rtp_address,
                                                                                             self.stream.local_rtp_port,
                                                                                             ice_candidates[self.stream.local_rtp_candidate.type.lower()],
                                                                                             self.stream.remote_rtp_address,
                                                                                             self.stream.remote_rtp_port,
                                                                                             ice_candidates[self.stream.remote_rtp_candidate.type.lower()]))

        if self.stream.local_rtp_candidate.type.lower() != 'relay' and self.stream.remote_rtp_candidate.type.lower() != 'relay':
            self.sessionController.log_info(u'Video stream is peer to peer')
        else:
            self.sessionController.log_info(u'Video stream is relayed by server')

        self.ice_negotiation_status = 'Success'

    def _NH_MediaStreamDidStart(self, sender, data):
        super(VideoController, self)._NH_MediaStreamDidStart(sender, data)
        self.started = True
        sample_rate = self.stream.clock_rate/1000
        codec = beautify_video_codec(self.stream.codec)
        self.sessionController.log_info("Video stream established to %s:%s using %s %0.fkHz codec" % (self.stream.remote_rtp_address, self.stream.remote_rtp_port, codec, sample_rate))

        self.videoWindowController.show()
        self.changeStatus(STREAM_CONNECTED)

        if self.sessionController.hasStreamOfType("chat") and self.videoWindowController.always_on_top:
            self.videoWindowController.toogleAlwaysOnTop()

        self.statistics_timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(STATISTICS_INTERVAL, self, "updateStatisticsTimer:", None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.statistics_timer, NSRunLoopCommonModes)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.statistics_timer, NSEventTrackingRunLoopMode)

    def _NH_MediaStreamDidFail(self, sender, data):
        super(VideoController, self)._NH_MediaStreamDidFail(sender, data)
        self.sessionController.log_info(u"Video call failed: %s" % data.reason)

        self.stopTimers()

        self.changeStatus(STREAM_FAILED, data.reason)
        self.ice_negotiation_status = None
        self.rtt_history = None
        self.loss_history = None
        self.jitter_history = None
        self.rx_speed_history = None
        self.tx_speed_history = None

    def _NH_MediaStreamDidEnd(self, sender, data):
        super(VideoController, self)._NH_MediaStreamDidEnd(sender, data)

        self.stopTimers()

        self.ice_negotiation_status = None
        self.rtt_history = None
        self.loss_history = None
        self.jitter_history = None
        self.rx_speed_history = None
        self.tx_speed_history = None

        if self.started:
            self.sessionController.log_info(u"Video stream ended")
        else:
            self.sessionController.log_info(u"Video stream canceled")

        self.changeStatus(STREAM_IDLE, self.sessionController.endingBy)
        if not self.started and self.sessionController.failureReason != "Session Cancelled":
            self.videoWindowController.showDisconnectedPanel()

    def _NH_BlinkSessionDidFail(self, sender, data):
        self.stopTimers()

    def _NH_BlinkSessionDidEnd(self, sender, data):
        pass

    def stopTimers(self):
        if self.statistics_timer is not None:
            if self.statistics_timer.isValid():
                self.statistics_timer.invalidate()
        self.statistics_timer = None
예제 #18
0
    def userDefaultsDidChange_(self, notification):
        userdef = NSUserDefaults.standardUserDefaults()
        notification_center = NotificationCenter()
        trace = userdef.integerForKey_("SIPTrace")
        if trace == Disabled:
            notification_center.discard_observer(self, name="SIPEngineSIPTrace")
            notification_center.discard_observer(self, name="DNSLookupTrace")
            self.sipTraceType = None
        elif trace == Simplified:
            notification_center.add_observer(self, name="SIPEngineSIPTrace")
            notification_center.add_observer(self, name="DNSLookupTrace")
            self.sipTraceType = "simple"
        elif trace == Full:
            notification_center.add_observer(self, name="SIPEngineSIPTrace")
            notification_center.add_observer(self, name="DNSLookupTrace")
            self.sipTraceType = "full"

        trace = userdef.integerForKey_("MSRPTrace")
        if trace == Disabled:
            notification_center.discard_observer(self, name="MSRPLibraryLog")
            notification_center.discard_observer(self, name="MSRPTransportTrace")
            self.msrpTraceType = None
        elif trace == Simplified:
            notification_center.add_observer(self, name="MSRPLibraryLog")
            notification_center.add_observer(self, name="MSRPTransportTrace")
            self.msrpTraceType = "simple"
        elif trace == Full:
            notification_center.add_observer(self, name="MSRPLibraryLog")
            notification_center.add_observer(self, name="MSRPTransportTrace")
            self.msrpTraceType = "full"
        
        trace = userdef.integerForKey_("XCAPTrace")
        if trace == Disabled:
            notification_center.discard_observer(self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.discard_observer(self, name="XCAPSubscriptionGotNotify")
            notification_center.discard_observer(self, name="XCAPManagerDidChangeState")
            self.xcapTraceType = None
        elif trace == Simplified:
            notification_center.add_observer(self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.add_observer(self, name="XCAPManagerDidChangeState")
            self.xcapTraceType = "simple"
        elif trace == Full:
            notification_center.add_observer(self, name="XCAPManagerDidDiscoverServerCapabilities")
            notification_center.add_observer(self, name="XCAPManagerDidChangeState")
            notification_center.add_observer(self, name="XCAPSubscriptionGotNotify")
            self.xcapTraceType = "full"

        trace = userdef.boolForKey_("EnablePJSIPTrace")
        if trace:
            notification_center.add_observer(self, name="SIPEngineLog")
        else:        
            notification_center.discard_observer(self, name="SIPEngineLog")

        trace = userdef.boolForKey_("EnableNotificationsTrace")
        if trace:
            notification_center.add_observer(self)
        else:        
            notification_center.discard_observer(self)
예제 #19
0
class VideoController(MediaStream):

    type = "video"
    ended = False
    started = False
    previous_rx_bytes = 0
    previous_tx_bytes = 0
    previous_tx_packets = 0
    previous_rx_packets = 0
    all_rx_bytes = 0
    statistics_timer = None
    last_stats = None
    initial_full_screen = False
    media_received = False
    waiting_label = NSLocalizedString("Waiting For Media...",
                                      "Audio status label")

    paused = False

    @objc.python_method
    @classmethod
    def createStream(self):
        return MediaStreamRegistry.VideoStream()

    @objc.python_method
    def resetStream(self):
        self.sessionController.log_debug("Reset stream %s" % self)
        self.notification_center.discard_observer(self, sender=self.stream)
        self.stream = MediaStreamRegistry.VideoStream()
        self.started = False
        self.previous_rx_bytes = 0
        self.previous_tx_bytes = 0
        self.all_rx_bytes = 0
        self.initial_full_screen = False
        self.media_received = False
        self.paused = False
        self.notification_center.add_observer(self, sender=self.stream)

    @property
    def zrtp_verified(self):
        if not self.zrtp_active:
            return False
        return self.stream.encryption.zrtp.verified

    @property
    def zrtp_sas(self):
        if not self.zrtp_active:
            return None
        return self.stream.encryption.zrtp.sas

    @property
    def zrtp_active(self):
        return self.stream.encryption.type == 'ZRTP' and self.stream.encryption.active

    @property
    def encryption_active(self):
        return self.stream.encryption.active

    @property
    def srtp_active(self):
        return self.stream.encryption.type == 'SRTP/SDES' and self.stream.encryption.active

    def confirm_sas(self):
        if not self.zrtp_active:
            return
        try:
            self.stream.encryption.zrtp.verified = True
        except Exception:
            pass

    @objc.python_method
    def decline_sas(self):
        if not self.zrtp_active:
            return
        try:
            self.stream.encryption.zrtp.verified = False
        except Exception:
            pass

    @objc.python_method
    @run_in_gui_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification.sender, notification.data)

    def initWithOwner_stream_(self, sessionController, stream):
        self = objc.super(VideoController,
                          self).initWithOwner_stream_(sessionController,
                                                      stream)
        self.notification_center = NotificationCenter()
        sessionController.log_debug("Init %s" % self)
        self.videoRecorder = VideoRecorder(self)
        self.videoWindowController = VideoWindowController(self)

        self.statistics = {
            'loss_rx': 0,
            'rtt': 0,
            'jitter': 0,
            'rx_bytes': 0,
            'tx_bytes': 0,
            'fps': 0
        }
        # 5 minutes of history data for Session Info graphs
        self.loss_rx_history = deque(maxlen=300)
        self.rtt_history = deque(maxlen=300)
        self.jitter_history = deque(maxlen=300)
        self.rx_speed_history = deque(maxlen=300)
        self.tx_speed_history = deque(maxlen=300)
        self.ice_negotiation_status = NSLocalizedString(
            "Disabled", "Label"
        ) if not self.sessionController.account.nat_traversal.use_ice else None
        if self.sessionController.video_consumer != "standalone":
            self.initial_full_screen = True

        return self

    @run_in_thread('video-io')
    def updateStatisticsTimer_(self, timer):
        if not self.stream:
            return

        stats = self.stream.statistics
        if stats is not None and self.last_stats is not None:
            jitter = stats['rx']['jitter']['last'] / 1000.0 + stats['tx'][
                'jitter']['last'] / 1000.0
            rtt = stats['rtt']['last'] / 1000 / 2
            rx_packets = stats['rx']['packets'] - self.last_stats['rx'][
                'packets']
            self.all_rx_bytes = +stats['rx']['bytes']
            rx_lost_packets = stats['rx']['packets_lost'] - self.last_stats[
                'rx']['packets_lost']
            loss_rx = 100.0 * rx_lost_packets / rx_packets if rx_packets else 0
            self.statistics['loss_rx'] = loss_rx
            self.statistics['jitter'] = jitter
            self.statistics['rtt'] = rtt
            try:
                self.statistics['fps'] = self.stream.producer.framerate
            except AttributeError:
                self.statistics['fps'] = 0

            rx_overhead = (stats['rx']['packets'] -
                           self.previous_rx_packets) * RTP_PACKET_OVERHEAD
            tx_overhead = (stats['tx']['packets'] -
                           self.previous_tx_packets) * RTP_PACKET_OVERHEAD

            if self.previous_rx_packets:
                self.statistics['rx_bytes'] = stats['rx'][
                    'bytes'] / STATISTICS_INTERVAL - self.previous_rx_bytes + rx_overhead

            if self.previous_tx_packets:
                self.statistics['tx_bytes'] = stats['tx'][
                    'bytes'] / STATISTICS_INTERVAL - self.previous_tx_bytes + tx_overhead

            if self.statistics['rx_bytes'] < 0:
                self.statistics['rx_bytes'] = 0

            if self.statistics['tx_bytes'] < 0:
                self.statistics['tx_bytes'] = 0

            self.previous_rx_bytes = stats['rx'][
                'bytes'] if stats['rx']['bytes'] >= 0 else 0
            self.previous_tx_bytes = stats['tx'][
                'bytes'] if stats['tx']['bytes'] >= 0 else 0

            self.previous_rx_packets = stats['rx']['packets']
            self.previous_tx_packets = stats['tx']['packets']

            # summarize statistics
            jitter = self.statistics['jitter']
            rtt = self.statistics['rtt']
            loss_rx = self.statistics['loss_rx']

            if self.jitter_history is not None:
                self.jitter_history.append(jitter)
            if self.rtt_history is not None:
                self.rtt_history.append(rtt)
            if self.loss_rx_history is not None:
                self.loss_rx_history.append(loss_rx)
            if self.rx_speed_history is not None:
                self.rx_speed_history.append(self.statistics['rx_bytes'] * 8)
            if self.tx_speed_history is not None:
                self.tx_speed_history.append(self.statistics['tx_bytes'] * 8)

        self.last_stats = stats

        if self.all_rx_bytes > 200000 and not self.initial_full_screen and self.sessionController.video_consumer == "standalone":
            settings = SIPSimpleSettings()
            if settings.video.full_screen_after_connect:
                self.initial_full_screen = True
                if self.videoWindowController:
                    self.videoWindowController.goToFullScreen()

        if self.all_rx_bytes > 200000 and not self.media_received:
            self.sessionController.log_info('Video channel received data')
            self.markMediaReceived()

    @objc.python_method
    def markMediaReceived(self):
        self.media_received = True
        if self.videoWindowController and self.videoWindowController.disconnectLabel and self.videoWindowController.disconnectLabel.stringValue(
        ) == self.waiting_label:
            self.videoWindowController.hideStatusLabel()

    @objc.python_method
    def togglePause(self):
        if self.stream is None:
            return

        if self.status != STREAM_CONNECTED:
            return

        if self.paused:
            self.sessionController.log_debug("Resume Video")
            self.paused = False
            self.stream.resume()
        else:
            self.paused = True
            self.sessionController.log_debug("Pause Video")
            self.stream.pause()

    @objc.python_method
    def showVideoWindow(self):
        if self.videoWindowController:
            self.videoWindowController.show()

    @objc.python_method
    def hideVideoWindow(self):
        if self.videoWindowController:
            if self.videoWindowController.window():
                self.videoWindowController.videoView.setProducer(None)
                if self.videoWindowController.full_screen or self.videoWindowController.full_screen_in_progress:
                    self.videoWindowController.must_hide_after_exit_full_screen = True
                    self.videoWindowController.goToWindowMode()
                else:
                    self.videoWindowController.window().orderOut_(None)

    @objc.python_method
    def hide(self):
        if self.videoWindowController:
            self.videoWindowController.hide()

    @objc.python_method
    def goToFullScreen(self):
        if self.videoWindowController:
            self.videoWindowController.goToFullScreen()

    @objc.python_method
    def startOutgoing(self, is_update):
        if self.videoWindowController:
            self.videoWindowController.initLocalVideoWindow()

        self.ended = False
        self.notification_center.add_observer(self, sender=self.stream)
        self.notification_center.add_observer(self,
                                              sender=self.sessionController)
        self.notification_center.add_observer(self,
                                              sender=self.sessionController,
                                              name='VideoRemovedByRemoteParty')
        if is_update and self.sessionController.canProposeMediaStreamChanges():
            self.changeStatus(STREAM_PROPOSING)
        else:
            self.changeStatus(STREAM_WAITING_DNS_LOOKUP)

        self.wait_for_camera_timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(
            5.0, self, "localVideoReadyTimer:", None, False)
        NSRunLoop.currentRunLoop().addTimer_forMode_(
            self.wait_for_camera_timer, NSRunLoopCommonModes)
        NSRunLoop.currentRunLoop().addTimer_forMode_(
            self.wait_for_camera_timer, NSEventTrackingRunLoopMode)

    def localVideoReadyTimer_(self, timer):
        self.notification_center.post_notification(
            "BlinkLocalVideoReady", sender=self.sessionController)
        self.wait_for_camera_timer = None

    @objc.python_method
    def startIncoming(self, is_update):
        self.ended = False
        self.notification_center.add_observer(self, sender=self.stream)
        self.notification_center.add_observer(self,
                                              sender=self.sessionController)
        self.notification_center.add_observer(self,
                                              sender=self.sessionController,
                                              name='VideoRemovedByRemoteParty')
        self.changeStatus(STREAM_PROPOSING if is_update else STREAM_INCOMING)

    def dealloc(self):
        self.sessionController.log_debug("Dealloc %s" % self)
        self.notification_center.discard_observer(
            self, sender=self.sessionController)
        self.notification_center.discard_observer(self, sender=self.stream)
        self.videoWindowController.release()
        self.videoWindowController = None
        self.videoRecorder = None
        self.stream = None
        self.sessionController = None
        self.notification_center = None
        objc.super(VideoController, self).dealloc()

    def deallocTimer_(self, timer):
        self.release()

    @objc.python_method
    def end(self):
        if self.ended:
            return

        self.sessionController.log_debug("End %s" % self)
        self.ended = True

        if self.sessionController.waitingForLocalVideo:
            self.stop_wait_for_camera_timer()
            self.sessionController.cancelBeforeDNSLookup()

        if self.sessionController.video_consumer == "audio":
            NSApp.delegate().contactsWindowController.detachVideo(
                self.sessionController)
        elif self.sessionController.video_consumer == "chat":
            NSApp.delegate().chatWindowController.detachVideo(
                self.sessionController)

        status = self.status
        if status in [STREAM_IDLE, STREAM_FAILED]:
            self.changeStatus(STREAM_IDLE)
        elif status == STREAM_PROPOSING:
            self.sessionController.cancelProposal(self)
            self.changeStatus(STREAM_CANCELLING)
        else:
            self.sessionController.endStream(self)
            self.changeStatus(STREAM_IDLE)

        self.removeFromSession()

        self.videoRecorder.stop()

        self.videoWindowController.close()

        self.notification_center.remove_observer(
            self,
            sender=self.sessionController,
            name='VideoRemovedByRemoteParty')

        dealloc_timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(
            5.0, self, "deallocTimer:", None, False)
        NSRunLoop.currentRunLoop().addTimer_forMode_(dealloc_timer,
                                                     NSRunLoopCommonModes)
        NSRunLoop.currentRunLoop().addTimer_forMode_(
            dealloc_timer, NSEventTrackingRunLoopMode)

    @objc.python_method
    def sessionStateChanged(self, state, detail):
        if state == STATE_CONNECTING:
            self.changeStatus(STREAM_CONNECTING)
        elif state in (STATE_FAILED, STATE_DNS_FAILED):
            if detail.startswith("DNS Lookup"):
                if self.videoWindowController:
                    self.videoWindowController.showStatusLabel(
                        NSLocalizedString("DNS Lookup failed",
                                          "Audio status label"))
                self.changeStatus(
                    STREAM_FAILED,
                    NSLocalizedString("DNS Lookup failed",
                                      "Audio status label"))
            else:
                self.videoWindowController.showStatusLabel(detail)
                self.changeStatus(STREAM_FAILED, detail)

    @objc.python_method
    @run_in_gui_thread
    def changeStatus(self, newstate, fail_reason=None):
        if self.status == newstate:
            return

        self.status = newstate
        MediaStream.changeStatus(self, newstate, fail_reason)

        if newstate in (STREAM_IDLE, STREAM_FAILED):
            self.end()
            if self.videoWindowController and self.videoWindowController.localVideoWindow:
                self.videoWindowController.localVideoWindow.cancelButton.setHidden_(
                    True)

        if self.videoWindowController:
            if newstate == STREAM_WAITING_DNS_LOOKUP:
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Finding Destination...",
                                      "Audio status label"))
            elif newstate == STREAM_RINGING:
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Ringing...", "Audio status label"))
            elif newstate == STREAM_CONNECTING:
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Connecting...", "Audio status label"))
            elif newstate == STREAM_CONNECTED:
                if not self.media_received:
                    self.videoWindowController.showStatusLabel(
                        self.waiting_label)
            elif newstate == STREAM_PROPOSING:
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Adding Video...", "Audio status label"))

    @objc.python_method
    def _NH_MediaStreamDidInitialize(self, sender, data):
        pass

    @objc.python_method
    def _NH_RTPStreamICENegotiationDidFail(self, sender, data):
        self.sessionController.log_info('Video ICE negotiation failed: %s' %
                                        data.reason)
        self.ice_negotiation_status = data.reason

    @objc.python_method
    @run_in_gui_thread
    def _NH_RTPStreamICENegotiationStateDidChange(self, sender, data):
        if self.videoWindowController:
            if data.state == 'GATHERING':
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Gathering ICE Candidates...",
                                      "Audio status label"))
            elif data.state == 'NEGOTIATION_START':
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Connecting...", "Audio status label"))
            elif data.state == 'NEGOTIATING':
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Negotiating ICE...",
                                      "Audio status label"))
            elif data.state == 'GATHERING_COMPLETE':
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Gathering Complete",
                                      "Audio status label"))
            elif data.state == 'RUNNING':
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("ICE Negotiation Succeeded",
                                      "Audio status label"))
            elif data.state == 'FAILED':
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("ICE Negotiation Failed",
                                      "Audio status label"))

    @objc.python_method
    def _NH_RTPStreamICENegotiationDidSucceed(self, sender, data):
        self.sessionController.log_info('Video ICE negotiation succeeded')
        self.sessionController.log_info(
            'Video RTP endpoints: %s:%d (%s) <-> %s:%d (%s)' %
            (self.stream.local_rtp_address, self.stream.local_rtp_port,
             ice_candidates[self.stream.local_rtp_candidate.type.lower()],
             self.stream.remote_rtp_address, self.stream.remote_rtp_port,
             ice_candidates[self.stream.remote_rtp_candidate.type.lower()]))

        self.ice_negotiation_status = 'Success'

    @objc.python_method
    def _NH_VideoStreamReceivedKeyFrame(self, sender, data):
        if not self.media_received:
            self.sessionController.log_info('Video channel received key frame')
            self.markMediaReceived()

    @objc.python_method
    @run_in_gui_thread
    def _NH_BlinkSessionChangedDisplayName(self, sender, data):
        if self.videoWindowController:
            self.videoWindowController.title = NSLocalizedString(
                "Video with %s",
                "Window title") % self.sessionController.titleShort
            if self.videoWindowController.window():
                self.videoWindowController.window().setTitle_(
                    self.videoWindowController.title)

    @objc.python_method
    def _NH_MediaStreamDidStart(self, sender, data):
        self.started = True
        sample_rate = self.stream.sample_rate / 1000
        codec = beautify_video_codec(self.stream.codec)
        self.sessionController.log_info(
            "Video stream established to %s:%s using %s codec" %
            (self.stream.remote_rtp_address, self.stream.remote_rtp_port,
             codec))

        self.changeStatus(STREAM_CONNECTED)
        self.sessionController.setVideoConsumer(
            self.sessionController.video_consumer)

        self.statistics_timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(
            STATISTICS_INTERVAL, self, "updateStatisticsTimer:", None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.statistics_timer,
                                                     NSRunLoopCommonModes)
        NSRunLoop.currentRunLoop().addTimer_forMode_(
            self.statistics_timer, NSEventTrackingRunLoopMode)

    @objc.python_method
    def _NH_MediaStreamDidNotInitialize(self, sender, data):
        self.sessionController.log_info("Video call failed: %s" % data.reason)

        self.stopTimers()
        self.changeStatus(STREAM_FAILED, data.reason)

        self.ice_negotiation_status = None
        self.rtt_history = None
        self.loss_rx_history = None
        self.jitter_history = None
        self.rx_speed_history = None
        self.tx_speed_history = None

    @objc.python_method
    def _NH_MediaStreamDidFail(self, sender, data):
        pass

    @objc.python_method
    def _NH_MediaStreamWillEnd(self, sender, data):
        self.stopTimers()
        if self.videoWindowController:
            self.videoWindowController.goToWindowMode()
        self.ice_negotiation_status = None
        self.rtt_history = None
        self.loss_rx_history = None
        self.jitter_history = None
        self.rx_speed_history = None
        self.tx_speed_history = None

    @objc.python_method
    def _NH_MediaStreamDidEnd(self, sender, data):
        if data.error is not None:
            self.sessionController.log_info("Video call failed: %s" %
                                            data.error)
            self.changeStatus(STREAM_FAILED, data.reason)
        elif self.started:
            self.sessionController.log_info("Video stream ended")
        else:
            self.sessionController.log_info("Video stream canceled")

        self.changeStatus(STREAM_IDLE, self.sessionController.endingBy)

    @objc.python_method
    def _NH_BlinkSessionGotRingIndication(self, sender, data):
        self.changeStatus(STREAM_RINGING)

    @objc.python_method
    def _NH_VideoRemovedByRemoteParty(self, sender, data):
        if self.videoWindowController:
            self.videoWindowController.showStatusLabel(
                NSLocalizedString("Video Ended", "Label"))

    @objc.python_method
    def _NH_BlinkProposalGotRejected(self, sender, data):
        if self.stream in data.proposed_streams:
            if self.videoWindowController:
                self.videoWindowController.showStatusLabel(
                    NSLocalizedString("Proposal rejected", "Label"))

    @objc.python_method
    def _NH_BlinkWillCancelProposal(self, sender, data):
        self.sessionController.log_info("Video proposal cancelled")
        self.changeStatus(STREAM_FAILED, "Proposal Cancelled")

    @objc.python_method
    def _NH_BlinkSessionDidStart(self, sender, data):
        if self.status != STREAM_CONNECTED:
            if self.videoWindowController:
                if not self.media_received:
                    self.videoWindowController.showStatusLabel(
                        NSLocalizedString("Waiting for Media...", "Label"))
            audio_stream = self.sessionController.streamHandlerOfType("audio")
            if audio_stream and audio_stream.status in (
                    STREAM_CONNECTING, STREAM_CONNECTED
            ) and self.sessionController.video_consumer == 'audio':
                NSApp.delegate().contactsWindowController.showAudioDrawer()

    @objc.python_method
    def _NH_BlinkSessionDidFail(self, sender, data):
        if host is None or host.default_ip is None:
            reason = NSLocalizedString("No Internet connection", "Label")
        else:
            reason = "%s (%s)" % (data.failure_reason.title(), data.code)
            if data.code is not None:
                if data.code == 486:
                    reason = NSLocalizedString("Busy Here", "Label")
                elif data.code == 487:
                    reason = NSLocalizedString("Session Cancelled", "Label")
                elif data.code == 603:
                    reason = NSLocalizedString("Call Declined", "Label")
                elif data.code == 408:
                    if data.originator == 'local':
                        reason = NSLocalizedString("Network Timeout", "Label")
                    else:
                        reason = NSLocalizedString("User Unreachable", "Label")
                elif data.code == 480:
                    reason = NSLocalizedString("User Not Online", "Label")
                elif data.code == 482:
                    reason = NSLocalizedString("User Unreachable", "Label")
                elif data.code >= 500 and data.code < 600:
                    reason = NSLocalizedString(
                        "Server Failure (%s)" % data.code, "Label")
            if self.videoWindowController:
                self.videoWindowController.showStatusLabel(reason)
        self.stopTimers()
        self.changeStatus(STREAM_FAILED)

    @objc.python_method
    def _NH_BlinkSessionWillEnd(self, sender, data):
        if self.videoWindowController:
            self.videoWindowController.showStatusLabel(
                NSLocalizedString("Video Ended", "Label"))

    @objc.python_method
    def stopTimers(self):
        if self.statistics_timer is not None:
            if self.statistics_timer.isValid():
                self.statistics_timer.invalidate()
            self.statistics_timer = None

    @objc.python_method
    def stop_wait_for_camera_timer(self):
        if self.wait_for_camera_timer is not None:
            if self.wait_for_camera_timer.isValid():
                self.wait_for_camera_timer.invalidate()
            self.wait_for_camera_timer = None

    @objc.python_method
    def _NH_RTPStreamDidEnableEncryption(self, sender, data):
        self.sessionController.log_info(
            "%s video encryption active using %s" %
            (sender.encryption.type, sender.encryption.cipher))
        try:
            otr = self.sessionController.encryption['video']
        except KeyError:
            self.sessionController.encryption['video'] = {}

        self.sessionController.encryption['video'][
            'type'] = sender.encryption.type
        if self.videoWindowController:
            self.videoWindowController.update_encryption_icon()

    @objc.python_method
    def _NH_RTPStreamDidNotEncryption(self, sender, data):
        self.sessionController.log_info("Video encryption not enabled: %s" %
                                        data.reason)
        if sender.encryption.type != 'ZRTP':
            return
        if self.videoWindowController:
            self.videoWindowController.update_encryption_icon()

    @objc.python_method
    def _NH_RTPStreamZRTPReceivedSAS(self, sender, data):
        if self.videoWindowController:
            self.videoWindowController.update_encryption_icon()

    @objc.python_method
    def _NH_RTPStreamZRTPVerifiedStateChanged(self, sender, data):
        if self.videoWindowController:
            try:
                otr = self.sessionController.encryption['video']
            except KeyError:
                self.sessionController.encryption['video'] = {}

            self.sessionController.encryption['video']['type'] = 'ZRTP'
            self.sessionController.encryption['video'][
                'verified'] = 'yes' if self.stream.encryption.zrtp.verified else 'no'

            self.videoWindowController.update_encryption_icon()