audio => develop

This commit is contained in:
ingvar1995 2016-04-24 13:45:11 +03:00
parent 7b9204a88b
commit e1db914f13
21 changed files with 1016 additions and 65 deletions

106
src/avwidgets.py Normal file
View File

@ -0,0 +1,106 @@
from PySide import QtCore, QtGui
import widgets
import profile
import util
import pyaudio
import wave
import settings
from util import curr_directory
class IncomingCallWidget(widgets.CenteredWidget):
def __init__(self, friend_number, text, name):
super(IncomingCallWidget, self).__init__()
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
self.resize(QtCore.QSize(500, 270))
self.avatar_label = QtGui.QLabel(self)
self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
self.avatar_label.setScaledContents(False)
self.name = widgets.DataLabel(self)
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setPointSize(16)
font.setBold(True)
self.name.setFont(font)
self.call_type = widgets.DataLabel(self)
self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25))
self.call_type.setFont(font)
self.accept_audio = QtGui.QPushButton(self)
self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150))
self.accept_video = QtGui.QPushButton(self)
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
self.decline = QtGui.QPushButton(self)
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
icon = QtGui.QIcon(pixmap)
self.accept_audio.setIcon(icon)
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_video.png')
icon = QtGui.QIcon(pixmap)
self.accept_video.setIcon(icon)
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/decline_call.png')
icon = QtGui.QIcon(pixmap)
self.decline.setIcon(icon)
self.accept_audio.setIconSize(QtCore.QSize(150, 150))
self.accept_video.setIconSize(QtCore.QSize(140, 140))
self.decline.setIconSize(QtCore.QSize(140, 140))
self.accept_audio.setStyleSheet("QPushButton { border: none }")
self.accept_video.setStyleSheet("QPushButton { border: none }")
self.decline.setStyleSheet("QPushButton { border: none }")
self.setWindowTitle(text)
self.name.setText(name)
self.call_type.setText(text)
pr = profile.Profile.get_instance()
self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop())
# self.accept_video.clicked.connect(lambda: pr.start_call(friend_number, True, True))
self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop())
class SoundPlay(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
class AudioFile(object):
chunk = 1024
def __init__(self, fl):
self.stop = False
self.wf = wave.open(fl, 'rb')
self.p = pyaudio.PyAudio()
self.stream = self.p.open(
format=self.p.get_format_from_width(self.wf.getsampwidth()),
channels=self.wf.getnchannels(),
rate=self.wf.getframerate(),
output=True
)
def play(self):
data = self.wf.readframes(self.chunk)
while data and not self.stop:
self.stream.write(data)
data = self.wf.readframes(self.chunk)
def close(self):
self.stream.close()
self.p.terminate()
self.a = AudioFile(curr_directory() + '/sounds/call.wav')
self.a.play()
self.a.close()
if settings.Settings.get_instance()['calls_sound']:
self.thread = SoundPlay()
self.thread.start()
else:
self.thread = None
def stop(self):
if self.thread is not None:
self.thread.a.stop = True
self.thread.wait()
self.close()
def set_pixmap(self, pixmap):
self.avatar_label.setPixmap(pixmap)

View File

@ -3,6 +3,7 @@ from notifications import *
from settings import Settings
from profile import Profile
from toxcore_enums_and_consts import *
from toxav_enums import *
from tox import bin_to_string
from ctypes import c_char_p, cast, pointer
@ -200,6 +201,35 @@ def file_recv_control(tox, friend_number, file_number, file_control, user_data):
if file_control == TOX_FILE_CONTROL['CANCEL']:
Profile.get_instance().cancel_transfer(friend_number, file_number, True)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - audio
# -----------------------------------------------------------------------------------------------------------------
def call_state(toxav, friend_number, mask, user_data):
"""New call state"""
print friend_number, mask
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
else:
Profile.get_instance().call.toxav_call_state_cb(friend_number, mask)
def call(toxav, friend_number, audio, video, user_data):
"""Incoming call from friend"""
print friend_number, audio, video
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
"""New audio chunk"""
print audio_samples_per_channel, audio_channels_count, rate
Profile.get_instance().call.chunk(
''.join(chr(x) for x in samples[:audio_samples_per_channel * 2 * audio_channels_count]),
audio_channels_count,
rate)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization
# -----------------------------------------------------------------------------------------------------------------
@ -225,3 +255,9 @@ def init_callbacks(tox, window, tray):
tox.callback_file_recv_chunk(file_recv_chunk, 0)
tox.callback_file_chunk_request(file_chunk_request, 0)
tox.callback_file_recv_control(file_recv_control, 0)
toxav = tox.AV
toxav.callback_call_state(call_state, 0)
toxav.callback_call(call, 0)
toxav.callback_audio_receive_frame(callback_audio, 0)

143
src/calls.py Normal file
View File

@ -0,0 +1,143 @@
import pyaudio
import time
import threading
import settings
from toxav_enums import *
# TODO: play sound until outgoing call will be started or cancelled
CALL_TYPE = {
'NONE': 0,
'AUDIO': 1,
'VIDEO': 2
}
class AV(object):
def __init__(self, toxav):
self._toxav = toxav
self._running = True
self._calls = {} # dict: key - friend number, value - call type
self._audio = None
self._audio_stream = None
self._audio_thread = None
self._audio_running = False
self._out_stream = None
self._audio_rate = 8000
self._audio_channels = 1
self._audio_duration = 60
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration / 1000
def __contains__(self, friend_number):
return friend_number in self._calls
def __call__(self, friend_number, audio, video):
"""Call friend with specified number"""
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
self._calls[friend_number] = CALL_TYPE['AUDIO']
self.start_audio_thread()
def finish_call(self, friend_number, by_friend=False):
if not by_friend:
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
if friend_number in self._calls:
del self._calls[friend_number]
if not len(self._calls):
self.stop_audio_thread()
def stop(self):
self._running = False
self.stop_audio_thread()
def start_audio_thread(self):
"""
Start audio sending
"""
if self._audio_thread is not None:
return
self._audio_running = True
self._audio = pyaudio.PyAudio()
self._audio_stream = self._audio.open(format=pyaudio.paInt16,
rate=self._audio_rate,
channels=self._audio_channels,
input=True,
input_device_index=settings.Settings().get_instance().audio['input'],
frames_per_buffer=self._audio_sample_count * 10)
self._audio_thread = threading.Thread(target=self.send_audio)
self._audio_thread.start()
def stop_audio_thread(self):
if self._audio_thread is None:
return
self._audio_running = False
self._audio_thread.join()
self._audio_thread = None
self._audio_stream = None
self._audio = None
if self._out_stream is not None:
self._out_stream.stop_stream()
self._out_stream.close()
self._out_stream = None
def chunk(self, samples, channels_count, rate):
"""
Incoming chunk
"""
if self._out_stream is None:
self._out_stream = self._audio.open(format=pyaudio.paInt16,
channels=channels_count,
rate=rate,
output_device_index=settings.Settings().get_instance().audio['output'],
output=True)
self._out_stream.write(samples)
def send_audio(self):
"""
This method sends audio to friends
"""
while self._audio_running:
try:
pcm = self._audio_stream.read(self._audio_sample_count)
if pcm:
for friend in self._calls:
if self._calls[friend] & 1:
try:
self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count,
self._audio_channels, self._audio_rate)
except:
pass
except:
pass
time.sleep(0.01)
def accept_call(self, friend_number, audio_enabled, video_enabled):
if self._running:
self._calls[friend_number] = int(video_enabled) * 2 + int(audio_enabled)
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
self.start_audio_thread()
def toxav_call_state_cb(self, friend_number, state):
"""
New call state
"""
if self._running:
if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']:
self._calls[friend_number] |= 1

