Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more controls, information and events #6

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 103 additions & 11 deletions tgvoip_pyrogram/file_stream_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,30 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PytgVoIP. If not, see <http://www.gnu.org/licenses/>.

import os
from threading import Thread
from collections import deque
from typing import Union, List, IO

from tgvoip_pyrogram import VoIPOutgoingCall, VoIPIncomingCall, VoIPService
from tgvoip_pyrogram.base_call import VoIPCallBase


class VoIPFileStreamCallMixin(VoIPCallBase):
def __init__(self, *args, **kwargs):
super(VoIPFileStreamCallMixin, self).__init__(*args, **kwargs)
self.input_files = deque()
self.hold_files = deque()
self.current_file_index = 0
self.last_file_name = None
self.current_file = None
self.current_bytes_offset = 0
self.force_seek = False
self.output_file = None
self.ctrl.set_send_audio_frame_callback(self._read_frame)
self.ctrl.set_recv_audio_frame_callback(self._write_frame)
self.file_changed_handlers = []
self.file_progress_handlers = []
self.last_progress_percentage = 0

def __del__(self):
self.clear_play_queue()
Expand Down Expand Up @@ -70,11 +79,19 @@ def clear_play_queue(self):
for f in self.input_files:
f.close()
self.input_files.clear()
self.clear_file_info()

def clear_hold_queue(self):
for f in self.hold_files:
f.close()
self.hold_files.clear()
self.clear_file_info()

def clear_file_info(self):
self.last_file_name = None
self.current_file = None
self.current_file_index = 0
self.current_bytes_offset = 0

def unset_output_file(self):
if self.output_file:
Expand All @@ -83,22 +100,97 @@ def unset_output_file(self):

def _read_frame(self, length: int) -> bytes:
frame = b''
if len(self.input_files):
frame = self.input_files[0].read(length)
if len(frame) != length:
self.input_files[0].close()
self.input_files.popleft()
elif len(self.hold_files):
frame = self.hold_files[0].read(length)
if len(frame) != length:
self.hold_files[0].seek(0)
self.hold_files.rotate(-1)
file_index = self.current_file_index
files = self.hold_files if len(self.hold_files) else self.input_files

if file_index >= len(files):
print('file index unnexistent')
return
self.current_file = file = files[file_index]
if self.force_seek:
file.seek(self.current_bytes_offset)
self.force_seek = False
frame = file.read(length)
self.current_bytes_offset = file.tell()

if not hasattr(file, 'size'):
file.size = os.path.getsize(file.name)

if self.last_file_name != file.name:
self.file_changed()
self.last_file_name = file.name
self.file_progress(self.current_bytes_offset, file.size, (self.current_bytes_offset*100)/file.size)

if len(frame) != length:
self.next_file()
return frame

def _write_frame(self, frame: bytes) -> None:
if self.output_file:
self.output_file.write(frame)

def on_file_changed(self, func: callable) -> callable: # the current file on self.hold_files has changed
self.file_changed_handlers.append(func)
return func

def on_file_progress(self, func: callable) -> callable: # when a frame is sent
self.file_progress_handlers.append(func)
return func

def file_changed(self):
args = (self, self.current_file, self.current_file_index)
for handler in self.file_changed_handlers:
callable(handler) and Thread(target=handler, args=args).start()

def file_progress(self, bytes_offset: int, total_bytes: int, percentage: int):
if self.last_progress_percentage == round(percentage, 1):
return
args = (self, bytes_offset, total_bytes, percentage)
self.last_progress_percentage = round(percentage, 1)
for handler in self.file_progress_handlers:
callable(handler) and Thread(target=handler, args=args).start()

def previous_file(self):
if len(self.input_files):
print('previous_file can only be used with play_on_hold')
return
file_index = self.current_file_index
file = self.hold_files[file_index]
file.seek(0)
self.current_bytes_offset = 0
self.current_file_index = file_index-1 if file_index-1 >= 0 else len(self.hold_files)-1
self.current_file = self.hold_files[self.current_file_index]
return self.current_file

def next_file(self):
file_index = self.current_file_index
if len(self.input_files):
file = self.input_files[file_index]
file.close()
self.input_files.popleft()
self.current_file = self.input_files[self.current_file_index]
elif len(self.hold_files):
file = self.hold_files[file_index]
file.seek(0)
self.current_bytes_offset = 0
self.current_file_index = file_index+1 if file_index+1 < len(self.hold_files) else 0
self.current_file = self.hold_files[self.current_file_index]
return self.current_file

def seek(self, bytes_offset: int, file_index: int = None):
# round to the largest even number lower than or equal to bytes_offset
even,rest = divmod(bytes_offset, 2)
bytes_offset = even*2

files = self.hold_files if len(self.hold_files) else self.input_files
if file_index >= len(files):
print('file index unnexistent')
return
if file_index != None:
self.current_file_index = file_index
self.current_file = files[file_index]
self.current_bytes_offset = bytes_offset
self.force_seek = True

class VoIPOutgoingFileStreamCall(VoIPFileStreamCallMixin, VoIPOutgoingCall):
pass
Expand Down