145 lines
4.6 KiB
Python
145 lines
4.6 KiB
Python
import pyaudio
|
|
import time
|
|
import threading
|
|
import settings
|
|
from toxav_enums import *
|
|
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
|
# TODO: add widget for call
|
|
|
|
CALL_TYPE = {
|
|
'NONE': 0,
|
|
'AUDIO': 1,
|
|
'VIDEO': 2
|
|
}
|
|
|
|
|
|
class AV:
|
|
|
|
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
|
|
|