BIN
src/images/accept_audio.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/images/accept_video.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/images/decline_call.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/images/finish_call.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
src/images/incoming_call.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

33
src/libtox.py Normal file
View File

@ -0,0 +1,33 @@
from platform import system
from ctypes import CDLL
class LibToxCore(object):
def __init__(self):
if system() == 'Linux':
# be sure that libtoxcore and libsodium are installed in your os
self._libtoxcore = CDLL('libtoxcore.so')
elif system() == 'Windows':
self._libtoxcore = CDLL('libs/libtox.dll')
else:
raise OSError('Unknown system.')
def __getattr__(self, item):
return self._libtoxcore.__getattr__(item)
class LibToxAV(object):
def __init__(self):
if system() == 'Linux':
# be sure that /usr/lib/libtoxav.so exists
self._libtoxav = CDLL('libtoxav.so')
elif system() == 'Windows':
# on Windows av api is in libtox.dll
self._libtoxav = CDLL('libs/libtox.dll')
else:
raise OSError('Unknown system.')
def __getattr__(self, item):
return self._libtoxav.__getattr__(item)

View File

@ -4,6 +4,7 @@ import profile
from file_transfers import TOX_FILE_TRANSFER_STATE
from util import curr_directory, convert_time
from messages import FILE_TRANSFER_MESSAGE_STATUS
from widgets import DataLabel
class MessageEdit(QtGui.QTextEdit):
@ -65,15 +66,6 @@ class MessageItem(QtGui.QWidget):
self.message.setStyleSheet("QTextEdit { color: red; }")
class DataLabel(QtGui.QLabel):
def paintEvent(self, event):
painter = QtGui.QPainter(self)
metrics = QtGui.QFontMetrics(self.font())
text = metrics.elidedText(self.text(), QtCore.Qt.ElideRight, self.width())
painter.drawText(self.rect(), self.alignment(), text)
class ContactItem(QtGui.QWidget):
"""
Contact in friends list

View File

@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from PySide import QtCore, QtGui
import sys
import os
from widgets import *
class LoginScreen(QtGui.QWidget):
class LoginScreen(CenteredWidget):
def __init__(self):
super(LoginScreen, self).__init__()
@ -15,7 +14,6 @@ class LoginScreen(QtGui.QWidget):
self.resize(400, 200)
self.setMinimumSize(QtCore.QSize(400, 200))
self.setMaximumSize(QtCore.QSize(400, 200))
self.setBaseSize(QtCore.QSize(400, 200))
self.new_profile = QtGui.QPushButton(self)
self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27))
self.new_profile.clicked.connect(self.create_profile)
@ -54,13 +52,6 @@ class LoginScreen(QtGui.QWidget):
self.name = None
self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self)
self.center()
def center(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -15,7 +15,7 @@ class Toxygen(object):
def __init__(self):
super(Toxygen, self).__init__()
self.tox = self.ms = self.init = self.mainloop = None
self.tox = self.ms = self.init = self.mainloop = self.avloop = None
def main(self):
"""
@ -123,15 +123,19 @@ class Toxygen(object):
self.init = self.InitThread(self.tox, self.ms, self.tray)
self.init.start()
# starting thread for tox iterate
# starting threads for tox iterate and toxav iterate
self.mainloop = self.ToxIterateThread(self.tox)
self.mainloop.start()
self.avloop = self.ToxAVIterateThread(self.tox.AV)
self.avloop.start()
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
app.exec_()
self.init.stop = True
self.mainloop.stop = True
self.avloop.stop = True
self.mainloop.wait()
self.init.wait()
self.avloop.wait()
data = self.tox.get_savedata()
ProfileHelper.save_profile(data)
settings.close()
@ -144,8 +148,10 @@ class Toxygen(object):
"""
self.mainloop.stop = True
self.init.stop = True
self.avloop.stop = True
self.mainloop.wait()
self.init.wait()
self.avloop.wait()
data = self.tox.get_savedata()
ProfileHelper.save_profile(data)
del self.tox
@ -155,9 +161,12 @@ class Toxygen(object):
self.init = self.InitThread(self.tox, self.ms, self.tray)
self.init.start()
# starting thread for tox iterate
# starting threads for tox iterate and toxav iterate
self.mainloop = self.ToxIterateThread(self.tox)
self.mainloop.start()
self.avloop = self.ToxAVIterateThread(self.tox.AV)
self.avloop.start()
return self.tox
# -----------------------------------------------------------------------------------------------------------------
@ -209,6 +218,18 @@ class Toxygen(object):
self.tox.iterate()
self.msleep(self.tox.iteration_interval())
class ToxAVIterateThread(QtCore.QThread):
def __init__(self, toxav):
QtCore.QThread.__init__(self)
self.toxav = toxav
self.stop = False
def run(self):
while not self.stop:
self.toxav.iterate()
self.msleep(self.toxav.iteration_interval())
class Login(object):
def __init__(self, arr):

View File

@ -60,12 +60,14 @@ class MainWindow(QtGui.QMainWindow):
self.actionAbout_program.setObjectName("actionAbout_program")
self.actionSettings = QtGui.QAction(MainWindow)
self.actionSettings.setObjectName("actionSettings")
self.audioSettings = QtGui.QAction(MainWindow)
self.menuProfile.addAction(self.actionAdd_friend)
self.menuProfile.addAction(self.actionSettings)
self.menuSettings.addAction(self.actionPrivacy_settings)
self.menuSettings.addAction(self.actionInterface_settings)
self.menuSettings.addAction(self.actionNotifications)
self.menuSettings.addAction(self.actionNetwork)
self.menuSettings.addAction(self.audioSettings)
self.menuAbout.addAction(self.actionAbout_program)
self.menubar.addAction(self.menuProfile.menuAction())
self.menubar.addAction(self.menuSettings.menuAction())
@ -78,6 +80,7 @@ class MainWindow(QtGui.QMainWindow):
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
self.actionInterface_settings.triggered.connect(self.interface_settings)
self.actionNotifications.triggered.connect(self.notification_settings)
self.audioSettings.triggered.connect(self.audio_settings)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def languageChange(self, *args, **kwargs):
@ -96,6 +99,7 @@ class MainWindow(QtGui.QMainWindow):
self.actionNetwork.setText(QtGui.QApplication.translate("MainWindow", "Network", None, QtGui.QApplication.UnicodeUTF8))
self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", None, QtGui.QApplication.UnicodeUTF8))
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
def setup_right_bottom(self, Form):
Form.setObjectName("right_bottom")
@ -202,10 +206,8 @@ class MainWindow(QtGui.QMainWindow):
self.callButton = QtGui.QPushButton(Form)
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
self.callButton.setObjectName("callButton")
pixmap = QtGui.QPixmap(curr_directory() + '/images/call.png')
icon = QtGui.QIcon(pixmap)
self.callButton.setIcon(icon)
self.callButton.setIconSize(QtCore.QSize(50, 50))
self.callButton.clicked.connect(self.call)
self.update_call_state('call')
QtCore.QMetaObject.connectSlotsByName(Form)
def setup_left_center(self, widget):
@ -271,6 +273,7 @@ class MainWindow(QtGui.QMainWindow):
def closeEvent(self, *args, **kwargs):
self.profile.save_history()
self.profile.close()
QtGui.QApplication.closeAllWindows()
# -----------------------------------------------------------------------------------------------------------------
@ -308,8 +311,12 @@ class MainWindow(QtGui.QMainWindow):
self.int_s = InterfaceSettings()
self.int_s.show()
def audio_settings(self):
self.audio_s = AudioSettings()
self.audio_s.show()
# -----------------------------------------------------------------------------------------------------------------
# Messages and file transfers
# Messages, calls and file transfers
# -----------------------------------------------------------------------------------------------------------------
def send_message(self):
@ -328,6 +335,25 @@ class MainWindow(QtGui.QMainWindow):
self.sw = ScreenShotWindow()
self.sw.show()
def call(self):
if self.profile.is_active_online(): # active friend exists and online
self.profile.call_click(True)
def active_call(self):
self.update_call_state('finish_call')
def incoming_call(self):
self.update_call_state('incoming_call')
def call_finished(self):
self.update_call_state('call')
def update_call_state(self, fl):
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
icon = QtGui.QIcon(pixmap)
self.callButton.setIcon(icon)
self.callButton.setIconSize(QtCore.QSize(50, 50))
# -----------------------------------------------------------------------------------------------------------------
# Functions which called when user open context menu in friends list
# -----------------------------------------------------------------------------------------------------------------

View File

@ -2,19 +2,8 @@ from PySide import QtCore, QtGui
from settings import *
from profile import Profile
from util import get_style, curr_directory
class CenteredWidget(QtGui.QWidget):
def __init__(self):
super(CenteredWidget, self).__init__()
self.center()
def center(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
from widgets import CenteredWidget
import pyaudio
class AddContact(CenteredWidget):
@ -442,3 +431,54 @@ class InterfaceSettings(CenteredWidget):
app.installTranslator(app.translator)
settings.save()
class AudioSettings(CenteredWidget):
def __init__(self):
super(AudioSettings, self).__init__()
self.initUI()
self.retranslateUi()
def initUI(self):
self.setObjectName("audioSettingsForm")
self.resize(400, 150)
self.setMinimumSize(QtCore.QSize(400, 150))
self.setMaximumSize(QtCore.QSize(400, 150))
self.in_label = QtGui.QLabel(self)
self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
self.out_label = QtGui.QLabel(self)
self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20))
font = QtGui.QFont()
font.setPointSize(16)
font.setBold(True)
self.in_label.setFont(font)
self.out_label.setFont(font)
self.input = QtGui.QComboBox(self)
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
self.output = QtGui.QComboBox(self)
self.output.setGeometry(QtCore.QRect(25, 90, 350, 30))
p = pyaudio.PyAudio()
settings = Settings.get_instance()
self.in_indexes, self.out_indexes = [], []
for i in xrange(p.get_device_count()):
device = p.get_device_info_by_index(i)
if device["maxInputChannels"]:
self.input.addItem(unicode(device["name"]))
self.in_indexes.append(i)
if device["maxOutputChannels"]:
self.output.addItem(unicode(device["name"]))
self.out_indexes.append(i)
self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("audioSettingsForm", "Audio settings", None, QtGui.QApplication.UnicodeUTF8))
self.in_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Input device:", None, QtGui.QApplication.UnicodeUTF8))
self.out_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Output device:", None, QtGui.QApplication.UnicodeUTF8))
def closeEvent(self, event):
settings = Settings.get_instance()
settings.audio['input'] = self.in_indexes[self.input.currentIndex()]
settings.audio['output'] = self.out_indexes[self.output.currentIndex()]
settings.save()

View File

@ -11,6 +11,8 @@ from tox_dns import tox_dns
from history import *
from file_transfers import *
import time
import calls
import avwidgets
class Contact(object):
@ -115,6 +117,9 @@ class Contact(object):
f.write(avatar)
self.load_avatar()
def get_pixmap(self):
return self._widget.avatar_label.pixmap()
class Friend(Contact):
"""
@ -287,6 +292,7 @@ class Profile(Contact, Singleton):
self._messages = screen.messages
self._tox = tox
self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
self._call = calls.AV(tox.AV) # object with data about calls
settings = Settings.get_instance()
self._show_online = settings['show_online_friends']
screen.online_contacts.setChecked(self._show_online)
@ -374,6 +380,7 @@ class Profile(Contact, Singleton):
"""
:param value: number of new active friend in friend's list or None to update active user's data
"""
# TODO: check if there is incoming call with friend
if value is None and self._active_friend == -1: # nothing to update
return
if value == -1: # all friends were deleted
@ -408,6 +415,10 @@ class Profile(Contact, Singleton):
else: # inline
self.create_inline_item(message.get_data())
self._messages.scrollToBottom()
if value in self._call:
self._screen.active_call()
else:
self._screen.call_finished()
else:
friend = self._friends[self._active_friend]
@ -745,6 +756,10 @@ class Profile(Contact, Singleton):
for friend in self._friends:
friend.status = None
def close(self):
self._call.stop()
del self._call
# -----------------------------------------------------------------------------------------------------------------
# File transfers support
# -----------------------------------------------------------------------------------------------------------------
@ -956,6 +971,51 @@ class Profile(Contact, Singleton):
for friend in filter(lambda x: x.status is not None, self._friends):
self.send_avatar(friend.number)
# -----------------------------------------------------------------------------------------------------------------
# AV support
# -----------------------------------------------------------------------------------------------------------------
def get_call(self):
return self._call
call = property(get_call)
def call_click(self, audio=True, video=False):
"""User clicked audio button in main window"""
num = self.get_active_number()
if num not in self._call and self.is_active_online(): # start call
self._call(num, audio, video)
self._screen.active_call()
elif num in self._call: # finish or cancel call if you call with active friend
self.stop_call(num, False)
def incoming_call(self, audio, video, friend_number):
friend = self.get_friend_by_number(friend_number)
if friend_number == self.get_active_number():
self._screen.incoming_call()
# self.accept_call(friend_number, audio, video)
else:
friend.set_messages(True)
if video:
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None, QtGui.QApplication.UnicodeUTF8)
else:
text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None, QtGui.QApplication.UnicodeUTF8)
self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
self._call_widget.set_pixmap(friend.get_pixmap())
self._call_widget.show()
def accept_call(self, friend_number, audio, video):
self._call.accept_call(friend_number, audio, video)
self._screen.active_call()
if hasattr(self, '_call_widget'):
del self._call_widget
def stop_call(self, friend_number, by_friend):
self._screen.call_finished()
self._call.finish_call(friend_number, by_friend) # finish or decline call
if hasattr(self, '_call_widget'):
del self._call_widget
def tox_factory(data=None, settings=None):
"""

View File

@ -3,6 +3,7 @@ import json
import os
import locale
from util import Singleton, curr_directory
import pyaudio
class Settings(Singleton, dict):
@ -17,6 +18,9 @@ class Settings(Singleton, dict):
else:
super(self.__class__, self).__init__(Settings.get_default_settings())
self.save()
p = pyaudio.PyAudio()
self.audio = {'input': p.get_default_input_device_info()['index'],
'output': p.get_default_output_device_info()['index']}
@staticmethod
def get_auto_profile():

BIN
src/sounds/call.wav Executable file

Binary file not shown.

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from ctypes import c_char_p, Structure, CDLL, c_bool, addressof, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64
from ctypes import c_char_p, Structure, c_bool, addressof, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64
from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8
from platform import system
from toxcore_enums_and_consts import *
from toxav import ToxAV
from libtox import LibToxCore
class ToxOptions(Structure):
@ -21,20 +22,6 @@ class ToxOptions(Structure):
]
class LibToxCore(object):
def __init__(self):
if system() == 'Linux':
# be sure that libtoxcore and libsodium are installed in your os
self._libtoxcore = CDLL('libtoxcore.so')
elif system() == 'Windows':
self._libtoxcore = CDLL('libs/libtox.dll')
else:
raise OSError('Unknown system.')
def __getattr__(self, item):
return self._libtoxcore.__getattr__(item)
def string_to_bin(tox_id):
return c_char_p(tox_id.decode('hex')) if tox_id is not None else None
@ -103,7 +90,10 @@ class Tox(object):
self.file_recv_cb = None
self.file_recv_chunk_cb = None
self.AV = ToxAV(self._tox_pointer)
def __del__(self):
del self.AV
Tox.libtoxcore.tox_kill(self._tox_pointer)
# -----------------------------------------------------------------------------------------------------------------
@ -1406,11 +1396,3 @@ class Tox(object):
return result
elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
raise RuntimeError('The instance was not bound to any port.')
if __name__ == '__main__':
tox = Tox(Tox.options_new())
p = tox.get_savedata()
print type(p)
print p
del tox

363
src/toxav.py Normal file
View File

@ -0,0 +1,363 @@
from ctypes import c_int, POINTER, c_void_p, addressof, ArgumentError, c_uint32, CFUNCTYPE, c_size_t, c_uint8, c_uint16
from ctypes import c_char_p, c_int32, c_bool, cast
from libtox import LibToxAV
from toxav_enums import *
class ToxAV(object):
"""
The ToxAV instance type. Each ToxAV instance can be bound to only one Tox instance, and Tox instance can have only
one ToxAV instance. One must make sure to close ToxAV instance prior closing Tox instance otherwise undefined
behaviour occurs. Upon closing of ToxAV instance, all active calls will be forcibly terminated without notifying
peers.
"""
libtoxav = LibToxAV()
# -----------------------------------------------------------------------------------------------------------------
# Creation and destruction
# -----------------------------------------------------------------------------------------------------------------
def __init__(self, tox_pointer):
"""
Start new A/V session. There can only be only one session per Tox instance.
:param tox_pointer: pointer to Tox instance
"""
toxav_err_new = c_int()
ToxAV.libtoxav.toxav_new.restype = POINTER(c_void_p)
self._toxav_pointer = ToxAV.libtoxav.toxav_new(tox_pointer, addressof(toxav_err_new))
toxav_err_new = toxav_err_new.value
if toxav_err_new == TOXAV_ERR_NEW['NULL']:
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
elif toxav_err_new == TOXAV_ERR_NEW['MALLOC']:
raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V '
'session.')
elif toxav_err_new == TOXAV_ERR_NEW['MULTIPLE']:
raise RuntimeError('Attempted to create a second session for the same Tox instance.')
self.call_state_cb = None
self.audio_receive_frame_cb = None
self.video_receive_frame_cb = None
self.call_cb = None
def __del__(self):
"""
Releases all resources associated with the A/V session.
If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this
function, no other functions may be called and the av pointer becomes invalid.
"""
ToxAV.libtoxav.toxav_kill(self._toxav_pointer)
def get_tox_pointer(self):
"""
Returns the Tox instance the A/V object was created for.
:return: pointer to the Tox instance
"""
ToxAV.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
return ToxAV.libtoxav.toxav_get_tox(self._toxav_pointer)
# -----------------------------------------------------------------------------------------------------------------
# A/V event loop
# -----------------------------------------------------------------------------------------------------------------
def iteration_interval(self):
"""
Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the
moment, this function returns 200.
:return: interval in milliseconds
"""
return ToxAV.libtoxav.toxav_iteration_interval(self._toxav_pointer)
def iterate(self):
"""
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
milliseconds. It is best called in the separate thread from tox_iterate.
"""
ToxAV.libtoxav.toxav_iterate(self._toxav_pointer)
# -----------------------------------------------------------------------------------------------------------------
# Call setup
# -----------------------------------------------------------------------------------------------------------------
def call(self, friend_number, audio_bit_rate, video_bit_rate):
"""
Call a friend. This will start ringing the friend.
It is the client's responsibility to stop ringing after a certain timeout, if such behaviour is desired. If the
client does not stop ringing, the library will not stop until the friend is disconnected. Audio and video
receiving are both enabled by default.
:param friend_number: The friend number of the friend that should be called.
:param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
:param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
:return: True on success.
"""
toxav_err_call = c_int()
result = ToxAV.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
c_uint32(video_bit_rate), addressof(toxav_err_call))
toxav_err_call = toxav_err_call.value
if toxav_err_call == TOXAV_ERR_CALL['OK']:
return bool(result)
elif toxav_err_call == TOXAV_ERR_CALL['MALLOC']:
raise MemoryError('A resource allocation error occurred while trying to create the structures required for '
'the call.')
elif toxav_err_call == TOXAV_ERR_CALL['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend number did not designate a valid friend.')
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']:
raise ArgumentError('The friend was valid, but not currently connected.')
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']:
raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.')
elif toxav_err_call == TOXAV_ERR_CALL['INVALID_BIT_RATE']:
raise ArgumentError('Audio or video bit rate is invalid.')
def callback_call(self, callback, user_data):
"""
Set the callback for the `call` event. Pass None to unset.
:param callback: The function for the call callback.
Should take pointer (c_void_p) to ToxAV object,
The friend number (c_uint32) from which the call is incoming.
True (c_bool) if friend is sending audio.
True (c_bool) if friend is sending video.
pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_bool, c_void_p)
self.call_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
def answer(self, friend_number, audio_bit_rate, video_bit_rate):
"""
Accept an incoming call.
If answering fails for any reason, the call will still be pending and it is possible to try and answer it later.
Audio and video receiving are both enabled by default.
:param friend_number: The friend number of the friend that is calling.
:param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
:param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
:return: True on success.
"""
toxav_err_answer = c_int()
result = ToxAV.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
c_uint32(video_bit_rate), addressof(toxav_err_answer))
toxav_err_answer = toxav_err_answer.value
if toxav_err_answer == TOXAV_ERR_ANSWER['OK']:
return bool(result)
elif toxav_err_answer == TOXAV_ERR_ANSWER['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']:
raise RuntimeError('Failed to initialize codecs for call session. Note that codec initiation will fail if '
'there is no receive callback registered for either audio or video.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend number did not designate a valid friend.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']:
raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is '
'also returned if this client is already in a call with the friend.')
elif toxav_err_answer == TOXAV_ERR_ANSWER['INVALID_BIT_RATE']:
raise ArgumentError('Audio or video bit rate is invalid.')
# -----------------------------------------------------------------------------------------------------------------
# Call state graph
# -----------------------------------------------------------------------------------------------------------------
def callback_call_state(self, callback, user_data):
"""
Set the callback for the `call_state` event. Pass None to unset.
:param callback: Python function.
The function for the call_state callback.
Should take pointer (c_void_p) to ToxAV object,
The friend number (c_uint32) for which the call state changed.
The bitmask of the new call state which is guaranteed to be different than the previous state. The state is set
to 0 when the call is paused. The bitmask represents all the activities currently performed by the friend.
pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
self.call_state_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Call control
# -----------------------------------------------------------------------------------------------------------------
def call_control(self, friend_number, control):
"""
Sends a call control command to a friend.
:param friend_number: The friend number of the friend this client is in a call with.
:param control: The control command to send.
:return: True on success.
"""
toxav_err_call_control = c_int()
result = ToxAV.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
addressof(toxav_err_call_control))
toxav_err_call_control = toxav_err_call_control.value
if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']:
return bool(result)
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number passed did not designate a valid friend.')
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']:
raise RuntimeError('This client is currently not in a call with the friend. Before the call is answered, '
'only CANCEL is a valid control.')
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']:
raise RuntimeError('Happens if user tried to pause an already paused call or if trying to resume a call '
'that is not paused.')
# -----------------------------------------------------------------------------------------------------------------
# TODO Controlling bit rates
# -----------------------------------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------------
# A/V sending
# -----------------------------------------------------------------------------------------------------------------
def audio_send_frame(self, friend_number, pcm, sample_count, channels, sampling_rate):
"""
Send an audio frame to a friend.
The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]...
Meaning: sample 1 for channel 1, sample 1 for channel 2, ...
For mono audio, this has no meaning, every sample is subsequent. For stereo, this means the expected format is
LRLRLR... with samples for left and right alternating.
:param friend_number: The friend number of the friend to which to send an audio frame.
:param pcm: An array of audio samples. The size of this array must be sample_count * channels.
:param sample_count: Number of samples in this frame. Valid numbers here are
((sample rate) * (audio length) / 1000), where audio length can be 2.5, 5, 10, 20, 40 or 60 milliseconds.
:param channels: Number of audio channels. Sulpported values are 1 and 2.
:param sampling_rate: Audio sampling rate used in this frame. Valid sampling rates are 8000, 12000, 16000,
24000, or 48000.
"""
toxav_err_send_frame = c_int()
result = ToxAV.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
cast(pcm, c_void_p),
c_size_t(sample_count), c_uint8(channels),
c_uint32(sampling_rate), addressof(toxav_err_send_frame))
toxav_err_send_frame = toxav_err_send_frame.value
if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
return bool(result)
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
raise ArgumentError('The samples data pointer was NULL.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number passed did not designate a valid friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
raise RuntimeError('This client is currently not in a call with the friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
'large, or the audio sampling rate may be unsupported.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
'payload.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
RuntimeError('Failed to push frame through rtp interface.')
def video_send_frame(self, friend_number, width, height, y, u, v):
"""
Send a video frame to a friend.
Y - plane should be of size: height * width
U - plane should be of size: (height/2) * (width/2)
V - plane should be of size: (height/2) * (width/2)
:param friend_number: The friend number of the friend to which to send a video frame.
:param width: Width of the frame in pixels.
:param height: Height of the frame in pixels.
:param y: Y (Luminance) plane data.
:param u: U (Chroma) plane data.
:param v: V (Chroma) plane data.
"""
toxav_err_send_frame = c_int()
result = ToxAV.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v),
addressof(toxav_err_send_frame))
toxav_err_send_frame = toxav_err_send_frame.value
if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
return bool(result)
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
raise ArgumentError('One of Y, U, or V was NULL.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number passed did not designate a valid friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
raise RuntimeError('This client is currently not in a call with the friend.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
raise RuntimeError('Synchronization error occurred.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
'large, or the audio sampling rate may be unsupported.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
'payload.')
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
RuntimeError('Failed to push frame through rtp interface.')
# -----------------------------------------------------------------------------------------------------------------
# A/V receiving
# -----------------------------------------------------------------------------------------------------------------
def callback_audio_receive_frame(self, callback, user_data):
"""
Set the callback for the `audio_receive_frame` event. Pass None to unset.
:param callback: Python function.
Function for the audio_receive_frame callback. The callback can be called multiple times per single
iteration depending on the amount of queued frames in the buffer. The received format is the same as in send
function.
Should take pointer (c_void_p) to ToxAV object,
The friend number (c_uint32) of the friend who sent an audio frame.
An array (c_uint8) of audio samples (sample_count * channels elements).
The number (c_size_t) of audio samples per channel in the PCM array.
Number (c_uint8) of audio channels.
Sampling rate (c_uint32) used in this frame.
pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p)
self.audio_receive_frame_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
def callback_video_receive_frame(self, callback, user_data):
"""
Set the callback for the `video_receive_frame` event. Pass None to unset.
:param callback: Python function.
The function type for the video_receive_frame callback.
Should take
toxAV pointer (c_void_p) to ToxAV object,
friend_number The friend number (c_uint32) of the friend who sent a video frame.
width Width (c_uint16) of the frame in pixels.
height Height (c_uint16) of the frame in pixels.
y
u
v Plane data (c_char_p).
The size of plane data is derived from width and height where
Y = MAX(width, abs(ystride)) * height,
U = MAX(width/2, abs(ustride)) * (height/2) and
V = MAX(width/2, abs(vstride)) * (height/2).
ystride
ustride
vstride Strides data (c_int32). Strides represent padding for each plane that may or may not be present. You must
handle strides in your image processing code. Strides are negative if the image is bottom-up
hence why you MUST abs() it when calculating plane buffer size.
user_data pointer (c_void_p) to user_data
:param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint16, c_uint16, c_char_p, c_char_p, c_char_p, c_int32,
c_int32, c_int32, c_void_p)
self.video_receive_frame_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)

131
src/toxav_enums.py Normal file
View File

@ -0,0 +1,131 @@
TOXAV_ERR_NEW = {
# The function returned successfully.
'OK': 0,
# One of the arguments to the function was NULL when it was not expected.
'NULL': 1,
# Memory allocation failure while trying to allocate structures required for the A/V session.
'MALLOC': 2,
# Attempted to create a second session for the same Tox instance.
'MULTIPLE': 3,
}
TOXAV_ERR_CALL = {
# The function returned successfully.
'OK': 0,
# A resource allocation error occurred while trying to create the structures required for the call.
'MALLOC': 1,
# Synchronization error occurred.
'SYNC': 2,
# The friend number did not designate a valid friend.
'FRIEND_NOT_FOUND': 3,
# The friend was valid, but not currently connected.
'FRIEND_NOT_CONNECTED': 4,
# Attempted to call a friend while already in an audio or video call with them.
'FRIEND_ALREADY_IN_CALL': 5,
# Audio or video bit rate is invalid.
'INVALID_BIT_RATE': 6,
}
TOXAV_ERR_ANSWER = {
# The function returned successfully.
'OK': 0,
# Synchronization error occurred.
'SYNC': 1,
# Failed to initialize codecs for call session. Note that codec initiation will fail if there is no receive callback
# registered for either audio or video.
'CODEC_INITIALIZATION': 2,
# The friend number did not designate a valid friend.
'FRIEND_NOT_FOUND': 3,
# The friend was valid, but they are not currently trying to initiate a call. This is also returned if this client
# is already in a call with the friend.
'FRIEND_NOT_CALLING': 4,
# Audio or video bit rate is invalid.
'INVALID_BIT_RATE': 5,
}
TOXAV_FRIEND_CALL_STATE = {
# Set by the AV core if an error occurred on the remote end or if friend timed out. This is the final state after
# which no more state transitions can occur for the call. This call state will never be triggered in combination
# with other call states.
'ERROR': 1,
# The call has finished. This is the final state after which no more state transitions can occur for the call. This
# call state will never be triggered in combination with other call states.
'FINISHED': 2,
# The flag that marks that friend is sending audio.
'SENDING_A': 4,
# The flag that marks that friend is sending video.
'SENDING_V': 8,
# The flag that marks that friend is receiving audio.
'ACCEPTING_A': 16,
# The flag that marks that friend is receiving video.
'ACCEPTING_V': 32,
}
TOXAV_CALL_CONTROL = {
# Resume a previously paused call. Only valid if the pause was caused by this client, if not, this control is
# ignored. Not valid before the call is accepted.
'RESUME': 0,
# Put a call on hold. Not valid before the call is accepted.
'PAUSE': 1,
# Reject a call if it was not answered, yet. Cancel a call after it was answered.
'CANCEL': 2,
# Request that the friend stops sending audio. Regardless of the friend's compliance, this will cause the
# audio_receive_frame event to stop being triggered on receiving an audio frame from the friend.
'MUTE_AUDIO': 3,
# Calling this control will notify client to start sending audio again.
'UNMUTE_AUDIO': 4,
# Request that the friend stops sending video. Regardless of the friend's compliance, this will cause the
# video_receive_frame event to stop being triggered on receiving a video frame from the friend.
'HIDE_VIDEO': 5,
# Calling this control will notify client to start sending video again.
'SHOW_VIDEO': 6,
}
TOXAV_ERR_CALL_CONTROL = {
# The function returned successfully.
'OK': 0,
# Synchronization error occurred.
'SYNC': 1,
# The friend_number passed did not designate a valid friend.
'FRIEND_NOT_FOUND': 2,
# This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid
# control.
'FRIEND_NOT_IN_CALL': 3,
# Happens if user tried to pause an already paused call or if trying to resume a call that is not paused.
'INVALID_TRANSITION': 4,
}
TOXAV_ERR_BIT_RATE_SET = {
# The function returned successfully.
'OK': 0,
# Synchronization error occurred.
'SYNC': 1,
# The audio bit rate passed was not one of the supported values.
'INVALID_AUDIO_BIT_RATE': 2,
# The video bit rate passed was not one of the supported values.
'INVALID_VIDEO_BIT_RATE': 3,
# The friend_number passed did not designate a valid friend.
'FRIEND_NOT_FOUND': 4,
# This client is currently not in a call with the friend.
'FRIEND_NOT_IN_CALL': 5,
}
TOXAV_ERR_SEND_FRAME = {
# The function returned successfully.
'OK': 0,
# In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL.
'NULL': 1,
# The friend_number passed did not designate a valid friend.
'FRIEND_NOT_FOUND': 2,
# This client is currently not in a call with the friend.
'FRIEND_NOT_IN_CALL': 3,
# Synchronization error occurred.
'SYNC': 4,
# One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling
# rate may be unsupported.
'INVALID': 5,
# Either friend turned off audio or video receiving or we turned off sending for the said payload.
'PAYLOAD_TYPE_DISABLED': 6,
# Failed to push frame through rtp interface.
'RTP_FAILED': 7,
}

23
src/widgets.py Normal file
View File

@ -0,0 +1,23 @@
from PySide import QtGui, QtCore
class DataLabel(QtGui.QLabel):
def paintEvent(self, event):
painter = QtGui.QPainter(self)
metrics = QtGui.QFontMetrics(self.font())
text = metrics.elidedText(self.text(), QtCore.Qt.ElideRight, self.width())
painter.drawText(self.rect(), self.alignment(), text)
class CenteredWidget(QtGui.QWidget):
def __init__(self):
super(CenteredWidget, self).__init__()
self.center()
def center(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())